[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/TagIt.yml",
    "content": "on:\n  push:\n    tags:\n      # Only match TagIt tags, which always start with this prefix\n      - 'v20*'\n\nname: TagIt\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    permissions:\n      contents: write  # for actions/create-release to create a release\n    name: Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n      - name: Archive project\n        id: archive_project\n        run: |\n          FILE_NAME=${GITHUB_REPOSITORY#*/}-${GITHUB_REF##*/}\n          git archive ${{ github.ref }} -o ${FILE_NAME}.zip\n          git archive ${{ github.ref }} -o ${FILE_NAME}.tar.gz\n          echo \"file_name=${FILE_NAME}\" >> $GITHUB_OUTPUT\n      - name: Compute digests\n        id: compute_digests\n        run: |\n          echo \"tgz_256=$(openssl dgst -sha256 ${{ steps.archive_project.outputs.file_name }}.tar.gz)\" >> $GITHUB_OUTPUT\n          echo \"tgz_512=$(openssl dgst -sha512 ${{ steps.archive_project.outputs.file_name }}.tar.gz)\" >> $GITHUB_OUTPUT\n          echo \"zip_256=$(openssl dgst -sha256 ${{ steps.archive_project.outputs.file_name }}.zip)\" >> $GITHUB_OUTPUT\n          echo \"zip_512=$(openssl dgst -sha512 ${{ steps.archive_project.outputs.file_name }}.zip)\" >> $GITHUB_OUTPUT\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: ${{ github.ref }}\n          body: |\n            Automated release from TagIt\n            <details>\n              <summary>File Hashes</summary>\n              <ul>\n                <li>${{ steps.compute_digests.outputs.zip_256 }}</li>\n                <li>${{ steps.compute_digests.outputs.zip_512 }}</li>\n                <li>${{ steps.compute_digests.outputs.tgz_256 }}</li>\n                <li>${{ steps.compute_digests.outputs.tgz_512 }}</li>\n              </ul>\n            </details>\n          draft: false\n          prerelease: false\n      - name: Upload zip\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./${{ steps.archive_project.outputs.file_name }}.zip\n          asset_name: ${{ steps.archive_project.outputs.file_name }}.zip\n          asset_content_type: application/zip\n      - name: Upload tar.gz\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./${{ steps.archive_project.outputs.file_name }}.tar.gz\n          asset_name: ${{ steps.archive_project.outputs.file_name }}.tar.gz\n          asset_content_type: application/gzip\n"
  },
  {
    "path": ".github/workflows/close_stale.yml",
    "content": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - uses: actions/stale@v9\n        with:\n          days-before-issue-stale: -1\n          days-before-issue-close: 14\n          stale-issue-label: \"need-input\"\n          stale-issue-message: \"This issue is stale because it has been open too long with no activity.\"\n          close-issue-message: \"This issue was closed because it has been inactive for two weeks since being marked as need-input.\"\n          days-before-pr-stale: -1\n          days-before-pr-close: -1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/getdeps_linux.yml",
    "content": "# This file was @generated by getdeps.py\n\nname: linux\n\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\n\npermissions:\n  contents: read  #  to fetch code (actions/checkout)\n\njobs:\n  build:\n    runs-on: ubuntu-22.04\n    steps:\n    - uses: actions/checkout@v6\n    - name: Update system package info\n      run: sudo --preserve-env=http_proxy apt-get update\n    - name: Install system deps\n      run: sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive proxygen && sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive patchelf\n    - id: paths\n      name: Query paths\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages query-paths --recursive --src-dir=. proxygen  >> \"$GITHUB_OUTPUT\"\n    - name: Fetch ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests ninja\n    - name: Fetch cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests cmake\n    - name: Fetch c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests c-ares\n    - name: Fetch zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zlib\n    - name: Fetch zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zstd\n    - name: Fetch boost\n      if: ${{ steps.paths.outputs.boost_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests boost\n    - name: Fetch double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests double-conversion\n    - name: Fetch fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fast_float\n    - name: Fetch fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fmt\n    - name: Fetch gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gflags\n    - name: Fetch glog\n      if: ${{ steps.paths.outputs.glog_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests glog\n    - name: Fetch googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests googletest\n    - name: Fetch libaio\n      if: ${{ steps.paths.outputs.libaio_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libaio\n    - name: Fetch libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libdwarf\n    - name: Fetch libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libevent\n    - name: Fetch lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests lz4\n    - name: Fetch snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests snappy\n    - name: Fetch openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests openssl\n    - name: Fetch liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests liboqs\n    - name: Fetch autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests autoconf\n    - name: Fetch automake\n      if: ${{ steps.paths.outputs.automake_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests automake\n    - name: Fetch libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libtool\n    - name: Fetch gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gperf\n    - name: Fetch libiberty\n      if: ${{ steps.paths.outputs.libiberty_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libiberty\n    - name: Fetch libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libsodium\n    - name: Fetch libunwind\n      if: ${{ steps.paths.outputs.libunwind_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libunwind\n    - name: Fetch xz\n      if: ${{ steps.paths.outputs.xz_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests xz\n    - name: Fetch folly\n      if: ${{ steps.paths.outputs.folly_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests folly\n    - name: Fetch fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fizz\n    - name: Fetch mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests mvfst\n    - name: Fetch wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests wangle\n    - name: Restore ninja from cache\n      id: restore_ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.ninja_INSTALL }}\n       key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install\n    - name: Build ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests ninja\n    - name: Save ninja to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.ninja_INSTALL }}\n       key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install\n    - name: Restore cmake from cache\n      id: restore_cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.cmake_INSTALL }}\n       key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install\n    - name: Build cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests cmake\n    - name: Save cmake to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.cmake_INSTALL }}\n       key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install\n    - name: Restore c-ares from cache\n      id: restore_c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.c-ares_INSTALL }}\n       key: ${{ steps.paths.outputs.c-ares_CACHE_KEY }}-install\n    - name: Build c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE && ! steps.restore_c-ares.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests c-ares\n    - name: Save c-ares to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.c-ares_SOURCE && ! steps.restore_c-ares.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.c-ares_INSTALL }}\n       key: ${{ steps.paths.outputs.c-ares_CACHE_KEY }}-install\n    - name: Restore zlib from cache\n      id: restore_zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.zlib_INSTALL }}\n       key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install\n    - name: Build zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests zlib\n    - name: Save zlib to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.zlib_INSTALL }}\n       key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install\n    - name: Restore zstd from cache\n      id: restore_zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.zstd_INSTALL }}\n       key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install\n    - name: Build zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests zstd\n    - name: Save zstd to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.zstd_INSTALL }}\n       key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install\n    - name: Restore boost from cache\n      id: restore_boost\n      if: ${{ steps.paths.outputs.boost_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.boost_INSTALL }}\n       key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install\n    - name: Build boost\n      if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests boost\n    - name: Save boost to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.boost_INSTALL }}\n       key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install\n    - name: Restore double-conversion from cache\n      id: restore_double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.double-conversion_INSTALL }}\n       key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install\n    - name: Build double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests double-conversion\n    - name: Save double-conversion to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.double-conversion_INSTALL }}\n       key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install\n    - name: Restore fast_float from cache\n      id: restore_fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fast_float_INSTALL }}\n       key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install\n    - name: Build fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fast_float\n    - name: Save fast_float to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fast_float_INSTALL }}\n       key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install\n    - name: Restore fmt from cache\n      id: restore_fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fmt_INSTALL }}\n       key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install\n    - name: Build fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fmt\n    - name: Save fmt to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fmt_INSTALL }}\n       key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install\n    - name: Restore gflags from cache\n      id: restore_gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.gflags_INSTALL }}\n       key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install\n    - name: Build gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests gflags\n    - name: Save gflags to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.gflags_INSTALL }}\n       key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install\n    - name: Restore glog from cache\n      id: restore_glog\n      if: ${{ steps.paths.outputs.glog_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.glog_INSTALL }}\n       key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install\n    - name: Build glog\n      if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests glog\n    - name: Save glog to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.glog_INSTALL }}\n       key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install\n    - name: Restore googletest from cache\n      id: restore_googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.googletest_INSTALL }}\n       key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install\n    - name: Build googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests googletest\n    - name: Save googletest to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.googletest_INSTALL }}\n       key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install\n    - name: Restore libaio from cache\n      id: restore_libaio\n      if: ${{ steps.paths.outputs.libaio_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libaio_INSTALL }}\n       key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install\n    - name: Build libaio\n      if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libaio\n    - name: Save libaio to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libaio_INSTALL }}\n       key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install\n    - name: Restore libdwarf from cache\n      id: restore_libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libdwarf_INSTALL }}\n       key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install\n    - name: Build libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libdwarf\n    - name: Save libdwarf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libdwarf_INSTALL }}\n       key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install\n    - name: Restore libevent from cache\n      id: restore_libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libevent_INSTALL }}\n       key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install\n    - name: Build libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libevent\n    - name: Save libevent to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libevent_INSTALL }}\n       key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install\n    - name: Restore lz4 from cache\n      id: restore_lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.lz4_INSTALL }}\n       key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install\n    - name: Build lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests lz4\n    - name: Save lz4 to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.lz4_INSTALL }}\n       key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install\n    - name: Restore snappy from cache\n      id: restore_snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.snappy_INSTALL }}\n       key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install\n    - name: Build snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests snappy\n    - name: Save snappy to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.snappy_INSTALL }}\n       key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install\n    - name: Restore openssl from cache\n      id: restore_openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.openssl_INSTALL }}\n       key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install\n    - name: Build openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests openssl\n    - name: Save openssl to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.openssl_INSTALL }}\n       key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install\n    - name: Restore liboqs from cache\n      id: restore_liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.liboqs_INSTALL }}\n       key: ${{ steps.paths.outputs.liboqs_CACHE_KEY }}-install\n    - name: Build liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE && ! steps.restore_liboqs.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests liboqs\n    - name: Save liboqs to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.liboqs_SOURCE && ! steps.restore_liboqs.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.liboqs_INSTALL }}\n       key: ${{ steps.paths.outputs.liboqs_CACHE_KEY }}-install\n    - name: Restore autoconf from cache\n      id: restore_autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.autoconf_INSTALL }}\n       key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install\n    - name: Build autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests autoconf\n    - name: Save autoconf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.autoconf_INSTALL }}\n       key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install\n    - name: Restore automake from cache\n      id: restore_automake\n      if: ${{ steps.paths.outputs.automake_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.automake_INSTALL }}\n       key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install\n    - name: Build automake\n      if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests automake\n    - name: Save automake to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.automake_INSTALL }}\n       key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install\n    - name: Restore libtool from cache\n      id: restore_libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libtool_INSTALL }}\n       key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install\n    - name: Build libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libtool\n    - name: Save libtool to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libtool_INSTALL }}\n       key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install\n    - name: Restore gperf from cache\n      id: restore_gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.gperf_INSTALL }}\n       key: ${{ steps.paths.outputs.gperf_CACHE_KEY }}-install\n    - name: Build gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE && ! steps.restore_gperf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests gperf\n    - name: Save gperf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.gperf_SOURCE && ! steps.restore_gperf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.gperf_INSTALL }}\n       key: ${{ steps.paths.outputs.gperf_CACHE_KEY }}-install\n    - name: Restore libiberty from cache\n      id: restore_libiberty\n      if: ${{ steps.paths.outputs.libiberty_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libiberty_INSTALL }}\n       key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install\n    - name: Build libiberty\n      if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libiberty\n    - name: Save libiberty to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libiberty_INSTALL }}\n       key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install\n    - name: Restore libsodium from cache\n      id: restore_libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libsodium_INSTALL }}\n       key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install\n    - name: Build libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libsodium\n    - name: Save libsodium to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libsodium_INSTALL }}\n       key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install\n    - name: Restore libunwind from cache\n      id: restore_libunwind\n      if: ${{ steps.paths.outputs.libunwind_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libunwind_INSTALL }}\n       key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install\n    - name: Build libunwind\n      if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libunwind\n    - name: Save libunwind to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libunwind_INSTALL }}\n       key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install\n    - name: Restore xz from cache\n      id: restore_xz\n      if: ${{ steps.paths.outputs.xz_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.xz_INSTALL }}\n       key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install\n    - name: Build xz\n      if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests xz\n    - name: Save xz to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.xz_INSTALL }}\n       key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install\n    - name: Restore folly from cache\n      id: restore_folly\n      if: ${{ steps.paths.outputs.folly_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.folly_INSTALL }}\n       key: ${{ steps.paths.outputs.folly_CACHE_KEY }}-install\n    - name: Build folly\n      if: ${{ steps.paths.outputs.folly_SOURCE && ! steps.restore_folly.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests folly\n    - name: Save folly to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.folly_SOURCE && ! steps.restore_folly.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.folly_INSTALL }}\n       key: ${{ steps.paths.outputs.folly_CACHE_KEY }}-install\n    - name: Restore fizz from cache\n      id: restore_fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fizz_INSTALL }}\n       key: ${{ steps.paths.outputs.fizz_CACHE_KEY }}-install\n    - name: Build fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE && ! steps.restore_fizz.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fizz\n    - name: Save fizz to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fizz_SOURCE && ! steps.restore_fizz.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fizz_INSTALL }}\n       key: ${{ steps.paths.outputs.fizz_CACHE_KEY }}-install\n    - name: Restore mvfst from cache\n      id: restore_mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.mvfst_INSTALL }}\n       key: ${{ steps.paths.outputs.mvfst_CACHE_KEY }}-install\n    - name: Build mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE && ! steps.restore_mvfst.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests mvfst\n    - name: Save mvfst to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.mvfst_SOURCE && ! steps.restore_mvfst.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.mvfst_INSTALL }}\n       key: ${{ steps.paths.outputs.mvfst_CACHE_KEY }}-install\n    - name: Restore wangle from cache\n      id: restore_wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.wangle_INSTALL }}\n       key: ${{ steps.paths.outputs.wangle_CACHE_KEY }}-install\n    - name: Build wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE && ! steps.restore_wangle.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests wangle\n    - name: Save wangle to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.wangle_SOURCE && ! steps.restore_wangle.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.wangle_INSTALL }}\n       key: ${{ steps.paths.outputs.wangle_CACHE_KEY }}-install\n    - name: Build proxygen\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --src-dir=. proxygen --project-install-prefix proxygen:/usr/local\n    - name: Copy artifacts\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fixup-dyn-deps --strip --src-dir=. proxygen _artifacts/linux --project-install-prefix proxygen:/usr/local --final-install-prefix /usr/local\n    - uses: actions/upload-artifact@v6\n      with:\n        name: proxygen\n        path: _artifacts\n    - name: Test proxygen\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages test --src-dir=. proxygen --project-install-prefix proxygen:/usr/local\n"
  },
  {
    "path": ".github/workflows/getdeps_mac.yml",
    "content": "# This file was @generated by getdeps.py\n\nname: mac\n\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\n\npermissions:\n  contents: read  #  to fetch code (actions/checkout)\n\njobs:\n  build:\n    runs-on: macOS-latest\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install system deps\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive proxygen\n    - id: paths\n      name: Query paths\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages query-paths --recursive --src-dir=. proxygen  >> \"$GITHUB_OUTPUT\"\n    - name: Fetch ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests ninja\n    - name: Fetch cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests cmake\n    - name: Fetch c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests c-ares\n    - name: Fetch zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zlib\n    - name: Fetch zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zstd\n    - name: Fetch boost\n      if: ${{ steps.paths.outputs.boost_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests boost\n    - name: Fetch double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests double-conversion\n    - name: Fetch fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fast_float\n    - name: Fetch fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fmt\n    - name: Fetch gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gflags\n    - name: Fetch glog\n      if: ${{ steps.paths.outputs.glog_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests glog\n    - name: Fetch googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests googletest\n    - name: Fetch libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libdwarf\n    - name: Fetch lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests lz4\n    - name: Fetch openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests openssl\n    - name: Fetch snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests snappy\n    - name: Fetch libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libevent\n    - name: Fetch liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests liboqs\n    - name: Fetch autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests autoconf\n    - name: Fetch automake\n      if: ${{ steps.paths.outputs.automake_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests automake\n    - name: Fetch libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libtool\n    - name: Fetch gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gperf\n    - name: Fetch libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libsodium\n    - name: Fetch xz\n      if: ${{ steps.paths.outputs.xz_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests xz\n    - name: Fetch folly\n      if: ${{ steps.paths.outputs.folly_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests folly\n    - name: Fetch fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fizz\n    - name: Fetch mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests mvfst\n    - name: Fetch wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests wangle\n    - name: Restore ninja from cache\n      id: restore_ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.ninja_INSTALL }}\n       key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install\n    - name: Build ninja\n      if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests ninja\n    - name: Save ninja to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.ninja_INSTALL }}\n       key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install\n    - name: Restore cmake from cache\n      id: restore_cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.cmake_INSTALL }}\n       key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install\n    - name: Build cmake\n      if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests cmake\n    - name: Save cmake to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.cmake_INSTALL }}\n       key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install\n    - name: Restore c-ares from cache\n      id: restore_c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.c-ares_INSTALL }}\n       key: ${{ steps.paths.outputs.c-ares_CACHE_KEY }}-install\n    - name: Build c-ares\n      if: ${{ steps.paths.outputs.c-ares_SOURCE && ! steps.restore_c-ares.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests c-ares\n    - name: Save c-ares to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.c-ares_SOURCE && ! steps.restore_c-ares.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.c-ares_INSTALL }}\n       key: ${{ steps.paths.outputs.c-ares_CACHE_KEY }}-install\n    - name: Restore zlib from cache\n      id: restore_zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.zlib_INSTALL }}\n       key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install\n    - name: Build zlib\n      if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests zlib\n    - name: Save zlib to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.zlib_INSTALL }}\n       key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install\n    - name: Restore zstd from cache\n      id: restore_zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.zstd_INSTALL }}\n       key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install\n    - name: Build zstd\n      if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests zstd\n    - name: Save zstd to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.zstd_INSTALL }}\n       key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install\n    - name: Restore boost from cache\n      id: restore_boost\n      if: ${{ steps.paths.outputs.boost_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.boost_INSTALL }}\n       key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install\n    - name: Build boost\n      if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests boost\n    - name: Save boost to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.boost_INSTALL }}\n       key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install\n    - name: Restore double-conversion from cache\n      id: restore_double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.double-conversion_INSTALL }}\n       key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install\n    - name: Build double-conversion\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests double-conversion\n    - name: Save double-conversion to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.double-conversion_INSTALL }}\n       key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install\n    - name: Restore fast_float from cache\n      id: restore_fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fast_float_INSTALL }}\n       key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install\n    - name: Build fast_float\n      if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fast_float\n    - name: Save fast_float to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fast_float_INSTALL }}\n       key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install\n    - name: Restore fmt from cache\n      id: restore_fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fmt_INSTALL }}\n       key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install\n    - name: Build fmt\n      if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fmt\n    - name: Save fmt to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fmt_INSTALL }}\n       key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install\n    - name: Restore gflags from cache\n      id: restore_gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.gflags_INSTALL }}\n       key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install\n    - name: Build gflags\n      if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests gflags\n    - name: Save gflags to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.gflags_INSTALL }}\n       key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install\n    - name: Restore glog from cache\n      id: restore_glog\n      if: ${{ steps.paths.outputs.glog_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.glog_INSTALL }}\n       key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install\n    - name: Build glog\n      if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests glog\n    - name: Save glog to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.glog_INSTALL }}\n       key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install\n    - name: Restore googletest from cache\n      id: restore_googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.googletest_INSTALL }}\n       key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install\n    - name: Build googletest\n      if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests googletest\n    - name: Save googletest to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.googletest_INSTALL }}\n       key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install\n    - name: Restore libdwarf from cache\n      id: restore_libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libdwarf_INSTALL }}\n       key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install\n    - name: Build libdwarf\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libdwarf\n    - name: Save libdwarf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libdwarf_INSTALL }}\n       key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install\n    - name: Restore lz4 from cache\n      id: restore_lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.lz4_INSTALL }}\n       key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install\n    - name: Build lz4\n      if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests lz4\n    - name: Save lz4 to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.lz4_INSTALL }}\n       key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install\n    - name: Restore openssl from cache\n      id: restore_openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.openssl_INSTALL }}\n       key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install\n    - name: Build openssl\n      if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests openssl\n    - name: Save openssl to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.openssl_INSTALL }}\n       key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install\n    - name: Restore snappy from cache\n      id: restore_snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.snappy_INSTALL }}\n       key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install\n    - name: Build snappy\n      if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests snappy\n    - name: Save snappy to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.snappy_INSTALL }}\n       key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install\n    - name: Restore libevent from cache\n      id: restore_libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libevent_INSTALL }}\n       key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install\n    - name: Build libevent\n      if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libevent\n    - name: Save libevent to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libevent_INSTALL }}\n       key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install\n    - name: Restore liboqs from cache\n      id: restore_liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.liboqs_INSTALL }}\n       key: ${{ steps.paths.outputs.liboqs_CACHE_KEY }}-install\n    - name: Build liboqs\n      if: ${{ steps.paths.outputs.liboqs_SOURCE && ! steps.restore_liboqs.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests liboqs\n    - name: Save liboqs to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.liboqs_SOURCE && ! steps.restore_liboqs.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.liboqs_INSTALL }}\n       key: ${{ steps.paths.outputs.liboqs_CACHE_KEY }}-install\n    - name: Restore autoconf from cache\n      id: restore_autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.autoconf_INSTALL }}\n       key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install\n    - name: Build autoconf\n      if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests autoconf\n    - name: Save autoconf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.autoconf_INSTALL }}\n       key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install\n    - name: Restore automake from cache\n      id: restore_automake\n      if: ${{ steps.paths.outputs.automake_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.automake_INSTALL }}\n       key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install\n    - name: Build automake\n      if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests automake\n    - name: Save automake to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.automake_INSTALL }}\n       key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install\n    - name: Restore libtool from cache\n      id: restore_libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libtool_INSTALL }}\n       key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install\n    - name: Build libtool\n      if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libtool\n    - name: Save libtool to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libtool_INSTALL }}\n       key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install\n    - name: Restore gperf from cache\n      id: restore_gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.gperf_INSTALL }}\n       key: ${{ steps.paths.outputs.gperf_CACHE_KEY }}-install\n    - name: Build gperf\n      if: ${{ steps.paths.outputs.gperf_SOURCE && ! steps.restore_gperf.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests gperf\n    - name: Save gperf to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.gperf_SOURCE && ! steps.restore_gperf.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.gperf_INSTALL }}\n       key: ${{ steps.paths.outputs.gperf_CACHE_KEY }}-install\n    - name: Restore libsodium from cache\n      id: restore_libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.libsodium_INSTALL }}\n       key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install\n    - name: Build libsodium\n      if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests libsodium\n    - name: Save libsodium to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.libsodium_INSTALL }}\n       key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install\n    - name: Restore xz from cache\n      id: restore_xz\n      if: ${{ steps.paths.outputs.xz_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.xz_INSTALL }}\n       key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install\n    - name: Build xz\n      if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests xz\n    - name: Save xz to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.xz_INSTALL }}\n       key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install\n    - name: Restore folly from cache\n      id: restore_folly\n      if: ${{ steps.paths.outputs.folly_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.folly_INSTALL }}\n       key: ${{ steps.paths.outputs.folly_CACHE_KEY }}-install\n    - name: Build folly\n      if: ${{ steps.paths.outputs.folly_SOURCE && ! steps.restore_folly.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests folly\n    - name: Save folly to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.folly_SOURCE && ! steps.restore_folly.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.folly_INSTALL }}\n       key: ${{ steps.paths.outputs.folly_CACHE_KEY }}-install\n    - name: Restore fizz from cache\n      id: restore_fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.fizz_INSTALL }}\n       key: ${{ steps.paths.outputs.fizz_CACHE_KEY }}-install\n    - name: Build fizz\n      if: ${{ steps.paths.outputs.fizz_SOURCE && ! steps.restore_fizz.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests fizz\n    - name: Save fizz to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.fizz_SOURCE && ! steps.restore_fizz.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.fizz_INSTALL }}\n       key: ${{ steps.paths.outputs.fizz_CACHE_KEY }}-install\n    - name: Restore mvfst from cache\n      id: restore_mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.mvfst_INSTALL }}\n       key: ${{ steps.paths.outputs.mvfst_CACHE_KEY }}-install\n    - name: Build mvfst\n      if: ${{ steps.paths.outputs.mvfst_SOURCE && ! steps.restore_mvfst.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests mvfst\n    - name: Save mvfst to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.mvfst_SOURCE && ! steps.restore_mvfst.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.mvfst_INSTALL }}\n       key: ${{ steps.paths.outputs.mvfst_CACHE_KEY }}-install\n    - name: Restore wangle from cache\n      id: restore_wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE }}\n      uses: actions/cache/restore@v4\n      with:\n       path: ${{ steps.paths.outputs.wangle_INSTALL }}\n       key: ${{ steps.paths.outputs.wangle_CACHE_KEY }}-install\n    - name: Build wangle\n      if: ${{ steps.paths.outputs.wangle_SOURCE && ! steps.restore_wangle.outputs.cache-hit }}\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --no-tests wangle\n    - name: Save wangle to cache\n      uses: actions/cache/save@v4\n      if: ${{ steps.paths.outputs.wangle_SOURCE && ! steps.restore_wangle.outputs.cache-hit }}\n      with:\n       path: ${{ steps.paths.outputs.wangle_INSTALL }}\n       key: ${{ steps.paths.outputs.wangle_CACHE_KEY }}-install\n    - name: Build proxygen\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --src-dir=. proxygen --project-install-prefix proxygen:/usr/local\n    - name: Copy artifacts\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fixup-dyn-deps --src-dir=. proxygen _artifacts/mac --project-install-prefix proxygen:/usr/local --final-install-prefix /usr/local\n    - uses: actions/upload-artifact@v6\n      with:\n        name: proxygen\n        path: _artifacts\n    - name: Test proxygen\n      run: python3 build/fbcode_builder/getdeps.py --allow-system-packages test --src-dir=. proxygen --project-install-prefix proxygen:/usr/local\n"
  },
  {
    "path": ".github/workflows/publish_mvfst_interop.yml",
    "content": "# Following instruction from https://docs.github.com/en/actions/publishing-packages/publishing-docker-images\n\nname: Publish mvfst interop image\n\non:\n  push:\n    tags:\n      # Build a new image weekly with each TagIt release\n      - 'v20*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}/mvfst-interop\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v5\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0\n        with:\n          context: .\n          file: proxygen/httpserver/samples/hq/quic-interop/Dockerfile\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n\\#*\\#\n\n# Build artifacts\n.libs\n*.log\n*.trs\n*.o\n*.lo\n*.a\n*.la\ngen-cpp\ngen-cpp2\n\n# Generated files\n/proxygen/lib/http/HTTPCommonHeaders.cpp\n/proxygen/lib/http/HTTPCommonHeaders.h\n/proxygen/lib/utils/TraceEventType.cpp\n/proxygen/lib/utils/TraceEventType.h\n/proxygen/lib/utils/TraceFieldType.cpp\n/proxygen/lib/utils/TraceFieldType.h\n/proxygen/lib/stats/StatsWrapper.h\n_build/\n\n# common editor artifacts\n*~\n*.vscode*\n\n# Generated document\nhtml/\nlatex/\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\ncmake_minimum_required(VERSION 3.10)\ninclude(CheckCXXCompilerFlag)\n\nproject(\n    proxygen\n)\n\n# Set for FetchContent to skip find_package(proxygen)\nset(proxygen_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\"\n  CACHE INTERNAL \"proxygen source directory\")\n\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\n\nset(CMAKE_MODULE_PATH\n  \"${CMAKE_CURRENT_SOURCE_DIR}/cmake\"\n  # for in-fbsource builds\n  \"${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake\"\n  # For shipit-transformed builds\n  \"${CMAKE_CURRENT_SOURCE_DIR}/build/fbcode_builder/CMake\"\n  ${CMAKE_MODULE_PATH})\n\noption(BUILD_SHARED_LIBS\n  \"If enabled, build proxygen as a shared library.  \\\n  This is generally discouraged, since proxygen does not commit to having \\\n  a stable ABI.\"\n  OFF\n)\noption(BUILD_SAMPLES\n  \"If enabled, proxygen will build various examples/samples\"\n  ON\n)\n# Mark BUILD_SHARED_LIBS as an \"advanced\" option, since enabling it\n# is generally discouraged.\nmark_as_advanced(BUILD_SHARED_LIBS)\n\ninclude(FBBuildOptions)\nfb_activate_static_library_option()\n\n# PROXYGEN_FBCODE_ROOT is where the top level proxygen/ directory resides, so\n# an #include <proxygen/path/to/file> will resolve to\n# $PROXYGEN_FBCODE_ROOT/proxygen/path/to/file on disk\nset(PROXYGEN_FBCODE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})\n\n# Similarly, PROXYGEN_GENERATED_ROOT is where the top level proxygen/ directory\n# resides for generated files, so a #include <proxygen/path/to/generated/file>\n# will be at $PROXYGEN_GENERATED_ROOT/proxygen/path/to/generated/file\nset(PROXYGEN_GENERATED_ROOT ${CMAKE_CURRENT_BINARY_DIR}/generated)\nfile(MAKE_DIRECTORY ${PROXYGEN_GENERATED_ROOT})\n\n# Build-time program requirements.\nif(WIN32)\n    find_program(PROXYGEN_PYTHON python)\nelse()\n    find_program(PROXYGEN_PYTHON python3)\nendif()\n\nif(NOT PROXYGEN_PYTHON)\n    message(FATAL_ERROR \"python is required for the proxygen build\")\nendif()\n\nfind_program(PROXYGEN_GPERF gperf)\nif(NOT PROXYGEN_GPERF)\n    message(FATAL_ERROR \"gperf is required for the proxygen build\")\nendif()\n\n# Dependencies\n#\n# IMPORTANT: If you change this, make the analogous update in:\n#   cmake/proxygen-config.cmake.in\nfind_package(fmt REQUIRED)\n# If deps are being built from source (FetchContent), skip find_package\nif (NOT DEFINED folly_SOURCE_DIR)\n  find_package(folly REQUIRED)\nendif()\nif (NOT DEFINED fizz_SOURCE_DIR)\n  find_package(fizz REQUIRED)\nendif()\nif (NOT DEFINED wangle_SOURCE_DIR)\n  find_package(wangle REQUIRED)\nendif()\nif (NOT DEFINED mvfst_SOURCE_DIR)\n  find_package(mvfst REQUIRED)\nendif()\nfind_package(Zstd REQUIRED)\nfind_package(ZLIB REQUIRED)\nfind_package(OpenSSL REQUIRED)\nfind_package(Threads)\nfind_package(Cares REQUIRED)\nfind_package(Glog REQUIRED)\n\n# Propagate glog's required compile definition to all proxygen targets.\n# glog 0.7+ requires GLOG_USE_GLOG_EXPORT but since glog::glog is linked\n# PRIVATE on the monolithic library, OBJECT libraries don't inherit it.\nget_target_property(_glog_defs glog::glog INTERFACE_COMPILE_DEFINITIONS)\nif(_glog_defs)\n  add_compile_definitions(${_glog_defs})\nendif()\n\nlist(APPEND\n    _PROXYGEN_COMMON_COMPILE_OPTIONS\n    -Wall\n    -Wextra\n)\n\nCHECK_CXX_COMPILER_FLAG(-Wnoexcept-type COMPILER_HAS_W_NOEXCEPT_TYPE)\nif (COMPILER_HAS_W_NOEXCEPT_TYPE)\n  list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-noexcept-type)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wunused-parameter COMPILER_HAS_W_UNUSED_PARAMETER)\nif (COMPILER_HAS_W_UNUSED_PARAMETER)\n  list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-unused-parameter)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wmissing-field-initializers COMPILER_HAS_W_MISSING_FIELD_INITIALIZERS)\nif (COMPILER_HAS_W_MISSING_FIELD_INITIALIZERS)\n  list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-missing-field-initializers)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wnullability-completeness COMPILER_HAS_W_NULLABILITY_COMPLETENESS)\nif (COMPILER_HAS_W_NULLABILITY_COMPLETENESS)\n    list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-nullability-completeness)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wdeprecated-register COMPILER_HAS_W_DEPRECATED_REGISTER)\nif (COMPILER_HAS_W_DEPRECATED_REGISTER)\n    list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-deprecated-register)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wregister COMPILER_HAS_W_REGISTER)\nif (COMPILER_HAS_W_REGISTER)\n    list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-register)\nendif()\nCHECK_CXX_COMPILER_FLAG(-Wunused-value COMPILER_HAS_W_UNUSED_VALUE)\nif (COMPILER_HAS_W_UNUSED_VALUE)\n    list(APPEND _PROXYGEN_COMMON_COMPILE_OPTIONS -Wno-unused-value)\nendif()\n\nSET(GFLAG_DEPENDENCIES \"\")\nSET(PROXYGEN_EXTRA_LINK_LIBRARIES \"\")\nSET(PROXYGEN_EXTRA_INCLUDE_DIRECTORIES \"\")\n\nfind_package(gflags CONFIG QUIET)\nif (gflags_FOUND)\n  message(\"module path: ${CMAKE_MODULE_PATH}\")\n  message(STATUS \"Found gflags from package config\")\n  if (TARGET gflags-shared)\n    list(APPEND GFLAG_DEPENDENCIES gflags-shared)\n  elseif (TARGET gflags)\n    list(APPEND GFLAG_DEPENDENCIES gflags)\n  else()\n    message(FATAL_ERROR\n            \"Unable to determine the target name for the GFlags package.\")\n  endif()\n  list(APPEND CMAKE_REQUIRED_LIBRARIES ${GFLAGS_LIBRARIES})\n  list(APPEND CMAKE_REQUIRED_INCLUDES ${GFLAGS_INCLUDE_DIR})\nelse()\n  find_package(Gflags REQUIRED MODULE)\n  list(APPEND PROXYGEN_EXTRA_LINK_LIBRARIES ${LIBGFLAGS_LIBRARY})\n  list(APPEND PROXYGEN_EXTRA_INCLUDE_DIRECTORIES ${LIBGFLAGS_INCLUDE_DIR})\n  list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBGFLAGS_LIBRARY})\n  list(APPEND CMAKE_REQUIRED_INCLUDES ${LIBGFLAGS_INCLUDE_DIR})\nendif()\n\ninclude(ProxygenTest)\ninclude(ProxygenFunctions)\n\n# Set PROXYGEN_DIR for proxygen_add_library to compute target names\nset(PROXYGEN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proxygen)\n\nadd_subdirectory(proxygen)\n\n# Resolve deferred dependencies for granular libraries\n# (monolithic proxygen target is manually maintained in proxygen/lib/CMakeLists.txt)\nproxygen_resolve_deferred_dependencies()\n\nif (NOT DEFINED LIB_INSTALL_DIR)\n    set(LIB_INSTALL_DIR \"lib\")\nendif()\n\nif (NOT DEFINED INCLUDE_INSTALL_DIR)\n    set(INCLUDE_INSTALL_DIR \"include\")\nendif()\n\nif (NOT DEFINED CMAKE_INSTALL_DIR)\n    set(CMAKE_INSTALL_DIR \"${LIB_INSTALL_DIR}/cmake/proxygen/\")\nendif()\n\ninstall(\n    EXPORT proxygen-exports\n    FILE proxygen-targets.cmake\n    NAMESPACE proxygen::\n    DESTINATION ${CMAKE_INSTALL_DIR}\n)\ninclude(CMakePackageConfigHelpers)\nconfigure_package_config_file(\n    cmake/proxygen-config.cmake.in\n    ${CMAKE_CURRENT_BINARY_DIR}/proxygen-config.cmake\n    INSTALL_DESTINATION ${CMAKE_INSTALL_DIR}\n)\ninstall(\n    FILES ${CMAKE_CURRENT_BINARY_DIR}/proxygen-config.cmake\n    DESTINATION ${CMAKE_INSTALL_DIR}\n)\n\n# uninstall target\nif(NOT TARGET uninstall)\n    configure_file(\n        \"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in\"\n        \"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake\"\n        IMMEDIATE @ONLY)\n\n    add_custom_target(uninstall\n        COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)\nendif()\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to make participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies within all project spaces, and it also applies when\nan individual is representing the project or its community in public spaces.\nExamples of representing a project or community include using an official\nproject e-mail address, posting via an official social media account, or acting\nas an appointed representative at an online or offline event. Representation of\na project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at <opensource-conduct@fb.com>. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Proxygen\nHere's a quick rundown of how to contribute to this project.\n\n## Code of Conduct\nThe code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md)\n\n## Our Development Process\nWe develop on a private branch internally at Facebook. We regularly update\nthis github project with the changes from the internal repo. External pull\nrequests are cherry-picked into our repo and then pushed back out.\n\n## Pull Requests\nWe actively welcome your pull requests.\n\n1. Fork the repo and create your branch from `main`.\n1. If you've added code that should be tested, add tests\n1. If you've changed APIs, update the documentation.\n1. Ensure the test suite passes.\n1. Make sure your code lints.\n1. If you haven't already, complete the Contributor License Agreement (\"CLA\").\n\n## Contributor License Agreement (\"CLA\")\nIn order to accept your pull request, we need you to submit a CLA. You\nonly need\nto do this once to work on any of Facebook's open source projects.\n\nComplete your CLA here: <https://code.facebook.com/cla>\n\n## Issues\nWe use GitHub issues to track public bugs. Please ensure your description\nis clear and has sufficient instructions to be able to reproduce the issue.\n\nFacebook has a [bounty program](https://www.facebook.com/whitehat/) for\nthe safe disclosure of security bugs. In those cases, please go through\nthe process outlined on that page and do not file a public issue.\n\n## Coding Style\n* 2 spaces for indentation rather than tabs\n* 80 character line length\n* Use `Type* foo` not `Type *foo`.\n* Align parameters passed to functions.\n* Prefer `std::make_unique<Foo>` to `new Foo`. In general, we discourage\nuse of raw `new` or `delete`.\n\n## License\nBy contributing to Proxygen, you agree that your contributions will be\nlicensed under its BSD license.\n"
  },
  {
    "path": "Doxyfile",
    "content": "# Doxyfile 1.8.5\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = proxygen\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify an logo or icon that is included in\n# the documentation. The maximum height of the logo should not exceed 55 pixels\n# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo\n# to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       =\n\n# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\n#### OPTION GENERATED IN generate_doxygen.sh\n\nCREATE_SUBDIRS         = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-\n# Traditional, Croatian, Czech, Danish, Dutch, English, Esperanto, Farsi,\n# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en,\n# Korean, Korean-en, Latvian, Norwegian, Macedonian, Persian, Polish,\n# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,\n# Turkish, Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a\n# new page for each member. If set to NO, the documentation of a member will be\n# part of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 2\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make\n# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C\n# (default is Fortran), use: inc=Fortran f=C.\n#\n# Note For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by by putting a % sign in front of the word\n# or globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = YES\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES, then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = YES\n\n# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. When set to YES local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO these classes will be included in the various overviews. This option has\n# no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO the members will appear in declaration order.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the\n# todo list. This list is created by putting \\todo commands in the\n# documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the\n# test list. This list is created by putting \\test commands in the\n# documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES the list\n# will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. Do not use file names with spaces, bibtex cannot handle them. See\n# also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO doxygen will only warn about wrong or incomplete parameter\n# documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces.\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  =\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank the\n# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,\n# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,\n# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,\n# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,\n# *.qsf, *.as and *.js.\n\n#### OPTION GENERATED IN generate_doxygen.sh\n\nFILE_PATTERNS          =\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\n#### OPTION GENERATED IN generate_doxygen.sh\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER ) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = YES\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = YES\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = YES\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES, then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = NO\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-\n# defined cascading style sheet that is included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefor more robust against future updates.\n# Doxygen will copy the style sheet file to the output directory. For an example\n# see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the stylesheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler ( hhc.exe). If non-empty\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated (\n# YES) or that it should be included in the master .chm file ( NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated (\n# YES) or a normal table of contents ( NO) in the .chm file.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using prerendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = NO\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavours of web server based searching depending on the\n# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for\n# searching and an index file used by the script. When EXTERNAL_SEARCH is\n# enabled the indexing and searching needs to be provided by external tools. See\n# the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer ( doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer ( doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = YES\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4wide\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. To get the times font for\n# instance you can specify\n# EXTRA_PACKAGES=times\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will\n# replace them by respectively the title of the page, the current date and time,\n# only the current date, the version number of doxygen, the project name (see\n# PROJECT_NAME), or the project number (see PROJECT_NUMBER).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = NO\n\n# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = NO\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a\n# validating XML parser to check the syntax of the XML files.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_SCHEMA             =\n\n# The XML_DTD tag can be used to specify a XML DTD, which can be used by a\n# validating XML parser to check the syntax of the XML files.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_DTD                =\n\n# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen\n# Definitions (see http://autogen.sf.net) file that captures the structure of\n# the code including all documentation. Note that this feature is still\n# experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names\n# in the source code. If set to NO only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = YES\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES the includes files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\n#### OPTION GENERATED IN generate_doxygen.sh\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all refrences to function-like macros that are alone on a line, have an\n# all uppercase name, and do not end with a semicolon. Such function macros are\n# typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have an unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES all external class will be listed in the\n# class index. If set to NO only the inherited external classes will be listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in\n# the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# If set to YES, the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font n the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = YES\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = YES\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot.\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif and svg.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD License\n\nFor Proxygen software\n\nCopyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n * Neither the name Facebook nor the names of its contributors may be used to\n   endorse or promote products derived from this software without specific\n   prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "## Proxygen: Facebook's C++ HTTP Libraries\n\n[![Linux Build Status](https://github.com/facebook/proxygen/actions/workflows/getdeps_linux.yml/badge.svg)](https://github.com/facebook/proxygen/actions/workflows/getdeps_linux.yml)\n[![macOS Build Status](https://github.com/facebook/proxygen/actions/workflows/getdeps_mac.yml/badge.svg)](https://github.com/facebook/proxygen/actions/workflows/getdeps_mac.yml)\n\nThis project comprises the core C++ HTTP abstractions used at\nFacebook. Internally, it is used as the basis for building many HTTP\nservers, proxies, and clients. This release focuses on the common HTTP\nabstractions and our simple HTTPServer framework. Future releases will\nprovide simple client APIs as well. The framework supports HTTP/1.1,\nSPDY/3, SPDY/3.1, HTTP/2, and HTTP/3. The goal is to provide a simple,\nperformant, and modern C++ HTTP library.\n\nWe have a Google group for general discussions at https://groups.google.com/d/forum/facebook-proxygen.\n\nThe [original blog post](https://engineering.fb.com/production-engineering/introducing-proxygen-facebook-s-c-http-framework/)\nalso has more background on the project.\n\n## Learn More in This Intro Video\n[![Explain Like I’m 5: Proxygen](https://img.youtube.com/vi/OsrBYHIYCYk/0.jpg)](https://www.youtube.com/watch?v=OsrBYHIYCYk)\n\n### Installing\n\nNote that currently this project has been tested on Ubuntu 18.04 and Mac OSX\nalthough it likely works on many other platforms.\n\nYou will need at least 3 GiB of memory to compile `proxygen` and its\ndependencies.\n\n##### Easy Install\n\nJust run `./build.sh` from the `proxygen/` directory to get and build all\nthe dependencies and `proxygen`. You can run the tests manually with `cd _build/ && make test`.\nThen run `./install.sh` to install it. You can remove the temporary build directory (`_build`) and `./build.sh && ./install.sh`\nto rebase the dependencies, and then rebuild and reinstall `proxygen`.\n\n##### Package Managers\n\nYou can download and install proxygen using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:\n\n    git clone https://github.com/Microsoft/vcpkg.git\n    cd vcpkg\n    ./bootstrap-vcpkg.sh\n    ./vcpkg integrate install\n    ./vcpkg install proxygen\n\nThe proxygen port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.\n\n##### Other Platforms\n\nIf you are running on another platform, you may need to install several\npackages first. Proxygen and `folly` are all Autotools based projects.\n\n### Introduction\n\nDirectory structure and contents:\n\n| Directory                  | Purpose                                                                       |\n|----------------------------|-------------------------------------------------------------------------------|\n| `proxygen/external/`       | Contains non-installed 3rd-party code proxygen depends on.                    |\n| `proxygen/lib/`            | Core networking abstractions.                                                 |\n| `proxygen/lib/http/`       | HTTP specific code. (including HTTP/2 and HTTP/3)                             |\n| `proxygen/lib/services/`   | Connection management and server code.                                        |\n| `proxygen/lib/utils/`      | Miscellaneous helper code.                                                    |\n| `proxygen/httpserver/`     | Contains code wrapping `proxygen/lib/` for building simple C++ http servers. We recommend building on top of these APIs. |\n\n### Architecture\n\nThe central abstractions to understand in `proxygen/lib` are the session, codec,\ntransaction, and handler. These are the lowest level abstractions, and we\ndon't generally recommend building off of these directly.\n\nWhen bytes are read off the wire, the `HTTPCodec` stored inside\n`HTTPSession` parses these into higher-level objects and associates with\nit a transaction identifier. The codec then calls into `HTTPSession` which\nis responsible for maintaining the mapping between transaction identifier\nand `HTTPTransaction` objects. Each HTTP request/response pair has a\nseparate `HTTPTransaction` object. Finally, `HTTPTransaction` forwards the\ncall to a handler object which implements `HTTPTransaction:: Handler`. The\nhandler is responsible for implementing business logic for the request or\nresponse.\n\nThe handler then calls back into the transaction to generate egress\n(whether the egress is a request or response). The call flows from the\ntransaction back to the session, which uses the codec to convert the\nhigher-level semantics of the particular call into the appropriate bytes\nto send on the wire.\n\nThe same handler and transaction interfaces are used to both create requests\nand handle responses. The API is generic enough to allow\nboth. `HTTPSession` is specialized slightly differently depending on\nwhether you are using the connection to issue or respond to HTTP\nrequests.\n\n![Core Proxygen Architecture](CoreProxygenArchitecture.png)\n\nMoving into higher levels of abstraction, `proxygen/HTTP server` has a\nsimpler set of APIs and is the recommended way to interface with `proxygen`\nwhen acting as a server if you don't need the full control of the lower\nlevel abstractions.\n\nThe basic components here are `HTTPServer`, `RequestHandlerFactory`, and\n`RequestHandler`. An `HTTPServer` takes some configuration and is given a\n`RequestHandlerFactory`. Once the server is started, the installed\n`RequestHandlerFactory` spawns a `RequestHandler` for each HTTP\nrequest. `RequestHandler` is a simple interface users of the library\nimplement. Subclasses of `RequestHandler` should use the inherited\nprotected member `ResponseHandler* downstream_` to send the response.\n\n### Using it\n\nProxygen is a library. After installing it, you can build your C++\nserver. Try `cd` ing to the directory containing the echo server at\n`proxygen/httpserver/samples/echo/`.\n\nAfter building proxygen you can start the echo server with `_build/proxygen/httpserver/proxygen_echo`\nand verify it works using curl in a different terminal:\n```shell\n$ curl -v http://localhost:11000/\n*   Trying 127.0.0.1...\n* Connected to localhost (127.0.0.1) port 11000 (#0)\n> GET / HTTP/1.1\n> User-Agent: curl/7.35.0\n> Host: localhost:11000\n> Accept: */*\n>\n< HTTP/1.1 200 OK\n< Request-Number: 1\n< Date: Thu, 30 Oct 2014 17:07:36 GMT\n< Connection: keep-alive\n< Content-Length: 0\n<\n* Connection #0 to host localhost left intact\n```\n\nYou can find other samples:\n  * a simple server that supports HTTP/2 server push (`_build/proxygen/httpserver/proxygen_push`),\n  * a simple server for static files (`_build/proxygen/httpserver/proxygen_static`)\n  * a simple fwdproxy (`_build/proxygen/httpserver/proxygen_proxy`)\n  * a curl-like client (`_build/proxygen/httpclient/samples/curl/proxygen_curl`)\n\n### QUIC and HTTP/3\n\nProxygen supports HTTP/3!\n\nIt depends on Facebook's [mvfst](https://github.com/facebook/mvfst)\nlibrary for the [IETF QUIC](https://github.com/quicwg/base-drafts) transport\nimplementation.\n\nThis comes with a handy command-line utility that can be used as an HTTP/3\nserver and client.\n\nSample usage:\n```shell\n_build/proxygen/httpserver/hq --mode=server\n_build/proxygen/httpserver/hq --mode=client --path=/\n```\nThe utility supports the [qlog](https://github.com/quiclog/internet-drafts)\nlogging format; just start the server with the `--qlogger_path` option and many\nknobs to tune both the quic transport and the http layer.\n\n### Documentation\n\nWe use Doxygen for Proxygen's internal documentation. You can generate a\ncopy of these docs by running `doxygen Doxyfile` from the project\nroot. You'll want to look at `html/namespaceproxygen.html` to start. This\nwill also generate `folly` documentation.\n\n### License\nSee [LICENSE](LICENSE).\n\n### Contributing\nContributions to Proxygen are more than welcome. [Read the guidelines in CONTRIBUTING.md](CONTRIBUTING.md).\nMake sure you've [signed the CLA](https://code.facebook.com/cla) before sending in a pull request.\n\n### Whitehat\n\nFacebook has a [bounty program](https://www.facebook.com/whitehat/) for\nthe safe disclosure of security bugs. If you find a vulnerability, please\ngo through the process outlined on that page and do not file a public issue.\n"
  },
  {
    "path": "build/README.md",
    "content": "# Building using `fbcode_builder`\n\nContinuous integration builds are powered by `fbcode_builder`, a tiny tool\nshared by several Facebook projects.  Its files are in `./fbcode_builder`\n(on Github) or in `fbcode/opensource/fbcode_builder` (inside Facebook's\nrepo).\n\nStart with the READMEs in the `fbcode_builder` directory.\n\n`./fbcode_builder_config.py` contains the project-specific configuration.\n"
  },
  {
    "path": "build/deps/github_hashes/facebook/folly-rev.txt",
    "content": "Subproject commit 4321dae73b18961089fc62532ee6dc011cbf1915\n"
  },
  {
    "path": "build/deps/github_hashes/facebook/mvfst-rev.txt",
    "content": "Subproject commit b229e95977aa5d4c271da60775f2b4bdbc40262a\n"
  },
  {
    "path": "build/deps/github_hashes/facebook/wangle-rev.txt",
    "content": "Subproject commit dbe97d189ad17a732477ecf4defdcf8cdac2c013\n"
  },
  {
    "path": "build/deps/github_hashes/facebookincubator/fizz-rev.txt",
    "content": "Subproject commit 8d74917ae1e80269c6e8e9da44f9713269d9c2ec\n"
  },
  {
    "path": "build/fbcode_builder/.gitignore",
    "content": "# Facebook-internal CI builds don't have write permission outside of the\n# source tree, so we install all projects into this directory.\n/facebook_ci\n__pycache__/\n*.pyc\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBBuildOptions.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\nfunction (fb_activate_static_library_option)\n  option(USE_STATIC_DEPS_ON_UNIX\n    \"If enabled, use static dependencies on unix systems. This is generally discouraged.\"\n    OFF\n  )\n  # Mark USE_STATIC_DEPS_ON_UNIX as an \"advanced\" option, since enabling it\n  # is generally discouraged.\n  mark_as_advanced(USE_STATIC_DEPS_ON_UNIX)\n\n  if(UNIX AND USE_STATIC_DEPS_ON_UNIX)\n    SET(CMAKE_FIND_LIBRARY_SUFFIXES \".a\" PARENT_SCOPE)\n  endif()\n\n  option(PREFER_STATIC_DEPS_ON_UNIX\n    \"If enabled, use static dependencies on unix systems as possible as we can. This is generally discouraged.\"\n    OFF\n  )\n  # Mark PREFER_STATIC_DEPS_ON_UNIX as an \"advanced\" option, since enabling it\n  # is generally discouraged.\n  mark_as_advanced(PREFER_STATIC_DEPS_ON_UNIX)\n\n  if(UNIX AND PREFER_STATIC_DEPS_ON_UNIX)\n    SET(CMAKE_FIND_LIBRARY_SUFFIXES \".a\" \".so\" PARENT_SCOPE)\n  endif()\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBCMakeParseArgs.cmake",
    "content": "#\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# Helper function for parsing arguments to a CMake function.\n#\n# This function is very similar to CMake's built-in cmake_parse_arguments()\n# function, with some improvements:\n# - This function correctly handles empty arguments.  (cmake_parse_arguments()\n#   ignores empty arguments.)\n# - If a multi-value argument is specified more than once, the subsequent\n#   arguments are appended to the original list rather than replacing it.  e.g.\n#   if \"SOURCES\" is a multi-value argument, and the argument list contains\n#   \"SOURCES a b c SOURCES x y z\" then the resulting value for SOURCES will be\n#   \"a;b;c;x;y;z\" rather than \"x;y;z\"\n# - This function errors out by default on unrecognized arguments.  You can\n#   pass in an extra \"ALLOW_UNPARSED_ARGS\" argument to make it behave like\n#   cmake_parse_arguments(), and return the unparsed arguments in a\n#   <prefix>_UNPARSED_ARGUMENTS variable instead.\n#\n# It does look like cmake_parse_arguments() handled empty arguments correctly\n# from CMake 3.0 through 3.3, but it seems like this was probably broken when\n# it was turned into a built-in function in CMake 3.4.  Here is discussion and\n# patches that fixed this behavior prior to CMake 3.0:\n# https://cmake.org/pipermail/cmake-developers/2013-November/020607.html\n#\n# The one downside to this function over the built-in cmake_parse_arguments()\n# is that I don't think we can achieve the PARSE_ARGV behavior in a non-builtin\n# function, so we can't properly handle arguments that contain \";\".  CMake will\n# treat the \";\" characters as list element separators, and treat it as multiple\n# separate arguments.\n#\nfunction(fb_cmake_parse_args PREFIX OPTIONS ONE_VALUE_ARGS MULTI_VALUE_ARGS ARGS)\n  foreach(option IN LISTS ARGN)\n    if (\"${option}\" STREQUAL \"ALLOW_UNPARSED_ARGS\")\n      set(ALLOW_UNPARSED_ARGS TRUE)\n    else()\n      message(\n        FATAL_ERROR\n        \"unknown optional argument for fb_cmake_parse_args(): ${option}\"\n      )\n    endif()\n  endforeach()\n\n  # Define all options as FALSE in the parent scope to start with\n  foreach(var_name IN LISTS OPTIONS)\n    set(\"${PREFIX}_${var_name}\" \"FALSE\" PARENT_SCOPE)\n  endforeach()\n\n  # TODO: We aren't extremely strict about error checking for one-value\n  # arguments here.  e.g., we don't complain if a one-value argument is\n  # followed by another option/one-value/multi-value name rather than an\n  # argument.  We also don't complain if a one-value argument is the last\n  # argument and isn't followed by a value.\n\n  list(APPEND all_args ${ONE_VALUE_ARGS})\n  list(APPEND all_args ${MULTI_VALUE_ARGS})\n  set(current_variable)\n  set(unparsed_args)\n  foreach(arg IN LISTS ARGS)\n    list(FIND OPTIONS \"${arg}\" opt_index)\n    if(\"${opt_index}\" EQUAL -1)\n      list(FIND all_args \"${arg}\" arg_index)\n      if(\"${arg_index}\" EQUAL -1)\n        # This argument does not match an argument name,\n        # must be an argument value\n        if(\"${current_variable}\" STREQUAL \"\")\n          list(APPEND unparsed_args \"${arg}\")\n        else()\n          # Ugh, CMake lists have a pretty fundamental flaw: they cannot\n          # distinguish between an empty list and a list with a single empty\n          # element.  We track our own SEEN_VALUES_arg setting to help\n          # distinguish this and behave properly here.\n          if (\"${SEEN_${current_variable}}\" AND \"${${current_variable}}\" STREQUAL \"\")\n            set(\"${current_variable}\" \";${arg}\")\n          else()\n            list(APPEND \"${current_variable}\" \"${arg}\")\n          endif()\n          set(\"SEEN_${current_variable}\" TRUE)\n        endif()\n      else()\n        # We found a single- or multi-value argument name\n        set(current_variable \"VALUES_${arg}\")\n        set(\"SEEN_${arg}\" TRUE)\n      endif()\n    else()\n      # We found an option variable\n      set(\"${PREFIX}_${arg}\" \"TRUE\" PARENT_SCOPE)\n      set(current_variable)\n    endif()\n  endforeach()\n\n  foreach(arg_name IN LISTS ONE_VALUE_ARGS)\n    if(NOT \"${SEEN_${arg_name}}\")\n      unset(\"${PREFIX}_${arg_name}\" PARENT_SCOPE)\n    elseif(NOT \"${SEEN_VALUES_${arg_name}}\")\n      # If the argument was seen but a value wasn't specified, error out.\n      # We require exactly one value to be specified.\n      message(\n        FATAL_ERROR \"argument ${arg_name} was specified without a value\"\n      )\n    else()\n      list(LENGTH \"VALUES_${arg_name}\" num_args)\n      if(\"${num_args}\" EQUAL 0)\n        # We know an argument was specified and that we called list(APPEND).\n        # If CMake thinks the list is empty that means there is really a single\n        # empty element in the list.\n        set(\"${PREFIX}_${arg_name}\" \"\" PARENT_SCOPE)\n      elseif(\"${num_args}\" EQUAL 1)\n        list(GET \"VALUES_${arg_name}\" 0 arg_value)\n        set(\"${PREFIX}_${arg_name}\" \"${arg_value}\" PARENT_SCOPE)\n      else()\n        message(\n          FATAL_ERROR \"too many arguments specified for ${arg_name}: \"\n          \"${VALUES_${arg_name}}\"\n        )\n      endif()\n    endif()\n  endforeach()\n\n  foreach(arg_name IN LISTS MULTI_VALUE_ARGS)\n    # If this argument name was never seen, then unset the parent scope\n    if (NOT \"${SEEN_${arg_name}}\")\n      unset(\"${PREFIX}_${arg_name}\" PARENT_SCOPE)\n    else()\n      # TODO: Our caller still won't be able to distinguish between an empty\n      # list and a list with a single empty element.  We can tell which is\n      # which, but CMake lists don't make it easy to show this to our caller.\n      set(\"${PREFIX}_${arg_name}\" \"${VALUES_${arg_name}}\" PARENT_SCOPE)\n    endif()\n  endforeach()\n\n  # By default we fatal out on unparsed arguments, but return them to the\n  # caller if ALLOW_UNPARSED_ARGS was specified.\n  if (DEFINED unparsed_args)\n    if (\"${ALLOW_UNPARSED_ARGS}\")\n      set(\"${PREFIX}_UNPARSED_ARGUMENTS\" \"${unparsed_args}\" PARENT_SCOPE)\n    else()\n      message(FATAL_ERROR \"unrecognized arguments: ${unparsed_args}\")\n    endif()\n  endif()\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBCompilerSettings.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\n# This file applies common compiler settings that are shared across\n# a number of Facebook opensource projects.\n# Please use caution and your best judgement before making changes\n# to these shared compiler settings in order to avoid accidentally\n# breaking a build in another project!\n\nif (WIN32)\n  include(FBCompilerSettingsMSVC)\nelse()\n  include(FBCompilerSettingsUnix)\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBCompilerSettingsMSVC.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\n# This file applies common compiler settings that are shared across\n# a number of Facebook opensource projects.\n# Please use caution and your best judgement before making changes\n# to these shared compiler settings in order to avoid accidentally\n# breaking a build in another project!\n\nadd_compile_options(\n  /wd4250 # 'class1' : inherits 'class2::member' via dominance\n  /Zc:preprocessor # Enable conforming preprocessor for __VA_OPT__ support\n)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBCompilerSettingsUnix.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\n# This file applies common compiler settings that are shared across\n# a number of Facebook opensource projects.\n# Please use caution and your best judgement before making changes\n# to these shared compiler settings in order to avoid accidentally\n# breaking a build in another project!\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -g -Wall -Wextra -Wno-deprecated -Wno-deprecated-declarations\")\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBPythonBinary.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\ninclude(FBCMakeParseArgs)\n\n#\n# This file contains helper functions for building self-executing Python\n# binaries.\n#\n# This is somewhat different than typical python installation with\n# distutils/pip/virtualenv/etc.  We primarily want to build a standalone\n# executable, isolated from other Python packages on the system.  We don't want\n# to install files into the standard library python paths.  This is more\n# similar to PEX (https://github.com/pantsbuild/pex) and XAR\n# (https://github.com/facebookincubator/xar).  (In the future it would be nice\n# to update this code to also support directly generating XAR files if XAR is\n# available.)\n#\n# We also want to be able to easily define \"libraries\" of python files that can\n# be shared and re-used between these standalone python executables, and can be\n# shared across projects in different repositories.  This means that we do need\n# a way to \"install\" libraries so that they are visible to CMake builds in\n# other repositories, without actually installing them in the standard python\n# library paths.\n#\n\n# If the caller has not already found Python, do so now.\n# If we fail to find python now we won't fail immediately, but\n# add_fb_python_executable() or add_fb_python_library() will fatal out if they\n# are used.\nif(NOT TARGET Python3::Interpreter)\n  # CMake 3.12+ ships with a FindPython3.cmake module.  Try using it first.\n  # We find with QUIET here, since otherwise this generates some noisy warnings\n  # on versions of CMake before 3.12\n  if (WIN32)\n    # On Windows we need both the Interpreter as well as the Development\n    # libraries.\n    find_package(Python3 COMPONENTS Interpreter Development QUIET)\n  else()\n    find_package(Python3 COMPONENTS Interpreter QUIET)\n  endif()\n  if(Python3_Interpreter_FOUND)\n    message(STATUS \"Found Python 3: ${Python3_EXECUTABLE}\")\n  else()\n    # Try with the FindPythonInterp.cmake module available in older CMake\n    # versions.  Check to see if the caller has already searched for this\n    # themselves first.\n    if(NOT PYTHONINTERP_FOUND)\n      set(Python_ADDITIONAL_VERSIONS 3 3.6 3.5 3.4 3.3 3.2 3.1)\n      find_package(PythonInterp)\n      # TODO: On Windows we require the Python libraries as well.\n      # We currently do not search for them on this code path.\n      # For now we require building with CMake 3.12+ on Windows, so that the\n      # FindPython3 code path above is available.\n    endif()\n    if(PYTHONINTERP_FOUND)\n      if(\"${PYTHON_VERSION_MAJOR}\" GREATER_EQUAL 3)\n        set(Python3_EXECUTABLE \"${PYTHON_EXECUTABLE}\")\n        add_custom_target(Python3::Interpreter)\n      else()\n        string(\n          CONCAT FBPY_FIND_PYTHON_ERR\n          \"found Python ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}, \"\n          \"but need Python 3\"\n        )\n      endif()\n    endif()\n  endif()\nendif()\n\n# Find our helper program.\n# We typically install this in the same directory as this .cmake file.\nfind_program(\n  FB_MAKE_PYTHON_ARCHIVE \"make_fbpy_archive.py\"\n  PATHS ${CMAKE_MODULE_PATH}\n)\nset(FB_PY_TEST_MAIN \"${CMAKE_CURRENT_LIST_DIR}/fb_py_test_main.py\")\nset(\n  FB_PY_TEST_DISCOVER_SCRIPT\n  \"${CMAKE_CURRENT_LIST_DIR}/FBPythonTestAddTests.cmake\"\n)\nset(\n  FB_PY_WIN_MAIN_C\n  \"${CMAKE_CURRENT_LIST_DIR}/fb_py_win_main.c\"\n)\n\n# An option to control the default installation location for\n# install_fb_python_library().  This is relative to ${CMAKE_INSTALL_PREFIX}\nset(\n  FBPY_LIB_INSTALL_DIR \"lib/fb-py-libs\" CACHE STRING\n  \"The subdirectory where FB python libraries should be installed\"\n)\n\n#\n# Build a self-executing python binary.\n#\n# This accepts the same arguments as add_fb_python_library().\n#\n# In addition, a MAIN_MODULE argument is accepted.  This argument specifies\n# which module should be started as the __main__ module when the executable is\n# run.  If left unspecified, a __main__.py script must be present in the\n# manifest.\n#\nfunction(add_fb_python_executable TARGET)\n  fb_py_check_available()\n\n  # Parse the arguments\n  set(one_value_args BASE_DIR NAMESPACE MAIN_MODULE TYPE)\n  set(multi_value_args SOURCES DEPENDS)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n  fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR)\n\n  # Use add_fb_python_library() to perform most of our source handling\n  add_fb_python_library(\n    \"${TARGET}.main_lib\"\n    BASE_DIR \"${ARG_BASE_DIR}\"\n    NAMESPACE \"${ARG_NAMESPACE}\"\n    SOURCES ${ARG_SOURCES}\n    DEPENDS ${ARG_DEPENDS}\n  )\n\n  set(\n    manifest_files\n    \"$<TARGET_PROPERTY:${TARGET}.main_lib.py_lib,INTERFACE_INCLUDE_DIRECTORIES>\"\n  )\n  set(\n    source_files\n    \"$<TARGET_PROPERTY:${TARGET}.main_lib.py_lib,INTERFACE_SOURCES>\"\n  )\n\n  # The command to build the executable archive.\n  #\n  # If we are using CMake 3.8+ we can use COMMAND_EXPAND_LISTS.\n  # CMP0067 isn't really the policy we care about, but seems like the best way\n  # to check if we are running 3.8+.\n  if (POLICY CMP0067)\n    set(extra_cmd_params COMMAND_EXPAND_LISTS)\n    set(make_py_args \"${manifest_files}\")\n  else()\n    set(extra_cmd_params)\n    set(make_py_args --manifest-separator \"::\" \"$<JOIN:${manifest_files},::>\")\n  endif()\n\n  set(output_file \"${TARGET}${CMAKE_EXECUTABLE_SUFFIX}\")\n  if(WIN32)\n    set(zipapp_output \"${TARGET}.py_zipapp\")\n  else()\n    set(zipapp_output \"${output_file}\")\n  endif()\n  set(zipapp_output_file \"${zipapp_output}\")\n\n  set(is_dir_output FALSE)\n  if(DEFINED ARG_TYPE)\n    list(APPEND make_py_args \"--type\" \"${ARG_TYPE}\")\n    if (\"${ARG_TYPE}\" STREQUAL \"dir\")\n      set(is_dir_output TRUE)\n      # CMake doesn't really seem to like having a directory specified as an\n      # output; specify the __main__.py file as the output instead.\n      set(zipapp_output_file \"${zipapp_output}/__main__.py\")\n      # Update output_file to match zipapp_output_file for dir type\n      set(output_file \"${zipapp_output_file}\")\n      list(APPEND\n        extra_cmd_params\n        COMMAND \"${CMAKE_COMMAND}\" -E remove_directory \"${zipapp_output}\"\n      )\n    endif()\n  endif()\n\n  if(DEFINED ARG_MAIN_MODULE)\n    list(APPEND make_py_args \"--main\" \"${ARG_MAIN_MODULE}\")\n  endif()\n\n  add_custom_command(\n    OUTPUT \"${zipapp_output_file}\"\n    ${extra_cmd_params}\n    COMMAND\n      \"${Python3_EXECUTABLE}\" \"${FB_MAKE_PYTHON_ARCHIVE}\"\n      -o \"${zipapp_output}\"\n      ${make_py_args}\n    DEPENDS\n      ${source_files}\n      \"${TARGET}.main_lib.py_sources_built\"\n      \"${FB_MAKE_PYTHON_ARCHIVE}\"\n  )\n\n  if(WIN32)\n    if(is_dir_output)\n      # TODO: generate a main executable that will invoke Python3\n      # with the correct main module inside the output directory\n    else()\n      add_executable(\"${TARGET}.winmain\" \"${FB_PY_WIN_MAIN_C}\")\n      target_link_libraries(\"${TARGET}.winmain\" Python3::Python)\n      # The Python3::Python target doesn't seem to be set up completely\n      # correctly on Windows for some reason, and we have to explicitly add\n      # ${Python3_LIBRARY_DIRS} to the target link directories.\n      target_link_directories(\n        \"${TARGET}.winmain\"\n        PUBLIC ${Python3_LIBRARY_DIRS}\n      )\n      add_custom_command(\n        OUTPUT \"${output_file}\"\n        DEPENDS \"${TARGET}.winmain\" \"${zipapp_output_file}\"\n        COMMAND\n          \"cmd.exe\" \"/c\" \"copy\" \"/b\"\n          \"${TARGET}.winmain${CMAKE_EXECUTABLE_SUFFIX}+${zipapp_output}\"\n          \"${output_file}\"\n      )\n    endif()\n  endif()\n\n  # Add an \"ALL\" target that depends on force ${TARGET},\n  # so that ${TARGET} will be included in the default list of build targets.\n  add_custom_target(\"${TARGET}.GEN_PY_EXE\" ALL DEPENDS \"${output_file}\")\n\n  # Allow resolving the executable path for the target that we generate\n  # via a generator expression like:\n  # \"WATCHMAN_WAIT_PATH=$<TARGET_PROPERTY:watchman-wait.GEN_PY_EXE,EXECUTABLE>\"\n  set_property(TARGET \"${TARGET}.GEN_PY_EXE\"\n      PROPERTY EXECUTABLE \"${CMAKE_CURRENT_BINARY_DIR}/${output_file}\")\nendfunction()\n\n# Define a python unittest executable.\n# The executable is built using add_fb_python_executable and has the\n# following differences:\n#\n# Each of the source files specified in SOURCES will be imported\n# and have unittest discovery performed upon them.\n# Those sources will be imported in the top level namespace.\n#\n# The ENV argument allows specifying a list of \"KEY=VALUE\"\n# pairs that will be used by the test runner to set up the environment\n# in the child process prior to running the test.  This is useful for\n# passing additional configuration to the test.\nfunction(add_fb_python_unittest TARGET)\n  # Parse the arguments\n  set(multi_value_args SOURCES DEPENDS ENV PROPERTIES)\n  set(\n    one_value_args\n    WORKING_DIRECTORY BASE_DIR NAMESPACE TEST_LIST DISCOVERY_TIMEOUT TYPE\n  )\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n  fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR)\n  if(NOT ARG_WORKING_DIRECTORY)\n    # Default the working directory to the current binary directory.\n    # This matches the default behavior of add_test() and other standard\n    # test functions like gtest_discover_tests()\n    set(ARG_WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")\n  endif()\n  if(NOT ARG_TEST_LIST)\n    set(ARG_TEST_LIST \"${TARGET}_TESTS\")\n  endif()\n  if(NOT ARG_DISCOVERY_TIMEOUT)\n    set(ARG_DISCOVERY_TIMEOUT 5)\n  endif()\n\n  # Tell our test program the list of modules to scan for tests.\n  # We scan all modules directly listed in our SOURCES argument, and skip\n  # modules that came from dependencies in the DEPENDS list.\n  #\n  # This is written into a __test_modules__.py module that the test runner\n  # will look at.\n  set(\n    test_modules_path\n    \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_test_modules.py\"\n  )\n  file(WRITE \"${test_modules_path}\" \"TEST_MODULES = [\\n\")\n  string(REPLACE \".\" \"/\" namespace_dir \"${ARG_NAMESPACE}\")\n  if (NOT \"${namespace_dir}\" STREQUAL \"\")\n    set(namespace_dir \"${namespace_dir}/\")\n  endif()\n  set(test_modules)\n  foreach(src_path IN LISTS ARG_SOURCES)\n    fb_py_compute_dest_path(\n      abs_source dest_path\n      \"${src_path}\" \"${namespace_dir}\" \"${ARG_BASE_DIR}\"\n    )\n    string(REPLACE \"/\" \".\" module_name \"${dest_path}\")\n    string(REGEX REPLACE \"\\\\.py$\" \"\" module_name \"${module_name}\")\n    list(APPEND test_modules \"${module_name}\")\n    file(APPEND \"${test_modules_path}\" \"  '${module_name}',\\n\")\n  endforeach()\n  file(APPEND \"${test_modules_path}\" \"]\\n\")\n\n  # The __main__ is provided by our runner wrapper/bootstrap\n  list(APPEND ARG_SOURCES \"${FB_PY_TEST_MAIN}=__main__.py\")\n  list(APPEND ARG_SOURCES \"${test_modules_path}=__test_modules__.py\")\n\n  if(NOT DEFINED ARG_TYPE)\n    set(ARG_TYPE \"zipapp\")\n  endif()\n\n  add_fb_python_executable(\n    \"${TARGET}\"\n    TYPE \"${ARG_TYPE}\"\n    NAMESPACE \"${ARG_NAMESPACE}\"\n    BASE_DIR \"${ARG_BASE_DIR}\"\n    SOURCES ${ARG_SOURCES}\n    DEPENDS ${ARG_DEPENDS}\n  )\n\n  # Run test discovery after the test executable is built.\n  # This logic is based on the code for gtest_discover_tests()\n  set(ctest_file_base \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}\")\n  set(ctest_include_file \"${ctest_file_base}_include.cmake\")\n  set(ctest_tests_file \"${ctest_file_base}_tests.cmake\")\n  add_custom_command(\n    TARGET \"${TARGET}.GEN_PY_EXE\" POST_BUILD\n    BYPRODUCTS \"${ctest_tests_file}\"\n    COMMAND\n      \"${CMAKE_COMMAND}\"\n      -D \"TEST_TARGET=${TARGET}\"\n      -D \"TEST_INTERPRETER=${Python3_EXECUTABLE}\"\n      -D \"TEST_ENV=${ARG_ENV}\"\n      -D \"TEST_EXECUTABLE=$<TARGET_PROPERTY:${TARGET}.GEN_PY_EXE,EXECUTABLE>\"\n      -D \"TEST_WORKING_DIR=${ARG_WORKING_DIRECTORY}\"\n      -D \"TEST_LIST=${ARG_TEST_LIST}\"\n      -D \"TEST_PREFIX=${TARGET}::\"\n      -D \"TEST_PROPERTIES=${ARG_PROPERTIES}\"\n      -D \"CTEST_FILE=${ctest_tests_file}\"\n      -P \"${FB_PY_TEST_DISCOVER_SCRIPT}\"\n    VERBATIM\n  )\n\n  file(\n    WRITE \"${ctest_include_file}\"\n    \"if(EXISTS \\\"${ctest_tests_file}\\\")\\n\"\n    \"  include(\\\"${ctest_tests_file}\\\")\\n\"\n    \"else()\\n\"\n    \"  add_test(\\\"${TARGET}_NOT_BUILT\\\" \\\"${TARGET}_NOT_BUILT\\\")\\n\"\n    \"endif()\\n\"\n  )\n  set_property(\n    DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES\n    \"${ctest_include_file}\"\n  )\nendfunction()\n\n#\n# Define a python library.\n#\n# If you want to install a python library generated from this rule note that\n# you need to use install_fb_python_library() rather than CMake's built-in\n# install() function.  This will make it available for other downstream\n# projects to use in their add_fb_python_executable() and\n# add_fb_python_library() calls.  (You do still need to use `install(EXPORT)`\n# later to install the CMake exports.)\n#\n# Parameters:\n# - BASE_DIR <dir>:\n#   The base directory path to strip off from each source path.  All source\n#   files must be inside this directory.  If not specified it defaults to\n#   ${CMAKE_CURRENT_SOURCE_DIR}.\n# - NAMESPACE <namespace>:\n#   The destination namespace where these files should be installed in python\n#   binaries.  If not specified, this defaults to the current relative path of\n#   ${CMAKE_CURRENT_SOURCE_DIR} inside ${CMAKE_SOURCE_DIR}.  e.g., a python\n#   library defined in the directory repo_root/foo/bar will use a default\n#   namespace of \"foo.bar\"\n# - SOURCES <src1> <...>:\n#   The python source files.\n#   You may optionally specify as source using the form: PATH=ALIAS where\n#   PATH is a relative path in the source tree and ALIAS is the relative\n#   path into which PATH should be rewritten.  This is useful for mapping\n#   an executable script to the main module in a python executable.\n#   e.g.: `python/bin/watchman-wait=__main__.py`\n# - DEPENDS <target1> <...>:\n#   Other python libraries that this one depends on.\n# - INSTALL_DIR <dir>:\n#   The directory where this library should be installed.\n#   install_fb_python_library() must still be called later to perform the\n#   installation.  If a relative path is given it will be treated relative to\n#   ${CMAKE_INSTALL_PREFIX}\n#\n# CMake is unfortunately pretty crappy at being able to define custom build\n# rules & behaviors.  It doesn't support transitive property propagation\n# between custom targets; only the built-in add_executable() and add_library()\n# targets support transitive properties.\n#\n# We hack around this janky CMake behavior by (ab)using interface libraries to\n# propagate some of the data we want between targets, without actually\n# generating a C library.\n#\n# add_fb_python_library(SOMELIB) generates the following things:\n# - An INTERFACE library rule named SOMELIB.py_lib which tracks some\n#   information about transitive dependencies:\n#   - the transitive set of source files in the INTERFACE_SOURCES property\n#   - the transitive set of manifest files that this library depends on in\n#     the INTERFACE_INCLUDE_DIRECTORIES property.\n# - A custom command that generates a SOMELIB.manifest file.\n#   This file contains the mapping of source files to desired destination\n#   locations in executables that depend on this library.  This manifest file\n#   will then be read at build-time in order to build executables.\n#\nfunction(add_fb_python_library LIB_NAME)\n  fb_py_check_available()\n\n  # Parse the arguments\n  # We use fb_cmake_parse_args() rather than cmake_parse_arguments() since\n  # cmake_parse_arguments() does not handle empty arguments, and it is common\n  # for callers to want to specify an empty NAMESPACE parameter.\n  set(one_value_args BASE_DIR NAMESPACE INSTALL_DIR)\n  set(multi_value_args SOURCES DEPENDS)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n  fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR)\n\n  string(REPLACE \".\" \"/\" namespace_dir \"${ARG_NAMESPACE}\")\n  if (NOT \"${namespace_dir}\" STREQUAL \"\")\n    set(namespace_dir \"${namespace_dir}/\")\n  endif()\n\n  if(NOT DEFINED ARG_INSTALL_DIR)\n    set(install_dir \"${FBPY_LIB_INSTALL_DIR}/\")\n  elseif(\"${ARG_INSTALL_DIR}\" STREQUAL \"\")\n    set(install_dir \"\")\n  else()\n    set(install_dir \"${ARG_INSTALL_DIR}/\")\n  endif()\n\n  # message(STATUS \"fb py library ${LIB_NAME}: \"\n  #         \"NS=${namespace_dir} BASE=${ARG_BASE_DIR}\")\n\n  # TODO: In the future it would be nice to support pre-compiling the source\n  # files.  We could emit a rule to compile each source file and emit a\n  # .pyc/.pyo file here, and then have the manifest reference the pyc/pyo\n  # files.\n\n  # Define a library target to help pass around information about the library,\n  # and propagate dependency information.\n  #\n  # CMake make a lot of assumptions that libraries are C++ libraries.  To help\n  # avoid confusion we name our target \"${LIB_NAME}.py_lib\" rather than just\n  # \"${LIB_NAME}\".  This helps avoid confusion if callers try to use\n  # \"${LIB_NAME}\" on their own as a target name.  (e.g., attempting to install\n  # it directly with install(TARGETS) won't work.  Callers must use\n  # install_fb_python_library() instead.)\n  add_library(\"${LIB_NAME}.py_lib\" INTERFACE)\n\n  # Emit the manifest file.\n  #\n  # We write the manifest file to a temporary path first, then copy it with\n  # configure_file(COPYONLY).  This is necessary to get CMake to understand\n  # that \"${manifest_path}\" is generated by the CMake configure phase,\n  # and allow using it as a dependency for add_custom_command().\n  # (https://gitlab.kitware.com/cmake/cmake/issues/16367)\n  set(manifest_path \"${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.manifest\") \n  set(tmp_manifest \"${manifest_path}.tmp\")\n  file(WRITE \"${tmp_manifest}\" \"FBPY_MANIFEST 1\\n\")\n  set(abs_sources)\n  foreach(src_path IN LISTS ARG_SOURCES)\n    fb_py_compute_dest_path(\n      abs_source dest_path\n      \"${src_path}\" \"${namespace_dir}\" \"${ARG_BASE_DIR}\"\n    )\n    list(APPEND abs_sources \"${abs_source}\")\n    target_sources(\n      \"${LIB_NAME}.py_lib\" INTERFACE\n      \"$<BUILD_INTERFACE:${abs_source}>\"\n      \"$<INSTALL_INTERFACE:${install_dir}${LIB_NAME}/${dest_path}>\"\n    )\n    file(\n      APPEND \"${tmp_manifest}\"\n      \"${abs_source} :: ${dest_path}\\n\"\n    )\n  endforeach()\n  configure_file(\"${tmp_manifest}\" \"${manifest_path}\" COPYONLY)\n\n  target_include_directories(\n    \"${LIB_NAME}.py_lib\" INTERFACE\n    \"$<BUILD_INTERFACE:${manifest_path}>\"\n    \"$<INSTALL_INTERFACE:${install_dir}${LIB_NAME}.manifest>\"\n  )\n\n  # Add a target that depends on all of the source files.\n  # This is needed in case some of the source files are generated.  This will\n  # ensure that these source files are brought up-to-date before we build\n  # any python binaries that depend on this library.\n  add_custom_target(\"${LIB_NAME}.py_sources_built\" DEPENDS ${abs_sources})\n  add_dependencies(\"${LIB_NAME}.py_lib\" \"${LIB_NAME}.py_sources_built\")\n\n  # Hook up library dependencies, and also make the *.py_sources_built target\n  # depend on the sources for all of our dependencies also being up-to-date.\n  foreach(dep IN LISTS ARG_DEPENDS)\n    target_link_libraries(\"${LIB_NAME}.py_lib\" INTERFACE \"${dep}.py_lib\")\n\n    # Mark that our .py_sources_built target depends on each our our dependent\n    # libraries.  This serves two functions:\n    # - This causes CMake to generate an error message if one of the\n    #   dependencies is never defined.  The target_link_libraries() call above\n    #   won't complain if one of the dependencies doesn't exist (since it is\n    #   intended to allow passing in file names for plain library files rather\n    #   than just targets).\n    # - It ensures that sources for our dependencies are built before any\n    #   executable that depends on us.  Note that we depend on \"${dep}.py_lib\"\n    #   rather than \"${dep}.py_sources_built\" for this purpose because the\n    #   \".py_sources_built\" target won't be available for imported targets.\n    add_dependencies(\"${LIB_NAME}.py_sources_built\" \"${dep}.py_lib\")\n  endforeach()\n\n  # Add a custom command to help with library installation, in case\n  # install_fb_python_library() is called later for this library.\n  # add_custom_command() only works with file dependencies defined in the same\n  # CMakeLists.txt file, so we want to make sure this is defined here, rather\n  # then where install_fb_python_library() is called.\n  # This command won't be run by default, but will only be run if it is needed\n  # by a subsequent install_fb_python_library() call.\n  #\n  # This command copies the library contents into the build directory.\n  # It would be nicer if we could skip this intermediate copy, and just run\n  # make_fbpy_archive.py at install time to copy them directly to the desired\n  # installation directory.  Unfortunately this is difficult to do, and seems\n  # to interfere with some of the CMake code that wants to generate a manifest\n  # of installed files.\n  set(build_install_dir \"${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.lib_install\")\n  add_custom_command(\n    OUTPUT\n      \"${build_install_dir}/${LIB_NAME}.manifest\"\n    COMMAND \"${CMAKE_COMMAND}\" -E remove_directory \"${build_install_dir}\"\n    COMMAND\n      \"${Python3_EXECUTABLE}\" \"${FB_MAKE_PYTHON_ARCHIVE}\" --type lib-install\n      --install-dir \"${LIB_NAME}\"\n      -o \"${build_install_dir}/${LIB_NAME}\" \"${manifest_path}\"\n    DEPENDS\n      \"${abs_sources}\"\n      \"${manifest_path}\"\n      \"${FB_MAKE_PYTHON_ARCHIVE}\"\n  )\n  add_custom_target(\n    \"${LIB_NAME}.py_lib_install\"\n    DEPENDS \"${build_install_dir}/${LIB_NAME}.manifest\"\n  )\n\n  # Set some properties to pass through the install paths to\n  # install_fb_python_library()\n  #\n  # Passing through ${build_install_dir} allows install_fb_python_library()\n  # to work even if used from a different CMakeLists.txt file than where\n  # add_fb_python_library() was called (i.e. such that\n  # ${CMAKE_CURRENT_BINARY_DIR} is different between the two calls).\n  set(abs_install_dir \"${install_dir}\")\n  if(NOT IS_ABSOLUTE \"${abs_install_dir}\")\n    set(abs_install_dir \"${CMAKE_INSTALL_PREFIX}/${abs_install_dir}\")\n  endif()\n  string(REGEX REPLACE \"/$\" \"\" abs_install_dir \"${abs_install_dir}\")\n  set_target_properties(\n    \"${LIB_NAME}.py_lib_install\"\n    PROPERTIES\n    INSTALL_DIR \"${abs_install_dir}\"\n    BUILD_INSTALL_DIR \"${build_install_dir}\"\n  )\nendfunction()\n\n#\n# Install an FB-style packaged python binary.\n#\n# - DESTINATION <export-name>:\n#   Associate the installed target files with the given export-name.\n#\nfunction(install_fb_python_executable TARGET)\n  # Parse the arguments\n  set(one_value_args DESTINATION)\n  set(multi_value_args)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n\n  if(NOT DEFINED ARG_DESTINATION)\n    set(ARG_DESTINATION bin)\n  endif()\n\n  install(\n    PROGRAMS \"$<TARGET_PROPERTY:${TARGET}.GEN_PY_EXE,EXECUTABLE>\"\n    DESTINATION \"${ARG_DESTINATION}\"\n  )\nendfunction()\n\n#\n# Install a python library.\n#\n# - EXPORT <export-name>:\n#   Associate the installed target files with the given export-name.\n#\n# Note that unlike the built-in CMake install() function we do not accept a\n# DESTINATION parameter.  Instead, use the INSTALL_DIR parameter to\n# add_fb_python_library() to set the installation location.\n#\nfunction(install_fb_python_library LIB_NAME)\n  set(one_value_args EXPORT)\n  fb_cmake_parse_args(ARG \"\" \"${one_value_args}\" \"\" \"${ARGN}\")\n\n  # Export our \"${LIB_NAME}.py_lib\" target so that it will be available to\n  # downstream projects in our installed CMake config files.\n  if(DEFINED ARG_EXPORT)\n    install(TARGETS \"${LIB_NAME}.py_lib\" EXPORT \"${ARG_EXPORT}\")\n  endif()\n\n  # add_fb_python_library() emits a .py_lib_install target that will prepare\n  # the installation directory.  However, it isn't part of the \"ALL\" target and\n  # therefore isn't built by default.\n  #\n  # Make sure the ALL target depends on it now.  We have to do this by\n  # introducing yet another custom target.\n  # Add it as a dependency to the ALL target now.\n  add_custom_target(\"${LIB_NAME}.py_lib_install_all\" ALL)\n  add_dependencies(\n    \"${LIB_NAME}.py_lib_install_all\" \"${LIB_NAME}.py_lib_install\"\n  )\n\n  # Copy the intermediate install directory generated at build time into\n  # the desired install location.\n  get_target_property(dest_dir \"${LIB_NAME}.py_lib_install\" \"INSTALL_DIR\")\n  get_target_property(\n    build_install_dir \"${LIB_NAME}.py_lib_install\" \"BUILD_INSTALL_DIR\"\n  )\n  install(\n    DIRECTORY \"${build_install_dir}/${LIB_NAME}\"\n    DESTINATION \"${dest_dir}\"\n  )\n  install(\n    FILES \"${build_install_dir}/${LIB_NAME}.manifest\"\n    DESTINATION \"${dest_dir}\"\n  )\nendfunction()\n\n# Helper macro to process the BASE_DIR and NAMESPACE arguments for\n# add_fb_python_executable() and add_fb_python_executable()\nmacro(fb_py_process_default_args NAMESPACE_VAR BASE_DIR_VAR)\n  # If the namespace was not specified, default to the relative path to the\n  # current directory (starting from the repository root).\n  if(NOT DEFINED \"${NAMESPACE_VAR}\")\n    file(\n      RELATIVE_PATH \"${NAMESPACE_VAR}\"\n      \"${CMAKE_SOURCE_DIR}\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}\"\n    )\n  endif()\n\n  if(NOT DEFINED \"${BASE_DIR_VAR}\")\n    # If the base directory was not specified, default to the current directory\n    set(\"${BASE_DIR_VAR}\" \"${CMAKE_CURRENT_SOURCE_DIR}\")\n  else()\n    # If the base directory was specified, always convert it to an\n    # absolute path.\n    get_filename_component(\"${BASE_DIR_VAR}\" \"${${BASE_DIR_VAR}}\" ABSOLUTE)\n  endif()\nendmacro()\n\nfunction(fb_py_check_available)\n  # Make sure that Python 3 and our make_fbpy_archive.py helper script are\n  # available.\n  if(NOT Python3_EXECUTABLE)\n    if(FBPY_FIND_PYTHON_ERR)\n      message(FATAL_ERROR \"Unable to find Python 3: ${FBPY_FIND_PYTHON_ERR}\")\n    else()\n      message(FATAL_ERROR \"Unable to find Python 3\")\n    endif()\n  endif()\n\n  if (NOT FB_MAKE_PYTHON_ARCHIVE)\n    message(\n      FATAL_ERROR \"unable to find make_fbpy_archive.py helper program (it \"\n      \"should be located in the same directory as FBPythonBinary.cmake)\"\n    )\n  endif()\nendfunction()\n\nfunction(\n    fb_py_compute_dest_path\n    src_path_output dest_path_output src_path namespace_dir base_dir\n)\n  if(\"${src_path}\" MATCHES \"=\")\n    # We want to split the string on the `=` sign, but cmake doesn't\n    # provide much in the way of helpers for this, so we rewrite the\n    # `=` sign to `;` so that we can treat it as a cmake list and\n    # then index into the components\n    string(REPLACE \"=\" \";\" src_path_list \"${src_path}\")\n    list(GET src_path_list 0 src_path)\n    # Note that we ignore the `namespace_dir` in the alias case\n    # in order to allow aliasing a source to the top level `__main__.py`\n    # filename.\n    list(GET src_path_list 1 dest_path)\n  else()\n    unset(dest_path)\n  endif()\n\n  get_filename_component(abs_source \"${src_path}\" ABSOLUTE)\n  if(NOT DEFINED dest_path)\n    file(RELATIVE_PATH rel_src \"${ARG_BASE_DIR}\" \"${abs_source}\")\n    if(\"${rel_src}\" MATCHES \"^../\")\n      message(\n        FATAL_ERROR \"${LIB_NAME}: source file \\\"${abs_source}\\\" is not inside \"\n        \"the base directory ${ARG_BASE_DIR}\"\n      )\n    endif()\n    set(dest_path \"${namespace_dir}${rel_src}\")\n  endif()\n\n  set(\"${src_path_output}\" \"${abs_source}\" PARENT_SCOPE)\n  set(\"${dest_path_output}\" \"${dest_path}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBPythonTestAddTests.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\n# Add a command to be emitted to the CTest file\nset(ctest_script)\nfunction(add_command CMD)\n  set(escaped_args \"\")\n  foreach(arg ${ARGN})\n    # Escape all arguments using \"Bracket Argument\" syntax\n    # We could skip this for argument that don't contain any special\n    # characters if we wanted to make the output slightly more human-friendly.\n    set(escaped_args \"${escaped_args} [==[${arg}]==]\")\n  endforeach()\n  set(ctest_script \"${ctest_script}${CMD}(${escaped_args})\\n\" PARENT_SCOPE)\nendfunction()\n\nif(NOT EXISTS \"${TEST_EXECUTABLE}\")\n  message(FATAL_ERROR \"Test executable does not exist: ${TEST_EXECUTABLE}\")\nendif()\nexecute_process(\n  COMMAND ${CMAKE_COMMAND} -E env ${TEST_ENV} \"${TEST_INTERPRETER}\" \"${TEST_EXECUTABLE}\" --list-tests\n  WORKING_DIRECTORY \"${TEST_WORKING_DIR}\"\n  OUTPUT_VARIABLE output\n  RESULT_VARIABLE result\n)\nif(NOT \"${result}\" EQUAL 0)\n  string(REPLACE \"\\n\" \"\\n  \" output \"${output}\")\n  message(\n    FATAL_ERROR\n    \"Error running test executable: ${TEST_EXECUTABLE}\\n\"\n    \"Output:\\n\"\n    \"  ${output}\\n\"\n  )\nendif()\n\n# Parse output\nstring(REPLACE \"\\n\" \";\" tests_list \"${output}\")\nforeach(test_name ${tests_list})\n  add_command(\n    add_test\n    \"${TEST_PREFIX}${test_name}\"\n    ${CMAKE_COMMAND} -E env ${TEST_ENV}\n    \"${TEST_INTERPRETER}\" \"${TEST_EXECUTABLE}\" \"${test_name}\"\n  )\n  add_command(\n    set_tests_properties\n    \"${TEST_PREFIX}${test_name}\"\n    PROPERTIES\n    WORKING_DIRECTORY \"${TEST_WORKING_DIR}\"\n    ${TEST_PROPERTIES}\n  )\nendforeach()\n\n# Set a list of discovered tests in the parent scope, in case users\n# want access to this list as a CMake variable\nif(TEST_LIST)\n  add_command(set ${TEST_LIST} ${tests_list})\nendif()\n\nfile(WRITE \"${CTEST_FILE}\" \"${ctest_script}\")\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBThriftCppLibrary.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\ninclude(FBCMakeParseArgs)\n\n# Generate a C++ library from a thrift file\n#\n# Parameters:\n# - SERVICES <svc1> [<svc2> ...]\n#   The names of the services defined in the thrift file.\n# - DEPENDS <dep1> [<dep2> ...]\n#   A list of other thrift C++ libraries that this library depends on.\n# - OPTIONS <opt1> [<opt2> ...]\n#   A list of options to pass to the thrift compiler.\n# - INCLUDE_DIR <path>\n#   The sub-directory where generated headers will be installed.\n#   Defaults to \"include\" if not specified.  The caller must still call\n#   install() to install the thrift library if desired.\n# - THRIFT_INCLUDE_DIR <path>\n#   The sub-directory where generated headers will be installed.\n#   Defaults to \"${INCLUDE_DIR}/thrift-files\" if not specified.\n#   The caller must still call install() to install the thrift library if\n#   desired.\nfunction(add_fbthrift_cpp_library LIB_NAME THRIFT_FILE)\n  # Parse the arguments\n  set(one_value_args INCLUDE_DIR THRIFT_INCLUDE_DIR)\n  set(multi_value_args SERVICES DEPENDS OPTIONS)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n  if(NOT DEFINED ARG_INCLUDE_DIR)\n    set(ARG_INCLUDE_DIR \"include\")\n  endif()\n  if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR)\n    set(ARG_THRIFT_INCLUDE_DIR \"${ARG_INCLUDE_DIR}/thrift-files\")\n  endif()\n\n  get_filename_component(base ${THRIFT_FILE} NAME_WE)\n  get_filename_component(\n    output_dir\n    ${CMAKE_CURRENT_BINARY_DIR}/${THRIFT_FILE}\n    DIRECTORY\n  )\n\n  # Generate relative paths in #includes\n  file(\n    RELATIVE_PATH include_prefix\n    \"${CMAKE_SOURCE_DIR}\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}\"\n  )\n  get_filename_component(include_prefix ${include_prefix} DIRECTORY)\n\n  if (NOT \"${include_prefix}\" STREQUAL \"\")\n    list(APPEND ARG_OPTIONS \"include_prefix=${include_prefix}\")\n  endif()\n  # CMake 3.12 is finally getting a list(JOIN) function, but until then\n  # treating the list as a string and replacing the semicolons is good enough.\n  string(REPLACE \";\" \",\" GEN_ARG_STR \"${ARG_OPTIONS}\")\n\n  # Compute the list of generated files\n  list(APPEND generated_headers\n    \"${output_dir}/gen-cpp2/${base}_constants.h\"\n    \"${output_dir}/gen-cpp2/${base}_types.h\"\n    \"${output_dir}/gen-cpp2/${base}_types.tcc\"\n    \"${output_dir}/gen-cpp2/${base}_types_custom_protocol.h\"\n    \"${output_dir}/gen-cpp2/${base}_metadata.h\"\n  )\n  list(APPEND generated_sources\n    \"${output_dir}/gen-cpp2/${base}_constants.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_data.h\"\n    \"${output_dir}/gen-cpp2/${base}_data.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_types.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_types_binary.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_types_compact.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_types_serialization.cpp\"\n    \"${output_dir}/gen-cpp2/${base}_metadata.cpp\"\n  )\n  foreach(service IN LISTS ARG_SERVICES)\n    list(APPEND generated_headers\n      \"${output_dir}/gen-cpp2/${service}.h\"\n      \"${output_dir}/gen-cpp2/${service}.tcc\"\n      \"${output_dir}/gen-cpp2/${service}AsyncClient.h\"\n      \"${output_dir}/gen-cpp2/${service}_custom_protocol.h\"\n    )\n    list(APPEND generated_sources\n      \"${output_dir}/gen-cpp2/${service}.cpp\"\n      \"${output_dir}/gen-cpp2/${service}AsyncClient.cpp\"\n      \"${output_dir}/gen-cpp2/${service}_processmap_binary.cpp\"\n      \"${output_dir}/gen-cpp2/${service}_processmap_compact.cpp\"\n    )\n  endforeach()\n\n  # This generator expression gets the list of include directories required\n  # for all of our dependencies.\n  # It requires using COMMAND_EXPAND_LISTS in the add_custom_command() call\n  # below.  COMMAND_EXPAND_LISTS is only available in CMake 3.8+\n  # If we really had to support older versions of CMake we would probably need\n  # to use a wrapper script around the thrift compiler that could take the\n  # include list as a single argument and split it up before invoking the\n  # thrift compiler.\n  if (NOT POLICY CMP0067)\n    message(FATAL_ERROR \"add_fbthrift_cpp_library() requires CMake 3.8+\")\n  endif()\n  set(\n    thrift_include_options\n    \"-I;$<JOIN:$<TARGET_PROPERTY:${LIB_NAME}.thrift_includes,INTERFACE_INCLUDE_DIRECTORIES>,;-I;>\"\n  )\n\n  # Emit the rule to run the thrift compiler\n  add_custom_command(\n    OUTPUT\n      ${generated_headers}\n      ${generated_sources}\n    COMMAND_EXPAND_LISTS\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E make_directory \"${output_dir}\"\n    COMMAND\n      \"${FBTHRIFT_COMPILER}\"\n      --legacy-strict\n      --gen \"mstch_cpp2:${GEN_ARG_STR}\"\n      \"${thrift_include_options}\"\n      -I \"${FBTHRIFT_INCLUDE_DIR}\"\n      -o \"${output_dir}\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}\"\n    WORKING_DIRECTORY\n      \"${CMAKE_BINARY_DIR}\"\n    MAIN_DEPENDENCY\n      \"${THRIFT_FILE}\"\n    DEPENDS\n      ${ARG_DEPENDS}\n      \"${FBTHRIFT_COMPILER}\"\n  )\n\n  # Now emit the library rule to compile the sources\n  if (BUILD_SHARED_LIBS)\n    set(LIB_TYPE SHARED)\n  else ()\n    set(LIB_TYPE STATIC)\n  endif ()\n\n  add_library(\n    \"${LIB_NAME}\" ${LIB_TYPE}\n    ${generated_sources}\n  )\n\n  target_include_directories(\n    \"${LIB_NAME}\"\n    PUBLIC\n      \"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>\"\n      \"$<INSTALL_INTERFACE:${ARG_INCLUDE_DIR}>\"\n      ${Xxhash_INCLUDE_DIR}\n  )\n  target_link_libraries(\n    \"${LIB_NAME}\"\n    PUBLIC\n      ${ARG_DEPENDS}\n      FBThrift::thriftcpp2\n      Folly::folly\n      mvfst::mvfst_server_async_tran\n      mvfst::mvfst_server\n      ${Xxhash_LIBRARY}\n  )\n\n  # Add ${generated_headers} to the PUBLIC_HEADER property for ${LIB_NAME}\n  #\n  # This allows callers to install it using\n  # \"install(TARGETS ${LIB_NAME} PUBLIC_HEADER)\"\n  # However, note that CMake's PUBLIC_HEADER behavior is rather inflexible,\n  # and does have any way to preserve header directory structure.  Callers\n  # must be careful to use the correct PUBLIC_HEADER DESTINATION parameter\n  # when doing this, to put the files the correct directory themselves.\n  # We define a HEADER_INSTALL_DIR property with the include directory prefix,\n  # so typically callers should specify the PUBLIC_HEADER DESTINATION as\n  # \"$<TARGET_PROPERTY:${LIB_NAME},HEADER_INSTALL_DIR>\"\n  set_property(\n    TARGET \"${LIB_NAME}\"\n    PROPERTY PUBLIC_HEADER ${generated_headers}\n  )\n\n  # Define a dummy interface library to help propagate the thrift include\n  # directories between dependencies.\n  add_library(\"${LIB_NAME}.thrift_includes\" INTERFACE)\n  target_include_directories(\n    \"${LIB_NAME}.thrift_includes\"\n    INTERFACE\n      \"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\"\n      \"$<INSTALL_INTERFACE:${ARG_THRIFT_INCLUDE_DIR}>\"\n  )\n  foreach(dep IN LISTS ARG_DEPENDS)\n    target_link_libraries(\n      \"${LIB_NAME}.thrift_includes\"\n      INTERFACE \"${dep}.thrift_includes\"\n    )\n  endforeach()\n\n  set_target_properties(\n    \"${LIB_NAME}\"\n    PROPERTIES\n      EXPORT_PROPERTIES \"THRIFT_INSTALL_DIR\"\n      THRIFT_INSTALL_DIR \"${ARG_THRIFT_INCLUDE_DIR}/${include_prefix}\"\n      HEADER_INSTALL_DIR \"${ARG_INCLUDE_DIR}/${include_prefix}/gen-cpp2\"\n  )\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBThriftLibrary.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\ninclude(FBCMakeParseArgs)\ninclude(FBThriftPyLibrary)\ninclude(FBThriftCppLibrary)\n\n#\n# add_fbthrift_library()\n#\n# This is a convenience function that generates thrift libraries for multiple\n# languages.\n#\n# For example:\n#   add_fbthrift_library(\n#     foo foo.thrift\n#     LANGUAGES cpp py\n#     SERVICES Foo\n#     DEPENDS bar)\n#\n# will be expanded into two separate calls:\n#\n# add_fbthrift_cpp_library(foo_cpp foo.thrift SERVICES Foo DEPENDS bar_cpp)\n# add_fbthrift_py_library(foo_py foo.thrift SERVICES Foo DEPENDS bar_py)\n#\nfunction(add_fbthrift_library LIB_NAME THRIFT_FILE)\n  # Parse the arguments\n  set(one_value_args PY_NAMESPACE INCLUDE_DIR THRIFT_INCLUDE_DIR)\n  set(multi_value_args SERVICES DEPENDS LANGUAGES CPP_OPTIONS PY_OPTIONS)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n\n  if(NOT DEFINED ARG_INCLUDE_DIR)\n    set(ARG_INCLUDE_DIR \"include\")\n  endif()\n  if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR)\n    set(ARG_THRIFT_INCLUDE_DIR \"${ARG_INCLUDE_DIR}/thrift-files\")\n  endif()\n\n  # CMake 3.12+ adds list(TRANSFORM) which would be nice to use here, but for\n  # now we still want to support older versions of CMake.\n  set(CPP_DEPENDS)\n  set(PY_DEPENDS)\n  foreach(dep IN LISTS ARG_DEPENDS)\n    list(APPEND CPP_DEPENDS \"${dep}_cpp\")\n    list(APPEND PY_DEPENDS \"${dep}_py\")\n  endforeach()\n\n  foreach(lang IN LISTS ARG_LANGUAGES)\n    if (\"${lang}\" STREQUAL \"cpp\")\n      add_fbthrift_cpp_library(\n        \"${LIB_NAME}_cpp\" \"${THRIFT_FILE}\"\n        SERVICES ${ARG_SERVICES}\n        DEPENDS ${CPP_DEPENDS}\n        OPTIONS ${ARG_CPP_OPTIONS}\n        INCLUDE_DIR \"${ARG_INCLUDE_DIR}\"\n        THRIFT_INCLUDE_DIR \"${ARG_THRIFT_INCLUDE_DIR}\"\n      )\n    elseif (\"${lang}\" STREQUAL \"py\" OR \"${lang}\" STREQUAL \"python\")\n      if (DEFINED ARG_PY_NAMESPACE)\n        set(namespace_args NAMESPACE \"${ARG_PY_NAMESPACE}\")\n      endif()\n      add_fbthrift_py_library(\n        \"${LIB_NAME}_py\" \"${THRIFT_FILE}\"\n        SERVICES ${ARG_SERVICES}\n        ${namespace_args}\n        DEPENDS ${PY_DEPENDS}\n        OPTIONS ${ARG_PY_OPTIONS}\n        THRIFT_INCLUDE_DIR \"${ARG_THRIFT_INCLUDE_DIR}\"\n      )\n    else()\n      message(\n        FATAL_ERROR \"unknown language for thrift library ${LIB_NAME}: ${lang}\"\n      )\n    endif()\n  endforeach()\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FBThriftPyLibrary.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n\ninclude(FBCMakeParseArgs)\ninclude(FBPythonBinary)\n\n# Generate a Python library from a thrift file\nfunction(add_fbthrift_py_library LIB_NAME THRIFT_FILE)\n  # Parse the arguments\n  set(one_value_args NAMESPACE THRIFT_INCLUDE_DIR)\n  set(multi_value_args SERVICES DEPENDS OPTIONS)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n\n  if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR)\n    set(ARG_THRIFT_INCLUDE_DIR \"include/thrift-files\")\n  endif()\n\n  get_filename_component(base ${THRIFT_FILE} NAME_WE)\n  set(output_dir \"${CMAKE_CURRENT_BINARY_DIR}/${THRIFT_FILE}-py\")\n\n  # Parse the namespace value\n  if (NOT DEFINED ARG_NAMESPACE)\n    set(ARG_NAMESPACE \"${base}\")\n  endif()\n\n  string(REPLACE \".\" \"/\" namespace_dir \"${ARG_NAMESPACE}\")\n  set(py_output_dir \"${output_dir}/gen-py/${namespace_dir}\")\n  list(APPEND generated_sources\n    \"${py_output_dir}/__init__.py\"\n    \"${py_output_dir}/ttypes.py\"\n    \"${py_output_dir}/constants.py\"\n  )\n  foreach(service IN LISTS ARG_SERVICES)\n    list(APPEND generated_sources\n      ${py_output_dir}/${service}.py\n    )\n  endforeach()\n\n  # Define a dummy interface library to help propagate the thrift include\n  # directories between dependencies.\n  add_library(\"${LIB_NAME}.thrift_includes\" INTERFACE)\n  target_include_directories(\n    \"${LIB_NAME}.thrift_includes\"\n    INTERFACE\n      \"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\"\n      \"$<INSTALL_INTERFACE:${ARG_THRIFT_INCLUDE_DIR}>\"\n  )\n  foreach(dep IN LISTS ARG_DEPENDS)\n    target_link_libraries(\n      \"${LIB_NAME}.thrift_includes\"\n      INTERFACE \"${dep}.thrift_includes\"\n    )\n  endforeach()\n\n  # This generator expression gets the list of include directories required\n  # for all of our dependencies.\n  # It requires using COMMAND_EXPAND_LISTS in the add_custom_command() call\n  # below.  COMMAND_EXPAND_LISTS is only available in CMake 3.8+\n  # If we really had to support older versions of CMake we would probably need\n  # to use a wrapper script around the thrift compiler that could take the\n  # include list as a single argument and split it up before invoking the\n  # thrift compiler.\n  if (NOT POLICY CMP0067)\n    message(FATAL_ERROR \"add_fbthrift_py_library() requires CMake 3.8+\")\n  endif()\n  set(\n    thrift_include_options\n    \"-I;$<JOIN:$<TARGET_PROPERTY:${LIB_NAME}.thrift_includes,INTERFACE_INCLUDE_DIRECTORIES>,;-I;>\"\n  )\n\n  # Always force generation of \"new-style\" python classes for Python 2\n  list(APPEND ARG_OPTIONS \"new_style\")\n  # CMake 3.12 is finally getting a list(JOIN) function, but until then\n  # treating the list as a string and replacing the semicolons is good enough.\n  string(REPLACE \";\" \",\" GEN_ARG_STR \"${ARG_OPTIONS}\")\n\n  # Emit the rule to run the thrift compiler\n  add_custom_command(\n    OUTPUT\n      ${generated_sources}\n    COMMAND_EXPAND_LISTS\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E make_directory \"${output_dir}\"\n    COMMAND\n      \"${FBTHRIFT_COMPILER}\"\n      --legacy-strict\n      --gen \"py:${GEN_ARG_STR}\"\n      \"${thrift_include_options}\"\n      -o \"${output_dir}\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}\"\n    WORKING_DIRECTORY\n      \"${CMAKE_BINARY_DIR}\"\n    MAIN_DEPENDENCY\n      \"${THRIFT_FILE}\"\n    DEPENDS\n      \"${FBTHRIFT_COMPILER}\"\n  )\n\n  # We always want to pass the namespace as \"\" to this call:\n  # thrift will already emit the files with the desired namespace prefix under\n  # gen-py.  We don't want add_fb_python_library() to prepend the namespace a\n  # second time.\n  add_fb_python_library(\n    \"${LIB_NAME}\"\n    BASE_DIR \"${output_dir}/gen-py\"\n    NAMESPACE \"\"\n    SOURCES ${generated_sources}\n    DEPENDS ${ARG_DEPENDS} FBThrift::thrift_py\n  )\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindCares.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nfind_path(CARES_INCLUDE_DIR NAMES ares.h)\nfind_library(CARES_LIBRARIES NAMES cares)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Cares DEFAULT_MSG CARES_LIBRARIES CARES_INCLUDE_DIR)\n\nmark_as_advanced(\n  CARES_LIBRARIES\n  CARES_INCLUDE_DIR\n)\n\nif(NOT TARGET cares)\n    if(\"${CARES_LIBRARIES}\" MATCHES \".*.a$\")\n    add_library(cares STATIC IMPORTED)\n    else()\n    add_library(cares SHARED IMPORTED)\n    endif()\n    set_target_properties(\n        cares\n        PROPERTIES\n            IMPORTED_LOCATION ${CARES_LIBRARIES}\n            INTERFACE_INCLUDE_DIRECTORIES ${CARES_INCLUDE_DIR}\n    )\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindDoubleConversion.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n\n# Finds libdouble-conversion.\n#\n# This module defines:\n# DOUBLE_CONVERSION_INCLUDE_DIR\n# DOUBLE_CONVERSION_LIBRARY\n#\n\nfind_path(DOUBLE_CONVERSION_INCLUDE_DIR double-conversion/double-conversion.h)\nfind_library(DOUBLE_CONVERSION_LIBRARY NAMES double-conversion)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(\n  DoubleConversion\n  DEFAULT_MSG\n  DOUBLE_CONVERSION_LIBRARY DOUBLE_CONVERSION_INCLUDE_DIR)\n\nmark_as_advanced(DOUBLE_CONVERSION_INCLUDE_DIR DOUBLE_CONVERSION_LIBRARY)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindGMock.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# Find libgmock\n#\n#  LIBGMOCK_DEFINES     - List of defines when using libgmock.\n#  LIBGMOCK_INCLUDE_DIR - where to find gmock/gmock.h, etc.\n#  LIBGMOCK_LIBRARIES   - List of libraries when using libgmock.\n#  LIBGMOCK_FOUND       - True if libgmock found.\n\nIF (LIBGMOCK_INCLUDE_DIR)\n  # Already in cache, be silent\n  SET(LIBGMOCK_FIND_QUIETLY TRUE)\nENDIF ()\n\nfind_package(GTest CONFIG QUIET)\nif (TARGET GTest::gmock)\n  get_target_property(LIBGMOCK_DEFINES GTest::gtest INTERFACE_COMPILE_DEFINITIONS)\n  if (NOT ${LIBGMOCK_DEFINES})\n    # Explicitly set to empty string if not found to avoid it being\n    # set to NOTFOUND and breaking compilation\n    set(LIBGMOCK_DEFINES \"\")\n  endif()\n  get_target_property(LIBGMOCK_INCLUDE_DIR GTest::gtest INTERFACE_INCLUDE_DIRECTORIES)\n  set(LIBGMOCK_LIBRARIES GTest::gmock_main GTest::gmock GTest::gtest)\n  set(LIBGMOCK_FOUND ON)\n  message(STATUS \"Found gmock via config, defines=${LIBGMOCK_DEFINES}, include=${LIBGMOCK_INCLUDE_DIR}, libs=${LIBGMOCK_LIBRARIES}\")\nelse()\n\n  FIND_PATH(LIBGMOCK_INCLUDE_DIR gmock/gmock.h)\n\n  FIND_LIBRARY(LIBGMOCK_MAIN_LIBRARY_DEBUG NAMES gmock_maind)\n  FIND_LIBRARY(LIBGMOCK_MAIN_LIBRARY_RELEASE NAMES gmock_main)\n  FIND_LIBRARY(LIBGMOCK_LIBRARY_DEBUG NAMES gmockd)\n  FIND_LIBRARY(LIBGMOCK_LIBRARY_RELEASE NAMES gmock)\n  FIND_LIBRARY(LIBGTEST_LIBRARY_DEBUG NAMES gtestd)\n  FIND_LIBRARY(LIBGTEST_LIBRARY_RELEASE NAMES gtest)\n\n  find_package(Threads REQUIRED)\n  INCLUDE(SelectLibraryConfigurations)\n  SELECT_LIBRARY_CONFIGURATIONS(LIBGMOCK_MAIN)\n  SELECT_LIBRARY_CONFIGURATIONS(LIBGMOCK)\n  SELECT_LIBRARY_CONFIGURATIONS(LIBGTEST)\n\n  set(LIBGMOCK_LIBRARIES\n    ${LIBGMOCK_MAIN_LIBRARY}\n    ${LIBGMOCK_LIBRARY}\n    ${LIBGTEST_LIBRARY}\n    Threads::Threads\n  )\n\n  if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    # The GTEST_LINKED_AS_SHARED_LIBRARY macro must be set properly on Windows.\n    #\n    # There isn't currently an easy way to determine if a library was compiled as\n    # a shared library on Windows, so just assume we've been built against a\n    # shared build of gmock for now.\n    SET(LIBGMOCK_DEFINES \"GTEST_LINKED_AS_SHARED_LIBRARY=1\" CACHE STRING \"\")\n  endif()\n\n  # handle the QUIETLY and REQUIRED arguments and set LIBGMOCK_FOUND to TRUE if\n  # all listed variables are TRUE\n  INCLUDE(FindPackageHandleStandardArgs)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(\n    GMock\n    DEFAULT_MSG\n    LIBGMOCK_MAIN_LIBRARY\n    LIBGMOCK_LIBRARY\n    LIBGTEST_LIBRARY\n    LIBGMOCK_LIBRARIES\n    LIBGMOCK_INCLUDE_DIR\n  )\n\n  MARK_AS_ADVANCED(\n    LIBGMOCK_DEFINES\n    LIBGMOCK_MAIN_LIBRARY\n    LIBGMOCK_LIBRARY\n    LIBGTEST_LIBRARY\n    LIBGMOCK_LIBRARIES\n    LIBGMOCK_INCLUDE_DIR\n  )\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindGflags.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# Find libgflags.\n# There's a lot of compatibility cruft going on in here, both\n# to deal with changes across the FB consumers of this and also\n# to deal with variances in behavior of cmake itself.\n#\n# Since this file is named FindGflags.cmake the cmake convention\n# is for the module to export both GFLAGS_FOUND and Gflags_FOUND.\n# The convention expected by consumers is that we export the\n# following variables, even though these do not match the cmake\n# conventions:\n#\n#  LIBGFLAGS_INCLUDE_DIR - where to find gflags/gflags.h, etc.\n#  LIBGFLAGS_LIBRARY     - List of libraries when using libgflags.\n#  LIBGFLAGS_FOUND       - True if libgflags found.\n#\n# We need to be able to locate gflags both from an installed\n# cmake config file and just from the raw headers and libs, so\n# test for the former and then the latter, and then stick\n# the results together and export them into the variables\n# listed above.\n#\n# For forwards compatibility, we export the following variables:\n#\n#  gflags_INCLUDE_DIR - where to find gflags/gflags.h, etc.\n#  gflags_TARGET / GFLAGS_TARGET / gflags_LIBRARIES\n#                     - List of libraries when using libgflags.\n#  gflags_FOUND       - True if libgflags found.\n#\n\nIF (LIBGFLAGS_INCLUDE_DIR)\n  # Already in cache, be silent\n  SET(Gflags_FIND_QUIETLY TRUE)\nENDIF ()\n\nfind_package(gflags CONFIG QUIET)\nif (gflags_FOUND)\n  if (NOT Gflags_FIND_QUIETLY)\n    message(STATUS \"Found gflags from package config ${gflags_CONFIG}\")\n  endif()\n  # Re-export the config-specified libs with our local names\n  set(LIBGFLAGS_LIBRARY ${gflags_LIBRARIES})\n  set(LIBGFLAGS_INCLUDE_DIR ${gflags_INCLUDE_DIR})\n  if(NOT EXISTS \"${gflags_INCLUDE_DIR}\")\n    # The gflags-devel RPM on recent RedHat-based systems is somewhat broken.\n    # RedHat symlinks /lib64 to /usr/lib64, and this breaks some of the\n    # relative path computation performed in gflags-config.cmake.  The package\n    # config file ends up being found via /lib64, but the relative path\n    # computation it does only works if it was found in /usr/lib64.\n    # If gflags_INCLUDE_DIR does not actually exist, simply default it to\n    # /usr/include on these systems.\n    set(LIBGFLAGS_INCLUDE_DIR \"/usr/include\")\n    set(GFLAGS_INCLUDE_DIR \"/usr/include\")\n  endif()\n  set(LIBGFLAGS_FOUND ${gflags_FOUND})\n  # cmake module compat\n  set(GFLAGS_FOUND ${gflags_FOUND})\n  set(Gflags_FOUND ${gflags_FOUND})\nelse()\n  FIND_PATH(LIBGFLAGS_INCLUDE_DIR gflags/gflags.h)\n\n  FIND_LIBRARY(LIBGFLAGS_LIBRARY_DEBUG NAMES gflagsd gflags_staticd)\n  FIND_LIBRARY(LIBGFLAGS_LIBRARY_RELEASE NAMES gflags gflags_static)\n\n  INCLUDE(SelectLibraryConfigurations)\n  SELECT_LIBRARY_CONFIGURATIONS(LIBGFLAGS)\n\n  # handle the QUIETLY and REQUIRED arguments and set LIBGFLAGS_FOUND to TRUE if\n  # all listed variables are TRUE\n  INCLUDE(FindPackageHandleStandardArgs)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(gflags DEFAULT_MSG LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR)\n  # cmake module compat\n  set(Gflags_FOUND ${GFLAGS_FOUND})\n  # compat with some existing FindGflags consumers\n  set(LIBGFLAGS_FOUND ${GFLAGS_FOUND})\n\n  # Compat with the gflags CONFIG based detection\n  set(gflags_FOUND ${GFLAGS_FOUND})\n  set(gflags_INCLUDE_DIR ${LIBGFLAGS_INCLUDE_DIR})\n  set(gflags_LIBRARIES ${LIBGFLAGS_LIBRARY})\n  set(GFLAGS_TARGET ${LIBGFLAGS_LIBRARY})\n  set(gflags_TARGET ${LIBGFLAGS_LIBRARY})\n\n  MARK_AS_ADVANCED(LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR)\nendif()\n\n# Compat with the gflags CONFIG based detection\nif (LIBGFLAGS_FOUND AND NOT TARGET gflags)\n  add_library(gflags UNKNOWN IMPORTED)\n  if(TARGET gflags-shared)\n    # If the installed gflags CMake package config defines a gflags-shared\n    # target but not gflags, just make the gflags target that we define\n    # depend on the gflags-shared target.\n    target_link_libraries(gflags INTERFACE gflags-shared)\n    # Export LIBGFLAGS_LIBRARY as the gflags-shared target in this case.\n    set(LIBGFLAGS_LIBRARY gflags-shared)\n  else()\n    set_target_properties(\n      gflags\n      PROPERTIES\n        IMPORTED_LINK_INTERFACE_LANGUAGES \"C\"\n        IMPORTED_LOCATION \"${LIBGFLAGS_LIBRARY}\"\n        INTERFACE_INCLUDE_DIRECTORIES \"${LIBGFLAGS_INCLUDE_DIR}\"\n    )\n  endif()\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindGlog.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# - Try to find Glog\n# Once done, this will define\n#\n# GLOG_FOUND - system has Glog\n# GLOG_INCLUDE_DIRS - the Glog include directories\n# GLOG_LIBRARIES - link these to use Glog\n\ninclude(FindPackageHandleStandardArgs)\ninclude(SelectLibraryConfigurations)\n\nfind_library(GLOG_LIBRARY_RELEASE glog\n  PATHS ${GLOG_LIBRARYDIR})\nfind_library(GLOG_LIBRARY_DEBUG glogd\n  PATHS ${GLOG_LIBRARYDIR})\n\nfind_path(GLOG_INCLUDE_DIR glog/logging.h\n  PATHS ${GLOG_INCLUDEDIR})\n\nselect_library_configurations(GLOG)\n\nfind_package_handle_standard_args(Glog DEFAULT_MSG\n  GLOG_LIBRARY\n  GLOG_INCLUDE_DIR)\n\nmark_as_advanced(\n  GLOG_LIBRARY\n  GLOG_INCLUDE_DIR)\n\nset(GLOG_LIBRARIES ${GLOG_LIBRARY})\nset(GLOG_INCLUDE_DIRS ${GLOG_INCLUDE_DIR})\n\nif (NOT TARGET glog::glog)\n  add_library(glog::glog UNKNOWN IMPORTED)\n  set_target_properties(glog::glog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES \"${GLOG_INCLUDE_DIRS}\")\n  set_target_properties(glog::glog PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES \"C\" IMPORTED_LOCATION \"${GLOG_LIBRARIES}\")\n  set_target_properties(glog::glog PROPERTIES\n    INTERFACE_COMPILE_DEFINITIONS \"GLOG_USE_GLOG_EXPORT\")\n\n  find_package(Gflags)\n  if(GFLAGS_FOUND)\n    message(STATUS \"Found gflags as a dependency of glog::glog, include=${LIBGFLAGS_INCLUDE_DIR}, libs=${LIBGFLAGS_LIBRARY}\")\n    set_property(TARGET glog::glog APPEND PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ${LIBGFLAGS_LIBRARY})\n  endif()\n\n  find_package(LibUnwind)\n  if(LIBUNWIND_FOUND)\n    message(STATUS \"Found LibUnwind as a dependency of glog::glog, include=${LIBUNWIND_INCLUDE_DIR}, libs=${LIBUNWIND_LIBRARY}\")\n    set_property(TARGET glog::glog APPEND PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ${LIBUNWIND_LIBRARY})\n  endif()\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindLMDB.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This software may be used and distributed according to the terms of the\n# GNU General Public License version 2.\n\nfind_library(LMDB_LIBRARIES NAMES lmdb liblmdb)\nmark_as_advanced(LMDB_LIBRARIES)\n\nfind_path(LMDB_INCLUDE_DIR NAMES  lmdb.h)\nmark_as_advanced(LMDB_INCLUDE_DIR)\n\nfind_package_handle_standard_args(\n     LMDB\n     REQUIRED_VARS LMDB_LIBRARIES LMDB_INCLUDE_DIR)\n\nif(LMDB_FOUND)\n  set(LMDB_LIBRARIES ${LMDB_LIBRARIES})\n  set(LMDB_INCLUDE_DIR, ${LMDB_INCLUDE_DIR})\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindLibEvent.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# - Find LibEvent (a cross event library)\n# This module defines\n# LIBEVENT_INCLUDE_DIR, where to find LibEvent headers\n# LIBEVENT_LIB, LibEvent libraries\n# LibEvent_FOUND, If false, do not try to use libevent\n\nset(LibEvent_EXTRA_PREFIXES /usr/local /opt/local \"$ENV{HOME}\")\nforeach(prefix ${LibEvent_EXTRA_PREFIXES})\n  list(APPEND LibEvent_INCLUDE_PATHS \"${prefix}/include\")\n  list(APPEND LibEvent_LIB_PATHS \"${prefix}/lib\")\nendforeach()\n\nfind_package(Libevent CONFIG QUIET)\nif (TARGET event)\n  # Re-export the config under our own names\n\n  # Somewhat gross, but some vcpkg installed libevents have a relative\n  # `include` path exported into LIBEVENT_INCLUDE_DIRS, which triggers\n  # a cmake error because it resolves to the `include` dir within the\n  # folly repo, which is not something cmake allows to be in the\n  # INTERFACE_INCLUDE_DIRECTORIES.  Thankfully on such a system the\n  # actual include directory is already part of the global include\n  # directories, so we can just skip it.\n  if (NOT \"${LIBEVENT_INCLUDE_DIRS}\" STREQUAL \"include\")\n    set(LIBEVENT_INCLUDE_DIR ${LIBEVENT_INCLUDE_DIRS})\n  else()\n    set(LIBEVENT_INCLUDE_DIR)\n  endif()\n\n  # Unfortunately, with a bare target name `event`, downstream consumers\n  # of the package that depends on `Libevent` located via CONFIG end\n  # up exporting just a bare `event` in their libraries.  This is problematic\n  # because this in interpreted as just `-levent` with no library path.\n  # When libevent is not installed in the default installation prefix\n  # this results in linker errors.\n  # To resolve this, we ask cmake to lookup the full path to the library\n  # and use that instead.\n  cmake_policy(PUSH)\n  if(POLICY CMP0026)\n    # Allow reading the LOCATION property\n    cmake_policy(SET CMP0026 OLD)\n  endif()\n  get_target_property(LIBEVENT_LIB event LOCATION)\n  cmake_policy(POP)\n\n  set(LibEvent_FOUND ${Libevent_FOUND})\n  if (NOT LibEvent_FIND_QUIETLY)\n    message(STATUS \"Found libevent from package config include=${LIBEVENT_INCLUDE_DIRS} lib=${LIBEVENT_LIB}\")\n  endif()\nelse()\n  find_path(LIBEVENT_INCLUDE_DIR event.h PATHS ${LibEvent_INCLUDE_PATHS})\n  find_library(LIBEVENT_LIB NAMES event PATHS ${LibEvent_LIB_PATHS})\n\n  if (LIBEVENT_LIB AND LIBEVENT_INCLUDE_DIR)\n    set(LibEvent_FOUND TRUE)\n    set(LIBEVENT_LIB ${LIBEVENT_LIB})\n  else ()\n    set(LibEvent_FOUND FALSE)\n  endif ()\n\n  if (LibEvent_FOUND)\n    if (NOT LibEvent_FIND_QUIETLY)\n      message(STATUS \"Found libevent: ${LIBEVENT_LIB}\")\n    endif ()\n  else ()\n    if (LibEvent_FIND_REQUIRED)\n      message(FATAL_ERROR \"Could NOT find libevent.\")\n    endif ()\n    message(STATUS \"libevent NOT found.\")\n  endif ()\n\n  mark_as_advanced(\n    LIBEVENT_LIB\n    LIBEVENT_INCLUDE_DIR\n  )\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindLibUnwind.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ninclude(FindPackageHandleStandardArgs)\n\n# Prefer pkg-config: picks up transitive deps (e.g. lzma, zlib) that\n# static libunwind needs but a bare find_library would miss.\nfind_package(PkgConfig QUIET)\nif(PKG_CONFIG_FOUND)\n  pkg_check_modules(PC_LIBUNWIND QUIET libunwind)\nendif()\n\nif(PC_LIBUNWIND_FOUND)\n  find_path(LIBUNWIND_INCLUDE_DIR NAMES libunwind.h\n    HINTS ${PC_LIBUNWIND_INCLUDE_DIRS}\n    PATH_SUFFIXES libunwind)\n  mark_as_advanced(LIBUNWIND_INCLUDE_DIR)\n\n  # Resolve each library from the static set (Libs + Libs.private) to a\n  # full path.  This gives the linker everything it needs for a fully-static\n  # link without leaking imported targets through cmake exports.\n  set(LIBUNWIND_LIBRARIES \"\")\n  foreach(_lib IN LISTS PC_LIBUNWIND_STATIC_LIBRARIES)\n    find_library(_libunwind_dep_${_lib} NAMES ${_lib}\n      HINTS ${PC_LIBUNWIND_STATIC_LIBRARY_DIRS})\n    if(_libunwind_dep_${_lib})\n      list(APPEND LIBUNWIND_LIBRARIES ${_libunwind_dep_${_lib}})\n    else()\n      # Fall back to bare name; the linker will resolve -l<name>.\n      list(APPEND LIBUNWIND_LIBRARIES ${_lib})\n    endif()\n    unset(_libunwind_dep_${_lib} CACHE)\n  endforeach()\n  set(LIBUNWIND_LIBRARY \"${LIBUNWIND_LIBRARIES}\")\n\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUnwind\n    REQUIRED_VARS LIBUNWIND_LIBRARIES LIBUNWIND_INCLUDE_DIR)\nelse()\n  # Fallback for systems without pkg-config.\n  # When using prepackaged LLVM libunwind on Ubuntu, its includes are\n  # installed in a subdirectory.\n  find_path(LIBUNWIND_INCLUDE_DIR NAMES libunwind.h PATH_SUFFIXES libunwind)\n  mark_as_advanced(LIBUNWIND_INCLUDE_DIR)\n\n  find_library(LIBUNWIND_LIBRARY NAMES unwind)\n  mark_as_advanced(LIBUNWIND_LIBRARY)\n\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUnwind\n    REQUIRED_VARS LIBUNWIND_LIBRARY LIBUNWIND_INCLUDE_DIR)\nendif()\n\nif(LibUnwind_FOUND)\n  if(NOT LIBUNWIND_LIBRARIES)\n    set(LIBUNWIND_LIBRARIES ${LIBUNWIND_LIBRARY})\n  endif()\n  set(LIBUNWIND_INCLUDE_DIRS ${LIBUNWIND_INCLUDE_DIR})\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindLibiberty.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfind_path(LIBIBERTY_INCLUDE_DIR NAMES libiberty.h PATH_SUFFIXES libiberty)\nmark_as_advanced(LIBIBERTY_INCLUDE_DIR)\n\nfind_library(LIBIBERTY_LIBRARY NAMES iberty)\nmark_as_advanced(LIBIBERTY_LIBRARY)\n\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(\n  LIBIBERTY\n  REQUIRED_VARS LIBIBERTY_LIBRARY LIBIBERTY_INCLUDE_DIR)\n\nif(LIBIBERTY_FOUND)\n  set(LIBIBERTY_LIBRARIES ${LIBIBERTY_LIBRARY})\n  set(LIBIBERTY_INCLUDE_DIRS ${LIBIBERTY_INCLUDE_DIR})\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindPCRE.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\ninclude(FindPackageHandleStandardArgs)\nfind_path(PCRE_INCLUDE_DIR NAMES pcre.h)\nfind_library(PCRE_LIBRARY NAMES pcre)\nfind_package_handle_standard_args(\n  PCRE\n  DEFAULT_MSG\n  PCRE_LIBRARY\n  PCRE_INCLUDE_DIR\n)\nmark_as_advanced(PCRE_INCLUDE_DIR PCRE_LIBRARY)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindPCRE2.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\ninclude(FindPackageHandleStandardArgs)\nfind_path(PCRE2_INCLUDE_DIR NAMES pcre2.h)\nfind_library(PCRE2_LIBRARY NAMES pcre2-8)\nfind_package_handle_standard_args(\n  PCRE2\n  DEFAULT_MSG\n  PCRE2_LIBRARY\n  PCRE2_INCLUDE_DIR\n)\nset(PCRE2_DEFINES \"PCRE2_CODE_UNIT_WIDTH=8\")\nmark_as_advanced(PCRE2_INCLUDE_DIR PCRE2_LIBRARY PCRE2_DEFINES)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindRe2.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This software may be used and distributed according to the terms of the\n# GNU General Public License version 2.\n\nfind_library(RE2_LIBRARY re2)\nmark_as_advanced(RE2_LIBRARY)\n\nfind_path(RE2_INCLUDE_DIR NAMES re2/re2.h)\nmark_as_advanced(RE2_INCLUDE_DIR)\n\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(\n     RE2\n     REQUIRED_VARS RE2_LIBRARY RE2_INCLUDE_DIR)\n\nif(RE2_FOUND)\n  set(RE2_LIBRARY ${RE2_LIBRARY})\n  set(RE2_INCLUDE_DIR, ${RE2_INCLUDE_DIR})\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindSodium.cmake",
    "content": "# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>\n#\n# To the extent possible under law, the author(s) have dedicated all\n# copyright and related and neighboring rights to this software to the\n# public domain worldwide. This software is distributed without any warranty.\n#\n# You should have received a copy of the CC0 Public Domain Dedication\n# along with this software. If not, see\n#\n#     http://creativecommons.org/publicdomain/zero/1.0/\n#\n########################################################################\n# Tries to find the local libsodium installation.\n#\n# On Windows the sodium_DIR environment variable is used as a default\n# hint which can be overridden by setting the corresponding cmake variable.\n#\n# Once done the following variables will be defined:\n#\n#   sodium_FOUND\n#   sodium_INCLUDE_DIR\n#   sodium_LIBRARY_DEBUG\n#   sodium_LIBRARY_RELEASE\n#\n#\n# Furthermore an imported \"sodium\" target is created.\n#\n\nif (CMAKE_C_COMPILER_ID STREQUAL \"GNU\"\n    OR CMAKE_C_COMPILER_ID STREQUAL \"Clang\")\n    set(_GCC_COMPATIBLE 1)\nendif()\n\n# static library option\nif (NOT DEFINED sodium_USE_STATIC_LIBS)\n    option(sodium_USE_STATIC_LIBS \"enable to statically link against sodium\" OFF)\nendif()\nif(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))\n    unset(sodium_LIBRARY CACHE)\n    unset(sodium_LIBRARY_DEBUG CACHE)\n    unset(sodium_LIBRARY_RELEASE CACHE)\n    unset(sodium_DLL_DEBUG CACHE)\n    unset(sodium_DLL_RELEASE CACHE)\n    set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL \"internal change tracking variable\")\nendif()\n\n\n########################################################################\n# UNIX\nif (UNIX)\n    # import pkg-config\n    find_package(PkgConfig QUIET)\n    if (PKG_CONFIG_FOUND)\n        pkg_check_modules(sodium_PKG QUIET libsodium)\n    endif()\n\n    if(sodium_USE_STATIC_LIBS)\n        foreach(_libname ${sodium_PKG_STATIC_LIBRARIES})\n            if (NOT _libname MATCHES \"^lib.*\\\\.a$\") # ignore strings already ending with .a\n                list(INSERT sodium_PKG_STATIC_LIBRARIES 0 \"lib${_libname}.a\")\n            endif()\n        endforeach()\n        list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES)\n\n        # if pkgconfig for libsodium doesn't provide\n        # static lib info, then override PKG_STATIC here..\n        if (NOT sodium_PKG_STATIC_FOUND)\n            set(sodium_PKG_STATIC_LIBRARIES libsodium.a)\n        endif()\n\n        set(XPREFIX sodium_PKG_STATIC)\n    else()\n        if (NOT sodium_PKG_FOUND)\n            set(sodium_PKG_LIBRARIES sodium)\n        endif()\n\n        set(XPREFIX sodium_PKG)\n    endif()\n\n    find_path(sodium_INCLUDE_DIR sodium.h\n        HINTS ${${XPREFIX}_INCLUDE_DIRS}\n    )\n    find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES}\n        HINTS ${${XPREFIX}_LIBRARY_DIRS}\n    )\n    find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES}\n        HINTS ${${XPREFIX}_LIBRARY_DIRS}\n    )\n\n\n########################################################################\n# Windows\nelseif (WIN32)\n    set(sodium_DIR \"$ENV{sodium_DIR}\" CACHE FILEPATH \"sodium install directory\")\n    mark_as_advanced(sodium_DIR)\n\n    find_path(sodium_INCLUDE_DIR sodium.h\n        HINTS ${sodium_DIR}\n        PATH_SUFFIXES include\n    )\n\n    if (MSVC)\n        # detect target architecture\n        file(WRITE \"${CMAKE_CURRENT_BINARY_DIR}/arch.cpp\" [=[\n            #if defined _M_IX86\n            #error ARCH_VALUE x86_32\n            #elif defined _M_X64\n            #error ARCH_VALUE x86_64\n            #endif\n            #error ARCH_VALUE unknown\n        ]=])\n        try_compile(_UNUSED_VAR \"${CMAKE_CURRENT_BINARY_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}/arch.cpp\"\n            OUTPUT_VARIABLE _COMPILATION_LOG\n        )\n        string(REGEX REPLACE \".*ARCH_VALUE ([a-zA-Z0-9_]+).*\" \"\\\\1\" _TARGET_ARCH \"${_COMPILATION_LOG}\")\n\n        # construct library path\n        if (_TARGET_ARCH STREQUAL \"x86_32\")\n            string(APPEND _PLATFORM_PATH \"Win32\")\n        elseif(_TARGET_ARCH STREQUAL \"x86_64\")\n            string(APPEND _PLATFORM_PATH \"x64\")\n        else()\n            message(FATAL_ERROR \"the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.\")\n        endif()\n        string(APPEND _PLATFORM_PATH \"/$$CONFIG$$\")\n\n        if (MSVC_VERSION LESS 1900)\n            math(EXPR _VS_VERSION \"${MSVC_VERSION} / 10 - 60\")\n        else()\n            math(EXPR _VS_VERSION \"${MSVC_VERSION} / 10 - 50\")\n        endif()\n        string(APPEND _PLATFORM_PATH \"/v${_VS_VERSION}\")\n\n        if (sodium_USE_STATIC_LIBS)\n            string(APPEND _PLATFORM_PATH \"/static\")\n        else()\n            string(APPEND _PLATFORM_PATH \"/dynamic\")\n        endif()\n\n        string(REPLACE \"$$CONFIG$$\" \"Debug\" _DEBUG_PATH_SUFFIX \"${_PLATFORM_PATH}\")\n        string(REPLACE \"$$CONFIG$$\" \"Release\" _RELEASE_PATH_SUFFIX \"${_PLATFORM_PATH}\")\n\n        find_library(sodium_LIBRARY_DEBUG libsodium.lib\n            HINTS ${sodium_DIR}\n            PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}\n        )\n        find_library(sodium_LIBRARY_RELEASE libsodium.lib\n            HINTS ${sodium_DIR}\n            PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}\n        )\n        if (NOT sodium_USE_STATIC_LIBS)\n            set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES})\n            set(CMAKE_FIND_LIBRARY_SUFFIXES \".dll\")\n            find_library(sodium_DLL_DEBUG libsodium\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}\n            )\n            find_library(sodium_DLL_RELEASE libsodium\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}\n            )\n            set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK})\n        endif()\n\n    elseif(_GCC_COMPATIBLE)\n        if (sodium_USE_STATIC_LIBS)\n            find_library(sodium_LIBRARY_DEBUG libsodium.a\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES lib\n            )\n            find_library(sodium_LIBRARY_RELEASE libsodium.a\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES lib\n            )\n        else()\n            find_library(sodium_LIBRARY_DEBUG libsodium.dll.a\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES lib\n            )\n            find_library(sodium_LIBRARY_RELEASE libsodium.dll.a\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES lib\n            )\n\n            file(GLOB _DLL\n                LIST_DIRECTORIES false\n                RELATIVE \"${sodium_DIR}/bin\"\n                \"${sodium_DIR}/bin/libsodium*.dll\"\n            )\n            find_library(sodium_DLL_DEBUG ${_DLL} libsodium\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES bin\n            )\n            find_library(sodium_DLL_RELEASE ${_DLL} libsodium\n                HINTS ${sodium_DIR}\n                PATH_SUFFIXES bin\n            )\n        endif()\n    else()\n        message(FATAL_ERROR \"this platform is not supported by FindSodium.cmake\")\n    endif()\n\n\n########################################################################\n# unsupported\nelse()\n    message(FATAL_ERROR \"this platform is not supported by FindSodium.cmake\")\nendif()\n\n\n########################################################################\n# common stuff\n\n# extract sodium version\nif (sodium_INCLUDE_DIR)\n    set(_VERSION_HEADER \"${_INCLUDE_DIR}/sodium/version.h\")\n    if (EXISTS _VERSION_HEADER)\n        file(READ \"${_VERSION_HEADER}\" _VERSION_HEADER_CONTENT)\n        string(REGEX REPLACE \".*#[ \\t]*define[ \\t]*SODIUM_VERSION_STRING[ \\t]*\\\"([^\\n]*)\\\".*\" \"\\\\1\"\n            sodium_VERSION \"${_VERSION_HEADER_CONTENT}\")\n        set(sodium_VERSION \"${sodium_VERSION}\" PARENT_SCOPE)\n    endif()\nendif()\n\n# communicate results\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(\n    Sodium # The name must be either uppercase or match the filename case.\n    REQUIRED_VARS\n        sodium_LIBRARY_RELEASE\n        sodium_LIBRARY_DEBUG\n        sodium_INCLUDE_DIR\n    VERSION_VAR\n        sodium_VERSION\n)\n\nif(Sodium_FOUND)\n    set(sodium_LIBRARIES\n        optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG})\nendif()\n\n# mark file paths as advanced\nmark_as_advanced(sodium_INCLUDE_DIR)\nmark_as_advanced(sodium_LIBRARY_DEBUG)\nmark_as_advanced(sodium_LIBRARY_RELEASE)\nif (WIN32)\n    mark_as_advanced(sodium_DLL_DEBUG)\n    mark_as_advanced(sodium_DLL_RELEASE)\nendif()\n\n# create imported target\nif(sodium_USE_STATIC_LIBS)\n    set(_LIB_TYPE STATIC)\nelse()\n    set(_LIB_TYPE SHARED)\nendif()\n\nif(NOT TARGET sodium)\n    add_library(sodium ${_LIB_TYPE} IMPORTED)\nendif()\n\nset_target_properties(sodium PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${sodium_INCLUDE_DIR}\"\n    IMPORTED_LINK_INTERFACE_LANGUAGES \"C\"\n)\n\nif (sodium_USE_STATIC_LIBS)\n    set_target_properties(sodium PROPERTIES\n        INTERFACE_COMPILE_DEFINITIONS \"SODIUM_STATIC\"\n        IMPORTED_LOCATION \"${sodium_LIBRARY_RELEASE}\"\n        IMPORTED_LOCATION_DEBUG \"${sodium_LIBRARY_DEBUG}\"\n    )\nelse()\n    if (UNIX)\n        set_target_properties(sodium PROPERTIES\n            IMPORTED_LOCATION \"${sodium_LIBRARY_RELEASE}\"\n            IMPORTED_LOCATION_DEBUG \"${sodium_LIBRARY_DEBUG}\"\n        )\n    elseif (WIN32)\n        set_target_properties(sodium PROPERTIES\n            IMPORTED_IMPLIB \"${sodium_LIBRARY_RELEASE}\"\n            IMPORTED_IMPLIB_DEBUG \"${sodium_LIBRARY_DEBUG}\"\n        )\n        if (NOT (sodium_DLL_DEBUG MATCHES \".*-NOTFOUND\"))\n            set_target_properties(sodium PROPERTIES\n                IMPORTED_LOCATION_DEBUG \"${sodium_DLL_DEBUG}\"\n            )\n        endif()\n        if (NOT (sodium_DLL_RELEASE MATCHES \".*-NOTFOUND\"))\n            set_target_properties(sodium PROPERTIES\n                IMPORTED_LOCATION_RELWITHDEBINFO \"${sodium_DLL_RELEASE}\"\n                IMPORTED_LOCATION_MINSIZEREL \"${sodium_DLL_RELEASE}\"\n                IMPORTED_LOCATION_RELEASE \"${sodium_DLL_RELEASE}\"\n            )\n        endif()\n    endif()\nendif()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindXxhash.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# - Try to find Facebook xxhash library\n# This will define\n# Xxhash_FOUND\n# Xxhash_INCLUDE_DIR\n# Xxhash_LIBRARY\n#\n\nfind_path(Xxhash_INCLUDE_DIR NAMES xxhash.h)\n\nfind_library(Xxhash_LIBRARY_RELEASE NAMES xxhash)\n\ninclude(SelectLibraryConfigurations)\nSELECT_LIBRARY_CONFIGURATIONS(Xxhash)\n\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(\n    Xxhash DEFAULT_MSG\n    Xxhash_LIBRARY Xxhash_INCLUDE_DIR\n)\n\nif (Xxhash_FOUND)\n  message(STATUS \"Found xxhash: ${Xxhash_LIBRARY}\")\nendif()\n\nmark_as_advanced(Xxhash_INCLUDE_DIR Xxhash_LIBRARY)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/FindZstd.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# - Try to find Facebook zstd library\n# This will define\n# ZSTD_FOUND\n# ZSTD_INCLUDE_DIR\n# ZSTD_LIBRARY\n#\n\nfind_path(ZSTD_INCLUDE_DIR NAMES zstd.h)\n\nfind_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd)\nfind_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static)\n\ninclude(SelectLibraryConfigurations)\nSELECT_LIBRARY_CONFIGURATIONS(ZSTD)\n\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(\n    Zstd DEFAULT_MSG\n    ZSTD_LIBRARY ZSTD_INCLUDE_DIR\n)\n\nif (ZSTD_FOUND)\n    message(STATUS \"Found Zstd: ${ZSTD_LIBRARY}\")\nendif()\n\nmark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/Findibverbs.cmake",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Find the ibverbs libraries\n#\n# The following variables are optionally searched for defaults\n#  IBVERBS_ROOT_DIR: Base directory where all ibverbs components are found\n#  IBVERBS_INCLUDE_DIR: Directory where ibverbs headers are found\n#  IBVERBS_LIB_DIR: Directory where ibverbs libraries are found\n\n# The following are set after configuration is done:\n#  IBVERBS_FOUND\n#  IBVERBS_INCLUDE_DIRS\n#  IBVERBS_LIBRARIES\n#  IBVERBS_VERSION\n\nfind_path(IBVERBS_INCLUDE_DIRS\n  NAMES infiniband/verbs.h\n  HINTS\n  ${IBVERBS_INCLUDE_DIR}\n  ${IBVERBS_ROOT_DIR}\n  ${IBVERBS_ROOT_DIR}/include)\n\nfind_library(IBVERBS_LIBRARIES\n  NAMES ibverbs\n  HINTS\n  ${IBVERBS_LIB_DIR}\n  ${IBVERBS_ROOT_DIR}\n  ${IBVERBS_ROOT_DIR}/lib)\n\n# Try to determine the rdma-core version\nif(IBVERBS_INCLUDE_DIRS AND IBVERBS_LIBRARIES)\n  # First try using pkg-config if available\n  find_package(PkgConfig QUIET)\n  if(PKG_CONFIG_FOUND)\n    pkg_check_modules(PC_RDMA_CORE QUIET rdma-core)\n    if(PC_RDMA_CORE_VERSION)\n      set(IBVERBS_VERSION ${PC_RDMA_CORE_VERSION})\n    endif()\n  endif()\n\n  # If pkg-config didn't work, try to extract version from library filename\n  # According to rdma-core Documentation/versioning.md:\n  # Library filename format:\n  #   libibverbs.so.SONAME.ABI.PACKAGE_VERSION_MAIN[.PACKAGE_VERSION_BRANCH]\n  # Where:\n  #   - SONAME: Major version (1st field)\n  #   - ABI: ABI version number (2nd field)\n  #   - PACKAGE_VERSION_MAIN: Main package version (3rd field)\n  #   - PACKAGE_VERSION_BRANCH: Optional counter for branched stable\n  #     releases (4th field, part of PACKAGE_VERSION)\n  # Example: libibverbs.so.1.14.57.0 → SONAME=1, ABI=14,\n  #   PACKAGE_VERSION=57.0\n  if(NOT IBVERBS_VERSION)\n    # Get the real path of the library (follows symlinks)\n    get_filename_component(IBVERBS_REAL_PATH \"${IBVERBS_LIBRARIES}\" REALPATH)\n    get_filename_component(IBVERBS_LIB_NAME \"${IBVERBS_REAL_PATH}\" NAME)\n\n    # Extract version from filename\n    if(IBVERBS_LIB_NAME MATCHES\n       \"libibverbs\\\\.so\\\\.([0-9]+)\\\\.([0-9]+)\\\\.([0-9]+)\\\\.([0-9]+)\")\n      # Four-component version: PACKAGE_VERSION_MAIN.PACKAGE_VERSION_BRANCH\n      set(IBVERBS_VERSION_MAJOR ${CMAKE_MATCH_3})\n      set(IBVERBS_VERSION_MINOR ${CMAKE_MATCH_4})\n      set(IBVERBS_VERSION \"${IBVERBS_VERSION_MAJOR}.${IBVERBS_VERSION_MINOR}\")\n    elseif(IBVERBS_LIB_NAME MATCHES\n           \"libibverbs\\\\.so\\\\.([0-9]+)\\\\.([0-9]+)\\\\.([0-9]+)\")\n      # Three-component version: PACKAGE_VERSION_MAIN only\n      set(IBVERBS_VERSION_MAJOR ${CMAKE_MATCH_3})\n      set(IBVERBS_VERSION \"${IBVERBS_VERSION_MAJOR}.0\")\n    else()\n      # If we can't parse the filename, set to empty string\n      # Feature detection will be done in CMakeLists.txt\n      set(IBVERBS_VERSION \"\")\n    endif()\n  endif()\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ibverbs\n  REQUIRED_VARS IBVERBS_INCLUDE_DIRS IBVERBS_LIBRARIES\n  VERSION_VAR IBVERBS_VERSION)\nmark_as_advanced(IBVERBS_INCLUDE_DIRS IBVERBS_LIBRARIES IBVERBS_VERSION)\n"
  },
  {
    "path": "build/fbcode_builder/CMake/RustStaticLibrary.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n\ninclude(FBCMakeParseArgs)\n\nset(\n  USE_CARGO_VENDOR AUTO CACHE STRING\n  \"Download Rust Crates from an internally vendored location\"\n)\nset_property(CACHE USE_CARGO_VENDOR PROPERTY STRINGS AUTO ON OFF)\n\nset(\n  GENERATE_CARGO_VENDOR_CONFIG AUTO CACHE STRING\n  \"Whether to generate Rust cargo vendor config or use existing\"\n)\nset_property(CACHE GENERATE_CARGO_VENDOR_CONFIG PROPERTY STRINGS AUTO ON OFF)\n\nset(RUST_VENDORED_CRATES_DIR \"$ENV{RUST_VENDORED_CRATES_DIR}\")\n\nif(\"${USE_CARGO_VENDOR}\" STREQUAL \"AUTO\")\n  if(EXISTS \"${RUST_VENDORED_CRATES_DIR}\")\n    set(USE_CARGO_VENDOR ON)\n  else()\n    set(USE_CARGO_VENDOR OFF)\n  endif()\nendif()\n\nif(\"${GENERATE_CARGO_VENDOR_CONFIG}\" STREQUAL \"AUTO\")\n  set(GENERATE_CARGO_VENDOR_CONFIG \"${USE_CARGO_VENDOR}\")\nendif()\n\nif(GENERATE_CARGO_VENDOR_CONFIG)\n  if(NOT EXISTS \"${RUST_VENDORED_CRATES_DIR}\")\n    message(\n      FATAL \"vendored rust crates not present: \"\n      \"${RUST_VENDORED_CRATES_DIR}\"\n    )\n  endif()\n\n  set(RUST_CARGO_HOME \"${CMAKE_BINARY_DIR}/_cargo_home\")\n  file(MAKE_DIRECTORY \"${RUST_CARGO_HOME}\")\n\n  file(\n    TO_NATIVE_PATH \"${RUST_VENDORED_CRATES_DIR}\"\n    ESCAPED_RUST_VENDORED_CRATES_DIR\n  )\n  string(\n    REPLACE \"\\\\\" \"\\\\\\\\\"\n    ESCAPED_RUST_VENDORED_CRATES_DIR\n    \"${ESCAPED_RUST_VENDORED_CRATES_DIR}\"\n  )\n  file(\n    WRITE \"${RUST_CARGO_HOME}/config\"\n    \"[source.crates-io]\\n\"\n    \"replace-with = \\\"vendored-sources\\\"\\n\"\n    \"\\n\"\n    \"[source.vendored-sources]\\n\"\n    \"directory = \\\"${ESCAPED_RUST_VENDORED_CRATES_DIR}\\\"\\n\"\n  )\nendif()\n\nfind_program(CARGO_COMMAND cargo REQUIRED)\n\n# Cargo is a build system in itself, and thus will try to take advantage of all\n# the cores on the system. Unfortunately, this conflicts with Ninja, since it\n# also tries to utilize all the cores. This can lead to a system that is\n# completely overloaded with compile jobs to the point where nothing else can\n# be achieved on the system.\n#\n# Let's inform Ninja of this fact so it won't try to spawn other jobs while\n# Rust being compiled.\nset_property(GLOBAL APPEND PROPERTY JOB_POOLS rust_job_pool=1)\n\n# This function creates an interface library target based on the static library\n# built by Cargo. It will call Cargo to build a staticlib and generate a CMake\n# interface library with it.\n#\n# This function requires `find_package(Python COMPONENTS Interpreter)`.\n#\n# You need to set `lib:crate-type = [\"staticlib\"]` in your Cargo.toml to make\n# Cargo build static library.\n#\n# ```cmake\n# rust_static_library(<TARGET> [CRATE <CRATE_NAME>] [FEATURES <FEATURE_NAME>] [USE_CXX_INCLUDE])\n# ```\n#\n# Parameters:\n# - TARGET:\n#   Name of the target name. This function will create an interface library\n#   target with this name.\n# - CRATE_NAME:\n#   Name of the crate. This parameter is optional. If unspecified, it will\n#   fallback to `${TARGET}`.\n# - FEATURE_NAME:\n#   Name of the Rust feature to enable.\n# - USE_CXX_INCLUDE:\n#   Include cxx.rs include path in `${TARGET}` INTERFACE.\n#\n# This function creates two targets:\n# - \"${TARGET}\": an interface library target contains the static library built\n#   from Cargo.\n# - \"${TARGET}.cargo\": an internal custom target that invokes Cargo.\n#\n# If you are going to use this static library from C/C++, you will need to\n# write header files for the library (or generate with cbindgen) and bind these\n# headers with the interface library.\n#\nfunction(rust_static_library TARGET)\n  fb_cmake_parse_args(ARG \"USE_CXX_INCLUDE\" \"CRATE;FEATURES\" \"\" \"${ARGN}\")\n\n  if(DEFINED ARG_CRATE)\n    set(crate_name \"${ARG_CRATE}\")\n  else()\n    set(crate_name \"${TARGET}\")\n  endif()\n  if(DEFINED ARG_FEATURES)\n    set(features --features ${ARG_FEATURES})\n  else()\n    set(features )\n  endif()\n\n  set(cargo_target \"${TARGET}.cargo\")\n  set(target_dir $<IF:$<CONFIG:Debug>,debug,release>)\n  set(staticlib_name \"${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n  set(rust_staticlib \"${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${staticlib_name}\")\n\n  if(DEFINED ARG_FEATURES)\n    set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name} --features ${ARG_FEATURES} --config fbcode_build=false)\n  else()\n    set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name} --config fbcode_build=false)\n  endif()\n  if(USE_CARGO_VENDOR)\n    set(extra_cargo_env \"CARGO_HOME=${RUST_CARGO_HOME}\")\n    set(cargo_flags ${cargo_flags})\n  endif()\n\n  add_custom_target(\n    ${cargo_target}\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E remove -f \"${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock\"\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E env\n      \"CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}\"\n      ${extra_cargo_env}\n      ${CARGO_COMMAND}\n      ${cargo_flags}\n    COMMENT \"Building Rust crate '${crate_name}'...\"\n    JOB_POOL rust_job_pool\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n    BYPRODUCTS\n      \"${CMAKE_CURRENT_BINARY_DIR}/debug/${staticlib_name}\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/release/${staticlib_name}\"\n  )\n\n  add_library(${TARGET} INTERFACE)\n  add_dependencies(${TARGET} ${cargo_target})\n  set_target_properties(\n    ${TARGET}\n    PROPERTIES\n      INTERFACE_STATICLIB_OUTPUT_PATH \"${rust_staticlib}\"\n      INTERFACE_INSTALL_LIBNAME\n        \"${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}_rs${CMAKE_STATIC_LIBRARY_SUFFIX}\"\n  )\n\n  if(DEFINED ARG_USE_CXX_INCLUDE)\n    target_include_directories(\n      ${TARGET}\n      INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/cxxbridge/\n    )\n  endif()\n\n  target_link_libraries(\n    ${TARGET}\n    INTERFACE \"$<BUILD_INTERFACE:${rust_staticlib}>\"\n  )\nendfunction()\n\n# This function instructs CMake to define a target that will use `cargo build`\n# to build a bin crate referenced by the Cargo.toml file in the current source\n# directory.\n# It accepts a single `TARGET` parameter which will be passed as the package\n# name to `cargo build -p TARGET`. If binary has different name as package,\n# use optional flag BINARY_NAME to override it.\n# It also accepts a `FEATURES` parameter if you want to enable certain features\n# in your Rust binary.\n# The CMake target will be registered to build by default as part of the\n# ALL target.\nfunction(rust_executable TARGET)\n  fb_cmake_parse_args(ARG \"\" \"BINARY_NAME;FEATURES\" \"\" \"${ARGN}\")\n\n  set(crate_name \"${TARGET}\")\n  set(cargo_target \"${TARGET}.cargo\")\n  set(target_dir $<IF:$<CONFIG:Debug>,debug,release>)\n\n  if(DEFINED ARG_BINARY_NAME)\n    set(executable_name \"${ARG_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX}\")\n  else()\n    set(executable_name \"${crate_name}${CMAKE_EXECUTABLE_SUFFIX}\")\n  endif()\n  if(DEFINED ARG_FEATURES)\n    set(features --features ${ARG_FEATURES})\n  else()\n    set(features )\n  endif()\n\n  if(DEFINED ARG_FEATURES)\n    set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name} --features ${ARG_FEATURES})\n  else()\n    set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name})\n  endif()\n  if(USE_CARGO_VENDOR)\n    set(extra_cargo_env \"CARGO_HOME=${RUST_CARGO_HOME}\")\n    set(cargo_flags ${cargo_flags})\n  endif()\n\n  add_custom_target(\n    ${cargo_target}\n    ALL\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E remove -f \"${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock\"\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E env\n      \"CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}\"\n      ${extra_cargo_env}\n      ${CARGO_COMMAND}\n      ${cargo_flags}\n    COMMENT \"Building Rust executable '${crate_name}'...\"\n    JOB_POOL rust_job_pool\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n    BYPRODUCTS\n      \"${CMAKE_CURRENT_BINARY_DIR}/debug/${executable_name}\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/release/${executable_name}\"\n  )\n\n  set_property(TARGET \"${cargo_target}\"\n      PROPERTY EXECUTABLE \"${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${executable_name}\")\nendfunction()\n\n# This function can be used to install the executable generated by a prior\n# call to the `rust_executable` function.\n# It requires a `TARGET` parameter to identify the target to be installed,\n# and an optional `DESTINATION` parameter to specify the installation\n# directory.  If DESTINATION is not specified then the `bin` directory\n# will be assumed.\nfunction(install_rust_executable TARGET)\n  # Parse the arguments\n  set(one_value_args DESTINATION)\n  set(multi_value_args)\n  fb_cmake_parse_args(\n    ARG \"\" \"${one_value_args}\" \"${multi_value_args}\" \"${ARGN}\"\n  )\n\n  if(NOT DEFINED ARG_DESTINATION)\n    set(ARG_DESTINATION bin)\n  endif()\n\n  get_target_property(foo \"${TARGET}.cargo\" EXECUTABLE)\n\n  install(\n    PROGRAMS \"${foo}\"\n    DESTINATION \"${ARG_DESTINATION}\"\n  )\nendfunction()\n\n# This function installs the interface target generated from the function\n# `rust_static_library`. Use this function if you want to export your Rust\n# target to external CMake targets.\n#\n# ```cmake\n# install_rust_static_library(\n#   <TARGET>\n#   INSTALL_DIR <INSTALL_DIR>\n#   [EXPORT <EXPORT_NAME>]\n# )\n# ```\n#\n# Parameters:\n# - TARGET: Name of the Rust static library target.\n# - EXPORT_NAME: Name of the exported target.\n# - INSTALL_DIR: Path to the directory where this library will be installed.\n#\nfunction(install_rust_static_library TARGET)\n  fb_cmake_parse_args(ARG \"\" \"EXPORT;INSTALL_DIR\" \"\" \"${ARGN}\")\n\n  get_property(\n    staticlib_output_path\n    TARGET \"${TARGET}\"\n    PROPERTY INTERFACE_STATICLIB_OUTPUT_PATH\n  )\n  get_property(\n    staticlib_output_name\n    TARGET \"${TARGET}\"\n    PROPERTY INTERFACE_INSTALL_LIBNAME\n  )\n\n  if(NOT DEFINED staticlib_output_path)\n    message(FATAL_ERROR \"Not a rust_static_library target.\")\n  endif()\n\n  if(NOT DEFINED ARG_INSTALL_DIR)\n    message(FATAL_ERROR \"Missing required argument.\")\n  endif()\n\n  if(DEFINED ARG_EXPORT)\n    set(install_export_args EXPORT \"${ARG_EXPORT}\")\n  endif()\n\n  set(install_interface_dir \"${ARG_INSTALL_DIR}\")\n  if(NOT IS_ABSOLUTE \"${install_interface_dir}\")\n    set(install_interface_dir \"\\${_IMPORT_PREFIX}/${install_interface_dir}\")\n  endif()\n\n  target_link_libraries(\n    ${TARGET} INTERFACE\n    \"$<INSTALL_INTERFACE:${install_interface_dir}/${staticlib_output_name}>\"\n  )\n  install(\n    TARGETS ${TARGET}\n    ${install_export_args}\n    LIBRARY DESTINATION ${ARG_INSTALL_DIR}\n  )\n  install(\n    FILES ${staticlib_output_path}\n    RENAME ${staticlib_output_name}\n    DESTINATION ${ARG_INSTALL_DIR}\n  )\nendfunction()\n\n# This function creates C++ bindings using the [cxx] crate.\n#\n# Original function found here: https://github.com/corrosion-rs/corrosion/blob/master/cmake/Corrosion.cmake#L1390\n# Simplified for use as part of RustStaticLibrary module. License below.\n#\n# MIT License\n#\n# Copyright (c) 2018 Andrew Gaspar\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n#\n# The rules approximately do the following:\n# - Check which version of `cxx` the Rust crate depends on.\n# - Check if the exact same version of `cxxbridge-cmd` is installed\n# - If not, create a rule to build the exact same version of `cxxbridge-cmd`.\n# - Create rules to run `cxxbridge` and generate\n#   - The `rust/cxx.h` header\n#   - A header and source file for the specified CXX_BRIDGE_FILE.\n# - The generated sources (and header include directories) are added to the\n#   `${TARGET}` CMake library target.\n#\n# ```cmake\n# rust_cxx_bridge(<TARGET> <CXX_BRIDGE_FILE> [CRATE <CRATE_NAME>] [LIBS <LIBNAMES>])\n# ```\n#\n# Parameters:\n# - TARGET:\n#   Name of the target name. The target that the bridge will be included with.\n# - CXX_BRIDGE_FILE:\n#   Name of the file that include the cxxbridge (e.g., \"src/ffi.rs\").\n# - CRATE_NAME:\n#   Name of the crate. This parameter is optional. If unspecified, it will\n#   fallback to `${TARGET}`.\n# - LIBS <lib1> [<lib2> ...]:\n#   A list of libraries that this library depends on.\n#\nfunction(rust_cxx_bridge TARGET CXX_BRIDGE_FILE)\n  fb_cmake_parse_args(ARG \"\" \"CRATE\" \"LIBS\" \"${ARGN}\")\n\n  if(DEFINED ARG_CRATE)\n    set(crate_name \"${ARG_CRATE}\")\n  else()\n    set(crate_name \"${TARGET}\")\n  endif()\n\n  if(USE_CARGO_VENDOR)\n    set(extra_cargo_env \"CARGO_HOME=${RUST_CARGO_HOME}\")\n  endif()\n\n  execute_process(\n    COMMAND\n      \"${CMAKE_COMMAND}\" -E env\n      ${extra_cargo_env}\n      \"${CARGO_COMMAND}\" tree -i cxx --depth=0\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n    RESULT_VARIABLE cxx_version_result\n    OUTPUT_VARIABLE cxx_version_output\n  )\n\n  if(NOT \"${cxx_version_result}\" EQUAL \"0\")\n    message(FATAL_ERROR \"Crate ${crate_name} does not depend on cxx.\")\n  endif()\n  if(cxx_version_output MATCHES \"cxx v([0-9]+.[0-9]+.[0-9]+)\")\n    set(cxx_required_version \"${CMAKE_MATCH_1}\")\n  else()\n    message(\n      FATAL_ERROR\n      \"Failed to parse cxx version from cargo tree output: `cxx_version_output`\")\n  endif()\n\n  # First check if a suitable version of cxxbridge is installed\n  find_program(INSTALLED_CXXBRIDGE cxxbridge PATHS \"$ENV{HOME}/.cargo/bin/\")\n  mark_as_advanced(INSTALLED_CXXBRIDGE)\n  if(INSTALLED_CXXBRIDGE)\n    execute_process(\n      COMMAND \"${INSTALLED_CXXBRIDGE}\" --version\n      OUTPUT_VARIABLE cxxbridge_version_output\n    )\n    if(cxxbridge_version_output MATCHES \"cxxbridge ([0-9]+.[0-9]+.[0-9]+)\")\n      set(cxxbridge_version \"${CMAKE_MATCH_1}\")\n    else()\n      set(cxxbridge_version \"\")\n    endif()\n  endif()\n\n  set(cxxbridge \"\")\n  if(cxxbridge_version)\n    if(cxxbridge_version VERSION_EQUAL cxx_required_version)\n      set(cxxbridge \"${INSTALLED_CXXBRIDGE}\")\n      if(NOT TARGET \"cxxbridge_v${cxx_required_version}\")\n        # Add an empty target.\n        add_custom_target(\"cxxbridge_v${cxx_required_version}\")\n      endif()\n    endif()\n  endif()\n\n  # No suitable version of cxxbridge was installed,\n  # so use custom target to install correct version.\n  if(NOT cxxbridge)\n    if(NOT TARGET \"cxxbridge_v${cxx_required_version}\")\n      add_custom_command(\n        OUTPUT\n          \"${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge\"\n        COMMAND\n          \"${CMAKE_COMMAND}\" -E make_directory\n          \"${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}\"\n        COMMAND\n          \"${CMAKE_COMMAND}\" -E remove -f \"${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock\"\n        COMMAND\n          \"${CMAKE_COMMAND}\" -E env\n          ${extra_cargo_env}\n          \"${CARGO_COMMAND}\" install cxxbridge-cmd\n          --version \"${cxx_required_version}\"\n          --root \"${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}\"\n          --quiet\n        COMMAND\n          \"${CMAKE_COMMAND}\" -E remove -f \"${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock\"\n        COMMENT \"Installing cxxbridge (version ${cxx_required_version})\"\n      )\n      add_custom_target(\n        \"cxxbridge_v${cxx_required_version}\"\n        DEPENDS \"${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge\"\n      )\n    endif()\n    set(\n      cxxbridge\n      \"${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge\"\n    )\n  endif()\n\n  add_library(${crate_name} STATIC)\n  target_include_directories(\n    ${crate_name}\n    PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<INSTALL_INTERFACE:include>\n  )\n  target_link_libraries(\n    ${crate_name}\n    PUBLIC\n    ${ARG_LIBS}\n  )\n\n  file(MAKE_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/rust\")\n  add_custom_command(\n    OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h\"\n    COMMAND\n      \"${cxxbridge}\" --header --output \"${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h\"\n    DEPENDS \"cxxbridge_v${cxx_required_version}\"\n    COMMENT \"Generating rust/cxx.h header\"\n  )\n\n  get_filename_component(filename_component ${CXX_BRIDGE_FILE} NAME)\n  get_filename_component(directory_component ${CXX_BRIDGE_FILE} DIRECTORY)\n  set(directory \"\")\n  if(directory_component)\n    set(directory \"${directory_component}\")\n  endif()\n\n  set(cxx_header ${directory}/${filename_component}.h)\n  set(cxx_source ${directory}/${filename_component}.cc)\n  set(rust_source_path \"${CMAKE_CURRENT_SOURCE_DIR}/${CXX_BRIDGE_FILE}\")\n\n  file(\n    MAKE_DIRECTORY\n    \"${CMAKE_CURRENT_BINARY_DIR}/${directory_component}\"\n  )\n\n  add_custom_command(\n    OUTPUT\n      \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}\"\n    COMMAND\n      ${cxxbridge} ${rust_source_path}\n        --cfg fbcode_build=false\n        --header\n        --output \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}\"\n    COMMAND\n      ${cxxbridge} ${rust_source_path}\n        --cfg fbcode_build=false\n        --output \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}\"\n        --include \"${cxx_header}\"\n    DEPENDS \"cxxbridge_v${cxx_required_version}\" \"${rust_source_path}\"\n    COMMENT \"Generating cxx bindings for crate ${crate_name}\"\n  )\n\n  target_sources(\n    ${crate_name}\n    PRIVATE\n    \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}\"\n    \"${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h\"\n    \"${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}\"\n  )\nendfunction()\n"
  },
  {
    "path": "build/fbcode_builder/CMake/fb_py_test_main.py",
    "content": "#!/usr/bin/env python\n#\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n\"\"\"\nThis file contains the main module code for Python test programs.\n\"\"\"\n\n\nimport contextlib\nimport ctypes\nimport fnmatch\nimport json\nimport logging\nimport optparse\nimport os\nimport platform\nimport re\nimport sys\nimport tempfile\nimport time\nimport traceback\nimport unittest\nimport warnings\nfrom importlib.machinery import PathFinder\n\n\ntry:\n    from StringIO import StringIO\nexcept ImportError:\n    from io import StringIO\ntry:\n    import coverage\nexcept ImportError:\n    coverage = None  # type: ignore\ntry:\n    from importlib.machinery import SourceFileLoader\nexcept ImportError:\n    SourceFileLoader = None  # type: ignore\n\n\nclass get_cpu_instr_counter:\n    def read(self):\n        # TODO\n        return 0\n\n\nEXIT_CODE_SUCCESS = 0\nEXIT_CODE_TEST_FAILURE = 70\n\n\nclass TestStatus:\n\n    ABORTED = \"FAILURE\"\n    PASSED = \"SUCCESS\"\n    FAILED = \"FAILURE\"\n    EXPECTED_FAILURE = \"SUCCESS\"\n    UNEXPECTED_SUCCESS = \"FAILURE\"\n    SKIPPED = \"ASSUMPTION_VIOLATION\"\n\n\nclass PathMatcher:\n    def __init__(self, include_patterns, omit_patterns):\n        self.include_patterns = include_patterns\n        self.omit_patterns = omit_patterns\n\n    def omit(self, path):\n        \"\"\"\n        Omit iff matches any of the omit_patterns or the include patterns are\n        not empty and none is matched\n        \"\"\"\n        path = os.path.realpath(path)\n        return any(fnmatch.fnmatch(path, p) for p in self.omit_patterns) or (\n            self.include_patterns\n            and not any(fnmatch.fnmatch(path, p) for p in self.include_patterns)\n        )\n\n    def include(self, path):\n        return not self.omit(path)\n\n\nclass DebugWipeFinder(PathFinder):\n    \"\"\"\n    PEP 302 finder that uses a DebugWipeLoader for all files which do not need\n    coverage\n    \"\"\"\n\n    def __init__(self, matcher):\n        self.matcher = matcher\n\n    def find_spec(self, fullname, path=None, target=None):\n        spec = super().find_spec(fullname, path=path, target=target)\n        if spec is None or spec.origin is None:\n            return None\n        if not spec.origin.endswith(\".py\"):\n            return None\n        if self.matcher.include(spec.origin):\n            return None\n\n        class PyVarObject(ctypes.Structure):\n            _fields_ = [\n                (\"ob_refcnt\", ctypes.c_long),\n                (\"ob_type\", ctypes.c_void_p),\n                (\"ob_size\", ctypes.c_ulong),\n            ]\n\n        class DebugWipeLoader(SourceFileLoader):\n            \"\"\"\n            PEP302 loader that zeros out debug information before execution\n            \"\"\"\n\n            def get_code(self, fullname):\n                code = super().get_code(fullname)\n                if code:\n                    # Ideally we'd do\n                    # code.co_lnotab = b''\n                    # But code objects are READONLY. Not to worry though; we'll\n                    # directly modify CPython's object\n                    code_impl = PyVarObject.from_address(id(code.co_lnotab))\n                    code_impl.ob_size = 0\n                return code\n\n        if isinstance(spec.loader, SourceFileLoader):\n            spec.loader = DebugWipeLoader(fullname, spec.origin)\n        return spec\n\n\ndef optimize_for_coverage(cov, include_patterns, omit_patterns):\n    \"\"\"\n    We get better performance if we zero out debug information for files which\n    we're not interested in. Only available in CPython 3.3+\n    \"\"\"\n    matcher = PathMatcher(include_patterns, omit_patterns)\n    if SourceFileLoader and platform.python_implementation() == \"CPython\":\n        sys.meta_path.insert(0, DebugWipeFinder(matcher))\n\n\nclass TeeStream:\n    def __init__(self, *streams):\n        self._streams = streams\n\n    def write(self, data):\n        for stream in self._streams:\n            stream.write(data)\n\n    def flush(self):\n        for stream in self._streams:\n            stream.flush()\n\n    def isatty(self):\n        return False\n\n\nclass CallbackStream:\n    def __init__(self, callback, bytes_callback=None, orig=None):\n        self._callback = callback\n        self._fileno = orig.fileno() if orig else None\n\n        # Python 3 APIs:\n        # - `encoding` is a string holding the encoding name\n        # - `errors` is a string holding the error-handling mode for encoding\n        # - `buffer` should look like an io.BufferedIOBase object\n\n        self.errors = orig.errors if orig else None\n        if bytes_callback:\n            # those members are only on the io.TextIOWrapper\n            self.encoding = orig.encoding if orig else \"UTF-8\"\n            self.buffer = CallbackStream(bytes_callback, orig=orig)\n\n    def write(self, data):\n        self._callback(data)\n\n    def flush(self):\n        pass\n\n    def isatty(self):\n        return False\n\n    def fileno(self):\n        return self._fileno\n\n\nclass BuckTestResult(unittest.TextTestResult):\n    \"\"\"\n    Our own TestResult class that outputs data in a format that can be easily\n    parsed by buck's test runner.\n    \"\"\"\n\n    _instr_counter = get_cpu_instr_counter()\n\n    def __init__(\n        self, stream, descriptions, verbosity, show_output, main_program, suite\n    ):\n        super(BuckTestResult, self).__init__(stream, descriptions, verbosity)\n        self._main_program = main_program\n        self._suite = suite\n        self._results = []\n        self._current_test = None\n        self._saved_stdout = sys.stdout\n        self._saved_stderr = sys.stderr\n        self._show_output = show_output\n\n    def getResults(self):\n        return self._results\n\n    def startTest(self, test):\n        super(BuckTestResult, self).startTest(test)\n\n        # Pass in the real stdout and stderr filenos.  We can't really do much\n        # here to intercept callers who directly operate on these fileno\n        # objects.\n        sys.stdout = CallbackStream(\n            self.addStdout, self.addStdoutBytes, orig=sys.stdout\n        )\n        sys.stderr = CallbackStream(\n            self.addStderr, self.addStderrBytes, orig=sys.stderr\n        )\n        self._current_test = test\n        self._test_start_time = time.time()\n        self._current_status = TestStatus.ABORTED\n        self._messages = []\n        self._stacktrace = None\n        self._stdout = \"\"\n        self._stderr = \"\"\n        self._start_instr_count = self._instr_counter.read()\n\n    def _find_next_test(self, suite):\n        \"\"\"\n        Find the next test that has not been run.\n        \"\"\"\n\n        for test in suite:\n\n            # We identify test suites by test that are iterable (as is done in\n            # the builtin python test harness).  If we see one, recurse on it.\n            if hasattr(test, \"__iter__\"):\n                test = self._find_next_test(test)\n\n            # The builtin python test harness sets test references to `None`\n            # after they have run, so we know we've found the next test up\n            # if it's not `None`.\n            if test is not None:\n                return test\n\n    def stopTest(self, test):\n        sys.stdout = self._saved_stdout\n        sys.stderr = self._saved_stderr\n\n        super(BuckTestResult, self).stopTest(test)\n\n        # If a failure occurred during module/class setup, then this \"test\" may\n        # actually be a `_ErrorHolder`, which doesn't contain explicit info\n        # about the upcoming test.  Since we really only care about the test\n        # name field (i.e. `_testMethodName`), we use that to detect an actual\n        # test cases, and fall back to looking the test up from the suite\n        # otherwise.\n        if not hasattr(test, \"_testMethodName\"):\n            test = self._find_next_test(self._suite)\n\n        result = {\n            \"testCaseName\": \"{0}.{1}\".format(\n                test.__class__.__module__, test.__class__.__name__\n            ),\n            \"testCase\": test._testMethodName,\n            \"type\": self._current_status,\n            \"time\": int((time.time() - self._test_start_time) * 1000),\n            \"message\": os.linesep.join(self._messages),\n            \"stacktrace\": self._stacktrace,\n            \"stdOut\": self._stdout,\n            \"stdErr\": self._stderr,\n        }\n\n        # TestPilot supports an instruction count field.\n        if \"TEST_PILOT\" in os.environ:\n            result[\"instrCount\"] = (\n                int(self._instr_counter.read() - self._start_instr_count),\n            )\n\n        self._results.append(result)\n        self._current_test = None\n\n    def stopTestRun(self):\n        cov = self._main_program.get_coverage()\n        if cov is not None:\n            self._results.append({\"coverage\": cov})\n\n    @contextlib.contextmanager\n    def _withTest(self, test):\n        self.startTest(test)\n        yield\n        self.stopTest(test)\n\n    def _setStatus(self, test, status, message=None, stacktrace=None):\n        assert test == self._current_test\n        self._current_status = status\n        self._stacktrace = stacktrace\n        if message is not None:\n            if message.endswith(os.linesep):\n                message = message[:-1]\n            self._messages.append(message)\n\n    def setStatus(self, test, status, message=None, stacktrace=None):\n        # addError() may be called outside of a test if one of the shared\n        # fixtures (setUpClass/tearDownClass/setUpModule/tearDownModule)\n        # throws an error.\n        #\n        # In this case, create a fake test result to record the error.\n        if self._current_test is None:\n            with self._withTest(test):\n                self._setStatus(test, status, message, stacktrace)\n        else:\n            self._setStatus(test, status, message, stacktrace)\n\n    def setException(self, test, status, excinfo):\n        exctype, value, tb = excinfo\n        self.setStatus(\n            test,\n            status,\n            \"{0}: {1}\".format(exctype.__name__, value),\n            \"\".join(traceback.format_tb(tb)),\n        )\n\n    def addSuccess(self, test):\n        super(BuckTestResult, self).addSuccess(test)\n        self.setStatus(test, TestStatus.PASSED)\n\n    def addError(self, test, err):\n        super(BuckTestResult, self).addError(test, err)\n        self.setException(test, TestStatus.ABORTED, err)\n\n    def addFailure(self, test, err):\n        super(BuckTestResult, self).addFailure(test, err)\n        self.setException(test, TestStatus.FAILED, err)\n\n    def addSkip(self, test, reason):\n        super(BuckTestResult, self).addSkip(test, reason)\n        self.setStatus(test, TestStatus.SKIPPED, \"Skipped: %s\" % (reason,))\n\n    def addExpectedFailure(self, test, err):\n        super(BuckTestResult, self).addExpectedFailure(test, err)\n        self.setException(test, TestStatus.EXPECTED_FAILURE, err)\n\n    def addUnexpectedSuccess(self, test):\n        super(BuckTestResult, self).addUnexpectedSuccess(test)\n        self.setStatus(test, TestStatus.UNEXPECTED_SUCCESS, \"Unexpected success\")\n\n    def addStdout(self, val):\n        self._stdout += val\n        if self._show_output:\n            self._saved_stdout.write(val)\n            self._saved_stdout.flush()\n\n    def addStdoutBytes(self, val):\n        string = val.decode(\"utf-8\", errors=\"backslashreplace\")\n        self.addStdout(string)\n\n    def addStderr(self, val):\n        self._stderr += val\n        if self._show_output:\n            self._saved_stderr.write(val)\n            self._saved_stderr.flush()\n\n    def addStderrBytes(self, val):\n        string = val.decode(\"utf-8\", errors=\"backslashreplace\")\n        self.addStderr(string)\n\n\nclass BuckTestRunner(unittest.TextTestRunner):\n    def __init__(self, main_program, suite, show_output=True, **kwargs):\n        super(BuckTestRunner, self).__init__(**kwargs)\n        self.show_output = show_output\n        self._main_program = main_program\n        self._suite = suite\n\n    def _makeResult(self):\n        return BuckTestResult(\n            self.stream,\n            self.descriptions,\n            self.verbosity,\n            self.show_output,\n            self._main_program,\n            self._suite,\n        )\n\n\ndef _format_test_name(test_class, attrname):\n    return \"{0}.{1}.{2}\".format(test_class.__module__, test_class.__name__, attrname)\n\n\nclass StderrLogHandler(logging.StreamHandler):\n    \"\"\"\n    This class is very similar to logging.StreamHandler, except that it\n    always uses the current sys.stderr object.\n\n    StreamHandler caches the current sys.stderr object when it is constructed.\n    This makes it behave poorly in unit tests, which may replace sys.stderr\n    with a StringIO buffer during tests.  The StreamHandler will continue using\n    the old sys.stderr object instead of the desired StringIO buffer.\n    \"\"\"\n\n    def __init__(self):\n        logging.Handler.__init__(self)\n\n    @property\n    def stream(self):\n        return sys.stderr\n\n\nclass RegexTestLoader(unittest.TestLoader):\n    def __init__(self, regex=None):\n        self.regex = regex\n        super(RegexTestLoader, self).__init__()\n\n    def getTestCaseNames(self, testCaseClass):\n        \"\"\"\n        Return a sorted sequence of method names found within testCaseClass\n        \"\"\"\n\n        testFnNames = super(RegexTestLoader, self).getTestCaseNames(testCaseClass)\n        if self.regex is None:\n            return testFnNames\n        robj = re.compile(self.regex)\n        matched = []\n        for attrname in testFnNames:\n            fullname = _format_test_name(testCaseClass, attrname)\n            if robj.search(fullname):\n                matched.append(attrname)\n        return matched\n\n\nclass Loader:\n\n    suiteClass = unittest.TestSuite\n\n    def __init__(self, modules, regex=None):\n        self.modules = modules\n        self.regex = regex\n\n    def load_all(self):\n        loader = RegexTestLoader(self.regex)\n        test_suite = self.suiteClass()\n        for module_name in self.modules:\n            __import__(module_name, level=0)\n            module = sys.modules[module_name]\n            module_suite = loader.loadTestsFromModule(module)\n            test_suite.addTest(module_suite)\n        return test_suite\n\n    def load_args(self, args):\n        loader = RegexTestLoader(self.regex)\n\n        suites = []\n        for arg in args:\n            suite = loader.loadTestsFromName(arg)\n            # loadTestsFromName() can only process names that refer to\n            # individual test functions or modules.  It can't process package\n            # names.  If there were no module/function matches, check to see if\n            # this looks like a package name.\n            if suite.countTestCases() != 0:\n                suites.append(suite)\n                continue\n\n            # Load all modules whose name is <arg>.<something>\n            prefix = arg + \".\"\n            for module in self.modules:\n                if module.startswith(prefix):\n                    suite = loader.loadTestsFromName(module)\n                    suites.append(suite)\n\n        return loader.suiteClass(suites)\n\n\n_COVERAGE_INI = \"\"\"\\\n[report]\nexclude_lines =\n    pragma: no cover\n    pragma: nocover\n    pragma:.*no${PLATFORM}\n    pragma:.*no${PY_IMPL}${PY_MAJOR}${PY_MINOR}\n    pragma:.*no${PY_IMPL}${PY_MAJOR}\n    pragma:.*nopy${PY_MAJOR}\n    pragma:.*nopy${PY_MAJOR}${PY_MINOR}\n\"\"\"\n\n\nclass MainProgram:\n    \"\"\"\n    This class implements the main program.  It can be subclassed by\n    users who wish to customize some parts of the main program.\n    (Adding additional command line options, customizing test loading, etc.)\n    \"\"\"\n\n    DEFAULT_VERBOSITY = 2\n\n    def __init__(self, argv):\n        self.init_option_parser()\n        self.parse_options(argv)\n        self.setup_logging()\n\n    def init_option_parser(self):\n        usage = \"%prog [options] [TEST] ...\"\n        op = optparse.OptionParser(usage=usage, add_help_option=False)\n        self.option_parser = op\n\n        op.add_option(\n            \"--hide-output\",\n            dest=\"show_output\",\n            action=\"store_false\",\n            default=True,\n            help=\"Suppress data that tests print to stdout/stderr, and only \"\n            \"show it if the test fails.\",\n        )\n        op.add_option(\n            \"-o\",\n            \"--output\",\n            help=\"Write results to a file in a JSON format to be read by Buck\",\n        )\n        op.add_option(\n            \"-f\",\n            \"--failfast\",\n            action=\"store_true\",\n            default=False,\n            help=\"Stop after the first failure\",\n        )\n        op.add_option(\n            \"-l\",\n            \"--list-tests\",\n            action=\"store_true\",\n            dest=\"list\",\n            default=False,\n            help=\"List tests and exit\",\n        )\n        op.add_option(\n            \"-r\",\n            \"--regex\",\n            default=None,\n            help=\"Regex to apply to tests, to only run those tests\",\n        )\n        op.add_option(\n            \"--collect-coverage\",\n            action=\"store_true\",\n            default=False,\n            help=\"Collect test coverage information\",\n        )\n        op.add_option(\n            \"--coverage-include\",\n            default=\"*\",\n            help='File globs to include in converage (split by \",\")',\n        )\n        op.add_option(\n            \"--coverage-omit\",\n            default=\"\",\n            help='File globs to omit from converage (split by \",\")',\n        )\n        op.add_option(\n            \"--logger\",\n            action=\"append\",\n            metavar=\"<category>=<level>\",\n            default=[],\n            help=\"Configure log levels for specific logger categories\",\n        )\n        op.add_option(\n            \"-q\",\n            \"--quiet\",\n            action=\"count\",\n            default=0,\n            help=\"Decrease the verbosity (may be specified multiple times)\",\n        )\n        op.add_option(\n            \"-v\",\n            \"--verbosity\",\n            action=\"count\",\n            default=self.DEFAULT_VERBOSITY,\n            help=\"Increase the verbosity (may be specified multiple times)\",\n        )\n        op.add_option(\n            \"-?\", \"--help\", action=\"help\", help=\"Show this help message and exit\"\n        )\n\n    def parse_options(self, argv):\n        self.options, self.test_args = self.option_parser.parse_args(argv[1:])\n        self.options.verbosity -= self.options.quiet\n\n        if self.options.collect_coverage and coverage is None:\n            self.option_parser.error(\"coverage module is not available\")\n        self.options.coverage_include = self.options.coverage_include.split(\",\")\n        if self.options.coverage_omit == \"\":\n            self.options.coverage_omit = []\n        else:\n            self.options.coverage_omit = self.options.coverage_omit.split(\",\")\n\n    def setup_logging(self):\n        # Configure the root logger to log at INFO level.\n        # This is similar to logging.basicConfig(), but uses our\n        # StderrLogHandler instead of a StreamHandler.\n        fmt = logging.Formatter(\"%(pathname)s:%(lineno)s: %(message)s\")\n        log_handler = StderrLogHandler()\n        log_handler.setFormatter(fmt)\n        root_logger = logging.getLogger()\n        root_logger.addHandler(log_handler)\n        root_logger.setLevel(logging.INFO)\n\n        level_names = {\n            \"debug\": logging.DEBUG,\n            \"info\": logging.INFO,\n            \"warn\": logging.WARNING,\n            \"warning\": logging.WARNING,\n            \"error\": logging.ERROR,\n            \"critical\": logging.CRITICAL,\n            \"fatal\": logging.FATAL,\n        }\n\n        for value in self.options.logger:\n            parts = value.rsplit(\"=\", 1)\n            if len(parts) != 2:\n                self.option_parser.error(\n                    \"--logger argument must be of the \"\n                    \"form <name>=<level>: %s\" % value\n                )\n            name = parts[0]\n            level_name = parts[1].lower()\n            level = level_names.get(level_name)\n            if level is None:\n                self.option_parser.error(\n                    \"invalid log level %r for log \" \"category %s\" % (parts[1], name)\n                )\n            logging.getLogger(name).setLevel(level)\n\n    def create_loader(self):\n        import __test_modules__\n\n        return Loader(__test_modules__.TEST_MODULES, self.options.regex)\n\n    def load_tests(self):\n        loader = self.create_loader()\n        if self.options.collect_coverage:\n            self.start_coverage()\n            include = self.options.coverage_include\n            omit = self.options.coverage_omit\n            if include and \"*\" not in include:\n                optimize_for_coverage(self.cov, include, omit)\n\n        if self.test_args:\n            suite = loader.load_args(self.test_args)\n        else:\n            suite = loader.load_all()\n        if self.options.collect_coverage:\n            self.cov.start()\n        return suite\n\n    def get_tests(self, test_suite):\n        tests = []\n\n        for test in test_suite:\n            if isinstance(test, unittest.TestSuite):\n                tests.extend(self.get_tests(test))\n            else:\n                tests.append(test)\n\n        return tests\n\n    def run(self):\n        test_suite = self.load_tests()\n\n        if self.options.list:\n            for test in self.get_tests(test_suite):\n                method_name = getattr(test, \"_testMethodName\", \"\")\n                name = _format_test_name(test.__class__, method_name)\n                print(name)\n            return EXIT_CODE_SUCCESS\n        else:\n            result = self.run_tests(test_suite)\n            if self.options.output is not None:\n                with open(self.options.output, \"w\") as f:\n                    json.dump(result.getResults(), f, indent=4, sort_keys=True)\n            if not result.wasSuccessful():\n                return EXIT_CODE_TEST_FAILURE\n            return EXIT_CODE_SUCCESS\n\n    def run_tests(self, test_suite):\n        # Install a signal handler to catch Ctrl-C and display the results\n        # (but only if running >2.6).\n        if sys.version_info[0] > 2 or sys.version_info[1] > 6:\n            unittest.installHandler()\n\n        # Run the tests\n        runner = BuckTestRunner(\n            self,\n            test_suite,\n            verbosity=self.options.verbosity,\n            show_output=self.options.show_output,\n        )\n        result = runner.run(test_suite)\n\n        if self.options.collect_coverage and self.options.show_output:\n            self.cov.stop()\n            try:\n                self.cov.report(file=sys.stdout)\n            except coverage.misc.CoverageException:\n                print(\"No lines were covered, potentially restricted by file filters\")\n\n        return result\n\n    def get_abbr_impl(self):\n        \"\"\"Return abbreviated implementation name.\"\"\"\n        impl = platform.python_implementation()\n        if impl == \"PyPy\":\n            return \"pp\"\n        elif impl == \"Jython\":\n            return \"jy\"\n        elif impl == \"IronPython\":\n            return \"ip\"\n        elif impl == \"CPython\":\n            return \"cp\"\n        else:\n            raise RuntimeError(\"unknown python runtime\")\n\n    def start_coverage(self):\n        if not self.options.collect_coverage:\n            return\n\n        with tempfile.NamedTemporaryFile(\"w\", delete=False) as coverage_ini:\n            coverage_ini.write(_COVERAGE_INI)\n            self._coverage_ini_path = coverage_ini.name\n\n        # Keep the original working dir in case tests use os.chdir\n        self._original_working_dir = os.getcwd()\n\n        # for coverage config ignores by platform/python version\n        os.environ[\"PLATFORM\"] = sys.platform\n        os.environ[\"PY_IMPL\"] = self.get_abbr_impl()\n        os.environ[\"PY_MAJOR\"] = str(sys.version_info.major)\n        os.environ[\"PY_MINOR\"] = str(sys.version_info.minor)\n\n        self.cov = coverage.Coverage(\n            include=self.options.coverage_include,\n            omit=self.options.coverage_omit,\n            config_file=coverage_ini.name,\n        )\n        self.cov.erase()\n        self.cov.start()\n\n    def get_coverage(self):\n        if not self.options.collect_coverage:\n            return None\n\n        try:\n            os.remove(self._coverage_ini_path)\n        except OSError:\n            pass  # Better to litter than to fail the test\n\n        # Switch back to the original working directory.\n        os.chdir(self._original_working_dir)\n\n        result = {}\n\n        self.cov.stop()\n\n        try:\n            f = StringIO()\n            self.cov.report(file=f)\n            lines = f.getvalue().split(\"\\n\")\n        except coverage.misc.CoverageException:\n            # Nothing was covered. That's fine by us\n            return result\n\n        # N.B.: the format of the coverage library's output differs\n        # depending on whether one or more files are in the results\n        for line in lines[2:]:\n            if line.strip(\"-\") == \"\":\n                break\n            r = line.split()[0]\n            analysis = self.cov.analysis2(r)\n            covString = self.convert_to_diff_cov_str(analysis)\n            if covString:\n                result[r] = covString\n\n        return result\n\n    def convert_to_diff_cov_str(self, analysis):\n        # Info on the format of analysis:\n        # http://nedbatchelder.com/code/coverage/api.html\n        if not analysis:\n            return None\n        numLines = max(\n            analysis[1][-1] if len(analysis[1]) else 0,\n            analysis[2][-1] if len(analysis[2]) else 0,\n            analysis[3][-1] if len(analysis[3]) else 0,\n        )\n        lines = [\"N\"] * numLines\n        for l in analysis[1]:\n            lines[l - 1] = \"C\"\n        for l in analysis[2]:\n            lines[l - 1] = \"X\"\n        for l in analysis[3]:\n            lines[l - 1] = \"U\"\n        return \"\".join(lines)\n\n\ndef main(argv):\n    return MainProgram(sys.argv).run()\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv))\n"
  },
  {
    "path": "build/fbcode_builder/CMake/fb_py_win_main.c",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates.\n\n#define WIN32_LEAN_AND_MEAN\n\n#include <Windows.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#define PATH_SIZE 32768\n\ntypedef int (*Py_Main)(int, wchar_t**);\n\nint locate_py_main(int argc, wchar_t** argv) {\n  /*\n   * We have to dynamically locate Python3.dll because we may be loading a\n   * Python native module while running. If that module is built with a\n   * different Python version, we will end up a DLL import error. To resolve\n   * this, we can either ship an embedded version of Python with us or\n   * dynamically look up existing Python distribution installed on user's\n   * machine. This way, we should be able to get a consistent version of\n   * Python3.dll and .pyd modules.\n   */\n  HINSTANCE python_dll;\n  Py_Main pymain;\n\n  python_dll =\n      LoadLibraryExW(L\"python3.dll\", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);\n\n  int returncode = 0;\n  if (python_dll != NULL) {\n    pymain = (Py_Main)GetProcAddress(python_dll, \"Py_Main\");\n\n    if (pymain != NULL) {\n      returncode = (pymain)(argc, argv);\n    } else {\n      fprintf(stderr, \"error: %d unable to load Py_Main\\n\", GetLastError());\n    }\n\n    FreeLibrary(python_dll);\n  } else {\n    fprintf(stderr, \"error: %d unable to locate python3.dll\\n\", GetLastError());\n    return 1;\n  }\n  return returncode;\n}\n\nint wmain() {\n  /*\n   * This executable will be prepended to the start of a Python ZIP archive.\n   * Python will be able to directly execute the ZIP archive, so we simply\n   * need to tell Py_Main() to run our own file.  Duplicate the argument list\n   * and add our file name to the beginning to tell Python what file to invoke.\n   */\n  wchar_t** pyargv = malloc(sizeof(wchar_t*) * (__argc + 1));\n  if (!pyargv) {\n    fprintf(stderr, \"error: failed to allocate argument vector\\n\");\n    return 1;\n  }\n\n  /* Py_Main wants the wide character version of the argv so we pull those\n   * values from the global __wargv array that has been prepared by MSVCRT.\n   *\n   * In order for the zipapp to run we need to insert an extra argument in\n   * the front of the argument vector that points to ourselves.\n   *\n   * An additional complication is that, depending on who prepared the argument\n   * string used to start our process, the computed __wargv[0] can be a simple\n   * shell word like `watchman-wait` which is normally resolved together with\n   * the PATH by the shell.\n   * That unresolved path isn't sufficient to start the zipapp on windows;\n   * we need the fully qualified path.\n   *\n   * Given:\n   * __wargv == {\"watchman-wait\", \"-h\"}\n   *\n   * we want to pass the following to Py_Main:\n   *\n   * {\n   *   \"z:\\build\\watchman\\python\\watchman-wait.exe\",\n   *   \"z:\\build\\watchman\\python\\watchman-wait.exe\",\n   *   \"-h\"\n   * }\n   */\n  wchar_t full_path_to_argv0[PATH_SIZE];\n  DWORD len = GetModuleFileNameW(NULL, full_path_to_argv0, PATH_SIZE);\n  if (len == 0 ||\n      len == PATH_SIZE && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {\n    fprintf(\n        stderr,\n        \"error: %d while retrieving full path to this executable\\n\",\n        GetLastError());\n    return 1;\n  }\n\n  for (int n = 1; n < __argc; ++n) {\n    pyargv[n + 1] = __wargv[n];\n  }\n  pyargv[0] = full_path_to_argv0;\n  pyargv[1] = full_path_to_argv0;\n\n  return locate_py_main(__argc + 1, pyargv);\n}\n"
  },
  {
    "path": "build/fbcode_builder/CMake/make_fbpy_archive.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\nimport argparse\nimport collections\nimport errno\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport zipapp\n\nMANIFEST_SEPARATOR = \" :: \"\nMANIFEST_HEADER_V1 = \"FBPY_MANIFEST 1\\n\"\n\n\nclass UsageError(Exception):\n    def __init__(self, message):\n        self.message = message\n\n    def __str__(self):\n        return self.message\n\n\nclass BadManifestError(UsageError):\n    def __init__(self, path, line_num, message):\n        full_msg = \"%s:%s: %s\" % (path, line_num, message)\n        super().__init__(full_msg)\n        self.path = path\n        self.line_num = line_num\n        self.raw_message = message\n\n\nPathInfo = collections.namedtuple(\n    \"PathInfo\", (\"src\", \"dest\", \"manifest_path\", \"manifest_line\")\n)\n\n\ndef parse_manifest(manifest, path_map):\n    bad_prefix = \"..\" + os.path.sep\n    manifest_dir = os.path.dirname(manifest)\n    with open(manifest, \"r\") as f:\n        line_num = 1\n        line = f.readline()\n        if line != MANIFEST_HEADER_V1:\n            raise BadManifestError(\n                manifest, line_num, \"Unexpected manifest file header\"\n            )\n\n        for line in f:\n            line_num += 1\n            if line.startswith(\"#\"):\n                continue\n            line = line.rstrip(\"\\n\")\n            parts = line.split(MANIFEST_SEPARATOR)\n            if len(parts) != 2:\n                msg = \"line must be of the form SRC %s DEST\" % MANIFEST_SEPARATOR\n                raise BadManifestError(manifest, line_num, msg)\n            src, dest = parts\n            dest = os.path.normpath(dest)\n            if dest.startswith(bad_prefix):\n                msg = \"destination path starts with %s: %s\" % (bad_prefix, dest)\n                raise BadManifestError(manifest, line_num, msg)\n\n            if not os.path.isabs(src):\n                src = os.path.normpath(os.path.join(manifest_dir, src))\n\n            if dest in path_map:\n                prev_info = path_map[dest]\n                msg = (\n                    \"multiple source paths specified for destination \"\n                    \"path %s.  Previous source was %s from %s:%s\"\n                    % (\n                        dest,\n                        prev_info.src,\n                        prev_info.manifest_path,\n                        prev_info.manifest_line,\n                    )\n                )\n                raise BadManifestError(manifest, line_num, msg)\n\n            info = PathInfo(\n                src=src,\n                dest=dest,\n                manifest_path=manifest,\n                manifest_line=line_num,\n            )\n            path_map[dest] = info\n\n\ndef populate_install_tree(inst_dir, path_map):\n    os.mkdir(inst_dir)\n    dest_dirs = {\"\": False}\n\n    def make_dest_dir(path):\n        if path in dest_dirs:\n            return\n        parent = os.path.dirname(path)\n        make_dest_dir(parent)\n        abs_path = os.path.join(inst_dir, path)\n        os.mkdir(abs_path)\n        dest_dirs[path] = False\n\n    def install_file(info):\n        dir_name, base_name = os.path.split(info.dest)\n        make_dest_dir(dir_name)\n        if base_name == \"__init__.py\":\n            dest_dirs[dir_name] = True\n        abs_dest = os.path.join(inst_dir, info.dest)\n        shutil.copy2(info.src, abs_dest)\n\n    # Copy all of the destination files\n    for info in path_map.values():\n        install_file(info)\n\n    # Create __init__ files in any directories that don't have them.\n    for dir_path, has_init in dest_dirs.items():\n        if has_init:\n            continue\n        init_path = os.path.join(inst_dir, dir_path, \"__init__.py\")\n        with open(init_path, \"w\"):\n            pass\n\n\ndef build_pex(args, path_map):\n    \"\"\"Create a self executing python binary using the PEX tool\n\n    This type of Python binary is more complex as it requires a third-party tool,\n    but it does support native language extensions (.so/.dll files).\n    \"\"\"\n    dest_dir = os.path.dirname(args.output)\n    with tempfile.TemporaryDirectory(prefix=\"make_fbpy.\", dir=dest_dir) as tmpdir:\n        inst_dir = os.path.join(tmpdir, \"tree\")\n        populate_install_tree(inst_dir, path_map)\n\n        if os.path.exists(os.path.join(inst_dir, \"__main__.py\")):\n            os.rename(\n                os.path.join(inst_dir, \"__main__.py\"),\n                os.path.join(inst_dir, \"main.py\"),\n            )\n            args.main = \"main\"\n\n        tmp_output = os.path.abspath(os.path.join(tmpdir, \"output.exe\"))\n        subprocess.check_call(\n            [\"pex\"]\n            + [\"--output-file\", tmp_output]\n            + [\"--python\", args.python]\n            + [\"--sources-directory\", inst_dir]\n            + [\"-e\", args.main]\n        )\n\n        os.replace(tmp_output, args.output)\n\n\ndef build_zipapp(args, path_map):\n    \"\"\"Create a self executing python binary using Python 3's built-in\n    zipapp module.\n\n    This type of Python binary is relatively simple, as zipapp is part of the\n    standard library, but it does not support native language extensions\n    (.so/.dll files).\n    \"\"\"\n    dest_dir = os.path.dirname(args.output)\n    with tempfile.TemporaryDirectory(prefix=\"make_fbpy.\", dir=dest_dir) as tmpdir:\n        inst_dir = os.path.join(tmpdir, \"tree\")\n        populate_install_tree(inst_dir, path_map)\n\n        tmp_output = os.path.join(tmpdir, \"output.exe\")\n        zipapp.create_archive(\n            inst_dir, target=tmp_output, interpreter=args.python, main=args.main\n        )\n        os.replace(tmp_output, args.output)\n\n\ndef create_main_module(args, inst_dir, path_map):\n    if not args.main:\n        assert \"__main__.py\" in path_map\n        return\n\n    dest_path = os.path.join(inst_dir, \"__main__.py\")\n    main_module, main_fn = args.main.split(\":\")\n    main_contents = \"\"\"\\\n#!{python}\n\nif __name__ == \"__main__\":\n    import {main_module}\n    {main_module}.{main_fn}()\n\"\"\".format(\n        python=args.python, main_module=main_module, main_fn=main_fn\n    )\n    with open(dest_path, \"w\") as f:\n        f.write(main_contents)\n    os.chmod(dest_path, 0o755)\n\n\ndef build_install_dir(args, path_map):\n    \"\"\"Create a directory that contains all of the sources, with a __main__\n    module to run the program.\n    \"\"\"\n    # Populate a temporary directory first, then rename to the destination\n    # location.  This ensures that we don't ever leave a halfway-built\n    # directory behind at the output path if something goes wrong.\n    dest_dir = os.path.dirname(args.output)\n    with tempfile.TemporaryDirectory(prefix=\"make_fbpy.\", dir=dest_dir) as tmpdir:\n        inst_dir = os.path.join(tmpdir, \"tree\")\n        populate_install_tree(inst_dir, path_map)\n        create_main_module(args, inst_dir, path_map)\n        os.rename(inst_dir, args.output)\n\n\ndef ensure_directory(path):\n    try:\n        os.makedirs(path)\n    except OSError as ex:\n        if ex.errno != errno.EEXIST:\n            raise\n\n\ndef install_library(args, path_map):\n    \"\"\"Create an installation directory a python library.\"\"\"\n    out_dir = args.output\n    out_manifest = args.output + \".manifest\"\n\n    install_dir = args.install_dir\n    if not install_dir:\n        install_dir = out_dir\n\n    os.makedirs(out_dir)\n    with open(out_manifest, \"w\") as manifest:\n        manifest.write(MANIFEST_HEADER_V1)\n        for info in path_map.values():\n            abs_dest = os.path.join(out_dir, info.dest)\n            ensure_directory(os.path.dirname(abs_dest))\n            print(\"copy %r --> %r\" % (info.src, abs_dest))\n            shutil.copy2(info.src, abs_dest)\n            installed_dest = os.path.join(install_dir, info.dest)\n            manifest.write(\"%s%s%s\\n\" % (installed_dest, MANIFEST_SEPARATOR, info.dest))\n\n\ndef parse_manifests(args):\n    # Process args.manifest_separator to help support older versions of CMake\n    if args.manifest_separator:\n        manifests = []\n        for manifest_arg in args.manifests:\n            split_arg = manifest_arg.split(args.manifest_separator)\n            manifests.extend(split_arg)\n        args.manifests = manifests\n\n    path_map = {}\n    for manifest in args.manifests:\n        parse_manifest(manifest, path_map)\n\n    return path_map\n\n\ndef check_main_module(args, path_map):\n    # Translate an empty string in the --main argument to None,\n    # just to allow the CMake logic to be slightly simpler and pass in an\n    # empty string when it really wants the default __main__.py module to be\n    # used.\n    if args.main == \"\":\n        args.main = None\n\n    if args.type == \"lib-install\":\n        if args.main is not None:\n            raise UsageError(\"cannot specify a --main argument with --type=lib-install\")\n        return\n\n    main_info = path_map.get(\"__main__.py\")\n    if args.main:\n        if main_info is not None:\n            msg = (\n                \"specified an explicit main module with --main, \"\n                \"but the file listing already includes __main__.py\"\n            )\n            raise BadManifestError(\n                main_info.manifest_path, main_info.manifest_line, msg\n            )\n        parts = args.main.split(\":\")\n        if len(parts) != 2:\n            raise UsageError(\n                \"argument to --main must be of the form MODULE:CALLABLE \"\n                \"(received %s)\" % (args.main,)\n            )\n    else:\n        if main_info is None:\n            raise UsageError(\n                \"no main module specified with --main, \"\n                \"and no __main__.py module present\"\n            )\n\n\nBUILD_TYPES = {\n    \"pex\": build_pex,\n    \"zipapp\": build_zipapp,\n    \"dir\": build_install_dir,\n    \"lib-install\": install_library,\n}\n\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument(\"-o\", \"--output\", required=True, help=\"The output file path\")\n    ap.add_argument(\n        \"--install-dir\",\n        help=\"When used with --type=lib-install, this parameter specifies the \"\n        \"final location where the library where be installed.  This can be \"\n        \"used to generate the library in one directory first, when you plan \"\n        \"to move or copy it to another final location later.\",\n    )\n    ap.add_argument(\n        \"--manifest-separator\",\n        help=\"Split manifest arguments around this separator.  This is used \"\n        \"to support older versions of CMake that cannot supply the manifests \"\n        \"as separate arguments.\",\n    )\n    ap.add_argument(\n        \"--main\",\n        help=\"The main module to run, specified as <module>:<callable>.  \"\n        \"This must be specified if and only if the archive does not contain \"\n        \"a __main__.py file.\",\n    )\n    ap.add_argument(\n        \"--python\",\n        help=\"Explicitly specify the python interpreter to use for the \" \"executable.\",\n    )\n    ap.add_argument(\n        \"--type\", choices=BUILD_TYPES.keys(), help=\"The type of output to build.\"\n    )\n    ap.add_argument(\n        \"manifests\",\n        nargs=\"+\",\n        help=\"The manifest files specifying how to construct the archive\",\n    )\n    args = ap.parse_args()\n\n    if args.python is None:\n        args.python = sys.executable\n\n    if args.type is None:\n        # In the future we might want different default output types\n        # for different platforms.\n        args.type = \"zipapp\"\n    build_fn = BUILD_TYPES[args.type]\n\n    try:\n        path_map = parse_manifests(args)\n        check_main_module(args, path_map)\n    except UsageError as ex:\n        print(\"error: %s\" % (ex,), file=sys.stderr)\n        sys.exit(1)\n\n    build_fn(args, path_map)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "build/fbcode_builder/LICENSE",
    "content": "MIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "build/fbcode_builder/README.md",
    "content": "# Easy builds for Facebook projects\n\nThis directory contains tools designed to simplify continuous-integration\n(and other builds) of Facebook open source projects.  In particular, this helps\nmanage builds for cross-project dependencies.\n\nThe main entry point is the `getdeps.py` script.  This script has several\nsubcommands, but the most notable is the `build` command.  This will download\nand build all dependencies for a project, and then build the project itself.\n\n## Deployment\n\nThis directory is copied literally into a number of different Facebook open\nsource repositories.  Any change made to code in this directory will be\nautomatically be replicated by our open source tooling into all GitHub hosted\nrepositories that use `fbcode_builder`.  Typically this directory is copied\ninto the open source repositories as `build/fbcode_builder/`.\n\n\n# Project Configuration Files\n\nThe `manifests` subdirectory contains configuration files for many different\nprojects, describing how to build each project.  These files also list\ndependencies between projects, enabling `getdeps.py` to build all dependencies\nfor a project before building the project itself.\n\n\n# Shared CMake utilities\n\nSince this directory is copied into many Facebook open source repositories,\nit is also used to help share some CMake utility files across projects.  The\n`CMake/` subdirectory contains a number of `.cmake` files that are shared by\nthe CMake-based build systems across several different projects.\n\n\n# Older Build Scripts\n\nThis directory also still contains a handful of older build scripts that\npre-date the current `getdeps.py` build system.  Most of the other `.py` files\nin this top directory, apart from `getdeps.py` itself, are from this older\nbuild system.  This older system is only used by a few remaining projects, and\nnew projects should generally use the newer `getdeps.py` script, by adding a\nnew configuration file in the `manifests/` subdirectory.\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/__init__.py",
    "content": "# pyre-strict\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/builder.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport glob\nimport json\nimport os\nimport os.path\nimport pathlib\nimport re\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport typing\nfrom collections.abc import Callable, Sequence\nfrom shlex import quote as shellquote\n\nfrom .copytree import rmtree_more, simple_copytree\nfrom .dyndeps import create_dyn_dep_munger\nfrom .envfuncs import add_path_entry, Env, path_search\nfrom .fetcher import copy_if_different, is_public_commit\nfrom .runcmd import make_memory_limit_preexec_fn, run_cmd\n\nif typing.TYPE_CHECKING:\n    from .buildopts import BuildOptions\n    from .dyndeps import DepBase\n    from .load import ManifestLoader\n    from .manifest import ManifestContext, ManifestParser\n\n\nclass BuilderBase:\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str | None,\n        inst_dir: str,\n        env: Env | None = None,\n        final_install_prefix: str | None = None,\n    ) -> None:\n        self.env: Env = Env()\n        if env:\n            # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got\n            #  `Env`.\n            self.env.update(env)\n\n        subdir: str | None = manifest.get(\"build\", \"subdir\", ctx=ctx)\n        if subdir:\n            src_dir = os.path.join(src_dir, subdir)\n\n        self.patchfile: str | None = manifest.get(\"build\", \"patchfile\", ctx=ctx)\n        self.patchfile_opts: str = (\n            manifest.get(\"build\", \"patchfile_opts\", ctx=ctx) or \"\"\n        )\n        self.ctx: ManifestContext = ctx\n        self.src_dir: str = src_dir\n        self.build_dir: str = build_dir or src_dir\n        self.inst_dir: str = inst_dir\n        self.build_opts: BuildOptions = build_opts\n        self.manifest: ManifestParser = manifest\n        self.final_install_prefix: str | None = final_install_prefix\n        self.loader: ManifestLoader = loader\n        self.dep_manifests: list[ManifestParser] = dep_manifests\n        self.install_dirs: list[str] = [\n            loader.get_project_install_dir(m) for m in dep_manifests\n        ]\n\n    def _get_cmd_prefix(self) -> list[str]:\n        if self.build_opts.is_windows():\n            vcvarsall = self.build_opts.get_vcvars_path()\n            if vcvarsall is not None:\n                # Since it sets rather a large number of variables we mildly abuse\n                # the cmd quoting rules to assemble a command that calls the script\n                # to prep the environment and then triggers the actual command that\n                # we wanted to run.\n\n                # Due to changes in vscrsall.bat, it now reports an ERRORLEVEL of 1\n                # even when succeeding. This occurs when an extension is not present.\n                # To continue, we must ignore the ERRORLEVEL returned. We do this by\n                # wrapping the call in a batch file that always succeeds.\n                wrapper = os.path.join(self.build_dir, \"succeed.bat\")\n                with open(wrapper, \"w\") as f:\n                    f.write(\"@echo off\\n\")\n                    f.write(f'call \"{vcvarsall}\" amd64\\n')\n                    f.write(\"set ERRORLEVEL=0\\n\")\n                    f.write(\"exit /b 0\\n\")\n                return [wrapper, \"&&\"]\n        return []\n\n    def _check_cmd(self, cmd: list[str], **kwargs: object) -> None:\n        \"\"\"Run the command and abort on failure\"\"\"\n        # pyre-fixme[6]: For 2nd argument expected `Optional[Env]` but got `object`.\n        # pyre-fixme[6]: For 2nd argument expected `Optional[str]` but got `object`.\n        # pyre-fixme[6]: For 2nd argument expected `bool` but got `object`.\n        rc = self._run_cmd(cmd, **kwargs)\n        if rc != 0:\n            raise RuntimeError(f\"Failure exit code {rc} for command {cmd}\")\n\n    def _run_cmd(\n        self,\n        cmd: list[str],\n        cwd: str | None = None,\n        env: Env | None = None,\n        use_cmd_prefix: bool = True,\n        allow_fail: bool = False,\n        preexec_fn: Callable[[], None] | None = None,\n    ) -> int:\n        if env:\n            e = self.env.copy()\n            # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got\n            #  `Env`.\n            e.update(env)\n            env = e\n        else:\n            env = self.env\n\n        if use_cmd_prefix:\n            cmd_prefix = self._get_cmd_prefix()\n            if cmd_prefix:\n                cmd = cmd_prefix + cmd\n\n        log_file = os.path.join(self.build_dir, \"getdeps_build.log\")\n        return run_cmd(\n            cmd=cmd,\n            env=env,\n            cwd=cwd or self.build_dir,\n            log_file=log_file,\n            allow_fail=allow_fail,\n            preexec_fn=preexec_fn,\n        )\n\n    def _reconfigure(self, reconfigure: bool) -> bool:\n        if self.build_dir is not None:\n            if not os.path.isdir(self.build_dir):\n                os.makedirs(self.build_dir)\n                reconfigure = True\n        return reconfigure\n\n    def _apply_patchfile(self) -> None:\n        if self.patchfile is None:\n            return\n        patched_sentinel_file = pathlib.Path(self.src_dir + \"/.getdeps_patched\")\n        if patched_sentinel_file.exists():\n            return\n        old_wd = os.getcwd()\n        os.chdir(self.src_dir)\n        # Apply patches from the git repo root so paths resolve correctly\n        # even when src_dir is a subdirectory of the repo.\n        try:\n            git_root = subprocess.check_output(\n                [\"git\", \"rev-parse\", \"--show-toplevel\"], text=True\n            ).strip()\n            os.chdir(git_root)\n        except subprocess.CalledProcessError:\n            pass  # not a git repo, stay in src_dir\n        print(f\"Patching {self.manifest.name} with {self.patchfile} in {os.getcwd()}\")\n        patchfile = os.path.join(\n            self.build_opts.fbcode_builder_dir,\n            \"patches\",\n            # pyre-fixme[6]: For 3rd argument expected `Union[PathLike[str], str]`\n            #  but got `Optional[str]`.\n            self.patchfile,\n        )\n        patchcmd = [\"git\", \"apply\", \"--ignore-space-change\"]\n        if self.patchfile_opts:\n            patchcmd.append(self.patchfile_opts)\n        try:\n            subprocess.check_call(patchcmd + [patchfile])\n        except subprocess.CalledProcessError:\n            raise ValueError(f\"Failed to apply patch to {self.manifest.name}\")\n        os.chdir(old_wd)\n        patched_sentinel_file.touch()\n\n    def prepare(self, reconfigure: bool) -> None:\n        print(\"Preparing %s...\" % self.manifest.name)\n        reconfigure = self._reconfigure(reconfigure)\n        self._apply_patchfile()\n        self._prepare(reconfigure=reconfigure)\n\n    def debug(self, reconfigure: bool) -> None:\n        reconfigure = self._reconfigure(reconfigure)\n        self._apply_patchfile()\n        self._prepare(reconfigure=reconfigure)\n        env = self._compute_env()\n        print(\"Starting a shell in %s, ^D to exit...\" % self.build_dir)\n        # TODO: print the command to run the build\n        shell = [\"powershell.exe\"] if sys.platform == \"win32\" else [\"/bin/sh\", \"-i\"]\n        self._run_cmd(shell, cwd=self.build_dir, env=env)\n\n    def printenv(self, reconfigure: bool) -> None:\n        \"\"\"print the environment in a shell sourcable format\"\"\"\n        reconfigure = self._reconfigure(reconfigure)\n        self._apply_patchfile()\n        self._prepare(reconfigure=reconfigure)\n        env = self._compute_env(env=Env(src={}))\n        prefix = \"export \"\n        sep = \":\"\n        expand = \"$\"\n        expandpost = \"\"\n        if self.build_opts.is_windows():\n            prefix = \"SET \"\n            sep = \";\"\n            expand = \"%\"\n            expandpost = \"%\"\n        for k, v in sorted(env.items()):\n            existing = os.environ.get(k, None)\n            if k.endswith(\"PATH\") and existing:\n                v = shellquote(v) + sep + f\"{expand}{k}{expandpost}\"\n            else:\n                v = shellquote(v)\n            print(\"%s%s=%s\" % (prefix, k, v))\n\n    def build(self, reconfigure: bool) -> None:\n        print(\"Building %s...\" % self.manifest.name)\n        reconfigure = self._reconfigure(reconfigure)\n        self._apply_patchfile()\n        self._prepare(reconfigure=reconfigure)\n        self._build(reconfigure=reconfigure)\n\n        if self.build_opts.free_up_disk:\n            # don't clean --src-dir=. case as user may want to build again or run tests on the build\n            if self.src_dir.startswith(self.build_opts.scratch_dir) and os.path.isdir(\n                self.build_dir\n            ):\n                if os.path.islink(self.build_dir):\n                    os.remove(self.build_dir)\n                else:\n                    rmtree_more(self.build_dir)\n        elif self.build_opts.is_windows():\n            # On Windows, emit a wrapper script that can be used to run build artifacts\n            # directly from the build directory, without installing them.  On Windows $PATH\n            # needs to be updated to include all of the directories containing the runtime\n            # library dependencies in order to run the binaries.\n            script_path = self.get_dev_run_script_path()\n            dep_munger = create_dyn_dep_munger(\n                self.build_opts, self._compute_env(), self.install_dirs\n            )\n            dep_dirs = self.get_dev_run_extra_path_dirs(dep_munger)\n            # pyre-fixme[16]: Optional type has no attribute `emit_dev_run_script`.\n            dep_munger.emit_dev_run_script(script_path, dep_dirs)\n\n    @property\n    def _job_weight_mib(self) -> int:\n        # This is a hack, but we don't have a \"defaults manifest\" that we can\n        # customize per platform.\n        # TODO: Introduce some sort of defaults config that can select by\n        # platform, just like manifest contexts.\n        if sys.platform.startswith(\"freebsd\"):\n            # clang on FreeBSD is quite memory-efficient.\n            default_job_weight = 512\n        else:\n            # 1.5 GiB is a lot to assume, but it's typical of Facebook-style C++.\n            # Some manifests are even heavier and should override.\n            default_job_weight = 1536\n        return int(\n            self.manifest.get(\n                \"build\", \"job_weight_mib\", str(default_job_weight), ctx=self.ctx\n            )\n        )\n\n    @property\n    def num_jobs(self) -> int:\n        return self.build_opts.get_num_jobs(self._job_weight_mib)\n\n    @property\n    def memory_limit_preexec_fn(self) -> Callable[[], None] | None:\n        \"\"\"Return a preexec_fn that caps per-process virtual memory.\n\n        Uses the same job_weight_mib that controls parallelism, so the memory\n        limit is consistent with the parallelism budget.\n        \"\"\"\n        return make_memory_limit_preexec_fn(self._job_weight_mib)\n\n    def run_tests(\n        self,\n        schedule_type: str,\n        owner: str | None,\n        test_filter: str | None,\n        test_exclude: str | None,\n        retry: int,\n        no_testpilot: bool,\n        timeout: int | None = None,\n    ) -> None:\n        \"\"\"Execute any tests that we know how to run.  If they fail,\n        raise an exception.\"\"\"\n        pass\n\n    def _prepare(self, reconfigure: bool) -> None:\n        \"\"\"Prepare the build. Useful when need to generate config,\n        but builder is not the primary build system.\n        e.g. cargo when called from cmake\"\"\"\n        pass\n\n    def _build(self, reconfigure: bool) -> None:\n        \"\"\"Perform the build.\n        reconfigure will be set to true if the fetcher determined\n        that the sources have changed in such a way that the build\n        system needs to regenerate its rules.\"\"\"\n        pass\n\n    def _compute_env(self, env: Env | None = None) -> Env:\n        if env is None:\n            env = self.env\n        # CMAKE_PREFIX_PATH is only respected when passed through the\n        # environment, so we construct an appropriate path to pass down\n        return self.build_opts.compute_env_for_install_dirs(\n            self.loader,\n            self.dep_manifests,\n            self.ctx,\n            env=env,\n            manifest=self.manifest,\n        )\n\n    def get_dev_run_script_path(self) -> str:\n        assert self.build_opts.is_windows()\n        return os.path.join(self.build_dir, \"run.ps1\")\n\n    def get_dev_run_extra_path_dirs(\n        self, dep_munger: DepBase | None = None\n    ) -> list[str]:\n        assert self.build_opts.is_windows()\n        if dep_munger is None:\n            dep_munger = create_dyn_dep_munger(\n                self.build_opts, self._compute_env(), self.install_dirs\n            )\n        # pyre-fixme[16]: Optional type has no attribute `compute_dependency_paths`.\n        return dep_munger.compute_dependency_paths(self.build_dir)\n\n\nclass MakeBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        build_args: list[str] | None,\n        install_args: list[str] | None,\n        test_args: list[str] | None,\n    ) -> None:\n        super(MakeBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n        self.build_args: list[str] = build_args or []\n        self.install_args: list[str] = install_args or []\n        self.test_args: list[str] | None = test_args\n\n    @property\n    def _make_binary(self) -> str | None:\n        return self.manifest.get(\"build\", \"make_binary\", \"make\", ctx=self.ctx)\n\n    def _get_prefix(self) -> list[str]:\n        return [\"PREFIX=\" + self.inst_dir, \"prefix=\" + self.inst_dir]\n\n    def _build(self, reconfigure: bool) -> None:\n\n        env = self._compute_env()\n\n        # Need to ensure that PREFIX is set prior to install because\n        # libbpf uses it when generating its pkg-config file.\n        # The lowercase prefix is used by some projects.\n        cmd = (\n            [self._make_binary, \"-j%s\" % self.num_jobs]\n            + self.build_args\n            + self._get_prefix()\n        )\n        # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n        #  `List[Optional[str]]`.\n        self._check_cmd(cmd, env=env)\n\n        install_cmd = [self._make_binary] + self.install_args + self._get_prefix()\n        # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n        #  `List[Optional[str]]`.\n        self._check_cmd(install_cmd, env=env)\n\n        # bz2's Makefile doesn't install its .so properly\n        if self.manifest and self.manifest.name == \"bz2\":\n            libdir = os.path.join(self.inst_dir, \"lib\")\n            srcpattern = os.path.join(self.src_dir, \"lib*.so.*\")\n            print(f\"copying to {libdir} from {srcpattern}\")\n            for file in glob.glob(srcpattern):\n                shutil.copy(file, libdir)\n\n    def run_tests(\n        self,\n        schedule_type: str,\n        owner: str | None,\n        test_filter: str | None,\n        test_exclude: str | None,\n        retry: int,\n        no_testpilot: bool,\n        timeout: int | None = None,\n    ) -> None:\n        if not self.test_args:\n            return\n\n        env = self._compute_env()\n        if test_filter:\n            env[\"GETDEPS_TEST_FILTER\"] = test_filter\n        else:\n            env[\"GETDEPS_TEST_FILTER\"] = \"\"\n\n        if retry:\n            # pyre-fixme[6]: Expected `str` but got `int`.\n            env[\"GETDEPS_TEST_RETRY\"] = retry\n        else:\n            # pyre-fixme[6]: Expected `str` but got `int`.\n            env[\"GETDEPS_TEST_RETRY\"] = 0\n\n        if timeout is not None:\n            env[\"GETDEPS_TEST_TIMEOUT\"] = str(timeout)\n\n        cmd = (\n            [self._make_binary, \"-j%s\" % self.num_jobs]\n            # pyre-fixme[58]: `+` is not supported for operand types\n            #  `list[Optional[str]]` and `Optional[list[str]]`.\n            + self.test_args\n            + self._get_prefix()\n        )\n        # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n        #  `List[Optional[str]]`.\n        self._check_cmd(cmd, allow_fail=False, env=env)\n\n\nclass CMakeBootStrapBuilder(MakeBuilder):\n    def _build(self, reconfigure: bool) -> None:\n        self._check_cmd(\n            [\n                \"./bootstrap\",\n                \"--prefix=\" + self.inst_dir,\n                f\"--parallel={self.num_jobs}\",\n            ]\n        )\n        super(CMakeBootStrapBuilder, self)._build(reconfigure)\n\n\nclass AutoconfBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        args: list[str] | None,\n        conf_env_args: dict[str, list[str]] | None,\n    ) -> None:\n        super(AutoconfBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n        self.args: list[str] = args or []\n        if (\n            not build_opts.shared_libs\n            and \"--disable-shared\" not in self.args\n            and \"--enable-shared\" not in self.args\n        ):\n            self.args.append(\"--disable-shared\")\n        self.conf_env_args: dict[str, list[str]] = conf_env_args or {}\n\n    @property\n    def _make_binary(self) -> str | None:\n        return self.manifest.get(\"build\", \"make_binary\", \"make\", ctx=self.ctx)\n\n    def _build(self, reconfigure: bool) -> None:\n        configure_path = os.path.join(self.src_dir, \"configure\")\n        autogen_path = os.path.join(self.src_dir, \"autogen.sh\")\n\n        env = self._compute_env()\n\n        # Some configure scripts need additional env values passed derived from cmds\n        for k, cmd_args in self.conf_env_args.items():\n            out = (\n                subprocess.check_output(cmd_args, env=dict(env.items()))\n                .decode(\"utf-8\")\n                .strip()\n            )\n            if out:\n                env.set(k, out)\n\n        if not os.path.exists(configure_path):\n            print(\"%s doesn't exist, so reconfiguring\" % configure_path)\n            # This libtoolize call is a bit gross; the issue is that\n            # `autoreconf` as invoked by libsodium's `autogen.sh` doesn't\n            # seem to realize that it should invoke libtoolize and then\n            # error out when the configure script references a libtool\n            # related symbol.\n            self._check_cmd([\"libtoolize\"], cwd=self.src_dir, env=env)\n\n            # We generally prefer to call the `autogen.sh` script provided\n            # by the project on the basis that it may know more than plain\n            # autoreconf does.\n            if os.path.exists(autogen_path):\n                self._check_cmd([\"bash\", autogen_path], cwd=self.src_dir, env=env)\n            else:\n                self._check_cmd([\"autoreconf\", \"-ivf\"], cwd=self.src_dir, env=env)\n        configure_cmd = [configure_path, \"--prefix=\" + self.inst_dir] + self.args\n        self._check_cmd(configure_cmd, env=env)\n        only_install = self.manifest.get(\"build\", \"only_install\", ctx=self.ctx)\n        if not only_install or only_install.lower() == \"false\":\n            # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n            #  `List[Optional[str]]`.\n            self._check_cmd([self._make_binary, \"-j%s\" % self.num_jobs], env=env)\n        # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n        #  `List[Union[str, None, str]]`.\n        self._check_cmd([self._make_binary, \"install\"], env=env)\n\n\nclass Iproute2Builder(BuilderBase):\n    # ./configure --prefix does not work for iproute2.\n    # Thus, explicitly copy sources from src_dir to build_dir, build,\n    # and then install to inst_dir using DESTDIR\n    # lastly, also copy include from build_dir to inst_dir\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(Iproute2Builder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n\n    def _build(self, reconfigure: bool) -> None:\n        configure_path = os.path.join(self.src_dir, \"configure\")\n        env = self.env.copy()\n        self._check_cmd([configure_path], env=env)\n        shutil.rmtree(self.build_dir)\n        shutil.copytree(self.src_dir, self.build_dir)\n        self._check_cmd([\"make\", \"-j%s\" % self.num_jobs], env=env)\n        install_cmd = [\"make\", \"install\", \"DESTDIR=\" + self.inst_dir]\n\n        for d in [\"include\", \"lib\"]:\n            if not os.path.isdir(os.path.join(self.inst_dir, d)):\n                shutil.copytree(\n                    os.path.join(self.build_dir, d), os.path.join(self.inst_dir, d)\n                )\n\n        self._check_cmd(install_cmd, env=env)\n\n\nclass MesonBuilder(BuilderBase):\n    # MesonBuilder assumes that meson build tool has already been installed on\n    # the machine.\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(MesonBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n\n    def _build(self, reconfigure: bool) -> None:\n        env = self._compute_env()\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        meson: str | None = path_search(env, \"meson\")\n        if meson is None:\n            raise Exception(\"Failed to find Meson\")\n\n        setup_args = self.manifest.get_section_as_args(\"meson.setup_args\", self.ctx)\n\n        # Meson builds typically require setup, compile, and install steps.\n        # During this setup step we ensure that the static library is built and\n        # the prefix is empty.\n        self._check_cmd(\n            [\n                meson,\n                \"setup\",\n            ]\n            + setup_args\n            + [\n                self.build_dir,\n                self.src_dir,\n            ]\n        )\n\n        # Compile step needs to satisfy the build directory that was previously\n        # prepared during setup.\n        self._check_cmd([meson, \"compile\", \"-C\", self.build_dir])\n\n        # Install step\n        self._check_cmd(\n            [meson, \"install\", \"-C\", self.build_dir, \"--destdir\", self.inst_dir]\n        )\n\n\nclass CMakeBuilder(BuilderBase):\n    MANUAL_BUILD_SCRIPT = \"\"\"\\\n#!{sys.executable}\n\n\nimport argparse\nimport subprocess\nimport sys\n\nCMAKE = {cmake!r}\nCTEST = {ctest!r}\nSRC_DIR = {src_dir!r}\nBUILD_DIR = {build_dir!r}\nINSTALL_DIR = {install_dir!r}\nCMD_PREFIX = {cmd_prefix!r}\nCMAKE_ENV = {env_str}\nCMAKE_DEFINE_ARGS = {define_args_str}\n\n\ndef get_jobs_argument(num_jobs_arg: int) -> str:\n    if num_jobs_arg > 0:\n        return \"-j\" + str(num_jobs_arg)\n\n    import multiprocessing\n    num_jobs = multiprocessing.cpu_count() // 2\n    return \"-j\" + str(num_jobs)\n\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument(\n      \"cmake_args\",\n      nargs=argparse.REMAINDER,\n      help='Any extra arguments after an \"--\" argument will be passed '\n      \"directly to CMake.\"\n    )\n    ap.add_argument(\n      \"--mode\",\n      choices=[\"configure\", \"build\", \"install\", \"test\"],\n      default=\"configure\",\n      help=\"The mode to run: configure, build, or install.  \"\n      \"Defaults to configure\",\n    )\n    ap.add_argument(\n      \"--build\",\n      action=\"store_const\",\n      const=\"build\",\n      dest=\"mode\",\n      help=\"An alias for --mode=build\",\n    )\n    ap.add_argument(\n      \"-j\",\n      \"--num-jobs\",\n      action=\"store\",\n      type=int,\n      default=0,\n      help=\"Run the build or tests with the specified number of parallel jobs\",\n    )\n    ap.add_argument(\n      \"--install\",\n      action=\"store_const\",\n      const=\"install\",\n      dest=\"mode\",\n      help=\"An alias for --mode=install\",\n    )\n    ap.add_argument(\n      \"--test\",\n      action=\"store_const\",\n      const=\"test\",\n      dest=\"mode\",\n      help=\"An alias for --mode=test\",\n    )\n    args = ap.parse_args()\n\n    # Strip off a leading \"--\" from the additional CMake arguments\n    if args.cmake_args and args.cmake_args[0] == \"--\":\n        args.cmake_args = args.cmake_args[1:]\n\n    env = CMAKE_ENV\n\n    if args.mode == \"configure\":\n        full_cmd = CMD_PREFIX + [CMAKE, SRC_DIR] + CMAKE_DEFINE_ARGS + args.cmake_args\n    elif args.mode in (\"build\", \"install\"):\n        target = \"all\" if args.mode == \"build\" else \"install\"\n        full_cmd = CMD_PREFIX + [\n                CMAKE,\n                \"--build\",\n                BUILD_DIR,\n                \"--target\",\n                target,\n                \"--config\",\n                \"{build_type}\",\n                get_jobs_argument(args.num_jobs),\n        ] + args.cmake_args\n    elif args.mode == \"test\":\n        full_cmd = CMD_PREFIX + [\n            {dev_run_script}CTEST,\n            \"--output-on-failure\",\n            get_jobs_argument(args.num_jobs),\n        ] + args.cmake_args\n    else:\n        ap.error(\"unknown invocation mode: %s\" % (args.mode,))\n\n    cmd_str = \" \".join(full_cmd)\n    print(\"Running: %r\" % (cmd_str,))\n    proc = subprocess.run(full_cmd, env=env, cwd=BUILD_DIR)\n    sys.exit(proc.returncode)\n\n\nif __name__ == \"__main__\":\n    main()\n\"\"\"\n\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        defines: dict[str, str] | None,\n        final_install_prefix: str | None = None,\n        extra_cmake_defines: dict[str, str] | None = None,\n        cmake_targets: list[str] | None = None,\n    ) -> None:\n        super(CMakeBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n            final_install_prefix=final_install_prefix,\n        )\n        self.defines: dict[str, str] = defines or {}\n        if extra_cmake_defines:\n            self.defines.update(extra_cmake_defines)\n        self.cmake_targets: list[str] = cmake_targets or [\"install\"]\n\n        if build_opts.is_windows():\n            try:\n                from .facebook.vcvarsall import extra_vc_cmake_defines\n            except ImportError:\n                pass\n            else:\n                self.defines.update(extra_vc_cmake_defines)\n\n        self.loader = loader\n        if build_opts.shared_libs:\n            self.defines[\"BUILD_SHARED_LIBS\"] = \"ON\"\n            self.defines[\"BOOST_LINK_STATIC\"] = \"OFF\"\n\n    def _invalidate_cache(self) -> None:\n        for name in [\n            \"CMakeCache.txt\",\n            \"CMakeFiles/CMakeError.log\",\n            \"CMakeFiles/CMakeOutput.log\",\n        ]:\n            name = os.path.join(self.build_dir, name)\n            if os.path.isdir(name):\n                shutil.rmtree(name)\n            elif os.path.exists(name):\n                os.unlink(name)\n\n    def _needs_reconfigure(self) -> bool:\n        for name in [\"CMakeCache.txt\", \"build.ninja\"]:\n            name = os.path.join(self.build_dir, name)\n            if not os.path.exists(name):\n                return True\n        return False\n\n    def _write_build_script(self, **kwargs: object) -> None:\n        # pyre-fixme[16]: `object` has no attribute `items`.\n        env_lines = [\"    {!r}: {!r},\".format(k, v) for k, v in kwargs[\"env\"].items()]\n        kwargs[\"env_str\"] = \"\\n\".join([\"{\"] + env_lines + [\"}\"])\n\n        if self.build_opts.is_windows():\n            kwargs[\"dev_run_script\"] = '\"powershell.exe\", {!r}, '.format(\n                self.get_dev_run_script_path()\n            )\n        else:\n            kwargs[\"dev_run_script\"] = \"\"\n\n        define_arg_lines = [\"[\"]\n        # pyre-fixme[16]: `object` has no attribute `__iter__`.\n        for arg in kwargs[\"define_args\"]:\n            # Replace the CMAKE_INSTALL_PREFIX argument to use the INSTALL_DIR\n            # variable that we define in the MANUAL_BUILD_SCRIPT code.\n            if arg.startswith(\"-DCMAKE_INSTALL_PREFIX=\"):\n                value = \"    {!r}.format(INSTALL_DIR),\".format(\n                    \"-DCMAKE_INSTALL_PREFIX={}\"\n                )\n            else:\n                value = \"    {!r},\".format(arg)\n            define_arg_lines.append(value)\n        define_arg_lines.append(\"]\")\n        kwargs[\"define_args_str\"] = \"\\n\".join(define_arg_lines)\n\n        # In order to make it easier for developers to manually run builds for\n        # CMake-based projects, write out some build scripts that can be used to invoke\n        # CMake manually.\n        build_script_path = os.path.join(self.build_dir, \"run_cmake.py\")\n        script_contents = self.MANUAL_BUILD_SCRIPT.format(**kwargs)\n        with open(build_script_path, \"wb\") as f:\n            f.write(script_contents.encode())\n        os.chmod(build_script_path, 0o755)\n\n    def _compute_cmake_define_args(self, env: Env) -> list[str]:\n        defines = {\n            \"CMAKE_INSTALL_PREFIX\": self.final_install_prefix or self.inst_dir,\n            \"BUILD_SHARED_LIBS\": \"OFF\",\n            # Some of the deps (rsocket) default to UBSAN enabled if left\n            # unspecified.  Some of the deps fail to compile in release mode\n            # due to warning->error promotion.  RelWithDebInfo is the happy\n            # medium.\n            \"CMAKE_BUILD_TYPE\": self.build_opts.build_type,\n        }\n\n        if \"SANDCASTLE\" not in os.environ:\n            # We sometimes see intermittent ccache related breakages on some\n            # of the FB internal CI hosts, so we prefer to disable ccache\n            # when running in that environment.\n            # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got\n            #  `Env`.\n            ccache = path_search(env, \"ccache\")\n            if ccache:\n                defines[\"CMAKE_CXX_COMPILER_LAUNCHER\"] = ccache\n        else:\n            # rocksdb does its own probing for ccache.\n            # Ensure that it is disabled on sandcastle\n            env[\"CCACHE_DISABLE\"] = \"1\"\n            # Some sandcastle hosts have broken ccache related dirs, and\n            # even though we've asked for it to be disabled ccache is\n            # still invoked by rocksdb's cmake.\n            # Redirect its config directory to somewhere that is guaranteed\n            # fresh to us, and that won't have any ccache data inside.\n            env[\"CCACHE_DIR\"] = f\"{self.build_opts.scratch_dir}/ccache\"\n\n        if \"GITHUB_ACTIONS\" in os.environ and self.build_opts.is_windows():\n            # GitHub actions: the host has both gcc and msvc installed, and\n            # the default behavior of cmake is to prefer gcc.\n            # Instruct cmake that we want it to use cl.exe; this is important\n            # because Boost prefers cl.exe and the mismatch results in cmake\n            # with gcc not being able to find boost built with cl.exe.\n            defines[\"CMAKE_C_COMPILER\"] = \"cl.exe\"\n            defines[\"CMAKE_CXX_COMPILER\"] = \"cl.exe\"\n\n        if self.build_opts.is_darwin():\n            # Try to persuade cmake to set the rpath to match the lib\n            # dirs of the dependencies.  This isn't automatic, and to\n            # make things more interesting, cmake uses `;` as the path\n            # separator, so translate the runtime path to something\n            # that cmake will parse\n            defines[\"CMAKE_INSTALL_RPATH\"] = \";\".join(\n                # pyre-fixme[16]: Optional type has no attribute `split`.\n                env.get(\"DYLD_LIBRARY_PATH\", \"\").split(\":\")\n            )\n            # Tell cmake that we want to set the rpath in the tree\n            # at build time.  Without this the rpath is only set\n            # at the moment that the binaries are installed.  That\n            # default is problematic for example when using the\n            # gtest integration in cmake which runs the built test\n            # executables during the build to discover the set of\n            # tests.\n            defines[\"CMAKE_BUILD_WITH_INSTALL_RPATH\"] = \"ON\"\n\n        defines.update(self.defines)\n        define_args = [\"-D%s=%s\" % (k, v) for (k, v) in defines.items()]\n\n        # if self.build_opts.is_windows():\n        #    define_args += [\"-G\", \"Visual Studio 15 2017 Win64\"]\n        define_args += [\"-G\", \"Ninja\"]\n\n        return define_args\n\n    def _run_include_rewriter(self) -> None:\n        \"\"\"Run include path rewriting on source files before building.\"\"\"\n        from .include_rewriter import rewrite_includes_from_manifest\n\n        print(f\"Rewriting include paths for {self.manifest.name}...\")\n        try:\n            modified_count = rewrite_includes_from_manifest(\n                self.manifest, self.ctx, self.src_dir, verbose=True\n            )\n            if modified_count > 0:\n                print(f\"Successfully modified {modified_count} files\")\n            else:\n                print(\"No files needed modification\")\n        except Exception as e:\n            print(f\"Warning: Include path rewriting failed: {e}\")\n            # Don't fail the build for include rewriting issues\n\n    def _build(self, reconfigure: bool) -> None:\n        # Check if include rewriting is enabled\n        rewrite_includes: str | None = self.manifest.get(\n            \"build\", \"rewrite_includes\", \"false\", ctx=self.ctx\n        )\n        # pyre-fixme[16]: Optional type has no attribute `lower`.\n        if rewrite_includes.lower() == \"true\":\n            self._run_include_rewriter()\n\n        reconfigure = reconfigure or self._needs_reconfigure()\n\n        env = self._compute_env()\n        if not self.build_opts.is_windows() and self.final_install_prefix:\n            env[\"DESTDIR\"] = self.inst_dir\n\n        # Resolve the cmake that we installed\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        cmake = path_search(env, \"cmake\")\n        if cmake is None:\n            raise Exception(\"Failed to find CMake\")\n\n        if self.build_opts.is_windows():\n            checkdir = self.src_dir\n            if os.path.exists(checkdir):\n                children = os.listdir(checkdir)\n                print(f\"Building from source {checkdir} contents: {children}\")\n            else:\n                print(f\"Source {checkdir} not found\")\n\n        if reconfigure:\n            define_args = self._compute_cmake_define_args(env)\n            self._write_build_script(\n                cmd_prefix=self._get_cmd_prefix(),\n                cmake=cmake,\n                # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but\n                #  got `Env`.\n                ctest=path_search(env, \"ctest\"),\n                env=env,\n                define_args=define_args,\n                src_dir=self.src_dir,\n                build_dir=self.build_dir,\n                install_dir=self.inst_dir,\n                sys=sys,\n                build_type=self.build_opts.build_type,\n            )\n\n            self._invalidate_cache()\n            self._check_cmd([cmake, self.src_dir] + define_args, env=env)\n\n        self._check_cmd(\n            # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n            #  `List[Optional[str]]`.\n            [cmake, \"--build\", self.build_dir, \"--target\"]\n            + self.cmake_targets\n            + [\n                \"--config\",\n                self.build_opts.build_type,\n                \"-j\",\n                str(self.num_jobs),\n            ],\n            env=env,\n            preexec_fn=self.memory_limit_preexec_fn,\n        )\n\n    def _build_targets(self, targets: Sequence[str]) -> None:\n        \"\"\"Build one or more cmake targets in parallel.\n\n        Args:\n            targets: Sequence of target names (strings) to build\n        \"\"\"\n        if not targets:\n            return\n\n        env = self._compute_env()\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        cmake = path_search(env, \"cmake\")\n        if cmake is None:\n            raise RuntimeError(\"unable to find cmake\")\n\n        # Build all targets in a single cmake invocation for better parallelism\n        cmd = [\n            cmake,\n            \"--build\",\n            self.build_dir,\n        ]\n\n        # Add all targets\n        for target in targets:\n            cmd.extend([\"--target\", target])\n\n        cmd.extend(\n            # pyre-fixme[6]: For 1st argument expected `Iterable[str]` but got\n            #  `Iterable[Union[str, str, None, str]]`.\n            [\n                \"--config\",\n                self.build_opts.build_type,\n                \"-j\",\n                str(self.num_jobs),\n            ]\n        )\n\n        self._check_cmd(cmd, env=env, preexec_fn=self.memory_limit_preexec_fn)\n\n    def _get_missing_test_executables(\n        self, test_filter: str | None, env: Env, ctest: str | None\n    ) -> set[str]:\n        \"\"\"Discover which test executables are missing for the given filter.\n        Returns a set of missing executable basenames (without path).\"\"\"\n        if ctest is None:\n            return set()\n\n        # Run ctest -N (show tests without running) with the filter to see which tests match\n        cmd = [ctest, \"-N\"]\n        if test_filter:\n            cmd += [\"-R\", test_filter]\n\n        try:\n            output = subprocess.check_output(\n                cmd,\n                env=dict(env.items()),\n                cwd=self.build_dir,\n                stderr=subprocess.STDOUT,\n                text=True,\n            )\n        except subprocess.CalledProcessError as e:\n            # If ctest fails, it might be because executables don't exist yet\n            # Parse the error output to find the missing executables\n            output = e.output\n\n        # Parse output to find missing executable paths\n        # Look for lines like \"Could not find executable /path/to/test_binary\"\n        missing_executables = set()\n        for line in output.split(\"\\n\"):\n            match = re.search(r\"Could not find executable (.+)\", line)\n            if match:\n                exe_path = match.group(1)\n                exe_name = os.path.basename(exe_path)\n                missing_executables.add(exe_name)\n\n        return missing_executables\n\n    def run_tests(\n        self,\n        schedule_type: str,\n        owner: str | None,\n        test_filter: str | None,\n        test_exclude: str | None,\n        retry: int,\n        no_testpilot: bool,\n        timeout: int | None = None,\n    ) -> None:\n        env = self._compute_env()\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        ctest: str | None = path_search(env, \"ctest\")\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        cmake = path_search(env, \"cmake\")\n\n        # Build only the missing test executables needed for the given filter.\n        # This is especially important for LocalDirFetcher projects (like fboss)\n        # where the build marker gets removed when building specific cmake targets.\n        missing_test_executables = self._get_missing_test_executables(\n            test_filter, env, ctest\n        )\n        if missing_test_executables:\n            sorted_executables = sorted(missing_test_executables)\n            print(f\"Building missing test executables: {', '.join(sorted_executables)}\")\n            # Build all missing executables in one cmake invocation for better parallelism\n            self._build_targets(sorted_executables)\n\n        def require_command(path: str | None, name: str) -> str:\n            if path is None:\n                raise RuntimeError(\"unable to find command `{}`\".format(name))\n            return path\n\n        # On Windows, we also need to update $PATH to include the directories that\n        # contain runtime library dependencies.  This is not needed on other platforms\n        # since CMake will emit RPATH properly in the binary so they can find these\n        # dependencies.\n        if self.build_opts.is_windows():\n            path_entries = self.get_dev_run_extra_path_dirs()\n            path = env.get(\"PATH\")\n            if path:\n                path_entries.insert(0, path)\n            env[\"PATH\"] = \";\".join(path_entries)\n\n        # Don't use the cmd_prefix when running tests.  This is vcvarsall.bat on\n        # Windows.  vcvarsall.bat is only needed for the build, not tests.  It\n        # unfortunately fails if invoked with a long PATH environment variable when\n        # running the tests.\n        use_cmd_prefix = False\n\n        def get_property(\n            test: dict[str, object], propname: str, defval: object = None\n        ) -> object:\n            \"\"\"extracts a named property from a cmake test info json blob.\n            The properties look like:\n            [{\"name\": \"WORKING_DIRECTORY\"},\n             {\"value\": \"something\"}]\n            We assume that it is invalid for the same named property to be\n            listed more than once.\n            \"\"\"\n            props = test.get(\"properties\", [])\n            # pyre-fixme[16]: `object` has no attribute `__iter__`.\n            for p in props:\n                if p.get(\"name\", None) == propname:\n                    return p.get(\"value\", defval)\n            return defval\n\n        # pyre-fixme[53]: Captured variable `cmake` is not annotated.\n        # pyre-fixme[53]: Captured variable `env` is not annotated.\n        def list_tests() -> list[dict[str, object]]:\n            output = subprocess.check_output(\n                [require_command(ctest, \"ctest\"), \"--show-only=json-v1\"],\n                env=env,\n                cwd=self.build_dir,\n            )\n            try:\n                data = json.loads(output.decode(\"utf-8\"))\n            except ValueError as exc:\n                raise Exception(\n                    \"Failed to decode cmake test info using %s: %s.  Output was: %r\"\n                    % (ctest, str(exc), output)\n                )\n\n            tests = []\n            machine_suffix = self.build_opts.host_type.as_tuple_string()\n            for test in data[\"tests\"]:\n                working_dir = get_property(test, \"WORKING_DIRECTORY\")\n                labels = []\n                machine_suffix = self.build_opts.host_type.as_tuple_string()\n                labels.append(\"tpx-fb-test-type=3\")\n                labels.append(\"tpx_test_config::buildsystem=getdeps\")\n                labels.append(\"tpx_test_config::platform={}\".format(machine_suffix))\n\n                if get_property(test, \"DISABLED\"):\n                    labels.append(\"disabled\")\n                command = test[\"command\"]\n                if working_dir:\n                    command = [\n                        require_command(cmake, \"cmake\"),\n                        \"-E\",\n                        \"chdir\",\n                        working_dir,\n                    ] + command\n\n                tests.append(\n                    {\n                        \"type\": \"custom\",\n                        \"target\": \"%s-%s-getdeps-%s\"\n                        % (self.manifest.name, test[\"name\"], machine_suffix),\n                        \"command\": command,\n                        \"labels\": labels,\n                        \"env\": {},\n                        \"required_paths\": [],\n                        \"contacts\": [],\n                        \"cwd\": os.getcwd(),\n                    }\n                )\n            return tests\n\n        discover_like_continuous = False\n        if schedule_type == \"continuous\" or (\n            schedule_type == \"base_retry\" and is_public_commit(self.build_opts)\n        ):\n            discover_like_continuous = True\n\n        if discover_like_continuous or schedule_type == \"testwarden\":\n            # for continuous and testwarden runs, disabling retry can give up\n            # better signals for flaky tests.\n            retry = 0\n\n        tpx = None\n        try:\n            from .facebook.testinfra import start_run\n\n            # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got\n            #  `Env`.\n            tpx = path_search(env, \"tpx\")\n        except ImportError:\n            # internal testinfra not available\n            pass\n\n        if tpx and not no_testpilot:\n            import os\n\n            buck_test_info = list_tests()\n\n            buck_test_info_name = os.path.join(self.build_dir, \".buck-test-info.json\")\n            with open(buck_test_info_name, \"w\") as f:\n                json.dump(buck_test_info, f)\n\n            env.set(\"http_proxy\", \"\")\n            env.set(\"https_proxy\", \"\")\n            runs = []\n\n            with start_run(env[\"FBSOURCE_HASH\"]) as run_id:\n                testpilot_args = [\n                    tpx,\n                    \"--force-local-execution\",\n                    \"--buck-test-info\",\n                    buck_test_info_name,\n                    \"--retry=%d\" % retry,\n                    \"-j=%s\" % str(self.num_jobs),\n                    \"--print-long-results\",\n                ]\n\n                if owner:\n                    testpilot_args += [\"--contacts\", owner]\n\n                if env:\n                    testpilot_args.append(\"--env\")\n                    testpilot_args.extend(f\"{key}={val}\" for key, val in env.items())\n\n                if run_id is not None:\n                    testpilot_args += [\"--run-id\", run_id]\n\n                if timeout is not None:\n                    testpilot_args += [\"--timeout\", str(timeout)]\n\n                if test_filter:\n                    testpilot_args += [\"--\", test_filter]\n\n                if schedule_type == \"diff\":\n                    runs.append([\"--collection\", \"oss-diff\", \"--purpose\", \"diff\"])\n                elif discover_like_continuous:\n                    runs.append(\n                        [\n                            \"--tag-new-tests\",\n                            \"--collection\",\n                            \"oss-continuous\",\n                            \"--purpose\",\n                            \"continuous\",\n                        ]\n                    )\n                elif schedule_type == \"testwarden\":\n                    # One run to assess new tests\n                    runs.append(\n                        [\n                            \"--tag-new-tests\",\n                            \"--collection\",\n                            \"oss-new-test-stress\",\n                            \"--stress-runs\",\n                            \"10\",\n                            \"--purpose\",\n                            \"stress-run-new-test\",\n                        ]\n                    )\n                    # And another for existing tests\n                    runs.append(\n                        [\n                            \"--tag-new-tests\",\n                            \"--collection\",\n                            \"oss-existing-test-stress\",\n                            \"--stress-runs\",\n                            \"10\",\n                            \"--purpose\",\n                            \"stress-run\",\n                        ]\n                    )\n                else:\n                    runs.append([])\n\n                for run in runs:\n                    # FIXME: What is this trying to accomplish? Should it fail on first or >=1 errors?\n                    self._run_cmd(\n                        testpilot_args + run,\n                        cwd=self.build_opts.fbcode_builder_dir,\n                        env=env,\n                        use_cmd_prefix=use_cmd_prefix,\n                    )\n        else:\n            args = [\n                require_command(ctest, \"ctest\"),\n                \"--output-on-failure\",\n                \"-j\",\n                str(self.num_jobs),\n            ]\n            if test_filter:\n                args += [\"-R\", test_filter]\n            if test_exclude:\n                args += [\"--exclude-regex\", test_exclude]\n            if timeout is not None:\n                args += [\"--timeout\", str(timeout)]\n\n            count: int = 0\n            retcode: int | None = -1\n            while count <= retry:\n                # FIXME: What is this trying to accomplish? Should it fail on first or >=1 errors?\n                retcode = self._check_cmd(\n                    args, env=env, use_cmd_prefix=use_cmd_prefix, allow_fail=True\n                )\n\n                if retcode == 0:\n                    break\n                if count == 0:\n                    # Only add this option in the second run.\n                    args += [\"--rerun-failed\"]\n                count += 1\n            if retcode is not None and retcode != 0:\n                # Allow except clause in getdeps.main to catch and exit gracefully\n                # This allows non-testpilot runs to fail through the same logic as failed testpilot runs, which may become handy in case if post test processing is needed in the future\n                raise subprocess.CalledProcessError(retcode, args)\n\n\nclass NinjaBootstrap(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        build_dir: str,\n        src_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(NinjaBootstrap, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n\n    def _build(self, reconfigure: bool) -> None:\n        self._check_cmd(\n            [sys.executable, \"configure.py\", \"--bootstrap\"], cwd=self.src_dir\n        )\n        src_ninja = os.path.join(self.src_dir, \"ninja\")\n        dest_ninja = os.path.join(self.inst_dir, \"bin/ninja\")\n        bin_dir = os.path.dirname(dest_ninja)\n        if not os.path.exists(bin_dir):\n            os.makedirs(bin_dir)\n        shutil.copyfile(src_ninja, dest_ninja)\n        shutil.copymode(src_ninja, dest_ninja)\n\n\nclass OpenSSLBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        build_dir: str,\n        src_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(OpenSSLBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n\n    def _build(self, reconfigure: bool) -> None:\n        configure = os.path.join(self.src_dir, \"Configure\")\n\n        # prefer to resolve the perl that we installed from\n        # our manifest on windows, but fall back to the system\n        # path on eg: darwin\n        env = self.env.copy()\n        for m in self.dep_manifests:\n            bindir = os.path.join(self.loader.get_project_install_dir(m), \"bin\")\n            add_path_entry(env, \"PATH\", bindir, append=False)\n\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        perl = typing.cast(str, path_search(env, \"perl\", \"perl\"))\n\n        make_j_args = []\n        extra_args = []\n        if self.build_opts.is_windows():\n            # jom is compatible with nmake, adds the /j argument for parallel build\n            make = \"jom.exe\"\n            make_j_args = [\"/j%s\" % self.num_jobs]\n            args = [\"VC-WIN64A-masm\", \"-utf-8\"]\n            # fixes \"if multiple CL.EXE write to the same .PDB file, please use /FS\"\n            extra_args = [\"/FS\"]\n        elif self.build_opts.is_darwin():\n            make = \"make\"\n            make_j_args = [\"-j%s\" % self.num_jobs]\n            args = (\n                [\"darwin64-x86_64-cc\"]\n                if not self.build_opts.is_arm()\n                else [\"darwin64-arm64-cc\"]\n            )\n        elif self.build_opts.is_linux():\n            make = \"make\"\n            make_j_args = [\"-j%s\" % self.num_jobs]\n            args = (\n                [\"linux-x86_64\"] if not self.build_opts.is_arm() else [\"linux-aarch64\"]\n            )\n        else:\n            raise Exception(\"don't know how to build openssl for %r\" % self.ctx)\n\n        self._check_cmd(\n            [\n                perl,\n                configure,\n                \"--prefix=%s\" % self.inst_dir,\n                \"--openssldir=%s\" % self.inst_dir,\n            ]\n            + args\n            + [\n                \"enable-static-engine\",\n                \"enable-capieng\",\n                \"no-makedepend\",\n                \"no-unit-test\",\n                \"no-tests\",\n            ]\n            + extra_args\n        )\n        # show the config produced\n        self._check_cmd([perl, \"configdata.pm\", \"--dump\"], env=env)\n        make_build = [make] + make_j_args\n        self._check_cmd(make_build, env=env)\n        make_install = [make, \"install_sw\", \"install_ssldirs\"]\n        self._check_cmd(make_install, env=env)\n\n\nclass Boost(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        b2_args: list[str],\n    ) -> None:\n        children = os.listdir(src_dir)\n        assert len(children) == 1, \"expected a single directory entry: %r\" % (children,)\n        boost_src = children[0]\n        assert boost_src.startswith(\"boost\")\n        src_dir = os.path.join(src_dir, children[0])\n        super(Boost, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n        self.b2_args: list[str] = b2_args\n\n    def _build(self, reconfigure: bool) -> None:\n        env = self._compute_env()\n        linkage: list[str] = [\"static\"]\n        if self.build_opts.is_windows() or self.build_opts.shared_libs:\n            linkage.append(\"shared\")\n\n        args = []\n        if self.build_opts.is_darwin():\n            clang = subprocess.check_output([\"xcrun\", \"--find\", \"clang\"])\n            user_config = os.path.join(self.build_dir, \"project-config.jam\")\n            with open(user_config, \"w\") as jamfile:\n                jamfile.write(\"using clang : : %s ;\\n\" % clang.decode().strip())\n            args.append(\"--user-config=%s\" % user_config)\n\n        for link in linkage:\n            bootstrap_args = self.manifest.get_section_as_args(\n                \"bootstrap.args\", self.ctx\n            )\n            if self.build_opts.is_windows():\n                bootstrap = os.path.join(self.src_dir, \"bootstrap.bat\")\n                self._check_cmd([bootstrap] + bootstrap_args, cwd=self.src_dir, env=env)\n                args += [\"address-model=64\"]\n            else:\n                bootstrap = os.path.join(self.src_dir, \"bootstrap.sh\")\n                self._check_cmd(\n                    [bootstrap, \"--prefix=%s\" % self.inst_dir] + bootstrap_args,\n                    cwd=self.src_dir,\n                    env=env,\n                )\n\n            b2 = os.path.join(self.src_dir, \"b2\")\n            self._check_cmd(\n                [\n                    b2,\n                    \"-j%s\" % self.num_jobs,\n                    \"--prefix=%s\" % self.inst_dir,\n                    \"--builddir=%s\" % self.build_dir,\n                ]\n                + args\n                + self.b2_args\n                + [\n                    \"link=%s\" % link,\n                    \"runtime-link=shared\",\n                    \"variant=release\",\n                    \"threading=multi\",\n                    \"debug-symbols=on\",\n                    \"visibility=global\",\n                    \"-d2\",\n                    \"install\",\n                ],\n                cwd=self.src_dir,\n                env=env,\n            )\n\n\nclass NopBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(NopBuilder, self).__init__(\n            loader, dep_manifests, build_opts, ctx, manifest, src_dir, None, inst_dir\n        )\n\n    def build(self, reconfigure: bool) -> None:\n        print(\"Installing %s -> %s\" % (self.src_dir, self.inst_dir))\n        parent = os.path.dirname(self.inst_dir)\n        if not os.path.exists(parent):\n            os.makedirs(parent)\n\n        install_files = self.manifest.get_section_as_ordered_pairs(\n            \"install.files\", self.ctx\n        )\n        if install_files:\n            for src_name, dest_name in self.manifest.get_section_as_ordered_pairs(\n                \"install.files\", self.ctx\n            ):\n                # pyre-fixme[6]: For 2nd argument expected `Union[PathLike[str],\n                #  str]` but got `Optional[str]`.\n                full_dest = os.path.join(self.inst_dir, dest_name)\n                full_src = os.path.join(self.src_dir, src_name)\n\n                dest_parent = os.path.dirname(full_dest)\n                if not os.path.exists(dest_parent):\n                    os.makedirs(dest_parent)\n                if os.path.isdir(full_src):\n                    if not os.path.exists(full_dest):\n                        simple_copytree(full_src, full_dest)\n                else:\n                    shutil.copyfile(full_src, full_dest)\n                    shutil.copymode(full_src, full_dest)\n                    # This is a bit gross, but the mac ninja.zip doesn't\n                    # give ninja execute permissions, so force them on\n                    # for things that look like they live in a bin dir\n                    # pyre-fixme[6]: For 1st argument expected `PathLike[AnyStr]`\n                    #  but got `Optional[str]`.\n                    if os.path.dirname(dest_name) == \"bin\":\n                        st = os.lstat(full_dest)\n                        os.chmod(full_dest, st.st_mode | stat.S_IXUSR)\n        else:\n            if not os.path.exists(self.inst_dir):\n                simple_copytree(self.src_dir, self.inst_dir)\n\n\nclass SetupPyBuilder(BuilderBase):\n    def _build(self, reconfigure: bool) -> None:\n        env = self._compute_env()\n\n        setup_env = self.manifest.get_section_as_dict(\"setup-py.env\", self.ctx)\n        for key, value in setup_env.items():\n            # pyre-fixme[6]: For 2nd argument expected `str` but got `Optional[str]`.\n            env[key] = value\n\n        setup_py_path = os.path.join(self.src_dir, \"setup.py\")\n\n        if not os.path.exists(setup_py_path):\n            raise RuntimeError(f\"setup.py script not found at {setup_py_path}\")\n\n        self._check_cmd(\n            # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n            #  `List[Union[str, None, str]]`.\n            # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got\n            #  `Env`.\n            [path_search(env, \"python3\"), setup_py_path, \"install\"],\n            cwd=self.src_dir,\n            env=env,\n        )\n\n        # Create the installation directory if it doesn't exist\n        os.makedirs(self.inst_dir, exist_ok=True)\n\n        # Mark the project as built\n        with open(os.path.join(self.inst_dir, \".built-by-getdeps\"), \"w\") as f:\n            f.write(\"built\")\n\n    def run_tests(\n        self,\n        schedule_type: str,\n        owner: str | None,\n        test_filter: str | None,\n        test_exclude: str | None,\n        retry: int,\n        no_testpilot: bool,\n        timeout: int | None = None,\n    ) -> None:\n        # setup.py actually no longer has a standard command for running tests.\n        # Instead we let manifest files specify an arbitrary Python file to run\n        # as a test.\n\n        # Get the test command from the manifest\n        python_script = self.manifest.get(\n            \"setup-py.test\", \"python_script\", ctx=self.ctx\n        )\n        if not python_script:\n            print(f\"No test script specified for {self.manifest.name}\")\n            return\n\n        # Run the command\n        env = self._compute_env()\n        self._check_cmd([\"python3\", python_script], cwd=self.src_dir, env=env)\n\n\nclass SqliteBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n    ) -> None:\n        super(SqliteBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n\n    def _build(self, reconfigure: bool) -> None:\n        for f in [\"sqlite3.c\", \"sqlite3.h\", \"sqlite3ext.h\"]:\n            src = os.path.join(self.src_dir, f)\n            dest = os.path.join(self.build_dir, f)\n            copy_if_different(src, dest)\n\n        cmake_lists = \"\"\"\ncmake_minimum_required(VERSION 3.5 FATAL_ERROR)\nproject(sqlite3 C)\nadd_library(sqlite3 STATIC sqlite3.c)\n# These options are taken from the defaults in Makefile.msc in\n# the sqlite distribution\ntarget_compile_definitions(sqlite3 PRIVATE\n    -DSQLITE_ENABLE_COLUMN_METADATA=1\n    -DSQLITE_ENABLE_FTS3=1\n    -DSQLITE_ENABLE_RTREE=1\n    -DSQLITE_ENABLE_GEOPOLY=1\n    -DSQLITE_ENABLE_JSON1=1\n    -DSQLITE_ENABLE_STMTVTAB=1\n    -DSQLITE_ENABLE_DBPAGE_VTAB=1\n    -DSQLITE_ENABLE_DBSTAT_VTAB=1\n    -DSQLITE_INTROSPECTION_PRAGMAS=1\n    -DSQLITE_ENABLE_DESERIALIZE=1\n)\ninstall(TARGETS sqlite3)\ninstall(FILES sqlite3.h sqlite3ext.h DESTINATION include)\n            \"\"\"\n\n        with open(os.path.join(self.build_dir, \"CMakeLists.txt\"), \"w\") as f:\n            f.write(cmake_lists)\n\n        defines = {\n            \"CMAKE_INSTALL_PREFIX\": self.inst_dir,\n            \"BUILD_SHARED_LIBS\": \"ON\" if self.build_opts.shared_libs else \"OFF\",\n            \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\",\n        }\n        define_args = [\"-D%s=%s\" % (k, v) for (k, v) in defines.items()]\n        define_args += [\"-G\", \"Ninja\"]\n\n        env = self._compute_env()\n\n        # Resolve the cmake that we installed\n        # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`.\n        cmake = path_search(env, \"cmake\")\n\n        # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n        #  `List[Optional[str]]`.\n        self._check_cmd([cmake, self.build_dir] + define_args, env=env)\n        self._check_cmd(\n            # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n            #  `List[Union[str, str, str, str, str, None, str]]`.\n            [\n                cmake,\n                \"--build\",\n                self.build_dir,\n                \"--target\",\n                \"install\",\n                \"--config\",\n                self.build_opts.build_type,\n                \"-j\",\n                str(self.num_jobs),\n            ],\n            env=env,\n        )\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/buildopts.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport argparse\nimport errno\nimport glob\nimport ntpath\nimport os\nimport subprocess\nimport sys\nimport tempfile\nimport typing\nfrom collections.abc import Mapping\n\nfrom .copytree import containing_repo_type\nfrom .envfuncs import add_flag, add_path_entry, Env\nfrom .fetcher import get_fbsource_repo_data, homebrew_package_prefix\nfrom .manifest import ContextGenerator\nfrom .platform import get_available_ram, HostType, is_windows\n\nif typing.TYPE_CHECKING:\n    from .load import ManifestLoader\n    from .manifest import ManifestContext, ManifestParser\n\n\nGITBASH_TMP: str = \"c:\\\\tools\\\\fb.gitbash\\\\tmp\"\n\n\ndef detect_project(path: str) -> tuple[str | None, str | None]:\n    repo_type, repo_root = containing_repo_type(path)\n    if repo_type is None:\n        return None, None\n\n    # Look for a .projectid file.  If it exists, read the project name from it.\n    # pyre-fixme[6]: For 1st argument expected `LiteralString` but got `Optional[str]`.\n    project_id_path = os.path.join(repo_root, \".projectid\")\n    try:\n        with open(project_id_path, \"r\") as f:\n            project_name = f.read().strip()\n            return repo_root, project_name\n    except EnvironmentError as ex:\n        if ex.errno != errno.ENOENT:\n            raise\n\n    return repo_root, None\n\n\nclass BuildOptions:\n    def __init__(\n        self,\n        fbcode_builder_dir: str,\n        scratch_dir: str,\n        host_type: HostType,\n        install_dir: str | None = None,\n        num_jobs: int = 0,\n        use_shipit: bool = False,\n        vcvars_path: str | None = None,\n        allow_system_packages: bool = False,\n        lfs_path: str | None = None,\n        shared_libs: bool = False,\n        facebook_internal: bool | None = None,\n        free_up_disk: bool = False,\n        build_type: str | None = None,\n    ) -> None:\n        \"\"\"fbcode_builder_dir - the path to either the in-fbsource fbcode_builder dir,\n                             or for shipit-transformed repos, the build dir that\n                             has been mapped into that dir.\n        scratch_dir - a place where we can store repos and build bits.\n                      This path should be stable across runs and ideally\n                      should not be in the repo of the project being built,\n                      but that is ultimately where we generally fall back\n                      for builds outside of FB\n        install_dir - where the project will ultimately be installed\n        num_jobs - the level of concurrency to use while building\n        use_shipit - use real shipit instead of the simple shipit transformer\n        vcvars_path - Path to external VS toolchain's vsvarsall.bat\n        shared_libs - whether to build shared libraries\n        free_up_disk - take extra actions to save runner disk space\n        build_type - CMAKE_BUILD_TYPE, used by cmake and cargo builders\n        \"\"\"\n\n        if not install_dir:\n            install_dir = os.path.join(scratch_dir, \"installed\")\n\n        self.project_hashes: str | None = None\n        for p in [\"../deps/github_hashes\", \"../project_hashes\"]:\n            hashes = os.path.join(fbcode_builder_dir, p)\n            if os.path.exists(hashes):\n                self.project_hashes = hashes\n                break\n\n        # Detect what repository and project we are being run from.\n        # pyre-fixme[4]: Attribute must be annotated.\n        self.repo_root, self.repo_project = detect_project(os.getcwd())\n\n        # If we are running from an fbsource repository, set self.fbsource_dir\n        # to allow the ShipIt-based fetchers to use it.\n        if self.repo_project == \"fbsource\":\n            self.fbsource_dir: str | None = self.repo_root\n        else:\n            self.fbsource_dir = None\n\n        if facebook_internal is None:\n            if self.fbsource_dir:\n                facebook_internal = True\n            else:\n                facebook_internal = False\n\n        self.facebook_internal: bool = facebook_internal\n        self.specified_num_jobs: int = num_jobs\n        self.scratch_dir: str = scratch_dir\n        self.install_dir: str = install_dir\n        self.fbcode_builder_dir: str = fbcode_builder_dir\n        self.host_type: HostType = host_type\n        self.use_shipit: bool = use_shipit\n        self.allow_system_packages: bool = allow_system_packages\n        self.lfs_path: str | None = lfs_path\n        self.shared_libs: bool = shared_libs\n        self.free_up_disk: bool = free_up_disk\n        self.build_type: str | None = build_type\n\n        lib_path: str | None = None\n        if self.is_darwin():\n            lib_path = \"DYLD_LIBRARY_PATH\"\n        elif self.is_linux():\n            lib_path = \"LD_LIBRARY_PATH\"\n        elif self.is_windows():\n            lib_path = \"PATH\"\n        else:\n            lib_path = None\n        self.lib_path: str | None = lib_path\n\n        if vcvars_path is None and is_windows():\n\n            try:\n                # Allow a site-specific vcvarsall path.\n                from .facebook.vcvarsall import build_default_vcvarsall\n            except ImportError:\n                vcvarsall: list[str] = []\n            else:\n                vcvarsall = (\n                    build_default_vcvarsall(self.fbsource_dir)\n                    if self.fbsource_dir is not None\n                    else []\n                )\n\n            # On Windows, the compiler is not available in the PATH by\n            # default so we need to run the vcvarsall script to populate the\n            # environment. We use a glob to find some version of this script\n            # as deployed with Visual Studio.\n            if len(vcvarsall) == 0:\n                # check the 64 bit installs\n                for year in [\"2022\"]:\n                    vcvarsall += glob.glob(\n                        os.path.join(\n                            os.environ.get(\"ProgramFiles\", \"C:\\\\Program Files\"),\n                            \"Microsoft Visual Studio\",\n                            year,\n                            \"*\",\n                            \"VC\",\n                            \"Auxiliary\",\n                            \"Build\",\n                            \"vcvarsall.bat\",\n                        )\n                    )\n\n                # then the 32 bit ones\n                for year in [\"2022\", \"2019\", \"2017\"]:\n                    vcvarsall += glob.glob(\n                        os.path.join(\n                            os.environ[\"ProgramFiles(x86)\"],\n                            \"Microsoft Visual Studio\",\n                            year,\n                            \"*\",\n                            \"VC\",\n                            \"Auxiliary\",\n                            \"Build\",\n                            \"vcvarsall.bat\",\n                        )\n                    )\n            if len(vcvarsall) == 0:\n                raise Exception(\n                    \"Could not find vcvarsall.bat. Please install Visual Studio.\"\n                )\n            vcvars_path = vcvarsall[0]\n            print(f\"Using vcvarsall.bat from {vcvars_path}\", file=sys.stderr)\n\n        self.vcvars_path: str | None = vcvars_path\n\n    @property\n    def manifests_dir(self) -> str:\n        return os.path.join(self.fbcode_builder_dir, \"manifests\")\n\n    def is_darwin(self) -> bool:\n        return self.host_type.is_darwin()\n\n    def is_windows(self) -> bool:\n        return self.host_type.is_windows()\n\n    def is_arm(self) -> bool:\n        return self.host_type.is_arm()\n\n    def get_vcvars_path(self) -> str | None:\n        return self.vcvars_path\n\n    def is_linux(self) -> bool:\n        return self.host_type.is_linux()\n\n    def is_freebsd(self) -> bool:\n        return self.host_type.is_freebsd()\n\n    def get_num_jobs(self, job_weight: int) -> int:\n        \"\"\"Given an estimated job_weight in MiB, compute a reasonable concurrency limit.\"\"\"\n        if self.specified_num_jobs:\n            return self.specified_num_jobs\n\n        available_ram = get_available_ram()\n\n        import multiprocessing\n\n        return max(1, min(multiprocessing.cpu_count(), available_ram // job_weight))\n\n    def get_context_generator(\n        self, host_tuple: str | HostType | None = None\n    ) -> ContextGenerator:\n        \"\"\"Create a manifest ContextGenerator for the specified target platform.\"\"\"\n        if host_tuple is None:\n            host_type = self.host_type\n        elif isinstance(host_tuple, HostType):\n            host_type = host_tuple\n        else:\n            host_type = HostType.from_tuple_string(host_tuple)\n\n        return ContextGenerator(\n            {\n                \"os\": host_type.ostype,\n                \"distro\": host_type.distro,\n                \"distro_vers\": host_type.distrovers,\n                \"fb\": \"on\" if self.facebook_internal else \"off\",\n                \"fbsource\": \"on\" if self.fbsource_dir else \"off\",\n                \"test\": \"off\",\n                \"shared_libs\": \"on\" if self.shared_libs else \"off\",\n            }\n        )\n\n    def compute_env_for_install_dirs(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        ctx: ManifestContext,\n        env: Env | None = None,\n        manifest: ManifestParser | None = None,\n    ) -> Env:  # noqa: C901\n        if env is not None:\n            env = env.copy()\n        else:\n            env = Env()\n\n        env[\"GETDEPS_BUILD_DIR\"] = os.path.join(self.scratch_dir, \"build\")\n        env[\"GETDEPS_INSTALL_DIR\"] = self.install_dir\n\n        # Python setuptools attempts to discover a local MSVC for\n        # building Python extensions. On Windows, getdeps already\n        # supports invoking a vcvarsall prior to compilation.\n        #\n        # Tell setuptools to bypass its own search. This fixes a bug\n        # where setuptools would fail when run from CMake on GitHub\n        # Actions with the inscrutable message 'error: Microsoft\n        # Visual C++ 14.0 is required. Get it with \"Build Tools for\n        # Visual Studio\"'. I suspect the actual error is that the\n        # environment or PATH is overflowing.\n        #\n        # For extra credit, someone could patch setuptools to\n        # propagate the actual error message from vcvarsall, because\n        # often it does not mean Visual C++ is not available.\n        #\n        # Related discussions:\n        # - https://github.com/pypa/setuptools/issues/2028\n        # - https://github.com/pypa/setuptools/issues/2307\n        # - https://developercommunity.visualstudio.com/t/error-microsoft-visual-c-140-is-required/409173\n        # - https://github.com/OpenMS/OpenMS/pull/4779\n        # - https://github.com/actions/virtual-environments/issues/1484\n\n        if self.is_windows() and self.get_vcvars_path():\n            env[\"DISTUTILS_USE_SDK\"] = \"1\"\n\n        # On macOS we need to set `SDKROOT` when we use clang for system\n        # header files.\n        if self.is_darwin() and \"SDKROOT\" not in env:\n            sdkroot = subprocess.check_output([\"xcrun\", \"--show-sdk-path\"])\n            env[\"SDKROOT\"] = sdkroot.decode().strip()\n\n        if (\n            self.is_darwin()\n            and self.allow_system_packages\n            and self.host_type.get_package_manager() == \"homebrew\"\n            and manifest\n            and manifest.resolved_system_packages\n        ):\n            # Homebrew packages may not be on the default PATHs\n            brew_packages = manifest.resolved_system_packages.get(\"homebrew\", [])\n            for p in brew_packages:\n                found = self.add_homebrew_package_to_env(p, env)\n                # Try extra hard to find openssl, needed with homebrew on macOS\n                if found and p.startswith(\"openssl\"):\n                    candidate = homebrew_package_prefix(\"openssl@1.1\")\n                    # pyre-fixme[6]: For 1st argument expected\n                    #  `Union[PathLike[bytes], PathLike[str], bytes, int, str]` but got\n                    #  `Optional[str]`.\n                    if os.path.exists(candidate):\n                        # pyre-fixme[6]: For 2nd argument expected `str` but got\n                        #  `Optional[str]`.\n                        os.environ[\"OPENSSL_ROOT_DIR\"] = candidate\n                        env[\"OPENSSL_ROOT_DIR\"] = os.environ[\"OPENSSL_ROOT_DIR\"]\n\n        if self.fbsource_dir:\n            env[\"YARN_YARN_OFFLINE_MIRROR\"] = os.path.join(\n                self.fbsource_dir, \"xplat/third-party/yarn/offline-mirror\"\n            )\n            yarn_exe = \"yarn.bat\" if self.is_windows() else \"yarn\"\n            env[\"YARN_PATH\"] = os.path.join(\n                # pyre-fixme[6]: For 1st argument expected `LiteralString` but got\n                #  `Optional[str]`.\n                self.fbsource_dir,\n                \"xplat/third-party/yarn/\",\n                yarn_exe,\n            )\n            node_exe = \"node-win-x64.exe\" if self.is_windows() else \"node\"\n            env[\"NODE_BIN\"] = os.path.join(\n                # pyre-fixme[6]: For 1st argument expected `LiteralString` but got\n                #  `Optional[str]`.\n                self.fbsource_dir,\n                \"xplat/third-party/node/bin/\",\n                node_exe,\n            )\n            env[\"RUST_VENDORED_CRATES_DIR\"] = os.path.join(\n                # pyre-fixme[6]: For 1st argument expected `LiteralString` but got\n                #  `Optional[str]`.\n                self.fbsource_dir,\n                \"third-party/rust/vendor\",\n            )\n            hash_data = get_fbsource_repo_data(self)\n            env[\"FBSOURCE_HASH\"] = hash_data.hash\n            env[\"FBSOURCE_DATE\"] = hash_data.date\n\n        # reverse as we are prepending to the PATHs\n        for m in reversed(dep_manifests):\n            is_direct_dep = (\n                manifest is not None and m.name in manifest.get_dependencies(ctx)\n            )\n            d = loader.get_project_install_dir(m)\n            if os.path.exists(d):\n                self.add_prefix_to_env(\n                    d,\n                    env,\n                    append=False,\n                    is_direct_dep=is_direct_dep,\n                )\n\n        # Linux is always system openssl\n        system_openssl: bool = self.is_linux()\n\n        # For other systems lets see if package is requested\n        if not system_openssl and manifest and manifest.resolved_system_packages:\n            for _pkg_type, pkgs in manifest.resolved_system_packages.items():\n                for p in pkgs:\n                    if p.startswith(\"openssl\") or p.startswith(\"libssl\"):\n                        system_openssl = True\n                        break\n\n        # Let openssl know to pick up the system certs if present\n        if system_openssl or \"OPENSSL_DIR\" in env:\n            for system_ssl_cfg in [\"/etc/pki/tls\", \"/etc/ssl\"]:\n                if os.path.isdir(system_ssl_cfg):\n                    cert_dir = system_ssl_cfg + \"/certs\"\n                    if os.path.isdir(cert_dir):\n                        env[\"SSL_CERT_DIR\"] = cert_dir\n                    cert_file = system_ssl_cfg + \"/cert.pem\"\n                    if os.path.isfile(cert_file):\n                        env[\"SSL_CERT_FILE\"] = cert_file\n\n        return env\n\n    def add_homebrew_package_to_env(self, package: str, env: Env) -> bool:\n        prefix = homebrew_package_prefix(package)\n        if prefix and os.path.exists(prefix):\n            return self.add_prefix_to_env(\n                prefix, env, append=False, add_library_path=True\n            )\n        return False\n\n    def add_prefix_to_env(\n        self,\n        d: str,\n        env: Env,\n        append: bool = True,\n        add_library_path: bool = False,\n        is_direct_dep: bool = False,\n    ) -> bool:  # noqa: C901\n        bindir: str = os.path.join(d, \"bin\")\n        found: bool = False\n        has_pkgconfig: bool = False\n        pkgconfig: str = os.path.join(d, \"lib\", \"pkgconfig\")\n        if os.path.exists(pkgconfig):\n            found = True\n            has_pkgconfig = True\n            add_path_entry(env, \"PKG_CONFIG_PATH\", pkgconfig, append=append)\n\n        pkgconfig = os.path.join(d, \"lib64\", \"pkgconfig\")\n        if os.path.exists(pkgconfig):\n            found = True\n            has_pkgconfig = True\n            add_path_entry(env, \"PKG_CONFIG_PATH\", pkgconfig, append=append)\n\n        add_path_entry(env, \"CMAKE_PREFIX_PATH\", d, append=append)\n\n        # Tell the thrift compiler about includes it needs to consider\n        thriftdir: str = os.path.join(d, \"include\", \"thrift-files\")\n        if os.path.exists(thriftdir):\n            found = True\n            add_path_entry(env, \"THRIFT_INCLUDE_PATH\", thriftdir, append=append)\n\n        # module detection for python is old fashioned and needs flags\n        includedir: str = os.path.join(d, \"include\")\n        if os.path.exists(includedir):\n            found = True\n            ncursesincludedir: str = os.path.join(d, \"include\", \"ncurses\")\n            if os.path.exists(ncursesincludedir):\n                add_path_entry(env, \"C_INCLUDE_PATH\", ncursesincludedir, append=append)\n                add_flag(env, \"CPPFLAGS\", f\"-I{includedir}\", append=append)\n                add_flag(env, \"CPPFLAGS\", f\"-I{ncursesincludedir}\", append=append)\n            elif \"/bz2-\" in d:\n                add_flag(env, \"CPPFLAGS\", f\"-I{includedir}\", append=append)\n            # For non-pkgconfig projects Cabal has no way to find the includes or\n            # libraries, so we provide a set of extra Cabal flags in the env\n            if not has_pkgconfig and is_direct_dep:\n                add_flag(\n                    env,\n                    \"GETDEPS_CABAL_FLAGS\",\n                    f\"--extra-include-dirs={includedir}\",\n                    append=append,\n                )\n\n            # The thrift compiler's built-in includes are installed directly to the include dir\n            includethriftdir: str = os.path.join(d, \"include\", \"thrift\")\n            if os.path.exists(includethriftdir):\n                add_path_entry(env, \"THRIFT_INCLUDE_PATH\", includedir, append=append)\n\n        # Map from FB python manifests to PYTHONPATH\n        pydir: str = os.path.join(d, \"lib\", \"fb-py-libs\")\n        if os.path.exists(pydir):\n            found = True\n            manifest_ext: str = \".manifest\"\n            pymanifestfiles: list[str] = [\n                f\n                for f in os.listdir(pydir)\n                if f.endswith(manifest_ext) and os.path.isfile(os.path.join(pydir, f))\n            ]\n            for f in pymanifestfiles:\n                subdir = f[: -len(manifest_ext)]\n                add_path_entry(\n                    env, \"PYTHONPATH\", os.path.join(pydir, subdir), append=append\n                )\n\n        # Allow resolving shared objects built earlier (eg: zstd\n        # doesn't include the full path to the dylib in its linkage\n        # so we need to give it an assist)\n        if self.lib_path:\n            for lib in [\"lib\", \"lib64\"]:\n                libdir: str = os.path.join(d, lib)\n                if os.path.exists(libdir):\n                    found = True\n                    # pyre-fixme[6]: For 2nd argument expected `str` but got\n                    #  `Optional[str]`.\n                    add_path_entry(env, self.lib_path, libdir, append=append)\n                    # module detection for python is old fashioned and needs flags\n                    if \"/ncurses-\" in d:\n                        add_flag(env, \"LDFLAGS\", f\"-L{libdir}\", append=append)\n                    elif \"/bz2-\" in d:\n                        add_flag(env, \"LDFLAGS\", f\"-L{libdir}\", append=append)\n                    if add_library_path:\n                        add_path_entry(env, \"LIBRARY_PATH\", libdir, append=append)\n                    if not has_pkgconfig and is_direct_dep:\n                        add_flag(\n                            env,\n                            \"GETDEPS_CABAL_FLAGS\",\n                            f\"--extra-lib-dirs={libdir}\",\n                            append=append,\n                        )\n\n        # Allow resolving binaries (eg: cmake, ninja) and dlls\n        # built by earlier steps\n        if os.path.exists(bindir):\n            found = True\n            add_path_entry(env, \"PATH\", bindir, append=append)\n\n        # If rustc is present in the `bin` directory, set RUSTC to prevent\n        # cargo uses the rustc installed in the system.\n        if self.is_windows():\n            cargo_path: str = os.path.join(bindir, \"cargo.exe\")\n            rustc_path: str = os.path.join(bindir, \"rustc.exe\")\n            rustdoc_path: str = os.path.join(bindir, \"rustdoc.exe\")\n        else:\n            cargo_path = os.path.join(bindir, \"cargo\")\n            rustc_path = os.path.join(bindir, \"rustc\")\n            rustdoc_path = os.path.join(bindir, \"rustdoc\")\n\n        if os.path.isfile(rustc_path):\n            env[\"CARGO_BIN\"] = cargo_path\n            env[\"RUSTC\"] = rustc_path\n            env[\"RUSTDOC\"] = rustdoc_path\n\n        openssl_include: str = os.path.join(d, \"include\", \"openssl\")\n        if os.path.isdir(openssl_include) and any(\n            os.path.isfile(os.path.join(d, \"lib\", libcrypto))\n            for libcrypto in (\"libcrypto.lib\", \"libcrypto.so\", \"libcrypto.a\")\n        ):\n            # This must be the openssl library, let Rust know about it\n            env[\"OPENSSL_DIR\"] = d\n\n        return found\n\n\ndef list_win32_subst_letters() -> dict[str, str]:\n    output = subprocess.check_output([\"subst\"]).decode(\"utf-8\")\n    # The output is a set of lines like: `F:\\: => C:\\open\\some\\where`\n    lines = output.strip().split(\"\\r\\n\")\n    mapping: dict[str, str] = {}\n    for line in lines:\n        fields = line.split(\": => \")\n        if len(fields) != 2:\n            continue\n        letter = fields[0]\n        path = fields[1]\n        mapping[letter] = path\n\n    return mapping\n\n\ndef find_existing_win32_subst_for_path(\n    path: str,\n    subst_mapping: Mapping[str, str],\n) -> str | None:\n    path = ntpath.normcase(ntpath.normpath(path))\n    for letter, target in subst_mapping.items():\n        if ntpath.normcase(target) == path:\n            return letter\n    return None\n\n\ndef find_unused_drive_letter() -> str | None:\n    import ctypes\n\n    buffer_len = 256\n    blen = ctypes.c_uint(buffer_len)\n    rv = ctypes.c_uint()\n    bufs = ctypes.create_string_buffer(buffer_len)\n    # pyre-fixme[16]: Module `ctypes` has no attribute `windll`.\n    rv = ctypes.windll.kernel32.GetLogicalDriveStringsA(blen, bufs)\n    if rv > buffer_len:\n        raise Exception(\"GetLogicalDriveStringsA result too large for buffer\")\n    nul = \"\\x00\".encode(\"ascii\")\n\n    used: list[str] = [\n        drive.decode(\"ascii\")[0] for drive in bufs.raw.strip(nul).split(nul)\n    ]\n    possible: list[str] = [c for c in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"]\n    available: list[str] = sorted(list(set(possible) - set(used)))\n    if len(available) == 0:\n        return None\n    # Prefer to assign later letters rather than earlier letters\n    return available[-1]\n\n\ndef map_subst_path(path: str) -> str:\n    \"\"\"find a short drive letter mapping for a path\"\"\"\n    for _attempt in range(0, 24):\n        drive = find_existing_win32_subst_for_path(\n            path, subst_mapping=list_win32_subst_letters()\n        )\n        if drive:\n            return drive\n        available = find_unused_drive_letter()\n        if available is None:\n            raise Exception(\n                (\n                    \"unable to make shorter subst mapping for %s; \"\n                    \"no available drive letters\"\n                )\n                % path\n            )\n\n        # Try to set up a subst mapping; note that we may be racing with\n        # other processes on the same host, so this may not succeed.\n        try:\n            subprocess.check_call([\"subst\", \"%s:\" % available, path])\n            subst = \"%s:\\\\\" % available\n            print(\"Mapped scratch dir %s -> %s\" % (path, subst), file=sys.stderr)\n            return subst\n        except Exception:\n            print(\"Failed to map %s -> %s\" % (available, path), file=sys.stderr)\n\n    raise Exception(\"failed to set up a subst path for %s\" % path)\n\n\ndef _check_host_type(args: argparse.Namespace, host_type: HostType | None) -> HostType:\n    if host_type is None:\n        host_tuple_string: str | None = getattr(args, \"host_type\", None)\n        if host_tuple_string:\n            host_type = HostType.from_tuple_string(host_tuple_string)\n        else:\n            host_type = HostType()\n\n    assert isinstance(host_type, HostType)\n    return host_type\n\n\ndef setup_build_options(\n    args: argparse.Namespace, host_type: HostType | None = None\n) -> BuildOptions:\n    \"\"\"Create a BuildOptions object based on the arguments\"\"\"\n\n    fbcode_builder_dir: str = os.path.dirname(\n        os.path.dirname(os.path.abspath(__file__))\n    )\n    scratch_dir: str | None = args.scratch_path\n    if not scratch_dir:\n        # TODO: `mkscratch` doesn't currently know how best to place things on\n        # sandcastle, so whip up something reasonable-ish\n        if \"SANDCASTLE\" in os.environ:\n            if \"DISK_TEMP\" not in os.environ:\n                raise Exception(\n                    (\n                        \"I need DISK_TEMP to be set in the sandcastle environment \"\n                        \"so that I can store build products somewhere sane\"\n                    )\n                )\n\n            disk_temp: str = os.environ[\"DISK_TEMP\"]\n            if is_windows():\n                # force use gitbash tmp dir for windows, as its less likely to have a tmp cleaner\n                # that removes extracted prior dated source files\n                os.makedirs(GITBASH_TMP, exist_ok=True)\n                print(\n                    f\"Using {GITBASH_TMP} instead of DISK_TEMP {disk_temp} for scratch dir\",\n                    file=sys.stderr,\n                )\n                disk_temp = GITBASH_TMP\n\n            scratch_dir = os.path.join(disk_temp, \"fbcode_builder_getdeps\")\n        if not scratch_dir:\n            try:\n                scratch_dir = (\n                    subprocess.check_output(\n                        [\"mkscratch\", \"path\", \"--subdir\", \"fbcode_builder_getdeps\"]\n                    )\n                    .strip()\n                    .decode(\"utf-8\")\n                )\n            except OSError as exc:\n                if exc.errno != errno.ENOENT:\n                    # A legit failure; don't fall back, surface the error\n                    raise\n                # This system doesn't have mkscratch so we fall back to\n                # something local.\n                munged: str = fbcode_builder_dir.replace(\"Z\", \"zZ\")\n                for s in [\"/\", \"\\\\\", \":\"]:\n                    munged = munged.replace(s, \"Z\")\n\n                if is_windows() and os.path.isdir(\"c:/open\"):\n                    temp: str = \"c:/open/scratch\"\n                else:\n                    temp = tempfile.gettempdir()\n\n                scratch_dir = os.path.join(temp, \"fbcode_builder_getdeps-%s\" % munged)\n                if not is_windows() and os.geteuid() == 0:\n                    # Running as root; in the case where someone runs\n                    # sudo getdeps.py install-system-deps\n                    # and then runs as build without privs, we want to avoid creating\n                    # a scratch dir that the second stage cannot write to.\n                    # So we generate a different path if we are root.\n                    scratch_dir += \"-root\"\n\n        if not os.path.exists(scratch_dir):\n            os.makedirs(scratch_dir)\n\n        if is_windows():\n            subst = map_subst_path(scratch_dir)\n            scratch_dir = subst\n    else:\n        if not os.path.exists(scratch_dir):\n            os.makedirs(scratch_dir)\n\n    # Make sure we normalize the scratch path.  This path is used as part of the hash\n    # computation for detecting if projects have been updated, so we need to always\n    # use the exact same string to refer to a given directory.\n    # But! realpath in some combinations of Windows/Python3 versions can expand the\n    # drive substitutions on Windows, so avoid that!\n    if not is_windows():\n        scratch_dir = os.path.realpath(scratch_dir)\n\n    # Save these args passed by the user in an env variable, so it\n    # can be used while hashing this build.\n    os.environ[\"GETDEPS_CMAKE_DEFINES\"] = getattr(args, \"extra_cmake_defines\", \"\") or \"\"\n\n    host_type = _check_host_type(args, host_type)\n\n    build_args: dict[str, object] = {\n        k: v\n        for (k, v) in vars(args).items()\n        if k\n        in {\n            \"num_jobs\",\n            \"use_shipit\",\n            \"vcvars_path\",\n            \"allow_system_packages\",\n            \"lfs_path\",\n            \"shared_libs\",\n            \"free_up_disk\",\n            \"build_type\",\n        }\n    }\n\n    return BuildOptions(\n        fbcode_builder_dir,\n        scratch_dir,\n        host_type,\n        install_dir=args.install_prefix,\n        facebook_internal=args.facebook_internal,\n        # pyre-fixme[6]: For 6th argument expected `Optional[str]` but got `object`.\n        # pyre-fixme[6]: For 6th argument expected `bool` but got `object`.\n        # pyre-fixme[6]: For 6th argument expected `int` but got `object`.\n        **build_args,\n    )\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/cache.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\n\nclass ArtifactCache:\n    \"\"\"The ArtifactCache is a small abstraction that allows caching\n    named things in some external storage mechanism.\n    The primary use case is for storing the build products on CI\n    systems to accelerate the build\"\"\"\n\n    def download_to_file(self, name: str, dest_file_name: str) -> bool:\n        \"\"\"If `name` exists in the cache, download it and place it\n        in the specified `dest_file_name` location on the filesystem.\n        If a transient issue was encountered a TransientFailure shall\n        be raised.\n        If `name` doesn't exist in the cache `False` shall be returned.\n        If `dest_file_name` was successfully updated `True` shall be\n        returned.\n        All other conditions shall raise an appropriate exception.\"\"\"\n        return False\n\n    def upload_from_file(self, name: str, source_file_name: str) -> None:\n        \"\"\"Causes `name` to be populated in the cache by uploading\n        the contents of `source_file_name` to the storage system.\n        If a transient issue was encountered a TransientFailure shall\n        be raised.\n        If the upload failed for some other reason, an appropriate\n        exception shall be raised.\"\"\"\n        pass\n\n\ndef create_cache() -> ArtifactCache | None:\n    \"\"\"This function is monkey patchable to provide an actual\n    implementation\"\"\"\n    return None\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/cargo.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport os\nimport re\nimport shutil\nimport sys\nimport typing\n\nfrom .builder import BuilderBase\nfrom .copytree import rmtree_more, simple_copytree\n\nif typing.TYPE_CHECKING:\n    from .buildopts import BuildOptions\n    from .load import ManifestLoader\n    from .manifest import ManifestContext, ManifestParser\n\n\nclass CargoBuilder(BuilderBase):\n    def __init__(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],  # manifests of dependencies\n        build_opts: BuildOptions,\n        ctx: ManifestContext,\n        manifest: ManifestParser,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        build_doc: bool,\n        workspace_dir: str | None,\n        manifests_to_build: str | None,\n        cargo_config_file: str | None,\n    ) -> None:\n        super(CargoBuilder, self).__init__(\n            loader,\n            dep_manifests,\n            build_opts,\n            ctx,\n            manifest,\n            src_dir,\n            build_dir,\n            inst_dir,\n        )\n        self.build_doc = build_doc\n        self.ws_dir: str | None = workspace_dir\n        # pyre-fixme[8]: Attribute has type `Optional[List[str]]`; used as\n        #  `Union[None, List[str], str]`.\n        self.manifests_to_build: list[str] | None = (\n            manifests_to_build and manifests_to_build.split(\",\")\n        )\n        self.loader: ManifestLoader = loader\n        self.cargo_config_file_subdir: str | None = cargo_config_file\n\n    def run_cargo(\n        self,\n        install_dirs: list[str],\n        operation: str,\n        args: list[str] | None = None,\n    ) -> None:\n        args = args or []\n        env = self._compute_env()\n        # Enable using nightly features with stable compiler\n        env[\"RUSTC_BOOTSTRAP\"] = \"1\"\n        env[\"LIBZ_SYS_STATIC\"] = \"1\"\n        cmd = [\n            \"cargo\",\n            operation,\n            \"--workspace\",\n            \"-j%s\" % self.num_jobs,\n        ] + args\n        self._check_cmd(cmd, cwd=self.workspace_dir(), env=env)\n\n    def build_source_dir(self) -> str:\n        return os.path.join(self.build_dir, \"source\")\n\n    def workspace_dir(self) -> str:\n        return os.path.join(self.build_source_dir(), self.ws_dir or \"\")\n\n    def manifest_dir(self, manifest: str) -> str:\n        return os.path.join(self.build_source_dir(), manifest)\n\n    def recreate_dir(self, src: str, dst: str) -> None:\n        if os.path.isdir(dst):\n            if os.path.islink(dst):\n                os.remove(dst)\n            else:\n                rmtree_more(dst)\n        simple_copytree(src, dst)\n\n    def recreate_linked_dir(self, src: str, dst: str) -> None:\n        if os.path.isdir(dst):\n            if os.path.islink(dst):\n                os.remove(dst)\n            elif os.path.isdir(dst):\n                shutil.rmtree(dst)\n        os.symlink(src, dst)\n\n    def cargo_config_file(self) -> str:\n        build_source_dir = self.build_dir\n        if self.cargo_config_file_subdir:\n            return os.path.join(build_source_dir, self.cargo_config_file_subdir)\n        else:\n            return os.path.join(build_source_dir, \".cargo\", \"config.toml\")\n\n    def _create_cargo_config(self) -> dict[str, dict[str, str]]:\n        cargo_config_file = self.cargo_config_file()\n        cargo_config_dir = os.path.dirname(cargo_config_file)\n        if not os.path.isdir(cargo_config_dir):\n            os.mkdir(cargo_config_dir)\n\n        dep_to_git = self._resolve_dep_to_git()\n\n        if os.path.isfile(cargo_config_file):\n            with open(cargo_config_file, \"r\") as f:\n                print(f\"Reading {cargo_config_file}\", file=sys.stderr)\n                cargo_content = f.read()\n        else:\n            cargo_content = \"\"\n\n        new_content = cargo_content\n        if \"# Generated by getdeps.py\" not in cargo_content:\n            new_content += \"\"\"\\\n# Generated by getdeps.py\n[build]\ntarget-dir = '''{}'''\n\n[profile.dev]\ndebug = false\nincremental = false\n\n[profile.release]\nopt-level = \"{}\"\n\"\"\".format(\n                self.build_dir.replace(\"\\\\\", \"\\\\\\\\\"),\n                \"z\" if self.build_opts.build_type == \"MinSizeRel\" else \"s\",\n            )\n\n        # Point to vendored sources from getdeps manifests\n        for _dep, git_conf in dep_to_git.items():\n            if \"cargo_vendored_sources\" in git_conf:\n                vendored_dir = git_conf[\"cargo_vendored_sources\"].replace(\"\\\\\", \"\\\\\\\\\")\n                override = (\n                    f'[source.\"{git_conf[\"repo_url\"]}\"]\\ndirectory = \"{vendored_dir}\"\\n'\n                )\n                if override not in cargo_content:\n                    new_content += override\n\n            if self.build_opts.fbsource_dir:\n                # Point to vendored crates.io if possible\n                try:\n                    from .facebook.rust import vendored_crates\n\n                    new_content = vendored_crates(\n                        self.build_opts.fbsource_dir, new_content\n                    )\n                except ImportError:\n                    # This FB internal module isn't shippped to github,\n                    # so just rely on cargo downloading crates on it's own\n                    pass\n\n        if new_content != cargo_content:\n            with open(cargo_config_file, \"w\") as f:\n                print(\n                    f\"Writing cargo config for {self.manifest.name} to {cargo_config_file}\",\n                    file=sys.stderr,\n                )\n                f.write(new_content)\n\n        return dep_to_git\n\n    def _prepare(self, reconfigure: bool) -> None:\n        build_source_dir = self.build_source_dir()\n        self.recreate_dir(self.src_dir, build_source_dir)\n\n        dep_to_git = self._create_cargo_config()\n\n        if self.ws_dir is not None:\n            self._patchup_workspace(dep_to_git)\n\n    def _build(self, reconfigure: bool) -> None:\n        # _prepare has been run already. Actually do the build\n        build_source_dir = self.build_source_dir()\n\n        build_args = [\n            \"--artifact-dir\",\n            os.path.join(self.inst_dir, \"bin\"),\n            \"-Zunstable-options\",\n        ]\n\n        if self.build_opts.build_type != \"Debug\":\n            build_args.append(\"--release\")\n\n        if self.manifests_to_build is None:\n            self.run_cargo(\n                self.install_dirs,\n                \"build\",\n                build_args,\n            )\n        else:\n            # pyre-fixme[16]: Optional type has no attribute `__iter__`.\n            for manifest in self.manifests_to_build:\n                self.run_cargo(\n                    self.install_dirs,\n                    \"build\",\n                    build_args\n                    + [\n                        \"--manifest-path\",\n                        self.manifest_dir(manifest),\n                    ],\n                )\n\n        self.recreate_linked_dir(\n            build_source_dir, os.path.join(self.inst_dir, \"source\")\n        )\n\n    def run_tests(\n        self,\n        schedule_type: str,\n        owner: str | None,\n        test_filter: str | None,\n        test_exclude: str | None,\n        retry: int,\n        no_testpilot: bool,\n        timeout: int | None = None,\n    ) -> None:\n        build_args: list[str] = []\n        if self.build_opts.build_type != \"Debug\":\n            build_args.append(\"--release\")\n\n        if test_filter:\n            filter_args = [\"--\", test_filter]\n        else:\n            filter_args = []\n\n        if self.manifests_to_build is None:\n            self.run_cargo(self.install_dirs, \"test\", build_args + filter_args)\n            if self.build_doc and not filter_args:\n                self.run_cargo(self.install_dirs, \"doc\", [\"--no-deps\"])\n        else:\n            # pyre-fixme[16]: Optional type has no attribute `__iter__`.\n            for manifest in self.manifests_to_build:\n                margs = [\"--manifest-path\", self.manifest_dir(manifest)]\n                self.run_cargo(\n                    self.install_dirs, \"test\", build_args + filter_args + margs\n                )\n                if self.build_doc and not filter_args:\n                    self.run_cargo(self.install_dirs, \"doc\", [\"--no-deps\"] + margs)\n\n    def _patchup_workspace(self, dep_to_git: dict[str, dict[str, str]]) -> None:\n        \"\"\"\n        This method makes some assumptions about the state of the project and\n        its cargo dependendies:\n        1. Crates from cargo dependencies can be extracted from Cargo.toml files\n           using _extract_crates function. It is using a heuristic so check its\n           code to understand how it is done.\n        2. The extracted cargo dependencies crates can be found in the\n           dependency's install dir using _resolve_crate_to_path function\n           which again is using a heuristic.\n\n        Notice that many things might go wrong here. E.g. if someone depends\n        on another getdeps crate by writing in their Cargo.toml file:\n\n            my-rename-of-crate = { package = \"crate\", git = \"...\" }\n\n        they can count themselves lucky because the code will raise an\n        Exception. There might be more cases where the code will silently pass\n        producing bad results.\n        \"\"\"\n        workspace_dir = self.workspace_dir()\n        git_url_to_crates_and_paths = self._resolve_config(dep_to_git)\n        if git_url_to_crates_and_paths:\n            patch_cargo = os.path.join(workspace_dir, \"Cargo.toml\")\n            if os.path.isfile(patch_cargo):\n                with open(patch_cargo, \"r\") as f:\n                    manifest_content = f.read()\n            else:\n                manifest_content = \"\"\n\n            new_content = manifest_content\n            if \"[package]\" not in manifest_content:\n                # A fake manifest has to be crated to change the virtual\n                # manifest into a non-virtual. The virtual manifests are limited\n                # in many ways and the inability to define patches on them is\n                # one. Check https://github.com/rust-lang/cargo/issues/4934 to\n                # see if it is resolved.\n                null_file = \"/dev/null\"\n                if self.build_opts.is_windows():\n                    null_file = \"nul\"\n                new_content += f\"\"\"\n[package]\nname = \"fake_manifest_of_{self.manifest.name}\"\nversion = \"0.0.0\"\n\n[lib]\npath = \"{null_file}\"\n\"\"\"\n            config: list[str] = []\n            for git_url, crates_to_patch_path in git_url_to_crates_and_paths.items():\n                crates_patches = [\n                    '{} = {{ path = \"{}\" }}'.format(\n                        crate,\n                        crates_to_patch_path[crate].replace(\"\\\\\", \"\\\\\\\\\"),\n                    )\n                    for crate in sorted(crates_to_patch_path.keys())\n                ]\n                patch_key = f'[patch.\"{git_url}\"]'\n                if patch_key not in manifest_content:\n                    config.append(f\"\\n{patch_key}\\n\" + \"\\n\".join(crates_patches))\n            new_content += \"\\n\".join(config)\n            if new_content != manifest_content:\n                with open(patch_cargo, \"w\") as f:\n                    print(\n                        f\"writing patch to {patch_cargo}\",\n                        file=sys.stderr,\n                    )\n                    f.write(new_content)\n\n    def _resolve_config(\n        self, dep_to_git: dict[str, dict[str, str]]\n    ) -> dict[str, dict[str, str]]:\n        \"\"\"\n        Returns a configuration to be put inside root Cargo.toml file which\n        patches the dependencies git code with local getdeps versions.\n        See https://doc.rust-lang.org/cargo/reference/manifest.html#the-patch-section\n        \"\"\"\n        dep_to_crates = self._resolve_dep_to_crates(self.build_source_dir(), dep_to_git)\n\n        git_url_to_crates_and_paths: dict[str, dict[str, str]] = {}\n        for dep_name in sorted(dep_to_git.keys()):\n            git_conf = dep_to_git[dep_name]\n            req_crates = sorted(dep_to_crates.get(dep_name, []))\n            if not req_crates:\n                continue  # nothing to patch, move along\n\n            git_url = git_conf.get(\"repo_url\", None)\n            crate_source_map = git_conf[\"crate_source_map\"]\n            if git_url and crate_source_map:\n                crates_to_patch_path = git_url_to_crates_and_paths.get(git_url, {})\n                for c in req_crates:\n                    if c in crate_source_map and c not in crates_to_patch_path:\n                        # pyre-fixme[6]: For 1st argument expected `Union[slice[Any,\n                        #  Any, Any], SupportsIndex]` but got `str`.\n                        crates_to_patch_path[c] = crate_source_map[c]\n                        print(\n                            f\"{self.manifest.name}: Patching crate {c} via virtual manifest in {self.workspace_dir()}\",\n                            file=sys.stderr,\n                        )\n                if crates_to_patch_path:\n                    git_url_to_crates_and_paths[git_url] = crates_to_patch_path\n\n        return git_url_to_crates_and_paths\n\n    def _resolve_dep_to_git(self) -> dict[str, dict[str, str]]:\n        \"\"\"\n        For each direct dependency of the currently build manifest check if it\n        is also cargo-builded and if yes then extract it's git configs and\n        install dir\n        \"\"\"\n        dependencies = self.manifest.get_dependencies(self.ctx)\n        if not dependencies:\n            return {}\n\n        dep_to_git: dict[str, dict[str, str]] = {}\n        for dep in dependencies:\n            dep_manifest = self.loader.load_manifest(dep)\n            dep_builder = dep_manifest.get(\"build\", \"builder\", ctx=self.ctx)\n\n            dep_cargo_conf = dep_manifest.get_section_as_dict(\"cargo\", self.ctx)\n            dep_crate_map = dep_manifest.get_section_as_dict(\"crate.pathmap\", self.ctx)\n\n            if (\n                not (dep_crate_map or dep_cargo_conf)\n                and dep_builder not in [\"cargo\"]\n                or dep == \"rust\"\n            ):\n                # This dependency has no cargo rust content so ignore it.\n                # The \"rust\" dependency is an exception since it contains the\n                # toolchain.\n                continue\n\n            git_conf = dep_manifest.get_section_as_dict(\"git\", self.ctx)\n            if dep != \"rust\" and \"repo_url\" not in git_conf:\n                raise Exception(\n                    f\"{dep}: A cargo dependency requires git.repo_url to be defined.\"\n                )\n\n            if dep_builder == \"cargo\":\n                dep_source_dir = self.loader.get_project_install_dir(dep_manifest)\n                dep_source_dir = os.path.join(dep_source_dir, \"source\")\n            else:\n                fetcher = self.loader.create_fetcher(dep_manifest)\n                dep_source_dir = fetcher.get_src_dir()\n\n            crate_source_map: dict[str, str] = {}\n            if dep_crate_map:\n                for crate, subpath in dep_crate_map.items():\n                    if crate not in crate_source_map:\n                        if self.build_opts.is_windows():\n                            # pyre-fixme[16]: Optional type has no attribute `replace`.\n                            subpath = subpath.replace(\"/\", \"\\\\\")\n                        crate_path = os.path.join(dep_source_dir, subpath)\n                        print(\n                            f\"{self.manifest.name}: Mapped crate {crate} to dep {dep} dir {crate_path}\",\n                            file=sys.stderr,\n                        )\n                        crate_source_map[crate] = crate_path\n            elif dep_cargo_conf:\n                # We don't know what crates are defined buy the dep, look for them\n                search_pattern = re.compile('\\\\[package\\\\]\\nname = \"(.*)\"')\n                for crate_root, _, files in os.walk(dep_source_dir):\n                    if \"Cargo.toml\" in files:\n                        with open(os.path.join(crate_root, \"Cargo.toml\"), \"r\") as f:\n                            content = f.read()\n                            match = search_pattern.search(content)\n                            if match:\n                                crate = match.group(1)\n                                if crate:\n                                    print(\n                                        f\"{self.manifest.name}: Discovered crate {crate} in dep {dep} dir {crate_root}\",\n                                        file=sys.stderr,\n                                    )\n                                    crate_source_map[crate] = crate_root\n\n            # pyre-fixme[6]: For 2nd argument expected `Optional[str]` but got\n            #  `Dict[str, str]`.\n            git_conf[\"crate_source_map\"] = crate_source_map\n\n            if not dep_crate_map and dep_cargo_conf:\n                dep_cargo_dir = self.loader.get_project_build_dir(dep_manifest)\n                dep_cargo_dir = os.path.join(dep_cargo_dir, \"source\")\n                dep_ws_dir = dep_cargo_conf.get(\"workspace_dir\", None)\n                if dep_ws_dir:\n                    dep_cargo_dir = os.path.join(dep_cargo_dir, dep_ws_dir)\n                git_conf[\"cargo_vendored_sources\"] = dep_cargo_dir\n\n            # pyre-fixme[6]: For 2nd argument expected `Dict[str, str]` but got\n            #  `Dict[str, Optional[str]]`.\n            dep_to_git[dep] = git_conf\n        return dep_to_git\n\n    def _resolve_dep_to_crates(\n        self,\n        build_source_dir: str,\n        dep_to_git: dict[str, dict[str, str]],\n    ) -> dict[str, set[str]]:\n        \"\"\"\n        This function traverse the build_source_dir in search of Cargo.toml\n        files, extracts the crate names from them using _extract_crates\n        function and returns a merged result containing crate names per\n        dependency name from all Cargo.toml files in the project.\n        \"\"\"\n        if not dep_to_git:\n            return {}  # no deps, so don't waste time traversing files\n\n        dep_to_crates: dict[str, set[str]] = {}\n\n        # First populate explicit crate paths from dependencies\n        for name, git_conf in dep_to_git.items():\n            # pyre-fixme[16]: `str` has no attribute `keys`.\n            crates = git_conf[\"crate_source_map\"].keys()\n            if crates:\n                dep_to_crates.setdefault(name, set()).update(crates)\n\n        # Now find from Cargo.tomls\n        for root, _, files in os.walk(build_source_dir):\n            for f in files:\n                if f == \"Cargo.toml\":\n                    more_dep_to_crates = CargoBuilder._extract_crates_used(\n                        os.path.join(root, f), dep_to_git\n                    )\n                    for dep_name, crates in more_dep_to_crates.items():\n                        existing_crates = dep_to_crates.get(dep_name, set())\n                        for c in crates:\n                            if c not in existing_crates:\n                                print(\n                                    f\"Patch {self.manifest.name} uses {dep_name} crate {crates}\",\n                                    file=sys.stderr,\n                                )\n                                existing_crates.add(c)\n                        # pyre-fixme[61]: `name` is undefined, or not always defined.\n                        dep_to_crates.setdefault(name, set()).update(existing_crates)\n        return dep_to_crates\n\n    @staticmethod\n    def _extract_crates_used(\n        cargo_toml_file: str,\n        dep_to_git: dict[str, dict[str, str]],\n    ) -> dict[str, set[str]]:\n        \"\"\"\n        This functions reads content of provided cargo toml file and extracts\n        crate names per each dependency. The extraction is done by a heuristic\n        so it might be incorrect.\n        \"\"\"\n        deps_to_crates: dict[str, set[str]] = {}\n        with open(cargo_toml_file, \"r\") as f:\n            for line in f.readlines():\n                if line.startswith(\"#\") or \"git = \" not in line:\n                    continue  # filter out commented lines and ones without git deps\n                for dep_name, conf in dep_to_git.items():\n                    # Only redirect deps that point to git URLS\n                    if 'git = \"{}\"'.format(conf[\"repo_url\"]) in line:\n                        pkg_template = ' package = \"'\n                        if pkg_template in line:\n                            crate_name, _, _ = line.partition(pkg_template)[\n                                2\n                            ].partition('\"')\n                        else:\n                            crate_name, _, _ = line.partition(\"=\")\n                        deps_to_crates.setdefault(dep_name, set()).add(\n                            crate_name.strip()\n                        )\n        return deps_to_crates\n\n    def _resolve_crate_to_path(\n        self,\n        crate: str,\n        crate_source_map: dict[str, str],\n    ) -> str:\n        \"\"\"\n        Tries to find <crate> in source_dir by searching a [package]\n        keyword followed by name = \"<crate>\".\n        \"\"\"\n        search_pattern = '[package]\\nname = \"{}\"'.format(crate)\n\n        for _crate, crate_source_dir in crate_source_map.items():\n            for crate_root, _, files in os.walk(crate_source_dir):\n                if \"Cargo.toml\" in files:\n                    with open(os.path.join(crate_root, \"Cargo.toml\"), \"r\") as f:\n                        content = f.read()\n                        if search_pattern in content:\n                            return crate_root\n\n        raise Exception(\n            f\"{self.manifest.name}: Failed to find dep crate {crate} in paths {crate_source_map}\"\n        )\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/copytree.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport os\nimport shutil\nimport stat\nimport subprocess\nfrom collections.abc import Callable\n\nfrom .platform import is_windows\nfrom .runcmd import run_cmd\n\n\nPREFETCHED_DIRS: set[str] = set()\n\n\ndef containing_repo_type(path: str) -> tuple[str | None, str | None]:\n    while True:\n        if os.path.exists(os.path.join(path, \".git\")):\n            return (\"git\", path)\n        if os.path.exists(os.path.join(path, \".hg\")):\n            return (\"hg\", path)\n\n        parent = os.path.dirname(path)\n        if parent == path:\n            return None, None\n        path = parent\n\n\ndef find_eden_root(dirpath: str) -> str | None:\n    \"\"\"If the specified directory is inside an EdenFS checkout, returns\n    the canonical absolute path to the root of that checkout.\n\n    Returns None if the specified directory is not in an EdenFS checkout.\n    \"\"\"\n    if is_windows():\n        repo_type, repo_root = containing_repo_type(dirpath)\n        if repo_root is not None:\n            if os.path.exists(os.path.join(repo_root, \".eden\", \"config\")):\n                return repo_root\n        return None\n\n    try:\n        return os.readlink(os.path.join(dirpath, \".eden\", \"root\"))\n    except OSError:\n        return None\n\n\ndef prefetch_dir_if_eden(dirpath: str) -> None:\n    \"\"\"After an amend/rebase, Eden may need to fetch a large number\n    of trees from the servers.  The simplistic single threaded walk\n    performed by copytree makes this more expensive than is desirable\n    so we help accelerate things by performing a prefetch on the\n    source directory\"\"\"\n    global PREFETCHED_DIRS\n    if dirpath in PREFETCHED_DIRS:\n        return\n    root = find_eden_root(dirpath)\n    if root is None:\n        return\n    glob = f\"{os.path.relpath(dirpath, root).replace(os.sep, '/')}/**\"\n    print(f\"Prefetching {glob}\")\n    subprocess.call([\"edenfsctl\", \"prefetch\", \"--repo\", root, glob, \"--background\"])\n    PREFETCHED_DIRS.add(dirpath)\n\n\ndef simple_copytree(src_dir: str, dest_dir: str, symlinks: bool = False) -> str:\n    \"\"\"A simple version of shutil.copytree() that can delegate to native tools if faster\"\"\"\n    if is_windows():\n        os.makedirs(dest_dir, exist_ok=True)\n        cmd = [\n            \"robocopy.exe\",\n            src_dir,\n            dest_dir,\n            # copy directories, including empty ones\n            \"/E\",\n            # Ignore Extra files in destination\n            \"/XX\",\n            # enable parallel copy\n            \"/MT\",\n            # be quiet\n            \"/NFL\",\n            \"/NDL\",\n            \"/NJH\",\n            \"/NJS\",\n            \"/NP\",\n        ]\n        if symlinks:\n            cmd.append(\"/SL\")\n        # robocopy exits with code 1 if it copied ok, hence allow_fail\n        # https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility\n        exit_code = run_cmd(cmd, allow_fail=True)\n        if exit_code > 1:\n            raise subprocess.CalledProcessError(exit_code, cmd)\n        return dest_dir\n    else:\n        return shutil.copytree(src_dir, dest_dir, symlinks=symlinks)\n\n\ndef _remove_readonly_and_try_again(\n    func: Callable[..., object],\n    path: str,\n    # pyre-fixme[24]: Generic type `type` expects 1 type parameter, use\n    #  `typing.Type[<base type>]` to avoid runtime subscripting errors.\n    exc_info: tuple[type, BaseException, object],\n) -> None:\n    \"\"\"\n    Error handler for shutil.rmtree.\n    If the error is due to an access error (read only file)\n    it attempts to add write permission and then retries the operation.\n    Any other failure propagates.\n    \"\"\"\n    # exc_info is a tuple (exc_type, exc_value, traceback)\n    exc_type = exc_info[0]\n    if exc_type is PermissionError:\n        os.chmod(path, stat.S_IWRITE)\n        # Retry the original function (os.remove or os.rmdir)\n        try:\n            func(path)\n        except Exception:\n            # If it still fails, the original exception from func() will propagate\n            raise\n    else:\n        # If the error is not a PermissionError, re-raise the original exception\n        raise exc_info[1]\n\n\ndef rmtree_more(path: str) -> None:\n    \"\"\"Wrapper around shutil.rmtree() that makes it remove readonly files as well.\n    Useful when git on windows decides to make some files readonly on checkout\"\"\"\n    shutil.rmtree(path, onerror=_remove_readonly_and_try_again)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/dyndeps.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport errno\nimport glob\nimport os\nimport re\nimport shlex\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport typing\nfrom collections.abc import Generator\nfrom struct import unpack\n\nif typing.TYPE_CHECKING:\n    from .buildopts import BuildOptions\n    from .envfuncs import Env\n\nOBJECT_SUBDIRS: tuple[str, ...] = (\"bin\", \"lib\", \"lib64\")\n\n\ndef copyfile(src: str, dest: str) -> None:\n    shutil.copyfile(src, dest)\n    shutil.copymode(src, dest)\n\n\nclass DepBase:\n    def __init__(\n        self,\n        buildopts: BuildOptions,\n        env: Env,\n        install_dirs: list[str],\n        strip: bool,\n    ) -> None:\n        self.buildopts: BuildOptions = buildopts\n        self.env: Env = env\n        self.install_dirs: list[str] = install_dirs\n        self.strip: bool = strip\n\n        # Deduplicates dependency processing. Keyed on the library\n        # destination path.\n        self.processed_deps: set[str] = set()\n\n        self.munged_lib_dir: str = \"\"\n\n    def list_dynamic_deps(self, objfile: str) -> list[str]:\n        raise RuntimeError(\"list_dynamic_deps not implemented\")\n\n    def interesting_dep(self, d: str) -> bool:\n        return True\n\n    # final_install_prefix must be the equivalent path to `destdir` on the\n    # installed system.  For example, if destdir is `/tmp/RANDOM/usr/local' which\n    # is intended to map to `/usr/local` in the install image, then\n    # final_install_prefix='/usr/local'.\n    # If left unspecified, destdir will be used.\n    def process_deps(\n        self, destdir: str, final_install_prefix: str | None = None\n    ) -> None:\n        if self.buildopts.is_windows():\n            lib_dir = \"bin\"\n        else:\n            lib_dir = \"lib\"\n        self.munged_lib_dir = os.path.join(destdir, lib_dir)\n\n        final_lib_dir: str = os.path.join(final_install_prefix or destdir, lib_dir)\n\n        if not os.path.isdir(self.munged_lib_dir):\n            os.makedirs(self.munged_lib_dir)\n\n        # Look only at the things that got installed in the leaf package,\n        # which will be the last entry in the install dirs list\n        inst_dir: str = self.install_dirs[-1]\n        print(\"Process deps under %s\" % inst_dir, file=sys.stderr)\n\n        for dir in OBJECT_SUBDIRS:\n            src_dir: str = os.path.join(inst_dir, dir)\n            if not os.path.isdir(src_dir):\n                continue\n            dest_dir: str = os.path.join(destdir, dir)\n            if not os.path.exists(dest_dir):\n                os.makedirs(dest_dir)\n\n            for objfile in self.list_objs_in_dir(src_dir):\n                print(\"Consider %s/%s\" % (dir, objfile))\n                dest_obj: str = os.path.join(dest_dir, objfile)\n                copyfile(os.path.join(src_dir, objfile), dest_obj)\n                self.munge_in_place(dest_obj, final_lib_dir)\n\n    def find_all_dependencies(self, build_dir: str) -> list[str]:\n        all_deps: set[str] = set()\n        for objfile in self.list_objs_in_dir(\n            build_dir, recurse=True, output_prefix=build_dir\n        ):\n            for d in self.list_dynamic_deps(objfile):\n                all_deps.add(d)\n\n        interesting_deps: set[str] = {d for d in all_deps if self.interesting_dep(d)}\n        dep_paths: list[str] = []\n        for dep in interesting_deps:\n            dep_path: str | None = self.resolve_loader_path(dep)\n            if dep_path:\n                dep_paths.append(dep_path)\n\n        return dep_paths\n\n    def munge_in_place(self, objfile: str, final_lib_dir: str) -> None:\n        print(\"Munging %s\" % objfile)\n        for d in self.list_dynamic_deps(objfile):\n            if not self.interesting_dep(d):\n                continue\n\n            # Resolve this dep: does it exist in any of our installation\n            # directories?  If so, then it is a candidate for processing\n            dep: str | None = self.resolve_loader_path(d)\n            if dep:\n                dest_dep: str = os.path.join(self.munged_lib_dir, os.path.basename(dep))\n                print(\"dep: %s -> %s\" % (d, dest_dep))\n                if dest_dep in self.processed_deps:\n                    # A previous dependency with the same name has already\n                    # been installed at dest_dep, so there is no need to copy\n                    # or munge the dependency again.\n                    # TODO: audit that both source paths have the same inode number\n                    pass\n                else:\n                    self.processed_deps.add(dest_dep)\n                    copyfile(dep, dest_dep)\n                    self.munge_in_place(dest_dep, final_lib_dir)\n\n                self.rewrite_dep(objfile, d, dep, dest_dep, final_lib_dir)\n\n        if self.strip:\n            self.strip_debug_info(objfile)\n\n    def rewrite_dep(\n        self,\n        objfile: str,\n        depname: str,\n        old_dep: str,\n        new_dep: str,\n        final_lib_dir: str,\n    ) -> None:\n        raise RuntimeError(\"rewrite_dep not implemented\")\n\n    def resolve_loader_path(self, dep: str) -> str | None:\n        if os.path.isabs(dep):\n            return dep\n        d: str = os.path.basename(dep)\n        for inst_dir in self.install_dirs:\n            for libdir in OBJECT_SUBDIRS:\n                candidate: str = os.path.join(inst_dir, libdir, d)\n                if os.path.exists(candidate):\n                    return candidate\n        return None\n\n    def list_objs_in_dir(\n        self, dir: str, recurse: bool = False, output_prefix: str = \"\"\n    ) -> Generator[str, None, None]:\n        for entry in os.listdir(dir):\n            entry_path: str = os.path.join(dir, entry)\n            st: os.stat_result = os.lstat(entry_path)\n            if stat.S_ISREG(st.st_mode):\n                if self.is_objfile(entry_path):\n                    relative_result: str = os.path.join(output_prefix, entry)\n                    yield os.path.normcase(relative_result)\n            elif recurse and stat.S_ISDIR(st.st_mode):\n                child_prefix: str = os.path.join(output_prefix, entry)\n                for result in self.list_objs_in_dir(\n                    entry_path, recurse=recurse, output_prefix=child_prefix\n                ):\n                    yield result\n\n    def is_objfile(self, objfile: str) -> bool:\n        return True\n\n    def strip_debug_info(self, objfile: str) -> None:\n        \"\"\"override this to define how to remove debug information\n        from an object file\"\"\"\n        pass\n\n    def check_call_verbose(self, args: list[str]) -> None:\n        print(\" \".join(map(shlex.quote, args)))\n        subprocess.check_call(args)\n\n\nclass WinDeps(DepBase):\n    def __init__(\n        self,\n        buildopts: BuildOptions,\n        env: Env,\n        install_dirs: list[str],\n        strip: bool,\n    ) -> None:\n        super(WinDeps, self).__init__(buildopts, env, install_dirs, strip)\n        self.dumpbin: str = self.find_dumpbin()\n\n    def find_dumpbin(self) -> str:\n        # Looking for dumpbin in the following hardcoded paths.\n        # The registry option to find the install dir doesn't work anymore.\n        globs: list[str] = [\n            (\n                \"C:/Program Files/\"\n                \"Microsoft Visual Studio/\"\n                \"*/*/VC/Tools/\"\n                \"MSVC/*/bin/Hostx64/x64/dumpbin.exe\"\n            ),\n            (\n                \"C:/Program Files (x86)/\"\n                \"Microsoft Visual Studio/\"\n                \"*/*/VC/Tools/\"\n                \"MSVC/*/bin/Hostx64/x64/dumpbin.exe\"\n            ),\n            (\n                \"C:/Program Files (x86)/\"\n                \"Common Files/\"\n                \"Microsoft/Visual C++ for Python/*/\"\n                \"VC/bin/dumpbin.exe\"\n            ),\n            (\"c:/Program Files (x86)/Microsoft Visual Studio */VC/bin/dumpbin.exe\"),\n            (\n                \"C:/Program Files/Microsoft Visual Studio/*/Professional/VC/Tools/MSVC/*/bin/HostX64/x64/dumpbin.exe\"\n            ),\n        ]\n        for pattern in globs:\n            for exe in glob.glob(pattern):\n                return exe\n\n        raise RuntimeError(\"could not find dumpbin.exe\")\n\n    # pyre-fixme[14]: `list_dynamic_deps` overrides method defined in `DepBase`\n    #  inconsistently.\n    def list_dynamic_deps(self, exe: str) -> list[str]:\n        deps: list[str] = []\n        print(\"Resolve deps for %s\" % exe)\n        output: str = subprocess.check_output(\n            [self.dumpbin, \"/nologo\", \"/dependents\", exe]\n        ).decode(\"utf-8\")\n\n        lines: list[str] = output.split(\"\\n\")\n        for line in lines:\n            m: re.Match[str] | None = re.match(\"\\\\s+(\\\\S+.dll)\", line, re.IGNORECASE)\n            if m:\n                deps.append(m.group(1).lower())\n\n        return deps\n\n    def rewrite_dep(\n        self,\n        objfile: str,\n        depname: str,\n        old_dep: str,\n        new_dep: str,\n        final_lib_dir: str,\n    ) -> None:\n        # We can't rewrite on windows, but we will\n        # place the deps alongside the exe so that\n        # they end up in the search path\n        pass\n\n    # These are the Windows system dll, which we don't want to copy while\n    # packaging.\n    SYSTEM_DLLS: set[str] = set(  # noqa: C405\n        [\n            \"advapi32.dll\",\n            \"dbghelp.dll\",\n            \"kernel32.dll\",\n            \"msvcp140.dll\",\n            \"vcruntime140.dll\",\n            \"ws2_32.dll\",\n            \"ntdll.dll\",\n            \"shlwapi.dll\",\n        ]\n    )\n\n    def interesting_dep(self, d: str) -> bool:\n        if \"api-ms-win-crt\" in d:\n            return False\n        if d in self.SYSTEM_DLLS:\n            return False\n        return True\n\n    def is_objfile(self, objfile: str) -> bool:\n        if not os.path.isfile(objfile):\n            return False\n        if objfile.lower().endswith(\".exe\"):\n            return True\n        return False\n\n    def emit_dev_run_script(self, script_path: str, dep_dirs: list[str]) -> None:\n        \"\"\"Emit a script that can be used to run build artifacts directly from the\n        build directory, without installing them.\n\n        The dep_dirs parameter should be a list of paths that need to be added to $PATH.\n        This can be computed by calling compute_dependency_paths() or\n        compute_dependency_paths_fast().\n\n        This is only necessary on Windows, which does not have RPATH, and instead\n        requires the $PATH environment variable be updated in order to find the proper\n        library dependencies.\n        \"\"\"\n        contents: str = self._get_dev_run_script_contents(dep_dirs)\n        with open(script_path, \"w\") as f:\n            f.write(contents)\n\n    def compute_dependency_paths(self, build_dir: str) -> list[str]:\n        \"\"\"Return a list of all directories that need to be added to $PATH to ensure\n        that library dependencies can be found correctly.  This is computed by scanning\n        binaries to determine exactly the right list of dependencies.\n\n        The compute_dependency_paths_fast() is a alternative function that runs faster\n        but may return additional extraneous paths.\n        \"\"\"\n        dep_dirs: set[str] = set()\n        # Find paths by scanning the binaries.\n        for dep in self.find_all_dependencies(build_dir):\n            dep_dirs.add(os.path.dirname(dep))\n\n        dep_dirs.update(self.read_custom_dep_dirs(build_dir))\n        return sorted(dep_dirs)\n\n    def compute_dependency_paths_fast(self, build_dir: str) -> list[str]:\n        \"\"\"Similar to compute_dependency_paths(), but rather than actually scanning\n        binaries, just add all library paths from the specified installation\n        directories.  This is much faster than scanning the binaries, but may result in\n        more paths being returned than actually necessary.\n        \"\"\"\n        dep_dirs: set[str] = set()\n        for inst_dir in self.install_dirs:\n            for subdir in OBJECT_SUBDIRS:\n                path: str = os.path.join(inst_dir, subdir)\n                if os.path.exists(path):\n                    dep_dirs.add(path)\n\n        dep_dirs.update(self.read_custom_dep_dirs(build_dir))\n        return sorted(dep_dirs)\n\n    def read_custom_dep_dirs(self, build_dir: str) -> set[str]:\n        # The build system may also have included libraries from other locations that\n        # we might not be able to find normally in find_all_dependencies().\n        # To handle this situation we support reading additional library paths\n        # from a LIBRARY_DEP_DIRS.txt file that may have been generated in the build\n        # output directory.\n        dep_dirs: set[str] = set()\n        try:\n            explicit_dep_dirs_path: str = os.path.join(\n                build_dir, \"LIBRARY_DEP_DIRS.txt\"\n            )\n            with open(explicit_dep_dirs_path, \"r\") as f:\n                for line in f.read().splitlines():\n                    dep_dirs.add(line)\n        except OSError as ex:\n            if ex.errno != errno.ENOENT:\n                raise\n\n        return dep_dirs\n\n    def _get_dev_run_script_contents(self, path_dirs: list[str]) -> str:\n        path_entries: list[str] = [\"$env:PATH\"] + path_dirs\n        path_str: str = \";\".join(path_entries)\n        return \"\"\"\\\n$orig_env = $env:PATH\n$env:PATH = \"{path_str}\"\n\ntry {{\n    $cmd_args = $args[1..$args.length]\n    & $args[0] @cmd_args\n}} finally {{\n    $env:PATH = $orig_env\n}}\n\"\"\".format(\n            path_str=path_str\n        )\n\n\nclass ElfDeps(DepBase):\n    def __init__(\n        self,\n        buildopts: BuildOptions,\n        env: Env,\n        install_dirs: list[str],\n        strip: bool,\n    ) -> None:\n        super(ElfDeps, self).__init__(buildopts, env, install_dirs, strip)\n\n        # We need patchelf to rewrite deps, so ensure that it is built...\n        args: list[str] = [sys.executable, sys.argv[0]]\n        if buildopts.allow_system_packages:\n            args.append(\"--allow-system-packages\")\n        subprocess.check_call(args + [\"build\", \"patchelf\"])\n\n        # ... and that we know where it lives\n        patchelf_install: str = os.fsdecode(\n            subprocess.check_output(args + [\"show-inst-dir\", \"patchelf\"]).strip()\n        )\n        if not patchelf_install:\n            # its a system package, so we assume it is in the path\n            patchelf_install = \"patchelf\"\n        else:\n            patchelf_install = os.path.join(patchelf_install, \"bin\", \"patchelf\")\n        self.patchelf: str = patchelf_install\n\n    def list_dynamic_deps(self, objfile: str) -> list[str]:\n        out: str = (\n            subprocess.check_output(\n                [self.patchelf, \"--print-needed\", objfile], env=dict(self.env.items())\n            )\n            .decode(\"utf-8\")\n            .strip()\n        )\n        lines: list[str] = out.split(\"\\n\")\n        return lines\n\n    def rewrite_dep(\n        self,\n        objfile: str,\n        depname: str,\n        old_dep: str,\n        new_dep: str,\n        final_lib_dir: str,\n    ) -> None:\n        final_dep: str = os.path.join(\n            final_lib_dir,\n            os.path.relpath(new_dep, self.munged_lib_dir),\n        )\n        self.check_call_verbose(\n            [self.patchelf, \"--replace-needed\", depname, final_dep, objfile]\n        )\n\n    def is_objfile(self, objfile: str) -> bool:\n        if not os.path.isfile(objfile):\n            return False\n        with open(objfile, \"rb\") as f:\n            # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header\n            magic: bytes = f.read(4)\n            return magic == b\"\\x7fELF\"\n\n    def strip_debug_info(self, objfile: str) -> None:\n        self.check_call_verbose([\"strip\", objfile])\n\n\n# MACH-O magic number\nMACH_MAGIC: int = 0xFEEDFACF\n\n\nclass MachDeps(DepBase):\n    def interesting_dep(self, d: str) -> bool:\n        if d.startswith(\"/usr/lib/\") or d.startswith(\"/System/\"):\n            return False\n        return True\n\n    def is_objfile(self, objfile: str) -> bool:\n        if not os.path.isfile(objfile):\n            return False\n        with open(objfile, \"rb\") as f:\n            # mach stores the magic number in native endianness,\n            # so unpack as native here and compare\n            header: bytes = f.read(4)\n            if len(header) != 4:\n                return False\n            magic: int = unpack(\"I\", header)[0]\n            return magic == MACH_MAGIC\n\n    def list_dynamic_deps(self, objfile: str) -> list[str]:\n        if not self.interesting_dep(objfile):\n            return []\n        out: str = (\n            subprocess.check_output(\n                [\"otool\", \"-L\", objfile], env=dict(self.env.items())\n            )\n            .decode(\"utf-8\")\n            .strip()\n        )\n        lines: list[str] = out.split(\"\\n\")\n        deps: list[str] = []\n        for line in lines:\n            m: re.Match[str] | None = re.match(\"\\t(\\\\S+)\\\\s\", line)\n            if m:\n                if os.path.basename(m.group(1)) != os.path.basename(objfile):\n                    deps.append(os.path.normcase(m.group(1)))\n        return deps\n\n    def rewrite_dep(\n        self,\n        objfile: str,\n        depname: str,\n        old_dep: str,\n        new_dep: str,\n        final_lib_dir: str,\n    ) -> None:\n        if objfile.endswith(\".dylib\"):\n            # Erase the original location from the id of the shared\n            # object.  It doesn't appear to hurt to retain it, but\n            # it does look weird, so let's rewrite it to be sure.\n            self.check_call_verbose(\n                [\"install_name_tool\", \"-id\", os.path.basename(objfile), objfile]\n            )\n        final_dep: str = os.path.join(\n            final_lib_dir,\n            os.path.relpath(new_dep, self.munged_lib_dir),\n        )\n\n        self.check_call_verbose(\n            [\"install_name_tool\", \"-change\", depname, final_dep, objfile]\n        )\n\n\ndef create_dyn_dep_munger(\n    buildopts: BuildOptions,\n    env: Env,\n    install_dirs: list[str],\n    strip: bool = False,\n) -> DepBase | None:\n    if buildopts.is_linux():\n        return ElfDeps(buildopts, env, install_dirs, strip)\n    if buildopts.is_darwin():\n        return MachDeps(buildopts, env, install_dirs, strip)\n    if buildopts.is_windows():\n        return WinDeps(buildopts, env, install_dirs, strip)\n    if buildopts.is_freebsd():\n        return ElfDeps(buildopts, env, install_dirs, strip)\n    return None\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/envfuncs.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport os\nimport shlex\nimport sys\nfrom collections.abc import ItemsView, Iterator, KeysView, Mapping, ValuesView\n\n\nclass Env:\n    def __init__(self, src: Mapping[str, str] | None = None) -> None:\n        self._dict: dict[str, str] = {}\n        if src is None:\n            self.update(os.environ)\n        else:\n            self.update(src)\n\n    def update(self, src: Mapping[str, str]) -> None:\n        for k, v in src.items():\n            self.set(k, v)\n\n    def copy(self) -> Env:\n        return Env(self._dict)\n\n    def _key(self, key: str) -> str | None:\n        # The `str` cast may not appear to be needed, but without it we run\n        # into issues when passing the environment to subprocess.  The main\n        # issue is that in python2 `os.environ` (which is the initial source\n        # of data for the environment) uses byte based strings, but this\n        # project uses `unicode_literals`.  `subprocess` will raise an error\n        # if the environment that it is passed has a mixture of byte and\n        # unicode strings.\n        # It is simplest to force everything to be `str` for the sake of\n        # consistency.\n        key = str(key)\n        if sys.platform.startswith(\"win\"):\n            # Windows env var names are case insensitive but case preserving.\n            # An implementation of PAR files on windows gets confused if\n            # the env block contains keys with conflicting case, so make a\n            # pass over the contents to remove any.\n            # While this O(n) scan is technically expensive and gross, it\n            # is practically not a problem because the volume of calls is\n            # relatively low and the cost of manipulating the env is dwarfed\n            # by the cost of spawning a process on windows.  In addition,\n            # since the processes that we run are expensive anyway, this\n            # overhead is not the worst thing to worry about.\n            for k in list(self._dict.keys()):\n                if str(k).lower() == key.lower():\n                    return k\n        elif key in self._dict:\n            return key\n        return None\n\n    def get(self, key: str, defval: str | None = None) -> str | None:\n        resolved_key = self._key(key)\n        if resolved_key is None:\n            return defval\n        return self._dict[resolved_key]\n\n    def __getitem__(self, key: str) -> str:\n        val = self.get(key)\n        if val is None:\n            raise KeyError(key)\n        return val\n\n    def unset(self, key: str) -> None:\n        if key is None:\n            raise KeyError(\"attempting to unset env[None]\")\n\n        resolved_key = self._key(key)\n        if resolved_key:\n            del self._dict[resolved_key]\n\n    def __delitem__(self, key: str) -> None:\n        self.unset(key)\n\n    def __repr__(self) -> str:\n        return repr(self._dict)\n\n    def set(self, key: str, value: str) -> None:\n        if key is None:\n            raise KeyError(\"attempting to assign env[None] = %r\" % value)\n\n        if value is None:\n            raise ValueError(\"attempting to assign env[%s] = None\" % key)\n\n        # The `str` conversion is important to avoid triggering errors\n        # with subprocess if we pass in a unicode value; see commentary\n        # in the `_key` method.\n        key = str(key)\n        value = str(value)\n\n        # The `unset` call is necessary on windows where the keys are\n        # case insensitive.   Since this dict is case sensitive, simply\n        # assigning the value to the new key is not sufficient to remove\n        # the old value.  The `unset` call knows how to match keys and\n        # remove any potential duplicates.\n        self.unset(key)\n        self._dict[key] = value\n\n    def __setitem__(self, key: str, value: str) -> None:\n        self.set(key, value)\n\n    def __iter__(self) -> Iterator[str]:\n        return self._dict.__iter__()\n\n    def __len__(self) -> int:\n        return len(self._dict)\n\n    def keys(self) -> KeysView[str]:\n        return self._dict.keys()\n\n    def values(self) -> ValuesView[str]:\n        return self._dict.values()\n\n    def items(self) -> ItemsView[str, str]:\n        return self._dict.items()\n\n\ndef add_path_entry(\n    env: Env, name: str, item: str, append: bool = True, separator: str = os.pathsep\n) -> None:\n    \"\"\"Cause `item` to be added to the path style env var named\n    `name` held in the `env` dict.  `append` specifies whether\n    the item is added to the end (the default) or should be\n    prepended if `name` already exists.\"\"\"\n    val = env.get(name, \"\")\n    if val is not None and len(val) > 0:\n        val_list = val.split(separator)\n    else:\n        val_list = []\n    if append:\n        val_list.append(item)\n    else:\n        val_list.insert(0, item)\n    env.set(name, separator.join(val_list))\n\n\ndef add_flag(env: Env, name: str, flag: str, append: bool = True) -> None:\n    \"\"\"Cause `flag` to be added to the CXXFLAGS-style env var named\n    `name` held in the `env` dict.  `append` specifies whether the\n    flag is added to the end (the default) or should be prepended if\n    `name` already exists.\"\"\"\n    val = shlex.split(env.get(name, \"\") or \"\")\n    if append:\n        val.append(flag)\n    else:\n        val.insert(0, flag)\n    env.set(name, \" \".join(val))\n\n\n_path_search_cache: dict[object, str | None] = {}\n_not_found: object = object()\n\n\ndef tpx_path() -> str:\n    return \"xplat/testinfra/tpx/ctp.tpx\"\n\n\ndef path_search(\n    env: Mapping[str, str], exename: str, defval: str | None = None\n) -> str | None:\n    \"\"\"Search for exename in the PATH specified in env.\n    exename is eg: `ninja` and this function knows to append a .exe\n    to the end on windows.\n    Returns the path to the exe if found, or None if either no\n    PATH is set in env or no executable is found.\"\"\"\n\n    path = env.get(\"PATH\", None)\n    if path is None:\n        return defval\n\n    # The project hash computation code searches for C++ compilers (g++, clang, etc)\n    # repeatedly.  Cache the result so we don't end up searching for these over and over\n    # again.\n    cache_key = (path, exename)\n    result = _path_search_cache.get(cache_key, _not_found)\n    if result is _not_found:\n        result = _perform_path_search(path, exename)\n        _path_search_cache[cache_key] = result\n    # pyre-fixme[7]: Expected `Optional[str]` but got `Optional[object]`.\n    return result\n\n\ndef _perform_path_search(path: str, exename: str) -> str | None:\n    is_win = sys.platform.startswith(\"win\")\n    if is_win:\n        exename = \"%s.exe\" % exename\n\n    for bindir in path.split(os.pathsep):\n        full_name = os.path.join(bindir, exename)\n        if os.path.exists(full_name) and os.path.isfile(full_name):\n            if not is_win and not os.access(full_name, os.X_OK):\n                continue\n            return full_name\n\n    return None\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/errors.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\n\nclass TransientFailure(Exception):\n    \"\"\"Raising this error causes getdeps to return with an error code\n    that Sandcastle will consider to be a retryable transient\n    infrastructure error\"\"\"\n\n    pass\n\n\nclass ManifestNotFound(Exception):\n    def __init__(self, manifest_name: str) -> None:\n        super(Exception, self).__init__(\"Unable to find manifest '%s'\" % manifest_name)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/expr.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport re\nimport shlex\nfrom collections.abc import Callable\n\n\ndef parse_expr(expr_text: str, valid_variables: set[str]) -> ExprNode:\n    \"\"\"parses the simple criteria expression syntax used in\n    dependency specifications.\n    Returns an ExprNode instance that can be evaluated like this:\n\n    ```\n    expr = parse_expr(\"os=windows\")\n    ok = expr.eval({\n        \"os\": \"windows\"\n    })\n    ```\n\n    Whitespace is allowed between tokens.  The following terms\n    are recognized:\n\n    KEY = VALUE   # Evaluates to True if ctx[KEY] == VALUE\n    not(EXPR)     # Evaluates to True if EXPR evaluates to False\n                  # and vice versa\n    all(EXPR1, EXPR2, ...) # Evaluates True if all of the supplied\n                           # EXPR's also evaluate True\n    any(EXPR1, EXPR2, ...) # Evaluates True if any of the supplied\n                           # EXPR's also evaluate True, False if\n                           # none of them evaluated true.\n    \"\"\"\n\n    p = Parser(expr_text, valid_variables)\n    return p.parse()\n\n\nclass ExprNode:\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        return False\n\n\nclass TrueExpr(ExprNode):\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        return True\n\n    def __str__(self) -> str:\n        return \"true\"\n\n\nclass NotExpr(ExprNode):\n    def __init__(self, node: ExprNode) -> None:\n        self._node: ExprNode = node\n\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        return not self._node.eval(ctx)\n\n    def __str__(self) -> str:\n        return \"not(%s)\" % self._node\n\n\nclass AllExpr(ExprNode):\n    def __init__(self, nodes: list[ExprNode]) -> None:\n        self._nodes: list[ExprNode] = nodes\n\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        for node in self._nodes:\n            if not node.eval(ctx):\n                return False\n        return True\n\n    def __str__(self) -> str:\n        items: list[str] = []\n        for node in self._nodes:\n            items.append(str(node))\n        return \"all(%s)\" % \",\".join(items)\n\n\nclass AnyExpr(ExprNode):\n    def __init__(self, nodes: list[ExprNode]) -> None:\n        self._nodes: list[ExprNode] = nodes\n\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        for node in self._nodes:\n            if node.eval(ctx):\n                return True\n        return False\n\n    def __str__(self) -> str:\n        items: list[str] = []\n        for node in self._nodes:\n            items.append(str(node))\n        return \"any(%s)\" % \",\".join(items)\n\n\nclass EqualExpr(ExprNode):\n    def __init__(self, key: str, value: str) -> None:\n        self._key: str = key\n        self._value: str = value\n\n    def eval(self, ctx: dict[str, str | None]) -> bool:\n        return ctx.get(self._key) == self._value\n\n    def __str__(self) -> str:\n        return \"%s=%s\" % (self._key, self._value)\n\n\nclass Parser:\n    def __init__(self, text: str, valid_variables: set[str]) -> None:\n        self.text: str = text\n        self.lex: shlex.shlex = shlex.shlex(text)\n        self.valid_variables: set[str] = valid_variables\n\n    def parse(self) -> ExprNode:\n        expr = self.top()\n        garbage = self.lex.get_token()\n        if garbage != \"\":\n            raise Exception(\n                \"Unexpected token %s after EqualExpr in %s\" % (garbage, self.text)\n            )\n        return expr\n\n    def top(self) -> ExprNode:\n        name = self.ident()\n        op = self.lex.get_token()\n\n        if op == \"(\":\n            parsers: dict[str, Callable[[], ExprNode]] = {\n                \"not\": self.parse_not,\n                \"any\": self.parse_any,\n                \"all\": self.parse_all,\n            }\n            func = parsers.get(name)\n            if not func:\n                raise Exception(\"invalid term %s in %s\" % (name, self.text))\n            return func()\n\n        if op == \"=\":\n            if name not in self.valid_variables:\n                raise Exception(\"unknown variable %r in expression\" % (name,))\n            # remove shell quote from value so can test things with period in them, e.g \"18.04\"\n            token = self.lex.get_token()\n            if token is None:\n                raise Exception(\"unexpected end of expression in %s\" % self.text)\n            unquoted = \" \".join(shlex.split(token))\n            return EqualExpr(name, unquoted)\n\n        raise Exception(\n            \"Unexpected token sequence '%s %s' in %s\" % (name, op, self.text)\n        )\n\n    def ident(self) -> str:\n        ident = self.lex.get_token()\n        if ident is None or not re.match(\"[a-zA-Z]+\", ident):\n            raise Exception(\"expected identifier found %s\" % ident)\n        return ident\n\n    def parse_not(self) -> NotExpr:\n        node = self.top()\n        expr = NotExpr(node)\n        tok = self.lex.get_token()\n        if tok != \")\":\n            raise Exception(\"expected ')' found %s\" % tok)\n        return expr\n\n    def parse_any(self) -> AnyExpr:\n        nodes: list[ExprNode] = []\n        while True:\n            nodes.append(self.top())\n            tok = self.lex.get_token()\n            if tok == \")\":\n                break\n            if tok != \",\":\n                raise Exception(\"expected ',' or ')' but found %s\" % tok)\n        return AnyExpr(nodes)\n\n    def parse_all(self) -> AllExpr:\n        nodes: list[ExprNode] = []\n        while True:\n            nodes.append(self.top())\n            tok = self.lex.get_token()\n            if tok == \")\":\n                break\n            if tok != \",\":\n                raise Exception(\"expected ',' or ')' but found %s\" % tok)\n        return AllExpr(nodes)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/fetcher.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom __future__ import annotations\n\n# pyre-strict\n\nimport errno\nimport hashlib\nimport os\nimport random\nimport re\nimport shlex\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport tarfile\nimport time\nimport zipfile\nfrom abc import ABC, abstractmethod\nfrom collections.abc import Iterator\nfrom datetime import datetime\nfrom typing import NamedTuple, TYPE_CHECKING\nfrom urllib.parse import urlparse\nfrom urllib.request import Request, urlopen\n\nfrom .copytree import prefetch_dir_if_eden\nfrom .envfuncs import Env\nfrom .errors import TransientFailure\nfrom .platform import HostType, is_windows\nfrom .runcmd import run_cmd\n\nif TYPE_CHECKING:\n    from .buildopts import BuildOptions\n    from .manifest import ManifestContext, ManifestParser\n\n\ndef file_name_is_cmake_file(file_name: str) -> bool:\n    file_name = file_name.lower()\n    base = os.path.basename(file_name)\n    return (\n        base.endswith(\".cmake\")\n        or base.endswith(\".cmake.in\")\n        or base == \"cmakelists.txt\"\n    )\n\n\nclass ChangeStatus:\n    \"\"\"Indicates the nature of changes that happened while updating\n    the source directory.  There are two broad uses:\n    * When extracting archives for third party software we want to\n      know that we did something (eg: we either extracted code or\n      we didn't do anything)\n    * For 1st party code where we use shipit to transform the code,\n      we want to know if we changed anything so that we can perform\n      a build, but we generally want to be a little more nuanced\n      and be able to distinguish between just changing a source file\n      and whether we might need to reconfigure the build system.\n    \"\"\"\n\n    def __init__(self, all_changed: bool = False) -> None:\n        \"\"\"Construct a ChangeStatus object.  The default is to create\n        a status that indicates no changes, but passing all_changed=True\n        will create one that indicates that everything changed\"\"\"\n        if all_changed:\n            self.source_files: int = 1\n            self.make_files: int = 1\n        else:\n            self.source_files: int = 0\n            self.make_files: int = 0\n\n    def record_change(self, file_name: str) -> None:\n        \"\"\"Used by the shipit fetcher to record changes as it updates\n        files in the destination.  If the file name might be one used\n        in the cmake build system that we use for 1st party code, then\n        record that as a \"make file\" change.  We could broaden this\n        to match any file used by various build systems, but it is\n        only really useful for our internal cmake stuff at this time.\n        If the file isn't a build file and is under the `fbcode_builder`\n        dir then we don't class that as an interesting change that we\n        might need to rebuild, so we ignore it.\n        Otherwise we record the file as a source file change.\"\"\"\n\n        file_name = file_name.lower()\n        if file_name_is_cmake_file(file_name):\n            self.make_files += 1\n        elif \"/fbcode_builder/cmake\" in file_name:\n            self.source_files += 1\n        elif \"/fbcode_builder/\" not in file_name:\n            self.source_files += 1\n\n    def sources_changed(self) -> bool:\n        \"\"\"Returns true if any source files were changed during\n        an update operation.  This will typically be used to decide\n        that the build system to be run on the source dir in an\n        incremental mode\"\"\"\n        return self.source_files > 0\n\n    def build_changed(self) -> bool:\n        \"\"\"Returns true if any build files were changed during\n        an update operation.  This will typically be used to decidfe\n        that the build system should be reconfigured and re-run\n        as a full build\"\"\"\n        return self.make_files > 0\n\n\nclass Fetcher(ABC):\n    \"\"\"The Fetcher is responsible for fetching and extracting the\n    sources for project.  The Fetcher instance defines where the\n    extracted data resides and reports this to the consumer via\n    its `get_src_dir` method.\"\"\"\n\n    def update(self) -> ChangeStatus:\n        \"\"\"Brings the src dir up to date, ideally minimizing\n        changes so that a subsequent build doesn't over-build.\n        Returns a ChangeStatus object that helps the caller to\n        understand the nature of the changes required during\n        the update.\"\"\"\n        return ChangeStatus()\n\n    @abstractmethod\n    def clean(self) -> None:\n        \"\"\"Reverts any changes that might have been made to\n        the src dir\"\"\"\n        pass\n\n    @abstractmethod\n    def hash(self) -> str:\n        \"\"\"Returns a hash that identifies the version of the code in the\n        working copy.  For a git repo this is commit hash for the working\n        copy.  For other Fetchers this should relate to the version of\n        the code in the src dir.  The intent is that if a manifest\n        changes the version/rev of a project that the hash be different.\n        Importantly, this should be computable without actually fetching\n        the code, as we want this to factor into a hash used to download\n        a pre-built version of the code, without having to first download\n        and extract its sources (eg: boost on windows is pretty painful).\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def get_src_dir(self) -> str:\n        \"\"\"Returns the source directory that the project was\n        extracted into\"\"\"\n        pass\n\n\nclass LocalDirFetcher:\n    \"\"\"This class exists to override the normal fetching behavior, and\n    use an explicit user-specified directory for the project sources.\n\n    This fetcher cannot update or track changes.  It always reports that the\n    project has changed, forcing it to always be built.\"\"\"\n\n    def __init__(self, path: str) -> None:\n        self.path: str = os.path.realpath(path)\n\n    def update(self) -> ChangeStatus:\n        return ChangeStatus(all_changed=True)\n\n    def hash(self) -> str:\n        return \"0\" * 40\n\n    def get_src_dir(self) -> str:\n        return self.path\n\n    def clean(self) -> None:\n        pass\n\n\nclass SystemPackageFetcher:\n    def __init__(\n        self, build_options: BuildOptions, packages: dict[str, list[str]]\n    ) -> None:\n        self.manager: str | None = build_options.host_type.get_package_manager()\n        # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`.\n        self.packages: list[str] | None = packages.get(self.manager)\n        self.host_type: HostType = build_options.host_type\n        if self.packages:\n            self.installed: bool | None = None\n        else:\n            self.installed = False\n\n    def packages_are_installed(self) -> bool:\n        if self.installed is not None:\n            return self.installed\n\n        cmd = None\n        if self.manager == \"rpm\":\n            # pyre-fixme[6]: For 1st argument expected\n            #  `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but\n            #  got `Optional[List[str]]`.\n            cmd = [\"rpm\", \"-q\"] + sorted(self.packages)\n        elif self.manager == \"deb\":\n            # pyre-fixme[6]: For 1st argument expected\n            #  `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but\n            #  got `Optional[List[str]]`.\n            cmd = [\"dpkg\", \"-s\"] + sorted(self.packages)\n        elif self.manager == \"homebrew\":\n            # pyre-fixme[6]: For 1st argument expected\n            #  `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but\n            #  got `Optional[List[str]]`.\n            cmd = [\"brew\", \"ls\", \"--versions\"] + sorted(self.packages)\n\n        if cmd:\n            proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n            if proc.returncode == 0:\n                # captured as binary as we will hash this later\n                # pyre-fixme[8]: Attribute has type `Optional[bool]`; used as `bytes`.\n                self.installed = proc.stdout\n            else:\n                # Need all packages to be present to consider us installed\n                self.installed = False\n\n        else:\n            self.installed = False\n\n        return bool(self.installed)\n\n    def update(self) -> ChangeStatus:\n        assert self.installed\n        return ChangeStatus(all_changed=False)\n\n    def hash(self) -> str:\n        if self.packages_are_installed():\n            return hashlib.sha256(self.installed).hexdigest()\n        else:\n            return \"0\" * 40\n\n    def get_src_dir(self) -> None:\n        return None\n\n\nclass PreinstalledNopFetcher(SystemPackageFetcher):\n    def __init__(self) -> None:\n        self.installed = True\n\n\nclass GitFetcher(Fetcher):\n    DEFAULT_DEPTH = 1\n\n    def __init__(\n        self,\n        build_options: BuildOptions,\n        manifest: ManifestParser,\n        repo_url: str,\n        rev: str,\n        depth: int,\n        branch: str,\n    ) -> None:\n        # Extract the host/path portions of the URL and generate a flattened\n        # directory name.  eg:\n        # github.com/facebook/folly.git -> github.com-facebook-folly.git\n        url = urlparse(repo_url)\n        directory = \"%s%s%s\" % (url.netloc, url.path, branch if branch else \"\")\n        for s in [\"/\", \"\\\\\", \":\"]:\n            directory = directory.replace(s, \"-\")\n\n        # Place it in a repos dir in the scratch space\n        repos_dir = os.path.join(build_options.scratch_dir, \"repos\")\n        if not os.path.exists(repos_dir):\n            os.makedirs(repos_dir)\n        self.repo_dir: str = os.path.join(repos_dir, directory)\n\n        if not rev and build_options.project_hashes:\n            hash_file = os.path.join(\n                build_options.project_hashes,\n                re.sub(\"\\\\.git$\", \"-rev.txt\", url.path[1:]),\n            )\n            if os.path.exists(hash_file):\n                with open(hash_file, \"r\") as f:\n                    data = f.read()\n                    m = re.match(\"Subproject commit ([a-fA-F0-9]{40})\", data)\n                    if not m:\n                        raise Exception(\"Failed to parse rev from %s\" % hash_file)\n                    rev = m.group(1)\n                    print(\n                        \"Using pinned rev %s for %s\" % (rev, repo_url), file=sys.stderr\n                    )\n\n        self.rev: str = rev or branch or \"main\"\n        self.origin_repo: str = repo_url\n        self.manifest: ManifestParser = manifest\n        self.depth: int = depth if depth else GitFetcher.DEFAULT_DEPTH\n        self.branch: str = branch\n\n    def _update(self) -> ChangeStatus:\n        current_hash = (\n            subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"], cwd=self.repo_dir)\n            .strip()\n            .decode(\"utf-8\")\n        )\n        target_hash = (\n            subprocess.check_output([\"git\", \"rev-parse\", self.rev], cwd=self.repo_dir)\n            .strip()\n            .decode(\"utf-8\")\n        )\n        if target_hash == current_hash:\n            # It's up to date, so there are no changes.  This doesn't detect eg:\n            # if origin/main moved and rev='main', but that's ok for our purposes;\n            # we should be using explicit hashes or eg: a stable branch for the cases\n            # that we care about, and it isn't unreasonable to require that the user\n            # explicitly perform a clean build if those have moved.  For the most\n            # part we prefer that folks build using a release tarball from github\n            # rather than use the git protocol, as it is generally a bit quicker\n            # to fetch and easier to hash and verify tarball downloads.\n            return ChangeStatus()\n\n        print(\"Updating %s -> %s\" % (self.repo_dir, self.rev))\n        run_cmd([\"git\", \"fetch\", \"origin\", self.rev], cwd=self.repo_dir)\n        run_cmd([\"git\", \"checkout\", self.rev], cwd=self.repo_dir)\n        run_cmd([\"git\", \"submodule\", \"update\", \"--init\"], cwd=self.repo_dir)\n\n        return ChangeStatus(True)\n\n    def update(self) -> ChangeStatus:\n        if os.path.exists(self.repo_dir):\n            return self._update()\n        self._clone()\n        return ChangeStatus(True)\n\n    def _clone(self) -> None:\n        print(\"Cloning %s...\" % self.origin_repo)\n        # The basename/dirname stuff allows us to dance around issues where\n        # eg: this python process is native win32, but the git.exe is cygwin\n        # or msys and doesn't like the absolute windows path that we'd otherwise\n        # pass to it.  Careful use of cwd helps avoid headaches with cygpath.\n        cmd = [\n            \"git\",\n            \"clone\",\n            \"--depth=\" + str(self.depth),\n        ]\n        if self.branch:\n            cmd.append(\"--branch=\" + self.branch)\n        cmd += [\n            \"--\",\n            self.origin_repo,\n            os.path.basename(self.repo_dir),\n        ]\n        run_cmd(cmd, cwd=os.path.dirname(self.repo_dir))\n        self._update()\n\n    def clean(self) -> None:\n        if os.path.exists(self.repo_dir):\n            run_cmd([\"git\", \"clean\", \"-fxd\"], cwd=self.repo_dir)\n\n    def hash(self) -> str:\n        return self.rev\n\n    def get_src_dir(self) -> str:\n        return self.repo_dir\n\n\ndef does_file_need_update(\n    src_name: str, src_st: os.stat_result, dest_name: str\n) -> bool:\n    try:\n        target_st = os.lstat(dest_name)\n    except OSError as exc:\n        if exc.errno != errno.ENOENT:\n            raise\n        return True\n\n    if src_st.st_size != target_st.st_size:\n        return True\n\n    if stat.S_IFMT(src_st.st_mode) != stat.S_IFMT(target_st.st_mode):\n        return True\n    if stat.S_ISLNK(src_st.st_mode):\n        return os.readlink(src_name) != os.readlink(dest_name)\n    if not stat.S_ISREG(src_st.st_mode):\n        return True\n\n    # They might have the same content; compare.\n    with open(src_name, \"rb\") as sf, open(dest_name, \"rb\") as df:\n        chunk_size = 8192\n        while True:\n            src_data = sf.read(chunk_size)\n            dest_data = df.read(chunk_size)\n            if src_data != dest_data:\n                return True\n            if len(src_data) < chunk_size:\n                # EOF\n                break\n    return False\n\n\ndef copy_if_different(src_name: str, dest_name: str) -> bool:\n    \"\"\"Copy src_name -> dest_name, but only touch dest_name\n    if src_name is different from dest_name, making this a\n    more build system friendly way to copy.\"\"\"\n    src_st = os.lstat(src_name)\n    if not does_file_need_update(src_name, src_st, dest_name):\n        return False\n\n    dest_parent = os.path.dirname(dest_name)\n    if not os.path.exists(dest_parent):\n        os.makedirs(dest_parent)\n    if stat.S_ISLNK(src_st.st_mode):\n        try:\n            os.unlink(dest_name)\n        except OSError as exc:\n            if exc.errno != errno.ENOENT:\n                raise\n        target = os.readlink(src_name)\n        os.symlink(target, dest_name)\n    else:\n        shutil.copy2(src_name, dest_name)\n\n    return True\n\n\ndef filter_strip_marker(dest_name: str, marker: str) -> None:\n    \"\"\"Strip lines/blocks tagged with the given marker from a file.\"\"\"\n    try:\n        with open(dest_name, \"r\") as f:\n            content = f.read()\n    except (UnicodeDecodeError, PermissionError):\n        return\n\n    if marker not in content:\n        return\n\n    escaped = re.escape(marker)\n    block_re = re.compile(\n        r\"[^\\n]*\" + escaped + r\"-start[^\\n]*\\n.*?[^\\n]*\" + escaped + r\"-end[^\\n]*\\n?\",\n        re.DOTALL,\n    )\n    line_re = re.compile(r\".*\" + escaped + r\".*\\n?\")\n\n    filtered = block_re.sub(\"\", content)\n    filtered = line_re.sub(\"\", filtered)\n    if filtered != content:\n        with open(dest_name, \"w\") as f:\n            f.write(filtered)\n\n\ndef list_files_under_dir_newer_than_timestamp(\n    dir_to_scan: str, ts: int\n) -> Iterator[str]:\n    for root, _dirs, files in os.walk(dir_to_scan):\n        for src_file in files:\n            full_name = os.path.join(root, src_file)\n            st = os.lstat(full_name)\n            if st.st_mtime > ts:\n                yield full_name\n\n\nclass ShipitPathMap:\n    def __init__(self) -> None:\n        self.roots: list[str] = []\n        self.mapping: list[str] = []\n        self.exclusion: list[str] = []\n        self.strip_marker: str = \"@fb-only\"\n\n    def add_mapping(self, fbsource_dir: str, target_dir: str) -> None:\n        \"\"\"Add a posix path or pattern.  We cannot normpath the input\n        here because that would change the paths from posix to windows\n        form and break the logic throughout this class.\"\"\"\n        self.roots.append(fbsource_dir)\n        # pyre-fixme[6]: For 1st argument expected `str` but got `Tuple[str, str]`.\n        self.mapping.append((fbsource_dir, target_dir))\n\n    def add_exclusion(self, pattern: str) -> None:\n        # pyre-fixme[6]: For 1st argument expected `str` but got `Pattern[str]`.\n        self.exclusion.append(re.compile(pattern))\n\n    def _minimize_roots(self) -> None:\n        \"\"\"compute the de-duplicated set of roots within fbsource.\n        We take the shortest common directory prefix to make this\n        determination\"\"\"\n        self.roots.sort(key=len)\n        minimized = []\n\n        for r in self.roots:\n            add_this_entry = True\n            for existing in minimized:\n                if r.startswith(existing + \"/\"):\n                    add_this_entry = False\n                    break\n            if add_this_entry:\n                minimized.append(r)\n\n        self.roots = minimized\n\n    def _sort_mapping(self) -> None:\n        self.mapping.sort(reverse=True, key=lambda x: len(x[0]))\n\n    def _map_name(self, norm_name: str, dest_root: str) -> str | None:\n        if norm_name.endswith(\".pyc\") or norm_name.endswith(\".swp\"):\n            # Ignore some incidental garbage while iterating\n            return None\n\n        for excl in self.exclusion:\n            # pyre-fixme[16]: `str` has no attribute `match`.\n            if excl.match(norm_name):\n                return None\n\n        for src_name, dest_name in self.mapping:\n            if norm_name == src_name or norm_name.startswith(src_name + \"/\"):\n                rel_name = os.path.relpath(norm_name, src_name)\n                # We can have \".\" as a component of some paths, depending\n                # on the contents of the shipit transformation section.\n                # normpath doesn't always remove `.` as the final component\n                # of the path, which be problematic when we later mkdir\n                # the dirname of the path that we return.  Take care to avoid\n                # returning a path with a `.` in it.\n                rel_name = os.path.normpath(rel_name)\n                if dest_name == \".\":\n                    return os.path.normpath(os.path.join(dest_root, rel_name))\n                dest_name = os.path.normpath(dest_name)\n                return os.path.normpath(os.path.join(dest_root, dest_name, rel_name))\n\n        raise Exception(\"%s did not match any rules\" % norm_name)\n\n    def mirror(self, fbsource_root: str, dest_root: str) -> ChangeStatus:\n        self._minimize_roots()\n        self._sort_mapping()\n\n        change_status = ChangeStatus()\n\n        # Record the full set of files that should be in the tree\n        full_file_list = set()\n\n        if sys.platform == \"win32\":\n            # Let's not assume st_dev has a consistent value on Windows.\n            def st_dev(path: str) -> int:\n                return 1\n\n        else:\n\n            def st_dev(path: str) -> int:\n                return os.lstat(path).st_dev\n\n        for fbsource_subdir in self.roots:\n            dir_to_mirror = os.path.join(fbsource_root, fbsource_subdir)\n            root_dev = st_dev(dir_to_mirror)\n            prefetch_dir_if_eden(dir_to_mirror)\n            if not os.path.exists(dir_to_mirror):\n                raise Exception(\n                    \"%s doesn't exist; check your sparse profile!\" % dir_to_mirror\n                )\n            update_count = 0\n            for root, dirs, files in os.walk(dir_to_mirror):\n                dirs[:] = [d for d in dirs if root_dev == st_dev(os.path.join(root, d))]\n\n                for src_file in files:\n                    full_name = os.path.join(root, src_file)\n                    rel_name = os.path.relpath(full_name, fbsource_root)\n                    norm_name = rel_name.replace(\"\\\\\", \"/\")\n\n                    target_name = self._map_name(norm_name, dest_root)\n                    if target_name:\n                        full_file_list.add(target_name)\n                        if copy_if_different(full_name, target_name):\n                            filter_strip_marker(target_name, self.strip_marker)\n                            change_status.record_change(target_name)\n                            if update_count < 10:\n                                print(\"Updated %s -> %s\" % (full_name, target_name))\n                            elif update_count == 10:\n                                print(\"...\")\n                            update_count += 1\n            if update_count:\n                print(\"Updated %s for %s\" % (update_count, fbsource_subdir))\n\n        # Compare the list of previously shipped files; if a file is\n        # in the old list but not the new list then it has been\n        # removed from the source and should be removed from the\n        # destination.\n        # Why don't we simply create this list by walking dest_root?\n        # Some builds currently have to be in-source builds and\n        # may legitimately need to keep some state in the source tree :-/\n        installed_name = os.path.join(dest_root, \".shipit_shipped\")\n        if os.path.exists(installed_name):\n            with open(installed_name, \"rb\") as f:\n                for name in f.read().decode(\"utf-8\").splitlines():\n                    name = name.strip()\n                    if name not in full_file_list:\n                        print(\"Remove %s\" % name)\n                        os.unlink(name)\n                        change_status.record_change(name)\n\n        with open(installed_name, \"wb\") as f:\n            for name in sorted(full_file_list):\n                f.write((\"%s\\n\" % name).encode(\"utf-8\"))\n\n        return change_status\n\n\nclass FbsourceRepoData(NamedTuple):\n    hash: str\n    date: str\n\n\nFBSOURCE_REPO_DATA: dict[str, FbsourceRepoData] = {}\n\n\ndef get_fbsource_repo_data(build_options: BuildOptions) -> FbsourceRepoData:\n    \"\"\"Returns the commit metadata for the fbsource repo.\n    Since we may have multiple first party projects to\n    hash, and because we don't mutate the repo, we cache\n    this hash in a global.\"\"\"\n    # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`.\n    cached_data = FBSOURCE_REPO_DATA.get(build_options.fbsource_dir)\n    if cached_data:\n        return cached_data\n\n    if \"GETDEPS_HG_REPO_DATA\" in os.environ:\n        log_data = os.environ[\"GETDEPS_HG_REPO_DATA\"]\n    else:\n        cmd = [\"hg\", \"log\", \"-r.\", \"-T{node}\\n{date|hgdate}\"]\n        env = Env()\n        env.set(\"HGPLAIN\", \"1\")\n        log_data = subprocess.check_output(\n            cmd, cwd=build_options.fbsource_dir, env=dict(env.items())\n        ).decode(\"ascii\")\n\n    (hash, datestr) = log_data.split(\"\\n\")\n\n    # datestr is like \"seconds fractionalseconds\"\n    # We want \"20200324.113140\"\n    (unixtime, _fractional) = datestr.split(\" \")\n    date = datetime.fromtimestamp(int(unixtime)).strftime(\"%Y%m%d.%H%M%S\")\n    cached_data = FbsourceRepoData(hash=hash, date=date)\n\n    # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`.\n    FBSOURCE_REPO_DATA[build_options.fbsource_dir] = cached_data\n\n    return cached_data\n\n\ndef is_public_commit(build_options: BuildOptions) -> bool:  # noqa: C901\n    \"\"\"Check if the current commit is public (shipped/will be shipped to remote).\n\n    Works across git, sapling (sl), and hg repositories:\n    - For hg/sapling: Uses 'phase' command to check if commit is public\n    - For git: Checks if commit exists in remote branches\n\n    Returns True if public, False if draft/local-only or on error (conservative).\n    \"\"\"\n    # Use fbsource_dir if available (Meta internal), otherwise fall back to repo_root\n    repo_dir = build_options.fbsource_dir or build_options.repo_root\n    if not repo_dir:\n        # No repository detected, be conservative\n        return False\n\n    env = Env()\n    env.set(\"HGPLAIN\", \"1\")\n    env_dict = dict(env.items())\n\n    try:\n        # Try hg/sapling phase command first (works for both hg and sl)\n        # Try 'sl' first as it's the preferred tool at Meta\n        for cmd in [[\"sl\", \"phase\", \"-r\", \".\"], [\"hg\", \"phase\", \"-r\", \".\"]]:\n            try:\n                output = (\n                    subprocess.check_output(\n                        cmd, cwd=repo_dir, env=env_dict, stderr=subprocess.DEVNULL\n                    )\n                    .decode(\"ascii\")\n                    .strip()\n                )\n                # Output format: \"hash: public\" or \"hash: draft\"\n                return \"public\" in output\n            except (subprocess.CalledProcessError, FileNotFoundError):\n                continue\n\n        # Try git if hg/sl didn't work\n        try:\n            # Detect the default branch for origin remote\n            default_branch = None\n            try:\n                # Get the symbolic ref for origin/HEAD to find default branch\n                output = (\n                    subprocess.check_output(\n                        [\"git\", \"symbolic-ref\", \"refs/remotes/origin/HEAD\"],\n                        cwd=repo_dir,\n                        stderr=subprocess.DEVNULL,\n                    )\n                    .decode(\"ascii\")\n                    .strip()\n                )\n                # Output format: \"refs/remotes/origin/main\"\n                if output.startswith(\"refs/remotes/\"):\n                    default_branch = output\n            except subprocess.CalledProcessError:\n                # If symbolic-ref fails, fall back to common names\n                pass\n\n            # Build list of branches to check\n            branches_to_check = []\n            if default_branch:\n                branches_to_check.append(default_branch)\n            # Also try common defaults as fallback\n            branches_to_check.extend([\"origin/main\", \"origin/master\"])\n\n            # Check if HEAD is an ancestor of any of these branches\n            for branch in branches_to_check:\n                try:\n                    subprocess.check_output(\n                        [\"git\", \"merge-base\", \"--is-ancestor\", \"HEAD\", branch],\n                        cwd=repo_dir,\n                        stderr=subprocess.DEVNULL,\n                    )\n                    # If command succeeds (exit 0), HEAD is an ancestor of the branch\n                    return True\n                except subprocess.CalledProcessError:\n                    # Not an ancestor of this branch, try next\n                    continue\n            # HEAD is not in any default branch\n            return False\n        except FileNotFoundError:\n            pass\n\n        # If all VCS commands failed, be conservative and don't upload\n        return False\n\n    except Exception:\n        # On any unexpected error, be conservative and don't upload\n        return False\n\n\nclass SimpleShipitTransformerFetcher(Fetcher):\n    def __init__(\n        self,\n        build_options: BuildOptions,\n        manifest: ManifestParser,\n        ctx: ManifestContext,\n    ) -> None:\n        self.build_options: BuildOptions = build_options\n        self.manifest: ManifestParser = manifest\n        self.repo_dir: str = os.path.join(\n            build_options.scratch_dir, \"shipit\", manifest.name\n        )\n        self.ctx: ManifestContext = ctx\n\n    def clean(self) -> None:\n        if os.path.exists(self.repo_dir):\n            shutil.rmtree(self.repo_dir)\n\n    def update(self) -> ChangeStatus:\n        mapping = ShipitPathMap()\n        for src, dest in self.manifest.get_section_as_ordered_pairs(\n            \"shipit.pathmap\", self.ctx\n        ):\n            # pyre-fixme[6]: For 2nd argument expected `str` but got `Optional[str]`.\n            mapping.add_mapping(src, dest)\n        if self.manifest.shipit_fbcode_builder:\n            mapping.add_mapping(\n                \"fbcode/opensource/fbcode_builder\", \"build/fbcode_builder\"\n            )\n        for pattern in self.manifest.get_section_as_args(\"shipit.strip\", self.ctx):\n            mapping.add_exclusion(pattern)\n\n        # pyre-fixme[8]: Attribute has type `str`; used as `Optional[str]`.\n        mapping.strip_marker = self.manifest.shipit_strip_marker\n\n        # pyre-fixme[6]: In call `ShipitPathMap.mirror`, for 1st positional argument, expected `str` but got `Optional[str]`\n        return mapping.mirror(self.build_options.fbsource_dir, self.repo_dir)\n\n    def hash(self) -> str:\n        # We return a fixed non-hash string for in-fbsource builds.\n        # We're relying on the `update` logic to correctly invalidate\n        # the build in the case that files have changed.\n        return \"fbsource\"\n\n    def get_src_dir(self) -> str:\n        return self.repo_dir\n\n\nclass SubFetcher(Fetcher):\n    \"\"\"Fetcher for a project with subprojects\"\"\"\n\n    def __init__(self, base: Fetcher, subs: list[tuple[Fetcher, str]]) -> None:\n        self.base: Fetcher = base\n        self.subs: list[tuple[Fetcher, str]] = subs\n\n    def update(self) -> ChangeStatus:\n        base = self.base.update()\n        changed = base.build_changed() or base.sources_changed()\n        for fetcher, dir in self.subs:\n            stat = fetcher.update()\n            if stat.build_changed() or stat.sources_changed():\n                changed = True\n            link = self.base.get_src_dir() + \"/\" + dir\n            if not os.path.exists(link):\n                os.symlink(fetcher.get_src_dir(), link)\n        return ChangeStatus(changed)\n\n    def clean(self) -> None:\n        self.base.clean()\n        for fetcher, _ in self.subs:\n            fetcher.clean()\n\n    def hash(self) -> str:\n        my_hash = self.base.hash()\n        for fetcher, _ in self.subs:\n            my_hash += fetcher.hash()\n        return my_hash\n\n    def get_src_dir(self) -> str:\n        return self.base.get_src_dir()\n\n\nclass ShipitTransformerFetcher(Fetcher):\n    @classmethod\n    def _shipit_paths(cls, build_options: BuildOptions) -> list[str]:\n        www_path = [\"/var/www/scripts/opensource/codesync\"]\n        if build_options.fbsource_dir:\n            fbcode_path = [\n                os.path.join(\n                    build_options.fbsource_dir,\n                    \"fbcode/opensource/codesync/codesync-cli/codesync\",\n                )\n            ]\n        else:\n            fbcode_path = []\n        return www_path + fbcode_path\n\n    def __init__(\n        self, build_options: BuildOptions, project_name: str, external_branch: str\n    ) -> None:\n        self.build_options: BuildOptions = build_options\n        self.project_name: str = project_name\n        self.external_branch: str = external_branch\n        self.repo_dir: str = os.path.join(\n            build_options.scratch_dir, \"shipit\", project_name\n        )\n        self.shipit: str | None = None\n        for path in ShipitTransformerFetcher._shipit_paths(build_options):\n            if os.path.exists(path):\n                self.shipit = path\n                break\n\n    def update(self) -> ChangeStatus:\n        if os.path.exists(self.repo_dir):\n            return ChangeStatus()\n        self.run_shipit()\n        return ChangeStatus(True)\n\n    def clean(self) -> None:\n        if os.path.exists(self.repo_dir):\n            shutil.rmtree(self.repo_dir)\n\n    @classmethod\n    def available(cls, build_options: BuildOptions) -> bool:\n        return any(\n            os.path.exists(path)\n            for path in ShipitTransformerFetcher._shipit_paths(build_options)\n        )\n\n    def run_shipit(self) -> None:\n        tmp_path = self.repo_dir + \".new\"\n        try:\n            if os.path.exists(tmp_path):\n                shutil.rmtree(tmp_path)\n            os.makedirs(os.path.dirname(tmp_path), exist_ok=True)\n            cmd = [\n                self.shipit,\n                \"shipit\",\n                \"--project=\" + self.project_name,\n                \"--create-new-repo\",\n                # pyre-fixme[58]: `+` is not supported for operand types `str` and\n                #  `Optional[str]`.\n                \"--source-repo-dir=\" + self.build_options.fbsource_dir,\n                \"--source-branch=.\",\n                \"--skip-source-init\",\n                \"--skip-source-pull\",\n                \"--skip-source-clean\",\n                \"--skip-push\",\n                \"--destination-use-anonymous-https\",\n                \"--create-new-repo-output-path=\" + tmp_path,\n            ]\n            if self.external_branch:\n                cmd += [\n                    f\"--external-branch={self.external_branch}\",\n                ]\n\n            # Run shipit\n            # pyre-fixme[6]: For 1st argument expected `List[str]` but got\n            #  `List[Optional[str]]`.\n            run_cmd(cmd)\n\n            # Remove the .git directory from the repository it generated.\n            # There is no need to commit this.\n            repo_git_dir = os.path.join(tmp_path, \".git\")\n            shutil.rmtree(repo_git_dir)\n            os.rename(tmp_path, self.repo_dir)\n        except Exception:\n            # Clean up after a failed extraction\n            if os.path.exists(tmp_path):\n                shutil.rmtree(tmp_path)\n            self.clean()\n            raise\n\n    def hash(self) -> str:\n        # We return a fixed non-hash string for in-fbsource builds.\n        return \"fbsource\"\n\n    def get_src_dir(self) -> str:\n        return self.repo_dir\n\n\ndef download_url_to_file_with_progress(url: str, file_name: str) -> None:\n    print(\"Download with %s -> %s ...\" % (url, file_name))\n\n    class Progress:\n        last_report: float = 0\n\n        def write_update(self, total: int, amount: int) -> None:\n            if total == -1:\n                total = \"(Unknown)\"\n\n            if sys.stdout.isatty():\n                sys.stdout.write(\"\\r downloading %s of %s \" % (amount, total))\n            else:\n                # When logging to CI logs, avoid spamming the logs and print\n                # status every few seconds\n                now = time.time()\n                if now - self.last_report > 5:\n                    sys.stdout.write(\".. %s of %s \" % (amount, total))\n                    self.last_report = now\n            sys.stdout.flush()\n\n        def progress_pycurl(\n            self, total: float, amount: float, _uploadtotal: float, _uploadamount: float\n        ) -> None:\n            self.write_update(total, amount)\n\n    progress = Progress()\n    start = time.time()\n    try:\n        if os.environ.get(\"GETDEPS_USE_WGET\") is not None:\n            procargs = (\n                [\n                    \"wget\",\n                ]\n                + os.environ.get(\"GETDEPS_WGET_ARGS\", \"\").split()\n                + [\n                    \"-O\",\n                    file_name,\n                    url,\n                ]\n            )\n            subprocess.run(procargs, capture_output=True)\n            headers = None\n\n        elif os.environ.get(\"GETDEPS_USE_LIBCURL\") is not None:\n            import pycurl\n\n            with open(file_name, \"wb\") as f:\n                c = pycurl.Curl()\n                c.setopt(pycurl.URL, url)\n                c.setopt(pycurl.WRITEDATA, f)\n                # display progress\n                c.setopt(pycurl.NOPROGRESS, False)\n                c.setopt(pycurl.XFERINFOFUNCTION, progress.progress_pycurl)\n                c.perform()\n                c.close()\n            headers = None\n        else:\n            try:\n                req_header = {\"Accept\": \"application/*\"}\n                res = urlopen(Request(url, None, req_header))\n                chunk_size = 8192  # urlretrieve uses this value\n                headers = res.headers\n                content_length = res.headers.get(\"Content-Length\")\n                total = int(content_length.strip()) if content_length else -1\n                amount = 0\n                with open(file_name, \"wb\") as f:\n                    chunk = res.read(chunk_size)\n                    while chunk:\n                        f.write(chunk)\n                        amount += len(chunk)\n                        progress.write_update(total, amount)\n                        chunk = res.read(chunk_size)\n            except (OSError, IOError) as exc:  # noqa: B014\n                # Downloading from within Meta's network needs to use a proxy.\n                if shutil.which(\"fwdproxy-config\") is None:\n                    print(\n                        \"Note: Could not find Meta-specific fallback 'fwdproxy-config'. \"\n                        \"If you are working externally, you can ignore this message.\"\n                    )\n                    raise\n\n                print(\"Default download failed, retrying with curl and fwdproxy...\")\n                cmd = f\"curl -L $(fwdproxy-config curl) -o {shlex.quote(file_name)} {shlex.quote(url)}\"\n                print(f\"Running command: {cmd}\")\n                result = subprocess.run(cmd, shell=True, capture_output=True)\n                if result.returncode != 0:\n                    raise TransientFailure(\n                        f\"Failed to download {url} to {file_name}: {exc} (fwdproxy fallback failed: {result.stderr.decode()})\"\n                    )\n                headers = None\n    except (OSError, IOError) as exc:  # noqa: B014\n        raise TransientFailure(\n            \"Failed to download %s to %s: %s\" % (url, file_name, str(exc))\n        )\n\n    end = time.time()\n    sys.stdout.write(\" [Complete in %f seconds]\\n\" % (end - start))\n    sys.stdout.flush()\n    if headers is not None:\n        print(f\"{headers}\")\n\n\nclass ArchiveFetcher(Fetcher):\n    def __init__(\n        self,\n        build_options: BuildOptions,\n        manifest: ManifestParser,\n        url: str,\n        sha256: str,\n    ) -> None:\n        self.manifest: ManifestParser = manifest\n        self.url: str = url\n        self.sha256: str = sha256\n        self.build_options: BuildOptions = build_options\n\n        parsed_url = urlparse(self.url)\n        basename = \"%s-%s\" % (manifest.name, os.path.basename(parsed_url.path))\n        self.file_name: str = os.path.join(\n            build_options.scratch_dir, \"downloads\", basename\n        )\n        self.src_dir: str = os.path.join(\n            build_options.scratch_dir, \"extracted\", basename\n        )\n        self.hash_file: str = self.src_dir + \".hash\"\n\n    def _verify_hash(self) -> None:\n        h = hashlib.sha256()\n        with open(self.file_name, \"rb\") as f:\n            while True:\n                block = f.read(8192)\n                if not block:\n                    break\n                h.update(block)\n        digest = h.hexdigest()\n        if digest != self.sha256:\n            os.unlink(self.file_name)\n            raise Exception(\n                \"%s: expected sha256 %s but got %s\" % (self.url, self.sha256, digest)\n            )\n\n    def _download_dir(self) -> str:\n        \"\"\"returns the download dir, creating it if it doesn't already exist\"\"\"\n        download_dir = os.path.dirname(self.file_name)\n        if not os.path.exists(download_dir):\n            os.makedirs(download_dir)\n        return download_dir\n\n    def _download(self) -> None:\n        self._download_dir()\n        max_attempts = 5\n        delay = 1\n        for attempt in range(max_attempts):\n            try:\n                download_url_to_file_with_progress(self.url, self.file_name)\n                break\n            except TransientFailure as tf:\n                if attempt < max_attempts - 1:\n                    delay *= 2\n                    delay_with_jitter = delay * (1 + random.random() * 0.1)\n                    time.sleep(min(delay_with_jitter, 10))\n                else:\n                    print(f\"Failed after retries: {tf}\")\n                    raise\n        self._verify_hash()\n\n    def clean(self) -> None:\n        if os.path.exists(self.src_dir):\n            shutil.rmtree(self.src_dir)\n\n    def update(self) -> ChangeStatus:\n        try:\n            with open(self.hash_file, \"r\") as f:\n                saved_hash = f.read().strip()\n                if saved_hash == self.sha256 and os.path.exists(self.src_dir):\n                    # Everything is up to date\n                    return ChangeStatus()\n                print(\n                    \"saved hash %s doesn't match expected hash %s, re-validating\"\n                    % (saved_hash, self.sha256)\n                )\n                os.unlink(self.hash_file)\n        except EnvironmentError:\n            pass\n\n        # If we got here we know the contents of src_dir are either missing\n        # or wrong, so blow away whatever happened to be there first.\n        if os.path.exists(self.src_dir):\n            shutil.rmtree(self.src_dir)\n\n        # If we already have a file here, make sure it looks legit before\n        # proceeding: any errors and we just remove it and re-download\n        if os.path.exists(self.file_name):\n            try:\n                self._verify_hash()\n            except Exception:\n                if os.path.exists(self.file_name):\n                    os.unlink(self.file_name)\n\n        if not os.path.exists(self.file_name):\n            self._download()\n            self._verify_hash()\n\n        if tarfile.is_tarfile(self.file_name):\n            opener = tarfile.open\n        elif zipfile.is_zipfile(self.file_name):\n            opener = zipfile.ZipFile\n        else:\n            raise Exception(\"don't know how to extract %s\" % self.file_name)\n        os.makedirs(self.src_dir)\n        print(\"Extract %s -> %s\" % (self.file_name, self.src_dir))\n        if is_windows():\n            # Ensure that we don't fall over when dealing with long paths\n            # on windows\n            src = r\"\\\\?\\%s\" % os.path.normpath(self.src_dir)\n        else:\n            src = self.src_dir\n\n        with opener(self.file_name) as t:\n            # The `str` here is necessary to ensure that we don't pass a unicode\n            # object down to tarfile.extractall on python2.  When extracting\n            # the boost tarball it makes some assumptions and tries to convert\n            # a non-ascii path to ascii and throws.\n            src = str(src)\n            t.extractall(src)\n\n        if is_windows():\n            subdir = self.manifest.get(\"build\", \"subdir\")\n            checkdir = src\n            if subdir:\n                checkdir = src + \"\\\\\" + subdir\n            if os.path.exists(checkdir):\n                children = os.listdir(checkdir)\n                print(f\"Extracted to {checkdir} contents: {children}\")\n\n        with open(self.hash_file, \"w\") as f:\n            f.write(self.sha256)\n\n        return ChangeStatus(True)\n\n    def hash(self) -> str:\n        return self.sha256\n\n    def get_src_dir(self) -> str:\n        return self.src_dir\n\n\ndef homebrew_package_prefix(package: str) -> str | None:\n    cmd = [\"brew\", \"--prefix\", package]\n    try:\n        proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    except FileNotFoundError:\n        return None\n\n    if proc.returncode == 0:\n        return proc.stdout.decode(\"utf-8\").rstrip()\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/include_rewriter.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\n\"\"\"\nInclude Path Rewriter for getdeps\n\nThis module provides functionality to rewrite #include statements in C++ files\nto handle differences between fbcode and open source project structures.\n\"\"\"\n\nimport os\nimport re\nimport typing\nfrom pathlib import Path\nfrom typing import Any\n\nif typing.TYPE_CHECKING:\n    from .manifest import ManifestParser\n\n\nclass IncludePathRewriter:\n    \"\"\"Rewrites #include paths in C++ source files based on path mappings.\"\"\"\n\n    # C++ file extensions to process\n    CPP_EXTENSIONS: set[str] = {\n        \".cpp\",\n        \".cc\",\n        \".cxx\",\n        \".c\",\n        \".h\",\n        \".hpp\",\n        \".hxx\",\n        \".tcc\",\n        \".inc\",\n    }\n\n    def __init__(self, mappings: list[tuple[str, str]], verbose: bool = False) -> None:\n        \"\"\"\n        Initialize the rewriter with path mappings.\n\n        Args:\n            mappings: List of (old_path_prefix, new_path_prefix) tuples\n            verbose: Enable verbose output\n        \"\"\"\n        self.mappings: list[tuple[str, str]] = mappings\n        self.verbose: bool = verbose\n\n        # Compile regex patterns for efficiency\n        self.patterns: list[tuple[re.Pattern[str], str, str]] = []\n        for old_prefix, new_prefix in mappings:\n            # Match both quoted and angle bracket includes\n            # Pattern matches: #include \"old_prefix/rest\" or #include <old_prefix/rest>\n            pattern = re.compile(\n                r'(#\\s*include\\s*[<\"])(' + re.escape(old_prefix) + r'/[^\">]+)([\">])',\n                re.MULTILINE,\n            )\n            self.patterns.append((pattern, old_prefix, new_prefix))\n\n    def rewrite_file(self, file_path: Path, dry_run: bool = False) -> bool:\n        \"\"\"\n        Rewrite includes in a single file.\n\n        Args:\n            file_path: Path to the file to process\n            dry_run: If True, don't actually modify files\n\n        Returns:\n            True if file was modified, False otherwise\n        \"\"\"\n        try:\n            with open(file_path, \"r\", encoding=\"utf-8\") as f:\n                original_content: str = f.read()\n        except (IOError, UnicodeDecodeError) as e:\n            if self.verbose:\n                print(f\"Warning: Could not read {file_path}: {e}\")\n            return False\n\n        modified_content: str = original_content\n        changes_made: bool = False\n\n        for pattern, old_prefix, new_prefix in self.patterns:\n\n            def make_replace_func(\n                old_prefix: str, new_prefix: str\n            ) -> typing.Callable[[re.Match[str]], str]:\n                def replace_func(match: re.Match[str]) -> str:\n                    nonlocal changes_made\n                    prefix: str = match.group(1)  # #include [<\"]\n                    full_path: str = match.group(2)  # full path\n                    suffix: str = match.group(3)  # [\">]\n\n                    # Replace the old prefix with new prefix\n                    new_path: str = full_path.replace(old_prefix, new_prefix, 1)\n\n                    if self.verbose and not changes_made:\n                        print(f\"  {full_path} -> {new_path}\")\n\n                    changes_made = True\n                    return f\"{prefix}{new_path}{suffix}\"\n\n                return replace_func\n\n            modified_content = pattern.sub(\n                make_replace_func(old_prefix, new_prefix), modified_content\n            )\n\n        if changes_made and not dry_run:\n            try:\n                with open(file_path, \"w\", encoding=\"utf-8\") as f:\n                    f.write(modified_content)\n                if self.verbose:\n                    print(f\"Modified: {file_path}\")\n            except IOError as e:\n                print(f\"Error: Could not write {file_path}: {e}\")\n                return False\n        elif changes_made and dry_run:\n            if self.verbose:\n                print(f\"Would modify: {file_path}\")\n\n        return changes_made\n\n    def process_directory(self, source_dir: Path, dry_run: bool = False) -> int:\n        \"\"\"\n        Process all C++ files in a directory recursively.\n\n        Args:\n            source_dir: Root directory to process\n            dry_run: If True, don't actually modify files\n\n        Returns:\n            Number of files modified\n        \"\"\"\n        if not source_dir.exists():\n            if self.verbose:\n                print(f\"Warning: Directory {source_dir} does not exist\")\n            return 0\n\n        modified_count: int = 0\n        processed_count: int = 0\n\n        for root, dirs, files in os.walk(source_dir):\n            # Skip hidden directories and common build directories\n            dirs[:] = [\n                d\n                for d in dirs\n                if not d.startswith(\".\")\n                and d not in {\"build\", \"_build\", \"__pycache__\", \"CMakeFiles\"}\n            ]\n\n            for file in files:\n                file_path: Path = Path(root) / file\n\n                # Only process C++ files\n                if file_path.suffix.lower() not in self.CPP_EXTENSIONS:\n                    continue\n\n                processed_count += 1\n                if self.verbose:\n                    print(f\"Processing: {file_path}\")\n\n                if self.rewrite_file(file_path, dry_run):\n                    modified_count += 1\n\n        if self.verbose or modified_count > 0:\n            print(f\"Processed {processed_count} files, modified {modified_count} files\")\n        return modified_count\n\n\ndef rewrite_includes_from_manifest(\n    manifest: ManifestParser, ctx: Any, source_dir: str, verbose: bool = False\n) -> int:\n    \"\"\"\n    Rewrite includes using mappings from a manifest file.\n\n    Args:\n        manifest: The manifest object containing shipit.pathmap section\n        ctx: The manifest context\n        source_dir: Directory containing source files to process\n        verbose: Enable verbose output\n\n    Returns:\n        Number of files modified\n    \"\"\"\n    mappings: list[tuple[str, str]] = []\n\n    # Get mappings from the manifest's shipit.pathmap section\n    for src, dest in manifest.get_section_as_ordered_pairs(\"shipit.pathmap\", ctx):\n        # Remove fbcode/ or xplat/ prefixes from src since they won't appear in #include statements\n        if src.startswith(\"fbcode/\"):\n            src = src[len(\"fbcode/\") :]\n        elif src.startswith(\"xplat/\"):\n            src = src[len(\"xplat/\") :]\n        # pyre-fixme[6]: For 1st argument expected `Tuple[str, str]` but got\n        #  `Tuple[str, Optional[str]]`.\n        mappings.append((src, dest))\n\n    if not mappings:\n        if verbose:\n            print(\"No include path mappings found in manifest\")\n        return 0\n\n    if verbose:\n        print(\"Include path mappings:\")\n        for old_path, new_path in mappings:\n            print(f\"  {old_path} -> {new_path}\")\n\n    rewriter: IncludePathRewriter = IncludePathRewriter(mappings, verbose)\n    return rewriter.process_directory(Path(source_dir), dry_run=False)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/load.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport base64\nimport copy\nimport hashlib\nimport os\nimport typing\nfrom collections.abc import Iterator\n\nfrom . import fetcher\nfrom .envfuncs import path_search\nfrom .errors import ManifestNotFound\nfrom .manifest import ManifestParser\n\nif typing.TYPE_CHECKING:\n    from .buildopts import BuildOptions\n    from .manifest import ContextGenerator, ManifestContext\n\n\nclass Loader:\n    \"\"\"The loader allows our tests to patch the load operation\"\"\"\n\n    def _list_manifests(self, build_opts: BuildOptions) -> Iterator[str]:\n        \"\"\"Returns a generator that iterates all the available manifests\"\"\"\n        for path, _, files in os.walk(build_opts.manifests_dir):\n            for name in files:\n                # skip hidden files\n                if name.startswith(\".\"):\n                    continue\n\n                yield os.path.join(path, name)\n\n    def _load_manifest(self, path: str) -> ManifestParser:\n        return ManifestParser(path)\n\n    def load_project(\n        self, build_opts: BuildOptions, project_name: str\n    ) -> ManifestParser:\n        if \"/\" in project_name or \"\\\\\" in project_name:\n            # Assume this is a path already\n            return ManifestParser(project_name)\n\n        for manifest in self._list_manifests(build_opts):\n            if os.path.basename(manifest) == project_name:\n                return ManifestParser(manifest)\n\n        raise ManifestNotFound(project_name)\n\n    def load_all(self, build_opts: BuildOptions) -> dict[str, ManifestParser]:\n        manifests_by_name: dict[str, ManifestParser] = {}\n\n        for manifest in self._list_manifests(build_opts):\n            m = self._load_manifest(manifest)\n\n            if m.name in manifests_by_name:\n                raise Exception(\"found duplicate manifest '%s'\" % m.name)\n\n            manifests_by_name[m.name] = m\n\n        return manifests_by_name\n\n\nclass ResourceLoader(Loader):\n    def __init__(self, namespace: str, manifests_dir: str) -> None:\n        self.namespace: str = namespace\n        self.manifests_dir: str = manifests_dir\n\n    def _list_manifests(self, build_opts: BuildOptions) -> Iterator[str]:\n        import pkg_resources\n\n        dirs: list[str] = [self.manifests_dir]\n\n        while dirs:\n            current = dirs.pop(0)\n            for name in pkg_resources.resource_listdir(self.namespace, current):\n                path = \"%s/%s\" % (current, name)\n\n                if pkg_resources.resource_isdir(self.namespace, path):\n                    dirs.append(path)\n                else:\n                    yield \"%s/%s\" % (current, name)\n\n    def _find_manifest(self, project_name: str) -> str:\n        # pyre-fixme[20]: Call `ResourceLoader._list_manifests` expects argument `build_opts`.\n        for name in self._list_manifests():\n            if name.endswith(\"/%s\" % project_name):\n                return name\n\n        raise ManifestNotFound(project_name)\n\n    def _load_manifest(self, path: str) -> ManifestParser:\n        import pkg_resources\n\n        contents = pkg_resources.resource_string(self.namespace, path).decode(\"utf8\")\n        return ManifestParser(file_name=path, fp=contents)\n\n    def load_project(\n        self, build_opts: BuildOptions, project_name: str\n    ) -> ManifestParser:\n        project_name = self._find_manifest(project_name)\n        # pyre-fixme[16]: `ResourceLoader` has no attribute `_load_resource_manifest`.\n        return self._load_resource_manifest(project_name)\n\n\nLOADER: Loader = Loader()\n\n\ndef patch_loader(namespace: str, manifests_dir: str = \"manifests\") -> None:\n    global LOADER\n    LOADER = ResourceLoader(namespace, manifests_dir)\n\n\ndef load_project(build_opts: BuildOptions, project_name: str) -> ManifestParser:\n    \"\"\"given the name of a project or a path to a manifest file,\n    load up the ManifestParser instance for it and return it\"\"\"\n    return LOADER.load_project(build_opts, project_name)\n\n\ndef load_all_manifests(build_opts: BuildOptions) -> dict[str, ManifestParser]:\n    return LOADER.load_all(build_opts)\n\n\nclass ManifestLoader:\n    \"\"\"ManifestLoader stores information about project manifest relationships for a\n    given set of (build options + platform) configuration.\n\n    The ManifestLoader class primarily serves as a location to cache project dependency\n    relationships and project hash values for this build configuration.\n    \"\"\"\n\n    def __init__(\n        self, build_opts: BuildOptions, ctx_gen: ContextGenerator | None = None\n    ) -> None:\n        self._loader: Loader = LOADER\n        self.build_opts: BuildOptions = build_opts\n        if ctx_gen is None:\n            self.ctx_gen: ContextGenerator = self.build_opts.get_context_generator()\n        else:\n            self.ctx_gen = ctx_gen\n\n        self.manifests_by_name: dict[str, ManifestParser] = {}\n        self._loaded_all: bool = False\n        self._project_hashes: dict[str, str] = {}\n        self._fetcher_overrides: dict[str, fetcher.LocalDirFetcher] = {}\n        self._build_dir_overrides: dict[str, str] = {}\n        self._install_dir_overrides: dict[str, str] = {}\n        self._install_prefix_overrides: dict[str, str] = {}\n\n    def load_manifest(self, name: str) -> ManifestParser:\n        manifest = self.manifests_by_name.get(name)\n        if manifest is None:\n            manifest = self._loader.load_project(self.build_opts, name)\n            self.manifests_by_name[name] = manifest\n        return manifest\n\n    def load_all_manifests(self) -> dict[str, ManifestParser]:\n        if not self._loaded_all:\n            all_manifests_by_name = self._loader.load_all(self.build_opts)\n            if self.manifests_by_name:\n                # To help ensure that we only ever have a single manifest object for a\n                # given project, and that it can't change once we have loaded it,\n                # only update our mapping for projects that weren't already loaded.\n                for name, manifest in all_manifests_by_name.items():\n                    self.manifests_by_name.setdefault(name, manifest)\n            else:\n                self.manifests_by_name = all_manifests_by_name\n            self._loaded_all = True\n\n        return self.manifests_by_name\n\n    def dependencies_of(self, manifest: ManifestParser) -> list[ManifestParser]:\n        \"\"\"Returns the dependencies of the given project, not including the project itself, in topological order.\"\"\"\n        return [\n            dep\n            for dep in self.manifests_in_dependency_order(manifest)\n            if dep != manifest\n        ]\n\n    def manifests_in_dependency_order(\n        self, manifest: ManifestParser | None = None\n    ) -> list[ManifestParser]:\n        \"\"\"Compute all dependencies of the specified project.  Returns a list of the\n        dependencies plus the project itself, in topologically sorted order.\n\n        Each entry in the returned list only depends on projects that appear before it\n        in the list.\n\n        If the input manifest is None, the dependencies for all currently loaded\n        projects will be computed.  i.e., if you call load_all_manifests() followed by\n        manifests_in_dependency_order() this will return a global dependency ordering of\n        all projects.\"\"\"\n        # The list of deps that have been fully processed\n        seen: set[str] = set()\n        # The list of deps which have yet to be evaluated.  This\n        # can potentially contain duplicates.\n        if manifest is None:\n            deps: list[ManifestParser] = list(self.manifests_by_name.values())\n        else:\n            assert manifest.name in self.manifests_by_name\n            deps = [manifest]\n        # The list of manifests in dependency order\n        dep_order: list[ManifestParser] = []\n        system_packages: dict[str, list[str]] = {}\n\n        while len(deps) > 0:\n            m = deps.pop(0)\n            if m.name in seen:\n                continue\n\n            # Consider its deps, if any.\n            # We sort them for increased determinism; we'll produce\n            # a correct order even if they aren't sorted, but we prefer\n            # to produce the same order regardless of how they are listed\n            # in the project manifest files.\n            ctx: ManifestContext = self.ctx_gen.get_context(m.name)\n            dep_list: list[str] = m.get_dependencies(ctx)\n\n            dep_count: int = 0\n            for dep_name in dep_list:\n                # If we're not sure whether it is done, queue it up\n                if dep_name not in seen:\n                    dep = self.manifests_by_name.get(dep_name)\n                    if dep is None:\n                        dep = self._loader.load_project(self.build_opts, dep_name)\n                        self.manifests_by_name[dep.name] = dep\n\n                    deps.append(dep)\n                    dep_count += 1\n\n            if dep_count > 0:\n                # If we queued anything, re-queue this item, as it depends\n                # those new item(s) and their transitive deps.\n                deps.append(m)\n                continue\n\n            # Its deps are done, so we can emit it\n            seen.add(m.name)\n            # Capture system packages as we may need to set PATHs to then later\n            if (\n                self.build_opts.allow_system_packages\n                and self.build_opts.host_type.get_package_manager()\n            ):\n                packages: dict[str, list[str]] = m.get_required_system_packages(ctx)\n                for pkg_type, v in packages.items():\n                    merged: list[str] = system_packages.get(pkg_type, [])\n                    if v not in merged:\n                        merged += v\n                    system_packages[pkg_type] = merged\n                # A manifest depends on all system packages in it dependencies as well\n                # pyre-fixme[8]: Attribute has type `Dict[str, str]`; used as\n                #  `Dict[str, List[str]]`.\n                m.resolved_system_packages = copy.copy(system_packages)\n            dep_order.append(m)\n\n        return dep_order\n\n    def set_project_src_dir(self, project_name: str, path: str) -> None:\n        self._fetcher_overrides[project_name] = fetcher.LocalDirFetcher(path)\n\n    def set_project_build_dir(self, project_name: str, path: str) -> None:\n        self._build_dir_overrides[project_name] = path\n\n    def set_project_install_dir(self, project_name: str, path: str) -> None:\n        self._install_dir_overrides[project_name] = path\n\n    def set_project_install_prefix(self, project_name: str, path: str) -> None:\n        self._install_prefix_overrides[project_name] = path\n\n    def create_fetcher(\n        self, manifest: ManifestParser\n    ) -> fetcher.Fetcher | fetcher.LocalDirFetcher:\n        override = self._fetcher_overrides.get(manifest.name)\n        if override is not None:\n            return override\n\n        ctx: ManifestContext = self.ctx_gen.get_context(manifest.name)\n        return manifest.create_fetcher(self.build_opts, self, ctx)\n\n    def get_project_hash(self, manifest: ManifestParser) -> str:\n        h = self._project_hashes.get(manifest.name)\n        if h is None:\n            h = self._compute_project_hash(manifest)\n            self._project_hashes[manifest.name] = h\n        return h\n\n    def _compute_project_hash(self, manifest: ManifestParser) -> str:\n        \"\"\"This recursive function computes a hash for a given manifest.\n        The hash takes into account some environmental factors on the\n        host machine and includes the hashes of its dependencies.\n        No caching of the computation is performed, which is theoretically\n        wasteful but the computation is fast enough that it is not required\n        to cache across multiple invocations.\"\"\"\n        ctx: ManifestContext = self.ctx_gen.get_context(manifest.name)\n\n        hasher = hashlib.sha256()\n        # Some environmental and configuration things matter\n        env: dict[str, str | None] = {}\n        env[\"install_dir\"] = self.build_opts.install_dir\n        env[\"scratch_dir\"] = self.build_opts.scratch_dir\n        env[\"vcvars_path\"] = self.build_opts.vcvars_path\n        env[\"os\"] = self.build_opts.host_type.ostype\n        env[\"distro\"] = self.build_opts.host_type.distro\n        env[\"distro_vers\"] = self.build_opts.host_type.distrovers\n        env[\"shared_libs\"] = str(self.build_opts.shared_libs)\n        for name in [\n            \"CXXFLAGS\",\n            \"CPPFLAGS\",\n            \"LDFLAGS\",\n            \"CXX\",\n            \"CC\",\n            \"GETDEPS_CMAKE_DEFINES\",\n        ]:\n            env[name] = os.environ.get(name)\n        for tool in [\"cc\", \"c++\", \"gcc\", \"g++\", \"clang\", \"clang++\"]:\n            env[\"tool-%s\" % tool] = path_search(os.environ, tool)\n        for name in manifest.get_section_as_args(\"depends.environment\", ctx):\n            env[name] = os.environ.get(name)\n\n        fetcher_inst: fetcher.Fetcher | fetcher.LocalDirFetcher = self.create_fetcher(\n            manifest\n        )\n        env[\"fetcher.hash\"] = fetcher_inst.hash()\n\n        for name in sorted(env.keys()):\n            hasher.update(name.encode(\"utf-8\"))\n            value = env.get(name)\n            if value is not None:\n                try:\n                    hasher.update(value.encode(\"utf-8\"))\n                except AttributeError as exc:\n                    raise AttributeError(\"name=%r, value=%r: %s\" % (name, value, exc))\n\n        manifest.update_hash(hasher, ctx)\n\n        # If a patchfile is specified, include its contents in the hash\n        patchfile: str | None = manifest.get(\"build\", \"patchfile\", ctx=ctx)\n        if patchfile:\n            patchfile_path: str = os.path.join(\n                self.build_opts.fbcode_builder_dir, \"patches\", patchfile\n            )\n            if os.path.exists(patchfile_path):\n                with open(patchfile_path, \"rb\") as f:\n                    hasher.update(f.read())\n\n        dep_list: list[str] = manifest.get_dependencies(ctx)\n        for dep in dep_list:\n            dep_manifest: ManifestParser = self.load_manifest(dep)\n            dep_hash: str = self.get_project_hash(dep_manifest)\n            hasher.update(dep_hash.encode(\"utf-8\"))\n\n        # Use base64 to represent the hash, rather than the simple hex digest,\n        # so that the string is shorter.  Use the URL-safe encoding so that\n        # the hash can also be safely used as a filename component.\n        h: str = base64.urlsafe_b64encode(hasher.digest()).decode(\"ascii\")\n        # ... and because cmd.exe is troublesome with `=` signs, nerf those.\n        # They tend to be padding characters at the end anyway, so we can\n        # safely discard them.\n        h = h.replace(\"=\", \"\")\n\n        return h\n\n    def _get_project_dir_name(self, manifest: ManifestParser) -> str:\n        if manifest.is_first_party_project():\n            return manifest.name\n        else:\n            project_hash: str = self.get_project_hash(manifest)\n            return \"%s-%s\" % (manifest.name, project_hash)\n\n    def get_project_install_dir(self, manifest: ManifestParser) -> str:\n        override = self._install_dir_overrides.get(manifest.name)\n        if override:\n            return override\n\n        project_dir_name: str = self._get_project_dir_name(manifest)\n        return os.path.join(self.build_opts.install_dir, project_dir_name)\n\n    def get_project_build_dir(self, manifest: ManifestParser) -> str:\n        override = self._build_dir_overrides.get(manifest.name)\n        if override:\n            return override\n\n        project_dir_name: str = self._get_project_dir_name(manifest)\n        return os.path.join(self.build_opts.scratch_dir, \"build\", project_dir_name)\n\n    def get_project_install_prefix(self, manifest: ManifestParser) -> str | None:\n        return self._install_prefix_overrides.get(manifest.name)\n\n    def get_project_install_dir_respecting_install_prefix(\n        self, manifest: ManifestParser\n    ) -> str:\n        inst_dir: str = self.get_project_install_dir(manifest)\n        prefix: str | None = self.get_project_install_prefix(manifest)\n        if prefix:\n            return inst_dir + prefix\n        return inst_dir\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/manifest.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport configparser\nimport hashlib\nimport io\nimport os\nimport sys\nimport typing\n\nfrom .builder import (\n    AutoconfBuilder,\n    Boost,\n    CMakeBootStrapBuilder,\n    CMakeBuilder,\n    Iproute2Builder,\n    MakeBuilder,\n    MesonBuilder,\n    NinjaBootstrap,\n    NopBuilder,\n    OpenSSLBuilder,\n    SetupPyBuilder,\n    SqliteBuilder,\n)\nfrom .cargo import CargoBuilder\nfrom .expr import ExprNode, parse_expr\nfrom .fetcher import (\n    ArchiveFetcher,\n    GitFetcher,\n    PreinstalledNopFetcher,\n    ShipitTransformerFetcher,\n    SimpleShipitTransformerFetcher,\n    SubFetcher,\n    SystemPackageFetcher,\n)\nfrom .py_wheel_builder import PythonWheelBuilder\n\nif typing.TYPE_CHECKING:\n    from .builder import BuilderBase\n    from .buildopts import BuildOptions\n    from .fetcher import Fetcher\n    from .load import ManifestLoader\n\nREQUIRED: str = \"REQUIRED\"\nOPTIONAL: str = \"OPTIONAL\"\n\nSCHEMA: dict[str, dict[str, object]] = {\n    \"manifest\": {\n        \"optional_section\": False,\n        \"fields\": {\n            \"name\": REQUIRED,\n            \"fbsource_path\": OPTIONAL,\n            \"shipit_project\": OPTIONAL,\n            \"shipit_fbcode_builder\": OPTIONAL,\n            \"use_shipit\": OPTIONAL,\n            \"shipit_external_branch\": OPTIONAL,\n            \"shipit_strip_marker\": OPTIONAL,\n        },\n    },\n    \"dependencies\": {\"optional_section\": True, \"allow_values\": False},\n    \"depends.environment\": {\"optional_section\": True},\n    \"git\": {\n        \"optional_section\": True,\n        \"fields\": {\n            \"repo_url\": REQUIRED,\n            \"rev\": OPTIONAL,\n            \"depth\": OPTIONAL,\n            \"branch\": OPTIONAL,\n        },\n    },\n    \"download\": {\n        \"optional_section\": True,\n        \"fields\": {\"url\": REQUIRED, \"sha256\": REQUIRED},\n    },\n    \"build\": {\n        \"optional_section\": True,\n        \"fields\": {\n            \"builder\": REQUIRED,\n            \"subdir\": OPTIONAL,\n            \"make_binary\": OPTIONAL,\n            \"build_in_src_dir\": OPTIONAL,\n            \"only_install\": OPTIONAL,\n            \"job_weight_mib\": OPTIONAL,\n            \"patchfile\": OPTIONAL,\n            \"patchfile_opts\": OPTIONAL,\n            \"rewrite_includes\": OPTIONAL,\n        },\n    },\n    \"msbuild\": {\"optional_section\": True, \"fields\": {\"project\": REQUIRED}},\n    \"cargo\": {\n        \"optional_section\": True,\n        \"fields\": {\n            \"build_doc\": OPTIONAL,\n            \"workspace_dir\": OPTIONAL,\n            \"manifests_to_build\": OPTIONAL,\n            # Where to write cargo config (defaults to build_dir/.cargo/config.toml)\n            \"cargo_config_file\": OPTIONAL,\n        },\n    },\n    \"github.actions\": {\n        \"optional_section\": True,\n        \"fields\": {\n            \"run_tests\": OPTIONAL,\n            \"required_locales\": OPTIONAL,\n            \"rust_version\": OPTIONAL,\n            \"build_type\": OPTIONAL,\n        },\n    },\n    \"crate.pathmap\": {\"optional_section\": True},\n    \"cmake.defines\": {\"optional_section\": True},\n    \"autoconf.args\": {\"optional_section\": True},\n    \"autoconf.envcmd.LDFLAGS\": {\"optional_section\": True},\n    \"rpms\": {\"optional_section\": True},\n    \"debs\": {\"optional_section\": True},\n    \"homebrew\": {\"optional_section\": True},\n    \"pps\": {\"optional_section\": True},\n    \"preinstalled.env\": {\"optional_section\": True},\n    \"bootstrap.args\": {\"optional_section\": True},\n    \"b2.args\": {\"optional_section\": True},\n    \"make.build_args\": {\"optional_section\": True},\n    \"make.install_args\": {\"optional_section\": True},\n    \"make.test_args\": {\"optional_section\": True},\n    \"meson.setup_args\": {\"optional_section\": True},\n    \"header-only\": {\"optional_section\": True, \"fields\": {\"includedir\": REQUIRED}},\n    \"shipit.pathmap\": {\"optional_section\": True},\n    \"shipit.strip\": {\"optional_section\": True},\n    \"install.files\": {\"optional_section\": True},\n    \"subprojects\": {\"optional_section\": True},\n    # fb-only\n    \"sandcastle\": {\"optional_section\": True, \"fields\": {\"run_tests\": OPTIONAL}},\n    \"setup-py.test\": {\"optional_section\": True, \"fields\": {\"python_script\": REQUIRED}},\n    \"setup-py.env\": {\"optional_section\": True},\n}\n\n# These sections are allowed to vary for different platforms\n# using the expression syntax to enable/disable sections\nALLOWED_EXPR_SECTIONS: list[str] = [\n    \"autoconf.args\",\n    \"autoconf.envcmd.LDFLAGS\",\n    \"build\",\n    \"cmake.defines\",\n    \"dependencies\",\n    \"make.build_args\",\n    \"make.install_args\",\n    \"bootstrap.args\",\n    \"b2.args\",\n    \"download\",\n    \"git\",\n    \"install.files\",\n    \"rpms\",\n    \"debs\",\n    \"shipit.pathmap\",\n    \"shipit.strip\",\n    \"homebrew\",\n    \"github.actions\",\n    \"pps\",\n]\n\n\ndef parse_conditional_section_name(name: str, section_def: str) -> ExprNode:\n    expr = name[len(section_def) + 1 :]\n    return parse_expr(expr, ManifestContext.ALLOWED_VARIABLES)\n\n\ndef validate_allowed_fields(\n    file_name: str,\n    section: str,\n    config: configparser.RawConfigParser,\n    allowed_fields: dict[str, str],\n) -> None:\n    for field in config.options(section):\n        if not allowed_fields.get(field):\n            raise Exception(\n                (\"manifest file %s section '%s' contains \" \"unknown field '%s'\")\n                % (file_name, section, field)\n            )\n\n    for field in allowed_fields:\n        if allowed_fields[field] == REQUIRED and not config.has_option(section, field):\n            raise Exception(\n                (\"manifest file %s section '%s' is missing \" \"required field '%s'\")\n                % (file_name, section, field)\n            )\n\n\ndef validate_allow_values(\n    file_name: str, section: str, config: configparser.RawConfigParser\n) -> None:\n    for field in config.options(section):\n        value = config.get(section, field)\n        if value is not None:\n            raise Exception(\n                (\n                    \"manifest file %s section '%s' has '%s = %s' but \"\n                    \"this section doesn't allow specifying values \"\n                    \"for its entries\"\n                )\n                % (file_name, section, field, value)\n            )\n\n\ndef validate_section(\n    file_name: str, section: str, config: configparser.RawConfigParser\n) -> str:\n    section_def = SCHEMA.get(section)\n    if not section_def:\n        for name in ALLOWED_EXPR_SECTIONS:\n            if section.startswith(name + \".\"):\n                # Verify that the conditional parses, but discard it\n                try:\n                    parse_conditional_section_name(section, name)\n                except Exception as exc:\n                    raise Exception(\n                        (\"manifest file %s section '%s' has invalid \" \"conditional: %s\")\n                        % (file_name, section, str(exc))\n                    )\n                section_def = SCHEMA.get(name)\n                canonical_section_name = name\n                break\n        if not section_def:\n            raise Exception(\n                \"manifest file %s contains unknown section '%s'\" % (file_name, section)\n            )\n    else:\n        canonical_section_name = section\n\n    allowed_fields = section_def.get(\"fields\")\n    if allowed_fields:\n        # pyre-ignore[6]: Expected `dict[str, str]` but got `object`.\n        validate_allowed_fields(file_name, section, config, allowed_fields)\n    elif not section_def.get(\"allow_values\", True):\n        validate_allow_values(file_name, section, config)\n    # pyre-fixme[61]: `canonical_section_name` is undefined, or not always defined.\n    return canonical_section_name\n\n\nclass ManifestParser:\n    def __init__(self, file_name: str, fp: str | typing.IO[str] | None = None) -> None:\n        # allow_no_value enables listing parameters in the\n        # autoconf.args section one per line\n        config = configparser.RawConfigParser(allow_no_value=True)\n        config.optionxform = str  # type: ignore[assignment]  # make it case sensitive\n        if fp is None:\n            with open(file_name, \"r\") as fp:\n                config.read_file(fp)\n        elif isinstance(fp, type(\"\")):\n            # For testing purposes, parse from a string (str\n            # or unicode)\n            config.read_file(io.StringIO(fp))\n        else:\n            config.read_file(fp)\n\n        # validate against the schema\n        seen_sections: set[str] = set()\n\n        for section in config.sections():\n            seen_sections.add(validate_section(file_name, section, config))\n\n        for section in SCHEMA.keys():\n            section_def = SCHEMA[section]\n            if (\n                not section_def.get(\"optional_section\", False)\n                and section not in seen_sections\n            ):\n                raise Exception(\n                    \"manifest file %s is missing required section %s\"\n                    % (file_name, section)\n                )\n\n        self._config: configparser.RawConfigParser = config\n        self.name: str = config.get(\"manifest\", \"name\")\n        self.fbsource_path: str | None = self.get(\"manifest\", \"fbsource_path\")\n        self.shipit_project: str | None = self.get(\"manifest\", \"shipit_project\")\n        self.shipit_fbcode_builder: str | None = self.get(\n            \"manifest\", \"shipit_fbcode_builder\"\n        )\n        self.resolved_system_packages: dict[str, str] = {}\n        self.shipit_strip_marker: str | None = self.get(\n            \"manifest\", \"shipit_strip_marker\", defval=\"@fb-only\"\n        )\n\n        if self.name != os.path.basename(file_name):\n            raise Exception(\n                \"filename of the manifest '%s' does not match the manifest name '%s'\"\n                % (file_name, self.name)\n            )\n\n        if \".\" in self.name:\n            raise Exception(\n                f\"manifest name ({self.name}) must not contain the '.' character (it is incompatible with github actions)\"\n            )\n\n    def get(\n        self,\n        section: str,\n        key: str,\n        defval: str | None = None,\n        ctx: ManifestContext | dict[str, str | None] | None = None,\n    ) -> str | None:\n        ctx = ctx or {}\n\n        for s in self._config.sections():\n            if s == section:\n                if self._config.has_option(s, key):\n                    return self._config.get(s, key)\n                return defval\n\n            if s.startswith(section + \".\"):\n                expr = parse_conditional_section_name(s, section)\n                # pyre-fixme[6]: For 1st argument expected `Dict[str,\n                #  Optional[str]]` but got `Union[Dict[str, Optional[str]],\n                #  ManifestContext]`.\n                if not expr.eval(ctx):\n                    continue\n\n                if self._config.has_option(s, key):\n                    return self._config.get(s, key)\n\n        return defval\n\n    def get_dependencies(self, ctx: ManifestContext) -> list[str]:\n        dep_list = list(self.get_section_as_dict(\"dependencies\", ctx).keys())\n        dep_list.sort()\n        builder = self.get(\"build\", \"builder\", ctx=ctx)\n        if builder in (\"cmake\", \"python-wheel\"):\n            dep_list.insert(0, \"cmake\")\n        elif builder == \"autoconf\" and self.name not in (\n            \"autoconf\",\n            \"libtool\",\n            \"automake\",\n        ):\n            # they need libtool and its deps (automake, autoconf) so add\n            # those as deps (but obviously not if we're building those\n            # projects themselves)\n            dep_list.insert(0, \"libtool\")\n\n        return dep_list\n\n    def get_section_as_args(\n        self,\n        section: str,\n        ctx: ManifestContext | dict[str, str | None] | None = None,\n    ) -> list[str]:\n        \"\"\"Intended for use with the make.[build_args/install_args] and\n        autoconf.args sections, this method collects the entries and returns an\n        array of strings.\n        If the manifest contains conditional sections, ctx is used to\n        evaluate the condition and merge in the values.\n        \"\"\"\n        args: list[str] = []\n        ctx = ctx or {}\n\n        for s in self._config.sections():\n            if s != section:\n                if not s.startswith(section + \".\"):\n                    continue\n                expr = parse_conditional_section_name(s, section)\n                # pyre-fixme[6]: For 1st argument expected `Dict[str,\n                #  Optional[str]]` but got `Union[Dict[str, Optional[str]],\n                #  ManifestContext]`.\n                if not expr.eval(ctx):\n                    continue\n            for field in self._config.options(s):\n                value = self._config.get(s, field)\n                if value is None:\n                    args.append(field)\n                else:\n                    args.append(\"%s=%s\" % (field, value))\n        return args\n\n    def get_section_as_ordered_pairs(\n        self,\n        section: str,\n        ctx: ManifestContext | dict[str, str | None] | None = None,\n    ) -> list[tuple[str, str | None]]:\n        \"\"\"Used for eg: shipit.pathmap which has strong\n        ordering requirements\"\"\"\n        res: list[tuple[str, str | None]] = []\n        ctx = ctx or {}\n\n        for s in self._config.sections():\n            if s != section:\n                if not s.startswith(section + \".\"):\n                    continue\n                expr = parse_conditional_section_name(s, section)\n                # pyre-fixme[6]: For 1st argument expected `Dict[str,\n                #  Optional[str]]` but got `Union[Dict[str, Optional[str]],\n                #  ManifestContext]`.\n                if not expr.eval(ctx):\n                    continue\n\n            for key in self._config.options(s):\n                value = self._config.get(s, key)\n                res.append((key, value))\n        return res\n\n    def get_section_as_dict(\n        self,\n        section: str,\n        ctx: ManifestContext | dict[str, str | None] | None,\n    ) -> dict[str, str | None]:\n        d: dict[str, str | None] = {}\n\n        for s in self._config.sections():\n            if s != section:\n                if not s.startswith(section + \".\"):\n                    continue\n                expr = parse_conditional_section_name(s, section)\n                # pyre-fixme[6]: For 1st argument expected `Dict[str,\n                #  Optional[str]]` but got `Union[None, Dict[str, Optional[str]],\n                #  ManifestContext]`.\n                if not expr.eval(ctx):\n                    continue\n            for field in self._config.options(s):\n                value = self._config.get(s, field)\n                d[field] = value\n        return d\n\n    def update_hash(self, hasher: hashlib._Hash, ctx: ManifestContext) -> None:\n        \"\"\"Compute a hash over the configuration for the given\n        context.  The goal is for the hash to change if the config\n        for that context changes, but not if a change is made to\n        the config only for a different platform than that expressed\n        by ctx.  The hash is intended to be used to help invalidate\n        a future cache for the third party build products.\n        The hasher argument is a hash object returned from hashlib.\"\"\"\n        for section in sorted(SCHEMA.keys()):\n            hasher.update(section.encode(\"utf-8\"))\n\n            # Note: at the time of writing, nothing in the implementation\n            # relies on keys in any config section being ordered.\n            # In theory we could have conflicting flags in different\n            # config sections and later flags override earlier flags.\n            # For the purposes of computing a hash we're not super\n            # concerned about this: manifest changes should be rare\n            # enough and we'd rather that this trigger an invalidation\n            # than strive for a cache hit at this time.\n            pairs = self.get_section_as_ordered_pairs(section, ctx)\n            pairs.sort(key=lambda pair: pair[0])\n            for key, value in pairs:\n                hasher.update(key.encode(\"utf-8\"))\n                if value is not None:\n                    hasher.update(value.encode(\"utf-8\"))\n\n    def is_first_party_project(self) -> bool:\n        \"\"\"returns true if this is an FB first-party project\"\"\"\n        return self.shipit_project is not None\n\n    def get_required_system_packages(\n        self, ctx: ManifestContext\n    ) -> dict[str, list[str]]:\n        \"\"\"Returns dictionary of packager system -> list of packages\"\"\"\n        return {\n            \"rpm\": self.get_section_as_args(\"rpms\", ctx),\n            \"deb\": self.get_section_as_args(\"debs\", ctx),\n            \"homebrew\": self.get_section_as_args(\"homebrew\", ctx),\n            \"pacman-package\": self.get_section_as_args(\"pps\", ctx),\n        }\n\n    def _is_satisfied_by_preinstalled_environment(self, ctx: ManifestContext) -> bool:\n        envs = self.get_section_as_args(\"preinstalled.env\", ctx)\n        if not envs:\n            return False\n        for key in envs:\n            val = os.environ.get(key, None)\n            print(\n                f\"Testing ENV[{key}]: {repr(val)}\",\n                file=sys.stderr,\n            )\n            if val is None:\n                return False\n            if len(val) == 0:\n                return False\n\n        return True\n\n    def get_repo_url(self, ctx: ManifestContext) -> str | None:\n        return self.get(\"git\", \"repo_url\", ctx=ctx)\n\n    def _create_fetcher(\n        self, build_options: BuildOptions, ctx: ManifestContext\n    ) -> Fetcher:\n        real_shipit_available = ShipitTransformerFetcher.available(build_options)\n        use_real_shipit = real_shipit_available and (\n            build_options.use_shipit\n            or self.get(\"manifest\", \"use_shipit\", defval=\"false\", ctx=ctx) == \"true\"\n        )\n        if (\n            not use_real_shipit\n            and self.fbsource_path\n            and build_options.fbsource_dir\n            and self.shipit_project\n        ):\n            return SimpleShipitTransformerFetcher(build_options, self, ctx)\n\n        if (\n            self.fbsource_path\n            and build_options.fbsource_dir\n            and self.shipit_project\n            and real_shipit_available\n        ):\n            # We can use the code from fbsource\n            return ShipitTransformerFetcher(\n                build_options,\n                self.shipit_project,\n                # pyre-fixme[6]: For 3rd argument expected `str` but got\n                #  `Optional[str]`.\n                self.get(\"manifest\", \"shipit_external_branch\"),\n            )\n\n        # If both of these are None, the package can only be coming from\n        # preinstalled toolchain or system packages\n        repo_url = self.get_repo_url(ctx)\n        url = self.get(\"download\", \"url\", ctx=ctx)\n\n        # Can we satisfy this dep with system packages?\n        if (repo_url is None and url is None) or build_options.allow_system_packages:\n            if self._is_satisfied_by_preinstalled_environment(ctx):\n                # pyre-fixme[7]: Expected `Fetcher` but got `PreinstalledNopFetcher`.\n                return PreinstalledNopFetcher()\n\n            if build_options.host_type.get_package_manager():\n                packages = self.get_required_system_packages(ctx)\n                package_fetcher = SystemPackageFetcher(build_options, packages)\n                if package_fetcher.packages_are_installed():\n                    # pyre-fixme[7]: Expected `Fetcher` but got `SystemPackageFetcher`.\n                    return package_fetcher\n\n        if repo_url:\n            rev = self.get(\"git\", \"rev\")\n            depth = self.get(\"git\", \"depth\")\n            branch = self.get(\"git\", \"branch\")\n            # pyre-fixme[6]: For 4th argument expected `str` but got `Optional[str]`.\n            # pyre-fixme[6]: For 5th argument expected `int` but got `Optional[str]`.\n            # pyre-fixme[6]: For 6th argument expected `str` but got `Optional[str]`.\n            return GitFetcher(build_options, self, repo_url, rev, depth, branch)\n\n        if url:\n            # We need to defer this import until now to avoid triggering\n            # a cycle when the facebook/__init__.py is loaded.\n            try:\n                from .facebook.lfs import LFSCachingArchiveFetcher\n\n                return LFSCachingArchiveFetcher(\n                    build_options,\n                    self,\n                    url,\n                    # pyre-fixme[6]: For 4th argument expected `str` but got\n                    #  `Optional[str]`.\n                    self.get(\"download\", \"sha256\", ctx=ctx),\n                )\n            except ImportError:\n                # This FB internal module isn't shippped to github,\n                # so just use its base class\n                return ArchiveFetcher(\n                    build_options,\n                    self,\n                    url,\n                    # pyre-fixme[6]: For 4th argument expected `str` but got\n                    #  `Optional[str]`.\n                    self.get(\"download\", \"sha256\", ctx=ctx),\n                )\n\n        raise KeyError(\n            f\"project {self.name} has no fetcher configuration or system packages matching {ctx} - have you run `getdeps.py install-system-deps --recursive`?\"\n        )\n\n    def create_fetcher(\n        self,\n        build_options: BuildOptions,\n        loader: ManifestLoader,\n        ctx: ManifestContext,\n    ) -> Fetcher:\n        fetcher = self._create_fetcher(build_options, ctx)\n        subprojects = self.get_section_as_ordered_pairs(\"subprojects\", ctx)\n        if subprojects:\n            subs: list[tuple[Fetcher, str | None]] = []\n            for project, subdir in subprojects:\n                submanifest = loader.load_manifest(project)\n                subfetcher = submanifest.create_fetcher(build_options, loader, ctx)\n                subs.append((subfetcher, subdir))\n            # pyre-fixme[6]: For 2nd argument expected `List[Tuple[Fetcher, str]]`\n            #  but got `List[Tuple[Fetcher, Optional[str]]]`.\n            return SubFetcher(fetcher, subs)\n        else:\n            return fetcher\n\n    def get_builder_name(self, ctx: ManifestContext) -> str:\n        builder = self.get(\"build\", \"builder\", ctx=ctx)\n        if not builder:\n            raise Exception(\"project %s has no builder for %r\" % (self.name, ctx))\n        return builder\n\n    def create_builder(  # noqa:C901\n        self,\n        build_options: BuildOptions,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        ctx: ManifestContext,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        final_install_prefix: str | None = None,\n        extra_cmake_defines: dict[str, str] | None = None,\n        cmake_targets: list[str] | None = None,\n        extra_b2_args: list[str] | None = None,\n    ) -> BuilderBase:\n        builder = self.get_builder_name(ctx)\n        build_in_src_dir = self.get(\"build\", \"build_in_src_dir\", \"false\", ctx=ctx)\n        if build_in_src_dir == \"true\":\n            # Some scripts don't work when they are configured and build in\n            # a different directory than source (or when the build directory\n            # is not a subdir of source).\n            build_dir = src_dir\n            subdir = self.get(\"build\", \"subdir\", None, ctx=ctx)\n            if subdir is not None:\n                build_dir = os.path.join(build_dir, subdir)\n            print(\"build_dir is %s\" % build_dir)  # just to quiet lint\n\n        if builder == \"make\" or builder == \"cmakebootstrap\":\n            build_args = self.get_section_as_args(\"make.build_args\", ctx)\n            install_args = self.get_section_as_args(\"make.install_args\", ctx)\n            test_args = self.get_section_as_args(\"make.test_args\", ctx)\n            if builder == \"cmakebootstrap\":\n                return CMakeBootStrapBuilder(\n                    loader,\n                    dep_manifests,\n                    build_options,\n                    ctx,\n                    self,\n                    src_dir,\n                    # pyre-fixme[6]: For 7th argument expected `str` but got `None`.\n                    None,\n                    inst_dir,\n                    build_args,\n                    install_args,\n                    test_args,\n                )\n            else:\n                return MakeBuilder(\n                    loader,\n                    dep_manifests,\n                    build_options,\n                    ctx,\n                    self,\n                    src_dir,\n                    # pyre-fixme[6]: For 7th argument expected `str` but got `None`.\n                    None,\n                    inst_dir,\n                    build_args,\n                    install_args,\n                    test_args,\n                )\n\n        if builder == \"autoconf\":\n            args = self.get_section_as_args(\"autoconf.args\", ctx)\n            conf_env_args: dict[str, list[str]] = {}\n            ldflags_cmd = self.get_section_as_args(\"autoconf.envcmd.LDFLAGS\", ctx)\n            if ldflags_cmd:\n                conf_env_args[\"LDFLAGS\"] = ldflags_cmd\n            return AutoconfBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n                args,\n                conf_env_args,\n            )\n\n        if builder == \"boost\":\n            args = self.get_section_as_args(\"b2.args\", ctx)\n            if extra_b2_args is not None:\n                args += extra_b2_args\n            return Boost(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n                args,\n            )\n\n        if builder == \"cmake\":\n            defines = self.get_section_as_dict(\"cmake.defines\", ctx)\n            return CMakeBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n                # pyre-fixme[6]: For 9th argument expected `Optional[Dict[str,\n                #  str]]` but got `Dict[str, Optional[str]]`.\n                defines,\n                final_install_prefix,\n                extra_cmake_defines,\n                cmake_targets,\n            )\n\n        if builder == \"python-wheel\":\n            return PythonWheelBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        if builder == \"sqlite\":\n            return SqliteBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        if builder == \"ninja_bootstrap\":\n            return NinjaBootstrap(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                build_dir,\n                src_dir,\n                inst_dir,\n            )\n\n        if builder == \"nop\":\n            return NopBuilder(\n                loader, dep_manifests, build_options, ctx, self, src_dir, inst_dir\n            )\n\n        if builder == \"openssl\":\n            return OpenSSLBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                build_dir,\n                src_dir,\n                inst_dir,\n            )\n\n        if builder == \"iproute2\":\n            return Iproute2Builder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        if builder == \"meson\":\n            return MesonBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        if builder == \"setup-py\":\n            return SetupPyBuilder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                self,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        if builder == \"cargo\":\n            return self.create_cargo_builder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n\n        raise KeyError(\"project %s has no known builder\" % (self.name))\n\n    def create_prepare_builders(\n        self,\n        build_options: BuildOptions,\n        ctx: ManifestContext,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n    ) -> list[BuilderBase]:\n        \"\"\"Create builders that have a prepare step run, e.g. to write config files\"\"\"\n        prepare_builders: list[BuilderBase] = []\n        builder = self.get_builder_name(ctx)\n        cargo = self.get_section_as_dict(\"cargo\", ctx)\n        if not builder == \"cargo\" and cargo:\n            cargo_builder = self.create_cargo_builder(\n                loader,\n                dep_manifests,\n                build_options,\n                ctx,\n                src_dir,\n                build_dir,\n                inst_dir,\n            )\n            prepare_builders.append(cargo_builder)\n        return prepare_builders\n\n    def create_cargo_builder(\n        self,\n        loader: ManifestLoader,\n        dep_manifests: list[ManifestParser],\n        build_options: BuildOptions,\n        ctx: ManifestContext,\n        src_dir: str,\n        build_dir: str,\n        inst_dir: str,\n    ) -> CargoBuilder:\n        # pyre-fixme[6]: For 3rd argument expected `Optional[str]` but got `bool`.\n        build_doc = self.get(\"cargo\", \"build_doc\", False, ctx)\n        workspace_dir = self.get(\"cargo\", \"workspace_dir\", None, ctx)\n        manifests_to_build = self.get(\"cargo\", \"manifests_to_build\", None, ctx)\n        cargo_config_file = self.get(\"cargo\", \"cargo_config_file\", None, ctx)\n        return CargoBuilder(\n            loader,\n            dep_manifests,\n            build_options,\n            ctx,\n            self,\n            src_dir,\n            build_dir,\n            inst_dir,\n            # pyre-fixme[6]: For 9th argument expected `bool` but got `Optional[str]`.\n            build_doc,\n            workspace_dir,\n            manifests_to_build,\n            cargo_config_file,\n        )\n\n\nclass ManifestContext:\n    \"\"\"ProjectContext contains a dictionary of values to use when evaluating boolean\n    expressions in a project manifest.\n\n    This object should be passed as the `ctx` parameter in ManifestParser.get() calls.\n    \"\"\"\n\n    ALLOWED_VARIABLES: set[str] = {\n        \"os\",\n        \"distro\",\n        \"distro_vers\",\n        \"fb\",\n        \"fbsource\",\n        \"test\",\n        \"shared_libs\",\n    }\n\n    def __init__(self, ctx_dict: dict[str, str | None]) -> None:\n        assert set(ctx_dict.keys()) == self.ALLOWED_VARIABLES\n        self.ctx_dict: dict[str, str | None] = ctx_dict\n\n    def get(self, key: str) -> str | None:\n        return self.ctx_dict[key]\n\n    def set(self, key: str, value: str | None) -> None:\n        assert key in self.ALLOWED_VARIABLES\n        self.ctx_dict[key] = value\n\n    def copy(self) -> ManifestContext:\n        return ManifestContext(dict(self.ctx_dict))\n\n    def __str__(self) -> str:\n        s = \", \".join(\n            \"%s=%s\" % (key, value) for key, value in sorted(self.ctx_dict.items())\n        )\n        return \"{\" + s + \"}\"\n\n\nclass ContextGenerator:\n    \"\"\"ContextGenerator allows creating ManifestContext objects on a per-project basis.\n    This allows us to evaluate different projects with slightly different contexts.\n\n    For instance, this can be used to only enable tests for some projects.\"\"\"\n\n    def __init__(self, default_ctx: dict[str, str | None]) -> None:\n        self.default_ctx: ManifestContext = ManifestContext(default_ctx)\n        self.ctx_by_project: dict[str, ManifestContext] = {}\n\n    def set_value_for_project(\n        self, project_name: str, key: str, value: str | None\n    ) -> None:\n        project_ctx = self.ctx_by_project.get(project_name)\n        if project_ctx is None:\n            project_ctx = self.default_ctx.copy()\n            self.ctx_by_project[project_name] = project_ctx\n        project_ctx.set(key, value)\n\n    def set_value_for_all_projects(self, key: str, value: str | None) -> None:\n        self.default_ctx.set(key, value)\n        for ctx in self.ctx_by_project.values():\n            ctx.set(key, value)\n\n    def get_context(self, project_name: str) -> ManifestContext:\n        return self.ctx_by_project.get(project_name, self.default_ctx)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/platform.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport os\nimport platform\nimport re\nimport shlex\nimport sys\n\n\ndef is_windows() -> bool:\n    \"\"\"Returns true if the system we are currently running on\n    is a Windows system\"\"\"\n    return sys.platform.startswith(\"win\")\n\n\ndef get_linux_type() -> tuple[str | None, str | None, str | None]:\n    try:\n        with open(\"/etc/os-release\") as f:\n            data = f.read()\n    except EnvironmentError:\n        return (None, None, None)\n\n    os_vars: dict[str, str] = {}\n    for line in data.splitlines():\n        parts = line.split(\"=\", 1)\n        if len(parts) != 2:\n            continue\n        key = parts[0].strip()\n        value_parts = shlex.split(parts[1].strip())\n        if not value_parts:\n            value = \"\"\n        else:\n            value = value_parts[0]\n        os_vars[key] = value\n\n    name = os_vars.get(\"NAME\")\n    if name:\n        name = name.lower()\n        name = re.sub(\"linux\", \"\", name)\n        name = name.strip().replace(\" \", \"_\")\n\n    version_id = os_vars.get(\"VERSION_ID\")\n    if version_id:\n        version_id = version_id.lower()\n\n    return \"linux\", name, version_id\n\n\n# Ideally we'd use a common library like `psutil` to read system information,\n# but getdeps can't take third-party dependencies.\n\n\ndef _get_available_ram_linux() -> int:\n    # TODO: Ideally, this function would inspect the current cgroup for any\n    # limits, rather than solely relying on system RAM.\n\n    meminfo_path = \"/proc/meminfo\"\n    try:\n        with open(meminfo_path) as f:\n            for line in f:\n                try:\n                    key, value = line.split(\":\", 1)\n                except ValueError:\n                    continue\n                suffix = \" kB\\n\"\n                if key == \"MemAvailable\" and value.endswith(suffix):\n                    value = value[: -len(suffix)]\n                    try:\n                        return int(value) // 1024\n                    except ValueError:\n                        continue\n    except OSError:\n        print(\"error opening {}\".format(meminfo_path), end=\"\", file=sys.stderr)\n    else:\n        print(\n            \"{} had no valid MemAvailable\".format(meminfo_path), end=\"\", file=sys.stderr\n        )\n\n    guess = 8\n    print(\", guessing {} GiB\".format(guess), file=sys.stderr)\n    return guess * 1024\n\n\ndef _get_available_ram_macos() -> int:\n    import ctypes.util\n\n    libc = ctypes.CDLL(ctypes.util.find_library(\"libc\"), use_errno=True)\n    sysctlbyname = libc.sysctlbyname\n    sysctlbyname.restype = ctypes.c_int\n    sysctlbyname.argtypes = [\n        ctypes.c_char_p,\n        ctypes.c_void_p,\n        ctypes.POINTER(ctypes.c_size_t),\n        ctypes.c_void_p,\n        ctypes.c_size_t,\n    ]\n    # TODO: There may be some way to approximate an availability\n    # metric, but just use total RAM for now.\n    memsize = ctypes.c_int64()\n    memsizesize = ctypes.c_size_t(8)\n    res = sysctlbyname(\n        b\"hw.memsize\", ctypes.byref(memsize), ctypes.byref(memsizesize), None, 0\n    )\n    if res != 0:\n        raise NotImplementedError(\n            f\"failed to retrieve hw.memsize sysctl: {ctypes.get_errno()}\"\n        )\n    return memsize.value // (1024 * 1024)\n\n\ndef _get_available_ram_windows() -> int:\n    import ctypes\n\n    DWORD = ctypes.c_uint32\n    QWORD = ctypes.c_uint64\n\n    class MEMORYSTATUSEX(ctypes.Structure):\n        _fields_ = [\n            (\"dwLength\", DWORD),\n            (\"dwMemoryLoad\", DWORD),\n            (\"ullTotalPhys\", QWORD),\n            (\"ullAvailPhys\", QWORD),\n            (\"ullTotalPageFile\", QWORD),\n            (\"ullAvailPageFile\", QWORD),\n            (\"ullTotalVirtual\", QWORD),\n            (\"ullAvailVirtual\", QWORD),\n            (\"ullExtendedVirtual\", QWORD),\n        ]\n\n    ms = MEMORYSTATUSEX()\n    ms.dwLength = ctypes.sizeof(ms)\n    # pyre-ignore[16]\n    res = ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(ms))\n    if res == 0:\n        raise NotImplementedError(\"error calling GlobalMemoryStatusEx\")\n\n    # This is fuzzy, but AvailPhys is too conservative, and AvailTotal is too\n    # aggressive, so average the two. It's okay for builds to use some swap.\n    return (ms.ullAvailPhys + ms.ullTotalPhys) // (2 * 1024 * 1024)\n\n\ndef _get_available_ram_freebsd() -> int:\n    import ctypes.util\n\n    libc = ctypes.CDLL(ctypes.util.find_library(\"libc\"), use_errno=True)\n    sysctlbyname = libc.sysctlbyname\n    sysctlbyname.restype = ctypes.c_int\n    sysctlbyname.argtypes = [\n        ctypes.c_char_p,\n        ctypes.c_void_p,\n        ctypes.POINTER(ctypes.c_size_t),\n        ctypes.c_void_p,\n        ctypes.c_size_t,\n    ]\n    # hw.usermem is pretty close to what we want.\n    memsize = ctypes.c_int64()\n    memsizesize = ctypes.c_size_t(8)\n    res = sysctlbyname(\n        b\"hw.usermem\", ctypes.byref(memsize), ctypes.byref(memsizesize), None, 0\n    )\n    if res != 0:\n        raise NotImplementedError(\n            f\"failed to retrieve hw.memsize sysctl: {ctypes.get_errno()}\"\n        )\n    return memsize.value // (1024 * 1024)\n\n\ndef get_available_ram() -> int:\n    \"\"\"\n    Returns a platform-appropriate available RAM metric in MiB.\n    \"\"\"\n    if sys.platform == \"linux\":\n        return _get_available_ram_linux()\n    elif sys.platform == \"darwin\":\n        return _get_available_ram_macos()\n    elif sys.platform == \"win32\":\n        return _get_available_ram_windows()\n    elif sys.platform.startswith(\"freebsd\"):\n        return _get_available_ram_freebsd()\n    else:\n        raise NotImplementedError(\n            f\"platform {sys.platform} does not have an implementation of get_available_ram\"\n        )\n\n\ndef is_current_host_arm() -> bool:\n    if sys.platform.startswith(\"darwin\"):\n        # platform.machine() can be fooled by rosetta for python < 3.9.2\n        return \"ARM64\" in os.uname().version\n    else:\n        machine = platform.machine().lower()\n        return \"arm\" in machine or \"aarch\" in machine\n\n\nclass HostType:\n    def __init__(\n        self,\n        ostype: str | None = None,\n        distro: str | None = None,\n        distrovers: str | None = None,\n    ) -> None:\n        # Maybe we should allow callers to indicate whether this machine uses\n        # an ARM architecture, but we need to change HostType serialization\n        # and deserialization in that case and hunt down anywhere that is\n        # persisting that serialized data.\n        isarm = False\n\n        if ostype is None:\n            distro = None\n            distrovers = None\n            if sys.platform.startswith(\"linux\"):\n                ostype, distro, distrovers = get_linux_type()\n            elif sys.platform.startswith(\"darwin\"):\n                ostype = \"darwin\"\n            elif is_windows():\n                ostype = \"windows\"\n                distrovers = str(sys.getwindowsversion().major)\n            elif sys.platform.startswith(\"freebsd\"):\n                ostype = \"freebsd\"\n            else:\n                ostype = sys.platform\n\n            isarm = is_current_host_arm()\n\n        # The operating system type\n        self.ostype: str | None = ostype\n        # The distribution, if applicable\n        self.distro: str | None = distro\n        # The OS/distro version if known\n        self.distrovers: str | None = distrovers\n        # Does the CPU use an ARM architecture? ARM includes Apple Silicon\n        # Macs as well as other ARM systems that might be running Linux or\n        # something.\n        self.isarm: bool = isarm\n\n    def is_windows(self) -> bool:\n        return self.ostype == \"windows\"\n\n    # is_arm is kinda half implemented at the moment. This method is only\n    # intended to be used when HostType represents information about the\n    # current machine we are running on.\n    # When HostType is being used to enumerate platform types (represent\n    # information about machine types that we may or may not be running on)\n    # the result could be nonsense (under the current implementation its always\n    # false.)\n    def is_arm(self) -> bool:\n        return self.isarm\n\n    def is_darwin(self) -> bool:\n        return self.ostype == \"darwin\"\n\n    def is_linux(self) -> bool:\n        return self.ostype == \"linux\"\n\n    def is_freebsd(self) -> bool:\n        return self.ostype == \"freebsd\"\n\n    def as_tuple_string(self) -> str:\n        return \"%s-%s-%s\" % (\n            self.ostype,\n            self.distro or \"none\",\n            self.distrovers or \"none\",\n        )\n\n    def get_package_manager(self) -> str | None:\n        if not self.is_linux() and not self.is_darwin():\n            return None\n        if self.is_darwin():\n            return \"homebrew\"\n        if self.distro in (\"fedora\", \"centos\", \"centos_stream\", \"rocky\"):\n            return \"rpm\"\n        if self.distro is not None and self.distro.startswith(\n            (\"debian\", \"ubuntu\", \"pop!_os\", \"mint\")\n        ):\n            return \"deb\"\n        if self.distro == \"arch\":\n            return \"pacman-package\"\n        return None\n\n    @staticmethod\n    def from_tuple_string(s: str) -> HostType:\n        ostype, distro, distrovers = s.split(\"-\")\n        return HostType(ostype=ostype, distro=distro, distrovers=distrovers)\n\n    def __eq__(self, b: object) -> bool:\n        if not isinstance(b, HostType):\n            return False\n        return (\n            self.ostype == b.ostype\n            and self.distro == b.distro\n            and self.distrovers == b.distrovers\n        )\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/py_wheel_builder.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\nfrom __future__ import annotations\n\nimport codecs\nimport collections\nimport email\nimport email.message\nimport os\nimport re\nimport stat\n\nfrom .builder import BuilderBase, CMakeBuilder\n\n\nWheelNameInfo = collections.namedtuple(\n    \"WheelNameInfo\", (\"distribution\", \"version\", \"build\", \"python\", \"abi\", \"platform\")\n)\n\nCMAKE_HEADER = \"\"\"\ncmake_minimum_required(VERSION 3.8)\n\nproject(\"{manifest_name}\" LANGUAGES C)\n\nset(CMAKE_MODULE_PATH\n  \"{cmake_dir}\"\n  ${{CMAKE_MODULE_PATH}}\n)\ninclude(FBPythonBinary)\n\nset(CMAKE_INSTALL_DIR lib/cmake/{manifest_name} CACHE STRING\n    \"The subdirectory where CMake package config files should be installed\")\n\"\"\"\n\nCMAKE_FOOTER = \"\"\"\ninstall_fb_python_library({lib_name} EXPORT all)\ninstall(\n  EXPORT all\n  FILE {manifest_name}-targets.cmake\n  NAMESPACE {namespace}::\n  DESTINATION ${{CMAKE_INSTALL_DIR}}\n)\n\ninclude(CMakePackageConfigHelpers)\nconfigure_package_config_file(\n  ${{CMAKE_BINARY_DIR}}/{manifest_name}-config.cmake.in\n  {manifest_name}-config.cmake\n  INSTALL_DESTINATION ${{CMAKE_INSTALL_DIR}}\n  PATH_VARS\n    CMAKE_INSTALL_DIR\n)\ninstall(\n  FILES ${{CMAKE_CURRENT_BINARY_DIR}}/{manifest_name}-config.cmake\n  DESTINATION ${{CMAKE_INSTALL_DIR}}\n)\n\"\"\"\n\nCMAKE_CONFIG_FILE = \"\"\"\n@PACKAGE_INIT@\n\ninclude(CMakeFindDependencyMacro)\n\nset_and_check({upper_name}_CMAKE_DIR \"@PACKAGE_CMAKE_INSTALL_DIR@\")\n\nif (NOT TARGET {namespace}::{lib_name})\n  include(\"${{{upper_name}_CMAKE_DIR}}/{manifest_name}-targets.cmake\")\nendif()\n\nset({upper_name}_LIBRARIES {namespace}::{lib_name})\n\n{find_dependency_lines}\n\nif (NOT {manifest_name}_FIND_QUIETLY)\n  message(STATUS \"Found {manifest_name}: ${{PACKAGE_PREFIX_DIR}}\")\nendif()\n\"\"\"\n\n\n# Note: for now we are manually manipulating the wheel packet contents.\n# The wheel format is documented here:\n# https://www.python.org/dev/peps/pep-0491/#file-format\n#\n# We currently aren't particularly smart about correctly handling the full wheel\n# functionality, but this is good enough to handle simple pure-python wheels,\n# which is the main thing we care about right now.\n#\n# We could potentially use pip to install the wheel to a temporary location and\n# then copy its \"installed\" files, but this has its own set of complications.\n# This would require pip to already be installed and available, and we would\n# need to correctly find the right version of pip or pip3 to use.\n# If we did ever want to go down that path, we would probably want to use\n# something like the following pip3 command:\n#   pip3 --isolated install --no-cache-dir --no-index --system \\\n#       --target <install_dir> <wheel_file>\nclass PythonWheelBuilder(BuilderBase):\n    \"\"\"This Builder can take Python wheel archives and install them as python libraries\n    that can be used by add_fb_python_library()/add_fb_python_executable() CMake rules.\n    \"\"\"\n\n    # pyre-fixme[13]: Attribute `dist_info_dir` is never initialized.\n    dist_info_dir: str\n    # pyre-fixme[13]: Attribute `template_format_dict` is never initialized.\n    template_format_dict: dict[str, str]\n\n    def _build(self, reconfigure: bool) -> None:\n        # When we are invoked, self.src_dir contains the unpacked wheel contents.\n        #\n        # Since a wheel file is just a zip file, the Fetcher code recognizes it as such\n        # and goes ahead and unpacks it.  (We could disable that Fetcher behavior in the\n        # future if we ever wanted to, say if we wanted to call pip here.)\n        wheel_name = self._parse_wheel_name()\n        name_version_prefix = \"-\".join((wheel_name.distribution, wheel_name.version))\n        dist_info_name = name_version_prefix + \".dist-info\"\n        data_dir_name = name_version_prefix + \".data\"\n        self.dist_info_dir = os.path.join(self.src_dir, dist_info_name)\n        wheel_metadata = self._read_wheel_metadata(wheel_name)\n\n        # Check that we can understand the wheel version.\n        # We don't really care about wheel_metadata[\"Root-Is-Purelib\"] since\n        # we are generating our own standalone python archives rather than installing\n        # into site-packages.\n        version = wheel_metadata[\"Wheel-Version\"]\n        if not version.startswith(\"1.\"):\n            raise Exception(\"unsupported wheel version %s\" % (version,))\n\n        # Add a find_dependency() call for each of our dependencies.\n        # The dependencies are also listed in the wheel METADATA file, but it is simpler\n        # to pull this directly from the getdeps manifest.\n        dep_list = sorted(\n            self.manifest.get_section_as_dict(\"dependencies\", self.ctx).keys()\n        )\n        find_dependency_lines = [\"find_dependency({})\".format(dep) for dep in dep_list]\n\n        getdeps_cmake_dir = os.path.join(\n            os.path.dirname(os.path.dirname(__file__)), \"CMake\"\n        )\n        self.template_format_dict = {\n            # Note that CMake files always uses forward slash separators in path names,\n            # even on Windows.  Therefore replace path separators here.\n            \"cmake_dir\": _to_cmake_path(getdeps_cmake_dir),\n            \"lib_name\": self.manifest.name,\n            \"manifest_name\": self.manifest.name,\n            \"namespace\": self.manifest.name,\n            \"upper_name\": self.manifest.name.upper().replace(\"-\", \"_\"),\n            \"find_dependency_lines\": \"\\n\".join(find_dependency_lines),\n        }\n\n        # Find sources from the root directory\n        path_mapping: dict[str, str] = {}\n        for entry in os.listdir(self.src_dir):\n            if entry == data_dir_name:\n                continue\n            self._add_sources(path_mapping, os.path.join(self.src_dir, entry), entry)\n\n        # Files under the .data directory also need to be installed in the correct\n        # locations\n        if os.path.exists(data_dir_name):\n            # TODO: process the subdirectories of data_dir_name\n            # This isn't implemented yet since for now we have only needed dependencies\n            # on some simple pure Python wheels, so I haven't tested against wheels with\n            # additional files in the .data directory.\n            raise Exception(\n                \"handling of the subdirectories inside %s is not implemented yet\"\n                % data_dir_name\n            )\n\n        # Emit CMake files\n        self._write_cmakelists(path_mapping, dep_list)\n        self._write_cmake_config_template()\n\n        # Run the build\n        self._run_cmake_build(reconfigure)\n\n    def _run_cmake_build(self, reconfigure: bool) -> None:\n        cmake_builder = CMakeBuilder(\n            loader=self.loader,\n            dep_manifests=self.dep_manifests,\n            build_opts=self.build_opts,\n            ctx=self.ctx,\n            manifest=self.manifest,\n            # Note that we intentionally supply src_dir=build_dir,\n            # since we wrote out our generated CMakeLists.txt in the build directory\n            src_dir=self.build_dir,\n            build_dir=self.build_dir,\n            inst_dir=self.inst_dir,\n            defines={},\n            final_install_prefix=None,\n        )\n        cmake_builder.build(reconfigure=reconfigure)\n\n    def _write_cmakelists(\n        self, path_mapping: dict[str, str], dependencies: list[str]\n    ) -> None:\n        cmake_path = os.path.join(self.build_dir, \"CMakeLists.txt\")\n        with open(cmake_path, \"w\") as f:\n            f.write(CMAKE_HEADER.format(**self.template_format_dict))\n            for dep in dependencies:\n                f.write(\"find_package({0} REQUIRED)\\n\".format(dep))\n\n            f.write(\n                \"add_fb_python_library({lib_name}\\n\".format(**self.template_format_dict)\n            )\n            f.write('  BASE_DIR \"%s\"\\n' % _to_cmake_path(self.src_dir))\n            f.write(\"  SOURCES\\n\")\n            for src_path, install_path in path_mapping.items():\n                f.write(\n                    '    \"%s=%s\"\\n'\n                    % (_to_cmake_path(src_path), _to_cmake_path(install_path))\n                )\n            if dependencies:\n                f.write(\"  DEPENDS\\n\")\n                for dep in dependencies:\n                    f.write('    \"{0}::{0}\"\\n'.format(dep))\n            f.write(\")\\n\")\n\n            f.write(CMAKE_FOOTER.format(**self.template_format_dict))\n\n    def _write_cmake_config_template(self) -> None:\n        config_path_name = self.manifest.name + \"-config.cmake.in\"\n        output_path = os.path.join(self.build_dir, config_path_name)\n\n        with open(output_path, \"w\") as f:\n            f.write(CMAKE_CONFIG_FILE.format(**self.template_format_dict))\n\n    def _add_sources(\n        self, path_mapping: dict[str, str], src_path: str, install_path: str\n    ) -> None:\n        s = os.lstat(src_path)\n        if not stat.S_ISDIR(s.st_mode):\n            path_mapping[src_path] = install_path\n            return\n\n        for entry in os.listdir(src_path):\n            self._add_sources(\n                path_mapping,\n                os.path.join(src_path, entry),\n                os.path.join(install_path, entry),\n            )\n\n    def _parse_wheel_name(self) -> WheelNameInfo:\n        # The ArchiveFetcher prepends \"manifest_name-\", so strip that off first.\n        wheel_name = os.path.basename(self.src_dir)\n        prefix = self.manifest.name + \"-\"\n        if not wheel_name.startswith(prefix):\n            raise Exception(\n                \"expected wheel source directory to be of the form %s-NAME.whl\"\n                % (prefix,)\n            )\n        wheel_name = wheel_name[len(prefix) :]\n\n        wheel_name_re = re.compile(\n            r\"(?P<distribution>[^-]+)\"\n            r\"-(?P<version>\\d+[^-]*)\"\n            r\"(-(?P<build>\\d+[^-]*))?\"\n            r\"-(?P<python>\\w+\\d+(\\.\\w+\\d+)*)\"\n            r\"-(?P<abi>\\w+)\"\n            r\"-(?P<platform>\\w+(\\.\\w+)*)\"\n            r\"\\.whl\"\n        )\n        match = wheel_name_re.match(wheel_name)\n        if not match:\n            raise Exception(\n                \"bad python wheel name %s: expected to have the form \"\n                \"DISTRIBUTION-VERSION-[-BUILD]-PYTAG-ABI-PLATFORM\"\n            )\n\n        return WheelNameInfo(\n            distribution=match.group(\"distribution\"),\n            version=match.group(\"version\"),\n            build=match.group(\"build\"),\n            python=match.group(\"python\"),\n            abi=match.group(\"abi\"),\n            platform=match.group(\"platform\"),\n        )\n\n    # pyre-fixme[24]: Generic type `email.message.Message` expects 2 type parameters.\n    def _read_wheel_metadata(self, wheel_name: WheelNameInfo) -> email.message.Message:\n        metadata_path = os.path.join(self.dist_info_dir, \"WHEEL\")\n        with codecs.open(metadata_path, \"r\", encoding=\"utf-8\") as f:\n            return email.message_from_file(f)\n\n\ndef _to_cmake_path(path: str) -> str:\n    # CMake always uses forward slashes to separate paths in CMakeLists.txt files,\n    # even on Windows.  It treats backslashes as character escapes, so using\n    # backslashes in the path will cause problems.  Therefore replace all path\n    # separators with forward slashes to make sure the paths are correct on Windows.\n    # e.g. \"C:\\foo\\bar.txt\" becomes \"C:/foo/bar.txt\"\n    return path.replace(os.path.sep, \"/\")\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/runcmd.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport os\nimport select\nimport subprocess\nimport sys\nfrom collections.abc import Callable\nfrom shlex import quote as shellquote\n\nfrom .envfuncs import Env\nfrom .platform import is_windows\n\n\nclass RunCommandError(Exception):\n    pass\n\n\ndef make_memory_limit_preexec_fn(\n    job_weight_mib: int,\n) -> Callable[[], None] | None:\n    \"\"\"Create a preexec_fn that sets a per-process virtual memory limit.\n\n    When getdeps spawns build commands (cmake -> ninja -> N compiler processes),\n    the parallelism is computed from available RAM divided by job_weight_mib.\n    However, there is no enforcement of that budget: if a compiler or linker\n    process exceeds its expected memory usage, the system can run out of RAM\n    and the Linux OOM killer may terminate arbitrary processes — including the\n    user's shell or terminal.\n\n    This function returns a callable suitable for subprocess.Popen's preexec_fn\n    parameter. It runs in each child process after fork() but before exec(),\n    setting RLIMIT_AS (virtual address space limit) so that a runaway process\n    gets a failed allocation (std::bad_alloc / ENOMEM) instead of triggering\n    the OOM killer. The limit is inherited by all descendant processes (ninja,\n    compiler invocations, etc.).\n\n    The per-process limit is set to job_weight_mib * 10. The 10x multiplier\n    accounts for the fact that RLIMIT_AS caps virtual address space, which is\n    typically 2-4x larger than resident (physical) memory for C++ compilers\n    due to memory-mapped files, shared libraries, and address space reservations\n    that don't consume physical RAM. The multiplier is intentionally generous:\n    the goal is a safety net that catches genuine runaways before the OOM killer\n    fires, not a tight per-job budget.\n\n    Only applies on Linux, where the OOM killer is the problem. Returns None\n    on other platforms.\n    \"\"\"\n    if sys.platform != \"linux\":\n        return None\n\n    # Each job is budgeted job_weight_mib of physical RAM. Virtual address\n    # space is typically 2-4x RSS. Use 10x as a generous safety net: tight\n    # enough to stop a runaway process before the OOM killer fires, but loose\n    # enough to avoid false positives from normal virtual memory overhead.\n    limit_bytes: int = job_weight_mib * 10 * 1024 * 1024\n\n    def _set_memory_limit() -> None:\n        import resource\n\n        resource.setrlimit(resource.RLIMIT_AS, (limit_bytes, limit_bytes))\n\n    return _set_memory_limit\n\n\ndef _print_env_diff(env: Env, log_fn: Callable[[str], None]) -> None:\n    current_keys = set(os.environ.keys())\n    wanted_env = set(env.keys())\n\n    unset_keys = current_keys.difference(wanted_env)\n    for k in sorted(unset_keys):\n        log_fn(\"+ unset %s\\n\" % k)\n\n    added_keys = wanted_env.difference(current_keys)\n    for k in wanted_env.intersection(current_keys):\n        if os.environ[k] != env[k]:\n            added_keys.add(k)\n\n    for k in sorted(added_keys):\n        if (\"PATH\" in k) and (os.pathsep in env[k]):\n            log_fn(\"+ %s=\\\\\\n\" % k)\n            for elem in env[k].split(os.pathsep):\n                log_fn(\"+      %s%s\\\\\\n\" % (shellquote(elem), os.pathsep))\n        else:\n            log_fn(\"+ %s=%s \\\\\\n\" % (k, shellquote(env[k])))\n\n\ndef check_cmd(\n    cmd: list[str],\n    env: Env | None = None,\n    cwd: str | None = None,\n    allow_fail: bool = False,\n    log_file: str | None = None,\n) -> None:\n    \"\"\"Run the command and abort on failure\"\"\"\n    rc = run_cmd(cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_file=log_file)\n    if rc != 0:\n        raise RuntimeError(f\"Failure exit code {rc} for command {cmd}\")\n\n\ndef run_cmd(\n    cmd: list[str],\n    env: Env | None = None,\n    cwd: str | None = None,\n    allow_fail: bool = False,\n    log_file: str | None = None,\n    preexec_fn: Callable[[], None] | None = None,\n) -> int:\n    def log_to_stdout(msg: str) -> None:\n        sys.stdout.buffer.write(msg.encode(errors=\"surrogateescape\"))\n\n    if log_file is not None:\n        with open(log_file, \"a\", encoding=\"utf-8\", errors=\"surrogateescape\") as log:\n\n            # pyre-fixme[53]: Captured variable `log` is not annotated.\n            def log_function(msg: str) -> None:\n                log.write(msg)\n                log_to_stdout(msg)\n\n            return _run_cmd(\n                cmd,\n                env=env,\n                cwd=cwd,\n                allow_fail=allow_fail,\n                log_fn=log_function,\n                preexec_fn=preexec_fn,\n            )\n    else:\n        return _run_cmd(\n            cmd,\n            env=env,\n            cwd=cwd,\n            allow_fail=allow_fail,\n            log_fn=log_to_stdout,\n            preexec_fn=preexec_fn,\n        )\n\n\ndef _run_cmd(\n    cmd: list[str],\n    env: Env | None,\n    cwd: str | None,\n    allow_fail: bool,\n    log_fn: Callable[[str], None],\n    preexec_fn: Callable[[], None] | None = None,\n) -> int:\n    log_fn(\"---\\n\")\n    try:\n        cmd_str = \" \\\\\\n+      \".join(shellquote(arg) for arg in cmd)\n    except TypeError:\n        # eg: one of the elements is None\n        raise RunCommandError(\"problem quoting cmd: %r\" % cmd)\n\n    if env:\n        assert isinstance(env, Env)\n        _print_env_diff(env, log_fn)\n\n        # Convert from our Env type to a regular dict.\n        # This is needed because python3 looks up b'PATH' and 'PATH'\n        # and emits an error if both are present.  In our Env type\n        # we'll return the same value for both requests, but we don't\n        # have duplicate potentially conflicting values which is the\n        # spirit of the check.\n        env_dict: dict[str, str] | None = dict(env.items())\n    else:\n        env_dict = None\n\n    if cwd:\n        log_fn(\"+ cd %s && \\\\\\n\" % shellquote(cwd))\n        # Our long path escape sequence may confuse cmd.exe, so if the cwd\n        # is short enough, strip that off.\n        if is_windows() and (len(cwd) < 250) and cwd.startswith(\"\\\\\\\\?\\\\\"):\n            cwd = cwd[4:]\n\n    log_fn(\"+ %s\\n\" % cmd_str)\n\n    isinteractive = os.isatty(sys.stdout.fileno())\n    if isinteractive:\n        stdout = None\n        sys.stdout.buffer.flush()\n    else:\n        stdout = subprocess.PIPE\n\n    try:\n        p = subprocess.Popen(\n            cmd,\n            env=env_dict,\n            cwd=cwd,\n            stdout=stdout,\n            stderr=subprocess.STDOUT,\n            preexec_fn=preexec_fn,\n        )\n    except (TypeError, ValueError, OSError) as exc:\n        log_fn(\"error running `%s`: %s\" % (cmd_str, exc))\n        raise RunCommandError(\n            \"%s while running `%s` with env=%r\\nos.environ=%r\"\n            % (str(exc), cmd_str, env_dict, os.environ)\n        )\n\n    if not isinteractive:\n        _pipe_output(p, log_fn)\n\n    p.wait()\n    if p.returncode != 0 and not allow_fail:\n        raise subprocess.CalledProcessError(p.returncode, cmd)\n\n    return p.returncode\n\n\nif hasattr(select, \"poll\"):\n\n    def _pipe_output(p: subprocess.Popen[bytes], log_fn: Callable[[str], None]) -> None:\n        \"\"\"Read output from p.stdout and call log_fn() with each chunk of data as it\n        becomes available.\"\"\"\n        # Perform non-blocking reads\n        import fcntl\n\n        assert p.stdout is not None\n        fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)\n        poll = select.poll()\n        poll.register(p.stdout.fileno(), select.POLLIN)\n\n        buffer_size = 4096\n        while True:\n            poll.poll()\n            data = p.stdout.read(buffer_size)\n            if not data:\n                break\n            # log_fn() accepts arguments as str (binary in Python 2, unicode in\n            # Python 3).  In Python 3 the subprocess output will be plain bytes,\n            # and need to be decoded.\n            if not isinstance(data, str):\n                data = data.decode(\"utf-8\", errors=\"surrogateescape\")\n            log_fn(data)\n\nelse:\n\n    def _pipe_output(p: subprocess.Popen[bytes], log_fn: Callable[[str], None]) -> None:\n        \"\"\"Read output from p.stdout and call log_fn() with each chunk of data as it\n        becomes available.\"\"\"\n        # Perform blocking reads.  Use a smaller buffer size to avoid blocking\n        # for very long when data is available.\n        assert p.stdout is not None\n        buffer_size = 64\n        while True:\n            # pyre-fixme[16]: Optional type has no attribute `read`.\n            data = p.stdout.read(buffer_size)\n            if not data:\n                break\n            # log_fn() accepts arguments as str (binary in Python 2, unicode in\n            # Python 3).  In Python 3 the subprocess output will be plain bytes,\n            # and need to be decoded.\n            if not isinstance(data, str):\n                data = data.decode(\"utf-8\", errors=\"surrogateescape\")\n            log_fn(data)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/subcmd.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\nfrom __future__ import annotations\n\nimport argparse\nfrom collections.abc import Callable\n\n\nclass SubCmd:\n    NAME: str | None = None\n    HELP: str | None = None\n\n    def run(self, args: argparse.Namespace) -> int:\n        \"\"\"perform the command\"\"\"\n        return 0\n\n    def setup_parser(self, parser: argparse.ArgumentParser) -> None:\n        # Subclasses should override setup_parser() if they have any\n        # command line options or arguments.\n        pass\n\n\nCmdTable: list[type[SubCmd]] = []\n\n\ndef add_subcommands(\n    parser: argparse._SubParsersAction[argparse.ArgumentParser],\n    common_args: argparse.ArgumentParser,\n    cmd_table: list[type[SubCmd]] = CmdTable,\n) -> None:\n    \"\"\"Register parsers for the defined commands with the provided parser\"\"\"\n    for cls in cmd_table:\n        command = cls()\n        command_parser = parser.add_parser(\n            # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`.\n            command.NAME,\n            help=command.HELP,\n            parents=[common_args],\n        )\n        command.setup_parser(command_parser)\n        command_parser.set_defaults(func=command.run)\n\n\ndef cmd(\n    name: str,\n    help: str | None = None,\n    cmd_table: list[type[SubCmd]] = CmdTable,\n) -> Callable[[type[SubCmd]], type[SubCmd]]:\n    \"\"\"\n    @cmd() is a decorator that can be used to help define Subcmd instances\n\n    Example usage:\n\n        @subcmd('list', 'Show the result list')\n        class ListCmd(Subcmd):\n            def run(self, args):\n                # Perform the command actions here...\n                pass\n    \"\"\"\n\n    def wrapper(cls: type[SubCmd]) -> type[SubCmd]:\n        class SubclassedCmd(cls):\n            NAME = name\n            HELP = help\n\n        # pyre-fixme[6]: For 1st argument expected `Type[SubCmd]` but got\n        #  `Type[SubclassedCmd]`.\n        # pyre-fixme[16]: Callable `cmd` has no attribute `wrapper`.\n        cmd_table.append(SubclassedCmd)\n        # pyre-fixme[7]: Expected `Type[SubCmd]` but got `Type[SubclassedCmd]`.\n        return SubclassedCmd\n\n    return wrapper\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/expr_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-unsafe\n\n\nimport unittest\n\nfrom ..expr import parse_expr\n\n\nclass ExprTest(unittest.TestCase):\n    def test_equal(self) -> None:\n        valid_variables = {\"foo\", \"some_var\", \"another_var\"}\n        e = parse_expr(\"foo=bar\", valid_variables)\n        self.assertTrue(e.eval({\"foo\": \"bar\"}))\n        self.assertFalse(e.eval({\"foo\": \"not-bar\"}))\n        self.assertFalse(e.eval({\"not-foo\": \"bar\"}))\n\n    def test_not_equal(self) -> None:\n        valid_variables = {\"foo\"}\n        e = parse_expr(\"not(foo=bar)\", valid_variables)\n        self.assertFalse(e.eval({\"foo\": \"bar\"}))\n        self.assertTrue(e.eval({\"foo\": \"not-bar\"}))\n\n    def test_bad_not(self) -> None:\n        valid_variables = {\"foo\"}\n        with self.assertRaises(Exception):\n            parse_expr(\"foo=not(bar)\", valid_variables)\n\n    def test_bad_variable(self) -> None:\n        valid_variables = {\"bar\"}\n        with self.assertRaises(Exception):\n            parse_expr(\"foo=bar\", valid_variables)\n\n    def test_all(self) -> None:\n        valid_variables = {\"foo\", \"baz\"}\n        e = parse_expr(\"all(foo = bar, baz = qux)\", valid_variables)\n        self.assertTrue(e.eval({\"foo\": \"bar\", \"baz\": \"qux\"}))\n        self.assertFalse(e.eval({\"foo\": \"bar\", \"baz\": \"nope\"}))\n        self.assertFalse(e.eval({\"foo\": \"nope\", \"baz\": \"nope\"}))\n\n    def test_any(self) -> None:\n        valid_variables = {\"foo\", \"baz\"}\n        e = parse_expr(\"any(foo = bar, baz = qux)\", valid_variables)\n        self.assertTrue(e.eval({\"foo\": \"bar\", \"baz\": \"qux\"}))\n        self.assertTrue(e.eval({\"foo\": \"bar\", \"baz\": \"nope\"}))\n        self.assertFalse(e.eval({\"foo\": \"nope\", \"baz\": \"nope\"}))\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/fixtures/duplicate/foo",
    "content": "[manifest]\nname = foo\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/fixtures/duplicate/subdir/foo",
    "content": "[manifest]\nname = foo\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/manifest_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-unsafe\n\n\nimport sys\nimport unittest\n\nfrom ..load import load_all_manifests, patch_loader\nfrom ..manifest import ManifestParser\n\n\nclass ManifestTest(unittest.TestCase):\n    def test_missing_section(self) -> None:\n        with self.assertRaisesRegex(\n            Exception, \"manifest file test is missing required section manifest\"\n        ):\n            ManifestParser(\"test\", \"\")\n\n    def test_missing_name(self) -> None:\n        with self.assertRaisesRegex(\n            Exception,\n            \"manifest file test section 'manifest' is missing required field 'name'\",\n        ):\n            ManifestParser(\n                \"test\",\n                \"\"\"\n[manifest]\n\"\"\",\n            )\n\n    def test_minimal(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\"\"\",\n        )\n        self.assertEqual(p.name, \"test\")\n        self.assertEqual(p.fbsource_path, None)\n\n    def test_minimal_with_fbsource_path(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\nfbsource_path = fbcode/wat\n\"\"\",\n        )\n        self.assertEqual(p.name, \"test\")\n        self.assertEqual(p.fbsource_path, \"fbcode/wat\")\n\n    def test_unknown_field(self) -> None:\n        with self.assertRaisesRegex(\n            Exception,\n            (\n                \"manifest file test section 'manifest' contains \"\n                \"unknown field 'invalid.field'\"\n            ),\n        ):\n            ManifestParser(\n                \"test\",\n                \"\"\"\n[manifest]\nname = test\ninvalid.field = woot\n\"\"\",\n            )\n\n    def test_invalid_section_name(self) -> None:\n        with self.assertRaisesRegex(\n            Exception, \"manifest file test contains unknown section 'invalid.section'\"\n        ):\n            ManifestParser(\n                \"test\",\n                \"\"\"\n[manifest]\nname = test\n\n[invalid.section]\nfoo = bar\n\"\"\",\n            )\n\n    def test_value_in_dependencies_section(self) -> None:\n        with self.assertRaisesRegex(\n            Exception,\n            (\n                \"manifest file test section 'dependencies' has \"\n                \"'foo = bar' but this section doesn't allow \"\n                \"specifying values for its entries\"\n            ),\n        ):\n            ManifestParser(\n                \"test\",\n                \"\"\"\n[manifest]\nname = test\n\n[dependencies]\nfoo = bar\n\"\"\",\n            )\n\n    def test_invalid_conditional_section_name(self) -> None:\n        with self.assertRaisesRegex(\n            Exception,\n            (\n                \"manifest file test section 'dependencies.=' \"\n                \"has invalid conditional: expected \"\n                \"identifier found =\"\n            ),\n        ):\n            ManifestParser(\n                \"test\",\n                \"\"\"\n[manifest]\nname = test\n\n[dependencies.=]\n\"\"\",\n            )\n\n    def test_section_as_args(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\n[dependencies]\na\nb\nc\n\n[dependencies.test=on]\nfoo\n\"\"\",\n        )\n        self.assertEqual(p.get_section_as_args(\"dependencies\"), [\"a\", \"b\", \"c\"])\n        self.assertEqual(\n            p.get_section_as_args(\"dependencies\", {\"test\": \"off\"}), [\"a\", \"b\", \"c\"]\n        )\n        self.assertEqual(\n            p.get_section_as_args(\"dependencies\", {\"test\": \"on\"}),\n            [\"a\", \"b\", \"c\", \"foo\"],\n        )\n\n        p2 = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\n[autoconf.args]\n--prefix=/foo\n--with-woot\n\"\"\",\n        )\n        self.assertEqual(\n            p2.get_section_as_args(\"autoconf.args\"), [\"--prefix=/foo\", \"--with-woot\"]\n        )\n\n    def test_section_as_dict(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\n[cmake.defines]\nfoo = bar\n\n[cmake.defines.test=on]\nfoo = baz\n\"\"\",\n        )\n        self.assertEqual(p.get_section_as_dict(\"cmake.defines\", {}), {\"foo\": \"bar\"})\n        self.assertEqual(\n            p.get_section_as_dict(\"cmake.defines\", {\"test\": \"on\"}), {\"foo\": \"baz\"}\n        )\n\n        p2 = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\n[cmake.defines.test=on]\nfoo = baz\n\n[cmake.defines]\nfoo = bar\n\"\"\",\n        )\n        self.assertEqual(\n            p2.get_section_as_dict(\"cmake.defines\", {\"test\": \"on\"}),\n            {\"foo\": \"bar\"},\n            msg=\"sections cascade in the order they appear in the manifest\",\n        )\n\n    def test_parse_common_manifests(self) -> None:\n        patch_loader(__name__)\n        # pyre-fixme[6]: For 1st argument expected `BuildOptions` but got `None`.\n        manifests = load_all_manifests(None)\n        self.assertNotEqual(0, len(manifests), msg=\"parsed some number of manifests\")\n\n    def test_mismatch_name(self) -> None:\n        with self.assertRaisesRegex(\n            Exception,\n            \"filename of the manifest 'foo' does not match the manifest name 'bar'\",\n        ):\n            ManifestParser(\n                \"foo\",\n                \"\"\"\n[manifest]\nname = bar\n\"\"\",\n            )\n\n    def test_duplicate_manifest(self) -> None:\n        patch_loader(__name__, \"fixtures/duplicate\")\n\n        with self.assertRaisesRegex(Exception, \"found duplicate manifest 'foo'\"):\n            # pyre-fixme[6]: For 1st argument expected `BuildOptions` but got `None`.\n            load_all_manifests(None)\n\n    if sys.version_info < (3, 2):\n\n        def assertRaisesRegex(self, *args, **kwargs):\n            return self.assertRaisesRegex(*args, **kwargs)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/platform_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-unsafe\n\n\nimport unittest\n\nfrom ..platform import HostType\n\n\nclass PlatformTest(unittest.TestCase):\n    def test_create(self) -> None:\n        p = HostType()\n        self.assertNotEqual(p.ostype, None, msg=\"probed and returned something\")\n\n        tuple_string = p.as_tuple_string()\n        round_trip = HostType.from_tuple_string(tuple_string)\n        self.assertEqual(round_trip, p)\n\n    def test_rendering_of_none(self) -> None:\n        p = HostType(ostype=\"foo\")\n        self.assertEqual(p.as_tuple_string(), \"foo-none-none\")\n\n    def test_is_methods(self) -> None:\n        p = HostType(ostype=\"windows\")\n        self.assertTrue(p.is_windows())\n        self.assertFalse(p.is_darwin())\n        self.assertFalse(p.is_linux())\n\n        p = HostType(ostype=\"darwin\")\n        self.assertFalse(p.is_windows())\n        self.assertTrue(p.is_darwin())\n        self.assertFalse(p.is_linux())\n\n        p = HostType(ostype=\"linux\")\n        self.assertFalse(p.is_windows())\n        self.assertFalse(p.is_darwin())\n        self.assertTrue(p.is_linux())\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/retry_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-unsafe\n\n\nimport unittest\nfrom unittest.mock import call, MagicMock, patch\n\nfrom ..buildopts import BuildOptions\nfrom ..errors import TransientFailure\nfrom ..fetcher import ArchiveFetcher\nfrom ..manifest import ManifestParser\n\n\nclass RetryTest(unittest.TestCase):\n    def _get_build_opts(self) -> BuildOptions:\n        mock_build_opts = MagicMock(spec=BuildOptions)\n        mock_build_opts.scratch_dir = \"/path/to/scratch_dir\"\n        return mock_build_opts\n\n    def _get_manifest(self) -> ManifestParser:\n        mock_manifest_parser = MagicMock(spec=ManifestParser)\n        mock_manifest_parser.name = \"mock_manifest_parser\"\n        return mock_manifest_parser\n\n    def _get_archive_fetcher(self) -> ArchiveFetcher:\n        return ArchiveFetcher(\n            build_options=self._get_build_opts(),\n            manifest=self._get_manifest(),\n            url=\"https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz\",\n            sha256=\"896d76ff65c88f5fd9e42f90d152b0579049158a163431dd77cdc57748b1d7b0\",\n        )\n\n    @patch(\"os.makedirs\")\n    @patch(\"os.environ.get\")\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.run\")\n    def test_no_retries(\n        self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs\n    ) -> None:\n        def custom_makedirs(path, exist_ok=False):\n            return None\n\n        def custom_get(key, default=None):\n            if key == \"GETDEPS_USE_WGET\":\n                return \"1\"\n            elif key == \"GETDEPS_WGET_ARGS\":\n                return \"\"\n            else:\n                return None\n\n        mock_makedirs.side_effect = custom_makedirs\n        mock_os_environ_get.side_effect = custom_get\n        mock_sleep.side_effect = None\n        fetcher = self._get_archive_fetcher()\n        fetcher._verify_hash = MagicMock(return_value=None)\n        fetcher._download()\n        mock_sleep.assert_has_calls([], any_order=False)\n        mock_run.assert_called_once_with(\n            [\n                \"wget\",\n                \"-O\",\n                \"/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz\",\n                \"https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz\",\n            ],\n            capture_output=True,\n        )\n\n    @patch(\"random.random\")\n    @patch(\"os.makedirs\")\n    @patch(\"os.environ.get\")\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.run\")\n    def test_retries(\n        self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs, mock_random\n    ) -> None:\n        def custom_makedirs(path, exist_ok=False):\n            return None\n\n        def custom_get(key, default=None):\n            if key == \"GETDEPS_USE_WGET\":\n                return \"1\"\n            elif key == \"GETDEPS_WGET_ARGS\":\n                return \"\"\n            else:\n                return None\n\n        mock_random.return_value = 0\n\n        mock_run.side_effect = [\n            IOError(\"<urlopen error [Errno 104] Connection reset by peer>\"),\n            IOError(\"<urlopen error [Errno 104] Connection reset by peer>\"),\n            None,\n        ]\n        mock_makedirs.side_effect = custom_makedirs\n        mock_os_environ_get.side_effect = custom_get\n        mock_sleep.side_effect = None\n        fetcher = self._get_archive_fetcher()\n        fetcher._verify_hash = MagicMock(return_value=None)\n        fetcher._download()\n        mock_sleep.assert_has_calls([call(2), call(4)], any_order=False)\n        calls = [\n            call(\n                [\n                    \"wget\",\n                    \"-O\",\n                    \"/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz\",\n                    \"https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz\",\n                ],\n                capture_output=True,\n            ),\n        ] * 3\n\n        mock_run.assert_has_calls(calls, any_order=False)\n\n    @patch(\"random.random\")\n    @patch(\"os.makedirs\")\n    @patch(\"os.environ.get\")\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.run\")\n    def test_all_retries(\n        self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs, mock_random\n    ) -> None:\n        def custom_makedirs(path, exist_ok=False):\n            return None\n\n        def custom_get(key, default=None):\n            if key == \"GETDEPS_USE_WGET\":\n                return \"1\"\n            elif key == \"GETDEPS_WGET_ARGS\":\n                return \"\"\n            else:\n                return None\n\n        mock_random.return_value = 0\n\n        mock_run.side_effect = IOError(\n            \"<urlopen error [Errno 104] Connection reset by peer>\"\n        )\n        mock_makedirs.side_effect = custom_makedirs\n        mock_os_environ_get.side_effect = custom_get\n        mock_sleep.side_effect = None\n        fetcher = self._get_archive_fetcher()\n        fetcher._verify_hash = MagicMock(return_value=None)\n        with self.assertRaises(TransientFailure):\n            fetcher._download()\n        mock_sleep.assert_has_calls(\n            [call(2), call(4), call(8), call(10)], any_order=False\n        )\n        calls = [\n            call(\n                [\n                    \"wget\",\n                    \"-O\",\n                    \"/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz\",\n                    \"https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz\",\n                ],\n                capture_output=True,\n            ),\n        ] * 5\n\n        mock_run.assert_has_calls(calls, any_order=False)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/scratch_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-unsafe\n\n\nimport unittest\n\nfrom ..buildopts import find_existing_win32_subst_for_path\n\n\nclass Win32SubstTest(unittest.TestCase):\n    def test_no_existing_subst(self) -> None:\n        self.assertIsNone(\n            find_existing_win32_subst_for_path(\n                r\"C:\\users\\alice\\appdata\\local\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={},\n            )\n        )\n        self.assertIsNone(\n            find_existing_win32_subst_for_path(\n                r\"C:\\users\\alice\\appdata\\local\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"C:\\users\\alice\\appdata\\local\\temp\\other\"},\n            )\n        )\n\n    def test_exact_match_returns_drive_path(self) -> None:\n        self.assertEqual(\n            find_existing_win32_subst_for_path(\n                r\"C:\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"C:\\temp\\fbcode_builder_getdeps\"},\n            ),\n            \"X:\\\\\",\n        )\n        self.assertEqual(\n            find_existing_win32_subst_for_path(\n                r\"C:/temp/fbcode_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"C:/temp/fbcode_builder_getdeps\"},\n            ),\n            \"X:\\\\\",\n        )\n\n    def test_multiple_exact_matches_returns_arbitrary_drive_path(self) -> None:\n        self.assertIn(\n            find_existing_win32_subst_for_path(\n                r\"C:\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={\n                    \"X:\\\\\": r\"C:\\temp\\fbcode_builder_getdeps\",\n                    \"Y:\\\\\": r\"C:\\temp\\fbcode_builder_getdeps\",\n                    \"Z:\\\\\": r\"C:\\temp\\fbcode_builder_getdeps\",\n                },\n            ),\n            (\"X:\\\\\", \"Y:\\\\\", \"Z:\\\\\"),\n        )\n\n    def test_drive_letter_is_case_insensitive(self) -> None:\n        self.assertEqual(\n            find_existing_win32_subst_for_path(\n                r\"C:\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"c:\\temp\\fbcode_builder_getdeps\"},\n            ),\n            \"X:\\\\\",\n        )\n\n    def test_path_components_are_case_insensitive(self) -> None:\n        self.assertEqual(\n            find_existing_win32_subst_for_path(\n                r\"C:\\TEMP\\FBCODE_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"C:\\temp\\fbcode_builder_getdeps\"},\n            ),\n            \"X:\\\\\",\n        )\n        self.assertEqual(\n            find_existing_win32_subst_for_path(\n                r\"C:\\temp\\fbcode_builder_getdeps\",\n                subst_mapping={\"X:\\\\\": r\"C:\\TEMP\\FBCODE_builder_getdeps\"},\n            ),\n            \"X:\\\\\",\n        )\n"
  },
  {
    "path": "build/fbcode_builder/getdeps/test/strip_marker_test.py",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\n# pyre-strict\n\n\nimport os\nimport tempfile\nimport unittest\n\nfrom ..fetcher import filter_strip_marker\nfrom ..manifest import ManifestParser\n\n\nclass ManifestStripMarkerTest(unittest.TestCase):\n    def test_default_strip_marker(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\n\"\"\",\n        )\n        self.assertEqual(p.shipit_strip_marker, \"@fb-only\")\n\n    def test_custom_strip_marker(self) -> None:\n        p = ManifestParser(\n            \"test\",\n            \"\"\"\n[manifest]\nname = test\nshipit_strip_marker = @oss-disable\n\"\"\",\n        )\n        self.assertEqual(p.shipit_strip_marker, \"@oss-disable\")\n\n\nclass FilterStripMarkerTest(unittest.TestCase):\n    def _write_temp(self, content: str) -> str:\n        fd, path = tempfile.mkstemp(suffix=\".txt\")\n        os.close(fd)\n        with open(path, \"w\") as f:\n            f.write(content)\n        return path\n\n    def _read(self, path: str) -> str:\n        with open(path, \"r\") as f:\n            return f.read()\n\n    def test_single_line_removal(self) -> None:\n        path = self._write_temp(\"keep this\\nremove this @fb-only\\nkeep this too\\n\")\n        try:\n            filter_strip_marker(path, \"@fb-only\")\n            self.assertEqual(self._read(path), \"keep this\\nkeep this too\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_block_removal(self) -> None:\n        content = (\n            \"before\\n\"\n            \"// @fb-only-start\\n\"\n            \"secret stuff\\n\"\n            \"more secret\\n\"\n            \"// @fb-only-end\\n\"\n            \"after\\n\"\n        )\n        path = self._write_temp(content)\n        try:\n            filter_strip_marker(path, \"@fb-only\")\n            self.assertEqual(self._read(path), \"before\\nafter\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_no_marker_present_no_change(self) -> None:\n        original = \"nothing special here\\njust plain code\\n\"\n        path = self._write_temp(original)\n        try:\n            filter_strip_marker(path, \"@fb-only\")\n            self.assertEqual(self._read(path), original)\n        finally:\n            os.unlink(path)\n\n    def test_custom_marker_single_line(self) -> None:\n        content = \"keep\\nremove @oss-disable\\nkeep too\\n\"\n        path = self._write_temp(content)\n        try:\n            filter_strip_marker(path, \"@oss-disable\")\n            self.assertEqual(self._read(path), \"keep\\nkeep too\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_custom_marker_block(self) -> None:\n        content = (\n            \"before\\n\"\n            \"# @oss-disable-start\\n\"\n            \"internal only\\n\"\n            \"# @oss-disable-end\\n\"\n            \"after\\n\"\n        )\n        path = self._write_temp(content)\n        try:\n            filter_strip_marker(path, \"@oss-disable\")\n            self.assertEqual(self._read(path), \"before\\nafter\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_custom_marker_ignores_default(self) -> None:\n        \"\"\"When using a custom marker, @fb-only lines should be kept.\"\"\"\n        content = \"keep @fb-only\\nremove @oss-disable\\nplain\\n\"\n        path = self._write_temp(content)\n        try:\n            filter_strip_marker(path, \"@oss-disable\")\n            self.assertEqual(self._read(path), \"keep @fb-only\\nplain\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_mixed_single_and_block(self) -> None:\n        content = (\n            \"line1\\n\"\n            \"line2 @fb-only\\n\"\n            \"line3\\n\"\n            \"// @fb-only-start\\n\"\n            \"block content\\n\"\n            \"// @fb-only-end\\n\"\n            \"line4\\n\"\n        )\n        path = self._write_temp(content)\n        try:\n            filter_strip_marker(path, \"@fb-only\")\n            self.assertEqual(self._read(path), \"line1\\nline3\\nline4\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_marker_with_regex_metacharacters(self) -> None:\n        \"\"\"Markers containing regex metacharacters should be escaped properly.\"\"\"\n        content = \"keep\\nremove @fb.only\\nkeep too\\n\"\n        path = self._write_temp(content)\n        try:\n            # With proper escaping, the dot is literal, not a wildcard\n            filter_strip_marker(path, \"@fb.only\")\n            self.assertEqual(self._read(path), \"keep\\nkeep too\\n\")\n        finally:\n            os.unlink(path)\n\n    def test_binary_file_skipped(self) -> None:\n        \"\"\"Binary files that can't be decoded as UTF-8 should be skipped.\"\"\"\n        fd, path = tempfile.mkstemp(suffix=\".bin\")\n        os.close(fd)\n        binary_content = b\"\\x80\\x81\\x82\\xff\\xfe\"\n        with open(path, \"wb\") as f:\n            f.write(binary_content)\n        try:\n            filter_strip_marker(path, \"@fb-only\")\n            with open(path, \"rb\") as f:\n                self.assertEqual(f.read(), binary_content)\n        finally:\n            os.unlink(path)\n"
  },
  {
    "path": "build/fbcode_builder/getdeps.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport tarfile\nimport tempfile\n\n# We don't import cache.create_cache directly as the facebook\n# specific import below may monkey patch it, and we want to\n# observe the patched version of this function!\nimport getdeps.cache as cache_module\nfrom getdeps.buildopts import setup_build_options\nfrom getdeps.dyndeps import create_dyn_dep_munger\nfrom getdeps.errors import TransientFailure\nfrom getdeps.fetcher import (\n    file_name_is_cmake_file,\n    is_public_commit,\n    list_files_under_dir_newer_than_timestamp,\n    SystemPackageFetcher,\n)\nfrom getdeps.load import ManifestLoader\nfrom getdeps.manifest import ManifestParser\nfrom getdeps.platform import HostType\nfrom getdeps.runcmd import check_cmd\nfrom getdeps.subcmd import add_subcommands, cmd, SubCmd\n\ntry:\n    import getdeps.facebook  # noqa: F401\nexcept ImportError:\n    # we don't ship the facebook specific subdir,\n    # so allow that to fail silently\n    pass\n\n\nsys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), \"getdeps\"))\n\n\nclass UsageError(Exception):\n    pass\n\n\n# Shared argument definition for --build-type used by multiple commands\nBUILD_TYPE_ARG = {\n    \"help\": \"Set the build type explicitly: Debug (unoptimized, debug symbols), RelWithDebInfo (optimized with debug symbols, default), MinSizeRel (size-optimized, no debug), or Release (optimized, no debug).\",\n    \"choices\": [\"Debug\", \"Release\", \"RelWithDebInfo\", \"MinSizeRel\"],\n    \"action\": \"store\",\n    \"default\": \"RelWithDebInfo\",\n}\n\n\n@cmd(\"validate-manifest\", \"parse a manifest and validate that it is correct\")\nclass ValidateManifest(SubCmd):\n    def run(self, args):\n        try:\n            ManifestParser(file_name=args.file_name)\n            print(\"OK\", file=sys.stderr)\n            return 0\n        except Exception as exc:\n            print(\"ERROR: %s\" % str(exc), file=sys.stderr)\n            return 1\n\n    def setup_parser(self, parser):\n        parser.add_argument(\"file_name\", help=\"path to the manifest file\")\n\n\n@cmd(\"show-host-type\", \"outputs the host type tuple for the host machine\")\nclass ShowHostType(SubCmd):\n    def run(self, args):\n        host = HostType()\n        print(\"%s\" % host.as_tuple_string())\n        return 0\n\n\nclass ProjectCmdBase(SubCmd):\n    def run(self, args):\n        opts = setup_build_options(args)\n\n        if args.current_project is not None:\n            opts.repo_project = args.current_project\n        if args.project is None:\n            if opts.repo_project is None:\n                raise UsageError(\n                    \"no project name specified, and no .projectid file found\"\n                )\n            if opts.repo_project == \"fbsource\":\n                # The fbsource repository is a little special.  There is no project\n                # manifest file for it.  A specific project must always be explicitly\n                # specified when building from fbsource.\n                raise UsageError(\n                    \"no project name specified (required when building in fbsource)\"\n                )\n            args.project = opts.repo_project\n\n        ctx_gen = opts.get_context_generator()\n        if args.test_dependencies:\n            ctx_gen.set_value_for_all_projects(\"test\", \"on\")\n        if args.enable_tests:\n            ctx_gen.set_value_for_project(args.project, \"test\", \"on\")\n        else:\n            ctx_gen.set_value_for_project(args.project, \"test\", \"off\")\n\n        if opts.shared_libs:\n            ctx_gen.set_value_for_all_projects(\"shared_libs\", \"on\")\n\n        loader = ManifestLoader(opts, ctx_gen)\n        self.process_project_dir_arguments(args, loader)\n\n        manifest = loader.load_manifest(args.project)\n\n        return self.run_project_cmd(args, loader, manifest)\n\n    def process_project_dir_arguments(self, args, loader):\n        def parse_project_arg(arg, arg_type):\n            parts = arg.split(\":\")\n            if len(parts) == 2:\n                project, path = parts\n            elif len(parts) == 1:\n                project = args.project\n                path = parts[0]\n            # On Windows path contains colon, e.g. C:\\open\n            elif os.name == \"nt\" and len(parts) == 3:\n                project = parts[0]\n                path = parts[1] + \":\" + parts[2]\n            else:\n                raise UsageError(\n                    \"invalid %s argument; too many ':' characters: %s\" % (arg_type, arg)\n                )\n\n            return project, os.path.abspath(path)\n\n        # If we are currently running from a project repository,\n        # use the current repository for the project sources.\n        build_opts = loader.build_opts\n        if build_opts.repo_project is not None and build_opts.repo_root is not None:\n            loader.set_project_src_dir(build_opts.repo_project, build_opts.repo_root)\n\n        for arg in args.src_dir:\n            project, path = parse_project_arg(arg, \"--src-dir\")\n            loader.set_project_src_dir(project, path)\n\n        for arg in args.build_dir:\n            project, path = parse_project_arg(arg, \"--build-dir\")\n            loader.set_project_build_dir(project, path)\n\n        for arg in args.install_dir:\n            project, path = parse_project_arg(arg, \"--install-dir\")\n            loader.set_project_install_dir(project, path)\n\n        for arg in args.project_install_prefix:\n            project, path = parse_project_arg(arg, \"--install-prefix\")\n            loader.set_project_install_prefix(project, path)\n\n    def setup_parser(self, parser):\n        parser.add_argument(\n            \"project\",\n            nargs=\"?\",\n            help=(\n                \"name of the project or path to a manifest \"\n                \"file describing the project\"\n            ),\n        )\n        parser.add_argument(\n            \"--no-tests\",\n            action=\"store_false\",\n            dest=\"enable_tests\",\n            default=True,\n            help=\"Disable building tests for this project.\",\n        )\n        parser.add_argument(\n            \"--test-dependencies\",\n            action=\"store_true\",\n            help=\"Enable building tests for dependencies as well.\",\n        )\n        parser.add_argument(\n            \"--current-project\",\n            help=\"Specify the name of the fbcode_builder manifest file for the \"\n            \"current repository.  If not specified, the code will attempt to find \"\n            \"this in a .projectid file in the repository root.\",\n        )\n        parser.add_argument(\n            \"--src-dir\",\n            default=[],\n            action=\"append\",\n            help=\"Specify a local directory to use for the project source, \"\n            \"rather than fetching it.\",\n        )\n        parser.add_argument(\n            \"--build-dir\",\n            default=[],\n            action=\"append\",\n            help=\"Explicitly specify the build directory to use for the \"\n            \"project, instead of the default location in the scratch path. \"\n            \"This only affects the project specified, and not its dependencies.\",\n        )\n        parser.add_argument(\n            \"--install-dir\",\n            default=[],\n            action=\"append\",\n            help=\"Explicitly specify the install directory to use for the \"\n            \"project, instead of the default location in the scratch path. \"\n            \"This only affects the project specified, and not its dependencies.\",\n        )\n        parser.add_argument(\n            \"--project-install-prefix\",\n            default=[],\n            action=\"append\",\n            help=\"Specify the final deployment installation path for a project\",\n        )\n\n        self.setup_project_cmd_parser(parser)\n\n    def setup_project_cmd_parser(self, parser):\n        pass\n\n    def create_builder(self, loader, manifest):\n        fetcher = loader.create_fetcher(manifest)\n        src_dir = fetcher.get_src_dir()\n        ctx = loader.ctx_gen.get_context(manifest.name)\n        build_dir = loader.get_project_build_dir(manifest)\n        inst_dir = loader.get_project_install_dir(manifest)\n        return manifest.create_builder(\n            loader.build_opts,\n            src_dir,\n            build_dir,\n            inst_dir,\n            ctx,\n            loader,\n            loader.dependencies_of(manifest),\n        )\n\n    def check_built(self, loader, manifest):\n        built_marker = os.path.join(\n            loader.get_project_install_dir(manifest), \".built-by-getdeps\"\n        )\n        return os.path.exists(built_marker)\n\n\nclass CachedProject:\n    \"\"\"A helper that allows calling the cache logic for a project\n    from both the build and the fetch code\"\"\"\n\n    def __init__(self, cache, loader, m):\n        self.m = m\n        self.inst_dir = loader.get_project_install_dir(m)\n        self.project_hash = loader.get_project_hash(m)\n        self.ctx = loader.ctx_gen.get_context(m.name)\n        self.loader = loader\n        self.cache = cache\n\n        self.cache_key = \"-\".join(\n            (\n                m.name,\n                self.ctx.get(\"os\"),\n                self.ctx.get(\"distro\") or \"none\",\n                self.ctx.get(\"distro_vers\") or \"none\",\n                self.project_hash,\n            )\n        )\n        self.cache_file_name = self.cache_key + \"-buildcache.tgz\"\n\n    def is_cacheable(self):\n        \"\"\"We only cache third party projects\"\"\"\n        return self.cache and self.m.shipit_project is None\n\n    def was_cached(self):\n        cached_marker = os.path.join(self.inst_dir, \".getdeps-cached-build\")\n        return os.path.exists(cached_marker)\n\n    def download(self):\n        if self.is_cacheable() and not os.path.exists(self.inst_dir):\n            print(\"check cache for %s\" % self.cache_file_name)\n            dl_dir = os.path.join(self.loader.build_opts.scratch_dir, \"downloads\")\n            if not os.path.exists(dl_dir):\n                os.makedirs(dl_dir)\n            try:\n                target_file_name = os.path.join(dl_dir, self.cache_file_name)\n                if self.cache.download_to_file(self.cache_file_name, target_file_name):\n                    tf = tarfile.open(target_file_name, \"r\")\n                    print(\n                        \"Extracting %s -> %s...\" % (self.cache_file_name, self.inst_dir)\n                    )\n                    tf.extractall(self.inst_dir)\n\n                    cached_marker = os.path.join(self.inst_dir, \".getdeps-cached-build\")\n                    with open(cached_marker, \"w\") as f:\n                        f.write(\"\\n\")\n\n                    return True\n            except Exception as exc:\n                print(\"%s\" % str(exc))\n\n        return False\n\n    def upload(self):\n        if self.is_cacheable():\n            # We can prepare an archive and stick it in LFS\n            tempdir = tempfile.mkdtemp()\n            tarfilename = os.path.join(tempdir, self.cache_file_name)\n            print(\"Archiving for cache: %s...\" % tarfilename)\n            tf = tarfile.open(tarfilename, \"w:gz\")\n            tf.add(self.inst_dir, arcname=\".\")\n            tf.close()\n            try:\n                self.cache.upload_from_file(self.cache_file_name, tarfilename)\n            except Exception as exc:\n                print(\n                    \"Failed to upload to cache (%s), continue anyway\" % str(exc),\n                    file=sys.stderr,\n                )\n            shutil.rmtree(tempdir)\n\n\n@cmd(\"fetch\", \"fetch the code for a given project\")\nclass FetchCmd(ProjectCmdBase):\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"fetch the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n        parser.add_argument(\n            \"--host-type\",\n            help=(\n                \"When recursively fetching, fetch deps for \"\n                \"this host type rather than the current system\"\n            ),\n        )\n\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            projects = loader.manifests_in_dependency_order()\n        else:\n            projects = [manifest]\n\n        cache = cache_module.create_cache()\n        for m in projects:\n            fetcher = loader.create_fetcher(m)\n            if isinstance(fetcher, SystemPackageFetcher):\n                # We are guaranteed that if the fetcher is set to\n                # SystemPackageFetcher then this item is completely\n                # satisfied by the appropriate system packages\n                continue\n            cached_project = CachedProject(cache, loader, m)\n            if cached_project.download():\n                continue\n\n            inst_dir = loader.get_project_install_dir(m)\n            built_marker = os.path.join(inst_dir, \".built-by-getdeps\")\n            if os.path.exists(built_marker):\n                with open(built_marker, \"r\") as f:\n                    built_hash = f.read().strip()\n\n                project_hash = loader.get_project_hash(m)\n                if built_hash == project_hash:\n                    continue\n\n            # We need to fetch the sources\n            fetcher.update()\n\n\n@cmd(\"install-system-deps\", \"Install system packages to satisfy the deps for a project\")\nclass InstallSysDepsCmd(ProjectCmdBase):\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"install the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n        parser.add_argument(\n            \"--dry-run\",\n            action=\"store_true\",\n            default=False,\n            help=\"Don't install, just print the commands specs we would run\",\n        )\n        parser.add_argument(\n            \"--os-type\",\n            help=\"Filter to just this OS type to run\",\n            choices=[\"linux\", \"darwin\", \"windows\", \"pacman-package\"],\n            action=\"store\",\n            dest=\"ostype\",\n            default=None,\n        )\n        parser.add_argument(\n            \"--distro\",\n            help=\"Filter to just this distro to run\",\n            choices=[\"ubuntu\", \"centos_stream\"],\n            action=\"store\",\n            dest=\"distro\",\n            default=None,\n        )\n        parser.add_argument(\n            \"--distro-version\",\n            help=\"Filter to just this distro version\",\n            action=\"store\",\n            dest=\"distrovers\",\n            default=None,\n        )\n\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            projects = loader.manifests_in_dependency_order()\n        else:\n            projects = [manifest]\n\n        rebuild_ctx_gen = False\n        if args.ostype:\n            loader.build_opts.host_type.ostype = args.ostype\n            loader.build_opts.host_type.distro = None\n            loader.build_opts.host_type.distrovers = None\n            rebuild_ctx_gen = True\n\n        if args.distro:\n            loader.build_opts.host_type.distro = args.distro\n            loader.build_opts.host_type.distrovers = None\n            rebuild_ctx_gen = True\n\n        if args.distrovers:\n            loader.build_opts.host_type.distrovers = args.distrovers\n            rebuild_ctx_gen = True\n\n        if rebuild_ctx_gen:\n            loader.ctx_gen = loader.build_opts.get_context_generator()\n\n        manager = loader.build_opts.host_type.get_package_manager()\n\n        all_packages = {}\n        for m in projects:\n            ctx = loader.ctx_gen.get_context(m.name)\n            packages = m.get_required_system_packages(ctx)\n            for k, v in packages.items():\n                merged = all_packages.get(k, [])\n                merged += v\n                all_packages[k] = merged\n\n        cmd_argss = []\n        if manager == \"rpm\":\n            packages = sorted(set(all_packages[\"rpm\"]))\n            if packages:\n                cmd_argss.append(\n                    [\"sudo\", \"dnf\", \"install\", \"-y\", \"--skip-broken\"] + packages\n                )\n        elif manager == \"deb\":\n            packages = sorted(set(all_packages[\"deb\"]))\n            if packages:\n                cmd_argss.append(\n                    [\n                        \"sudo\",\n                        \"--preserve-env=http_proxy\",\n                        \"apt-get\",\n                        \"install\",\n                        \"-y\",\n                    ]\n                    + packages\n                )\n                cmd_argss.append([\"pip\", \"install\", \"pex\"])\n        elif manager == \"homebrew\":\n            packages = sorted(set(all_packages[\"homebrew\"]))\n            if packages:\n                cmd_argss.append([\"brew\", \"install\"] + packages)\n        elif manager == \"pacman-package\":\n            packages = sorted(list(set(all_packages[\"pacman-package\"])))\n            if packages:\n                cmd_argss.append([\"pacman\", \"-S\"] + packages)\n        else:\n            host_tuple = loader.build_opts.host_type.as_tuple_string()\n            print(\n                f\"I don't know how to install any packages on this system {host_tuple}\"\n            )\n            return\n\n        for cmd_args in cmd_argss:\n            if args.dry_run:\n                print(\" \".join(cmd_args))\n            else:\n                check_cmd(cmd_args)\n        else:\n            print(\"no packages to install\")\n\n\n@cmd(\"list-deps\", \"lists the transitive deps for a given project\")\nclass ListDepsCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        for m in loader.manifests_in_dependency_order():\n            print(m.name)\n        return 0\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--host-type\",\n            help=(\n                \"Produce the list for the specified host type, \"\n                \"rather than that of the current system\"\n            ),\n        )\n\n\ndef clean_dirs(opts):\n    for d in [\"build\", \"installed\", \"extracted\", \"shipit\"]:\n        d = os.path.join(opts.scratch_dir, d)\n        print(\"Cleaning %s...\" % d)\n        if os.path.exists(d):\n            shutil.rmtree(d)\n\n\n@cmd(\"clean\", \"clean up the scratch dir\")\nclass CleanCmd(SubCmd):\n    def run(self, args):\n        opts = setup_build_options(args)\n        clean_dirs(opts)\n\n\n@cmd(\"show-scratch-dir\", \"show the scratch dir\")\nclass ShowScratchDirCmd(SubCmd):\n    def run(self, args):\n        opts = setup_build_options(args)\n        print(opts.scratch_dir)\n\n\n@cmd(\"show-build-dir\", \"print the build dir for a given project\")\nclass ShowBuildDirCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            manifests = loader.manifests_in_dependency_order()\n        else:\n            manifests = [manifest]\n\n        for m in manifests:\n            inst_dir = loader.get_project_build_dir(m)\n            print(inst_dir)\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"print the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n\n\n@cmd(\"show-inst-dir\", \"print the installation dir for a given project\")\nclass ShowInstDirCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            manifests = loader.manifests_in_dependency_order()\n        else:\n            manifests = [manifest]\n\n        for m in manifests:\n            fetcher = loader.create_fetcher(m)\n            if isinstance(fetcher, SystemPackageFetcher):\n                # We are guaranteed that if the fetcher is set to\n                # SystemPackageFetcher then this item is completely\n                # satisfied by the appropriate system packages\n                continue\n            inst_dir = loader.get_project_install_dir_respecting_install_prefix(m)\n            print(inst_dir)\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"print the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n\n\n@cmd(\"query-paths\", \"print the paths for tooling to use\")\nclass QueryPathsCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            manifests = loader.manifests_in_dependency_order()\n        else:\n            manifests = [manifest]\n\n        cache = cache_module.create_cache()\n        for m in manifests:\n            fetcher = loader.create_fetcher(m)\n            if isinstance(fetcher, SystemPackageFetcher):\n                # We are guaranteed that if the fetcher is set to\n                # SystemPackageFetcher then this item is completely\n                # satisfied by the appropriate system packages\n                continue\n            src_dir = fetcher.get_src_dir()\n            print(f\"{m.name}_SOURCE={src_dir}\")\n            inst_dir = loader.get_project_install_dir_respecting_install_prefix(m)\n            print(f\"{m.name}_INSTALL={inst_dir}\")\n            cached_project = CachedProject(cache, loader, m)\n            print(f\"{m.name}_CACHE_KEY={cached_project.cache_key}\")\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"print the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n\n\n@cmd(\"show-source-dir\", \"print the source dir for a given project\")\nclass ShowSourceDirCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if args.recursive:\n            manifests = loader.manifests_in_dependency_order()\n        else:\n            manifests = [manifest]\n\n        for m in manifests:\n            fetcher = loader.create_fetcher(m)\n            print(fetcher.get_src_dir())\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--recursive\",\n            help=\"print the transitive deps also\",\n            action=\"store_true\",\n            default=False,\n        )\n\n\n@cmd(\"build\", \"build a given project\")\nclass BuildCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if args.clean:\n            clean_dirs(loader.build_opts)\n\n        print(\"Building on %s\" % loader.ctx_gen.get_context(args.project))\n        projects = loader.manifests_in_dependency_order()\n\n        cache = cache_module.create_cache() if args.use_build_cache else None\n\n        dep_manifests = []\n\n        for m in projects:\n            dep_manifests.append(m)\n\n            fetcher = loader.create_fetcher(m)\n\n            if args.build_skip_lfs_download and hasattr(fetcher, \"skip_lfs_download\"):\n                print(\"skipping lfs download for %s\" % m.name)\n                fetcher.skip_lfs_download()\n\n            if isinstance(fetcher, SystemPackageFetcher):\n                # We are guaranteed that if the fetcher is set to\n                # SystemPackageFetcher then this item is completely\n                # satisfied by the appropriate system packages\n                continue\n\n            if args.clean:\n                fetcher.clean()\n\n            build_dir = loader.get_project_build_dir(m)\n            inst_dir = loader.get_project_install_dir(m)\n\n            if (\n                m == manifest\n                and not args.only_deps\n                or m != manifest\n                and not args.no_deps\n            ):\n                print(\"Assessing %s...\" % m.name)\n                project_hash = loader.get_project_hash(m)\n                ctx = loader.ctx_gen.get_context(m.name)\n                built_marker = os.path.join(inst_dir, \".built-by-getdeps\")\n\n                cached_project = CachedProject(cache, loader, m)\n\n                reconfigure, sources_changed = self.compute_source_change_status(\n                    cached_project, fetcher, m, built_marker, project_hash\n                )\n\n                if os.path.exists(built_marker) and not cached_project.was_cached():\n                    # We've previously built this. We may need to reconfigure if\n                    # our deps have changed, so let's check them.\n                    dep_reconfigure, dep_build = self.compute_dep_change_status(\n                        m, built_marker, loader\n                    )\n                    if dep_reconfigure:\n                        reconfigure = True\n                    if dep_build:\n                        sources_changed = True\n\n                extra_cmake_defines = (\n                    json.loads(args.extra_cmake_defines)\n                    if args.extra_cmake_defines\n                    else {}\n                )\n\n                extra_b2_args = args.extra_b2_args or []\n                cmake_targets = args.cmake_target or [\"install\"]\n\n                if sources_changed or reconfigure or not os.path.exists(built_marker):\n                    if os.path.exists(built_marker):\n                        os.unlink(built_marker)\n                    src_dir = fetcher.get_src_dir()\n                    # Prepare builders write out config before the main builder runs\n                    prepare_builders = m.create_prepare_builders(\n                        loader.build_opts,\n                        ctx,\n                        src_dir,\n                        build_dir,\n                        inst_dir,\n                        loader,\n                        dep_manifests,\n                    )\n                    for preparer in prepare_builders:\n                        preparer.prepare(reconfigure=reconfigure)\n\n                    builder = m.create_builder(\n                        loader.build_opts,\n                        src_dir,\n                        build_dir,\n                        inst_dir,\n                        ctx,\n                        loader,\n                        dep_manifests,\n                        final_install_prefix=loader.get_project_install_prefix(m),\n                        extra_cmake_defines=extra_cmake_defines,\n                        cmake_targets=(cmake_targets if m == manifest else [\"install\"]),\n                        extra_b2_args=extra_b2_args,\n                    )\n                    builder.build(reconfigure=reconfigure)\n\n                    # If we are building the project (not dependency) and a specific\n                    # cmake_target (not 'install') has been requested, then we don't\n                    # set the built_marker. This allows subsequent runs of getdeps.py\n                    # for the project to run with different cmake_targets to trigger\n                    # cmake\n                    has_built_marker = False\n                    if not (m == manifest and \"install\" not in cmake_targets):\n                        os.makedirs(os.path.dirname(built_marker), exist_ok=True)\n                        with open(built_marker, \"w\") as f:\n                            f.write(project_hash)\n                            has_built_marker = True\n\n                    # Only populate the cache from continuous build runs, and\n                    # only if we have a built_marker.\n                    if not args.skip_upload and has_built_marker:\n                        if args.schedule_type == \"continuous\":\n                            cached_project.upload()\n                        elif args.schedule_type == \"base_retry\":\n                            # Check if on public commit before uploading\n                            if is_public_commit(loader.build_opts):\n                                cached_project.upload()\n                elif args.verbose:\n                    print(\"found good %s\" % built_marker)\n\n    def compute_dep_change_status(self, m, built_marker, loader):\n        reconfigure = False\n        sources_changed = False\n        st = os.lstat(built_marker)\n\n        ctx = loader.ctx_gen.get_context(m.name)\n        dep_list = m.get_dependencies(ctx)\n        for dep in dep_list:\n            if reconfigure and sources_changed:\n                break\n\n            dep_manifest = loader.load_manifest(dep)\n            dep_root = loader.get_project_install_dir(dep_manifest)\n            for dep_file in list_files_under_dir_newer_than_timestamp(\n                dep_root, st.st_mtime\n            ):\n                if os.path.basename(dep_file) == \".built-by-getdeps\":\n                    continue\n                if file_name_is_cmake_file(dep_file):\n                    if not reconfigure:\n                        reconfigure = True\n                        print(\n                            f\"Will reconfigure cmake because {dep_file} is newer than {built_marker}\"\n                        )\n                else:\n                    if not sources_changed:\n                        sources_changed = True\n                        print(\n                            f\"Will run build because {dep_file} is newer than {built_marker}\"\n                        )\n\n                if reconfigure and sources_changed:\n                    break\n\n        return reconfigure, sources_changed\n\n    def compute_source_change_status(\n        self, cached_project, fetcher, m, built_marker, project_hash\n    ):\n        reconfigure = False\n        sources_changed = False\n        if cached_project.download():\n            if not os.path.exists(built_marker):\n                fetcher.update()\n        else:\n            check_fetcher = True\n            if os.path.exists(built_marker):\n                check_fetcher = False\n                with open(built_marker, \"r\") as f:\n                    built_hash = f.read().strip()\n                if built_hash == project_hash:\n                    if cached_project.is_cacheable():\n                        # We can blindly trust the build status\n                        reconfigure = False\n                        sources_changed = False\n                    else:\n                        # Otherwise, we may have changed the source, so let's\n                        # check in with the fetcher layer\n                        check_fetcher = True\n                else:\n                    # Some kind of inconsistency with a prior build,\n                    # let's run it again to be sure\n                    os.unlink(built_marker)\n                    reconfigure = True\n                    sources_changed = True\n                    # While we don't need to consult the fetcher for the\n                    # status in this case, we may still need to have eg: shipit\n                    # run in order to have a correct source tree.\n                    fetcher.update()\n\n            if check_fetcher:\n                change_status = fetcher.update()\n                reconfigure = change_status.build_changed()\n                sources_changed = change_status.sources_changed()\n\n        return reconfigure, sources_changed\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--clean\",\n            action=\"store_true\",\n            default=False,\n            help=(\n                \"Clean up the build and installation area prior to building, \"\n                \"causing the projects to be built from scratch\"\n            ),\n        )\n        parser.add_argument(\n            \"--no-deps\",\n            action=\"store_true\",\n            default=False,\n            help=(\n                \"Only build the named project, not its deps. \"\n                \"This is most useful after you've built all of the deps, \"\n                \"and helps to avoid waiting for relatively \"\n                \"slow up-to-date-ness checks\"\n            ),\n        )\n        parser.add_argument(\n            \"--only-deps\",\n            action=\"store_true\",\n            default=False,\n            help=(\n                \"Only build the named project's deps. \"\n                \"This is most useful when you want to separate out building \"\n                \"of all of the deps and your project\"\n            ),\n        )\n        parser.add_argument(\n            \"--no-build-cache\",\n            action=\"store_false\",\n            default=True,\n            dest=\"use_build_cache\",\n            help=\"Do not attempt to use the build cache.\",\n        )\n        parser.add_argument(\n            \"--cmake-target\",\n            help=(\"Repeatable argument that specifies targets for cmake build.\"),\n            default=[],\n            action=\"append\",\n        )\n        parser.add_argument(\n            \"--extra-b2-args\",\n            help=(\n                \"Repeatable argument that contains extra arguments to pass \"\n                \"to b2, which compiles boost. \"\n                \"e.g.: 'cxxflags=-fPIC' 'cflags=-fPIC'\"\n            ),\n            action=\"append\",\n        )\n        parser.add_argument(\n            \"--free-up-disk\",\n            help=\"Remove unused tools and clean up intermediate files if possible to maximise space for the build\",\n            action=\"store_true\",\n            default=False,\n        )\n        parser.add_argument(\"--build-type\", **BUILD_TYPE_ARG)\n\n\n@cmd(\"fixup-dyn-deps\", \"Adjusts dynamic dependencies for packaging purposes\")\nclass FixupDeps(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        projects = loader.manifests_in_dependency_order()\n\n        # Accumulate the install directories so that the build steps\n        # can find their dep installation\n        install_dirs = []\n        dep_manifests = []\n\n        for m in projects:\n            inst_dir = loader.get_project_install_dir_respecting_install_prefix(m)\n            install_dirs.append(inst_dir)\n            dep_manifests.append(m)\n\n            if m == manifest:\n                ctx = loader.ctx_gen.get_context(m.name)\n                env = loader.build_opts.compute_env_for_install_dirs(\n                    loader, dep_manifests, ctx\n                )\n                dep_munger = create_dyn_dep_munger(\n                    loader.build_opts, env, install_dirs, args.strip\n                )\n                if dep_munger is None:\n                    print(f\"dynamic dependency fixups not supported on {sys.platform}\")\n                else:\n                    dep_munger.process_deps(args.destdir, args.final_install_prefix)\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\"destdir\", help=\"Where to copy the fixed up executables\")\n        parser.add_argument(\n            \"--final-install-prefix\", help=\"specify the final installation prefix\"\n        )\n        parser.add_argument(\n            \"--strip\",\n            action=\"store_true\",\n            default=False,\n            help=\"Strip debug info while processing executables\",\n        )\n\n\n@cmd(\"test\", \"test a given project\")\nclass TestCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        if not self.check_built(loader, manifest):\n            print(\"project %s has not been built\" % manifest.name)\n            return 1\n        return self.create_builder(loader, manifest).run_tests(\n            schedule_type=args.schedule_type,\n            owner=args.test_owner,\n            test_filter=args.filter,\n            test_exclude=args.exclude,\n            retry=args.retry,\n            no_testpilot=args.no_testpilot,\n            timeout=args.timeout,\n        )\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\"--test-owner\", help=\"Owner for testpilot\")\n        parser.add_argument(\"--filter\", help=\"Only run the tests matching the regex\")\n        parser.add_argument(\"--exclude\", help=\"Exclude tests matching the regex\")\n        parser.add_argument(\n            \"--retry\",\n            type=int,\n            default=3,\n            help=\"Number of immediate retries for failed tests \"\n            \"(noop in continuous and testwarden runs)\",\n        )\n        parser.add_argument(\n            \"--no-testpilot\",\n            help=\"Do not use Test Pilot even when available\",\n            action=\"store_true\",\n        )\n        parser.add_argument(\n            \"--timeout\",\n            type=int,\n            default=None,\n            help=\"Timeout in seconds for each individual test\",\n        )\n        parser.add_argument(\"--build-type\", **BUILD_TYPE_ARG)\n\n\n@cmd(\n    \"debug\",\n    \"start a shell in the given project's build dir with the correct environment for running the build\",\n)\nclass DebugCmd(ProjectCmdBase):\n    def run_project_cmd(self, args, loader, manifest):\n        self.create_builder(loader, manifest).debug(reconfigure=False)\n\n\n@cmd(\n    \"env\",\n    \"print the environment in a shell sourceable format\",\n)\nclass EnvCmd(ProjectCmdBase):\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--os-type\",\n            help=\"Filter to just this OS type to run\",\n            choices=[\"linux\", \"darwin\", \"windows\"],\n            action=\"store\",\n            dest=\"ostype\",\n            default=None,\n        )\n\n    def run_project_cmd(self, args, loader, manifest):\n        if args.ostype:\n            loader.build_opts.host_type.ostype = args.ostype\n        self.create_builder(loader, manifest).printenv(reconfigure=False)\n\n\n@cmd(\"generate-github-actions\", \"generate a GitHub actions configuration\")\nclass GenerateGitHubActionsCmd(ProjectCmdBase):\n    RUN_ON_ALL = \"\"\" [push, pull_request]\"\"\"\n\n    WORKFLOW_DISPATCH_TMATE = \"\"\"\n  workflow_dispatch:\n    inputs:\n      tmate_enabled:\n        description: 'Start a tmate SSH session on failure'\n        required: false\n        default: false\n        type: boolean\"\"\"\n\n    def run_project_cmd(self, args, loader, manifest):\n        platforms = [\n            HostType(\"linux\", \"ubuntu\", \"24\"),\n            HostType(\"darwin\", None, None),\n            HostType(\"windows\", None, None),\n        ]\n\n        for p in platforms:\n            if args.os_types and p.ostype not in args.os_types:\n                continue\n            self.write_job_for_platform(p, args)\n\n    def get_run_on(self, args):\n        if args.run_on_all_branches:\n            return (\n                \"\"\"\n  push:\n  pull_request:\"\"\"\n                + self.WORKFLOW_DISPATCH_TMATE\n            )\n        if args.cron:\n            if args.cron == \"never\":\n                return \" {}\"\n            elif args.cron == \"workflow_dispatch\":\n                return self.WORKFLOW_DISPATCH_TMATE\n            else:\n                return (\n                    f\"\"\"\n  schedule:\n    - cron: '{args.cron}'\"\"\"\n                    + self.WORKFLOW_DISPATCH_TMATE\n                )\n\n        return (\n            f\"\"\"\n  push:\n    branches:\n    - {args.main_branch}\n  pull_request:\n    branches:\n    - {args.main_branch}\"\"\"\n            + self.WORKFLOW_DISPATCH_TMATE\n        )\n\n    # TODO: Break up complex function\n    def write_job_for_platform(self, platform, args):  # noqa: C901\n        build_opts = setup_build_options(args, platform)\n        ctx_gen = build_opts.get_context_generator()\n        if args.enable_tests:\n            ctx_gen.set_value_for_project(args.project, \"test\", \"on\")\n        else:\n            ctx_gen.set_value_for_project(args.project, \"test\", \"off\")\n        loader = ManifestLoader(build_opts, ctx_gen)\n        self.process_project_dir_arguments(args, loader)\n        manifest = loader.load_manifest(args.project)\n        manifest_ctx = loader.ctx_gen.get_context(manifest.name)\n        run_tests = (\n            args.enable_tests\n            and manifest.get(\"github.actions\", \"run_tests\", ctx=manifest_ctx) != \"off\"\n        )\n        rust_version = (\n            manifest.get(\"github.actions\", \"rust_version\", ctx=manifest_ctx) or \"stable\"\n        )\n\n        override_build_type = args.build_type or manifest.get(\n            \"github.actions\", \"build_type\", ctx=manifest_ctx\n        )\n        if run_tests:\n            manifest_ctx.set(\"test\", \"on\")\n        run_on = self.get_run_on(args)\n\n        tests_arg = \"--no-tests \"\n        if run_tests:\n            tests_arg = \"\"\n\n        # Some projects don't do anything \"useful\" as a leaf project, only\n        # as a dep for a leaf project. Check for those here; we don't want\n        # to waste the effort scheduling them on CI.\n        # We do this by looking at the builder type in the manifest file\n        # rather than creating a builder and checking its type because we\n        # don't know enough to create the full builder instance here.\n        builder_name = manifest.get(\"build\", \"builder\", ctx=manifest_ctx)\n        if builder_name == \"nop\":\n            return None\n\n        # We want to be sure that we're running things with python 3\n        # but python versioning is honestly a bit of a frustrating mess.\n        # `python` may be version 2 or version 3 depending on the system.\n        # python3 may not be a thing at all!\n        # Assume an optimistic default\n        py3 = \"python3\"\n\n        if build_opts.is_linux():\n            artifacts = \"linux\"\n            if args.runs_on:\n                runs_on = args.runs_on\n            else:\n                runs_on = f\"ubuntu-{args.ubuntu_version}\"\n                if args.cpu_cores:\n                    runs_on = f\"{args.cpu_cores}-core-ubuntu-{args.ubuntu_version}\"\n        elif build_opts.is_windows():\n            artifacts = \"windows\"\n            if args.runs_on:\n                runs_on = args.runs_on\n            else:\n                runs_on = \"windows-2022\"\n            # The windows runners are python 3 by default; python2.exe\n            # is available if needed.\n            py3 = \"python\"\n        else:\n            artifacts = \"mac\"\n            if args.runs_on:\n                runs_on = args.runs_on\n            else:\n                runs_on = \"macOS-latest\"\n\n        os.makedirs(args.output_dir, exist_ok=True)\n\n        job_file_prefix = \"getdeps_\"\n        if args.job_file_prefix:\n            job_file_prefix = args.job_file_prefix\n\n        output_file = os.path.join(args.output_dir, f\"{job_file_prefix}{artifacts}.yml\")\n\n        if args.job_name_prefix:\n            job_name = args.job_name_prefix + artifacts.capitalize()\n        else:\n            job_name = artifacts\n\n        with open(output_file, \"w\") as out:\n            # Deliberate line break here because the @ and the generated\n            # symbols are meaningful to our internal tooling when they\n            # appear in a single token\n            out.write(\"# This file was @\")\n            out.write(\"generated by getdeps.py\\n\")\n            out.write(\n                f\"\"\"\nname: {job_name}\n\non:{run_on}\n\npermissions:\n  contents: read  #  to fetch code (actions/checkout)\n\njobs:\n\"\"\"\n            )\n\n            getdepscmd = f\"{py3} build/fbcode_builder/getdeps.py\"\n\n            out.write(\"  build:\\n\")\n            out.write(\"    runs-on: %s\\n\" % runs_on)\n            out.write(\"    steps:\\n\")\n\n            if build_opts.is_windows():\n                # cmake relies on BOOST_ROOT but GH deliberately don't set it in order\n                # to avoid versioning issues:\n                # https://github.com/actions/virtual-environments/issues/319\n                # Instead, set the version we think we need; this is effectively\n                # coupled with the boost manifest\n                # This is the unusual syntax for setting an env var for the rest of\n                # the steps in a workflow:\n                # https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/\n                out.write(\"    - name: Export boost environment\\n\")\n                out.write(\n                    '      run: \"echo BOOST_ROOT=%BOOST_ROOT_1_83_0% >> %GITHUB_ENV%\"\\n'\n                )\n                out.write(\"      shell: cmd\\n\")\n\n                out.write(\"    - name: Fix Git config\\n\")\n                out.write(\"      run: >\\n\")\n                out.write(\"        git config --system core.longpaths true &&\\n\")\n                out.write(\"        git config --system core.autocrlf false &&\\n\")\n                # cxx crate needs symlinks enabled\n                out.write(\"        git config --system core.symlinks true\\n\")\n                # && is not supported on default windows powershell, so use cmd\n                out.write(\"      shell: cmd\\n\")\n\n            out.write(\"    - uses: actions/checkout@v6\\n\")\n\n            build_type_arg = \"\"\n            if override_build_type:\n                build_type_arg = f\"--build-type {override_build_type} \"\n\n            if args.shared_libs:\n                build_type_arg += \"--shared-libs \"\n\n            if build_opts.free_up_disk:\n                free_up_disk = \"--free-up-disk \"\n                if not build_opts.is_windows():\n                    out.write(\"    - name: Show disk space at start\\n\")\n                    out.write(\"      run: df -h\\n\")\n                    # remove the unused github supplied android dev tools\n                    out.write(\"    - name: Free up disk space\\n\")\n                    out.write(\"      run: sudo rm -rf /usr/local/lib/android\\n\")\n                    out.write(\"    - name: Show disk space after freeing up\\n\")\n                    out.write(\"      run: df -h\\n\")\n            else:\n                free_up_disk = \"\"\n\n            allow_sys_arg = \"\"\n            if (\n                build_opts.allow_system_packages\n                and build_opts.host_type.get_package_manager()\n            ):\n                sudo_arg = \"sudo --preserve-env=http_proxy \"\n                allow_sys_arg = \" --allow-system-packages\"\n                if build_opts.host_type.get_package_manager() == \"deb\":\n                    out.write(\"    - name: Update system package info\\n\")\n                    out.write(f\"      run: {sudo_arg}apt-get update\\n\")\n\n                out.write(\"    - name: Install system deps\\n\")\n                if build_opts.is_darwin():\n                    # brew is installed as regular user\n                    sudo_arg = \"\"\n\n                system_deps_cmd = f\"{sudo_arg}{getdepscmd}{allow_sys_arg} install-system-deps {tests_arg}--recursive {manifest.name}\"\n                if build_opts.is_linux() or build_opts.is_freebsd():\n                    system_deps_cmd += f\" && {sudo_arg}{getdepscmd}{allow_sys_arg} install-system-deps {tests_arg}--recursive patchelf\"\n                out.write(f\"      run: {system_deps_cmd}\\n\")\n\n                required_locales = manifest.get(\n                    \"github.actions\", \"required_locales\", ctx=manifest_ctx\n                )\n                if (\n                    build_opts.host_type.get_package_manager() == \"deb\"\n                    and required_locales\n                ):\n                    # ubuntu doesn't include this by default\n                    out.write(\"    - name: Install locale-gen\\n\")\n                    out.write(f\"      run: {sudo_arg}apt-get install locales\\n\")\n                    for loc in required_locales.split():\n                        out.write(f\"    - name: Ensure {loc} locale present\\n\")\n                        out.write(f\"      run: {sudo_arg}locale-gen {loc}\\n\")\n\n            out.write(\"    - id: paths\\n\")\n            out.write(\"      name: Query paths\\n\")\n            if build_opts.is_windows():\n                out.write(\n                    f\"      run: {getdepscmd}{allow_sys_arg} query-paths {tests_arg}--recursive --src-dir=. {manifest.name}  >> $env:GITHUB_OUTPUT\\n\"\n                )\n                out.write(\"      shell: pwsh\\n\")\n            else:\n                out.write(\n                    f'      run: {getdepscmd}{allow_sys_arg} query-paths {tests_arg}--recursive --src-dir=. {manifest.name}  >> \"$GITHUB_OUTPUT\"\\n'\n                )\n\n            projects = loader.manifests_in_dependency_order()\n\n            main_repo_url = manifest.get_repo_url(manifest_ctx)\n            has_same_repo_dep = False\n\n            # Add the rust dep which doesn't have a manifest\n            for m in projects:\n                if m == manifest:\n                    continue\n                mbuilder_name = m.get(\"build\", \"builder\", ctx=manifest_ctx)\n                if (\n                    m.name == \"rust\"\n                    or builder_name == \"cargo\"\n                    or mbuilder_name == \"cargo\"\n                ):\n                    out.write(f\"    - name: Install Rust {rust_version.capitalize()}\\n\")\n                    out.write(f\"      uses: dtolnay/rust-toolchain@{rust_version}\\n\")\n                    break\n\n            # Normal deps that have manifests\n            for m in projects:\n                if m == manifest or m.name == \"rust\":\n                    continue\n                ctx = loader.ctx_gen.get_context(m.name)\n                if m.get_repo_url(ctx) != main_repo_url:\n                    out.write(\"    - name: Fetch %s\\n\" % m.name)\n                    out.write(\n                        f\"      if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\\n\"\n                    )\n                    out.write(\n                        f\"      run: {getdepscmd}{allow_sys_arg} fetch --no-tests {m.name}\\n\"\n                    )\n\n            for m in projects:\n                if m == manifest or m.name == \"rust\":\n                    continue\n                src_dir_arg = \"\"\n                ctx = loader.ctx_gen.get_context(m.name)\n                if main_repo_url and m.get_repo_url(ctx) == main_repo_url:\n                    # Its in the same repo, so src-dir is also .\n                    src_dir_arg = \"--src-dir=. \"\n                    has_same_repo_dep = True\n\n                if args.use_build_cache and not src_dir_arg:\n                    out.write(f\"    - name: Restore {m.name} from cache\\n\")\n                    out.write(f\"      id: restore_{m.name}\\n\")\n                    # only need to restore if would build it\n                    out.write(\n                        f\"      if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\\n\"\n                    )\n                    out.write(\"      uses: actions/cache/restore@v4\\n\")\n                    out.write(\"      with:\\n\")\n                    out.write(\n                        f\"       path: ${{{{ steps.paths.outputs.{m.name}_INSTALL }}}}\\n\"\n                    )\n                    out.write(\n                        f\"       key: ${{{{ steps.paths.outputs.{m.name}_CACHE_KEY }}}}-install\\n\"\n                    )\n\n                out.write(\"    - name: Build %s\\n\" % m.name)\n                if not src_dir_arg:\n                    if args.use_build_cache:\n                        out.write(\n                            f\"      if: ${{{{ steps.paths.outputs.{m.name}_SOURCE && ! steps.restore_{m.name}.outputs.cache-hit }}}}\\n\"\n                        )\n                    else:\n                        out.write(\n                            f\"      if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\\n\"\n                        )\n                out.write(\n                    f\"      run: {getdepscmd}{allow_sys_arg} build {build_type_arg}{src_dir_arg}{free_up_disk}--no-tests {m.name}\\n\"\n                )\n\n                if args.use_build_cache and not src_dir_arg:\n                    out.write(f\"    - name: Save {m.name} to cache\\n\")\n                    out.write(\"      uses: actions/cache/save@v4\\n\")\n                    out.write(\n                        f\"      if: ${{{{ steps.paths.outputs.{m.name}_SOURCE && ! steps.restore_{m.name}.outputs.cache-hit }}}}\\n\"\n                    )\n                    out.write(\"      with:\\n\")\n                    out.write(\n                        f\"       path: ${{{{ steps.paths.outputs.{m.name}_INSTALL }}}}\\n\"\n                    )\n                    out.write(\n                        f\"       key: ${{{{ steps.paths.outputs.{m.name}_CACHE_KEY }}}}-install\\n\"\n                    )\n\n            out.write(\"    - name: Build %s\\n\" % manifest.name)\n\n            project_prefix = \"\"\n            if not build_opts.is_windows():\n                prefix = loader.get_project_install_prefix(manifest) or \"/usr/local\"\n                project_prefix = \" --project-install-prefix %s:%s\" % (\n                    manifest.name,\n                    prefix,\n                )\n\n            # If we have dep from same repo, we already built it and don't want to rebuild it again\n            no_deps_arg = \"\"\n            if has_same_repo_dep:\n                no_deps_arg = \"--no-deps \"\n\n            out.write(\n                f\"      run: {getdepscmd}{allow_sys_arg} build {build_type_arg}{tests_arg}{no_deps_arg}--src-dir=. {manifest.name}{project_prefix}\\n\"\n            )\n\n            out.write(\"    - name: Copy artifacts\\n\")\n            if build_opts.is_linux():\n                # Strip debug info from the binaries, but only on linux.\n                # While the `strip` utility is also available on macOS,\n                # attempting to strip there results in an error.\n                # The `strip` utility is not available on Windows.\n                strip = \" --strip\"\n            else:\n                strip = \"\"\n\n            out.write(\n                f\"      run: {getdepscmd}{allow_sys_arg} fixup-dyn-deps{strip} \"\n                f\"--src-dir=. {manifest.name} _artifacts/{artifacts}{project_prefix} \"\n                f\"--final-install-prefix /usr/local\\n\"\n            )\n\n            out.write(\"    - uses: actions/upload-artifact@v6\\n\")\n            out.write(\"      with:\\n\")\n            out.write(\"        name: %s\\n\" % manifest.name)\n            out.write(\"        path: _artifacts\\n\")\n\n            if run_tests:\n                num_jobs_arg = \"\"\n                if args.num_jobs:\n                    num_jobs_arg = f\"--num-jobs {args.num_jobs} \"\n\n                out.write(\"    - name: Test %s\\n\" % manifest.name)\n                out.write(\n                    f\"      run: {getdepscmd}{allow_sys_arg} test {build_type_arg}{num_jobs_arg}--src-dir=. {manifest.name}{project_prefix}\\n\"\n                )\n            if build_opts.free_up_disk and not build_opts.is_windows():\n                out.write(\"    - name: Show disk space at end\\n\")\n                out.write(\"      if: always()\\n\")\n                out.write(\"      run: df -h\\n\")\n\n            out.write(\"    - name: Setup tmate session\\n\")\n            out.write(\n                \"      if: failure() && github.event_name == 'workflow_dispatch' && inputs.tmate_enabled\\n\"\n            )\n            out.write(\"      uses: mxschmitt/action-tmate@v3\\n\")\n\n    def setup_project_cmd_parser(self, parser):\n        parser.add_argument(\n            \"--disallow-system-packages\",\n            help=\"Disallow satisfying third party deps from installed system packages\",\n            action=\"store_true\",\n            default=False,\n        )\n        parser.add_argument(\n            \"--output-dir\", help=\"The directory that will contain the yml files\"\n        )\n        parser.add_argument(\n            \"--run-on-all-branches\",\n            action=\"store_true\",\n            help=\"Allow CI to fire on all branches - Handy for testing\",\n        )\n        parser.add_argument(\n            \"--ubuntu-version\", default=\"24.04\", help=\"Version of Ubuntu to use\"\n        )\n        parser.add_argument(\n            \"--cpu-cores\",\n            help=\"Number of CPU cores to use (applicable for Linux OS)\",\n        )\n        parser.add_argument(\n            \"--runs-on\",\n            help=\"Allow specifying explicit runs-on: for github actions\",\n        )\n        parser.add_argument(\n            \"--cron\",\n            help=\"Specify that the job runs on a cron schedule instead of on pushes. Pass never to disable the action.\",\n        )\n        parser.add_argument(\n            \"--main-branch\",\n            default=\"main\",\n            help=\"Main branch to trigger GitHub Action on\",\n        )\n        parser.add_argument(\n            \"--os-type\",\n            help=\"Filter to just this OS type to run\",\n            choices=[\"linux\", \"darwin\", \"windows\"],\n            action=\"append\",\n            dest=\"os_types\",\n            default=[],\n        )\n        parser.add_argument(\n            \"--job-file-prefix\",\n            type=str,\n            help=\"add a prefix to all job file names\",\n            default=None,\n        )\n        parser.add_argument(\n            \"--job-name-prefix\",\n            type=str,\n            help=\"add a prefix to all job names\",\n            default=None,\n        )\n        parser.add_argument(\n            \"--free-up-disk\",\n            help=\"Remove unused tools and clean up intermediate files if possible to maximise space for the build\",\n            action=\"store_true\",\n            default=False,\n        )\n        parser.add_argument(\"--build-type\", **BUILD_TYPE_ARG)\n        parser.add_argument(\n            \"--no-build-cache\",\n            action=\"store_false\",\n            default=True,\n            dest=\"use_build_cache\",\n            help=\"Do not attempt to use the build cache.\",\n        )\n\n\ndef get_arg_var_name(args):\n    for arg in args:\n        if arg.startswith(\"--\"):\n            return arg[2:].replace(\"-\", \"_\")\n\n    raise Exception(\"unable to determine argument variable name from %r\" % (args,))\n\n\ndef parse_args():\n    # We want to allow common arguments to be specified either before or after\n    # the subcommand name.  In order to do this we add them to the main parser\n    # and to subcommand parsers.  In order for this to work, we need to tell\n    # argparse that the default value is SUPPRESS, so that the default values\n    # from the subparser arguments won't override values set by the user from\n    # the main parser.  We maintain our own list of desired defaults in the\n    # common_defaults dictionary, and manually set those if the argument wasn't\n    # present at all.\n    common_args = argparse.ArgumentParser(add_help=False)\n    common_defaults = {}\n\n    def add_common_arg(*args, **kwargs):\n        var_name = get_arg_var_name(args)\n        default_value = kwargs.pop(\"default\", None)\n        common_defaults[var_name] = default_value\n        kwargs[\"default\"] = argparse.SUPPRESS\n        common_args.add_argument(*args, **kwargs)\n\n    add_common_arg(\"--scratch-path\", help=\"Where to maintain checkouts and build dirs\")\n    add_common_arg(\n        \"--vcvars-path\", default=None, help=\"Path to the vcvarsall.bat on Windows.\"\n    )\n    add_common_arg(\n        \"--install-prefix\",\n        help=(\n            \"Where the final build products will be installed \"\n            \"(default is [scratch-path]/installed)\"\n        ),\n    )\n    add_common_arg(\n        \"--num-jobs\",\n        type=int,\n        help=(\n            \"Number of concurrent jobs to use while building. \"\n            \"(default=number of cpu cores)\"\n        ),\n    )\n    add_common_arg(\n        \"--use-shipit\",\n        help=\"use the real ShipIt instead of the simple shipit transformer\",\n        action=\"store_true\",\n        default=False,\n    )\n    add_common_arg(\n        \"--facebook-internal\",\n        help=\"Setup the build context as an FB internal build\",\n        action=\"store_true\",\n        default=None,\n    )\n    add_common_arg(\n        \"--no-facebook-internal\",\n        help=\"Perform a non-FB internal build, even when in an fbsource repository\",\n        action=\"store_false\",\n        dest=\"facebook_internal\",\n    )\n    add_common_arg(\n        \"--shared-libs\",\n        help=\"Build shared libraries if possible\",\n        action=\"store_true\",\n        default=False,\n    )\n    add_common_arg(\n        \"--extra-cmake-defines\",\n        help=(\n            \"Input json map that contains extra cmake defines to be used \"\n            \"when compiling the current project and all its deps. \"\n            'e.g: \\'{\"CMAKE_CXX_FLAGS\": \"--bla\"}\\''\n        ),\n    )\n    add_common_arg(\n        \"--allow-system-packages\",\n        help=\"Allow satisfying third party deps from installed system packages\",\n        action=\"store_true\",\n        default=False,\n    )\n    add_common_arg(\n        \"-v\",\n        \"--verbose\",\n        help=\"Print more output\",\n        action=\"store_true\",\n        default=False,\n    )\n    add_common_arg(\n        \"-su\",\n        \"--skip-upload\",\n        help=\"skip upload steps\",\n        action=\"store_true\",\n        default=False,\n    )\n    add_common_arg(\n        \"--lfs-path\",\n        help=\"Provide a parent directory for lfs when fbsource is unavailable\",\n        default=None,\n    )\n    add_common_arg(\n        \"--build-skip-lfs-download\",\n        action=\"store_true\",\n        default=False,\n        help=(\n            \"Download from the URL, rather than LFS. This is useful \"\n            \"in cases where the upstream project has uploaded a new \"\n            \"version of the archive with a different hash\"\n        ),\n    )\n    add_common_arg(\n        \"--schedule-type\",\n        nargs=\"?\",\n        help=\"Indicates how the build was activated\",\n    )\n\n    ap = argparse.ArgumentParser(\n        description=\"Get and build dependencies and projects\", parents=[common_args]\n    )\n    sub = ap.add_subparsers(\n        # metavar suppresses the long and ugly default list of subcommands on a\n        # single line.  We still render the nicer list below where we would\n        # have shown the nasty one.\n        metavar=\"\",\n        title=\"Available commands\",\n        help=\"\",\n    )\n\n    add_subcommands(sub, common_args)\n\n    args = ap.parse_args()\n    for var_name, default_value in common_defaults.items():\n        if not hasattr(args, var_name):\n            setattr(args, var_name, default_value)\n\n    return ap, args\n\n\ndef main():\n    ap, args = parse_args()\n    if getattr(args, \"func\", None) is None:\n        ap.print_help()\n        return 0\n    try:\n        return args.func(args)\n    except UsageError as exc:\n        ap.error(str(exc))\n        return 1\n    except TransientFailure as exc:\n        print(\"TransientFailure: %s\" % str(exc))\n        # This return code is treated as a retryable transient infrastructure\n        # error by Facebook's internal CI, rather than eg: a build or code\n        # related error that needs to be fixed before progress can be made.\n        return 128\n    except subprocess.CalledProcessError as exc:\n        print(\"%s\" % str(exc), file=sys.stderr)\n        print(\"!! Failed\", file=sys.stderr)\n        return 1\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "build/fbcode_builder/manifests/CLI11",
    "content": "[manifest]\nname = CLI11\n\n[download]\nurl = https://github.com/CLIUtils/CLI11/archive/v2.0.0.tar.gz\nsha256 = 2c672f17bf56e8e6223a3bfb74055a946fa7b1ff376510371902adb9cb0ab6a3\n\n[build]\nbuilder = cmake\nsubdir = CLI11-2.0.0\n\n[cmake.defines]\nCLI11_BUILD_TESTS = OFF\nCLI11_BUILD_EXAMPLES = OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/autoconf",
    "content": "[manifest]\nname = autoconf\n\n[debs]\nautoconf\n\n[homebrew]\nautoconf\n\n[rpms]\nautoconf\n\n[pps]\nautoconf\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz\nsha256 = 954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969\n\n[build]\nbuilder = autoconf\nsubdir = autoconf-2.69\n"
  },
  {
    "path": "build/fbcode_builder/manifests/automake",
    "content": "[manifest]\nname = automake\n\n[homebrew]\nautomake\n\n[debs]\nautomake\n\n[rpms]\nautomake\n\n[pps]\nautomake\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/automake/automake-1.16.1.tar.gz\nsha256 = 608a97523f97db32f1f5d5615c98ca69326ced2054c9f82e65bade7fc4c9dea8\n\n[build]\nbuilder = autoconf\nsubdir = automake-1.16.1\n\n[dependencies]\nautoconf\n"
  },
  {
    "path": "build/fbcode_builder/manifests/benchmark",
    "content": "[manifest]\nname = benchmark\n\n[download]\nurl = https://github.com/google/benchmark/archive/refs/tags/v1.8.0.tar.gz\nsha256 = ea2e94c24ddf6594d15c711c06ccd4486434d9cf3eca954e2af8a20c88f9f172\n\n[build]\nbuilder = cmake\nsubdir = benchmark-1.8.0/\n\n[cmake.defines]\nBENCHMARK_ENABLE_TESTING=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/blake3",
    "content": "[manifest]\nname = blake3\n\n[download]\nurl = https://github.com/BLAKE3-team/BLAKE3/archive/refs/tags/1.5.1.tar.gz\nsha256 = 822cd37f70152e5985433d2c50c8f6b2ec83aaf11aa31be9fe71486a91744f37\n\n[build]\nbuilder = cmake\nsubdir = BLAKE3-1.5.1/c\n"
  },
  {
    "path": "build/fbcode_builder/manifests/boost",
    "content": "[manifest]\nname = boost\n\n[download.not(os=windows)]\nurl = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.gz\nsha256 = c0685b68dd44cc46574cce86c4e17c0f611b15e195be9848dfd0769a0a207628\n\n[download.os=windows]\nurl = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.zip\nsha256 = c86bd9d9eef795b4b0d3802279419fde5221922805b073b9bd822edecb1ca28e\n\n[preinstalled.env]\n# Here we list the acceptable versions that cmake needs a hint to find\nBOOST_ROOT_1_69_0\nBOOST_ROOT_1_83_0\n\n[debs]\nlibboost-all-dev\n\n[homebrew]\nboost\n# Boost cmake detection on homebrew adds this as requirement: https://github.com/Homebrew/homebrew-core/issues/67427#issuecomment-754187345\nicu4c\n\n[pps]\nboost\n\n[rpms.all(distro=centos_stream,distro_vers=8)]\nboost169\nboost169-math\nboost169-test\nboost169-fiber\nboost169-graph\nboost169-log\nboost169-openmpi\nboost169-timer\nboost169-chrono\nboost169-locale\nboost169-thread\nboost169-atomic\nboost169-random\nboost169-static\nboost169-contract\nboost169-date-time\nboost169-iostreams\nboost169-container\nboost169-coroutine\nboost169-filesystem\nboost169-system\nboost169-stacktrace\nboost169-regex\nboost169-devel\nboost169-context\nboost169-python3-devel\nboost169-type_erasure\nboost169-wave\nboost169-python3\nboost169-serialization\nboost169-program-options\n\n[rpms.distro=fedora]\nboost-devel\nboost-static\n\n[build]\nbuilder = boost\njob_weight_mib = 512\npatchfile = boost_1_83_0.patch\n\n[b2.args]\n--with-atomic\n--with-chrono\n--with-container\n--with-context\n--with-contract\n--with-coroutine\n--with-date_time\n--with-exception\n--with-fiber\n--with-filesystem\n--with-graph\n--with-graph_parallel\n--with-iostreams\n--with-locale\n--with-log\n--with-math\n--with-mpi\n--with-program_options\n--with-python\n--with-random\n--with-regex\n--with-serialization\n--with-stacktrace\n--with-system\n--with-test\n--with-thread\n--with-timer\n--with-type_erasure\n\n[bootstrap.args.os=darwin]\n# Not really gcc, but CI puts a broken clang in the PATH, and saying gcc\n# here selects the correct one from Xcode.\n--with-toolset=gcc\n\n[b2.args.os=linux]\n# RHEL hardened gcc is not compatible with PCH\n# https://bugzilla.redhat.com/show_bug.cgi?id=1806545\npch=off\n\n[b2.args.os=darwin]\ntoolset=clang\n# Since Xcode 15.3 std::piecewise_construct is only visible in C++17 and later modes\ncxxflags=\"-DBOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT=0\"\n\n[b2.args.all(os=windows,fb=on)]\ntoolset=msvc-14.3\n"
  },
  {
    "path": "build/fbcode_builder/manifests/boost-python",
    "content": "[manifest]\nname = boost-python\n\n[download.not(os=windows)]\nurl = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.gz\nsha256 = c0685b68dd44cc46574cce86c4e17c0f611b15e195be9848dfd0769a0a207628\n\n[download.os=windows]\nurl = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.zip\nsha256 = c86bd9d9eef795b4b0d3802279419fde5221922805b073b9bd822edecb1ca28e\n\n[preinstalled.env]\n# Here we list the acceptable versions that cmake needs a hint to find\nBOOST_ROOT_1_69_0\nBOOST_ROOT_1_83_0\n\n[homebrew]\nboost\n# Boost cmake detection on homebrew adds this as requirement: https://github.com/Homebrew/homebrew-core/issues/67427#issuecomment-754187345\nicu4c\n\n[pps]\nboost\n\n[rpms.all(distro=centos_stream,distro_vers=8)]\nboost169\nboost169-math\nboost169-test\nboost169-fiber\nboost169-graph\nboost169-log\nboost169-openmpi\nboost169-timer\nboost169-chrono\nboost169-locale\nboost169-thread\nboost169-atomic\nboost169-random\nboost169-static\nboost169-contract\nboost169-date-time\nboost169-iostreams\nboost169-container\nboost169-coroutine\nboost169-filesystem\nboost169-system\nboost169-stacktrace\nboost169-regex\nboost169-devel\nboost169-context\nboost169-python3-devel\nboost169-type_erasure\nboost169-wave\nboost169-python3\nboost169-serialization\nboost169-program-options\n\n[rpms.distro=fedora]\nboost-devel\nboost-static\n\n[build]\nbuilder = boost\njob_weight_mib = 512\npatchfile = boost_1_83_0.patch\n\n[build.not(os=linux)]\nbuilder = nop\n\n[b2.args]\n--with-atomic\n--with-chrono\n--with-container\n--with-context\n--with-contract\n--with-coroutine\n--with-date_time\n--with-exception\n--with-fiber\n--with-filesystem\n--with-graph\n--with-graph_parallel\n--with-iostreams\n--with-locale\n--with-log\n--with-math\n--with-mpi\n--with-program_options\n--with-python\n--with-random\n--with-regex\n--with-serialization\n--with-stacktrace\n--with-system\n--with-test\n--with-thread\n--with-timer\n--with-type_erasure\n\n[bootstrap.args.os=darwin]\n# Not really gcc, but CI puts a broken clang in the PATH, and saying gcc\n# here selects the correct one from Xcode.\n--with-toolset=gcc\n\n[b2.args.os=linux]\n# RHEL hardened gcc is not compatible with PCH\n# https://bugzilla.redhat.com/show_bug.cgi?id=1806545\npch=off\n# Python extensions need -fPIC for static library linking into shared objects\ncxxflags=\"-fPIC\"\n\n[b2.args.os=darwin]\ntoolset=clang\n# Since Xcode 15.3 std::piecewise_construct is only visible in C++17 and later modes\ncxxflags=\"-DBOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT=0\"\n\n[b2.args.all(os=windows,fb=on)]\ntoolset=msvc-14.3\n"
  },
  {
    "path": "build/fbcode_builder/manifests/bz2",
    "content": "[manifest]\nname = bz2\n\n[debs]\nlibbz2-dev\nbzip2\n\n[homebrew]\nbzip2\n\n[rpms]\nbzip2-devel\nbzip2\n\n[download]\nurl = https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz\nsha256 = ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269\n\n[build.not(os=windows)]\nbuilder = make\nsubdir = bzip2-1.0.8\n\n[make.build_args.os=linux]\n# python bz2 support on linux needs dynamic library\n-f\nMakefile-libbz2_so\n\n[make.install_args]\ninstall\n\n[build.os=windows]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/c-ares",
    "content": "[manifest]\nname = c-ares\n\n[download]\nurl = https://github.com/c-ares/c-ares/releases/download/v1.34.6/c-ares-1.34.6.tar.gz\nsha256 = 912dd7cc3b3e8a79c52fd7fb9c0f4ecf0aaa73e45efda880266a2d6e26b84ef5\n\n[build]\nbuilder = cmake\nsubdir = c-ares-1.34.6\n\n[cmake.defines]\nCARES_STATIC = ON\n\n[cmake.defines.shared_libs=off]\nCARES_SHARED = OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cabal",
    "content": "[manifest]\nname = cabal\n\n[download.os=linux]\nurl = https://downloads.haskell.org/~cabal/cabal-install-3.6.2.0/cabal-install-3.6.2.0-x86_64-linux-deb10.tar.xz\nsha256 = 4759b56e9257e02f29fa374a6b25d6cb2f9d80c7e3a55d4f678a8e570925641c\n\n[build]\nbuilder = nop\n\n[install.files]\ncabal = bin/cabal\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cachelib",
    "content": "[manifest]\nname = cachelib\nfbsource_path = fbcode/cachelib\nshipit_project = cachelib\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/cachelib.git\n\n[build]\nbuilder = cmake\nsubdir = cachelib\njob_weight_mib = 2048\n\n[dependencies]\nzlib\nfizz\nfmt\nfolly\nfbthrift\ngoogletest\nsparsemap\nwangle\nzstd\nmvfst\nnuma\nlibaio\nmagic_enum\n# cachelib also depends on openssl but since the latter requires a platform-\n# specific configuration we rely on the folly manifest to provide this\n# dependency to avoid duplication.\n\n[dependencies.all(distro=centos_stream,distro_vers=9)]\ngcc14\n\n[cmake.defines.all(distro=centos_stream,distro_vers=9)]\nCMAKE_C_COMPILER=/opt/rh/gcc-toolset-14/root/usr/bin/gcc\nCMAKE_CXX_COMPILER=/opt/rh/gcc-toolset-14/root/usr/bin/g++\n\n[shipit.pathmap]\nfbcode/cachelib = cachelib\nfbcode/cachelib/public_tld = .\n\n[shipit.strip]\n^fbcode/cachelib/examples(/|$)\n^fbcode/cachelib/facebook(/|$)\n^fbcode/cachelib/public_tld/website/docs/facebook(/|$)\n^fbcode/cachelib/public_tld/website/node_modules(/|$)\n^fbcode/cachelib/public_tld/website/build(/|$)\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cinderx-3_14",
    "content": "[manifest]\nname = cinderx-3_14\nfbsource_path = fbcode/cinderx\nshipit_project = facebookincubator/cinderx\n\n[git]\nrepo_url = https://github.com/facebookincubator/cinderx.git\n\n[build.os=linux]\nbuilder = setup-py\n\n[build.not(os=linux)]\nbuilder = nop\n\n[dependencies]\npython-setuptools\npython-3_14\n\n[shipit.pathmap]\nfbcode/cinderx = cinderx\nfbcode/cinderx/oss_toplevel = .\n\n[setup-py.test]\npython_script = cinderx/PythonLib/test_cinderx/test_oss_quick.py\n\n[setup-py.env]\nCINDERX_ENABLE_PGO=1\nCINDERX_ENABLE_LTO=1\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cinderx-main",
    "content": "# For building CinderX against CPython main.\n# Note that externally this can be broken  because in that environment we will\n# be checking out the head of the CPython repo. However CinderX is only built\n# and tested against our internal copy of CPython which updates ~daily, and so\n# may be behind CPython head.\n\n[manifest]\nname = cinderx-main\nfbsource_path = fbcode/cinderx\nshipit_project = facebookincubator/cinderx\n\n[git]\nrepo_url = https://github.com/facebookincubator/cinderx.git\n\n[build.os=linux]\nbuilder = setup-py\n\n[build.not(os=linux)]\nbuilder = nop\n\n[dependencies]\npython-setuptools\npython-main\n\n[shipit.pathmap]\nfbcode/cinderx = cinderx\nfbcode/cinderx/oss_toplevel = .\n\n[setup-py.test]\npython_script = cinderx/PythonLib/test_cinderx/test_oss_quick.py\n\n[setup-py.env]\nCINDERX_ENABLE_PGO=1\nCINDERX_ENABLE_LTO=1\n"
  },
  {
    "path": "build/fbcode_builder/manifests/clang",
    "content": "[manifest]\nname = clang\n\n[rpms]\nclang15-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/clang19",
    "content": "[manifest]\nname = clang19\n\n[debs.os=linux]\nclang-19\n\n[rpms.os=linux]\nclang\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cmake",
    "content": "[manifest]\nname = cmake\n\n[homebrew]\ncmake\n\n# 18.04 cmake is too old\n[debs.not(all(distro=ubuntu,distro_vers=\"18.04\"))]\ncmake\n\n[rpms]\ncmake\n\n[pps]\ncmake\n\n[dependencies]\nninja\n\n[download.os=windows]\nurl = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4-windows-x86_64.zip\nsha256 = 965d2f001c3ca807d288f2b6b15c42b25579a0e73ef12c2a72c95f4c69123638\n\n[download.os=darwin]\nurl = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4-macos-universal.tar.gz\nsha256 = df90016635e3183834143c6d94607f0804fe9762f7cc6032f6a4afd7c19cd43b\n\n[download.any(os=linux,os=freebsd)]\nurl = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4.tar.gz\nsha256 = 87a4060298f2c6bb09d479de1400bc78195a5b55a65622a7dceeb3d1090a1b16\n\n[build.os=windows]\nbuilder = nop\nsubdir = cmake-3.20.4-windows-x86_64\n\n[build.os=darwin]\nbuilder = nop\nsubdir = cmake-3.20.4-macos-universal\n\n[install.files.os=darwin]\nCMake.app/Contents/bin = bin\nCMake.app/Contents/share = share\n\n[build.any(os=linux,os=freebsd)]\nbuilder = cmakebootstrap\nsubdir = cmake-3.20.4\n\n[make.install_args.any(os=linux,os=freebsd)]\ninstall\n"
  },
  {
    "path": "build/fbcode_builder/manifests/cpptoml",
    "content": "[manifest]\nname = cpptoml\n\n[homebrew]\ncpptoml\n\n[download]\nurl = https://github.com/chadaustin/cpptoml/archive/refs/tags/v0.1.2.tar.gz\nsha256 = beda37e94f9746874436c8090c045fd80ae6f8a51f7c668c932a2b110a4fc277\n\n[build]\nbuilder = cmake\nsubdir = cpptoml-0.1.2\n\n[cmake.defines.os=freebsd]\nENABLE_LIBCXX=NO\n"
  },
  {
    "path": "build/fbcode_builder/manifests/double-conversion",
    "content": "[manifest]\nname = double-conversion\n\n[download]\nurl = https://github.com/google/double-conversion/archive/v3.1.4.tar.gz\nsha256 = 95004b65e43fefc6100f337a25da27bb99b9ef8d4071a36a33b5e83eb1f82021\n\n[homebrew]\ndouble-conversion\n\n[debs]\nlibdouble-conversion-dev\n\n[rpms]\ndouble-conversion\ndouble-conversion-devel\n\n[pps]\ndouble-conversion\n\n[build]\nbuilder = cmake\nsubdir = double-conversion-3.1.4\n"
  },
  {
    "path": "build/fbcode_builder/manifests/double-conversion-python",
    "content": "[manifest]\nname = double-conversion-python\n\n[download]\nurl = https://github.com/google/double-conversion/archive/v3.1.4.tar.gz\nsha256 = 95004b65e43fefc6100f337a25da27bb99b9ef8d4071a36a33b5e83eb1f82021\n\n[homebrew]\ndouble-conversion\n\n[debs]\nlibdouble-conversion-dev\n\n[rpms]\ndouble-conversion\ndouble-conversion-devel\n\n[pps]\ndouble-conversion\n\n[build]\nbuilder = cmake\nsubdir = double-conversion-3.1.4\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/eden",
    "content": "[manifest]\nname = eden\nfbsource_path = fbcode/eden\nshipit_project = eden\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/sapling.git\n\n[github.actions]\nrun_tests = off\n\n[sandcastle]\nrun_tests = off\n\n[build]\nbuilder = cmake\n\n[dependencies]\nblake3\ngoogletest\nfolly\nfbthrift\nfb303\ncpptoml\nrocksdb\nre2\nlibgit2\npexpect\npython-psutil\npython-toml\npython-filelock\nedencommon\nrust-shed\n\n[dependencies.fbsource=on]\nrust\n\n# macOS ships with sqlite3, and some of the core system\n# frameworks require that that version be linked rather\n# than the one we might build for ourselves here, so we\n# skip building it on macos.\n[dependencies.not(os=darwin)]\nsqlite3\n\n[dependencies.os=darwin]\nosxfuse\n\n[dependencies.not(os=windows)]\n# TODO: teach getdeps to compile curl on Windows.\n# Enabling curl on Windows requires us to find a way to compile libcurl with\n# msvc.\nlibcurl\n# Added so that OSS doesn't see system \"python\" which is python 2 on darwin and some linux\npython\n# TODO: teach getdeps to compile lmdb on Windows.\nlmdb\n\n[dependencies.test=on]\n# sapling CLI is needed to run the tests\nsapling\n\n[shipit.pathmap.fb=on]\n# for internal builds that use getdeps\nfbcode/fb303 = fb303\nfbcode/common/rust/shed = common/rust/shed\nfbcode/thrift/lib/cpp = thrift/lib/cpp\nfbcode/thrift/lib/cpp2 = thrift/lib/cpp2\nfbcode/thrift/lib/java = thrift/lib/java\nfbcode/thrift/lib/py = thrift/lib/py\nfbcode/thrift/lib/python = thrift/lib/python\nfbcode/thrift/lib/rust = thrift/lib/rust\n\n[shipit.pathmap]\n# Map hostcaps for now as eden C++ includes its .h. Rust-shed should install it\nfbcode/common/rust/shed/hostcaps = common/rust/shed/hostcaps\nfbcode/configerator/structs/scm/hg = configerator/structs/scm/hg\nfbcode/eden/oss = .\nfbcode/eden = eden\nfbcode/tools/lfs = tools/lfs\n\n[shipit.pathmap.fb=off]\nfbcode/eden/fs/public_autocargo = eden/fs\nfbcode/eden/scm/public_autocargo = eden/scm\nfbcode/common/rust/shed/hostcaps/public_cargo = common/rust/shed/hostcaps\nfbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg\n\n[shipit.strip]\n^fbcode/eden/addons/.*$\n^fbcode/eden/fs/eden-config\\.h$\n^fbcode/eden/fs/py/eden/config\\.py$\n^fbcode/eden/hg-server/.*$\n^fbcode/eden/mononoke/(?!lfs_protocol)\n^fbcode/eden/scm/build/.*$\n^fbcode/eden/scm/lib/third-party/rust/.*/Cargo.toml$\n^fbcode/eden/website/.*$\n^fbcode/eden/.*/\\.cargo/.*$\n/Cargo\\.lock$\n\\.pyc$\n\n[shipit.strip.fb=off]\n^fbcode/common/rust/shed(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/fs(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/scm(?!/public_autocargo|/saplingnative).*/Cargo\\.toml$\n^.*/facebook/.*$\n^.*/fb/.*$\n\n[cmake.defines.all(fb=on,os=windows)]\nENABLE_GIT=OFF\nINSTALL_PYTHON_LIB=ON\n\n[cmake.defines.all(not(fb=on),os=windows)]\nENABLE_GIT=OFF\n\n[cmake.defines.fbsource=on]\nUSE_CARGO_VENDOR=ON\n\n[cmake.defines.fb=on]\nIS_FB_BUILD=ON\n\n[depends.environment]\nEDEN_VERSION_OVERRIDE\n"
  },
  {
    "path": "build/fbcode_builder/manifests/edencommon",
    "content": "[manifest]\nname = edencommon\nfbsource_path = fbcode/eden/common\nshipit_project = edencommon\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookexperimental/edencommon.git\n\n[build]\nbuilder = cmake\n\n[dependencies]\nfbthrift\nfb303\nfmt\nfolly\ngflags\nglog\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n[shipit.pathmap]\nfbcode/eden/common = eden/common\nfbcode/eden/common/oss = .\n\n[shipit.strip]\n@README.facebook@\n"
  },
  {
    "path": "build/fbcode_builder/manifests/exprtk",
    "content": "[manifest]\nname = exprtk\n\n[download]\nurl = https://github.com/ArashPartow/exprtk/archive/refs/tags/0.0.1.tar.gz\nsha256 = fb72791c88ae3b3426e14fdad630027715682584daf56b973569718c56e33f28\n\n[build.not(os=windows)]\nbuilder = nop\nsubdir = exprtk-0.0.1\n\n[install.files]\nexprtk.hpp = exprtk.hpp\n\n[dependencies]\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fast_float",
    "content": "[manifest]\nname = fast_float\n\n[download]\nurl = https://github.com/fastfloat/fast_float/archive/refs/tags/v8.0.0.tar.gz\nsha256 = f312f2dc34c61e665f4b132c0307d6f70ad9420185fa831911bc24408acf625d\n\n[build]\nbuilder = cmake\nsubdir = fast_float-8.0.0\n\n[cmake.defines]\nFASTFLOAT_TEST = OFF\nFASTFLOAT_SANITIZE = OFF\n\n[debs.not(all(distro=ubuntu,any(distro_vers=\"18.04\",distro_vers=\"20.04\",distro_vers=\"22.04\",distro_vers=\"24.04\")))]\nlibfast-float-dev\n\n[rpms.distro=fedora]\nfast_float-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fatal",
    "content": "[manifest]\nname = fatal\nfbsource_path = fbcode/fatal\nshipit_project = fatal\n\n[git]\nrepo_url = https://github.com/facebook/fatal.git\n\n[shipit.pathmap]\nfbcode/fatal = fatal\nfbcode/fatal/public_tld = .\n\n[build]\nbuilder = nop\nsubdir = .\n\n[install.files]\nfatal/portability.h = fatal/portability.h\nfatal/preprocessor.h = fatal/preprocessor.h\nfatal/container = fatal/container\nfatal/functional = fatal/functional\nfatal/math = fatal/math\nfatal/string = fatal/string\nfatal/type = fatal/type\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fb303",
    "content": "[manifest]\nname = fb303\nfbsource_path = fbcode/fb303\nshipit_project = fb303\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/fb303.git\n\n[cargo]\ncargo_config_file = source/fb303/thrift/.cargo/config.toml\n\n[crate.pathmap]\nfb303_core = fb303/thrift/rust\n\n[build]\nbuilder = cmake\n\n[dependencies]\nfolly\ngflags\nglog\nfbthrift\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n[shipit.pathmap]\nfbcode/fb303/github = .\nfbcode/fb303/public_autocargo = fb303\nfbcode/fb303 = fb303\n\n[shipit.strip]\n^fbcode/fb303/(?!public_autocargo).+/Cargo\\.toml$\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fboss",
    "content": "[manifest]\nname = fboss\nfbsource_path = fbcode/fboss\nshipit_project = fboss\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/fboss.git\n\n[build.os=linux]\nbuilder = cmake\n# fboss files take a lot of RAM to compile.\njob_weight_mib = 3072\n\n[build.not(os=linux)]\nbuilder = nop\n\n[dependencies]\nfolly\nfb303\nwangle\nfizz\nmvfst\nfmt\nlibsodium\ngoogletest\nzstd\nfatal\nfbthrift\niproute2\nlibusb\nlibcurl\nlibnl\nlibsai\nre2\npython\nyaml-cpp\nlibyaml\nCLI11\nexprtk\nnlohmann-json\nlibgpiod\nsystemd\nrange-v3\ntabulate\ngcc12\npython-pyyaml\n\n# ShipitPathMap always assume to use directory to fetch the source code.\n# If you need to sync the files to fboss github repo, please make changes in\n# configerator/source/opensource/shipit_config/facebook/fboss.cconf\n[shipit.pathmap]\nfbcode/fboss/github = .\nfbcode/fboss/common = common\nfbcode/fboss = fboss\n# NOTE: Although this directory has other thrift files might not be ready for\n# opensource, this pathmap should only be used internally `getdeps.py fetch`\nfbcode/configerator/structs/neteng/fboss/thrift = configerator/structs/neteng/fboss/thrift\n\n[shipit.strip]\n^fbcode/fboss/github/docs/.*\n^fbcode/fboss/oss/.*\n^fbcode/fboss/github/.github/.*\n^fbcode/fboss/github/fboss-image/.*\n^fbcode/fboss/github/.pre-commit-config.yaml\n^fbcode/fboss/github/requirements-dev.txt\n^fbcode/fboss/.llms/.*\n\n[sandcastle]\nrun_tests = off\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fbthrift",
    "content": "[manifest]\nname = fbthrift\nfbsource_path = xplat/thrift\nshipit_project = fbthrift\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/fbthrift.git\n\n[cargo]\ncargo_config_file =  source/thrift/lib/rust/.cargo/config.toml\n\n[crate.pathmap]\nfbthrift = thrift/lib/rust\n\n[build]\nbuilder = cmake\njob_weight_mib = 2048\n\n[cmake.defines.all(not(os=windows),test=on)]\nenable_tests=ON\n\n[cmake.defines.any(os=windows,test=off)]\nenable_tests=OFF\n\n[dependencies]\nfizz\nfmt\nfolly\ngoogletest\nlibsodium\nwangle\nzstd\nmvfst\nxxhash\n# Thrift also depends on openssl but since the latter requires a platform-\n# specific configuration we rely on the folly manifest to provide this\n# dependency to avoid duplication.\n\n[shipit.pathmap]\nxplat/thrift/public_tld = .\nxplat/thrift = thrift\n\n[shipit.strip]\n^xplat/thrift/thrift-config\\.h$\n^xplat/thrift/perf/canary.py$\n^xplat/thrift/perf/loadtest.py$\n^xplat/thrift/.castle/.*\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fbthrift-python",
    "content": "[manifest]\nname = fbthrift-python\nfbsource_path = xplat/thrift\nshipit_project = fbthrift\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/fbthrift.git\n\n[cargo]\ncargo_config_file =  source/thrift/lib/rust/.cargo/config.toml\n\n[crate.pathmap]\nfbthrift = thrift/lib/rust\n\n[build]\nbuilder = cmake\njob_weight_mib = 2048\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines.all(not(os=windows),test=on)]\nenable_tests=ON\n\n[cmake.defines.any(os=windows,test=off)]\nenable_tests=OFF\n\n[cmake.defines.os=linux]\nthrift_python=ON\nenable_tests=ON\n\n[dependencies]\nfizz-python\nfmt-python\nfolly-python\ngoogletest\nlibsodium\nwangle-python\nzstd-python\nmvfst-python\nxxhash\n# Thrift also depends on openssl but since the latter requires a platform-\n# specific configuration we rely on the folly manifest to provide this\n# dependency to avoid duplication.\n\n[dependencies.os=linux]\nlibaio-python\nlibevent-python\n\n[shipit.pathmap]\nxplat/thrift/public_tld = .\nxplat/thrift = thrift\n\n[shipit.strip]\n^xplat/thrift/thrift-config\\.h$\n^xplat/thrift/perf/canary.py$\n^xplat/thrift/perf/loadtest.py$\n^xplat/thrift/.castle/.*\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fizz",
    "content": "[manifest]\nname = fizz\nfbsource_path = fbcode/fizz\nshipit_project = fizz\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookincubator/fizz.git\n\n[build]\nbuilder = cmake\nsubdir = fizz\n\n[cmake.defines]\nBUILD_EXAMPLES = OFF\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.all(os=windows, test=on)]\nBUILD_TESTS = OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[dependencies]\nfolly\nliboqs\nlibsodium\nzlib\nzstd\n\n[dependencies.all(test=on, not(os=windows))]\ngoogletest\n\n[shipit.pathmap]\nfbcode/fizz/public_tld = .\nfbcode/fizz = fizz\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fizz-python",
    "content": "[manifest]\nname = fizz-python\nfbsource_path = fbcode/fizz\nshipit_project = fizz\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookincubator/fizz.git\n\n[build]\nbuilder = cmake\nsubdir = fizz\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nBUILD_EXAMPLES = OFF\n\n[cmake.defines.os=linux]\nCMAKE_POSITION_INDEPENDENT_CODE = ON\nBUILD_SHARED_LIBS = ON\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.all(os=windows, test=on)]\nBUILD_TESTS = OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[dependencies]\nfolly-python\nliboqs\nlibsodium\nzlib-python\nzstd-python\n\n[dependencies.all(test=on, not(os=windows))]\ngoogletest\n\n[shipit.pathmap]\nfbcode/fizz/public_tld = .\nfbcode/fizz = fizz\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fmt",
    "content": "[manifest]\nname = fmt\n\n[download]\nurl = https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz\nsha256 = ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea\n\n[build]\nbuilder = cmake\nsubdir = fmt-12.1.0\n\n[cmake.defines]\nFMT_TEST = OFF\nFMT_DOC = OFF\n\n[homebrew]\nfmt\n\n[rpms.distro=fedora]\nfmt-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/fmt-python",
    "content": "[manifest]\nname = fmt-python\n\n[download]\nurl = https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz\nsha256 = ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea\n\n[build]\nbuilder = cmake\nsubdir = fmt-12.1.0\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nFMT_TEST = OFF\nFMT_DOC = OFF\n# Build as shared library so Python extensions can find fmt symbols at runtime\n# (fmt uses -fvisibility=hidden, so static linking leaves symbols unexported)\nBUILD_SHARED_LIBS = ON\n\n[homebrew]\nfmt\n\n[rpms.distro=fedora]\nfmt-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/folly",
    "content": "[manifest]\nname = folly\nfbsource_path = fbcode/folly\nshipit_project = folly\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/folly.git\n\n[build]\nbuilder = cmake\njob_weight_mib = 1024\n\n[dependencies]\ngflags\nglog\ngoogletest\nboost\nlibdwarf\nlibevent\nlibsodium\ndouble-conversion\nfast_float\nfmt\nlz4\nsnappy\nzstd\n# no openssl or zlib in the linux case, why?\n# these are usually installed on the system\n# and are the easiest system deps to pull in.\n# In the future we want to be able to express\n# that a system dep is sufficient in the manifest\n# for eg: openssl and zlib, but for now we don't\n# have it.\n\n# macOS doesn't expose the openssl api so we need\n# to build our own.\n[dependencies.os=darwin]\nopenssl\n\n# Windows has neither openssl nor zlib, so we get\n# to provide both\n[dependencies.os=windows]\nopenssl\nzlib\n\n[dependencies.os=linux]\nlibaio\nlibiberty\nlibunwind\n\n# xz depends on autoconf which does not build on\n# Windows\n[dependencies.not(os=windows)]\nxz\n\n[shipit.pathmap]\nfbcode/folly/public_tld = .\nfbcode/folly = folly\n\n[shipit.strip]\n^fbcode/folly/folly-config\\.h$\n^fbcode/folly/public_tld/build/facebook_.*\n\n[cmake.defines]\nBUILD_SHARED_LIBS=OFF\n\n[cmake.defines.not(os=windows)]\nBOOST_LINK_STATIC=ON\n\n[cmake.defines.os=freebsd]\nLIBDWARF_FOUND=NO\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\nBUILD_BENCHMARKS=OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\nBUILD_BENCHMARKS=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/folly-python",
    "content": "[manifest]\nname = folly-python\nfbsource_path = fbcode/folly\nshipit_project = folly\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/folly.git\n\n[build]\nbuilder = cmake\njob_weight_mib = 1024\n\n[build.not(os=linux)]\nbuilder = nop\n\n[dependencies]\ngflags\nglog\ngoogletest\nboost-python\nlibdwarf-python\nlibevent-python\nlibsodium\ndouble-conversion-python\nfast_float\nfmt-python\nlz4-python\nsnappy\nzstd-python\n# no openssl or zlib in the linux case, why?\n# these are usually installed on the system\n# and are the easiest system deps to pull in.\n# In the future we want to be able to express\n# that a system dep is sufficient in the manifest\n# for eg: openssl and zlib, but for now we don't\n# have it.\n\n# macOS doesn't expose the openssl api so we need\n# to build our own.\n[dependencies.os=darwin]\nopenssl\n\n# Windows has neither openssl nor zlib, so we get\n# to provide both\n[dependencies.os=windows]\nopenssl\nzlib\n\n[dependencies.os=linux]\nlibaio-python\nlibiberty-python\nlibunwind\n\n# xz depends on autoconf which does not build on\n# Windows\n[dependencies.not(os=windows)]\nxz\n\n[shipit.pathmap]\nfbcode/folly/public_tld = .\nfbcode/folly = folly\n\n[shipit.strip]\n^fbcode/folly/folly-config\\.h$\n^fbcode/folly/public_tld/build/facebook_.*\n\n[cmake.defines.os=linux]\nPYTHON_EXTENSIONS=ON\nBUILD_SHARED_LIBS=ON\n\n[cmake.defines.not(os=windows)]\nBOOST_LINK_STATIC=ON\n\n[cmake.defines.os=freebsd]\nLIBDWARF_FOUND=NO\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\nBUILD_BENCHMARKS=OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\nBUILD_BENCHMARKS=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/gcc12",
    "content": "[manifest]\nname = gcc12\n\n[rpms.all(distro=centos_stream,distro_vers=9)]\ngcc-toolset-12\n"
  },
  {
    "path": "build/fbcode_builder/manifests/gcc14",
    "content": "[manifest]\nname = gcc14\n\n[rpms.all(distro=centos_stream,distro_vers=9)]\ngcc-toolset-14\n"
  },
  {
    "path": "build/fbcode_builder/manifests/gflags",
    "content": "[manifest]\nname = gflags\n\n[download]\nurl = https://github.com/gflags/gflags/archive/v2.2.2.tar.gz\nsha256 = 34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf\n\n[build]\nbuilder = cmake\nsubdir = gflags-2.2.2\n\n[cmake.defines]\nBUILD_SHARED_LIBS = ON\nBUILD_STATIC_LIBS = ON\n#BUILD_gflags_nothreads_LIB = OFF\nBUILD_gflags_LIB = ON\n\n[homebrew]\ngflags\n\n[debs]\nlibgflags-dev\n\n[rpms.distro=fedora]\ngflags-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/ghc",
    "content": "[manifest]\nname = ghc\n\n[download.os=linux]\nurl = https://downloads.haskell.org/~ghc/9.2.8/ghc-9.2.8-x86_64-fedora27-linux.tar.xz\nsha256 = 845f63cd365317bb764d81025554a2527dbe315d6fa268c9859e21b911bf2d3c\n\n[build]\nbuilder = autoconf\nsubdir = ghc-9.2.8\nbuild_in_src_dir = true\nonly_install = true\n\n[make.install_args]\ninstall\n"
  },
  {
    "path": "build/fbcode_builder/manifests/git-lfs",
    "content": "[manifest]\nname = git-lfs\n\n[rpms]\ngit-lfs\n\n[debs]\ngit-lfs\n\n[homebrew]\ngit-lfs\n\n# only used from system packages currently\n[build]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/glean",
    "content": "[manifest]\nname = glean\nfbsource_path = fbcode/glean\nshipit_project = facebookincubator/Glean\nuse_shipit = true\n\n[shipit.pathmap]\n# These are only used by target determinator to trigger builds, the\n# real path mappings are in the ShipIt config.\nfbcode/glean = glean\nfbcode/common/hs = hsthrift\n\n[subprojects]\nhsthrift = hsthrift\n\n[dependencies]\ncabal\nghc\ngflags\nglog\nfolly\nrocksdb\nxxhash\nllvm\nclang\nre2\n\n[build]\nbuilder = make\n\n[make.build_args]\nsetup-folly\nsetup-folly-version\ncabal-update\nall\nglean-hie\nglass\nglean-clang\nEXTRA_GHC_OPTS=-j4 +RTS -A32m -n4m -RTS\nCABAL_CONFIG_FLAGS=-f-hack-tests -f-typescript-tests -f-python-tests -f-dotnet-tests -f-go-tests -f-rust-tests -f-java-lsif-tests -f-flow-tests -f-bundled-folly\n\n[make.install_args]\ninstall\n\n[make.test_args]\ntest\nEXTRA_GHC_OPTS=-j4 +RTS -A32m -n4m -RTS\nCABAL_CONFIG_FLAGS=-f-hack-tests -f-typescript-tests -f-python-tests -f-dotnet-tests -f-go-tests -f-rust-tests -f-java-lsif-tests -f-flow-tests -f-bundled-folly\n"
  },
  {
    "path": "build/fbcode_builder/manifests/glog",
    "content": "[manifest]\nname = glog\n\n[download]\nurl = https://github.com/google/glog/archive/v0.5.0.tar.gz\nsha256 = eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5\n\n[build]\nbuilder = cmake\nsubdir = glog-0.5.0\n\n[dependencies]\ngflags\n\n[cmake.defines]\nBUILD_SHARED_LIBS=ON\nBUILD_TESTING=NO\nWITH_PKGCONFIG=ON\n\n[cmake.defines.os=freebsd]\nHAVE_TR1_UNORDERED_MAP=OFF\nHAVE_TR1_UNORDERED_SET=OFF\n\n[homebrew]\nglog\n\n# on ubuntu glog brings in liblzma-dev, which in turn breaks watchman tests\n[debs.not(distro=ubuntu)]\nlibgoogle-glog-dev\n\n[rpms.distro=fedora]\nglog-devel\n\n"
  },
  {
    "path": "build/fbcode_builder/manifests/googletest",
    "content": "[manifest]\nname = googletest\n\n[download]\nurl = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz\nsha256 = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c\n\n[build]\nbuilder = cmake\nsubdir = googletest-1.17.0\n\n[cmake.defines]\n# Everything else defaults to the shared runtime, so tell gtest that\n# it should not use its choice of the static runtime\ngtest_force_shared_crt=ON\n\n[cmake.defines.os=windows]\nBUILD_SHARED_LIBS=ON\n\n[homebrew]\ngoogletest\n\n# packaged googletest is too old\n[debs.not(all(distro=ubuntu,any(distro_vers=\"18.04\",distro_vers=\"20.04\",distro_vers=\"22.04\")))]\nlibgtest-dev\nlibgmock-dev\n\n[rpms.distro=fedora]\ngmock-devel\ngtest-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/gperf",
    "content": "[manifest]\nname = gperf\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/gperf/gperf-3.1.tar.gz\nsha256 = 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = gperf-3.1\n\n[build.os=windows]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/hexdump",
    "content": "[manifest]\nname = hexdump\n\n[rpms]\nutil-linux\n\n[debs]\nbsdmainutils\n\n# only used from system packages currently\n[build]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/hsthrift",
    "content": "[manifest]\nname = hsthrift\nfbsource_path = fbcode/common/hs\nshipit_project = facebookincubator/hsthrift\nuse_shipit = true\n\n[shipit.pathmap]\n# These are only used by target determinator to trigger builds, the\n# real path mappings are in the ShipIt config.\nfbcode/common/hs = .\n\n[dependencies]\ncabal\nghc\ngflags\nglog\nfolly\nfbthrift\nwangle\nfizz\nboost\n\n[build]\nbuilder = make\n\n[make.build_args]\nsetup-folly\nsetup-meta\ncabal-update\nall\n\n[make.install_args]\ninstall\n\n[make.test_args]\ntest\n"
  },
  {
    "path": "build/fbcode_builder/manifests/iproute2",
    "content": "[manifest]\nname = iproute2\n\n[download]\nurl = https://mirrors.edge.kernel.org/pub/linux/utils/net/iproute2/iproute2-4.12.0.tar.gz\nsha256 = 46612a1e2d01bb31932557bccdb1b8618cae9a439dfffc08ef35ed8e197f14ce\n\n[build.os=linux]\nbuilder = iproute2\nsubdir = iproute2-4.12.0\npatchfile = iproute2_oss.patch\n\n[build.not(os=linux)]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/jom",
    "content": "# jom is compatible with MSVC nmake, but adds the /j<number of jobs> argment which \n# speeds up openssl build a lot\n[manifest]\nname = jom\n\n# see https://download.qt.io/official_releases/jom/changelog.txt for latest version\n[download.os=windows]\nurl = https://download.qt.io/official_releases/jom/jom_1_1_4.zip\nsha256 = d533c1ef49214229681e90196ed2094691e8c4a0a0bef0b2c901debcb562682b\n\n[build.os=windows]\nbuilder = nop\n\n[install.files.os=windows]\n. = bin\n"
  },
  {
    "path": "build/fbcode_builder/manifests/jq",
    "content": "[manifest]\nname = jq\n\n[rpms.distro=fedora]\njq\n\n[homebrew]\njq\n\n[download.not(os=windows)]\n# we use jq-1.7+ to get fix for number truncation https://github.com/jqlang/jq/pull/1752\nurl = https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-1.7.1.tar.gz\nsha256 = 478c9ca129fd2e3443fe27314b455e211e0d8c60bc8ff7df703873deeee580c2\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = jq-1.7.1\n\n[build.os=windows]\nbuilder = nop\n\n[autoconf.args]\n# This argument turns off some developers tool and it is recommended in jq's\n# README\n--disable-maintainer-mode\n"
  },
  {
    "path": "build/fbcode_builder/manifests/katran",
    "content": "[manifest]\nname = katran\nfbsource_path = fbcode/katran\nshipit_project = katran\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookincubator/katran.git\n\n[build.not(os=linux)]\nbuilder = nop\n\n[build.os=linux]\nbuilder = cmake\nsubdir = .\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n[dependencies]\nfolly\nfizz\nlibbpf\nlibmnl\nzlib\ngoogletest\nfmt\n\n[debs]\nlibssl-dev\n\n[shipit.pathmap]\nfbcode/katran/public_root = .\nfbcode/katran = katran\n\n[shipit.strip]\n^fbcode/katran/facebook\n^fbcode/katran/OSS_SYNC\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libaio",
    "content": "[manifest]\nname = libaio\n\n[debs]\nlibaio-dev\n\n[rpms.distro=centos_stream]\nlibaio-devel\n\n[download]\nurl = https://pagure.io/libaio/archive/libaio-0.3.113/libaio-libaio-0.3.113.tar.gz\nsha256 = 716c7059703247344eb066b54ecbc3ca2134f0103307192e6c2b7dab5f9528ab\n\n[build]\nbuilder = make\nsubdir = libaio-libaio-0.3.113\n\n[make.build_args.shared_libs=off]\nENABLE_SHARED=0\n\n[make.install_args]\ninstall\n\n[make.install_args.shared_libs=off]\nENABLE_SHARED=0\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libaio-python",
    "content": "[manifest]\nname = libaio-python\n\n[debs]\nlibaio-dev\n\n[rpms.distro=centos_stream]\nlibaio-devel\n\n[download]\nurl = https://pagure.io/libaio/archive/libaio-0.3.113/libaio-libaio-0.3.113.tar.gz\nsha256 = 716c7059703247344eb066b54ecbc3ca2134f0103307192e6c2b7dab5f9528ab\n\n[build]\nbuilder = make\nsubdir = libaio-libaio-0.3.113\n\n[build.not(os=linux)]\nbuilder = nop\n\n[make.build_args]\nCFLAGS=-fPIC -O2\n\n[make.install_args]\ninstall\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libbpf",
    "content": "[manifest]\nname = libbpf\n\n[download]\nurl = https://github.com/libbpf/libbpf/archive/refs/tags/v1.6.2.tar.gz\nsha256 = 16f31349c70764cba8e0fad3725cc9f52f6cf952554326aa0229daaa21ef4fbd\n\n# BPF only builds on linux, so make it a NOP on other platforms\n[build.not(os=linux)]\nbuilder = nop\n\n[build.os=linux]\nbuilder = make\nsubdir = libbpf-1.6.2/src\n\n[make.build_args]\nBUILD_STATIC_ONLY=y\n\n# libbpf-0.3 requires uapi headers >= 5.8\n[make.install_args]\ninstall\ninstall_uapi_headers\nBUILD_STATIC_ONLY=y\n\n[dependencies]\nlibelf\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libcurl",
    "content": "[manifest]\nname = libcurl\n\n[rpms]\nlibcurl-devel\nlibcurl-minimal\n\n[debs]\nlibcurl4-openssl-dev\n\n[pps]\nlibcurl-gnutls\n\n[download]\nurl = https://curl.haxx.se/download/curl-7.65.1.tar.gz\nsha256 = 821aeb78421375f70e55381c9ad2474bf279fc454b791b7e95fc83562951c690\n\n[dependencies]\nnghttp2\n\n# We use system OpenSSL on Linux (see folly's manifest for details)\n[dependencies.not(os=linux)]\nopenssl\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = curl-7.65.1\n\n[autoconf.args]\n# fboss (which added the libcurl dep) doesn't need ldap so it is disabled here.\n# if someone in the future wants to add ldap for something else, it won't hurt\n# fboss. However, that would require adding an ldap manifest.\n#\n# For the same reason, we disable libssh2 and libidn2 which aren't really used\n# but would require adding manifests if we don't disable them.\n--disable-ldap\n--without-libssh2\n--without-libidn2\n\n[build.os=windows]\nbuilder = cmake\nsubdir = curl-7.65.1\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libdwarf",
    "content": "[manifest]\nname = libdwarf\n\n[rpms]\nlibdwarf-devel\nlibdwarf\n\n[debs]\nlibdwarf-dev\n\n[homebrew]\ndwarfutils\n\n[download]\nurl = https://www.prevanders.net/libdwarf-0.9.2.tar.xz\nsha256 = 22b66d06831a76f6a062126cdcad3fcc58540b89a1acb23c99f8861f50999ec3\n\n[build]\nbuilder = cmake\nsubdir = libdwarf-0.9.2\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libdwarf-python",
    "content": "[manifest]\nname = libdwarf-python\n\n[rpms]\nlibdwarf-devel\nlibdwarf\n\n[debs]\nlibdwarf-dev\n\n[homebrew]\ndwarfutils\n\n[download]\nurl = https://www.prevanders.net/libdwarf-0.9.2.tar.xz\nsha256 = 22b66d06831a76f6a062126cdcad3fcc58540b89a1acb23c99f8861f50999ec3\n\n[build]\nbuilder = cmake\nsubdir = libdwarf-0.9.2\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libelf",
    "content": "[manifest]\nname = libelf\n\n[rpms]\nelfutils-libelf-devel-static\n\n[debs]\nlibelf-dev\n\n[pps]\nlibelf\n\n[download]\nurl = https://sourceware.org/elfutils/ftp/0.193/elfutils-0.193.tar.bz2\nsha256 = 7857f44b624f4d8d421df851aaae7b1402cfe6bcdd2d8049f15fc07d3dde7635\n\n# libelf only makes sense on linux, so make it a NOP on other platforms\n[build.not(os=linux)]\nbuilder = nop\n\n[build.os=linux]\nbuilder = autoconf\nsubdir = elfutils-0.193\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libevent",
    "content": "[manifest]\nname = libevent\n\n[debs]\nlibevent-dev\n\n[homebrew]\nlibevent\n\n[rpms]\nlibevent-devel\n\n[pps]\nlibevent\n\n# Note that the CMakeLists.txt file is present only in\n# git repo and not in the release tarball, so take care\n# to use the github generated source tarball rather than\n# the explicitly uploaded source tarball\n[download]\nurl = https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz\nsha256 = 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb\n\n[build]\nbuilder = cmake\nsubdir = libevent-2.1.12-stable\n\n[cmake.defines]\nEVENT__DISABLE_TESTS = ON\nEVENT__DISABLE_BENCHMARK = ON\nEVENT__DISABLE_SAMPLES = ON\nEVENT__DISABLE_REGRESS = ON\n\n[cmake.defines.shared_libs=on]\nEVENT__BUILD_SHARED_LIBRARIES = ON\n\n[cmake.defines.shared_libs=off]\nEVENT__LIBRARY_TYPE = STATIC\n\n[cmake.defines.os=windows]\nEVENT__LIBRARY_TYPE = STATIC\n\n[dependencies.not(any(os=linux, os=freebsd))]\nopenssl\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libevent-python",
    "content": "[manifest]\nname = libevent-python\n\n# NOTE: System packages (debs, rpms) removed because they don't include\n# LibeventConfig.cmake which is required by find_package(Libevent REQUIRED CONFIG).\n# Building from source ensures CMake config files are present.\n\n[homebrew]\nlibevent\n\n[pps]\nlibevent\n\n# Note that the CMakeLists.txt file is present only in\n# git repo and not in the release tarball, so take care\n# to use the github generated source tarball rather than\n# the explicitly uploaded source tarball\n[download]\nurl = https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz\nsha256 = 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb\n\n[build]\nbuilder = cmake\nsubdir = libevent-2.1.12-stable\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nEVENT__DISABLE_TESTS = ON\nEVENT__DISABLE_BENCHMARK = ON\nEVENT__DISABLE_SAMPLES = ON\nEVENT__DISABLE_REGRESS = ON\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n\n[cmake.defines.shared_libs=on]\nEVENT__BUILD_SHARED_LIBRARIES = ON\n\n[cmake.defines.os=windows]\nEVENT__LIBRARY_TYPE = STATIC\n\n[dependencies.not(any(os=linux, os=freebsd))]\nopenssl\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libffi",
    "content": "[manifest]\nname = libffi\n\n[debs]\nlibffi-dev\n\n[homebrew]\nlibffi\n\n[rpms]\nlibffi-devel\nlibffi\n\n[pps]\nlibffi\n\n[download]\nurl = https://github.com/libffi/libffi/releases/download/v3.4.2/libffi-3.4.2.tar.gz\nsha256 = 540fb721619a6aba3bdeef7d940d8e9e0e6d2c193595bc243241b77ff9e93620\n\n[build]\nbuilder = autoconf\nsubdir = libffi-3.4.2\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libgit2",
    "content": "[manifest]\nname = libgit2\n\n[homebrew]\nlibgit2\n\n[rpms]\nlibgit2-devel\n\n[pps]\nlibgit2\n\n# Ubuntu 18.04 libgit2 has clash with libcurl4-openssl-dev as it depends on\n# libcurl4-gnutls-dev.  Should be ok from 20.04 again\n# There is a description at https://github.com/r-hub/sysreqsdb/issues/77\n[debs.not(all(distro=ubuntu,distro_vers=\"18.04\"))]\nlibgit2-dev\n\n[download]\nurl = https://github.com/libgit2/libgit2/archive/v0.28.1.tar.gz\nsha256 = 0ca11048795b0d6338f2e57717370208c2c97ad66c6d5eac0c97a8827d13936b\n\n[build]\nbuilder = cmake\nsubdir = libgit2-0.28.1\n\n[cmake.defines]\n# Could turn this on if we also wanted to add a manifest for libssh2\nUSE_SSH = OFF\nBUILD_CLAR = OFF\n# Have to build shared to work around annoying problems with cmake\n# mis-parsing the frameworks required to link this on macos :-/\nBUILD_SHARED_LIBS = ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libgpiod",
    "content": "[manifest]\nname = libgpiod\n\n[download]\nurl = https://cdn.kernel.org/pub/software/libs/libgpiod/libgpiod-1.6.tar.xz\nsha256 = 62908023d59e8cbb9137ddd14deec50ced862d8f9b8749f288d3dbe7967151ef\n\n[build]\nbuilder = autoconf\nsubdir = libgpiod-1.6\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libiberty",
    "content": "[manifest]\nname = libiberty\n\n[rpms]\nbinutils-devel\nbinutils\n\n[debs.not(all(distro=ubuntu,distro_vers=\"24.04\"))]\nbinutils-dev\n\n[debs.all(distro=ubuntu,distro_vers=\"24.04\")]\nbinutils-x86-64-linux-gnu\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/binutils/binutils-2.43.tar.xz\nsha256 = b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365\n\n[dependencies]\nzlib\n\n[build]\nbuilder = autoconf\nsubdir = binutils-2.43/libiberty\npatchfile = libiberty_install_pic_lib.patch\n\n# only build the parts needed for demangling\n# as we still want to use system linker and assembler etc\n[autoconf.args]\n--enable-install-libiberty\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libiberty-python",
    "content": "[manifest]\nname = libiberty-python\n\n[rpms]\nbinutils-devel\nbinutils\n\n[debs.not(all(distro=ubuntu,distro_vers=\"24.04\"))]\nbinutils-dev\n\n[debs.all(distro=ubuntu,distro_vers=\"24.04\")]\nbinutils-x86-64-linux-gnu\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/binutils/binutils-2.43.tar.xz\nsha256 = b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365\n\n[dependencies]\nzlib-python\n\n[build]\nbuilder = autoconf\nsubdir = binutils-2.43/libiberty\npatchfile = libiberty_install_pic_lib.patch\n\n[build.not(os=linux)]\nbuilder = nop\n\n# only build the parts needed for demangling\n# as we still want to use system linker and assembler etc\n[autoconf.args]\n--enable-install-libiberty\nCFLAGS=-fPIC -O2\nPICFLAG=-fPIC\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libibverbs",
    "content": "[manifest]\nname = libibverbs\n\n[debs]\nlibibverbs-dev\nrdma-core\n\n[rpms]\nlibibverbs\nrdma-core-devel\n\n[download]\nurl = https://github.com/linux-rdma/rdma-core/releases/download/v60.0/rdma-core-60.0.tar.gz\nsha256 = 9b1b892e4eaaaa5dfbade07a290fbf5079e39117724fa1ef80d0ad78839328de\n\n[build]\nbuilder = cmake\nsubdir = rdma-core-60.0\n\n[dependencies]\nlibnl\n\n[cmake.defines]\nNO_MAN_PAGES=1\nNO_PYVERBS=1\nENABLE_RESOLVE_NEIGH=0\n# Use absolute short path for runtime dir to avoid Unix socket path length limit (108 chars)\nCMAKE_INSTALL_RUNDIR=/tmp/ibacm\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libmnl",
    "content": "[manifest]\nname = libmnl\n\n[rpms]\nlibmnl-devel\n\n# all centos 8 distros are missing this,\n# but its in fedora so may be back in a later version\n[rpms.not(all(any(distro=centos_stream,distro=centos),distro_vers=8))]\nlibmnl-static\n\n[debs]\nlibmnl-dev\n\n[pps]\nlibmnl\n\n[download]\nurl = https://www.netfilter.org/pub/libmnl/libmnl-1.0.4.tar.bz2\nsha256 = 171f89699f286a5854b72b91d06e8f8e3683064c5901fb09d954a9ab6f551f81\n\n[build.os=linux]\nbuilder = autoconf\nsubdir = libmnl-1.0.4\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libnl",
    "content": "[manifest]\nname = libnl\n\n[rpms]\nlibnl3-devel\nlibnl3\n\n[debs]\nlibnl-3-dev\nlibnl-route-3-dev\n\n[pps]\nlibnl\n\n[download]\nurl = https://github.com/thom311/libnl/releases/download/libnl3_2_25/libnl-3.2.25.tar.gz\nsha256 = 8beb7590674957b931de6b7f81c530b85dc7c1ad8fbda015398bc1e8d1ce8ec5\n\n[build.os=linux]\nbuilder = autoconf\nsubdir = libnl-3.2.25\n"
  },
  {
    "path": "build/fbcode_builder/manifests/liboqs",
    "content": "[manifest]\nname = liboqs\n\n[download]\nurl = https://github.com/open-quantum-safe/liboqs/archive/refs/tags/0.12.0.tar.gz\nsha256 = df999915204eb1eba311d89e83d1edd3a514d5a07374745d6a9e5b2dd0d59c08\n\n[build]\nbuilder = cmake\nsubdir = liboqs-0.12.0\n\n[cmake.defines]\nOQS_MINIMAL_BUILD = KEM_kyber_512;KEM_kyber_768;KEM_kyber_1024;KEM_ml_kem_512;KEM_ml_kem_768;KEM_ml_kem_1024\n\n[dependencies]\nopenssl\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libsai",
    "content": "[manifest]\nname = libsai\n\n[download]\nurl = https://github.com/opencomputeproject/SAI/archive/v1.16.3.tar.gz\nsha256 = 5c89cdb6b2e4f1b42ced6b78d43d06d22434ddbf423cdc551f7c2001f12e63d9\n\n[build]\nbuilder = nop\nsubdir = SAI-1.16.3\n\n[install.files]\ninc = include\nexperimental = experimental\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libsodium",
    "content": "[manifest]\nname = libsodium\n\n[debs]\nlibsodium-dev\n\n[homebrew]\nlibsodium\n\n[rpms]\nlibsodium-devel\nlibsodium-static\n\n[pps]\nlibsodium\n\n[download.not(os=windows)]\nurl = https://github.com/jedisct1/libsodium/releases/download/1.0.20-RELEASE/libsodium-1.0.20.tar.gz\nsha256 = ebb65ef6ca439333c2bb41a0c1990587288da07f6c7fd07cb3a18cc18d30ce19\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = libsodium-1.0.20\n\n[download.os=windows]\nurl = https://github.com/jedisct1/libsodium/releases/download/1.0.20-RELEASE/libsodium-1.0.20-msvc.zip\nsha256 = 2ff97f9e3f5b341bdc808e698057bea1ae454f99e29ff6f9b62e14d0eb1b1baa\n\n[build.os=windows]\nbuilder = nop\n\n[install.files.os=windows]\nlibsodium/x64/Release/v143/dynamic/libsodium.dll = bin/libsodium.dll\nlibsodium/x64/Release/v143/dynamic/libsodium.lib = lib/libsodium.lib\nlibsodium/x64/Release/v143/dynamic/libsodium.exp = lib/libsodium.exp\nlibsodium/x64/Release/v143/dynamic/libsodium.pdb = lib/libsodium.pdb\nlibsodium/include = include\n\n[autoconf.args]\n--with-pic\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libtool",
    "content": "[manifest]\nname = libtool\n\n[homebrew]\nlibtool\n\n[rpms]\nlibtool\n\n[debs]\nlibtool\n\n[pps]\nlibtool\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/libtool/libtool-2.4.6.tar.gz\nsha256 = e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3\n\n[build]\nbuilder = autoconf\nsubdir = libtool-2.4.6\n\n[dependencies]\nautomake\n\n[autoconf.args]\n--enable-ltdl-install\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libunwind",
    "content": "[manifest]\nname = libunwind\n\n[rpms]\nlibunwind-devel\nlibunwind\n\n# on ubuntu this brings in liblzma-dev, which in turn breaks watchman tests\n[debs.not(distro=ubuntu)]\nlibunwind-dev\n\n# The current libunwind v1.8.1 release has compiler issues with aarch64 (https://github.com/libunwind/libunwind/issues/702).\n# This more recent libunwind version (based on the latest commit, not a release version) got it fixed.\n[download]\nurl = https://github.com/libunwind/libunwind/archive/f081cf42917bdd5c428b77850b473f31f81767cf.tar.gz\nsha256 = 4ff5c335c02d225491d6c885db827fb5fa505fee4e68b4d7e866efc0087e7264\n\n[build]\nbuilder = autoconf\nsubdir = libunwind-f081cf42917bdd5c428b77850b473f31f81767cf\n\n[autoconf.args]\n--with-pic\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libusb",
    "content": "[manifest]\nname = libusb\n\n[debs]\nlibusb-1.0-0-dev\n\n[homebrew]\nlibusb\n\n[rpms]\nlibusb-devel\nlibusb\n\n[pps]\nlibusb\n\n[download]\nurl = https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2\nsha256 = 75aeb9d59a4fdb800d329a545c2e6799f732362193b465ea198f2aa275518157\n\n[build.os=linux]\nbuilder = autoconf\nsubdir = libusb-1.0.22\n\n[autoconf.args]\n# fboss (which added the libusb dep) doesn't need udev so it is disabled here.\n# if someone in the future wants to add udev for something else, it won't hurt\n# fboss.\n--disable-udev\n"
  },
  {
    "path": "build/fbcode_builder/manifests/libyaml",
    "content": "[manifest]\nname = libyaml\n\n[download]\nurl = https://pyyaml.org/download/libyaml/yaml-0.1.7.tar.gz\nsha256 = 8088e457264a98ba451a90b8661fcb4f9d6f478f7265d48322a196cec2480729\n\n[build.os=linux]\nbuilder = autoconf\nsubdir = yaml-0.1.7\n\n[build.not(os=linux)]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/llvm",
    "content": "[manifest]\nname = llvm\n\n[rpms]\nllvm15-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/lmdb",
    "content": "[manifest]\nname = lmdb\n\n[build]\nbuilder = make\nsubdir = lmdb-LMDB_0.9.31/libraries/liblmdb\n\n[download]\nurl = https://github.com/LMDB/lmdb/archive/refs/tags/LMDB_0.9.31.tar.gz\nsha256 = dd70a8c67807b3b8532b3e987b0a4e998962ecc28643e1af5ec77696b081c9b0\n\n[make.build_args]\nBUILD_STATIC_ONLY=y\n\n[make.install_args]\ninstall\nBUILD_STATIC_ONLY=y\n"
  },
  {
    "path": "build/fbcode_builder/manifests/lz4",
    "content": "[manifest]\nname = lz4\n\n[homebrew]\nlz4\n\n[rpms]\nlz4-devel\n# centos 8 and centos_stream 9 are missing this rpm\n[rpms.not(any(all(distro=centos,distro_vers=8),all(distro=centos_stream,distro_vers=9)))]\nlz4-static\n\n[debs]\nliblz4-dev\n\n[pps]\nlz4\n\n[download]\nurl = https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz\nsha256 = 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b\n\n[build]\nbuilder = cmake\nsubdir = lz4-1.10.0/build/cmake\n"
  },
  {
    "path": "build/fbcode_builder/manifests/lz4-python",
    "content": "[manifest]\nname = lz4-python\n\n[homebrew]\nlz4\n\n[rpms]\nlz4-devel\n# centos 8 and centos_stream 9 are missing this rpm\n[rpms.not(any(all(distro=centos,distro_vers=8),all(distro=centos_stream,distro_vers=9)))]\nlz4-static\n\n[debs]\nliblz4-dev\n\n[pps]\nlz4\n\n[download]\nurl = https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz\nsha256 = 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b\n\n[build]\nbuilder = cmake\nsubdir = lz4-1.10.0/build/cmake\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/magic_enum",
    "content": "[manifest]\nname = magic_enum\n\n[download]\nurl = https://github.com/Neargye/magic_enum/releases/download/v0.9.7/magic_enum-v0.9.7.tar.gz\nsha256 = c047bc7ca0b76752168140e7ae9a4a30d72bf6530c196fdfbf5105a39d40cc46\n\n[build]\nbuilder = cmake\n\n[cmake.defines]\nMAGIC_ENUM_OPT_BUILD_EXAMPLES = OFF\nMAGIC_ENUM_OPT_BUILD_TESTS = OFF\nMAGIC_ENUM_OPT_INSTALL = ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/mcrouter",
    "content": "[manifest]\nname = mcrouter\nfbsource_path = fbcode/mcrouter\nshipit_project = mcrouter\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/mcrouter.git\n\n[dependencies]\nfolly\nwangle\nfizz\nfbthrift\nmvfst\nragel\ngflags\nglog\nboost\nlibevent\nopenssl\nzlib\ndouble-conversion\n\n[shipit.pathmap]\nfbcode/mcrouter/public_tld = .\nfbcode/mcrouter = mcrouter\n\n[shipit.strip]\n^fbcode/mcrouter/(.*/)?facebook/\n\n[build]\nbuilder = cmake\n\n[cmake.defines]\nBUILD_SHARED_LIBS=OFF\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/mononoke",
    "content": "[manifest]\nname = mononoke\nfbsource_path = fbcode/eden\nshipit_project = eden\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/sapling.git\n\n[build.not(os=windows)]\nbuilder = cargo\n\n[build.os=windows]\n# building Mononoke on windows is not supported\nbuilder = nop\n\n[cargo]\nbuild_doc = true\nworkspace_dir = eden/mononoke\n\n[github.actions]\nrust_version = 1.91\nbuild_type = MinSizeRel\n\n[shipit.pathmap]\nfbcode/configerator/structs/scm/hg = configerator/structs/scm/hg\nfbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg\nfbcode/configerator/structs/scm/mononoke/public_autocargo = configerator/structs/scm/mononoke\nfbcode/configerator/structs/scm/mononoke = configerator/structs/scm/mononoke\nfbcode/eden/oss = .\nfbcode/eden = eden\nfbcode/eden/fs/public_autocargo = eden/fs\nfbcode/eden/mononoke/public_autocargo = eden/mononoke\nfbcode/eden/scm/public_autocargo = eden/scm\nfbcode/tools/lfs = tools/lfs\ntools/rust/ossconfigs = .\n\n[shipit.strip]\n^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/configerator/structs/scm/mononoke(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/fs(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/scm/lib/third-party/rust/.*/Cargo\\.toml$\n^fbcode/eden/mononoke(?!/public_autocargo).*/Cargo\\.toml$\n# strip other scm code  unrelated to mononoke to prevent triggering unnecessary checks\n^fbcode/eden(?!/mononoke|/scm/(lib|public_autocargo))/.*$\n^.*/facebook/.*$\n^.*/fb/.*$\n\n[dependencies]\nfb303\nfbthrift\nrust-shed\n\n[dependencies.fb=on]\nrust\n"
  },
  {
    "path": "build/fbcode_builder/manifests/mononoke_integration",
    "content": "[manifest]\nname = mononoke_integration\nfbsource_path = fbcode/eden\nshipit_project = eden\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/sapling.git\n\n[build.not(os=windows)]\nbuilder = make\nsubdir = eden/mononoke/tests/integration\n\n[build.os=windows]\n# building Mononoke on windows is not supported\nbuilder = nop\n\n[make.build_args]\nbuild-getdeps\n\n[make.install_args]\ninstall-getdeps\n\n[make.test_args]\ntest-getdeps\n\n[shipit.pathmap]\nfbcode/eden/mononoke/tests/integration = eden/mononoke/tests/integration\n\n[shipit.strip]\n^.*/facebook/.*$\n^.*/fb/.*$\n\n[dependencies]\ngit-lfs\njq\nmononoke\nnmap\npython\npython-click\nripgrep\nsapling\ntree\nzstd\n\n[dependencies.os=linux]\nsqlite3\n"
  },
  {
    "path": "build/fbcode_builder/manifests/moxygen",
    "content": "[manifest]\nname = moxygen\nfbsource_path = fbcode/moxygen\nshipit_project = moxygen\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookexperimental/moxygen.git\n\n[build.os=windows]\nbuilder = nop\n\n[build]\nbuilder = cmake\nsubdir = .\njob_weight_mib = 3072\nrewrite_includes = true\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[dependencies]\nfolly\nfizz\nwangle\nmvfst\nproxygen\n\n[dependencies.test=on]\ngoogletest\n\n[shipit.pathmap]\nfbcode/ti/experimental/moxygen/project_root = .\nfbcode/ti/experimental/moxygen = moxygen\n"
  },
  {
    "path": "build/fbcode_builder/manifests/mvfst",
    "content": "[manifest]\nname = mvfst\nfbsource_path = fbcode/quic\nshipit_project = mvfst\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/mvfst.git\n\n[build]\nbuilder = cmake\nsubdir = .\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.all(os=windows, test=on)]\nBUILD_TESTS = OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[dependencies]\nfolly\nfizz\n\n[dependencies.all(test=on, not(os=windows))]\ngoogletest\n\n[shipit.pathmap]\nfbcode/quic/public_root = .\nfbcode/quic = quic\n"
  },
  {
    "path": "build/fbcode_builder/manifests/mvfst-python",
    "content": "[manifest]\nname = mvfst-python\nfbsource_path = fbcode/quic\nshipit_project = mvfst\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/mvfst.git\n\n[build]\nbuilder = cmake\nsubdir = .\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.all(os=windows, test=on)]\nBUILD_TESTS = OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[cmake.defines.os=linux]\nCMAKE_POSITION_INDEPENDENT_CODE = ON\nBUILD_SHARED_LIBS = ON\n\n[dependencies]\nfolly-python\nfizz-python\n\n[dependencies.all(test=on, not(os=windows))]\ngoogletest\n\n[shipit.pathmap]\nfbcode/quic/public_root = .\nfbcode/quic = quic\n"
  },
  {
    "path": "build/fbcode_builder/manifests/ncurses",
    "content": "[manifest]\nname = ncurses\n\n[debs]\nlibncurses-dev\n\n[homebrew]\nncurses\n\n[rpms]\nncurses-devel\n\n[download]\nurl = https://ftpmirror.gnu.org/gnu/ncurses/ncurses-6.3.tar.gz\nsha256 = 97fc51ac2b085d4cde31ef4d2c3122c21abc217e9090a43a30fc5ec21684e059\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = ncurses-6.3\n\n[autoconf.args]\n--without-cxx-binding\n--without-ada\n\n[autoconf.args.os=linux]\n--enable-shared\n--with-shared\n\n[build.os=windows]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/nghttp2",
    "content": "[manifest]\nname = nghttp2\n\n[rpms]\nlibnghttp2-devel\nlibnghttp2\n\n[debs]\nlibnghttp2-dev\n\n[pps]\nlibnghttp2\n\n[download]\nurl = https://github.com/nghttp2/nghttp2/releases/download/v1.47.0/nghttp2-1.47.0.tar.gz\nsha256 = 62f50f0e9fc479e48b34e1526df8dd2e94136de4c426b7680048181606832b7c\n\n[build]\nbuilder = autoconf\nsubdir = nghttp2-1.47.0\n\n[autoconf.args]\n--enable-lib-only\n--disable-dependency-tracking\n"
  },
  {
    "path": "build/fbcode_builder/manifests/ninja",
    "content": "[manifest]\nname = ninja\n\n[debs]\nninja-build\n\n[homebrew]\nninja\n\n[rpms]\nninja-build\n\n[pps]\nninja\n\n[download.os=windows]\nurl = https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip\nsha256 = f550fec705b6d6ff58f2db3c374c2277a37691678d6aba463adcbb129108467a\n\n[build.os=windows]\nbuilder = nop\n\n[install.files.os=windows]\nninja.exe = bin/ninja.exe\n\n[download.not(os=windows)]\nurl = https://github.com/ninja-build/ninja/archive/v1.12.1.tar.gz\nsha256 = 821bdff48a3f683bc4bb3b6f0b5fe7b2d647cf65d52aeb63328c91a6c6df285a\n\n[build.not(os=windows)]\nbuilder = ninja_bootstrap\nsubdir = ninja-1.12.1"
  },
  {
    "path": "build/fbcode_builder/manifests/nlohmann-json",
    "content": "[manifest]\nname = nlohmann-json\n\n[download]\nurl = https://github.com/nlohmann/json/archive/refs/tags/v3.10.5.tar.gz\nsha256 = 5daca6ca216495edf89d167f808d1d03c4a4d929cef7da5e10f135ae1540c7e4\n\n[dependencies]\n\n[build]\nbuilder = cmake\nsubdir = json-3.10.5\n"
  },
  {
    "path": "build/fbcode_builder/manifests/nmap",
    "content": "[manifest]\nname = nmap\n\n[rpms]\nnmap\nnmap-ncat\n\n[debs]\nnmap\n\n# 18.04 combines ncat into the nmap package, newer need the separate one\n[debs.not(all(distro=ubuntu,distro_vers=\"18.04\"))]\nncat\n\n[download.not(os=windows)]\nurl = https://api.github.com/repos/nmap/nmap/tarball/ef8213a36c2e89233c806753a57b5cd473605408\nsha256 = eda39e5a8ef4964fac7db16abf91cc11ff568eac0fa2d680b0bfa33b0ed71f4a\n\n[build.not(os=windows)]\nbuilder = autoconf\nsubdir = nmap-nmap-ef8213a\nbuild_in_src_dir = true\n\n[build.os=windows]\nbuilder = nop\n\n[autoconf.args]\n# Without this option the build was filing to find some third party libraries\n# that we don't need\nenable_rdma=no\n"
  },
  {
    "path": "build/fbcode_builder/manifests/numa",
    "content": "[manifest]\nname = numa\n\n[download]\nurl = https://github.com/numactl/numactl/releases/download/v2.0.19/numactl-2.0.19.tar.gz\nsha256 = f2672a0381cb59196e9c246bf8bcc43d5568bc457700a697f1a1df762b9af884\n\n[build]\nbuilder = autoconf\nsubdir = numactl-2.0.19\n\n[rpms.distro=centos_stream]\nnumactl-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/openr",
    "content": "[manifest]\nname = openr\nfbsource_path = facebook/openr\nshipit_project = openr\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/openr.git\n\n[build.os=linux]\nbuilder = cmake\n# openr files take a lot of RAM to compile.\njob_weight_mib = 3072\n\n[build.not(os=linux)]\n# boost.fiber is required and that is not available on macos.\nbuilder = nop\n\n[dependencies]\nboost\nfb303\nfbthrift\nfolly\ngoogletest\nre2\nrange-v3\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\nADD_ROOT_TESTS=OFF\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n\n[shipit.pathmap]\nfbcode/openr = openr\nfbcode/openr/public_tld = .\n"
  },
  {
    "path": "build/fbcode_builder/manifests/openssl",
    "content": "[manifest]\nname = openssl\n\n[debs]\nlibssl-dev\n\n[homebrew]\nopenssl\n# on homebrew need the matching curl and ca-\n\n[rpms]\nopenssl\nopenssl-devel\nopenssl-libs\n\n[pps]\nopenssl\n\n# no need to download on the systems where we always use the system libs\n[download.not(any(os=linux, os=freebsd))]\n# match the openssl version packages in ubuntu LTS folly current supports\nurl = https://www.openssl.org/source/openssl-3.0.15.tar.gz\nsha256 = 23c666d0edf20f14249b3d8f0368acaee9ab585b09e1de82107c66e1f3ec9533\n\n# We use the system openssl on these platforms even without --allow-system-packages\n[build.any(os=linux, os=freebsd)]\nbuilder = nop\n\n[build.not(any(os=linux, os=freebsd))]\nbuilder = openssl\nsubdir = openssl-3.0.15\n\n[dependencies.os=windows]\njom\nperl\n"
  },
  {
    "path": "build/fbcode_builder/manifests/osxfuse",
    "content": "[manifest]\nname = osxfuse\n\n[download]\nurl = https://github.com/osxfuse/osxfuse/archive/osxfuse-3.8.3.tar.gz\nsha256 = 93bab6731bdfe8dc1ef069483437270ce7fe5a370f933d40d8d0ef09ba846c0c\n\n[build]\nbuilder = nop\n\n[install.files]\nosxfuse-osxfuse-3.8.3/common = include\n"
  },
  {
    "path": "build/fbcode_builder/manifests/patchelf",
    "content": "[manifest]\nname = patchelf\n\n[rpms]\npatchelf\n\n[debs]\npatchelf\n\n[pps]\npatchelf\n\n[download]\nurl = https://github.com/NixOS/patchelf/archive/0.10.tar.gz\nsha256 = b3cb6bdedcef5607ce34a350cf0b182eb979f8f7bc31eae55a93a70a3f020d13\n\n[build]\nbuilder = autoconf\nsubdir = patchelf-0.10\n\n"
  },
  {
    "path": "build/fbcode_builder/manifests/pcre2",
    "content": "[manifest]\nname = pcre2\n\n[homebrew]\npcre2\n\n[rpms]\npcre2-devel\npcre-static\n\n[debs]\nlibpcre2-dev\n\n[download]\nurl = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.40/pcre2-10.40.tar.bz2\nsha256 = 14e4b83c4783933dc17e964318e6324f7cae1bc75d8f3c79bc6969f00c159d68\n\n[build]\nbuilder = cmake\nsubdir = pcre2-10.40\n"
  },
  {
    "path": "build/fbcode_builder/manifests/perl",
    "content": "[manifest]\nname = perl\n\n[download.os=windows]\nurl = https://strawberryperl.com/download/5.28.1.1/strawberry-perl-5.28.1.1-64bit-portable.zip\nsha256 = 935c95ba096fa11c4e1b5188732e3832d330a2a79e9882ab7ba8460ddbca810d\n\n[build.os=windows]\nbuilder = nop\nsubdir = perl\n"
  },
  {
    "path": "build/fbcode_builder/manifests/pexpect",
    "content": "[manifest]\nname = pexpect\n\n[download]\nurl = https://files.pythonhosted.org/packages/0e/3e/377007e3f36ec42f1b84ec322ee12141a9e10d808312e5738f52f80a232c/pexpect-4.7.0-py2.py3-none-any.whl\nsha256 = 2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1\n\n[build]\nbuilder = python-wheel\n\n[dependencies]\npython-ptyprocess\n"
  },
  {
    "path": "build/fbcode_builder/manifests/proxygen",
    "content": "[manifest]\nname = proxygen\nfbsource_path = fbcode/proxygen\nshipit_project = proxygen\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/proxygen.git\n\n[build.os=windows]\nbuilder = nop\n\n[build]\nbuilder = cmake\nsubdir = .\njob_weight_mib = 3072\n\n[cmake.defines.test=on]\nBUILD_TESTS = ON\n\n[cmake.defines.test=off]\nBUILD_TESTS = OFF\n\n[dependencies]\nzlib\ngperf\nfolly\nfizz\nwangle\nmvfst\nc-ares\n\n[dependencies.test=on]\ngoogletest\n\n[shipit.pathmap]\nfbcode/proxygen/public_tld = .\nfbcode/proxygen = proxygen\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python",
    "content": "[manifest]\nname = python\n\n[homebrew]\npython@3.10\n\n# sapling needs match statements with arrive in python 3.12 in centos 10\n[rpms.not(all(distro=centos_stream,distro_vers=9))]\npython3\npython3-devel\n\n# Centos Stream 9 default python is 3.9, sapling needs 3.10+\n[rpms.all(distro=centos_stream,distro_vers=9)]\npython3.12\npython3.12-devel\n\n# sapling needs match statements with arrive in python 3.10 in ubuntu 22.04\n[debs.not(all(distro=ubuntu,any(distro_vers=\"18.04\",distro_vers=\"20.04\")))]\npython3-all-dev\n\n[pps]\npython3\n\n[download]\nurl = https://www.python.org/ftp/python/3.10.19/Python-3.10.19.tgz\nsha256 = a078fb2d7a216071ebbe2e34b5f5355dd6b6e9b0cd1bacc4a41c63990c5a0eec\n\n[build]\nbuilder = autoconf\nsubdir = Python-3.10.19\n\n[autoconf.args]\n--enable-shared\n--with-ensurepip=install\n\n# python's pkg-config libffi detection is broken\n# See https://bugs.python.org/issue34823 for clearest description\n# and pending PR https://github.com/python/cpython/pull/20451\n# The documented workaround requires an environment variable derived from\n# pkg-config to be passed into its configure step\n[autoconf.envcmd.LDFLAGS]\npkg-config\n--libs-only-L\nlibffi\n\n[dependencies]\nlibffi\n# eden tests expect the python bz2 support\nbz2\n# eden tests expect the python curses support\nncurses\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-3_14",
    "content": "# This is primarily to support CinderX's CI, so it's not heavily configured.\n\n[manifest]\nname = python-3_14\n\n[download]\nurl = https://github.com/python/cpython/archive/refs/tags/v3.14.3.tar.gz\nsha256 = f229a232052ae318d2fc8eb0aca4a02d631e7e1a8790ef1f9b65e1632743a469\n\n[build]\nbuilder = autoconf\nsubdir = cpython-3.14.3\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-click",
    "content": "[manifest]\nname = python-click\n\n[download]\nurl = https://files.pythonhosted.org/packages/d2/3d/fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e/click-7.1.2-py2.py3-none-any.whl\nsha256 = dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc\n\n[build]\nbuilder = python-wheel\n\n[rpms]\npython3-click\n\n[debs]\npython3-click\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-filelock",
    "content": "[manifest]\nname = python-filelock\n\n[download]\nurl = https://files.pythonhosted.org/packages/31/24/ee722b92f23b9ebd87783e893a75352c048bbbc1f67dce0d63b58b46cb48/filelock-3.3.2-py3-none-any.whl\nsha256 = bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b\n\n[build]\nbuilder = python-wheel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-main",
    "content": "# This is primarily to support CinderX's CI, so it's not heavily configured.\n\n[manifest]\nname = python-main\nfbsource_path = third-party/python/main/pristine\n# We don't actually have a shipit project for python-main, but we use getdeps\n# built-in shipit implementation which just needs a shipit.pathmap.\nshipit_project = dummy-name\n\n\n[git]\nrepo_url = https://github.com/python/cpython.git\n\n[shipit.pathmap]\nthird-party/python/main/pristine = .\n\n[build]\nbuilder = autoconf\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-psutil",
    "content": "[manifest]\nname = python-psutil\n\n[download]\nurl = https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl\nsha256 = 4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34\n\n[build]\nbuilder = python-wheel\n\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-ptyprocess",
    "content": "[manifest]\nname = python-ptyprocess\n\n[download]\nurl = https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl\nsha256 = d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f\n\n[build]\nbuilder = python-wheel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-pyyaml",
    "content": "[manifest]\nname = python-pyyaml\n\n[download]\nurl = https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\nsha256 = 22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6\n\n[build]\nbuilder = python-wheel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-setuptools",
    "content": "[manifest]\nname = python-setuptools\n\n[download]\nurl = https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl\nsha256 = 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922\n\n[build]\nbuilder = python-wheel\n\n[rpms]\npython3-setuptools\n\n# Centos Stream 9 default python is 3.9, sapling needs 3.10+\n[rpms.all(distro=centos_stream,distro_vers=9)]\npython3.12-setuptools\n\n[homebrew]\npython-setuptools\n\n[debs]\npython3-setuptools\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-setuptools-69",
    "content": "[manifest]\nname = python-setuptools-69\n\n[download]\nurl = https://files.pythonhosted.org/packages/c0/7a/3da654f49c95d0cc6e9549a855b5818e66a917e852ec608e77550c8dc08b/setuptools-69.1.1-py3-none-any.whl\nsha256 = 02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56\n\n[build]\nbuilder = python-wheel\n\n[rpms]\npython3-setuptools\n\n[homebrew]\npython-setuptools\n\n[debs]\npython3-setuptools\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-six",
    "content": "[manifest]\nname = python-six\n\n[download]\nurl = https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl\nsha256 = 3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c\n\n[build]\nbuilder = python-wheel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/python-toml",
    "content": "[manifest]\nname = python-toml\n\n[download]\nurl = https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl\nsha256 = 235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e\n\n[build]\nbuilder = python-wheel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/ragel",
    "content": "[manifest]\nname = ragel\n\n[debs]\nragel\n\n[homebrew]\nragel\n\n[rpms]\nragel\n\n[download]\nurl = https://www.colm.net/files/ragel/ragel-6.10.tar.gz\nsha256 = 5f156edb65d20b856d638dd9ee2dfb43285914d9aa2b6ec779dac0270cd56c3f\n\n[build]\nbuilder = autoconf\nsubdir = ragel-6.10\n"
  },
  {
    "path": "build/fbcode_builder/manifests/range-v3",
    "content": "[manifest]\nname = range-v3\n\n[download]\nurl = https://github.com/ericniebler/range-v3/archive/refs/tags/0.11.0.tar.gz\nsha256 = 376376615dbba43d3bef75aa590931431ecb49eb36d07bb726a19f680c75e20c \n\n\n[build]\nbuilder = cmake\nsubdir = range-v3-0.11.0\n\n[cmake.defines]\nRANGE_V3_EXAMPLES=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/rdma-core",
    "content": "[manifest]\nname = rdma-core\n\n[debs]\nrdma-core\n\n[rpms]\nrdma-core-devel\n"
  },
  {
    "path": "build/fbcode_builder/manifests/re2",
    "content": "[manifest]\nname = re2\n\n[homebrew]\nre2\n\n[debs]\nlibre2-dev\n\n[rpms]\nre2\nre2-devel\n\n[pps]\nre2\n\n[download]\nurl = https://github.com/google/re2/archive/2020-11-01.tar.gz\nsha256 = 8903cc66c9d34c72e2bc91722288ebc7e3ec37787ecfef44d204b2d6281954d7\n\n[build]\nbuilder = cmake\nsubdir = re2-2020-11-01\n"
  },
  {
    "path": "build/fbcode_builder/manifests/rebalancer",
    "content": "[manifest]\nname = rebalancer\nfbsource_path = fbcode/algopt/rebalancer/\nshipit_project = rebalancer\nshipit_fbcode_builder = true\nuse_shipit = true\n\n[git]\n# To git clone on devserver, setup fwdproxy:\n# https://www.internalfb.com/wiki/Open_Source/Maintain_a_FB_OSS_Project/Devserver_GitHub_Access/\nrepo_url = https://github.com/facebookincubator/rebalancer.git\nbranch = main\n\n[build]\nbuilder = cmake\n\n[dependencies]\nboost\nfbthrift\nfizz\nfmt\nfolly\ngflags\nglog\ngoogletest\nxxhash\n\n[dependencies.os=linux]\nclang19\n\n[cmake.defines.os=linux]\nCMAKE_C_COMPILER=clang-19\nCMAKE_CXX_COMPILER=clang++-19\n\n[cmake.defines.os=darwin]\nREBALANCER_USE_SCIP=0\n\n[shipit.strip]\n^.*/fb/.*$\n\n[shipit.pathmap]\nfbcode/algopt/rebalancer/oss_root = .\nfbcode/algopt/rebalancer = algopt/rebalancer\nfbcode/algopt/lp = algopt/lp\n\n[dependencies.all(distro=centos_stream,distro_vers=9)]\nclang19\n\n[cmake.defines.all(distro=centos_stream,distro_vers=9)]\nCMAKE_C_COMPILER=clang-19\nCMAKE_CXX_COMPILER=clang++-19\n"
  },
  {
    "path": "build/fbcode_builder/manifests/ripgrep",
    "content": "[manifest]\nname = ripgrep\n\n[rpms]\nripgrep\n\n[debs]\nripgrep\n\n[homebrew]\nripgrep\n\n# only used from system packages currently\n[build]\nbuilder = nop\n"
  },
  {
    "path": "build/fbcode_builder/manifests/rocksdb",
    "content": "[manifest]\nname = rocksdb\n\n[download]\nurl = https://github.com/facebook/rocksdb/archive/refs/tags/v8.7.3.zip\nsha256 = 36c06b61dc167f2455990d60dd88d734b73aa8c4dfc095243efd0243834c6cd3\n\n[dependencies]\nlz4\nsnappy\n\n[build]\nbuilder = cmake\nsubdir = rocksdb-8.7.3\n\n[cmake.defines]\nWITH_SNAPPY=ON\nWITH_LZ4=ON\nWITH_TESTS=OFF\nWITH_BENCHMARK_TOOLS=OFF\n# We get relocation errors with the static gflags lib,\n# and there's no clear way to make it pick the shared gflags\n# so just turn it off.\nWITH_GFLAGS=OFF\n# Disable the use of -Werror\nFAIL_ON_WARNINGS = OFF\n\n[cmake.defines.os=windows]\nROCKSDB_INSTALL_ON_WINDOWS=ON\n# RocksDB hard codes the paths to the snappy libs to something\n# that doesn't exist; ignoring the usual cmake rules.  As a result,\n# we can't build it with snappy without either patching rocksdb or\n# without introducing more complex logic to the build system to\n# connect the snappy build outputs to rocksdb's custom logic here.\n# Let's just turn it off on windows.\nWITH_SNAPPY=OFF\nWITH_LZ4=ON\nROCKSDB_SKIP_THIRDPARTY=ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/rust-shed",
    "content": "[manifest]\nname = rust-shed\nfbsource_path = fbcode/common/rust/shed\nshipit_project = rust-shed\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebookexperimental/rust-shed.git\n\n[build]\nbuilder = cargo\n\n[cargo]\nbuild_doc = true\nworkspace_dir =\n\n[shipit.pathmap]\nfbcode/common/rust/shed = shed\nfbcode/common/rust/shed/public_autocargo = shed\nfbcode/common/rust/shed/public_tld = .\ntools/rust/ossconfigs = .\n\n[shipit.strip]\n^fbcode/common/rust/shed/(?!public_autocargo|public_tld).+/Cargo\\.toml$\n\n[dependencies]\nfbthrift\nfb303\n\n# We use the system openssl on linux\n[dependencies.not(os=linux)]\nopenssl\n\n[dependencies.fbsource=on]\nrust\n"
  },
  {
    "path": "build/fbcode_builder/manifests/sapling",
    "content": "[manifest]\nname = sapling\nfbsource_path = fbcode/eden\nshipit_project = eden\nshipit_fbcode_builder = true\n\n[github.actions]\nrequired_locales = en_US.UTF-8\n\n[git]\nrepo_url = https://github.com/facebook/sapling.git\n\n[build.not(os=windows)]\nbuilder = make\nsubdir = eden/scm\n\n[build.os=windows]\n# For now the biggest blocker is missing \"make\" on windows, but there are bound\n# to be more\nbuilder = nop\n\n[make.build_args]\ngetdepsbuild\n\n[make.install_args]\ninstall-getdeps\n\n[make.test_args]\ntest-getdeps\n\n[shipit.pathmap]\nfbcode/configerator/structs/scm/hg = configerator/structs/scm/hg\nfbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg\nfbcode/eden/oss = .\nfbcode/eden = eden\nfbcode/eden/fs/public_autocargo = eden/fs\nfbcode/eden/mononoke/public_autocargo = eden/mononoke\nfbcode/eden/scm/public_autocargo = eden/scm\nfbcode/tools/lfs = tools/lfs\n\n[shipit.strip]\n^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/addons/.*$\n^fbcode/eden/fs/eden-config\\.h$\n^fbcode/eden/fs/py/eden/config\\.py$\n^fbcode/eden/hg-server/.*$\n^fbcode/eden/fs(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/mononoke(?!/public_autocargo).*/Cargo\\.toml$\n^fbcode/eden/scm(?!/public_autocargo|/edenscmnative/bindings).*/Cargo\\.toml$\n^fbcode/eden/scm/build/.*$\n^fbcode/eden/website/.*$\n^fbcode/eden/.*/\\.cargo/.*$\n^.*/facebook/.*$\n^.*/fb/.*$\n/Cargo\\.lock$\n\\.pyc$\n\n[dependencies]\nfb303\nfbthrift\nrust-shed\n\n[dependencies.all(test=on,not(os=darwin))]\nhexdump\n\n[dependencies.not(os=windows)]\npython\npython-setuptools\n\n# We use the system openssl on linux\n[dependencies.not(os=linux)]\nopenssl\n\n[dependencies.fbsource=on]\nrust\n"
  },
  {
    "path": "build/fbcode_builder/manifests/snappy",
    "content": "[manifest]\nname = snappy\n\n[homebrew]\nsnappy\n\n[debs]\nlibsnappy-dev\n\n[rpms]\nsnappy-devel\n\n[pps]\nsnappy\n\n[download]\nurl = https://github.com/google/snappy/archive/1.1.7.tar.gz\nsha256 = 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4\n\n[build]\nbuilder = cmake\nsubdir = snappy-1.1.7\n\n[cmake.defines]\nSNAPPY_BUILD_TESTS = OFF\n\n# Avoid problems like `relocation R_X86_64_PC32 against symbol` on ELF systems\n# when linking rocksdb, which builds PIC even when building a static lib\n[cmake.defines.os=linux]\nBUILD_SHARED_LIBS = ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/sparsemap",
    "content": "[manifest]\nname = sparsemap\n\n[download]\nurl = https://github.com/Tessil/sparse-map/archive/refs/tags/v0.6.2.tar.gz\nsha256 = 7020c21e8752e59d72e37456cd80000e18671c803890a3e55ae36b295eba99f6\n\n[build]\nbuilder = cmake\nsubdir = sparse-map-0.6.2/\n"
  },
  {
    "path": "build/fbcode_builder/manifests/sqlite3",
    "content": "[manifest]\nname = sqlite3\n\n[debs]\nlibsqlite3-dev\nsqlite3\n\n[homebrew]\nsqlite\n\n[rpms]\nsqlite-devel\nsqlite-libs\nsqlite\n\n[pps]\nsqlite3\n\n[download]\nurl = https://sqlite.org/2019/sqlite-amalgamation-3280000.zip\nsha256 = d02fc4e95cfef672b45052e221617a050b7f2e20103661cda88387349a9b1327\n\n[dependencies]\ncmake\nninja\n\n[build]\nbuilder = sqlite\nsubdir = sqlite-amalgamation-3280000\n"
  },
  {
    "path": "build/fbcode_builder/manifests/systemd",
    "content": "[manifest]\nname = systemd\n\n[rpms]\nsystemd\nsystemd-devel\n\n\n[download]\nurl = https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz\nsha256 = 896d76ff65c88f5fd9e42f90d152b0579049158a163431dd77cdc57748b1d7b0\n\n[build.os=linux]\nbuilder = meson\nsubdir = systemd-256.7\n\n[meson.setup_args]\n-Dstatic-libsystemd=true\n-Dprefix=/\n"
  },
  {
    "path": "build/fbcode_builder/manifests/tabulate",
    "content": "[manifest]\nname = tabulate\n\n[download]\nurl = https://github.com/p-ranav/tabulate/archive/refs/tags/v1.5.tar.gz\nsha256 = 16b289f46306283544bb593f4601e80d6ea51248fde52e910cc569ef08eba3fb\n\n[build]\nbuilder = cmake\nsubdir = tabulate-1.5\n\n[cmake.defines]\ntabulate_BUILD_TESTS = OFF\ntabulate_BUILD_SAMPLES = OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/tree",
    "content": "[manifest]\nname = tree\n\n[debs]\ntree\n\n[homebrew]\ntree\n\n[rpms]\ntree\n\n[download.os=linux]\nurl = https://salsa.debian.org/debian/tree-packaging/-/archive/debian/1.8.0-1/tree-packaging-debian-1.8.0-1.tar.gz\nsha256 = a841eee1d52bfd64a48f54caab9937b9bd92935055c48885c4ab1ae4dab7fae5\n\n[download.os=darwin]\n# The official package of tree source requires users of non-Linux platform to\n# comment/uncomment certain lines in the Makefile to build for their platform.\n# Besauce getdeps.py doesn't have that functionality we just use this custom\n# fork of tree which has proper lines uncommented for a OSX build\nurl = https://github.com/lukaspiatkowski/tree-command/archive/debian/1.8.0-1-macos.tar.gz\nsha256 = 9cbe889553d95cf5a2791dd0743795d46a3c092c5bba691769c0e5c52e11229e\n\n[build.os=linux]\nbuilder = make\nsubdir = tree-packaging-debian-1.8.0-1\n\n[build.os=darwin]\nbuilder = make\nsubdir = tree-command-debian-1.8.0-1-macos\n\n[build.os=windows]\nbuilder = nop\n\n[make.install_args]\ninstall\n"
  },
  {
    "path": "build/fbcode_builder/manifests/wangle",
    "content": "[manifest]\nname = wangle\nfbsource_path = fbcode/wangle\nshipit_project = wangle\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/wangle.git\n\n[build]\nbuilder = cmake\nsubdir = wangle\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n[dependencies]\nfolly\ngoogletest\nfizz\n\n[shipit.pathmap]\nfbcode/wangle/public_tld = .\nfbcode/wangle = wangle\n"
  },
  {
    "path": "build/fbcode_builder/manifests/wangle-python",
    "content": "[manifest]\nname = wangle-python\nfbsource_path = fbcode/wangle\nshipit_project = wangle\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/wangle.git\n\n[build]\nbuilder = cmake\nsubdir = wangle\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines.test=on]\nBUILD_TESTS=ON\n\n[cmake.defines.test=off]\nBUILD_TESTS=OFF\n\n[cmake.defines.os=linux]\nCMAKE_POSITION_INDEPENDENT_CODE = ON\nBUILD_SHARED_LIBS = ON\n\n[dependencies]\nfolly-python\ngoogletest\nfizz-python\n\n[shipit.pathmap]\nfbcode/wangle/public_tld = .\nfbcode/wangle = wangle\n"
  },
  {
    "path": "build/fbcode_builder/manifests/watchman",
    "content": "[manifest]\nname = watchman\nfbsource_path = fbcode/watchman\nshipit_project = watchman\nshipit_fbcode_builder = true\n\n[git]\nrepo_url = https://github.com/facebook/watchman.git\n\n[build]\nbuilder = cmake\n\n[dependencies]\nboost\ncpptoml\nedencommon\nfb303\nfbthrift\nfolly\npcre2\ngoogletest\npython-setuptools-69\n\n[dependencies.fbsource=on]\nrust\n\n[shipit.pathmap]\nfbcode/watchman = watchman\nfbcode/watchman/oss = .\nfbcode/eden/fs = eden/fs\n\n[shipit.strip]\n^fbcode/eden/fs/(?!.*\\.thrift|service/shipit_test_file\\.txt)\n\n[cmake.defines.fb=on]\nENABLE_EDEN_SUPPORT=ON\nIS_FB_BUILD=ON\n\n# FB macos specific settings\n[cmake.defines.all(fb=on,os=darwin)]\n# this path is coupled with the FB internal watchman-osx.spec\nWATCHMAN_STATE_DIR=/opt/facebook/watchman/var/run/watchman\n# tell cmake not to try to create /opt/facebook/...\nINSTALL_WATCHMAN_STATE_DIR=OFF\nUSE_SYS_PYTHON=OFF\n\n[depends.environment]\nWATCHMAN_VERSION_OVERRIDE\n"
  },
  {
    "path": "build/fbcode_builder/manifests/xxhash",
    "content": "[manifest]\nname = xxhash\n\n[download]\nurl = https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.2.tar.gz\nsha256 = baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4\n\n[rpms]\nxxhash-devel\n\n[debs]\nlibxxhash-dev\nxxhash\n\n[homebrew]\nxxhash\n\n[build]\nbuilder = cmake\nsubdir = xxHash-0.8.2/cmake_unofficial\n\n[cmake.defines]\nCMAKE_POSITION_INDEPENDENT_CODE = ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/xz",
    "content": "[manifest]\nname = xz\n\n# ubuntu's package causes watchman's tests to hang\n[debs.not(distro=ubuntu)]\nliblzma-dev\n\n[homebrew]\nxz\n\n[rpms]\nxz-devel\n\n[download]\nurl = https://tukaani.org/xz/xz-5.2.5.tar.gz\nsha256 = f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10\n\n[build]\nbuilder = autoconf\nsubdir = xz-5.2.5\n\n[autoconf.args]\n--with-pic\n"
  },
  {
    "path": "build/fbcode_builder/manifests/yaml-cpp",
    "content": "[manifest]\nname = yaml-cpp\n\n[download]\nurl = https://github.com/jbeder/yaml-cpp/archive/yaml-cpp-0.6.2.tar.gz\nsha256 = e4d8560e163c3d875fd5d9e5542b5fd5bec810febdcba61481fe5fc4e6b1fd05\n\n[build.os=linux]\nbuilder = cmake\nsubdir = yaml-cpp-yaml-cpp-0.6.2\n\n[build.not(os=linux)]\nbuilder = nop\n\n[dependencies]\nboost\ngoogletest\n\n[cmake.defines]\nYAML_CPP_BUILD_TESTS=OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/zlib",
    "content": "[manifest]\nname = zlib\n\n[debs]\nzlib1g-dev\n\n[homebrew]\nzlib\n\n[rpms.not(distro=fedora)]\nzlib-devel\nzlib-static\n\n[rpms.distro=fedora]\nzlib-ng-compat-devel\nzlib-ng-compat-static\n\n[pps]\nzlib\n\n[download]\nurl = https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz\nsha256 = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23\n\n[build]\nbuilder = cmake\nsubdir = zlib-1.3.1\npatchfile = zlib_dont_build_more_than_needed.patch\n"
  },
  {
    "path": "build/fbcode_builder/manifests/zlib-python",
    "content": "[manifest]\nname = zlib-python\n\n[debs]\nzlib1g-dev\n\n[homebrew]\nzlib\n\n[rpms.not(distro=fedora)]\nzlib-devel\nzlib-static\n\n[rpms.distro=fedora]\nzlib-ng-compat-devel\nzlib-ng-compat-static\n\n[pps]\nzlib\n\n[download]\nurl = https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz\nsha256 = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23\n\n[build]\nbuilder = cmake\nsubdir = zlib-1.3.1\npatchfile = zlib_dont_build_more_than_needed.patch\n\n[build.not(os=linux)]\nbuilder = nop\n\n[cmake.defines]\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n"
  },
  {
    "path": "build/fbcode_builder/manifests/zstd",
    "content": "[manifest]\nname = zstd\n\n[homebrew]\nzstd\n\n# 18.04 zstd is too old\n[debs.not(all(distro=ubuntu,distro_vers=\"18.04\"))]\nlibclang-dev\nlibzstd-dev\nzstd\n\n[rpms]\nlibzstd-devel\nlibzstd\n\n[pps]\nzstd\n\n[download]\nurl = https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz\nsha256 = 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4\n\n[build]\nbuilder = cmake\nsubdir = zstd-1.5.5/build/cmake\n\n# The zstd cmake build explicitly sets the install name\n# for the shared library in such a way that cmake discards\n# the path to the library from the install_name, rendering\n# the library non-resolvable during the build.  The short\n# term solution for this is just to link static on macos.\n#\n# And while we're at it, let's just always link statically.\n[cmake.defines]\nZSTD_BUILD_SHARED = OFF\n"
  },
  {
    "path": "build/fbcode_builder/manifests/zstd-python",
    "content": "[manifest]\nname = zstd-python\n\n[homebrew]\nzstd\n\n# 18.04 zstd is too old\n[debs.not(all(distro=ubuntu,distro_vers=\"18.04\"))]\nlibclang-dev\nlibzstd-dev\nzstd\n\n[rpms]\nlibzstd-devel\nlibzstd\n\n[pps]\nzstd\n\n[download]\nurl = https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz\nsha256 = 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4\n\n[build]\nbuilder = cmake\nsubdir = zstd-1.5.5/build/cmake\n\n[build.not(os=linux)]\nbuilder = nop\n\n# The zstd cmake build explicitly sets the install name\n# for the shared library in such a way that cmake discards\n# the path to the library from the install_name, rendering\n# the library non-resolvable during the build.  The short\n# term solution for this is just to link static on macos.\n#\n# And while we're at it, let's just always link statically.\n[cmake.defines]\nZSTD_BUILD_SHARED = OFF\nCMAKE_POSITION_INDEPENDENT_CODE=ON\n"
  },
  {
    "path": "build/fbcode_builder/patches/boost_1_83_0.patch",
    "content": "diff --git a/boost/serialization/strong_typedef.hpp b/boost/serialization/strong_typedef.hpp\n--- a/boost/serialization/strong_typedef.hpp\n+++ b/boost/serialization/strong_typedef.hpp\n@@ -44,6 +44,7 @@\n     operator const T&() const {return t;}                                                                        \\\n     operator T&() {return t;}                                                                                    \\\n     bool operator==(const D& rhs) const {return t == rhs.t;}                                                     \\\n+    bool operator==(const T& lhs) const {return t == lhs;}                                                       \\\n     bool operator<(const D& rhs) const {return t < rhs.t;}                                                       \\\n };\n \ndiff --git a/tools/build/src/tools/msvc.jam b/tools/build/src/tools/msvc.jam\n--- a/tools/build/src/tools/msvc.jam\n+++ b/tools/build/src/tools/msvc.jam\n@@ -1137,6 +1137,14 @@\n         }\n         else\n         {\n+            if [ MATCH \"(14.4)\" : $(version) ]\n+            {\n+                if $(.debug-configuration)\n+                {\n+                    ECHO \"notice: [generate-setup-cmd] $(version) is 14.4x\" ;\n+                }\n+                parent = [ path.native [ path.join  $(parent) \"..\\\\..\\\\..\\\\..\\\\..\\\\Auxiliary\\\\Build\" ] ] ;\n+            }\n             if [ MATCH \"(14.3)\" : $(version) ]\n             {\n                 if $(.debug-configuration)\n"
  },
  {
    "path": "build/fbcode_builder/patches/iproute2_oss.patch",
    "content": "diff --git a/bridge/fdb.c b/bridge/fdb.c\n--- a/bridge/fdb.c\n+++ b/bridge/fdb.c\n@@ -31,7 +31,7 @@\n\n static unsigned int filter_index, filter_vlan, filter_state;\n\n-json_writer_t *jw_global;\n+static json_writer_t *jw_global;\n\n static void usage(void)\n {\ndiff --git a/ip/ipmroute.c b/ip/ipmroute.c\n--- a/ip/ipmroute.c\n+++ b/ip/ipmroute.c\n@@ -44,7 +44,7 @@\n        exit(-1);\n }\n\n-struct rtfilter {\n+static struct rtfilter {\n        int tb;\n        int af;\n        int iif;\ndiff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c\n--- a/ip/xfrm_monitor.c\n+++ b/ip/xfrm_monitor.c\n@@ -34,7 +34,7 @@\n #include \"ip_common.h\"\n\n static void usage(void) __attribute__((noreturn));\n-int listen_all_nsid;\n+static int listen_all_nsid;\n\n static void usage(void)\n {\n"
  },
  {
    "path": "build/fbcode_builder/patches/libiberty_install_pic_lib.patch",
    "content": "diff --git a/Makefile.in b/Makefile.in\nindex b77a41c..cbe71fe 100644\n--- a/Makefile.in\n+++ b/Makefile.in\n@@ -389,7 +389,7 @@ MULTIOSDIR = `$(CC) $(CFLAGS) -print-multi-os-directory`\n install_to_libdir: all\n        if test -n \"${target_header_dir}\"; then \\\n                ${mkinstalldirs} $(DESTDIR)$(libdir)/$(MULTIOSDIR); \\\n-               $(INSTALL_DATA) $(TARGETLIB) $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n; \\\n+               $(INSTALL_DATA) pic/$(TARGETLIB) $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n; \\\n                ( cd $(DESTDIR)$(libdir)/$(MULTIOSDIR) ; chmod 644 $(TARGETLIB)n ;$(RANLIB) $(TARGETLIB)n ); \\\n                mv -f $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB); \\\n                case \"${target_header_dir}\" in \\\n"
  },
  {
    "path": "build/fbcode_builder/patches/zlib_dont_build_more_than_needed.patch",
    "content": "diff -Naur ../zlib-1.3.1/CMakeLists.txt ./CMakeLists.txt\n--- ../zlib-1.3.1/CMakeLists.txt\t2024-01-22 10:32:37.000000000 -0800\n+++ ./CMakeLists.txt\t2024-01-23 13:14:09.870289968 -0800\n@@ -149,10 +149,8 @@\n     set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj)\n endif(MINGW)\n \n-add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})\n+add_library(zlib ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})\n target_include_directories(zlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})\n-add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})\n-target_include_directories(zlibstatic PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})\n set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)\n set_target_properties(zlib PROPERTIES SOVERSION 1)\n \n@@ -169,7 +167,7 @@\n \n if(UNIX)\n     # On unix-like platforms the library is almost always called libz\n-   set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z)\n+   set_target_properties(zlib PROPERTIES OUTPUT_NAME z)\n    if(NOT APPLE AND NOT(CMAKE_SYSTEM_NAME STREQUAL AIX))\n      set_target_properties(zlib PROPERTIES LINK_FLAGS \"-Wl,--version-script,\\\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\\\"\")\n    endif()\n@@ -179,7 +177,7 @@\n endif()\n \n if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL )\n-    install(TARGETS zlib zlibstatic\n+    install(TARGETS zlib\n         RUNTIME DESTINATION \"${INSTALL_BIN_DIR}\"\n         ARCHIVE DESTINATION \"${INSTALL_LIB_DIR}\"\n         LIBRARY DESTINATION \"${INSTALL_LIB_DIR}\" )\n "
  },
  {
    "path": "cmake/FindGMock.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n#\n# Find libgmock\n#\n#  LIBGMOCK_DEFINES     - List of defines when using libgmock.\n#  LIBGMOCK_INCLUDE_DIR - where to find gmock/gmock.h, etc.\n#  LIBGMOCK_LIBRARIES   - List of libraries when using libgmock.\n#  LIBGMOCK_FOUND       - True if libgmock found.\n\nIF (LIBGMOCK_INCLUDE_DIR)\n  # Already in cache, be silent\n  SET(LIBGMOCK_FIND_QUIETLY TRUE)\nENDIF ()\n\nFIND_PATH(LIBGTEST_INCLUDE_DIR gtest/gtest.h)\nFIND_PATH(LIBGMOCK_INCLUDE_DIR gmock/gmock.h)\n\nFIND_LIBRARY(LIBGMOCK_MAIN_LIBRARY gmock_main)\nFIND_LIBRARY(LIBGMOCK_LIBRARY gmock)\nFIND_LIBRARY(LIBGTEST_LIBRARY gtest)\nset(LIBGMOCK_LIBRARIES\n  ${LIBGMOCK_MAIN_LIBRARY}\n  ${LIBGMOCK_LIBRARY}\n  ${LIBGTEST_LIBRARY}\n)\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n  # The GTEST_LINKED_AS_SHARED_LIBRARY macro must be set properly on Windows.\n  #\n  # There isn't currently an easy way to determine if a library was compiled as\n  # a shared library on Windows, so just assume we've been built against a\n  # shared build of gmock for now.\n  SET(LIBGMOCK_DEFINES \"GTEST_LINKED_AS_SHARED_LIBRARY=1\" CACHE STRING \"\")\nendif()\n\n# handle the QUIETLY and REQUIRED arguments and set LIBGMOCK_FOUND to TRUE if\n# all listed variables are TRUE\nINCLUDE(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(\n  LIBGMOCK\n  DEFAULT_MSG\n  LIBGMOCK_MAIN_LIBRARY\n  LIBGMOCK_LIBRARY\n  LIBGTEST_LIBRARY\n  LIBGMOCK_LIBRARIES\n  LIBGMOCK_INCLUDE_DIR\n  LIBGTEST_INCLUDE_DIR\n)\n\nMARK_AS_ADVANCED(\n  LIBGMOCK_DEFINES\n  LIBGMOCK_MAIN_LIBRARY\n  LIBGMOCK_LIBRARY\n  LIBGTEST_LIBRARY\n  LIBGMOCK_LIBRARIES\n  LIBGMOCK_INCLUDE_DIR\n)\n"
  },
  {
    "path": "cmake/FindZstd.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# - Find zstd\n# Find the zstd compression library and includes\n#\n# ZSTD_INCLUDE_DIR - where to find zstd.h, etc.\n# ZSTD_LIBRARIES - List of libraries when using zstd.\n# ZSTD_FOUND - True if zstd found.\n\nfind_path(ZSTD_INCLUDE_DIR\n  NAMES zstd.h\n  HINTS ${ZSTD_ROOT_DIR}/include)\n\nfind_library(ZSTD_LIBRARIES\n  NAMES zstd\n  HINTS ${ZSTD_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARIES ZSTD_INCLUDE_DIR)\n\nmark_as_advanced(\n  ZSTD_LIBRARIES\n  ZSTD_INCLUDE_DIR\n)\n\nif(NOT TARGET zstd)\n    if(\"${ZSTD_LIBRARIES}\" MATCHES \".*.a$\")\n        add_library(zstd STATIC IMPORTED)\n    else()\n        add_library(zstd SHARED IMPORTED)\n    endif()\n    set_target_properties(\n        zstd\n        PROPERTIES\n            IMPORTED_LOCATION ${ZSTD_LIBRARIES}\n            INTERFACE_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIR}\n    )\nendif()\n"
  },
  {
    "path": "cmake/ProxygenCompatAliases.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Helper macro to create a compat alias with multiple dependencies\nmacro(proxygen_compat_alias _name)\n  add_library(${_name} INTERFACE)\n  target_link_libraries(${_name} INTERFACE ${ARGN})\n  install(TARGETS ${_name} EXPORT proxygen-exports)\n  add_library(proxygen::${_name} ALIAS ${_name})\nendmacro()\n\n# =============================================================================\n# Backwards compatibility aliases (legacy names used by downstream projects)\n# =============================================================================\n\n# quicwebtransport: used by moxygen for QUIC WebTransport support\nproxygen_compat_alias(quicwebtransport\n  proxygen_http_webtransport_quicwebtransport\n)\n\n# =============================================================================\n# Bundle aliases (group multiple granular targets for convenience)\n#\n# See TARGETS.txt for the rationale behind these groupings.\n# =============================================================================\n\n# Core HTTP types and utilities\nproxygen_compat_alias(proxygen_http_core\n  proxygen_error\n  proxygen_http_status_type\n  proxygen_http_http_utils\n  proxygen_http_types\n)\n\n# HTTP/1.1 and HTTP/2 codecs\nproxygen_compat_alias(proxygen_codec\n  proxygen_http_codec\n  proxygen_http_codec_direction\n  proxygen_http_codec_error_code\n  proxygen_http_codec_util\n)\n\n# HQ codec, framer, H3 errors\nproxygen_compat_alias(proxygen_hq_core\n  proxygen_http_codec_hq_codec\n  proxygen_http_h3_errors\n)\n\n# HPACK compression\nproxygen_compat_alias(proxygen_hpack\n  proxygen_http_codec_compress_hpack\n)\n\n# QPACK compression\nproxygen_compat_alias(proxygen_qpack\n  proxygen_http_codec_compress_qpack\n)\n\n# Session base\nproxygen_compat_alias(proxygen_http_session\n  proxygen_http_session_session\n  proxygen_http_session_http_transaction\n)\n\n# HQ Session\nproxygen_compat_alias(proxygen_hq_session\n  proxygen_http_session_hq_session\n  proxygen_http_session_hq_upstream_session\n  proxygen_http_session_hq_downstream_session\n)\n\n# WebTransport\nproxygen_compat_alias(proxygen_webtransport\n  proxygen_http_webtransport\n)\n\n# Combined wt_stream_manager + wt_egress_container\nproxygen_compat_alias(proxygen_webtransport_helpers\n  proxygen_http_webtransport_wt_stream_manager\n  proxygen_http_webtransport_wt_egress_container\n)\n\n# Coro client\nproxygen_compat_alias(proxygen_coro_client\n  proxygen_http_coro_client_http_client_lib\n  proxygen_http_coro_client_http_client_connection_cache\n)\n\n# Coro server\nproxygen_compat_alias(proxygen_coro_server\n  proxygen_http_coro_server_coro_acceptor\n  proxygen_http_coro_server_coro_httpserver\n)\n\n# Coro filters\nproxygen_compat_alias(proxygen_coro_filters\n  proxygen_http_coro_filters_compression_filter\n  proxygen_http_coro_filters_decompression_filter\n)\n\n# Connpool\nproxygen_compat_alias(proxygen_connpool\n  proxygen_http_connpool\n  proxygen_http_connpool_session_holder\n)\n\n# Utils\nproxygen_compat_alias(proxygen_utils\n  proxygen_utils_time_util\n  proxygen_utils_url\n  proxygen_utils_util\n)\n"
  },
  {
    "path": "cmake/ProxygenFunctions.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Initialize global properties for tracking targets and deferred dependencies\nset_property(GLOBAL PROPERTY PROXYGEN_COMPONENT_TARGETS)\nset_property(GLOBAL PROPERTY PROXYGEN_DEFERRED_DEPS)\nset_property(GLOBAL PROPERTY PROXYGEN_GRANULAR_INTERFACE_TARGETS)\n\n# Define a granular proxygen library that:\n# 1. Compiles sources ONCE via OBJECT library\n# 2. Creates a STATIC library for individual linking (static builds)\n# 3. Creates an INTERFACE library linking to monolithic proxygen (shared builds)\n# 4. Defers internal proxygen deps to be resolved later\n# 5. Tracks OBJECT target for monolithic aggregation\n# 6. Creates proxygen:: namespace alias\n#\n# Usage:\n#   proxygen_add_library(proxygen_http_message\n#     SRCS HTTPMessage.cpp\n#     DEPS proxygen_http_headers         # Private dependencies\n#     EXPORTED_DEPS Folly::folly_io_iobuf  # Public dependencies (propagated)\n#     EXCLUDE_FROM_MONOLITH              # Don't include in monolithic proxygen library\n#   )\nfunction(proxygen_add_library _target_name)\n  cmake_parse_arguments(\n    PROXYGEN_LIB\n    \"EXCLUDE_FROM_MONOLITH\"         # Options (boolean flags)\n    \"\"                              # Single-value args\n    \"SRCS;DEPS;EXPORTED_DEPS\"       # Multi-value args\n    ${ARGN}\n  )\n\n  set(_sources ${PROXYGEN_LIB_SRCS})\n  if(NOT _sources)\n    # Legacy support: if no SRCS keyword, treat remaining args as sources\n    set(_sources ${PROXYGEN_LIB_UNPARSED_ARGUMENTS})\n  endif()\n\n  # Object library name - used for monolithic aggregation\n  set(_obj_target \"${_target_name}_obj\")\n\n  # Skip if no sources (header-only library)\n  list(LENGTH _sources _src_count)\n  if(_src_count EQUAL 0)\n    # Header-only: create INTERFACE library\n    add_library(${_target_name} INTERFACE)\n    target_include_directories(${_target_name}\n      INTERFACE\n        $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n        $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n        $<INSTALL_INTERFACE:include/>\n    )\n\n    # Link exported deps for INTERFACE libraries\n    if(PROXYGEN_LIB_EXPORTED_DEPS)\n      target_link_libraries(${_target_name} INTERFACE ${PROXYGEN_LIB_EXPORTED_DEPS})\n    endif()\n\n    install(TARGETS ${_target_name} EXPORT proxygen-exports)\n    add_library(proxygen::${_target_name} ALIAS ${_target_name})\n    return()\n  endif()\n\n  # 1. Create OBJECT library (compiles sources once)\n  add_library(${_obj_target} OBJECT ${_sources})\n\n  # Ensure generated headers are built before any proxygen sources\n  # This is needed because transitive deps don't carry build-order dependencies\n  if(TARGET proxygen-generated)\n    add_dependencies(${_obj_target} proxygen-generated)\n  endif()\n\n  if(DEFINED PACKAGE_VERSION)\n    set_property(TARGET ${_obj_target} PROPERTY VERSION ${PACKAGE_VERSION})\n  endif()\n\n  if(BUILD_SHARED_LIBS)\n    set_property(TARGET ${_obj_target} PROPERTY POSITION_INDEPENDENT_CODE ON)\n  endif()\n\n  target_include_directories(${_obj_target}\n    PUBLIC\n      $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n      $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n      $<INSTALL_INTERFACE:include/>\n  )\n\n  target_compile_options(${_obj_target}\n    PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n  )\n\n  target_compile_features(${_obj_target} PUBLIC cxx_std_20)\n\n  # Separate proxygen internal deps (defer) from external deps (link immediately)\n  # Also separate utility deps (like proxygen-generated) that need add_dependencies\n  set(_immediate_deps \"\")\n  set(_proxygen_deps \"\")\n  set(_utility_deps \"\")\n  foreach(_dep IN LISTS PROXYGEN_LIB_EXPORTED_DEPS)\n    if(_dep STREQUAL \"proxygen-generated\")\n      # Utility target - use add_dependencies, not target_link_libraries\n      list(APPEND _utility_deps ${_dep})\n    elseif(_dep MATCHES \"^proxygen_\")\n      list(APPEND _proxygen_deps ${_dep})\n    else()\n      # Folly::*, fizz::*, mvfst::*, wangle::*, external libs - link immediately\n      list(APPEND _immediate_deps ${_dep})\n    endif()\n  endforeach()\n\n  # Add build-order dependencies on utility targets (like proxygen-generated)\n  if(_utility_deps)\n    add_dependencies(${_obj_target} ${_utility_deps})\n  endif()\n\n  # Link non-proxygen deps immediately - they provide include paths needed at compile time\n  if(_immediate_deps)\n    target_link_libraries(${_obj_target} PUBLIC ${_immediate_deps})\n  endif()\n\n  # For shared builds: link Folly::folly, fizz::fizz, wangle::wangle to OBJECT libraries\n  # to get transitive includes\n  if(BUILD_SHARED_LIBS)\n    target_link_libraries(${_obj_target} PUBLIC Folly::folly fizz::fizz wangle::wangle)\n  endif()\n\n  # Defer internal proxygen dependencies until all targets are created\n  # This is needed for both static and shared builds because:\n  # - Static: avoid circular dependencies during library creation\n  # - Shared: OBJECT libraries need include paths from deps that may not exist yet\n  if(_proxygen_deps)\n    list(JOIN _proxygen_deps \",\" _deps_str)\n    set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n      \"${_obj_target}|PUBLIC|${_deps_str}\"\n    )\n  endif()\n  if(PROXYGEN_LIB_DEPS)\n    # Separate proxygen internal deps (defer) from external deps (link immediately)\n    set(_private_immediate_deps \"\")\n    set(_private_proxygen_deps \"\")\n    foreach(_dep IN LISTS PROXYGEN_LIB_DEPS)\n      if(_dep MATCHES \"^proxygen_\")\n        list(APPEND _private_proxygen_deps ${_dep})\n      else()\n        list(APPEND _private_immediate_deps ${_dep})\n      endif()\n    endforeach()\n\n    if(_private_immediate_deps)\n      target_link_libraries(${_obj_target} PRIVATE ${_private_immediate_deps})\n    endif()\n\n    if(_private_proxygen_deps)\n      list(JOIN _private_proxygen_deps \",\" _deps_str)\n      set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n        \"${_obj_target}|PRIVATE|${_deps_str}\"\n      )\n    endif()\n  endif()\n\n  # Track OBJECT target for monolithic aggregation (unless excluded)\n  if(NOT PROXYGEN_LIB_EXCLUDE_FROM_MONOLITH)\n    set_property(GLOBAL APPEND PROPERTY PROXYGEN_COMPONENT_TARGETS ${_obj_target})\n  endif()\n\n  # 2. Create the granular library target\n  if(BUILD_SHARED_LIBS AND NOT PROXYGEN_LIB_EXCLUDE_FROM_MONOLITH)\n    # For shared builds: create INTERFACE library that will link to monolithic proxygen\n    add_library(${_target_name} INTERFACE)\n\n    target_include_directories(${_target_name}\n      INTERFACE\n        $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n        $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n        $<INSTALL_INTERFACE:include/>\n    )\n\n    # Track this target to link to proxygen after monolithic library is created\n    set_property(GLOBAL APPEND PROPERTY PROXYGEN_GRANULAR_INTERFACE_TARGETS ${_target_name})\n\n    install(TARGETS ${_target_name} EXPORT proxygen-exports)\n  elseif(BUILD_SHARED_LIBS AND PROXYGEN_LIB_EXCLUDE_FROM_MONOLITH)\n    # For excluded targets in shared builds: create SHARED library with actual code\n    # These are NOT in the monolithic proxygen, so they need their own implementation\n    add_library(${_target_name} SHARED $<TARGET_OBJECTS:${_obj_target}>)\n\n    if(DEFINED PACKAGE_VERSION)\n      set_property(TARGET ${_target_name} PROPERTY VERSION ${PACKAGE_VERSION})\n    endif()\n\n    target_include_directories(${_target_name}\n      PUBLIC\n        $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n        $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n        $<INSTALL_INTERFACE:include/>\n    )\n\n    target_compile_features(${_target_name} PUBLIC cxx_std_20)\n\n    # Add build-order dependencies on utility targets\n    if(_utility_deps)\n      add_dependencies(${_target_name} ${_utility_deps})\n    endif()\n\n    # Link non-proxygen deps immediately\n    if(_immediate_deps)\n      target_link_libraries(${_target_name} PUBLIC ${_immediate_deps})\n    endif()\n\n    # Defer internal proxygen dependencies\n    if(_proxygen_deps)\n      list(JOIN _proxygen_deps \",\" _deps_str)\n      set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n        \"${_target_name}|PUBLIC|${_deps_str}\"\n      )\n    endif()\n    if(PROXYGEN_LIB_DEPS)\n      set(_priv_imm \"\")\n      set(_priv_prox \"\")\n      foreach(_dep IN LISTS PROXYGEN_LIB_DEPS)\n        if(_dep MATCHES \"^proxygen_\")\n          list(APPEND _priv_prox ${_dep})\n        else()\n          list(APPEND _priv_imm ${_dep})\n        endif()\n      endforeach()\n      if(_priv_imm)\n        target_link_libraries(${_target_name} PRIVATE ${_priv_imm})\n      endif()\n      if(_priv_prox)\n        list(JOIN _priv_prox \",\" _deps_str)\n        set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n          \"${_target_name}|PRIVATE|${_deps_str}\"\n        )\n      endif()\n    endif()\n\n    install(\n      TARGETS ${_target_name}\n      EXPORT proxygen-exports\n      LIBRARY DESTINATION ${LIB_INSTALL_DIR}\n      ARCHIVE DESTINATION ${LIB_INSTALL_DIR}\n    )\n  else()\n    # For static builds: create STATIC library\n    add_library(${_target_name} STATIC $<TARGET_OBJECTS:${_obj_target}>)\n\n    if(DEFINED PACKAGE_VERSION)\n      set_property(TARGET ${_target_name} PROPERTY VERSION ${PACKAGE_VERSION})\n    endif()\n\n    target_include_directories(${_target_name}\n      PUBLIC\n        $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n        $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n        $<INSTALL_INTERFACE:include/>\n    )\n\n    target_compile_features(${_target_name} PUBLIC cxx_std_20)\n\n    # Add build-order dependencies on utility targets (reuse _utility_deps from above)\n    if(_utility_deps)\n      add_dependencies(${_target_name} ${_utility_deps})\n    endif()\n\n    # Link non-proxygen deps immediately (reuse _immediate_deps computed above)\n    if(_immediate_deps)\n      target_link_libraries(${_target_name} PUBLIC ${_immediate_deps})\n    endif()\n\n    # Defer internal proxygen dependencies for STATIC library too (reuse _proxygen_deps)\n    if(_proxygen_deps)\n      list(JOIN _proxygen_deps \",\" _deps_str)\n      set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n        \"${_target_name}|PUBLIC|${_deps_str}\"\n      )\n    endif()\n    if(PROXYGEN_LIB_DEPS)\n      set(_priv_imm \"\")\n      set(_priv_prox \"\")\n      foreach(_dep IN LISTS PROXYGEN_LIB_DEPS)\n        if(_dep MATCHES \"^proxygen_\")\n          list(APPEND _priv_prox ${_dep})\n        else()\n          list(APPEND _priv_imm ${_dep})\n        endif()\n      endforeach()\n      if(_priv_imm)\n        target_link_libraries(${_target_name} PRIVATE ${_priv_imm})\n      endif()\n      if(_priv_prox)\n        list(JOIN _priv_prox \",\" _deps_str)\n        set_property(GLOBAL APPEND PROPERTY PROXYGEN_DEFERRED_DEPS\n          \"${_target_name}|PRIVATE|${_deps_str}\"\n        )\n      endif()\n    endif()\n\n    install(\n      TARGETS ${_target_name}\n      EXPORT proxygen-exports\n      LIBRARY DESTINATION ${LIB_INSTALL_DIR}\n      ARCHIVE DESTINATION ${LIB_INSTALL_DIR}\n    )\n  endif()\n\n  # Create alias for the library\n  add_library(proxygen::${_target_name} ALIAS ${_target_name})\nendfunction()\n\n# Create a backwards-compatible alias target\n# This creates an INTERFACE library with the old name that links to the new target\nfunction(proxygen_add_compat_alias _old_name _new_name)\n  if(NOT TARGET ${_new_name})\n    message(WARNING \"Cannot create compat alias ${_old_name}: target ${_new_name} does not exist\")\n    return()\n  endif()\n\n  add_library(${_old_name} INTERFACE)\n  target_link_libraries(${_old_name} INTERFACE ${_new_name})\n  install(TARGETS ${_old_name} EXPORT proxygen-exports)\n  add_library(proxygen::${_old_name} ALIAS ${_old_name})\nendfunction()\n\n# Create the monolithic proxygen library from all component OBJECT libraries\n# Call this after all add_subdirectory() calls, before proxygen_resolve_deferred_dependencies()\nfunction(proxygen_create_monolithic_library)\n  get_property(_component_targets GLOBAL PROPERTY PROXYGEN_COMPONENT_TARGETS)\n\n  if(NOT _component_targets)\n    message(STATUS \"No component targets found, skipping monolithic library creation\")\n    return()\n  endif()\n\n  # Collect all object files from component targets\n  set(_all_objects)\n  foreach(_target IN LISTS _component_targets)\n    list(APPEND _all_objects $<TARGET_OBJECTS:${_target}>)\n  endforeach()\n\n  # Create the monolithic library\n  add_library(proxygen ${_all_objects})\n\n  if(BUILD_SHARED_LIBS)\n    set_property(TARGET proxygen PROPERTY POSITION_INDEPENDENT_CODE ON)\n    if(DEFINED PACKAGE_VERSION)\n      set_target_properties(proxygen PROPERTIES VERSION ${PACKAGE_VERSION})\n    endif()\n  endif()\n\n  target_include_directories(proxygen\n    PUBLIC\n      $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n      $<BUILD_INTERFACE:${PROXYGEN_GENERATED_ROOT}>\n      $<INSTALL_INTERFACE:include/>\n  )\n\n  target_compile_features(proxygen PUBLIC cxx_std_20)\n\n  # Link all dependencies\n  target_link_libraries(proxygen\n    PUBLIC\n      Folly::folly\n      fizz::fizz\n      wangle::wangle\n      ${ZSTD_LIBRARIES}\n      ZLIB::ZLIB\n      ${OPENSSL_LIBRARIES}\n      Threads::Threads\n      cares\n    PRIVATE\n      glog::glog\n      ${GFLAG_DEPENDENCIES}\n      ${CMAKE_DL_LIBS}\n  )\n\n  # Create alias for consistency\n  add_library(proxygen::proxygen ALIAS proxygen)\n\n  # For shared builds: link all granular INTERFACE targets to the monolithic library\n  if(BUILD_SHARED_LIBS)\n    cmake_policy(SET CMP0079 NEW)\n    get_property(_interface_targets GLOBAL PROPERTY PROXYGEN_GRANULAR_INTERFACE_TARGETS)\n    foreach(_target IN LISTS _interface_targets)\n      target_link_libraries(${_target} INTERFACE proxygen)\n    endforeach()\n  endif()\nendfunction()\n\n# Resolve all deferred dependencies after all targets have been created\n# Call this after all add_subdirectory() calls\nfunction(proxygen_resolve_deferred_dependencies)\n  # Allow linking targets defined in other directories\n  cmake_policy(SET CMP0079 NEW)\n\n  # For shared builds: link all granular INTERFACE targets to the monolithic library\n  # This is needed because proxygen_add_library creates INTERFACE libraries for shared builds\n  # that need to link to the monolithic proxygen target\n  if(BUILD_SHARED_LIBS)\n    get_property(_interface_targets GLOBAL PROPERTY PROXYGEN_GRANULAR_INTERFACE_TARGETS)\n    foreach(_target IN LISTS _interface_targets)\n      if(TARGET ${_target} AND TARGET proxygen)\n        target_link_libraries(${_target} INTERFACE proxygen)\n      endif()\n    endforeach()\n  endif()\n\n  get_property(_deferred_deps GLOBAL PROPERTY PROXYGEN_DEFERRED_DEPS)\n\n  foreach(_spec IN LISTS _deferred_deps)\n    # Parse the spec: \"target|visibility|dep1,dep2,...\"\n    string(REPLACE \"|\" \";\" _parts \"${_spec}\")\n    list(LENGTH _parts _len)\n    if(_len LESS 3)\n      continue()\n    endif()\n\n    list(GET _parts 0 _target)\n    list(GET _parts 1 _visibility)\n    list(GET _parts 2 _deps_str)\n\n    # Split deps by comma\n    string(REPLACE \",\" \";\" _deps \"${_deps_str}\")\n\n    # Filter to only existing targets (skip deps that weren't generated)\n    # For shared builds and OBJECT targets, prefer linking to _obj version for include paths\n    set(_valid_deps \"\")\n    foreach(_dep IN LISTS _deps)\n      # For shared builds: if target is an OBJECT library (*_obj) and dep has an _obj version,\n      # link to _obj for include path propagation\n      if(BUILD_SHARED_LIBS AND _target MATCHES \"_obj$\" AND TARGET ${_dep}_obj)\n        list(APPEND _valid_deps ${_dep}_obj)\n      elseif(BUILD_SHARED_LIBS AND _target MATCHES \"_obj$\")\n        # No _obj version exists; skip to avoid cycle through monolithic library\n      elseif(TARGET ${_dep})\n        list(APPEND _valid_deps ${_dep})\n      endif()\n    endforeach()\n\n    if(_valid_deps)\n      target_link_libraries(${_target} ${_visibility} ${_valid_deps})\n    endif()\n  endforeach()\nendfunction()\n\n# =============================================================================\n# Header installation function\n# =============================================================================\n# Install headers preserving directory structure relative to rootDir\n# Usage: proxygen_install_headers(proxygen ${CMAKE_CURRENT_SOURCE_DIR} ${HEADERS})\nfunction(proxygen_install_headers rootName rootDir)\n  file(TO_CMAKE_PATH \"${rootDir}\" rootDir)\n  string(LENGTH \"${rootDir}\" rootDirLength)\n  foreach(fil ${ARGN})\n    file(TO_CMAKE_PATH \"${fil}\" filePath)\n    string(FIND \"${filePath}\" \"/\" rIdx REVERSE)\n    if(rIdx EQUAL -1)\n      continue()\n    endif()\n    string(SUBSTRING \"${filePath}\" 0 ${rIdx} filePath)\n\n    string(LENGTH \"${filePath}\" filePathLength)\n    string(FIND \"${filePath}\" \"${rootDir}\" rIdx)\n    if(rIdx EQUAL 0)\n      math(EXPR filePathLength \"${filePathLength} - ${rootDirLength}\")\n      string(SUBSTRING \"${filePath}\" ${rootDirLength} ${filePathLength} fileGroup)\n      install(FILES ${fil}\n              DESTINATION ${INCLUDE_INSTALL_DIR}/${rootName}${fileGroup})\n    endif()\n  endforeach()\nendfunction()\n"
  },
  {
    "path": "cmake/ProxygenTest.cmake",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\noption(BUILD_TESTS  \"Enable tests\" OFF)\ninclude(CTest)\nif(BUILD_TESTS)\n  find_package(GMock 1.10.0 MODULE REQUIRED)\n  find_package(GTest 1.10.0 MODULE REQUIRED)\nendif()\n\nfunction(proxygen_add_test)\n    if(NOT BUILD_TESTS)\n        return()\n    endif()\n\n    set(options)\n    set(one_value_args TARGET WORKING_DIRECTORY PREFIX)\n    set(multi_value_args SOURCES DEPENDS INCLUDES EXTRA_ARGS)\n    cmake_parse_arguments(PARSE_ARGV 0 PROXYGEN_TEST \"${options}\" \"${one_value_args}\" \"${multi_value_args}\")\n\n    if(NOT PROXYGEN_TEST_TARGET)\n      message(FATAL_ERROR \"The TARGET parameter is mandatory.\")\n    endif()\n\n    if(NOT PROXYGEN_TEST_SOURCES)\n      set(PROXYGEN_TEST_SOURCES \"${PROXYGEN_TEST_TARGET}.cpp\")\n    endif()\n\n    add_executable(${PROXYGEN_TEST_TARGET}\n      \"${PROXYGEN_TEST_SOURCES}\"\n    )\n\n    # Ensure generated headers are built before test sources compile\n    if(TARGET proxygen-generated)\n      add_dependencies(${PROXYGEN_TEST_TARGET} proxygen-generated)\n    endif()\n\n    set_property(TARGET ${PROXYGEN_TEST_TARGET} PROPERTY ENABLE_EXPORTS true)\n\n    target_include_directories(${PROXYGEN_TEST_TARGET} PUBLIC\n      \"${PROXYGEN_TEST_INCLUDES}\"\n      ${LIBGMOCK_INCLUDE_DIR}\n      ${LIBGTEST_INCLUDE_DIRS}\n    )\n\n    target_compile_definitions(${PROXYGEN_TEST_TARGET} PUBLIC\n      ${LIBGMOCK_DEFINES}\n    )\n\n    target_link_libraries(${PROXYGEN_TEST_TARGET} PUBLIC\n      \"${PROXYGEN_TEST_DEPENDS}\"\n      ${LIBGMOCK_LIBRARIES}\n      ${GLOG_LIBRARY}\n    )\n\n    target_compile_options(${PROXYGEN_TEST_TARGET} PRIVATE\n      \"${_PROXYGEN_COMMON_COMPILE_OPTIONS}\"\n    )\n\n    gtest_add_tests(TARGET ${PROXYGEN_TEST_TARGET}\n                    EXTRA_ARGS \"${PROXYGEN_TEST_EXTRA_ARGS}\"\n                    WORKING_DIRECTORY ${PROXYGEN_TEST_WORKING_DIRECTORY}\n                    TEST_PREFIX ${PROXYGEN_TEST_PREFIX}\n                    TEST_LIST PROXYGEN_TEST_CASES)\n\n    set_tests_properties(${PROXYGEN_TEST_CASES} PROPERTIES TIMEOUT 120)\nendfunction()\n"
  },
  {
    "path": "cmake/TARGETS.txt",
    "content": "Proxygen CMake Target Compatibility Aliases\n============================================\n\nThis file explains the rationale behind the compatibility aliases defined in\nProxygenCompatAliases.cmake.\n\nBackground\n----------\nProxygen's CMake build now uses granular library targets generated from BUCK\nfiles (via generate_cmake.py). Each BUCK target maps to a CMake target with\na name like proxygen_<path>_<name>.\n\nHowever, downstream projects (like moxygen) depend on higher-level groupings\nrather than individual fine-grained targets. The compat aliases provide stable,\nsemantic names that bundle related granular targets together.\n\nAlias Categories\n----------------\n\nhttp_core\n  Core HTTP types: error codes, status types, utilities, and common types.\n  Groups: proxygen_error, proxygen_http_status_type, proxygen_http_http_utils,\n          proxygen_http_types\n\ncodec\n  HTTP/1.1 and HTTP/2 codec infrastructure.\n  Groups: proxygen_http_codec, proxygen_http_codec_direction,\n          proxygen_http_codec_error_code, proxygen_http_codec_util\n\nhq_core\n  HTTP/3 (HQ) codec and error types.\n  Groups: proxygen_http_codec_hq_codec, proxygen_http_h3_errors\n\nhpack / qpack\n  Header compression libraries for HTTP/2 and HTTP/3 respectively.\n\nsession / hq_session\n  HTTP session management for HTTP/1-2 and HTTP/3.\n\nwebtransport / quicwebtransport\n  WebTransport protocol support over HTTP/2 and QUIC.\n\ncoro / coro_client / coro_server / coro_filters\n  C++20 coroutine-based HTTP client and server APIs.\n\nconnpool\n  Connection pooling infrastructure.\n\ndns\n  DNS resolution using c-ares.\n\nservices\n  Acceptor and service configuration.\n\nutils\n  Common utilities (time, URL parsing, etc.).\n\nhttpserver\n  HTTP server framework including ScopedHTTPServer for testing.\n\nhq_server\n  HQ (HTTP/3) server implementation.\n\nAdding New Aliases\n------------------\nWhen adding new aliases:\n1. Choose a semantic name that describes the functionality, not the path\n2. Group related granular targets that are typically used together\n3. Document the grouping in this file\n4. Avoid breaking existing downstream consumers\n"
  },
  {
    "path": "cmake/proxygen-config.cmake.in",
    "content": "#  Copyright (c) 2018, Facebook, Inc.\n#  All rights reserved.\n#\n#  This source code is licensed under the BSD-style license found in the\n#  LICENSE file in the root directory of this source tree.\n\n# This module sets the following variables:\n#   proxygen_FOUND\n#   proxygen_INCLUDE_DIRS\n#\n# This module exports the following target:\n#    proxygen::proxygen\n#\n# which can be used with target_link_libraries() to pull in the proxygen\n# library.\n\n@PACKAGE_INIT@\n\ninclude(CMakeFindDependencyMacro)\nfind_dependency(Glog)\nfind_dependency(fmt)\nfind_dependency(folly)\nfind_dependency(wangle)\nfind_dependency(mvfst)\nfind_dependency(Fizz)\n# For now, anything that depends on Proxygen has to copy its FindZstd.cmake\n# and issue a `find_package(Zstd)`.  Uncommenting this won't work because\n# this Zstd module exposes a library called `zstd`.  The right fix is\n# discussed on D24686032.\n#\n# find_dependency(Zstd)\nfind_dependency(ZLIB)\nfind_dependency(OpenSSL)\nfind_dependency(Threads)\nfind_dependency(c-ares REQUIRED)\n\nif(NOT TARGET proxygen::proxygen)\n    include(\"${CMAKE_CURRENT_LIST_DIR}/proxygen-targets.cmake\")\n    get_target_property(proxygen_INCLUDE_DIRS proxygen::proxygen INTERFACE_INCLUDE_DIRECTORIES)\nendif()\n\nif(NOT proxygen_FIND_QUIETLY)\n    message(STATUS \"Found proxygen: ${PACKAGE_PREFIX_DIR}\")\nendif()\n\nset(proxygen_LIBRARIES\n  proxygen::proxygen\n  proxygen::proxygencurl\n  proxygen::proxygendeviousbaton\n  proxygen::proxygenhqloggerhelper\n  proxygen::proxygenhttpserver\n  proxygen::proxygenhqserver\n)\n"
  },
  {
    "path": "cmake_uninstall.cmake.in",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT EXISTS \"@CMAKE_BINARY_DIR@/install_manifest.txt\")\n  message(FATAL_ERROR \"Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt\")\nendif(NOT EXISTS \"@CMAKE_BINARY_DIR@/install_manifest.txt\")\n\nfile(READ \"@CMAKE_BINARY_DIR@/install_manifest.txt\" files)\nstring(REGEX REPLACE \"\\n\" \";\" files \"${files}\")\nforeach(file ${files})\n  message(STATUS \"Uninstalling $ENV{DESTDIR}${file}\")\n  if(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    exec_program(\n      \"@CMAKE_COMMAND@\" ARGS \"-E remove \\\"$ENV{DESTDIR}${file}\\\"\"\n      OUTPUT_VARIABLE rm_out\n      RETURN_VALUE rm_retval\n      )\n    if(NOT \"${rm_retval}\" STREQUAL 0)\n      message(FATAL_ERROR \"Problem when removing $ENV{DESTDIR}${file}\")\n    endif(NOT \"${rm_retval}\" STREQUAL 0)\n  else(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    message(STATUS \"File $ENV{DESTDIR}${file} does not exist.\")\n  endif(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\nendforeach(file)\n"
  },
  {
    "path": "getdeps.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nset -xeo pipefail\n\nTOOLCHAIN_DIR=/opt/rh/devtoolset-8/root/usr/bin\nif [[ -d \"$TOOLCHAIN_DIR\" ]]; then\n    PATH=\"$TOOLCHAIN_DIR:$PATH\"\nfi\n\nPROJECT_DIR=$(dirname \"$0\")\nGETDEPS_PATHS=(\n    \"$PROJECT_DIR/build/fbcode_builder/getdeps.py\"\n    \"$PROJECT_DIR/../../opensource/fbcode_builder/getdeps.py\"\n)\n\nROOT_DIR=$(pwd)\nSTAGE=${ROOT_DIR}/_build/\nmkdir -p \"$STAGE\"\n\nfor getdeps in \"${GETDEPS_PATHS[@]}\"; do\n    if [[ -x \"$getdeps\" ]]; then\n        \"$getdeps\" build proxygen --current-project proxygen \"$@\" --install-prefix=${STAGE}\n        exit 0\n    fi\ndone\n\necho \"Could not find getdeps.py!?\" >&2\nexit 1\n"
  },
  {
    "path": "proxygen/.clang-format",
    "content": "---\nAccessModifierOffset: -1\nAlignAfterOpenBracket: Align\nAlignEscapedNewlinesLeft: true\nAlignOperands: true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: false\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: false\nBinPackParameters: false\nBreakBeforeBinaryOperators: false\nBreakBeforeBraces: Attach\nBreakBeforeInheritanceComma: true\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nColumnLimit:     80\nCompactNamespaces: true\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerBinding: true\nExperimentalAutoDetectBinPacking: true\nFixNamespaceComments: true\nIndentCaseLabels: true\nIndentFunctionDeclarationAfterType: false\nIndentWidth:     2\nLanguage: Cpp\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\nObjCSpaceBeforeProtocolList: false\nPenaltyBreakBeforeFirstCallParameter: 10\nPenaltyBreakComment: 60\nPenaltyBreakFirstLessLess: 20\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 200\nPointerBindsToType: true\nSortIncludes: true\nSortUsingDeclarations: true\nSpaceAfterControlStatementKeyword: true\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles: false\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n"
  },
  {
    "path": "proxygen/.clang-tidy",
    "content": "# NOTE there must be no spaces before the '-', so put the comma after.\n# When making changes, be sure to verify the output of the following command to ensure\n# the desired checks are enabled (run from the directory containing a .clang-tidy file):\n# `clang-tidy -list-checks`\n# NOTE: Please don't disable inheritance from the parent to make sure that common checks get propagated.\n---\nInheritParentConfig: true\nChecks: '\nboost-*,\nbugprone-*,\nclang-analyzer-*,\nmodernize-*,\nperformance-*,\n-modernize-use-trailing-return-type,\nfacebook-hte-BadCall-mlock,\nfacebook-hte-PortabilityInclude-WinSock2.h,\nfacebook-hte-PortabilityInclude-Windows.h,\nfacebook-hte-PortabilityInclude-arpa/inet.h,\nfacebook-hte-PortabilityInclude-direct.h,\nfacebook-hte-PortabilityInclude-dirent.h,\nfacebook-hte-PortabilityInclude-gflags/gflags.h,\nfacebook-hte-PortabilityInclude-io.h,\nfacebook-hte-PortabilityInclude-libgen.h,\nfacebook-hte-PortabilityInclude-netdb.h,\nfacebook-hte-PortabilityInclude-netinet/in.h,\nfacebook-hte-PortabilityInclude-netinet/tcp.h,\nfacebook-hte-PortabilityInclude-openssl/asn1.h,\nfacebook-hte-PortabilityInclude-openssl/bio.h,\nfacebook-hte-PortabilityInclude-openssl/crypto.h,\nfacebook-hte-PortabilityInclude-openssl/dh.h,\nfacebook-hte-PortabilityInclude-openssl/ec.h,\nfacebook-hte-PortabilityInclude-openssl/ecdsa.h,\nfacebook-hte-PortabilityInclude-openssl/err.h,\nfacebook-hte-PortabilityInclude-openssl/evp.h,\nfacebook-hte-PortabilityInclude-openssl/hmac.h,\nfacebook-hte-PortabilityInclude-openssl/opensslv.h,\nfacebook-hte-PortabilityInclude-openssl/rand.h,\nfacebook-hte-PortabilityInclude-openssl/rsa.h,\nfacebook-hte-PortabilityInclude-openssl/sha.h,\nfacebook-hte-PortabilityInclude-openssl/ssl.h,\nfacebook-hte-PortabilityInclude-openssl/tls1.h,\nfacebook-hte-PortabilityInclude-openssl/x509.h,\nfacebook-hte-PortabilityInclude-openssl/x509v3.h,\nfacebook-hte-PortabilityInclude-poll.h,\nfacebook-hte-PortabilityInclude-pthread.h,\nfacebook-hte-PortabilityInclude-sched.h,\nfacebook-hte-PortabilityInclude-semaphore.h,\nfacebook-hte-PortabilityInclude-strings.h,\nfacebook-hte-PortabilityInclude-sys/file.h,\nfacebook-hte-PortabilityInclude-sys/mman.h,\nfacebook-hte-PortabilityInclude-sys/resource.h,\nfacebook-hte-PortabilityInclude-sys/socket.h,\nfacebook-hte-PortabilityInclude-sys/syscall.h,\nfacebook-hte-PortabilityInclude-sys/time.h,\nfacebook-hte-PortabilityInclude-sys/uio.h,\nfacebook-hte-PortabilityInclude-sys/un.h,\nfacebook-hte-PortabilityInclude-syslog.h,\nfacebook-hte-PortabilityInclude-unistd.h,\nfacebook-hte-StdToStringUse,\ncppcoreguidelines-pro-type-member-init,\ncppcoreguidelines-init-variables,\n'\n...\n"
  },
  {
    "path": "proxygen/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif (NOT DEFINED LIB_INSTALL_DIR)\n    set(LIB_INSTALL_DIR \"lib\")\nendif()\n\nadd_subdirectory(external)\nadd_subdirectory(lib)\nadd_subdirectory(httpserver)\nadd_subdirectory(httpclient)\nadd_subdirectory(fuzzers)\n\n# =============================================================================\n# Backwards compatibility aliases for downstream projects\n# =============================================================================\ninclude(ProxygenCompatAliases)\n\n# =============================================================================\n# Resolve deferred dependencies after ALL subdirectories are processed\n# =============================================================================\nproxygen_resolve_deferred_dependencies()\n"
  },
  {
    "path": "proxygen/VERSION",
    "content": "32:0\n"
  },
  {
    "path": "proxygen/build.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n## Run this script to build proxygen and run the tests. If you want to\n## install proxygen to use in another C++ project on this machine, run\n## the sibling file `reinstall.sh`.\n\n# Obtain the base directory this script resides in.\nBASE_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n\n# Useful constants\nCOLOR_RED=\"\\033[0;31m\"\nCOLOR_GREEN=\"\\033[0;32m\"\nCOLOR_OFF=\"\\033[0m\"\n\nfunction detect_platform() {\n  unameOut=\"$(uname -s)\"\n  case \"${unameOut}\" in\n      Linux*)\n        PLATFORM=Linux\n        if [ -x \"$(command -v lsb_release)\" ]; then\n          DISTRO=\"$(lsb_release -is)\"\n        else\n          DISTRO=\"CentOS\"\n        fi\n        ;;\n      Darwin*)    PLATFORM=Mac;;\n      *)          PLATFORM=\"UNKNOWN:${unameOut}\"\n  esac\n  echo -e \"${COLOR_GREEN}Detected platform: $PLATFORM  Distribution $DISTRO ${COLOR_OFF}\"\n}\n\nfunction install_dependencies_linux_default() {\n  sudo apt-get install -yq \\\n    $deps_universal \\\n    libgflags-dev \\\n    libgoogle-glog-dev \\\n    libkrb5-dev \\\n    libsasl2-dev \\\n    libnuma-dev \\\n    pkg-config \\\n    libssl-dev \\\n    libcap-dev \\\n    libevent-dev \\\n    libtool \\\n    libboost-all-dev \\\n    libjemalloc-dev \\\n    libsnappy-dev \\\n    libiberty-dev \\\n    liblz4-dev \\\n    liblzma-dev \\\n    zlib1g-dev \\\n    binutils-dev \\\n    libsodium-dev \\\n    libdouble-conversion-dev\n}\n\nfunction install_dependencies_linux_fedora() {\n  sudo dnf install -y \\\n    $deps_universal \\\n    m4 \\\n    g++ \\\n    flex \\\n    bison \\\n    gflags-devel \\\n    glog-devel \\\n    krb5-libs \\\n    double-conversion-devel \\\n    libzstd-devel \\\n    libsodium-devel \\\n    binutils-devel \\\n    zlib-devel \\\n    make \\\n    lz4-devel \\\n    wget \\\n    unzip \\\n    snappy-devel \\\n    jemalloc-devel \\\n    boost-devel\\\n    cyrus-sasl-devel \\\n    numactl-libs \\\n    openssl-devel \\\n    libcap-devel \\\n    libevent-devel \\\n    libtool \\\n    gperf\n}\n\nfunction install_dependencies_linux_centos() {\n  sudo dnf install -y \\\n    $deps_universal \\\n    m4 \\\n    g++ \\\n    flex \\\n    bison \\\n    fast_float-devel \\\n    gflags-devel \\\n    glog-devel \\\n    krb5-libs \\\n    double-conversion-devel \\\n    libzstd-devel \\\n    libsodium-devel \\\n    binutils-devel \\\n    zlib-devel \\\n    make \\\n    lz4-devel \\\n    wget \\\n    unzip \\\n    snappy-devel \\\n    jemalloc-devel \\\n    boost-devel\\\n    cyrus-sasl-devel \\\n    numactl-libs \\\n    openssl-devel \\\n    libcap-devel \\\n    libevent-devel \\\n    libtool \\\n    gperf\n}\n\nfunction install_dependencies_linux {\n  deps_universal=\"\\\n    git \\\n    cmake \\\n    m4 \\\n    g++ \\\n    flex \\\n    bison \\\n    gperf \\\n    wget \\\n    unzip \\\n    make\"\n\n  case \"$DISTRO\" in\n      Fedora*)    install_dependencies_linux_fedora;;\n      CentOS*)    install_dependencies_linux_centos;;\n      *)          install_dependencies_linux_default;;\n  esac\n}\n\nfunction install_dependencies_mac() {\n  # install the default dependencies from homebrew\n  brew install -f            \\\n    cmake                    \\\n    m4                       \\\n    boost                    \\\n    double-conversion        \\\n    gflags                   \\\n    glog                     \\\n    gperf                    \\\n    libevent                 \\\n    lz4                      \\\n    snappy                   \\\n    xz                       \\\n    openssl                  \\\n    libsodium\n\n  brew link                 \\\n    cmake                   \\\n    boost                   \\\n    double-conversion       \\\n    gflags                  \\\n    glog                    \\\n    gperf                   \\\n    libevent                \\\n    lz4                     \\\n    snappy                  \\\n    openssl                 \\\n    xz                      \\\n    libsodium\n}\n\nfunction install_dependencies() {\n  echo -e \"${COLOR_GREEN}[ INFO ] install dependencies ${COLOR_OFF}\"\n  if [ \"$PLATFORM\" = \"Linux\" ]; then\n    install_dependencies_linux\n  elif [ \"$PLATFORM\" = \"Mac\" ]; then\n    install_dependencies_mac\n  else\n    echo -e \"${COLOR_RED}[ ERROR ] Unknown platform: $PLATFORM ${COLOR_OFF}\"\n    exit 1\n  fi\n}\n\nfunction synch_dependency_to_commit() {\n  # Utility function to synch a dependency to a specific commit. Takes two arguments:\n  #   - $1: folder of the dependency's git repository\n  #   - $2: path to the text file containing the desired commit hash\n  if [ \"$FETCH_DEPENDENCIES\" = false ] ; then\n    return\n  fi\n  DEP_REV=$(sed 's/Subproject commit //' \"$2\")\n  pushd \"$1\"\n  git fetch\n  # Disable git warning about detached head when checking out a specific commit.\n  git -c advice.detachedHead=false checkout \"$DEP_REV\"\n  popd\n}\n\nfunction setup_fmt() {\n  FMT_DIR=$DEPS_DIR/fmt\n  FMT_BUILD_DIR=$DEPS_DIR/fmt/build/\n  FMT_TAG=$(grep \"subdir = \" ../../build/fbcode_builder/manifests/fmt | cut -d \"-\" -f 2)\n  if [ ! -d \"$FMT_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning fmt repo ${COLOR_OFF}\"\n    git clone https://github.com/fmtlib/fmt.git  \"$FMT_DIR\"\n  fi\n  cd \"$FMT_DIR\"\n  git fetch --tags\n  git checkout \"${FMT_TAG}\"\n  echo -e \"${COLOR_GREEN}Building fmt ${COLOR_OFF}\"\n  mkdir -p \"$FMT_BUILD_DIR\"\n  cd \"$FMT_BUILD_DIR\" || exit\n\n  cmake                                           \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"               \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"            \\\n    -DCMAKE_BUILD_TYPE=RelWithDebInfo             \\\n    \"$MAYBE_OVERRIDE_CXX_FLAGS\"                   \\\n    -DFMT_DOC=OFF                                 \\\n    -DFMT_TEST=OFF                                \\\n    ..\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}fmt is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_googletest() {\n  GTEST_DIR=$DEPS_DIR/googletest\n  GTEST_BUILD_DIR=$DEPS_DIR/googletest/build/\n  GTEST_TAG=$(grep \"subdir = \" ../../build/fbcode_builder/manifests/googletest | cut -d \"-\" -f 2,3)\n  if [ ! -d \"$GTEST_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning googletest repo ${COLOR_OFF}\"\n    git clone https://github.com/google/googletest.git  \"$GTEST_DIR\"\n  fi\n  cd \"$GTEST_DIR\"\n  git fetch --tags\n  git checkout \"${GTEST_TAG}\"\n  echo -e \"${COLOR_GREEN}Building googletest ${COLOR_OFF}\"\n  mkdir -p \"$GTEST_BUILD_DIR\"\n  cd \"$GTEST_BUILD_DIR\" || exit\n\n  cmake                                           \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"               \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"            \\\n    -DCMAKE_BUILD_TYPE=RelWithDebInfo             \\\n    ..\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}googletest is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_zstd() {\n  ZSTD_DIR=$DEPS_DIR/zstd\n  ZSTD_BUILD_DIR=$DEPS_DIR/zstd/build/cmake/builddir\n  ZSTD_INSTALL_DIR=$DEPS_DIR\n  ZSTD_TAG=$(grep \"subdir = \" ../../build/fbcode_builder/manifests/zstd | cut -d \"-\" -f 2 | cut -d \"/\" -f 1)\n  if [ ! -d \"$ZSTD_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning zstd repo ${COLOR_OFF}\"\n    git clone https://github.com/facebook/zstd.git \"$ZSTD_DIR\"\n  fi\n  cd \"$ZSTD_DIR\"\n  git fetch --tags\n  git checkout \"v${ZSTD_TAG}\"\n  echo -e \"${COLOR_GREEN}Building Zstd ${COLOR_OFF}\"\n  mkdir -p \"$ZSTD_BUILD_DIR\"\n  cd \"$ZSTD_BUILD_DIR\" || exit\n  cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo           \\\n    -DBUILD_TESTS=OFF                               \\\n    -DCMAKE_PREFIX_PATH=\"$ZSTD_INSTALL_DIR\"         \\\n    -DCMAKE_INSTALL_PREFIX=\"$ZSTD_INSTALL_DIR\"      \\\n    ${CMAKE_EXTRA_ARGS[@]+\"${CMAKE_EXTRA_ARGS[@]}\"} \\\n    ..\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}Zstd is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_folly() {\n  FOLLY_DIR=$DEPS_DIR/folly\n  FOLLY_BUILD_DIR=$DEPS_DIR/folly/build/\n\n  if [ ! -d \"$FOLLY_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning folly repo ${COLOR_OFF}\"\n    git clone https://github.com/facebook/folly.git \"$FOLLY_DIR\"\n  fi\n  synch_dependency_to_commit \"$FOLLY_DIR\" \"$BASE_DIR\"/../build/deps/github_hashes/facebook/folly-rev.txt\n  if [ \"$PLATFORM\" = \"Mac\" ]; then\n    # Homebrew installs OpenSSL in a non-default location on MacOS >= Mojave\n    # 10.14 because MacOS has its own SSL implementation.  If we find the\n    # typical Homebrew OpenSSL dir, load OPENSSL_ROOT_DIR so that cmake\n    # will find the Homebrew version.\n    dir=/usr/local/opt/openssl\n    if [ -d $dir ]; then\n        export OPENSSL_ROOT_DIR=$dir\n    fi\n  fi\n  echo -e \"${COLOR_GREEN}Building Folly ${COLOR_OFF}\"\n  mkdir -p \"$FOLLY_BUILD_DIR\"\n  cd \"$FOLLY_BUILD_DIR\" || exit\n  MAYBE_DISABLE_JEMALLOC=\"\"\n  if [ \"$NO_JEMALLOC\" == true ] ; then\n    MAYBE_DISABLE_JEMALLOC=\"-DFOLLY_USE_JEMALLOC=0\"\n  fi\n\n  MAYBE_USE_STATIC_DEPS=\"\"\n  MAYBE_USE_STATIC_BOOST=\"\"\n  MAYBE_BUILD_SHARED_LIBS=\"\"\n  if [ \"$BUILD_FOR_FUZZING\" == true ] ; then\n    MAYBE_USE_STATIC_DEPS=\"-DUSE_STATIC_DEPS_ON_UNIX=ON\"\n    MAYBE_USE_STATIC_BOOST=\"-DBOOST_LINK_STATIC=ON\"\n    MAYBE_BUILD_SHARED_LIBS=\"-DBUILD_SHARED_LIBS=OFF\"\n  fi\n\n  cmake                                           \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"               \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"            \\\n    -DCMAKE_BUILD_TYPE=RelWithDebInfo             \\\n    -DBUILD_TESTS=OFF                             \\\n    \"$MAYBE_USE_STATIC_DEPS\"                      \\\n    \"$MAYBE_USE_STATIC_BOOST\"                     \\\n    \"$MAYBE_BUILD_SHARED_LIBS\"                    \\\n    \"$MAYBE_OVERRIDE_CXX_FLAGS\"                   \\\n    $MAYBE_DISABLE_JEMALLOC                       \\\n    ..\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}Folly is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_fizz() {\n  FIZZ_DIR=$DEPS_DIR/fizz\n  FIZZ_BUILD_DIR=$DEPS_DIR/fizz/build/\n  if [ ! -d \"$FIZZ_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning fizz repo ${COLOR_OFF}\"\n    git clone https://github.com/facebookincubator/fizz \"$FIZZ_DIR\"\n  fi\n  synch_dependency_to_commit \"$FIZZ_DIR\" \"$BASE_DIR\"/../build/deps/github_hashes/facebookincubator/fizz-rev.txt\n  echo -e \"${COLOR_GREEN}Building Fizz ${COLOR_OFF}\"\n  mkdir -p \"$FIZZ_BUILD_DIR\"\n  cd \"$FIZZ_BUILD_DIR\" || exit\n\n  MAYBE_USE_STATIC_DEPS=\"\"\n  MAYBE_USE_SODIUM_STATIC_LIBS=\"\"\n  MAYBE_BUILD_SHARED_LIBS=\"\"\n  if [ \"$BUILD_FOR_FUZZING\" == true ] ; then\n    MAYBE_USE_STATIC_DEPS=\"-DUSE_STATIC_DEPS_ON_UNIX=ON\"\n    MAYBE_USE_SODIUM_STATIC_LIBS=\"-Dsodium_USE_STATIC_LIBS=ON\"\n    MAYBE_BUILD_SHARED_LIBS=\"-DBUILD_SHARED_LIBS=OFF\"\n  fi\n\n  cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo       \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"             \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"          \\\n    -DBUILD_TESTS=OFF                           \\\n    \"$MAYBE_USE_STATIC_DEPS\"                    \\\n    \"$MAYBE_BUILD_SHARED_LIBS\"                  \\\n    \"$MAYBE_OVERRIDE_CXX_FLAGS\"                 \\\n    \"$MAYBE_USE_SODIUM_STATIC_LIBS\"             \\\n    \"$FIZZ_DIR/fizz\"\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}Fizz is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_wangle() {\n  WANGLE_DIR=$DEPS_DIR/wangle\n  WANGLE_BUILD_DIR=$DEPS_DIR/wangle/build/\n  if [ ! -d \"$WANGLE_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning wangle repo ${COLOR_OFF}\"\n    git clone https://github.com/facebook/wangle \"$WANGLE_DIR\"\n  fi\n  synch_dependency_to_commit \"$WANGLE_DIR\" \"$BASE_DIR\"/../build/deps/github_hashes/facebook/wangle-rev.txt\n  echo -e \"${COLOR_GREEN}Building Wangle ${COLOR_OFF}\"\n  mkdir -p \"$WANGLE_BUILD_DIR\"\n  cd \"$WANGLE_BUILD_DIR\" || exit\n\n  MAYBE_USE_STATIC_DEPS=\"\"\n  MAYBE_BUILD_SHARED_LIBS=\"\"\n  if [ \"$BUILD_FOR_FUZZING\" == true ] ; then\n    MAYBE_USE_STATIC_DEPS=\"-DUSE_STATIC_DEPS_ON_UNIX=ON\"\n    MAYBE_BUILD_SHARED_LIBS=\"-DBUILD_SHARED_LIBS=OFF\"\n  fi\n\n  cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo       \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"             \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"          \\\n    -DBUILD_TESTS=OFF                           \\\n    \"$MAYBE_USE_STATIC_DEPS\"                    \\\n    \"$MAYBE_BUILD_SHARED_LIBS\"                  \\\n    \"$MAYBE_OVERRIDE_CXX_FLAGS\"                 \\\n    \"$WANGLE_DIR/wangle\"\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}Wangle is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\nfunction setup_mvfst() {\n  MVFST_DIR=$DEPS_DIR/mvfst\n  MVFST_BUILD_DIR=$DEPS_DIR/mvfst/build/\n  if [ ! -d \"$MVFST_DIR\" ] ; then\n    echo -e \"${COLOR_GREEN}[ INFO ] Cloning mvfst repo ${COLOR_OFF}\"\n    git clone https://github.com/facebook/mvfst \"$MVFST_DIR\"\n  fi\n  synch_dependency_to_commit \"$MVFST_DIR\" \"$BASE_DIR\"/../build/deps/github_hashes/facebook/mvfst-rev.txt\n  echo -e \"${COLOR_GREEN}Building Mvfst ${COLOR_OFF}\"\n  mkdir -p \"$MVFST_BUILD_DIR\"\n  cd \"$MVFST_BUILD_DIR\" || exit\n\n  MAYBE_USE_STATIC_DEPS=\"\"\n  MAYBE_BUILD_SHARED_LIBS=\"\"\n  if [ \"$BUILD_FOR_FUZZING\" == true ] ; then\n    MAYBE_USE_STATIC_DEPS=\"-DUSE_STATIC_DEPS_ON_UNIX=ON\"\n    MAYBE_BUILD_SHARED_LIBS=\"-DBUILD_SHARED_LIBS=OFF\"\n  fi\n\n\n  cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo       \\\n    -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"             \\\n    -DCMAKE_INSTALL_PREFIX=\"$DEPS_DIR\"          \\\n    -DBUILD_TESTS=OFF                           \\\n    \"$MAYBE_USE_STATIC_DEPS\"                    \\\n    \"$MAYBE_BUILD_SHARED_LIBS\"                  \\\n    \"$MAYBE_OVERRIDE_CXX_FLAGS\"                 \\\n    \"$MVFST_DIR\"\n  make -j \"$JOBS\"\n  make install\n  echo -e \"${COLOR_GREEN}Mvfst is installed ${COLOR_OFF}\"\n  cd \"$BWD\" || exit\n}\n\n# Parse args\nJOBS=8\nINSTALL_DEPENDENCIES=true\nFETCH_DEPENDENCIES=true\nPREFIX=\"\"\nCOMPILER_FLAGS=\"\"\nPROXY_SERVER_HOST=\"\"\nPROXY_SERVER_PORT=\"8080\"\n\nUSAGE=\"./build.sh [-j num_jobs] [-m|--no-jemalloc] [--no-install-dependencies] [-p|--prefix] [-x|--compiler-flags] [--no-fetch-dependencies] [--proxy_server_host] [--proxy_server_port]\"\nwhile [ \"$1\" != \"\" ]; do\n  case $1 in\n    -j | --jobs ) shift\n                  JOBS=$1\n                  ;;\n    -m | --no-jemalloc )\n                  NO_JEMALLOC=true\n                  ;;\n    --no-install-dependencies )\n                  INSTALL_DEPENDENCIES=false\n          ;;\n    --no-fetch-dependencies )\n                  FETCH_DEPENDENCIES=false\n          ;;\n    --build-for-fuzzing )\n                  BUILD_FOR_FUZZING=true\n      ;;\n    -t | --no-tests )\n                  NO_BUILD_TESTS=true\n      ;;\n    -p | --prefix )\n                  shift\n                  PREFIX=$1\n      ;;\n    -x | --compiler-flags )\n                  shift\n                  COMPILER_FLAGS=$1\n      ;;\n    --proxy_server_host )\n                  shift\n                  PROXY_SERVER_HOST=$1\n      ;;\n    --proxy_server_port )\n                  shift\n                  PROXY_SERVER_PORT=$1\n      ;;\n    * )           echo $USAGE\n                  exit 1\nesac\nshift\ndone\n\ndetect_platform\n\nif [ \"$INSTALL_DEPENDENCIES\" == true ] ; then\n  install_dependencies\nfi\n\nMAYBE_OVERRIDE_CXX_FLAGS=\"\"\nif [ -n \"$COMPILER_FLAGS\" ] ; then\n  MAYBE_OVERRIDE_CXX_FLAGS=\"-DCMAKE_CXX_FLAGS=$COMPILER_FLAGS\"\nfi\n\nBUILD_DIR=_build\nmkdir -p $BUILD_DIR\n\nset -e nounset\ntrap 'cd $BASE_DIR' EXIT\ncd $BUILD_DIR || exit\nBWD=$(pwd)\nDEPS_DIR=$BWD/deps\nmkdir -p \"$DEPS_DIR\"\n\n# Must execute from the directory containing this script\ncd \"$(dirname \"$0\")\"\n\nif [ -n \"$PROXY_SERVER_HOST\" ]; then\n    export https_proxy=http://$PROXY_SERVER_HOST:$PROXY_SERVER_PORT\n    export http_proxy=http://$PROXY_SERVER_HOST:$PROXY_SERVER_PORT\nfi\nsetup_fmt\nsetup_googletest\nsetup_zstd\nsetup_folly\nsetup_fizz\nsetup_wangle\nsetup_mvfst\n\nMAYBE_BUILD_FUZZERS=\"\"\nMAYBE_USE_STATIC_DEPS=\"\"\nMAYBE_LIB_FUZZING_ENGINE=\"\"\nMAYBE_BUILD_SHARED_LIBS=\"\"\nMAYBE_BUILD_TESTS=\"-DBUILD_TESTS=ON\"\nif [ \"$NO_BUILD_TESTS\" == true ] ; then\n  MAYBE_BUILD_TESTS=\"-DBUILD_TESTS=OFF\"\nfi\nif [ \"$BUILD_FOR_FUZZING\" == true ] ; then\n  MAYBE_BUILD_FUZZERS=\"-DBUILD_FUZZERS=ON\"\n  MAYBE_USE_STATIC_DEPS=\"-DUSE_STATIC_DEPS_ON_UNIX=ON\"\n  MAYBE_LIB_FUZZING_ENGINE=\"-DLIB_FUZZING_ENGINE='$LIB_FUZZING_ENGINE'\"\n  MAYBE_BUILD_SHARED_LIBS=\"-DBUILD_SHARED_LIBS=OFF\"\nfi\n\nif [ -z \"$PREFIX\" ]; then\n  PREFIX=$BWD\nfi\n\n# Build proxygen with cmake\ncd \"$BWD\" || exit\ncmake                                     \\\n  -DCMAKE_BUILD_TYPE=RelWithDebInfo       \\\n  -DCMAKE_PREFIX_PATH=\"$DEPS_DIR\"         \\\n  -DCMAKE_INSTALL_PREFIX=\"$PREFIX\"        \\\n  \"$MAYBE_BUILD_TESTS\"                    \\\n  \"$MAYBE_BUILD_FUZZERS\"                  \\\n  \"$MAYBE_BUILD_SHARED_LIBS\"              \\\n  \"$MAYBE_OVERRIDE_CXX_FLAGS\"             \\\n  \"$MAYBE_USE_STATIC_DEPS\"                \\\n  \"$MAYBE_LIB_FUZZING_ENGINE\"             \\\n  ../..\n\nmake -j \"$JOBS\"\necho -e \"${COLOR_GREEN}Proxygen build is complete. To run unit test: \\\n  cd _build/ && make test ${COLOR_OFF}\"\n"
  },
  {
    "path": "proxygen/external/CMakeLists.txt",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nadd_library(\n    proxygen_http_parser OBJECT\n    http_parser/http_parser_cpp.cpp\n)\n\ntarget_compile_options(\n    proxygen_http_parser PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n    \"-DHTTP_PARSER_STRICT_URL=1\"\n    \"-Wno-implicit-fallthrough\"\n)\n\nif (BUILD_SHARED_LIBS)\n  set_property(TARGET proxygen_http_parser PROPERTY POSITION_INDEPENDENT_CODE ON)\nendif()\n\ntarget_include_directories(\n    proxygen_http_parser PRIVATE\n    ${PROXYGEN_FBCODE_ROOT}\n)\ninstall(FILES http_parser/http_parser.h DESTINATION\n        include/proxygen/external/http_parser)\n"
  },
  {
    "path": "proxygen/external/http_parser/CONTRIBUTIONS",
    "content": "Contributors must agree to the Contributor License Agreement before patches\ncan be accepted.\n\nhttp://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ\n"
  },
  {
    "path": "proxygen/external/http_parser/LICENSE-MIT",
    "content": "http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright\nIgor Sysoev.\n\nAdditional changes are licensed under the same terms as NGINX and\ncopyright Joyent, Inc. and other Node contributors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE. \n"
  },
  {
    "path": "proxygen/external/http_parser/README.md",
    "content": "HTTP Parser\n===========\n\nThis is a parser for HTTP messages written in C. It parses both requests and\nresponses. The parser is designed to be used in performance HTTP\napplications. It does not make any syscalls nor allocations, it does not\nbuffer data, it can be interrupted at anytime. Depending on your\narchitecture, it only requires about 40 bytes of data per message\nstream (in a web server that is per connection).\n\nFeatures:\n\n  * No dependencies\n  * Handles persistent streams (keep-alive).\n  * Decodes chunked encoding.\n  * Upgrade support\n  * Defends against buffer overflow attacks.\n\nThe parser extracts the following information from HTTP messages:\n\n  * Header fields and values\n  * Content-Length\n  * Request method\n  * Response status code\n  * Transfer-Encoding\n  * HTTP version\n  * Request URL\n  * Message body\n\n\nUsage\n-----\n\nOne `http_parser` object is used per TCP connection. Initialize the struct\nusing `http_parser_init()` and set the callbacks. That might look something\nlike this for a request parser:\n\n    http_parser_settings settings;\n    settings.on_path = my_path_callback;\n    settings.on_header_field = my_header_field_callback;\n    /* ... */\n\n    http_parser *parser = malloc(sizeof(http_parser));\n    http_parser_init(parser, HTTP_REQUEST);\n    parser->data = my_socket;\n\nWhen data is received on the socket execute the parser and check for errors.\n\n    size_t len = 80*1024, nparsed;\n    char buf[len];\n    ssize_t recved;\n\n    recved = recv(fd, buf, len, 0);\n\n    if (recved < 0) {\n      /* Handle error. */\n    }\n\n    /* Start up / continue the parser.\n     * Note we pass recved==0 to signal that EOF has been received.\n     */\n    nparsed = http_parser_execute(parser, &settings, buf, recved);\n\n    if (parser->upgrade) {\n      /* handle new protocol */\n    } else if (nparsed != recved) {\n      /* Handle error. Usually just close the connection. */\n    }\n\nHTTP needs to know where the end of the stream is. For example, sometimes\nservers send responses without Content-Length and expect the client to\nconsume input (for the body) until EOF. To tell http_parser about EOF, give\n`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors\ncan still be encountered during an EOF, so one must still be prepared\nto receive them.\n\nScalar valued message information such as `status_code`, `method`, and the\nHTTP version are stored in the parser structure. This data is only\ntemporally stored in `http_parser` and gets reset on each new message. If\nthis information is needed later, copy it out of the structure during the\n`headers_complete` callback.\n\nThe parser decodes the transfer-encoding for both requests and responses\ntransparently. That is, a chunked encoding is decoded before being sent to\nthe on_body callback.\n\n\nThe Special Problem of Upgrade\n------------------------------\n\nHTTP supports upgrading the connection to a different protocol. An\nincreasingly common example of this is the Web Socket protocol which sends\na request like\n\n        GET /demo HTTP/1.1\n        Upgrade: WebSocket\n        Connection: Upgrade\n        Host: example.com\n        Origin: http://example.com\n        WebSocket-Protocol: sample\n\nfollowed by non-HTTP data.\n\n(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more\ninformation the Web Socket protocol.)\n\nTo support this, the parser will treat this as a normal HTTP message without a\nbody. Issuing both on_headers_complete and on_message_complete callbacks. However\nhttp_parser_execute() will stop parsing at the end of the headers and return.\n\nThe user is expected to check if `parser->upgrade` has been set to 1 after\n`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied\noffset by the return value of `http_parser_execute()`.\n\n\nCallbacks\n---------\n\nDuring the `http_parser_execute()` call, the callbacks set in\n`http_parser_settings` will be executed. The parser maintains state and\nnever looks behind, so buffering the data is not necessary. If you need to\nsave certain data for later usage, you can do that from the callbacks.\n\nThere are two types of callbacks:\n\n* notification `typedef int (*http_cb) (http_parser*);`\n    Callbacks: on_message_begin, on_headers_complete, on_message_complete.\n* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`\n    Callbacks: (requests only) on_uri,\n               (common) on_header_field, on_header_value, on_body;\n\nCallbacks must return 0 on success. Returning a non-zero value indicates\nerror to the parser, making it exit immediately.\n\nIn case you parse HTTP message in chunks (i.e. `read()` request line\nfrom socket, parse, read half headers, parse, etc) your data callbacks\nmay be called more than once. Http-parser guarantees that data pointer is only\nvalid for the lifetime of callback. You can also `read()` into a heap allocated\nbuffer to avoid copying memory around if this fits your application.\n\nReading headers may be a tricky task if you read/parse headers partially.\nBasically, you need to remember whether last header callback was field or value\nand apply following logic:\n\n    (on_header_field and on_header_value shortened to on_h_*)\n     ------------------------ ------------ --------------------------------------------\n    | State (prev. callback) | Callback   | Description/action                         |\n     ------------------------ ------------ --------------------------------------------\n    | nothing (first call)   | on_h_field | Allocate new buffer and copy callback data |\n    |                        |            | into it                                    |\n     ------------------------ ------------ --------------------------------------------\n    | value                  | on_h_field | New header started.                        |\n    |                        |            | Copy current name,value buffers to headers |\n    |                        |            | list and allocate new buffer for new name  |\n     ------------------------ ------------ --------------------------------------------\n    | field                  | on_h_field | Previous name continues. Reallocate name   |\n    |                        |            | buffer and append callback data to it      |\n     ------------------------ ------------ --------------------------------------------\n    | field                  | on_h_value | Value for current header started. Allocate |\n    |                        |            | new buffer and copy callback data to it    |\n     ------------------------ ------------ --------------------------------------------\n    | value                  | on_h_value | Value continues. Reallocate value buffer   |\n    |                        |            | and append callback data to it             |\n     ------------------------ ------------ --------------------------------------------\n\n\nSee examples of reading in headers:\n\n* [partial example](http://gist.github.com/155877) in C\n* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C\n* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript\n"
  },
  {
    "path": "proxygen/external/http_parser/http_parser.h",
    "content": "/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n#ifndef http_parser_h\n#define http_parser_h\n\n#define HTTP_PARSER_VERSION_MAJOR 1\n#define HTTP_PARSER_VERSION_MINOR 0\n\n#include <sys/types.h>\n#if defined(_WIN32) && !defined(__MINGW32__) && \\\n  (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)\n#include <BaseTsd.h>  // @manual\n#include <stddef.h>\ntypedef __int8 int8_t;\ntypedef unsigned __int8 uint8_t;\ntypedef __int16 int16_t;\ntypedef unsigned __int16 uint16_t;\ntypedef __int32 int32_t;\ntypedef unsigned __int32 uint32_t;\ntypedef __int64 int64_t;\ntypedef unsigned __int64 uint64_t;\n#else\n#include <stdint.h>\n#endif\n\n#if __cplusplus\nnamespace proxygen {\n#endif /* __cplusplus */\n\n/* Compile with -DHTTP_PARSER_STRICT_URL=1 to parse URLs\n * strictly according to the RFCs\n */\n#ifndef HTTP_PARSER_STRICT_URL\n# define HTTP_PARSER_STRICT_URL 0\n#endif\n\n/* Compile with -DHTTP_PARSER_STRICT_HOSTNAME=1 to parse hostnames\n * strictly according to the RFCs\n */\n#ifndef HTTP_PARSER_STRICT_HOSTNAME\n# define HTTP_PARSER_STRICT_HOSTNAME 0\n#endif\n\n/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to\n * the error reporting facility.\n */\n#ifndef HTTP_PARSER_DEBUG\n# define HTTP_PARSER_DEBUG 0\n#endif\n\n\n/* Maximium header size allowed */\n#define HTTP_MAX_HEADER_SIZE (80*1024)\n\n\ntypedef struct http_parser http_parser;\ntypedef struct http_parser_settings http_parser_settings;\ntypedef struct http_parser_result http_parser_result;\n\n\n/* Callbacks should return non-zero to indicate an error. The parser will\n * then halt execution.\n *\n * The one exception is on_headers_complete. In a HTTP_RESPONSE parser\n * returning '1' from on_headers_complete will tell the parser that it\n * should not expect a body. This is used when receiving a response to a\n * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:\n * chunked' headers that indicate the presence of a body.\n *\n * http_data_cb does not return data chunks. It will be call arbitrarally\n * many times for each string. E.G. you might get 10 callbacks for \"on_path\"\n * each providing just a few characters more data.\n */\ntypedef int (*http_data_cb) (http_parser*, const char *at, size_t length);\ntypedef int (*http_cb) (http_parser*);\n\n\n/* Request Methods */\nenum http_method\n  { HTTP_DELETE    = 0\n  , HTTP_GET\n  , HTTP_HEAD\n  , HTTP_POST\n  , HTTP_PUT\n  /* pathological */\n  , HTTP_CONNECT\n  , HTTP_OPTIONS\n  , HTTP_TRACE\n  /* webdav */\n  , HTTP_COPY\n  , HTTP_LOCK\n  , HTTP_MKCOL\n  , HTTP_MOVE\n  , HTTP_PROPFIND\n  , HTTP_PROPPATCH\n  , HTTP_UNLOCK\n  /* subversion */\n  , HTTP_REPORT\n  , HTTP_MKACTIVITY\n  , HTTP_CHECKOUT\n  , HTTP_MERGE\n  /* upnp */\n  , HTTP_MSEARCH\n  , HTTP_NOTIFY\n  , HTTP_SUBSCRIBE\n  , HTTP_UNSUBSCRIBE\n  /* RFC-5789 */\n  , HTTP_PATCH\n  };\n\n\nenum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };\n\n\n/* Flag values for http_parser.flags field */\nenum flags\n  { F_CHUNKED               = 1 << 0\n  , F_TRAILING              = 1 << 3\n  , F_UPGRADE               = 1 << 4\n  , F_SKIPBODY              = 1 << 5\n  };\n\n\n/* Map for errno-related constants\n *\n * The provided argument should be a macro that takes 2 arguments.\n */\n#define HTTP_ERRNO_MAP(XX)                                           \\\n  /* No error */                                                     \\\n  XX(OK, \"success\")                                                  \\\n                                                                     \\\n  /* Callback-related errors */                                      \\\n  XX(CB_message_begin, \"the on_message_begin callback failed\")       \\\n  XX(CB_path, \"the on_path callback failed\")                         \\\n  XX(CB_query_string, \"the on_query_string callback failed\")         \\\n  XX(CB_url, \"the on_url callback failed\")                           \\\n  XX(CB_fragment, \"the on_fragment callback failed\")                 \\\n  XX(CB_header_field, \"the on_header_field callback failed\")         \\\n  XX(CB_header_value, \"the on_header_value callback failed\")         \\\n  XX(CB_headers_complete, \"the on_headers_complete callback failed\") \\\n  XX(CB_body, \"the on_body callback failed\")                         \\\n  XX(CB_message_complete, \"the on_message_complete callback failed\") \\\n  XX(CB_reason, \"the on_reason callback failed\")                     \\\n  XX(CB_chunk_header, \"the on_chunk_header callback failed\")         \\\n  XX(CB_chunk_complete, \"the on_chunk_complete callback failed\")     \\\n                                                                     \\\n  /* Parsing-related errors */                                       \\\n  XX(INVALID_EOF_STATE, \"stream ended at an unexpected time\")        \\\n  XX(HEADER_OVERFLOW,                                                \\\n     \"too many header bytes seen; overflow detected\")                \\\n  XX(CLOSED_CONNECTION,                                              \\\n     \"data received after completed connection: close message\")      \\\n  XX(INVALID_VERSION, \"invalid HTTP version\")                        \\\n  XX(INVALID_STATUS, \"invalid HTTP status code\")                     \\\n  XX(INVALID_METHOD, \"invalid HTTP method\")                          \\\n  XX(INVALID_URL, \"invalid URL\")                                     \\\n  XX(INVALID_HOST, \"invalid host\")                                   \\\n  XX(INVALID_PORT, \"invalid port\")                                   \\\n  XX(INVALID_PATH, \"invalid path\")                                   \\\n  XX(INVALID_QUERY_STRING, \"invalid query string\")                   \\\n  XX(INVALID_FRAGMENT, \"invalid fragment\")                           \\\n  XX(LF_EXPECTED, \"LF character expected\")                           \\\n  XX(INVALID_HEADER_TOKEN, \"invalid character in header\")            \\\n  XX(INVALID_CONTENT_LENGTH,                                         \\\n     \"invalid character in content-length header\")                   \\\n  XX(HUGE_CONTENT_LENGTH,                                            \\\n     \"content-length header too large\")                              \\\n  XX(INVALID_CHUNK_SIZE,                                             \\\n     \"invalid character in chunk size header\")                       \\\n  XX(HUGE_CHUNK_SIZE,                                                \\\n     \"chunk header size too large\")                                  \\\n  XX(INVALID_TRANSFER_ENCODING,                                      \\\n     \"invalid character in transfer-encoding header\")                \\\n  XX(INVALID_UPGRADE,                                                \\\n     \"invalid character in upgrade header\")                          \\\n  XX(INVALID_CONSTANT, \"invalid constant string\")                    \\\n  XX(INVALID_INTERNAL_STATE, \"encountered unexpected internal state\")\\\n  XX(STRICT, \"strict mode assertion failed\")                         \\\n  XX(PAUSED, \"parser is paused\")                                     \\\n  XX(UNKNOWN, \"an unknown error occurred\")\n\n\n/* Define HPE_* values for each errno value above */\n#define HTTP_ERRNO_GEN(n, s) HPE_##n,\nenum http_errno {\n  HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)\n};\n#undef HTTP_ERRNO_GEN\n\n\n/* Get an http_errno value from an http_parser */\n#define HTTP_PARSER_ERRNO(p)            ((enum http_errno) (p)->http_errno)\n\n/* Get the line number that generated the current error */\n#if HTTP_PARSER_DEBUG\n#define HTTP_PARSER_ERRNO_LINE(p)       ((p)->error_lineno)\n#else\n#define HTTP_PARSER_ERRNO_LINE(p)       0\n#endif\n\n\nstruct http_parser {\n  /** PRIVATE **/\n  unsigned char type : 2;     /* enum http_parser_type */\n  unsigned char flags : 6;    /* F_* values from 'flags' enum; semi-public */\n  unsigned char state;        /* enum state from http_parser.c */\n  unsigned char header_state; /* enum header_state from http_parser.c */\n  unsigned char index;        /* index into current matcher */\n\n  uint32_t nread;          /* # bytes read in various scenarios */\n  int64_t content_length;  /* # bytes in body (0 if no Content-Length header) */\n\n  /** READ-ONLY **/\n  unsigned short http_major;\n  unsigned short http_minor;\n  unsigned short status_code; /* responses only */\n  unsigned char method;       /* requests only */\n  unsigned char http_errno : 7;\n\n  /* 1 = Upgrade header was present and the parser has exited because of that.\n   * 0 = No upgrade header present.\n   * Should be checked when http_parser_execute() returns in addition to\n   * error checking.\n   */\n  char upgrade : 1;\n\n#if HTTP_PARSER_DEBUG\n  uint32_t error_lineno;\n#endif\n\n  /** PUBLIC **/\n  void *data; /* A pointer to get hook to the \"connection\" or \"socket\" object */\n};\n\n\nstruct http_parser_settings {\n  http_cb      on_message_begin;\n  http_data_cb on_url;\n  http_data_cb on_header_field;\n  http_data_cb on_header_value;\n  http_data_cb on_headers_complete;\n  http_data_cb on_body;\n  http_cb      on_message_complete;\n  http_data_cb on_reason;\n  /* When on_chunk_header is called, the current chunk length is stored\n   * in parser->content_length.\n   */\n  http_cb      on_chunk_header;\n  http_cb      on_chunk_complete;\n};\n\n\nenum http_parser_url_fields\n  { UF_SCHEMA           = 0\n  , UF_HOST             = 1\n  , UF_PORT             = 2\n  , UF_PATH             = 3\n  , UF_QUERY            = 4\n  , UF_FRAGMENT         = 5\n  , UF_USERINFO         = 6\n  , UF_MAX              = 7\n};\n\n\n/* Result structure for http_parser_parse_url().\n *\n * Callers should index into field_data[] with UF_* values iff field_set\n * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and\n * because we probably have padding left over), we convert any port to\n * a uint16_t.\n */\nstruct http_parser_url {\n  uint16_t field_set;           /* Bitmask of (1 << UF_*) values */\n  uint16_t port;                /* Converted UF_PORT string */\n\n  struct {\n    uint16_t off;               /* Offset into buffer in which field starts */\n    uint16_t len;               /* Length of run in buffer */\n  } field_data[UF_MAX];\n};\n\n\nvoid http_parser_init(http_parser *parser, enum http_parser_type type);\n\n\nsize_t http_parser_execute(http_parser *parser,\n                           const http_parser_settings *settings,\n                           const char *data,\n                           size_t len);\n\n  /* Begin Facebook */\nenum http_parser_options\n{\n  F_HTTP_PARSER_OPTIONS_URL_STRICT           = (1 << 0)\n};\n\nsize_t http_parser_execute_options(http_parser *parser,\n                                   const http_parser_settings *settings,\n                                   uint8_t options,\n                                   const char *data,\n                                   size_t len);\n/* End Facebook */\n\n/* Returns a string version of the HTTP method. */\nconst char *http_method_str(enum http_method m);\n\n/* Return a string name of the given error */\nconst char *http_errno_name(enum http_errno err);\n\n/* Return a string description of the given error */\nconst char *http_errno_description(enum http_errno err);\n\n/* Parse a URL; return nonzero on failure */\nint http_parser_parse_url(const char *buf, size_t buflen,\n                          int is_connect,\n                          struct http_parser_url *u);\n\n/* Begin Facebook */\nenum http_parser_parse_url_options\n{\n  F_PARSE_URL_OPTIONS_URL_STRICT           = (1 << 0)\n};\n\nint http_parser_parse_url_options(\n    const char *buf, size_t buflen,\n    int is_connect,\n    struct http_parser_url *u,\n    uint8_t options);\n/* End Facebook */\n\n/* Pause or un-pause the parser; a nonzero value pauses */\nvoid http_parser_pause(http_parser *parser, int paused);\n\n#if __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif\n"
  },
  {
    "path": "proxygen/external/http_parser/http_parser_cpp.cpp",
    "content": "/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev\n *\n * Additional changes are licensed under the same terms as NGINX and\n * copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n#include \"proxygen/external/http_parser/http_parser.h\"\n\n#include <assert.h>\n#include <stddef.h>\n#include <stdlib.h>\n\n#if __cplusplus\n#include <limits>\n\nnamespace proxygen {\n\n#ifndef INT64_MAX\n# define INT64_MAX std::numeric_limits<int64_t>::max()\n#endif\n\n#else\n#include <stdbool.h>\n#define nullptr NULL\n\n#endif /* __cplusplus */\n\n#ifndef MIN\n# define MIN(a,b) ((a) < (b) ? (a) : (b))\n#endif\n\n\n#if HTTP_PARSER_DEBUG\n#define SET_ERRNO(e)                                                 \\\ndo {                                                                 \\\n  parser->http_errno = (e);                                          \\\n  parser->error_lineno = __LINE__;                                   \\\n} while (0)\n#else\n#define SET_ERRNO(e)                                                 \\\ndo {                                                                 \\\n  parser->http_errno = (e);                                          \\\n} while(0)\n#endif\n\n#define RETURN(r)                                                    \\\ndo {                                                                 \\\n  parser->state = state;                                             \\\n  return (r);                                                        \\\n} while(0)\n\n/* Run the notify callback FOR, returning ER if it fails */\n#define _CALLBACK_NOTIFY(FOR, ER)                                    \\\ndo {                                                                 \\\n  parser->state = state;                                             \\\n  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \\\n                                                                     \\\n  if (0 != settings->on_##FOR(parser)) {                             \\\n    SET_ERRNO(HPE_CB_##FOR);                                         \\\n  }                                                                  \\\n                                                                     \\\n  /* We either errored above or got paused; get out */               \\\n  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {                         \\\n    return (ER);                                                     \\\n  }                                                                  \\\n} while (0)\n\n/* Run the notify callback FOR and consume the current byte */\n#define CALLBACK_NOTIFY(FOR)            _CALLBACK_NOTIFY(FOR, p - data + 1)\n\n/* Run the notify callback FOR and don't consume the current byte */\n#define CALLBACK_NOTIFY_NOADVANCE(FOR)  _CALLBACK_NOTIFY(FOR, p - data)\n\n/* Run data callback FOR with LEN bytes, returning ER if it fails */\n#define _CALLBACK_DATA(FOR, LEN, ER)                                 \\\ndo {                                                                 \\\n  parser->state = state;                                             \\\n  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \\\n                                                                     \\\n  if (FOR##_mark) {                                                  \\\n    if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) {        \\\n      SET_ERRNO(HPE_CB_##FOR);                                       \\\n    }                                                                \\\n                                                                     \\\n    /* We either errored above or got paused; get out */             \\\n    if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {                       \\\n      return (ER);                                                   \\\n    }                                                                \\\n    FOR##_mark = nullptr;                                               \\\n  }                                                                  \\\n} while (0)\n\n/* Run the data callback FOR and consume the current byte */\n#define CALLBACK_DATA(FOR)                                           \\\n    _CALLBACK_DATA(FOR, p - FOR##_mark, p - data + 1)\n\n/* Run the data callback FOR and don't consume the current byte */\n#define CALLBACK_DATA_NOADVANCE(FOR)                                 \\\n    _CALLBACK_DATA(FOR, p - FOR##_mark, p - data)\n\n/* We just saw a synthetic space */\n#define CALLBACK_SPACE(FOR)                                          \\\ndo {                                                                 \\\n  parser->state = state;                                             \\\n  if (0 != settings->on_##FOR(parser, SPACE, 1)) {                   \\\n    SET_ERRNO(HPE_CB_##FOR);                                         \\\n    return (p - data);                                               \\\n  }                                                                  \\\n                                                                     \\\n  /* We either errored above or got paused; get out */               \\\n  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {                         \\\n    return (p - data);                                               \\\n  }                                                                  \\\n} while (0)\n\n/* Set the mark FOR; non-destructive if mark is already set */\n#define MARK(FOR)                                                    \\\ndo {                                                                 \\\n  if (!FOR##_mark) {                                                 \\\n    FOR##_mark = p;                                                  \\\n  }                                                                  \\\n} while (0)\n\n\n#define CONTENT_LENGTH \"content-length\"\n#define TRANSFER_ENCODING \"transfer-encoding\"\n#define UPGRADE \"upgrade\"\n#define CHUNKED \"chunked\"\n#define SPACE \" \"\n\n\nstatic const char *method_strings[] =\n  { \"DELETE\"\n  , \"GET\"\n  , \"HEAD\"\n  , \"POST\"\n  , \"PUT\"\n  , \"CONNECT\"\n  , \"OPTIONS\"\n  , \"TRACE\"\n  , \"COPY\"\n  , \"LOCK\"\n  , \"MKCOL\"\n  , \"MOVE\"\n  , \"PROPFIND\"\n  , \"PROPPATCH\"\n  , \"UNLOCK\"\n  , \"REPORT\"\n  , \"MKACTIVITY\"\n  , \"CHECKOUT\"\n  , \"MERGE\"\n  , \"M-SEARCH\"\n  , \"NOTIFY\"\n  , \"SUBSCRIBE\"\n  , \"UNSUBSCRIBE\"\n  , \"PATCH\"\n  };\n\n\n/* Tokens as defined by rfc 2616. Also lowercases them.\n *        token       = 1*<any CHAR except CTLs or separators>\n *     separators     = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n *                    | \",\" | \";\" | \":\" | \"\\\" | <\">\n *                    | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n *                    | \"{\" | \"}\" | SP | HT\n */\nstatic const char tokens[256] = {\n/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  32 sp    33  !    34  \"    35  #    36  $    37  %    38  &    39  '  */\n        0,      '!',      0,     '#',     '$',     '%',     '&',    '\\'',\n/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */\n        0,       0,      '*',     '+',      0,      '-',     '.',      0,\n/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */\n       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',\n/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */\n       '8',     '9',      0,       0,       0,       0,       0,       0,\n/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */\n        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/*  88  X    89  Y    90  Z    91  [    92  \\    93  ]    94  ^    95  _  */\n       'x',     'y',     'z',      0,       0,       0,      '^',     '_',\n/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */\n       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */\n       'x',     'y',     'z',      0,      '|',      0,     '~',       0 };\n\n\nstatic const int8_t unhex[256] =\n  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1\n  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  };\n\n#if HTTP_PARSER_STRICT_URL\n/* The value here is \"0b10000000\" so when shifted by\n * F_PARSE_URL_OPTIONS_URL_STRICT/F_HTTP_PARSER_OPTIONS_URL_STRICT it remains\n * > 0 for non-strict (0) and becomes 0 for strict (1 << 0 = 1)\n */\nstatic_assert(F_PARSE_URL_OPTIONS_URL_STRICT == 1, \"must be 1!\");\nstatic_assert(F_HTTP_PARSER_OPTIONS_URL_STRICT == 1, \"must be 1!\");\n# define T(v) 0x80\n# define IS_PARSER_STRICT(options)                                             \\\n  ((options) & F_HTTP_PARSER_OPTIONS_URL_STRICT)\n#else\n# define T(v) v\n# define IS_PARSER_STRICT(options) (0)\n#endif\n\nstatic const uint8_t normal_url_char[256] = {\n/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */\n        0,     T(1),      0,       0,     T(1),      0,       0,       0,\n/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  32 sp    33  !    34  \"    35  #    36  $    37  %    38  &    39  '  */\n        0,       1,       1,       0,       1,       1,       1,       1,\n/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */\n        1,       1,       1,       1,       1,       1,       1,       0,\n/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  88  X    89  Y    90  Z    91  [    92  \\    93  ]    94  ^    95  _  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */\n        1,       1,       1,       1,       1,       1,       1,       1,\n/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */\n        1,       1,       1,       1,       1,       1,       1,       0, };\n\n#undef T\n\nenum state\n  { s_dead = 1 /* important that this is > 0 */\n  , s_pre_start_req_or_res\n  , s_start_req_or_res\n  , s_res_or_resp_H\n\n  , s_pre_start_res\n  , s_start_res\n  , s_res_H\n  , s_res_HT\n  , s_res_HTT\n  , s_res_HTTP\n  , s_res_first_http_major\n  , s_res_http_major\n  , s_res_first_http_minor\n  , s_res_http_minor\n  , s_res_first_status_code\n  , s_res_status_code\n  , s_res_status_start\n  , s_res_status\n  , s_res_line_almost_done\n\n  , s_pre_start_req\n  , s_start_req\n  , s_req_method\n  , s_req_spaces_before_url\n  , s_req_schema\n  , s_req_schema_slash\n  , s_req_schema_slash_slash\n  , s_req_server_start\n  , s_req_server\n  , s_req_server_with_at\n  , s_req_host_start\n  , s_req_host\n  , s_req_host_ipv6\n  , s_req_host_done\n  , s_req_port\n  , s_req_path\n  , s_req_query_string_start\n  , s_req_query_string\n  , s_req_fragment_start\n  , s_req_fragment\n  , s_req_http_start\n  , s_req_http_H\n  , s_req_http_HT\n  , s_req_http_HTT\n  , s_req_http_HTTP\n  , s_req_first_http_major\n  , s_req_http_major\n  , s_req_first_http_minor\n  , s_req_http_minor\n  , s_req_line_almost_done\n\n  , s_header_field_start\n  , s_header_field\n  , s_header_value_start\n  , s_header_value\n  , s_header_value_lws\n\n  , s_header_almost_done\n\n  , s_chunk_size_start\n  , s_chunk_size\n  , s_chunk_parameters\n  , s_chunk_size_almost_done\n\n  , s_headers_almost_done\n  , s_headers_done\n\n  /* Important: 's_headers_done' must be the last 'header' state. All\n   * states beyond this must be 'body' states. It is used for overflow\n   * checking. See the PARSING_HEADER() macro.\n   */\n\n  , s_chunk_data\n  , s_chunk_data_almost_done\n  , s_chunk_data_done\n\n  , s_body_identity\n  , s_body_identity_eof\n\n  , s_message_done\n  };\n\n\n#define PARSING_HEADER(state) (state <= s_headers_done)\n\n\nenum header_states\n  { h_general = 0\n\n  , h_general_and_quote\n  , h_general_and_quote_and_escape\n\n  , h_matching_content_length\n  , h_matching_transfer_encoding\n  , h_matching_upgrade\n\n  , h_content_length\n  , h_transfer_encoding\n  , h_upgrade\n\n  , h_matching_transfer_encoding_chunked\n\n  , h_transfer_encoding_chunked\n  };\n\nenum http_host_state\n  {\n    s_http_host_dead = 1\n  , s_http_userinfo_start\n  , s_http_userinfo\n  , s_http_host_start\n  , s_http_host_v6_start\n  , s_http_host\n  , s_http_host_v6\n  , s_http_host_v6_end\n  , s_http_host_port_start\n  , s_http_host_port\n};\n\n\n/* Macros for character classes; depends on strict-mode  */\n#define CR                  '\\r'\n#define LF                  '\\n'\n#define QT                  '\"'\n#define BS                  '\\\\'\n#define LOWER(c)            (unsigned char)(c | 0x20)\n#define TOKEN(c)            (tokens[(unsigned char)c])\n#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')\n#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')\n#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))\n#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))\n#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \\\n  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\\'' || (c) == '(' || \\\n  (c) == ')')\n#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \\\n  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \\\n  (c) == '$' || (c) == ',')\n\n#if HTTP_PARSER_STRICT_URL\n#define IS_URL_CHAR(c, strict)                                                 \\\n  (((normal_url_char[(unsigned char) (c)] << (strict)) != 0) ||                \\\n   (((c) & 0x80) && !(strict)))\n#else\n#define IS_URL_CHAR(c, strict)                                                 \\\n  (normal_url_char[(unsigned char) (c)] || ((c) & 0x80))\n#endif\n\n#if HTTP_PARSER_STRICT_HOSTNAME\n#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')\n#else\n#define IS_HOST_CHAR(c)                                                        \\\n  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')\n#endif\n\n\n/**\n * Verify that a char is a valid visible (printable) US-ASCII\n * character or %x80-FF\n **/\n#define IS_HEADER_CHAR(ch)                                                     \\\n  (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))\n\n#define start_state (parser->type == HTTP_REQUEST ? s_pre_start_req : s_pre_start_res)\n\n#define NOOP_CHECK(cond)\n#define STRICT_CHECK(cond)                                          \\\ndo {                                                                 \\\n  if (cond) {                                                        \\\n    SET_ERRNO(HPE_STRICT);                                           \\\n    goto error;                                                      \\\n  }                                                                  \\\n} while (0)\n#define NEW_MESSAGE() start_state\n\n/* Map errno values to strings for human-readable output */\n#define HTTP_STRERROR_GEN(n, s) { \"HPE_\" #n, s },\nstatic struct {\n  const char *name;\n  const char *description;\n} http_strerror_tab[] = {\n  HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)\n};\n#undef HTTP_STRERROR_GEN\n\n/* Our URL parser.\n *\n * This is designed to be shared by http_parser_execute() for URL validation,\n * hence it has a state transition + byte-for-byte interface. In addition, it\n * is meant to be embedded in http_parser_parse_url(), which does the dirty\n * work of turning state transitions URL components for its API.\n *\n * This function should only be invoked with non-space characters. It is\n * assumed that the caller cares about (and can detect) the transition between\n * URL and non-URL states by looking for these.\n */\nstatic enum state\nparse_url_char(enum state s, const char ch, int strict_flag)\n{\n  if (ch == ' ' || ch == '\\r' || ch == '\\n') {\n    return s_dead;\n  }\n\n#if HTTP_PARSER_STRICT_URL\n  if (ch == '\\t' || ch == '\\f') {\n    return s_dead;\n  }\n#endif\n\n  switch (s) {\n    case s_req_spaces_before_url:\n      /* Proxied requests are followed by scheme of an absolute URI (alpha).\n       * All methods except CONNECT are followed by '/' or '*'.\n       */\n\n      if (ch == '/' || ch == '*') {\n        return s_req_path;\n      }\n\n      if (IS_ALPHA(ch)) {\n        return s_req_schema;\n      }\n\n      break;\n\n    case s_req_schema:\n      if (IS_ALPHA(ch)) {\n        return s;\n      }\n\n      if (ch == ':') {\n        return s_req_schema_slash;\n      }\n\n      break;\n\n    case s_req_schema_slash:\n      if (ch == '/') {\n        return s_req_schema_slash_slash;\n      }\n\n      break;\n\n    case s_req_schema_slash_slash:\n      if (ch == '/') {\n        return s_req_server_start;\n      }\n\n      break;\n\n    case s_req_server_with_at:\n      if (ch == '@') {\n        return s_dead;\n      }\n\n#if __cplusplus\n      [[fallthrough]];\n#else /* __cplusplus */\n    __attribute__((fallthrough));\n#endif /* __cplusplus */\n    case s_req_server_start:\n    case s_req_server:\n      if (ch == '/') {\n        return s_req_path;\n      }\n\n      if (ch == '?') {\n        return s_req_query_string_start;\n      }\n\n      if (ch == '@') {\n        return s_req_server_with_at;\n      }\n\n      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {\n        return s_req_server;\n      }\n\n      break;\n\n    case s_req_path:\n      if (IS_URL_CHAR(ch, strict_flag)) {\n        return s;\n      }\n\n      switch (ch) {\n        case '?':\n          return s_req_query_string_start;\n\n        case '#':\n          return s_req_fragment_start;\n      }\n\n      break;\n\n    case s_req_query_string_start:\n    case s_req_query_string:\n      if (IS_URL_CHAR(ch, strict_flag)) {\n        return s_req_query_string;\n      }\n\n      switch (ch) {\n        case '?':\n          /* allow extra '?' in query string */\n          return s_req_query_string;\n\n        case '#':\n          return s_req_fragment_start;\n      }\n\n      break;\n\n    case s_req_fragment_start:\n      if (IS_URL_CHAR(ch, strict_flag)) {\n        return s_req_fragment;\n      }\n\n      switch (ch) {\n        case '?':\n          return s_req_fragment;\n\n        case '#':\n          return s;\n      }\n\n      break;\n\n    case s_req_fragment:\n      if (IS_URL_CHAR(ch, strict_flag)) {\n        return s;\n      }\n\n      switch (ch) {\n        case '?':\n        case '#':\n          return s;\n      }\n\n      break;\n\n    default:\n      break;\n  }\n\n  /* We should never fall out of the switch above unless there's an error */\n  return s_dead;\n}\n\nsize_t http_parser_execute (http_parser *parser,\n                            const http_parser_settings *settings,\n                            const char *data,\n                            size_t len)\n{\n  return http_parser_execute_options(parser, settings, 0, data, len);\n}\n\nsize_t http_parser_execute_options (http_parser *parser,\n                                    const http_parser_settings *settings,\n                                    uint8_t options,\n                                    const char *data,\n                                    size_t len)\n{\n  char c, ch;\n  int8_t unhex_val;\n  const char *p = data;\n\n  /* Optimization: within the parsing loop below, we refer to this\n   * local copy of the state rather than parser->state.  The compiler\n   * can't be sure whether parser->state will change during a callback,\n   * so it generates a lot of memory loads and stores to keep a register\n   * copy of the state in sync with the memory copy.  We know, however,\n   * that the callbacks aren't allowed to change the parser state, so\n   * the parsing loop works with this local variable and only copies\n   * the value back to parser->loop before returning or invoking a\n   * callback.\n   */\n  unsigned char state = parser->state;\n  const unsigned int lenient = 0;\n\n  /* We're in an error state. Don't bother doing anything. */\n  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {\n    RETURN(0);\n  }\n\n  if (len == 0) {\n    switch (state) {\n      case s_body_identity_eof:\n        /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if\n         * we got paused.\n         */\n        CALLBACK_NOTIFY_NOADVANCE(message_complete);\n        RETURN(0);\n\n      case s_pre_start_req_or_res:\n      case s_pre_start_res:\n      case s_pre_start_req:\n        RETURN(0);\n\n      default:\n        SET_ERRNO(HPE_INVALID_EOF_STATE);\n        RETURN(1);\n    }\n  }\n\n  /* technically we could combine all of these (except for url_mark) into one\n     variable, saving stack space, but it seems more clear to have them\n     separated. */\n  const char *header_field_mark = nullptr;\n  const char *header_value_mark = nullptr;\n  const char *url_mark = nullptr;\n  const char *reason_mark = nullptr;\n  const char *body_mark = nullptr;\n\n  if (state == s_header_field)\n    header_field_mark = data;\n  if (state == s_header_value)\n    header_value_mark = data;\n  if (state == s_req_path ||\n      state == s_req_schema ||\n      state == s_req_schema_slash ||\n      state == s_req_schema_slash_slash ||\n      state == s_req_port ||\n      state == s_req_query_string_start ||\n      state == s_req_query_string ||\n      state == s_req_host_start ||\n      state == s_req_host ||\n      state == s_req_host_ipv6 ||\n      state == s_req_host_done ||\n      state == s_req_fragment_start ||\n      state == s_req_fragment)\n    url_mark = data;\n  if (state == s_res_status)\n    reason_mark = data;\n\n  /* Used only for overflow checking. If the parser is in a parsing-headers\n   * state, then its value is equal to max(data, the beginning of the current\n   * message or chunk). If the parser is in a not-parsing-headers state, then\n   * its value is irrelevant.\n   */\n  const char* data_or_header_data_start = data;\n\n  for (p = data; p != data + len; p++) {\n    ch = *p;\n\n    reexecute_byte:\n    switch (state) {\n\n      case s_pre_start_req_or_res:\n        if (ch == CR || ch == LF)\n          break;\n        state = s_start_req_or_res;\n        CALLBACK_NOTIFY_NOADVANCE(message_begin);\n        goto reexecute_byte;\n\n      case s_start_req_or_res:\n      {\n        parser->flags = 0;\n        parser->content_length = -1;\n\n        if (ch == 'H') {\n          state = s_res_or_resp_H;\n        } else {\n          parser->type = HTTP_REQUEST;\n          state = s_start_req;\n          goto reexecute_byte;\n        }\n\n        break;\n      }\n\n      case s_res_or_resp_H:\n        if (ch == 'T') {\n          parser->type = HTTP_RESPONSE;\n          state = s_res_HT;\n        } else {\n          if (ch != 'E') {\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n          }\n\n          parser->type = HTTP_REQUEST;\n          parser->method = HTTP_HEAD;\n          parser->index = 2;\n          state = s_req_method;\n        }\n        break;\n\n      case s_pre_start_res:\n        if (ch == CR || ch == LF)\n          break;\n        state = s_start_res;\n        CALLBACK_NOTIFY_NOADVANCE(message_begin);\n        goto reexecute_byte;\n\n      case s_start_res:\n      {\n        parser->flags = 0;\n        parser->content_length = -1;\n\n        switch (ch) {\n          case 'H':\n            state = s_res_H;\n            break;\n\n          default:\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n        }\n\n        break;\n      }\n\n      case s_res_H:\n        NOOP_CHECK(ch != 'T');\n        state = s_res_HT;\n        break;\n\n      case s_res_HT:\n        NOOP_CHECK(ch != 'T');\n        state = s_res_HTT;\n        break;\n\n      case s_res_HTT:\n        NOOP_CHECK(ch != 'P');\n        state = s_res_HTTP;\n        break;\n\n      case s_res_HTTP:\n        NOOP_CHECK(ch != '/');\n        state = s_res_first_http_major;\n        break;\n\n      case s_res_first_http_major:\n        if (ch < '0' || ch > '9') {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major = ch - '0';\n        state = s_res_http_major;\n        break;\n\n      /* major HTTP version or dot */\n      case s_res_http_major:\n      {\n        if (ch == '.') {\n          state = s_res_first_http_minor;\n          break;\n        }\n\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major *= 10;\n        parser->http_major += ch - '0';\n\n        if (parser->http_major > 999) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* first digit of minor HTTP version */\n      case s_res_first_http_minor:\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor = ch - '0';\n        state = s_res_http_minor;\n        break;\n\n      /* minor HTTP version or end of request line */\n      case s_res_http_minor:\n      {\n        if (ch == ' ') {\n          state = s_res_first_status_code;\n          break;\n        }\n\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor *= 10;\n        parser->http_minor += ch - '0';\n\n        if (parser->http_minor > 999) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      case s_res_first_status_code:\n      {\n        if (!IS_NUM(ch)) {\n          if (ch == ' ') {\n            break;\n          }\n\n          SET_ERRNO(HPE_INVALID_STATUS);\n          goto error;\n        }\n        parser->status_code = ch - '0';\n        state = s_res_status_code;\n        break;\n      }\n\n      case s_res_status_code:\n      {\n        if (!IS_NUM(ch)) {\n          switch (ch) {\n            case ' ':\n              state = s_res_status;\n              break;\n            case CR:\n              state = s_res_line_almost_done;\n              break;\n            case LF:\n              state = s_header_field_start;\n              break;\n            default:\n              SET_ERRNO(HPE_INVALID_STATUS);\n              goto error;\n          }\n          break;\n        }\n\n        parser->status_code *= 10;\n        parser->status_code += ch - '0';\n\n        if (parser->status_code > 999) {\n          SET_ERRNO(HPE_INVALID_STATUS);\n          goto error;\n        }\n\n        break;\n      }\n\n      case s_res_status:\n        /* the human readable status. e.g. \"NOT FOUND\" */\n        MARK(reason);\n        if (ch == CR) {\n          state = s_res_line_almost_done;\n          CALLBACK_DATA(reason);\n          break;\n        }\n\n        if (ch == LF) {\n          state = s_header_field_start;\n          CALLBACK_DATA(reason);\n          break;\n        }\n        break;\n\n      case s_res_line_almost_done:\n        NOOP_CHECK(ch != LF);\n        state = s_header_field_start;\n        break;\n\n      case s_pre_start_req:\n        if (ch == CR || ch == LF) {\n          break;\n        }\n        state = s_start_req;\n        CALLBACK_NOTIFY_NOADVANCE(message_begin);\n        goto reexecute_byte;\n\n      case s_start_req:\n      {\n        parser->flags = 0;\n        parser->content_length = -1;\n\n        if (!IS_ALPHA(ch)) {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        parser->method = (enum http_method) 0;\n        parser->index = 1;\n        switch (ch) {\n          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;\n          case 'D': parser->method = HTTP_DELETE; break;\n          case 'G': parser->method = HTTP_GET; break;\n          case 'H': parser->method = HTTP_HEAD; break;\n          case 'L': parser->method = HTTP_LOCK; break;\n          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;\n          case 'N': parser->method = HTTP_NOTIFY; break;\n          case 'O': parser->method = HTTP_OPTIONS; break;\n          case 'P': parser->method = HTTP_POST;\n            /* or PROPFIND or PROPPATCH or PUT or PATCH */\n            break;\n          case 'R': parser->method = HTTP_REPORT; break;\n          case 'S': parser->method = HTTP_SUBSCRIBE; break;\n          case 'T': parser->method = HTTP_TRACE; break;\n          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;\n          default:\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n        }\n        state = s_req_method;\n\n        break;\n      }\n\n      case s_req_method:\n      {\n        if (ch == '\\0') {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        const char *matcher = method_strings[parser->method];\n        if (ch == ' ' && matcher[parser->index] == '\\0') {\n          state = s_req_spaces_before_url;\n        } else if (ch == matcher[parser->index]) {\n          /* nada */\n        } else if (parser->method == HTTP_CONNECT) {\n          if (parser->index == 1 && ch == 'H') {\n            parser->method = HTTP_CHECKOUT;\n          } else if (parser->index == 2  && ch == 'P') {\n            parser->method = HTTP_COPY;\n          } else {\n            goto error;\n          }\n        } else if (parser->method == HTTP_MKCOL) {\n          if (parser->index == 1 && ch == 'O') {\n            parser->method = HTTP_MOVE;\n          } else if (parser->index == 1 && ch == 'E') {\n            parser->method = HTTP_MERGE;\n          } else if (parser->index == 1 && ch == '-') {\n            parser->method = HTTP_MSEARCH;\n          } else if (parser->index == 2 && ch == 'A') {\n            parser->method = HTTP_MKACTIVITY;\n          } else {\n            goto error;\n          }\n        } else if (parser->index == 1 && parser->method == HTTP_POST) {\n          if (ch == 'R') {\n            parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */\n          } else if (ch == 'U') {\n            parser->method = HTTP_PUT;\n          } else if (ch == 'A') {\n            parser->method = HTTP_PATCH;\n          } else {\n            goto error;\n          }\n        } else if (parser->index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {\n          parser->method = HTTP_UNSUBSCRIBE;\n        } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {\n          parser->method = HTTP_PROPPATCH;\n        } else {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        ++parser->index;\n        break;\n      }\n\n      case s_req_spaces_before_url:\n      {\n        if (ch == ' ') break;\n\n        // CONNECT requests must be followed by a <host>:<port>\n        if (parser->method == HTTP_CONNECT) {\n          MARK(url);\n          state = s_req_host_start;\n          goto reexecute_byte;\n        }\n\n        if (ch == '/' || ch == '*') {\n          MARK(url);\n          state = s_req_path;\n          break;\n        }\n\n        /* Proxied requests are followed by scheme of an absolute URI (alpha).\n         * All other methods are followed by '/' or '*' (handled above).\n         */\n        if (IS_ALPHA(ch)) {\n          MARK(url);\n          state = s_req_schema;\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_URL);\n        goto error;\n      }\n\n      case s_req_schema:\n      {\n        if (IS_ALPHA(ch)) break;\n\n        if (ch == ':') {\n          state = s_req_schema_slash;\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_URL);\n        goto error;\n      }\n\n      case s_req_schema_slash:\n        NOOP_CHECK(ch != '/');\n        state = s_req_schema_slash_slash;\n        break;\n\n      case s_req_schema_slash_slash:\n        NOOP_CHECK(ch != '/');\n        state = s_req_host_start;\n        break;\n\n      case s_req_host_start:\n        if (ch == '[') {\n          state = s_req_host_ipv6;\n          break;\n        } else if (IS_ALPHANUM(ch)) {\n          state = s_req_host;\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_HOST);\n        goto error;\n\n      case s_req_host:\n        if (IS_HOST_CHAR(ch)) break;\n        state = s_req_host_done;\n        goto reexecute_byte;\n\n      case s_req_host_ipv6:\n        if (IS_HEX(ch) || ch == ':') break;\n        if (ch == ']') {\n          state = s_req_host_done;\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_HOST);\n        goto error;\n\n      case s_req_host_done:\n        switch (ch) {\n          case ':':\n            state = s_req_port;\n            break;\n          case '/':\n            state = s_req_path;\n            break;\n          case ' ':\n            /* The request line looks like:\n             *   \"GET http://foo.bar.com HTTP/1.1\"\n             * That is, there is no path.\n             */\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case '?':\n            state = s_req_query_string_start;\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_HOST);\n            goto error;\n        }\n\n        break;\n\n      case s_req_port:\n      {\n        if (IS_NUM(ch)) break;\n        switch (ch) {\n          case '/':\n            state = s_req_path;\n            break;\n          case ' ':\n            /* The request line looks like:\n             *   \"GET http://foo.bar.com:1234 HTTP/1.1\"\n             * That is, there is no path.\n             */\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case '?':\n            state = s_req_query_string_start;\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_PORT);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_path:\n      {\n        if (IS_URL_CHAR(ch, IS_PARSER_STRICT(options))) break;\n\n        switch (ch) {\n          case ' ':\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            break;\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            goto reexecute_byte;\n          case '?':\n            state = s_req_query_string_start;\n            break;\n          case '#':\n            state = s_req_fragment_start;\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_PATH);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_query_string_start:\n      {\n        if (IS_URL_CHAR(ch, IS_PARSER_STRICT(options))) {\n          state = s_req_query_string;\n          break;\n        }\n\n        switch (ch) {\n          case '?':\n            break; /* XXX ignore extra '?' ... is this right? */\n          case ' ':\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            break;\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            goto reexecute_byte;\n          case '#':\n            state = s_req_fragment_start;\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_QUERY_STRING);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_query_string:\n      {\n        if (IS_URL_CHAR(ch, IS_PARSER_STRICT(options))) break;\n\n        switch (ch) {\n          case '?':\n            /* allow extra '?' in query string */\n            break;\n          case ' ':\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            break;\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            goto reexecute_byte;\n          case '#':\n            state = s_req_fragment_start;\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_QUERY_STRING);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_fragment_start:\n      {\n        if (IS_URL_CHAR(ch, IS_PARSER_STRICT(options))) {\n          state = s_req_fragment;\n          break;\n        }\n\n        switch (ch) {\n          case ' ':\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            break;\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            goto reexecute_byte;\n          case '?':\n            state = s_req_fragment;\n            break;\n          case '#':\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_FRAGMENT);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_fragment:\n      {\n        if (IS_URL_CHAR(ch, IS_PARSER_STRICT(options))) break;\n\n        switch (ch) {\n          case ' ':\n            state = s_req_http_start;\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            break;\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            state = s_headers_almost_done;\n            CALLBACK_DATA(url);\n            goto reexecute_byte;\n          case '?':\n          case '#':\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_FRAGMENT);\n            goto error;\n        }\n        break;\n      }\n\n      case s_req_http_start:\n        switch (ch) {\n          case 'H':\n            state = s_req_http_H;\n            break;\n          case ' ':\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n        }\n        break;\n\n      case s_req_http_H:\n        NOOP_CHECK(ch != 'T');\n        state = s_req_http_HT;\n        break;\n\n      case s_req_http_HT:\n        NOOP_CHECK(ch != 'T');\n        state = s_req_http_HTT;\n        break;\n\n      case s_req_http_HTT:\n        NOOP_CHECK(ch != 'P');\n        state = s_req_http_HTTP;\n        break;\n\n      case s_req_http_HTTP:\n        NOOP_CHECK(ch != '/');\n        state = s_req_first_http_major;\n        break;\n\n      /* first digit of major HTTP version */\n      case s_req_first_http_major:\n        if (ch < '0' || ch > '9') {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major = ch - '0';\n        state = s_req_http_major;\n        break;\n\n      /* major HTTP version or dot */\n      case s_req_http_major:\n      {\n        if (ch == '.') {\n          state = s_req_first_http_minor;\n          break;\n        }\n\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major *= 10;\n        parser->http_major += ch - '0';\n\n        if (parser->http_major > 999) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* first digit of minor HTTP version */\n      case s_req_first_http_minor:\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor = ch - '0';\n        state = s_req_http_minor;\n        break;\n\n      /* minor HTTP version or end of request line */\n      case s_req_http_minor:\n      {\n        if (ch == CR) {\n          if (parser->http_major== 0 && parser->http_minor == 9) {\n            state = s_headers_almost_done;\n          } else {\n            state = s_req_line_almost_done;\n          }\n          break;\n        }\n\n        if (ch == LF) {\n          if (parser->http_major == 0 && parser->http_minor == 9) {\n            state = s_headers_almost_done;\n            goto reexecute_byte;\n          } else {\n            state = s_header_field_start;\n          }\n          break;\n        }\n\n        /* XXX allow spaces after digit? */\n\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor *= 10;\n        parser->http_minor += ch - '0';\n\n        if (parser->http_minor > 999) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* end of request line */\n      case s_req_line_almost_done:\n      {\n        if (ch != LF) {\n          SET_ERRNO(HPE_LF_EXPECTED);\n          goto error;\n        }\n\n        state = s_header_field_start;\n        break;\n      }\n\n      case s_header_field_start:\n      {\n        if (ch == CR) {\n          state = s_headers_almost_done;\n          break;\n        }\n\n        if (ch == LF) {\n          /* they might be just sending \\n instead of \\r\\n so this would be\n           * the second \\n to denote the end of headers*/\n          state = s_headers_almost_done;\n          goto reexecute_byte;\n        }\n\n        c = TOKEN(ch);\n\n        if (!c) {\n          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n          goto error;\n        }\n\n        MARK(header_field);\n\n        parser->index = 0;\n        state = s_header_field;\n\n        switch (c) {\n          case 'c':\n            parser->header_state = h_matching_content_length;\n            break;\n\n          case 't':\n            parser->header_state = h_matching_transfer_encoding;\n            break;\n\n          case 'u':\n            parser->header_state = h_matching_upgrade;\n            break;\n\n          default:\n            parser->header_state = h_general;\n            break;\n        }\n        break;\n      }\n\n      case s_header_field:\n      {\n        c = TOKEN(ch);\n\n        if (c) {\n          switch (parser->header_state) {\n            case h_general:\n\n              // fast-forwarding, wheeeeeee!\n              #define MOVE_THE_HEAD do { \\\n                ++p;                     \\\n                if (!TOKEN(*p)) {        \\\n                  ch = *p;               \\\n                  goto notatoken;        \\\n                }                        \\\n              } while(0);\n\n              if (data + len - p >= 9) {\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n              } else if (data + len - p >= 4) {\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n                MOVE_THE_HEAD\n              }\n\n              break;\n\n            /* content-length */\n\n            case h_matching_content_length:\n              parser->index++;\n              if (parser->index > sizeof(CONTENT_LENGTH)-1\n                  || c != CONTENT_LENGTH[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {\n                parser->header_state = h_content_length;\n              }\n              break;\n\n            /* transfer-encoding */\n\n            case h_matching_transfer_encoding:\n              parser->index++;\n              if (parser->index > sizeof(TRANSFER_ENCODING)-1\n                  || c != TRANSFER_ENCODING[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {\n                parser->header_state = h_transfer_encoding;\n              }\n              break;\n\n            /* upgrade */\n\n            case h_matching_upgrade:\n              parser->index++;\n              if (parser->index > sizeof(UPGRADE)-1\n                  || c != UPGRADE[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(UPGRADE)-2) {\n                parser->header_state = h_upgrade;\n              }\n              break;\n\n            case h_content_length:\n            case h_transfer_encoding:\n            case h_upgrade:\n              if (ch != ' ') parser->header_state = h_general;\n              break;\n\n            default:\n              assert(0 && \"Unknown header_state\");\n              break;\n          }\n          break;\n        }\n\n        notatoken:\n        if (ch == ':') {\n          state = s_header_value_start;\n          // do not allow headers with trailing whitespaces\n          // https://tools.ietf.org/html/rfc7230#section-3.2.4\n          if (p - header_field_mark > 1 &&\n              data[p - data - 1] == ' ') {\n            SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n            goto error;\n          }\n          CALLBACK_DATA(header_field);\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n        goto error;\n      }\n\n      case s_header_value_start:\n      {\n        if (ch == ' ' || ch == '\\t') break;\n\n        MARK(header_value);\n\n        state = s_header_value;\n        parser->index = 0;\n\n        // Error out if a content_length, transfer_encoding, or upgrade header\n        // was present with no actual value.  These headers correspond with\n        // special parser states that without the below accept empty header\n        // values and so we can reject such requests here in the parser.\n        // If more headers are added, can consider moving to a hash/map based\n        // model below.\n        if (ch == CR || ch == LF) {\n          if (parser->header_state == h_content_length) {\n            SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n          } else if (parser->header_state == h_transfer_encoding) {\n            SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING);\n          } else if (parser->header_state == h_upgrade) {\n            SET_ERRNO(HPE_INVALID_UPGRADE);\n          }\n\n          if (parser->http_errno != HPE_OK) {\n            goto error;\n          }\n        }\n\n        if (ch == CR) {\n          NOOP_CHECK(parser->quote != 0);\n          parser->header_state = h_general;\n          state = s_header_almost_done;\n          CALLBACK_DATA(header_value);\n          break;\n        }\n\n        if (ch == LF) {\n          NOOP_CHECK(parser->quote != 0);\n          state = s_header_field_start;\n          CALLBACK_DATA(header_value);\n          break;\n        }\n\n        c = LOWER(ch);\n\n        switch (parser->header_state) {\n          case h_upgrade:\n            parser->flags |= F_UPGRADE;\n            parser->header_state = h_general;\n            break;\n\n          case h_transfer_encoding:\n            /* looking for 'Transfer-Encoding: chunked' */\n            if ('c' == c) {\n              parser->header_state = h_matching_transfer_encoding_chunked;\n            } else {\n              parser->header_state = h_general;\n            }\n            break;\n\n          case h_content_length:\n            if (!IS_NUM(ch)) {\n              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n              goto error;\n            }\n\n            parser->content_length = ch - '0';\n            break;\n\n          default:\n            parser->header_state = ch == QT ? h_general_and_quote : h_general;\n            break;\n        }\n        break;\n      }\n\n      case s_header_value:\n      {\n        cr_or_lf_or_qt:\n        if (ch == CR &&\n            parser->header_state != h_general_and_quote_and_escape) {\n          state = s_header_almost_done;\n          CALLBACK_DATA(header_value);\n          break;\n        }\n\n        if (ch == LF &&\n            parser->header_state != h_general_and_quote_and_escape) {\n          state = s_header_almost_done;\n          CALLBACK_DATA_NOADVANCE(header_value);\n          goto reexecute_byte;\n        }\n\n        if (!lenient && !IS_HEADER_CHAR(ch) &&\n            parser->header_state != h_general_and_quote_and_escape) {\n          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n          goto error;\n        }\n\n        switch (parser->header_state) {\n          case h_general:\n            if (ch == QT) {\n              parser->header_state = h_general_and_quote;\n            }\n\n            // fast-forwarding, wheee!\n            #define MOVE_FAST do {                    \\\n              ++p;                                    \\\n              ch = *p;                                \\\n              if (ch == CR || ch == LF || ch == QT || \\\n                  ch == BS || !IS_HEADER_CHAR(ch)) {  \\\n                goto cr_or_lf_or_qt;                  \\\n              }                                       \\\n            } while(0);\n\n            if (data + len - p >= 12) {\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n            } else if (data + len - p >= 5) {\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n              MOVE_FAST\n            }\n\n            break;\n\n          case h_general_and_quote:\n            if (ch == QT) {\n              parser->header_state = h_general;\n            } else if (ch == BS) {\n              parser->header_state = h_general_and_quote_and_escape;\n            }\n            break;\n\n          case h_general_and_quote_and_escape:\n            parser->header_state = h_general_and_quote;\n            break;\n\n          // Not sure the below is relevant anymore as from\n          // s_header_value_start it appears as though we can never\n          // be in the situation below\n          case h_transfer_encoding:\n            SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n            goto error;\n\n          case h_content_length:\n            if (ch == ' ') break;\n            if (!IS_NUM(ch)) {\n              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n              goto error;\n            }\n\n            if (parser->content_length > ((INT64_MAX - 10) / 10)) {\n              /* overflow */\n              SET_ERRNO(HPE_HUGE_CONTENT_LENGTH);\n              goto error;\n            }\n\n            parser->content_length *= 10;\n            parser->content_length += ch - '0';\n            break;\n\n          /* Transfer-Encoding: chunked */\n          case h_matching_transfer_encoding_chunked:\n            parser->index++;\n            if (parser->index > sizeof(CHUNKED)-1\n                || LOWER(ch) != CHUNKED[parser->index]) {\n              parser->header_state = h_general;\n            } else if (parser->index == sizeof(CHUNKED)-2) {\n              parser->header_state = h_transfer_encoding_chunked;\n            }\n            break;\n\n          case h_transfer_encoding_chunked:\n            if (ch != ' ') {\n              parser->header_state = h_general;\n            }\n            break;\n\n          default:\n            state = s_header_value;\n            parser->header_state = h_general;\n            break;\n        }\n        break;\n      }\n\n      case s_header_almost_done:\n      {\n        if (ch == LF) {\n          state = s_header_value_lws;\n        } else {\n          state = s_header_value;\n        }\n\n        switch (parser->header_state) {\n          case h_transfer_encoding_chunked:\n            parser->flags |= F_CHUNKED;\n            break;\n          default:\n            break;\n        }\n\n        if (ch != LF) {\n          CALLBACK_SPACE(header_value);\n        }\n\n        break;\n      }\n\n      case s_header_value_lws:\n      {\n        if (ch == ' ' || ch == '\\t')\n        {\n          state = s_header_value_start;\n          CALLBACK_SPACE(header_value);\n        }\n        else\n        {\n          state = s_header_field_start;\n          goto reexecute_byte;\n        }\n        break;\n      }\n\n      case s_headers_almost_done:\n      {\n        NOOP_CHECK(ch != LF);\n\n        if (ch != LF) {\n          SET_ERRNO(HPE_STRICT);\n          goto error;\n        }\n\n        if (parser->flags & F_TRAILING) {\n          /* End of a chunked request */\n          state = s_message_done;\n          CALLBACK_NOTIFY_NOADVANCE(chunk_complete);\n          goto reexecute_byte;\n        }\n\n        state = s_headers_done;\n\n        /* Set this here so that on_headers_complete() callbacks can see it */\n        parser->upgrade =\n            (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);\n\n        /* Here we call the headers_complete callback. This is somewhat\n         * different than other callbacks because if the user returns 1, we\n         * will interpret that as saying that this message has no body. This\n         * is needed for the annoying case of receiving a response to a HEAD\n         * request.\n         *\n         * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so\n         * we have to simulate it by handling a change in errno below.\n         */\n        size_t header_size = p - data + 1;\n        switch (settings->on_headers_complete(parser, nullptr, header_size)) {\n          case 0:\n            break;\n\n          case 1:\n            parser->flags |= F_SKIPBODY;\n            break;\n\n          default:\n            SET_ERRNO(HPE_CB_headers_complete);\n            RETURN(p - data); /* Error */\n        }\n\n        if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {\n          RETURN(p - data);\n        }\n\n        goto reexecute_byte;\n      }\n\n      case s_headers_done:\n      {\n        NOOP_CHECK(ch != LF);\n\n        // we're done parsing headers, reset overflow counters\n        parser->nread = 0;\n        // (if we now move to s_body_*, then this is irrelevant)\n        data_or_header_data_start = p;\n\n        int hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;\n        if (parser->upgrade && (parser->method == HTTP_CONNECT ||\n                                (parser->flags & F_SKIPBODY) || !hasBody)) {\n          /* Exit, the rest of the message is in a different protocol. */\n          state = NEW_MESSAGE();\n          CALLBACK_NOTIFY(message_complete);\n          RETURN((p - data) + 1);\n        }\n\n        if (parser->flags & F_SKIPBODY) {\n          state = NEW_MESSAGE();\n          CALLBACK_NOTIFY(message_complete);\n        } else if (parser->flags & F_CHUNKED) {\n          /* chunked encoding - ignore Content-Length header */\n          state = s_chunk_size_start;\n        } else {\n          if (parser->content_length == 0) {\n            /* Content-Length header given but zero: Content-Length: 0\\r\\n */\n            state = NEW_MESSAGE();\n            CALLBACK_NOTIFY(message_complete);\n          } else if (parser->content_length > 0) {\n            /* Content-Length header given and non-zero */\n            state = s_body_identity;\n          } else {\n            unsigned short sc = parser->status_code;\n            if (parser->type == HTTP_REQUEST ||\n                ((100 <= sc && sc <= 199) || sc == 204 || sc == 304)) {\n              /* Assume content-length 0 - read the next */\n              state = NEW_MESSAGE();\n              CALLBACK_NOTIFY(message_complete);\n            } else {\n              /* Read body until EOF */\n              state = s_body_identity_eof;\n            }\n          }\n        }\n\n        break;\n      }\n\n      case s_body_identity:\n      {\n        uint64_t to_read = MIN(parser->content_length, (data + len) - p);\n\n        assert(parser->content_length > 0);\n\n        /* The difference between advancing content_length and p is because\n         * the latter will automatically advance on the next loop iteration.\n         * Further, if content_length ends up at 0, we want to see the last\n         * byte again for our message complete callback.\n         */\n        MARK(body);\n        parser->content_length -= to_read;\n        p += to_read - 1;\n\n        if (parser->content_length == 0) {\n          state = s_message_done;\n\n          /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.\n           *\n           * The alternative to doing this is to wait for the next byte to\n           * trigger the data callback, just as in every other case. The\n           * problem with this is that this makes it difficult for the test\n           * harness to distinguish between complete-on-EOF and\n           * complete-on-length. It's not clear that this distinction is\n           * important for applications, but let's keep it for now.\n           */\n          _CALLBACK_DATA(body, p - body_mark + 1, p - data);\n          goto reexecute_byte;\n        }\n\n        break;\n      }\n\n      /* read until EOF */\n      case s_body_identity_eof:\n        MARK(body);\n        p = data + len - 1;\n\n        break;\n\n      case s_message_done:\n        state = NEW_MESSAGE();\n        parser->nread = 0;\n        data_or_header_data_start = p;\n        CALLBACK_NOTIFY(message_complete);\n        if (parser->upgrade) {\n          /* Exit, the rest of the message is in a different protocol. */\n          RETURN((p - data) + 1);\n        }\n        break;\n\n      case s_chunk_size_start:\n      {\n        assert(parser->flags & F_CHUNKED);\n\n        unhex_val = unhex[(unsigned char)ch];\n        if (unhex_val == -1) {\n          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);\n          goto error;\n        }\n\n        parser->content_length = unhex_val;\n        state = s_chunk_size;\n        break;\n      }\n\n      case s_chunk_size:\n      {\n        assert(parser->flags & F_CHUNKED);\n\n        if (ch == CR) {\n          state = s_chunk_size_almost_done;\n          break;\n        }\n\n        unhex_val = unhex[(unsigned char)ch];\n\n        if (unhex_val == -1) {\n          if (ch == ';' || ch == ' ') {\n            state = s_chunk_parameters;\n            break;\n          }\n\n          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);\n          goto error;\n        }\n\n        if (parser->content_length > (INT64_MAX - unhex_val) >> 4) {\n          /* overflow */\n          SET_ERRNO(HPE_HUGE_CHUNK_SIZE);\n          goto error;\n        }\n        parser->content_length *= 16;\n        parser->content_length += unhex_val;\n        break;\n      }\n\n      case s_chunk_parameters:\n      {\n        assert(parser->flags & F_CHUNKED);\n        /*\n         * just ignore this shit. TODO check for overflow\n         * TODO: It would be nice to pass this information to the\n         * on_chunk_header callback.\n         */\n        if (ch == CR) {\n          state = s_chunk_size_almost_done;\n          break;\n        }\n        break;\n      }\n\n      case s_chunk_size_almost_done:\n      {\n        assert(parser->flags & F_CHUNKED);\n        STRICT_CHECK(ch != LF);\n\n        if (parser->content_length == 0) {\n          parser->flags |= F_TRAILING;\n          state = s_header_field_start;\n          CALLBACK_NOTIFY(chunk_header);\n        } else {\n          state = s_chunk_data;\n          CALLBACK_NOTIFY(chunk_header);\n        }\n        break;\n      }\n\n      case s_chunk_data:\n      {\n        uint64_t to_read = MIN(parser->content_length, (data + len) - p);\n\n        assert(parser->flags & F_CHUNKED);\n        assert(parser->content_length > 0);\n\n        /* See the explanation in s_body_identity for why the content\n         * length and data pointers are managed this way.\n         */\n        MARK(body);\n        parser->content_length -= to_read;\n        p += to_read - 1;\n\n        if (parser->content_length == 0) {\n          state = s_chunk_data_almost_done;\n        }\n\n        break;\n      }\n\n      case s_chunk_data_almost_done:\n        assert(parser->flags & F_CHUNKED);\n        assert(parser->content_length == 0);\n        NOOP_CHECK(ch != CR);\n        state = s_chunk_data_done;\n        CALLBACK_DATA(body);\n        break;\n\n      case s_chunk_data_done:\n        assert(parser->flags & F_CHUNKED);\n        NOOP_CHECK(ch != LF);\n        state = s_chunk_size_start;\n        parser->nread = 0;\n        data_or_header_data_start = p;\n        CALLBACK_NOTIFY(chunk_complete);\n        break;\n\n      default:\n        assert(0 && \"unhandled state\");\n        SET_ERRNO(HPE_INVALID_INTERNAL_STATE);\n        goto error;\n    }\n  }\n\n  /* We can check for overflow here because in Proxygen, len <= ~8KB and so the\n   * worst thing that can happen is that we catch the overflow at 88KB rather\n   * than at 80KB.\n   * In case of chunk encoding, we count the overflow for every\n   * chunk separately.\n   * We zero the nread counter (and reset data_or_header_data_start) when we\n   * start parsing a new message or a new chunk.\n   */\n  if (PARSING_HEADER(state)) {\n    parser->nread += p - data_or_header_data_start;\n    if (parser->nread > HTTP_MAX_HEADER_SIZE) {\n      SET_ERRNO(HPE_HEADER_OVERFLOW);\n      goto error;\n    }\n  }\n\n  /* Run callbacks for any marks that we have leftover after we ran out of\n   * bytes. There should be at most one of these set, so it's OK to invoke\n   * them in series (unset marks will not result in callbacks).\n   *\n   * We use the NOADVANCE() variety of callbacks here because 'p' has already\n   * overflowed 'data' and this allows us to correct for the off-by-one that\n   * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'\n   * value that's in-bounds).\n   */\n\n  assert(((header_field_mark ? 1 : 0) +\n          (header_value_mark ? 1 : 0) +\n          (url_mark ? 1 : 0)  +\n          (reason_mark ? 1 : 0)  +\n          (body_mark ? 1 : 0)) <= 1);\n\n  CALLBACK_DATA_NOADVANCE(header_field);\n  CALLBACK_DATA_NOADVANCE(header_value);\n  CALLBACK_DATA_NOADVANCE(url);\n  CALLBACK_DATA_NOADVANCE(reason);\n  CALLBACK_DATA_NOADVANCE(body);\n\n  RETURN(len);\n\nerror:\n  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {\n    SET_ERRNO(HPE_UNKNOWN);\n  }\n\n  RETURN(p - data);\n}\n\n\nconst char * http_method_str (enum http_method m)\n{\n  return method_strings[m];\n}\n\n\nvoid\nhttp_parser_init (http_parser *parser, enum http_parser_type t)\n{\n  parser->type = t;\n  parser->state = (t == HTTP_REQUEST ? s_pre_start_req : (t == HTTP_RESPONSE ? s_pre_start_res : s_pre_start_req_or_res));\n  parser->nread = 0;\n  parser->upgrade = 0;\n  parser->flags = 0;\n  parser->method = 0;\n  parser->http_major = 0;\n  parser->http_minor = 0;\n  parser->http_errno = HPE_OK;\n}\n\nconst char *\nhttp_errno_name(enum http_errno err) {\n  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));\n  return http_strerror_tab[err].name;\n}\n\nconst char *\nhttp_errno_description(enum http_errno err) {\n  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));\n  return http_strerror_tab[err].description;\n}\n\n\nstatic enum http_host_state\nhttp_parse_host_char(enum http_host_state s, const char ch) {\n  switch(s) {\n    case s_http_userinfo:\n    case s_http_userinfo_start:\n      if (ch == '@') {\n        return s_http_host_start;\n      }\n\n      if (IS_USERINFO_CHAR(ch)) {\n        return s_http_userinfo;\n      }\n      break;\n\n    case s_http_host_start:\n      if (ch == '[') {\n        return s_http_host_v6_start;\n      }\n\n      if (IS_HOST_CHAR(ch)) {\n        return s_http_host;\n      }\n\n      break;\n\n    case s_http_host:\n      if (IS_HOST_CHAR(ch)) {\n        return s_http_host;\n      }\n\n#if __cplusplus\n      [[fallthrough]];\n#else /* __cplusplus */\n    __attribute__((fallthrough));\n#endif /* __cplusplus */\n    case s_http_host_v6_end:\n      if (ch == ':') {\n        return s_http_host_port_start;\n      }\n\n      break;\n\n    case s_http_host_v6:\n      if (ch == ']') {\n        return s_http_host_v6_end;\n      }\n\n#if __cplusplus\n      [[fallthrough]];\n#else /* __cplusplus */\n    __attribute__((fallthrough));\n#endif /* __cplusplus */\n    case s_http_host_v6_start:\n      if (IS_HEX(ch) || ch == ':' || ch == '.') {\n        return s_http_host_v6;\n      }\n\n      break;\n\n    case s_http_host_port:\n    case s_http_host_port_start:\n      if (IS_NUM(ch)) {\n        return s_http_host_port;\n      }\n\n      break;\n\n    default:\n      break;\n  }\n  return s_http_host_dead;\n}\n\nstatic int\nhttp_parse_host(const char * buf, struct http_parser_url *u, int found_at) {\n  enum http_host_state s;\n\n  const char *p;\n  size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;\n\n  u->field_data[UF_HOST].len = 0;\n\n  s = found_at ? s_http_userinfo_start : s_http_host_start;\n\n  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {\n    enum http_host_state new_s = http_parse_host_char(s, *p);\n\n    if (new_s == s_http_host_dead) {\n      return 1;\n    }\n\n    switch(new_s) {\n      case s_http_host:\n        if (s != s_http_host) {\n          u->field_data[UF_HOST].off = p - buf;\n        }\n        u->field_data[UF_HOST].len++;\n        break;\n\n      case s_http_host_v6:\n        if (s != s_http_host_v6) {\n          u->field_data[UF_HOST].off = p - buf;\n        }\n        u->field_data[UF_HOST].len++;\n        break;\n\n      case s_http_host_port:\n        if (s != s_http_host_port) {\n          u->field_data[UF_PORT].off = p - buf;\n          u->field_data[UF_PORT].len = 0;\n          u->field_set |= (1 << UF_PORT);\n        }\n        u->field_data[UF_PORT].len++;\n        break;\n\n      case s_http_userinfo:\n        if (s != s_http_userinfo) {\n          u->field_data[UF_USERINFO].off = p - buf ;\n          u->field_data[UF_USERINFO].len = 0;\n          u->field_set |= (1 << UF_USERINFO);\n        }\n        u->field_data[UF_USERINFO].len++;\n        break;\n\n      default:\n        break;\n    }\n    s = new_s;\n  }\n\n  /* Make sure we don't end somewhere unexpected */\n  switch (s) {\n    case s_http_host_start:\n    case s_http_host_v6_start:\n    case s_http_host_v6:\n    case s_http_host_port_start:\n    case s_http_userinfo:\n    case s_http_userinfo_start:\n      return 1;\n    default:\n      break;\n  }\n\n  return 0;\n}\n\n\nint\nhttp_parser_parse_url(const char *buf, size_t buflen, int is_connect,\n                      struct http_parser_url *u)\n{\n  return http_parser_parse_url_options(buf, buflen, is_connect, u, 0);\n}\n\nint\nhttp_parser_parse_url_options(const char *buf, size_t buflen, int is_connect,\n                              struct http_parser_url *u, uint8_t options)\n{\n  enum state s;\n  const char *p;\n  enum http_parser_url_fields uf, old_uf;\n  int found_at = 0;\n\n  u->port = u->field_set = 0;\n  s = is_connect ? s_req_server_start : s_req_spaces_before_url;\n  uf = old_uf = UF_MAX;\n\n  for (p = buf; p < buf + buflen; p++) {\n    s = parse_url_char(s, *p, options & F_PARSE_URL_OPTIONS_URL_STRICT);\n\n    /* Figure out the next field that we're operating on */\n    switch (s) {\n      case s_dead:\n        return 1;\n\n      /* Skip delimeters */\n      case s_req_schema_slash:\n      case s_req_schema_slash_slash:\n      case s_req_server_start:\n      case s_req_query_string_start:\n      case s_req_fragment_start:\n        continue;\n\n      case s_req_schema:\n        uf = UF_SCHEMA;\n        break;\n\n      case s_req_server_with_at:\n        found_at = 1;\n\n#if __cplusplus\n        [[fallthrough]];\n#else /* __cplusplus */\n      __attribute__((fallthrough));\n#endif /* __cplusplus */\n      case s_req_server:\n        uf = UF_HOST;\n        break;\n\n      case s_req_path:\n        uf = UF_PATH;\n        break;\n\n      case s_req_query_string:\n        uf = UF_QUERY;\n        break;\n\n      case s_req_fragment:\n        uf = UF_FRAGMENT;\n        break;\n\n      default:\n        assert(false && \"Unexpected state\");\n        return 1;\n    }\n\n    /* Nothing's changed; soldier on */\n    if (uf == old_uf) {\n      u->field_data[uf].len++;\n      continue;\n    }\n\n    u->field_data[uf].off = p - buf;\n    u->field_data[uf].len = 1;\n\n    u->field_set |= (1 << uf);\n    old_uf = uf;\n  }\n\n  /* host must be present if there is a schema */\n  /* parsing http:///toto will fail */\n  if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {\n    if (http_parse_host(buf, u, found_at) != 0) {\n      return 1;\n    }\n  }\n\n  /* CONNECT requests can only contain \"hostname:port\" */\n  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {\n    return 1;\n  }\n\n  if (u->field_set & (1 << UF_PORT)) {\n    uint16_t off;\n    uint16_t len;\n    const char* portp;\n    const char* end;\n    unsigned long v;\n\n    off = u->field_data[UF_PORT].off;\n    len = u->field_data[UF_PORT].len;\n    end = buf + off + len;\n\n    /* NOTE: The characters are already validated and are in the [0-9] range */\n    assert(off + len <= buflen && \"Port number overflow\");\n    v = 0;\n    for (portp = buf + off; portp < end; portp++) {\n      v *= 10;\n      v += *portp - '0';\n\n      /* Ports have a max value of 2^16 */\n      if (v > 0xffff) {\n        return 1;\n      }\n    }\n\n    u->port = (uint16_t) v;\n  }\n\n  return 0;\n}\n\nvoid\nhttp_parser_pause(http_parser *parser, int paused) {\n  /* Users should only be pausing/unpausing a parser that is not in an error\n   * state. In non-debug builds, there's not much that we can do about this\n   * other than ignore it.\n   */\n  if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||\n      HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {\n    SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);\n  } else {\n    assert(0 && \"Attempting to pause parser in error state\");\n  }\n}\n\n#if __cplusplus\n}\n#endif /* __cplusplus */\n"
  },
  {
    "path": "proxygen/external/http_parser/test.c",
    "content": "/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n#include \"http_parser.h\"\n#include <stdlib.h>\n#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h> /* rand */\n#include <string.h>\n#include <stdarg.h>\n\n#undef TRUE\n#define TRUE 1\n#undef FALSE\n#define FALSE 0\n\n#define MAX_HEADERS 13\n#define MAX_ELEMENT_SIZE 500\n#define MAX_CHUNKS 16\n\n#define MIN(a,b) ((a) < (b) ? (a) : (b))\n\nstatic http_parser *parser;\nstatic uint8_t parser_options = 0;\n\nstruct message {\n  const char *name; // for debugging purposes\n  const char *raw;\n  enum http_parser_type type;\n  enum http_method method;\n  int status_code;\n  char request_url[MAX_ELEMENT_SIZE];\n  char response_reason[MAX_ELEMENT_SIZE];\n  size_t response_reason_size;\n  char body[MAX_ELEMENT_SIZE];\n  size_t body_size;\n  int num_headers;\n  enum { NONE=0, FIELD, VALUE } last_header_element;\n  char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];\n  int should_keep_alive;\n\n  int num_chunks;\n  int num_chunks_complete;\n  int chunk_lengths[MAX_CHUNKS];\n\n  const char *upgrade; // upgraded body\n\n  unsigned short http_major;\n  unsigned short http_minor;\n\n  int message_begin_cb_called;\n  int headers_complete_cb_called;\n  int message_complete_cb_called;\n  int message_complete_on_eof;\n};\n\nstatic int currently_parsing_eof;\n\nstatic struct message messages[5];\nstatic int num_messages;\nstatic http_parser_settings *current_pause_parser;\n\n/* * R E Q U E S T S * */\nconst struct message requests[] =\n#define CURL_GET 0\n{ {.name= \"curl get\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /test HTTP/1.1\\r\\n\"\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\"\n         \"Host: 0.0.0.0=5000\\r\\n\"\n         \"Accept: */*\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/test\"\n  ,.num_headers= 3\n  ,.headers=\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\" }\n    , { \"Host\", \"0.0.0.0=5000\" }\n    , { \"Accept\", \"*/*\" }\n    }\n  ,.body= \"\"\n  }\n\n#define FIREFOX_GET 1\n, {.name= \"firefox get\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /favicon.ico HTTP/1.1\\r\\n\"\n         \"Host: 0.0.0.0=5000\\r\\n\"\n         \"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\\r\\n\"\n         \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\"\n         \"Accept-Language: en-us,en;q=0.5\\r\\n\"\n         \"Accept-Encoding: gzip,deflate\\r\\n\"\n         \"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\"\n         \"Keep-Alive: 300\\r\\n\"\n         \"Connection: keep-alive\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/favicon.ico\"\n  ,.num_headers= 8\n  ,.headers=\n    { { \"Host\", \"0.0.0.0=5000\" }\n    , { \"User-Agent\", \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\" }\n    , { \"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\" }\n    , { \"Accept-Language\", \"en-us,en;q=0.5\" }\n    , { \"Accept-Encoding\", \"gzip,deflate\" }\n    , { \"Accept-Charset\", \"ISO-8859-1,utf-8;q=0.7,*;q=0.7\" }\n    , { \"Keep-Alive\", \"300\" }\n    , { \"Connection\", \"keep-alive\" }\n    }\n  ,.body= \"\"\n  }\n\n#define DUMBFUCK 2\n, {.name= \"dumbfuck\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /dumbfuck HTTP/1.1\\r\\n\"\n         \"aaaaaaaaaaaaa:++++++++++\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/dumbfuck\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"aaaaaaaaaaaaa\",  \"++++++++++\" }\n    }\n  ,.body= \"\"\n  }\n\n#define FRAGMENT_IN_URI 3\n, {.name= \"fragment in url\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  /* XXX request url does include fragment? */\n  ,.request_url= \"/forums/1/topics/2375?page=1#posts-17408\"\n  ,.num_headers= 0\n  ,.body= \"\"\n  }\n\n#define GET_NO_HEADERS_NO_BODY 4\n, {.name= \"get no headers no body\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /get_no_headers_no_body/world HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE /* would need Connection: close */\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/get_no_headers_no_body/world\"\n  ,.num_headers= 0\n  ,.body= \"\"\n  }\n\n#define GET_ONE_HEADER_NO_BODY 5\n, {.name= \"get one header no body\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /get_one_header_no_body HTTP/1.1\\r\\n\"\n         \"Accept: */*\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE /* would need Connection: close */\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/get_one_header_no_body\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"Accept\" , \"*/*\" }\n    }\n  ,.body= \"\"\n  }\n\n#define GET_FUNKY_CONTENT_LENGTH 6\n, {.name= \"get funky content length body hello\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /get_funky_content_length_body_hello HTTP/1.0\\r\\n\"\n         \"conTENT-Length: 5\\r\\n\"\n         \"\\r\\n\"\n         \"HELLO\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_GET\n  ,.request_url= \"/get_funky_content_length_body_hello\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"conTENT-Length\" , \"5\" }\n    }\n  ,.body= \"HELLO\"\n  }\n\n#define POST_IDENTITY_BODY_WORLD 7\n, {.name= \"post identity body world\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /post_identity_body_world?q=search#hey HTTP/1.1\\r\\n\"\n         \"Accept: */*\\r\\n\"\n         \"Transfer-Encoding: identity\\r\\n\"\n         \"Content-Length: 5\\r\\n\"\n         \"\\r\\n\"\n         \"World\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/post_identity_body_world?q=search#hey\"\n  ,.num_headers= 3\n  ,.headers=\n    { { \"Accept\", \"*/*\" }\n    , { \"Transfer-Encoding\", \"identity\" }\n    , { \"Content-Length\", \"5\" }\n    }\n  ,.body= \"World\"\n  }\n\n#define POST_CHUNKED_ALL_YOUR_BASE 8\n, {.name= \"post - chunked body: all your base are belong to us\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /post_chunked_all_your_base HTTP/1.1\\r\\n\"\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"\\r\\n\"\n         \"1e\\r\\nall your base are belong to us\\r\\n\"\n         \"0\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/post_chunked_all_your_base\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"Transfer-Encoding\" , \"chunked\" }\n    }\n  ,.body= \"all your base are belong to us\"\n  ,.num_chunks= 1\n  ,.num_chunks_complete= 2\n  ,.chunk_lengths= { 0x1e }\n  }\n\n#define TWO_CHUNKS_MULT_ZERO_END 9\n, {.name= \"two chunks ; triple zero ending\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /two_chunks_mult_zero_end HTTP/1.1\\r\\n\"\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"\\r\\n\"\n         \"5\\r\\nhello\\r\\n\"\n         \"6\\r\\n world\\r\\n\"\n         \"000\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/two_chunks_mult_zero_end\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"Transfer-Encoding\", \"chunked\" }\n    }\n  ,.body= \"hello world\"\n  ,.num_chunks= 2\n  ,.num_chunks_complete= 3\n  ,.chunk_lengths= { 5, 6 }\n  }\n\n#define CHUNKED_W_TRAILING_HEADERS 10\n, {.name= \"chunked with trailing headers. blech.\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /chunked_w_trailing_headers HTTP/1.1\\r\\n\"\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"\\r\\n\"\n         \"5\\r\\nhello\\r\\n\"\n         \"6\\r\\n world\\r\\n\"\n         \"0\\r\\n\"\n         \"Vary: *\\r\\n\"\n         \"Content-Type: text/plain\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/chunked_w_trailing_headers\"\n  ,.num_headers= 3\n  ,.headers=\n    { { \"Transfer-Encoding\",  \"chunked\" }\n    , { \"Vary\", \"*\" }\n    , { \"Content-Type\", \"text/plain\" }\n    }\n  ,.body= \"hello world\"\n  ,.num_chunks= 2\n  ,.num_chunks_complete= 3\n  ,.chunk_lengths= { 5, 6 }\n  }\n\n#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11\n, {.name= \"with bullshit after the length\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /chunked_w_bullshit_after_length HTTP/1.1\\r\\n\"\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"\\r\\n\"\n         \"5; ihatew3;whatthefuck=aretheseparametersfor\\r\\nhello\\r\\n\"\n         \"6; blahblah; blah\\r\\n world\\r\\n\"\n         \"0\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/chunked_w_bullshit_after_length\"\n  ,.num_headers= 1\n  ,.headers=\n    { { \"Transfer-Encoding\", \"chunked\" }\n    }\n  ,.body= \"hello world\"\n  ,.num_chunks= 2\n  ,.num_chunks_complete= 3\n  ,.chunk_lengths= { 5, 6 }\n  }\n\n#define WITH_QUOTES 12\n, {.name= \"with quotes\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /with_\\\"stupid\\\"_quotes?foo=\\\"bar\\\" HTTP/1.1\\r\\n\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/with_\\\"stupid\\\"_quotes?foo=\\\"bar\\\"\"\n  ,.num_headers= 0\n  ,.headers= { }\n  ,.body= \"\"\n  }\n\n#define APACHEBENCH_GET 13\n/* The server receiving this request SHOULD NOT wait for EOF\n * to know that content-length == 0.\n * How to represent this in a unit test? message_complete_on_eof\n * Compare with NO_CONTENT_LENGTH_RESPONSE.\n */\n, {.name = \"apachebench get\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /test HTTP/1.0\\r\\n\"\n         \"Host: 0.0.0.0:5000\\r\\n\"\n         \"User-Agent: ApacheBench/2.3\\r\\n\"\n         \"Accept: */*\\r\\n\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_GET\n  ,.request_url= \"/test\"\n  ,.num_headers= 3\n  ,.headers= { { \"Host\", \"0.0.0.0:5000\" }\n             , { \"User-Agent\", \"ApacheBench/2.3\" }\n             , { \"Accept\", \"*/*\" }\n             }\n  ,.body= \"\"\n  }\n\n#define QUERY_URL_WITH_QUESTION_MARK_GET 14\n/* Some clients include '?' characters in query strings.\n */\n, {.name = \"query url with question mark\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /test.cgi?foo=bar?baz HTTP/1.1\\r\\n\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/test.cgi?foo=bar?baz\"\n  ,.num_headers= 0\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define PREFIX_NEWLINE_GET 15\n/* Some clients, especially after a POST in a keep-alive connection,\n * will send an extra CRLF before the next request\n */\n, {.name = \"newline prefix get\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"\\r\\nGET /test HTTP/1.1\\r\\n\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/test\"\n  ,.num_headers= 0\n  ,.headers= { }\n  ,.body= \"\"\n  }\n\n#define UPGRADE_REQUEST 16\n, {.name = \"upgrade request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /demo HTTP/1.1\\r\\n\"\n         \"Host: example.com\\r\\n\"\n         \"Connection: Upgrade\\r\\n\"\n         \"Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\\r\\n\"\n         \"Sec-WebSocket-Protocol: sample\\r\\n\"\n         \"Upgrade: WebSocket\\r\\n\"\n         \"Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\\r\\n\"\n         \"Origin: http://example.com\\r\\n\"\n         \"\\r\\n\"\n         \"Hot diggity dogg\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/demo\"\n  ,.num_headers= 7\n  ,.upgrade=\"Hot diggity dogg\"\n  ,.headers= { { \"Host\", \"example.com\" }\n             , { \"Connection\", \"Upgrade\" }\n             , { \"Sec-WebSocket-Key2\", \"12998 5 Y3 1  .P00\" }\n             , { \"Sec-WebSocket-Protocol\", \"sample\" }\n             , { \"Upgrade\", \"WebSocket\" }\n             , { \"Sec-WebSocket-Key1\", \"4 @1  46546xW%0l 1 5\" }\n             , { \"Origin\", \"http://example.com\" }\n             }\n  ,.body= \"\"\n  }\n\n#define CONNECT_REQUEST 17\n, {.name = \"connect request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"CONNECT 0-home0.netscape.com:443 HTTP/1.0\\r\\n\"\n         \"User-agent: Mozilla/1.1N\\r\\n\"\n         \"Proxy-authorization: basic aGVsbG86d29ybGQ=\\r\\n\"\n         \"\\r\\n\"\n         \"some data\\r\\n\"\n         \"and yet even more data\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_CONNECT\n  ,.request_url= \"0-home0.netscape.com:443\"\n  ,.num_headers= 2\n  ,.upgrade=\"some data\\r\\nand yet even more data\"\n  ,.headers= { { \"User-agent\", \"Mozilla/1.1N\" }\n             , { \"Proxy-authorization\", \"basic aGVsbG86d29ybGQ=\" }\n             }\n  ,.body= \"\"\n  }\n\n#define REPORT_REQ 18\n, {.name= \"report request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"REPORT /test HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_REPORT\n  ,.request_url= \"/test\"\n  ,.num_headers= 0\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define NO_HTTP_VERSION 19\n, {.name= \"request with no http version\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 0\n  ,.http_minor= 9\n  ,.method= HTTP_GET\n  ,.request_url= \"/\"\n  ,.num_headers= 0\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define MSEARCH_REQ 20\n, {.name= \"m-search request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"M-SEARCH * HTTP/1.1\\r\\n\"\n         \"HOST: 239.255.255.250:1900\\r\\n\"\n         \"MAN: \\\"ssdp:discover\\\"\\r\\n\"\n         \"ST: \\\"ssdp:all\\\"\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_MSEARCH\n  ,.request_url= \"*\"\n  ,.num_headers= 3\n  ,.headers= { { \"HOST\", \"239.255.255.250:1900\" }\n             , { \"MAN\", \"\\\"ssdp:discover\\\"\" }\n             , { \"ST\", \"\\\"ssdp:all\\\"\" }\n             }\n  ,.body= \"\"\n  }\n\n#define LINE_FOLDING_IN_HEADER 20\n, {.name= \"line folding in header value\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET / HTTP/1.1\\r\\n\"\n         \"Line1:   abc\\r\\n\"\n         \"\\tdef\\r\\n\"\n         \" ghi\\r\\n\"\n         \"\\t\\tjkl\\r\\n\"\n         \"  mno \\r\\n\"\n         \"\\t \\tqrs\\r\\n\"\n         \"Line2: \\t line2\\t\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/\"\n  ,.num_headers= 2\n  ,.headers= { { \"Line1\", \"abc def ghi jkl mno  qrs\" }\n             , { \"Line2\", \"line2\\t\" }\n             }\n  ,.body= \"\"\n  }\n\n\n#define QUERY_TERMINATED_HOST 21\n, {.name= \"host terminated by a query string\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET http://hypnotoad.org?hail=all HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"http://hypnotoad.org?hail=all\"\n  ,.num_headers= 0\n  ,.headers= { }\n  ,.body= \"\"\n  }\n\n#define QUERY_TERMINATED_HOSTPORT 22\n, {.name= \"host:port terminated by a query string\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET http://hypnotoad.org:1234?hail=all HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"http://hypnotoad.org:1234?hail=all\"\n  ,.num_headers= 0\n  ,.headers= { }\n  ,.body= \"\"\n  }\n\n#define SPACE_TERMINATED_HOSTPORT 23\n, {.name= \"host:port terminated by a space\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET http://hypnotoad.org:1234 HTTP/1.1\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"http://hypnotoad.org:1234\"\n  ,.num_headers= 0\n  ,.headers= { }\n  ,.body= \"\"\n  }\n\n#if !HTTP_PARSER_STRICT_URL\n#define UTF8_PATH_REQ 24\n, {.name= \"utf-8 path request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET /δ¶/δt/pope?q=1#narf HTTP/1.1\\r\\n\"\n         \"Host: github.com\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_GET\n  ,.request_url= \"/δ¶/δt/pope?q=1#narf\"\n  ,.num_headers= 1\n  ,.headers= { {\"Host\", \"github.com\" }\n             }\n  ,.body= \"\"\n  }\n#endif  /* !HTTP_PARSER_STRICT_URL */\n\n#if !HTTP_PARSER_STRICT_HOSTNAME\n#define HOSTNAME_UNDERSCORE 25\n, {.name = \"hostname underscore\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"CONNECT home_0.netscape.com:443 HTTP/1.0\\r\\n\"\n         \"User-agent: Mozilla/1.1N\\r\\n\"\n         \"Proxy-authorization: basic aGVsbG86d29ybGQ=\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_CONNECT\n  ,.request_url= \"home_0.netscape.com:443\"\n  ,.num_headers= 2\n  ,.upgrade=\"\"\n  ,.headers= { { \"User-agent\", \"Mozilla/1.1N\" }\n             , { \"Proxy-authorization\", \"basic aGVsbG86d29ybGQ=\" }\n             }\n  ,.body= \"\"\n  }\n#endif  /* !HTTP_PARSER_STRICT_HOSTNAME */\n\n#define PATCH_REQ 26\n, {.name = \"PATCH request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"PATCH /file.txt HTTP/1.1\\r\\n\"\n         \"Host: www.example.com\\r\\n\"\n         \"Content-Type: application/example\\r\\n\"\n         \"If-Match: \\\"e0023aa4e\\\"\\r\\n\"\n         \"Content-Length: 10\\r\\n\"\n         \"\\r\\n\"\n         \"cccccccccc\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_PATCH\n  ,.request_url= \"/file.txt\"\n  ,.num_headers= 4\n  ,.headers= { { \"Host\", \"www.example.com\" }\n             , { \"Content-Type\", \"application/example\" }\n             , { \"If-Match\", \"\\\"e0023aa4e\\\"\" }\n             , { \"Content-Length\", \"10\" }\n             }\n  ,.body= \"cccccccccc\"\n  }\n\n#define CONNECT_CAPS_REQUEST 27\n, {.name = \"connect caps request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\\r\\n\"\n         \"User-agent: Mozilla/1.1N\\r\\n\"\n         \"Proxy-authorization: basic aGVsbG86d29ybGQ=\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_CONNECT\n  ,.request_url= \"HOME0.NETSCAPE.COM:443\"\n  ,.num_headers= 2\n  ,.upgrade=\"\"\n  ,.headers= { { \"User-agent\", \"Mozilla/1.1N\" }\n             , { \"Proxy-authorization\", \"basic aGVsbG86d29ybGQ=\" }\n             }\n  ,.body= \"\"\n  }\n\n#define IPV6_LITERAL_URI_GET 28\n, {.name = \"IPv6 literal URI GET\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET http://[2a03:2880:2050:1f01:face:b00c:0:9]/ HTTP/1.0\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_GET\n  ,.request_url= \"http://[2a03:2880:2050:1f01:face:b00c:0:9]/\"\n  ,.num_headers= 0\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define IPV6_LITERAL_URI_CONNECT 29\n, {.name = \"IPv6 literal URI CONNECT\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"CONNECT [2a03:2880:2050:1f01:face:b00c:0:9]:443 HTTP/1.0\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_CONNECT\n  ,.request_url= \"[2a03:2880:2050:1f01:face:b00c:0:9]:443\"\n  ,.num_headers= 0\n  ,.upgrade=\"\"\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define UPGRADE_POST_REQUEST 30\n, {.name = \"upgrade post request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"POST /demo HTTP/1.1\\r\\n\"\n         \"Host: example.com\\r\\n\"\n         \"Connection: Upgrade\\r\\n\"\n         \"Upgrade: HTTP/2.0\\r\\n\"\n         \"Content-Length: 15\\r\\n\"\n         \"\\r\\n\"\n         \"sweet post body\"\n         \"Hot diggity dogg\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.method= HTTP_POST\n  ,.request_url= \"/demo\"\n  ,.num_headers= 4\n  ,.upgrade=\"Hot diggity dogg\"\n  ,.headers= { { \"Host\", \"example.com\" }\n             , { \"Connection\", \"Upgrade\" }\n             , { \"Upgrade\", \"HTTP/2.0\" }\n             , { \"Content-Length\", \"15\" }\n             }\n  ,.body= \"sweet post body\"\n  }\n\n#define CONNECT_WITH_BODY_REQUEST 31\n, {.name = \"connect with body request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"CONNECT foo.bar.com:443 HTTP/1.0\\r\\n\"\n         \"User-agent: Mozilla/1.1N\\r\\n\"\n         \"Proxy-authorization: basic aGVsbG86d29ybGQ=\\r\\n\"\n         \"Content-Length: 10\\r\\n\"\n         \"\\r\\n\"\n         \"blarfcicle\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.method= HTTP_CONNECT\n  ,.request_url= \"foo.bar.com:443\"\n  ,.num_headers= 3\n  ,.upgrade=\"blarfcicle\"\n  ,.headers= { { \"User-agent\", \"Mozilla/1.1N\" }\n             , { \"Proxy-authorization\", \"basic aGVsbG86d29ybGQ=\" }\n             , { \"Content-Length\", \"10\" }\n             }\n  ,.body= \"\"\n  }\n\n, {.name= NULL } /* sentinel */\n};\n\n/* * R E S P O N S E S * */\nconst struct message responses[] =\n#define GOOGLE_301 0\n{ {.name= \"google 301\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 301 Moved Permanently\\r\\n\"\n         \"Location: http://www.google.com/\\r\\n\"\n         \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n         \"Date: Sun, 26 Apr 2009 11:11:49 GMT\\r\\n\"\n         \"Expires: Tue, 26 May 2009 11:11:49 GMT\\r\\n\"\n         \"X-$PrototypeBI-Version: 1.6.0.3\\r\\n\" /* $ char in header field */\n         \"Cache-Control: public, max-age=2592000\\r\\n\"\n         \"Server: gws\\r\\n\"\n         \"Content-Length:  219  \\r\\n\"\n         \"\\r\\n\"\n         \"<HTML><HEAD><meta http-equiv=\\\"content-type\\\" content=\\\"text/html;charset=utf-8\\\">\\n\"\n         \"<TITLE>301 Moved</TITLE></HEAD><BODY>\\n\"\n         \"<H1>301 Moved</H1>\\n\"\n         \"The document has moved\\n\"\n         \"<A HREF=\\\"http://www.google.com/\\\">here</A>.\\r\\n\"\n         \"</BODY></HTML>\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 301\n  ,.response_reason= \"Moved Permanently\"\n  ,.num_headers= 8\n  ,.headers=\n    { { \"Location\", \"http://www.google.com/\" }\n    , { \"Content-Type\", \"text/html; charset=UTF-8\" }\n    , { \"Date\", \"Sun, 26 Apr 2009 11:11:49 GMT\" }\n    , { \"Expires\", \"Tue, 26 May 2009 11:11:49 GMT\" }\n    , { \"X-$PrototypeBI-Version\", \"1.6.0.3\" }\n    , { \"Cache-Control\", \"public, max-age=2592000\" }\n    , { \"Server\", \"gws\" }\n    , { \"Content-Length\", \"219  \" }\n    }\n  ,.body= \"<HTML><HEAD><meta http-equiv=\\\"content-type\\\" content=\\\"text/html;charset=utf-8\\\">\\n\"\n          \"<TITLE>301 Moved</TITLE></HEAD><BODY>\\n\"\n          \"<H1>301 Moved</H1>\\n\"\n          \"The document has moved\\n\"\n          \"<A HREF=\\\"http://www.google.com/\\\">here</A>.\\r\\n\"\n          \"</BODY></HTML>\\r\\n\"\n  }\n\n#define NO_CONTENT_LENGTH_RESPONSE 1\n/* The client should wait for the server's EOF. That is, when content-length\n * is not specified, and \"Connection: close\", the end of body is specified\n * by the EOF.\n * Compare with APACHEBENCH_GET\n */\n, {.name= \"no content-length response\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n         \"Date: Tue, 04 Aug 2009 07:59:32 GMT\\r\\n\"\n         \"Server: Apache\\r\\n\"\n         \"X-Powered-By: Servlet/2.5 JSP/2.1\\r\\n\"\n         \"Content-Type: text/xml; charset=utf-8\\r\\n\"\n         \"Connection: close\\r\\n\"\n         \"\\r\\n\"\n         \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\"\n         \"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\">\\n\"\n         \"  <SOAP-ENV:Body>\\n\"\n         \"    <SOAP-ENV:Fault>\\n\"\n         \"       <faultcode>SOAP-ENV:Client</faultcode>\\n\"\n         \"       <faultstring>Client Error</faultstring>\\n\"\n         \"    </SOAP-ENV:Fault>\\n\"\n         \"  </SOAP-ENV:Body>\\n\"\n         \"</SOAP-ENV:Envelope>\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= TRUE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 5\n  ,.headers=\n    { { \"Date\", \"Tue, 04 Aug 2009 07:59:32 GMT\" }\n    , { \"Server\", \"Apache\" }\n    , { \"X-Powered-By\", \"Servlet/2.5 JSP/2.1\" }\n    , { \"Content-Type\", \"text/xml; charset=utf-8\" }\n    , { \"Connection\", \"close\" }\n    }\n  ,.body= \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\"\n          \"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\">\\n\"\n          \"  <SOAP-ENV:Body>\\n\"\n          \"    <SOAP-ENV:Fault>\\n\"\n          \"       <faultcode>SOAP-ENV:Client</faultcode>\\n\"\n          \"       <faultstring>Client Error</faultstring>\\n\"\n          \"    </SOAP-ENV:Fault>\\n\"\n          \"  </SOAP-ENV:Body>\\n\"\n          \"</SOAP-ENV:Envelope>\"\n  }\n\n#define NO_HEADERS_NO_BODY_404 2\n, {.name= \"404 no headers no body\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 404 Not Found\\r\\n\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= TRUE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 404\n  ,.response_reason= \"Not Found\"\n  ,.num_headers= 0 ,.headers= {}\n  ,.body_size= 0\n  ,.body= \"\"\n  }\n\n#define NO_REASON_PHRASE 3\n, {.name= \"304 no response phrase\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 304\\r\\n\\r\\n\"\n  ,.should_keep_alive = TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 304\n  ,.response_reason= \"\"\n  ,.num_headers= 0\n  ,.headers= {}\n  ,.body= \"\"\n  }\n\n#define TRAILING_SPACE_ON_CHUNKED_BODY 4\n, {.name=\"200 trailing space on chunked body\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n         \"Content-Type: text/plain\\r\\n\"\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"\\r\\n\"\n         \"25  \\r\\n\"\n         \"This is the data in the first chunk\\r\\n\"\n         \"\\r\\n\"\n         \"1C\\r\\n\"\n         \"and this is the second one\\r\\n\"\n         \"\\r\\n\"\n         \"0  \\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 2\n  ,.headers=\n    { {\"Content-Type\", \"text/plain\" }\n    , {\"Transfer-Encoding\", \"chunked\" }\n    }\n  ,.body_size = 37+28\n  ,.body =\n         \"This is the data in the first chunk\\r\\n\"\n         \"and this is the second one\\r\\n\"\n  ,.num_chunks= 2\n  ,.num_chunks_complete= 3\n  ,.chunk_lengths= { 0x25, 0x1c }\n\n  }\n\n#define NO_CARRIAGE_RET 5\n, {.name=\"no carriage ret\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\n\"\n         \"Content-Type: text/html; charset=utf-8\\n\"\n         \"Connection: close\\n\"\n         \"\\n\"\n         \"these headers are from http://news.ycombinator.com/\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= TRUE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 2\n  ,.headers=\n    { {\"Content-Type\", \"text/html; charset=utf-8\" }\n    , {\"Connection\", \"close\" }\n    }\n  ,.body= \"these headers are from http://news.ycombinator.com/\"\n  }\n\n#define PROXY_CONNECTION 6\n, {.name=\"proxy connection\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n         \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n         \"Content-Length: 11\\r\\n\"\n         \"Proxy-Connection: close\\r\\n\"\n         \"Date: Thu, 31 Dec 2009 20:55:48 +0000\\r\\n\"\n         \"\\r\\n\"\n         \"hello world\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 4\n  ,.headers=\n    { {\"Content-Type\", \"text/html; charset=UTF-8\" }\n    , {\"Content-Length\", \"11\" }\n    , {\"Proxy-Connection\", \"close\" }\n    , {\"Date\", \"Thu, 31 Dec 2009 20:55:48 +0000\"}\n    }\n  ,.body= \"hello world\"\n  }\n\n#define UNDERSTORE_HEADER_KEY 7\n  // shown by\n  // curl -o /dev/null -v \"http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;\"\n, {.name=\"underscore header key\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n         \"Server: DCLK-AdSvr\\r\\n\"\n         \"Content-Type: text/xml\\r\\n\"\n         \"Content-Length: 0\\r\\n\"\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\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 4\n  ,.headers=\n    { {\"Server\", \"DCLK-AdSvr\" }\n    , {\"Content-Type\", \"text/xml\" }\n    , {\"Content-Length\", \"0\" }\n    , {\"DCLK_imp\", \"v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\" }\n    }\n  ,.body= \"\"\n  }\n\n#define BONJOUR_MADAME_FR 8\n/* The client should not merge two headers fields when the first one doesn't\n * have a value.\n */\n, {.name= \"bonjourmadame.fr\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.0 301 Moved Permanently\\r\\n\"\n         \"Date: Thu, 03 Jun 2010 09:56:32 GMT\\r\\n\"\n         \"Server: Apache/2.2.3 (Red Hat)\\r\\n\"\n         \"Cache-Control: public\\r\\n\"\n         \"Pragma: \\r\\n\"\n         \"Location: http://www.bonjourmadame.fr/\\r\\n\"\n         \"Vary: Accept-Encoding\\r\\n\"\n         \"Content-Length: 0\\r\\n\"\n         \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n         \"Connection: keep-alive\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= TRUE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 0\n  ,.status_code= 301\n  ,.response_reason= \"Moved Permanently\"\n  ,.num_headers= 9\n  ,.headers=\n    { { \"Date\", \"Thu, 03 Jun 2010 09:56:32 GMT\" }\n    , { \"Server\", \"Apache/2.2.3 (Red Hat)\" }\n    , { \"Cache-Control\", \"public\" }\n    , { \"Pragma\", \"\" }\n    , { \"Location\", \"http://www.bonjourmadame.fr/\" }\n    , { \"Vary\",  \"Accept-Encoding\" }\n    , { \"Content-Length\", \"0\" }\n    , { \"Content-Type\", \"text/html; charset=UTF-8\" }\n    , { \"Connection\", \"keep-alive\" }\n    }\n  ,.body= \"\"\n  }\n\n#define RES_FIELD_UNDERSCORE 10\n/* Should handle spaces in header fields */\n, {.name= \"field underscore\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n         \"Date: Tue, 28 Sep 2010 01:14:13 GMT\\r\\n\"\n         \"Server: Apache\\r\\n\"\n         \"Cache-Control: no-cache, must-revalidate\\r\\n\"\n         \"Expires: Mon, 26 Jul 1997 05:00:00 GMT\\r\\n\"\n         \".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\\r\\n\"\n         \"Vary: Accept-Encoding\\r\\n\"\n         \"_eep-Alive: timeout=45\\r\\n\" /* semantic value ignored */\n         \"_onnection: Keep-Alive\\r\\n\" /* semantic value ignored */\n         \"Transfer-Encoding: chunked\\r\\n\"\n         \"Content-Type: text/html\\r\\n\"\n         \"Connection: close\\r\\n\"\n         \"\\r\\n\"\n         \"0\\r\\n\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 200\n  ,.response_reason= \"OK\"\n  ,.num_headers= 11\n  ,.headers=\n    { { \"Date\", \"Tue, 28 Sep 2010 01:14:13 GMT\" }\n    , { \"Server\", \"Apache\" }\n    , { \"Cache-Control\", \"no-cache, must-revalidate\" }\n    , { \"Expires\", \"Mon, 26 Jul 1997 05:00:00 GMT\" }\n    , { \".et-Cookie\", \"PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\" }\n    , { \"Vary\", \"Accept-Encoding\" }\n    , { \"_eep-Alive\", \"timeout=45\" }\n    , { \"_onnection\", \"Keep-Alive\" }\n    , { \"Transfer-Encoding\", \"chunked\" }\n    , { \"Content-Type\", \"text/html\" }\n    , { \"Connection\", \"close\" }\n    }\n  ,.body= \"\"\n  ,.num_chunks= 0\n  ,.num_chunks_complete= 1\n  ,.chunk_lengths= {}\n  }\n\n#define NON_ASCII_IN_STATUS_LINE 11\n/* Should handle non-ASCII in status line */\n, {.name= \"non-ASCII in status line\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 500 Oriëntatieprobleem\\r\\n\"\n         \"Date: Fri, 5 Nov 2010 23:07:12 GMT+2\\r\\n\"\n         \"Content-Length: 0\\r\\n\"\n         \"Connection: close\\r\\n\"\n         \"\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.http_major= 1\n  ,.http_minor= 1\n  ,.status_code= 500\n  ,.response_reason= \"Oriëntatieprobleem\"\n  ,.num_headers= 3\n  ,.headers=\n    { { \"Date\", \"Fri, 5 Nov 2010 23:07:12 GMT+2\" }\n    , { \"Content-Length\", \"0\" }\n    , { \"Connection\", \"close\" }\n    }\n  ,.body= \"\"\n  }\n\n\n, {.name= NULL } /* sentinel */\n};\n\n/* Test for execution of on_message_callback regardless of the request/response\n * correctness */\n/* Invalid response */\nconst struct message on_message_begin_cb_test[] =\n{ {.name= \"Invalid response\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"ZZZZZ\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Invalid request */\n  {.name= \"Invalid request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"ZZZZZ\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Invalid unspecified request/response */\n  {.name= \"Invalid 'both' 'request/response'\"\n  ,.type= HTTP_BOTH\n  ,.raw= \"ZZZZZ\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Valid 200 response */\n  {.name= \"Simple valid 200 reponse\"\n  ,.type= HTTP_RESPONSE\n  ,.raw= \"HTTP/1.1 200 OK\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Valid request */\n  {.name= \"Simple valid request\"\n  ,.type= HTTP_REQUEST\n  ,.raw= \"GET / HTTP/1.1\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Valid unspecified request */\n  {.name= \"Simple valid 'both' request\"\n  ,.type= HTTP_BOTH\n  ,.raw= \"GET / HTTP/1.1\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  },\n\n/* Valid unspecified response */\n  {.name= \"Simple valid 'both' response\"\n  ,.type= HTTP_BOTH\n  ,.raw= \"GET / HTTP/1.1\\r\\n\"\n  ,.should_keep_alive= FALSE\n  ,.message_complete_on_eof= FALSE\n  ,.body= \"\"\n  }\n, {.name= NULL } /* sentinel */\n};\n\nint\nrequest_url_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  strncat(messages[num_messages].request_url, buf, len);\n  return 0;\n}\n\nint\nheader_field_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  struct message *m = &messages[num_messages];\n\n  if (m->last_header_element != FIELD)\n    m->num_headers++;\n\n  strncat(m->headers[m->num_headers-1][0], buf, len);\n\n  m->last_header_element = FIELD;\n\n  return 0;\n}\n\nint\nheader_value_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  struct message *m = &messages[num_messages];\n\n  const size_t cursize = strlen(m->headers[m->num_headers-1][1]);\n  if (cursize + len + 1 > MAX_ELEMENT_SIZE) {\n    len = MAX_ELEMENT_SIZE - (cursize + 1);\n  }\n\n  strncat(m->headers[m->num_headers-1][1], buf, len);\n\n  m->last_header_element = VALUE;\n\n  return 0;\n}\n\nint\nbody_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  strncat(messages[num_messages].body, buf, len);\n  messages[num_messages].body_size += len;\n // printf(\"body_cb: '%s'\\n\", requests[num_messages].body);\n  return 0;\n}\n\nint\ncount_body_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  assert(buf);\n  messages[num_messages].body_size += len;\n  return 0;\n}\n\nint\nmessage_begin_cb (http_parser *p)\n{\n  assert(p == parser);\n  messages[num_messages].message_begin_cb_called++;\n  return 0;\n}\n\nint\nheaders_complete_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  assert(!buf);\n  messages[num_messages].method = parser->method;\n  messages[num_messages].status_code = parser->status_code;\n  messages[num_messages].http_major = parser->http_major;\n  messages[num_messages].http_minor = parser->http_minor;\n  messages[num_messages].headers_complete_cb_called = TRUE;\n  return 0;\n}\n\nint\nmessage_complete_cb (http_parser *p)\n{\n  assert(p == parser);\n  messages[num_messages].message_complete_cb_called = TRUE;\n\n  messages[num_messages].message_complete_on_eof = currently_parsing_eof;\n\n  num_messages++;\n  return 0;\n}\n\nint\nresponse_reason_cb (http_parser *p, const char *buf, size_t len)\n{\n  assert(p == parser);\n  strncat(messages[num_messages].response_reason, buf, len);\n  return 0;\n}\n\nint\nchunk_header_cb (http_parser *p)\n{\n  assert(p == parser);\n  if (p->content_length == 0) {\n    // Terminating chunk.  Don't increment num_chunks in this case.\n    return 0;\n  }\n\n  int chunk_idx = messages[num_messages].num_chunks;\n  ++messages[num_messages].num_chunks;\n  if (chunk_idx < MAX_CHUNKS) {\n    messages[num_messages].chunk_lengths[chunk_idx] = p->content_length;\n  }\n\n  return 0;\n}\n\nint\nchunk_complete_cb (http_parser *p)\n{\n  assert(p == parser);\n  ++messages[num_messages].num_chunks_complete;\n  return 0;\n}\n\n/* These dontcall_* callbacks exist so that we can verify that when we're\n * paused, no additional callbacks are invoked */\nint\ndontcall_message_begin_cb (http_parser *p)\n{\n  if (p) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_message_begin() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_header_field_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_header_field() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_header_value_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_header_value() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_request_url_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_request_url() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_body_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_body_cb() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_headers_complete_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_headers_complete() called on paused \"\n                  \"parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_message_complete_cb (http_parser *p)\n{\n  if (p) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_message_complete() called on paused \"\n                  \"parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_response_reason_cb (http_parser *p, const char *buf, size_t len)\n{\n  if (p || buf || len) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_reason() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_chunk_header_cb (http_parser *p)\n{\n  if (p) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_chunk_header() called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nint\ndontcall_chunk_complete_cb (http_parser *p)\n{\n  if (p) { } // gcc\n  fprintf(stderr, \"\\n\\n*** on_chunk_complete() \"\n          \"called on paused parser ***\\n\\n\");\n  exit(1);\n}\n\nstatic http_parser_settings settings_dontcall =\n  {.on_message_begin = dontcall_message_begin_cb\n  ,.on_header_field = dontcall_header_field_cb\n  ,.on_header_value = dontcall_header_value_cb\n  ,.on_url = dontcall_request_url_cb\n  ,.on_body = dontcall_body_cb\n  ,.on_headers_complete = dontcall_headers_complete_cb\n  ,.on_message_complete = dontcall_message_complete_cb\n  ,.on_reason = dontcall_response_reason_cb\n  ,.on_chunk_header = dontcall_chunk_header_cb\n  ,.on_chunk_complete = dontcall_chunk_complete_cb\n  };\n\n/* These pause_* callbacks always pause the parser and just invoke the regular\n * callback that tracks content. Before returning, we overwrite the parser\n * settings to point to the _dontcall variety so that we can verify that\n * the pause actually did, you know, pause. */\nint\npause_message_begin_cb (http_parser *p)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return message_begin_cb(p);\n}\n\nint\npause_header_field_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return header_field_cb(p, buf, len);\n}\n\nint\npause_header_value_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return header_value_cb(p, buf, len);\n}\n\nint\npause_request_url_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return request_url_cb(p, buf, len);\n}\n\nint\npause_body_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return body_cb(p, buf, len);\n}\n\nint\npause_headers_complete_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return headers_complete_cb(p, buf, len);\n}\n\nint\npause_message_complete_cb (http_parser *p)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return message_complete_cb(p);\n}\n\nint\npause_response_reason_cb (http_parser *p, const char *buf, size_t len)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return response_reason_cb(p, buf, len);\n}\n\nint\npause_chunk_header_cb (http_parser *p)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return chunk_header_cb(p);\n}\n\nint\npause_chunk_complete_cb (http_parser *p)\n{\n  http_parser_pause(p, 1);\n  *current_pause_parser = settings_dontcall;\n  return chunk_complete_cb(p);\n}\n\nint empty_cb (http_parser *p) { return 0; }\nint empty_data_cb (http_parser *p, const char *buf, size_t len) { return 0; }\n\nstatic http_parser_settings settings_pause =\n  {.on_message_begin = pause_message_begin_cb\n  ,.on_header_field = pause_header_field_cb\n  ,.on_header_value = pause_header_value_cb\n  ,.on_url = pause_request_url_cb\n  ,.on_body = pause_body_cb\n  ,.on_headers_complete = pause_headers_complete_cb\n  ,.on_message_complete = pause_message_complete_cb\n  ,.on_reason = pause_response_reason_cb\n  ,.on_chunk_header = pause_chunk_header_cb\n  ,.on_chunk_complete = pause_chunk_complete_cb\n  };\n\nstatic http_parser_settings settings =\n  {.on_message_begin = message_begin_cb\n  ,.on_header_field = header_field_cb\n  ,.on_header_value = header_value_cb\n  ,.on_url = request_url_cb\n  ,.on_body = body_cb\n  ,.on_headers_complete = headers_complete_cb\n  ,.on_message_complete = message_complete_cb\n  ,.on_reason = response_reason_cb\n  ,.on_chunk_header = chunk_header_cb\n  ,.on_chunk_complete = chunk_complete_cb\n  };\n\nstatic http_parser_settings settings_count_body =\n  {.on_message_begin = message_begin_cb\n  ,.on_header_field = header_field_cb\n  ,.on_header_value = header_value_cb\n  ,.on_url = request_url_cb\n  ,.on_body = count_body_cb\n  ,.on_headers_complete = headers_complete_cb\n  ,.on_message_complete = message_complete_cb\n  ,.on_reason = response_reason_cb\n  ,.on_chunk_header = chunk_header_cb\n  ,.on_chunk_complete = chunk_complete_cb\n  };\n\nstatic http_parser_settings settings_null =\n  {.on_message_begin = empty_cb\n  ,.on_header_field = empty_data_cb\n  ,.on_header_value = empty_data_cb\n  ,.on_url = empty_data_cb\n  ,.on_body = empty_data_cb\n  ,.on_headers_complete = empty_data_cb\n  ,.on_message_complete = empty_cb\n  ,.on_reason = empty_data_cb\n  ,.on_chunk_header = empty_cb\n  ,.on_chunk_complete = empty_cb\n  };\n\nvoid\nparser_init (enum http_parser_type type)\n{\n  num_messages = 0;\n\n  assert(parser == NULL);\n\n  parser = malloc(sizeof(http_parser));\n\n  http_parser_init(parser, type);\n#if HTTP_PARSER_STRICT_URL\n  parser_options |= F_HTTP_PARSER_OPTIONS_URL_STRICT;\n#endif\n  memset(&messages, 0, sizeof messages);\n\n}\n\nvoid\nparser_free ()\n{\n  assert(parser);\n  free(parser);\n  parser = NULL;\n}\n\nsize_t parse (const char *buf, size_t len)\n{\n  size_t nparsed;\n  currently_parsing_eof = (len == 0);\n  nparsed = http_parser_execute_options(\n    parser, &settings, parser_options, buf, len);\n  return nparsed;\n}\n\nsize_t parse_count_body (const char *buf, size_t len)\n{\n  size_t nparsed;\n  currently_parsing_eof = (len == 0);\n  nparsed = http_parser_execute_options(\n    parser, &settings_count_body, parser_options, buf, len);\n  return nparsed;\n}\n\nsize_t parse_pause (const char *buf, size_t len)\n{\n  size_t nparsed;\n  http_parser_settings s = settings_pause;\n\n  currently_parsing_eof = (len == 0);\n  current_pause_parser = &s;\n  nparsed = http_parser_execute_options(\n    parser, current_pause_parser, parser_options, buf, len);\n  return nparsed;\n}\n\nstatic inline int\ncheck_str_eq (const struct message *m,\n              const char *prop,\n              const char *expected,\n              const char *found) {\n  if ((expected == NULL) != (found == NULL)) {\n    printf(\"\\n*** Error: %s in '%s' ***\\n\\n\", prop, m->name);\n    printf(\"expected %s\\n\", (expected == NULL) ? \"NULL\" : expected);\n    printf(\"   found %s\\n\", (found == NULL) ? \"NULL\" : found);\n    return 0;\n  }\n  if (expected != NULL && 0 != strcmp(expected, found)) {\n    printf(\"\\n*** Error: %s in '%s' ***\\n\\n\", prop, m->name);\n    printf(\"expected '%s'\\n\", expected);\n    printf(\"   found '%s'\\n\", found);\n    return 0;\n  }\n  return 1;\n}\n\nstatic inline int\ncheck_num_eq (const struct message *m,\n              const char *prop,\n              int expected,\n              int found) {\n  if (expected != found) {\n    printf(\"\\n*** Error: %s in '%s' ***\\n\\n\", prop, m->name);\n    printf(\"expected %d\\n\", expected);\n    printf(\"   found %d\\n\", found);\n    return 0;\n  }\n  return 1;\n}\n\n#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \\\n  if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0\n\n#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \\\n  if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0\n\n\nint\nmessage_eq (int index, const struct message *expected)\n{\n  int i;\n  struct message *m = &messages[index];\n\n  MESSAGE_CHECK_NUM_EQ(expected, m, http_major);\n  MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);\n\n  if (expected->type == HTTP_REQUEST) {\n    MESSAGE_CHECK_NUM_EQ(expected, m, method);\n  } else {\n    MESSAGE_CHECK_NUM_EQ(expected, m, status_code);\n    MESSAGE_CHECK_STR_EQ(expected, m, response_reason);\n  }\n\n  MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);\n\n  assert(m->message_begin_cb_called);\n  assert(m->headers_complete_cb_called);\n  assert(m->message_complete_cb_called);\n\n\n  MESSAGE_CHECK_STR_EQ(expected, m, request_url);\n  if (expected->body_size) {\n    MESSAGE_CHECK_NUM_EQ(expected, m, body_size);\n  } else {\n    MESSAGE_CHECK_STR_EQ(expected, m, body);\n  }\n\n  MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks);\n  MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete);\n  for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) {\n    MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]);\n  }\n\n  MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);\n\n  int r;\n  for (i = 0; i < m->num_headers; i++) {\n    r = check_str_eq(expected, \"header field\", expected->headers[i][0], m->headers[i][0]);\n    if (!r) return 0;\n    r = check_str_eq(expected, \"header value\", expected->headers[i][1], m->headers[i][1]);\n    if (!r) return 0;\n  }\n\n  MESSAGE_CHECK_STR_EQ(expected, m, upgrade);\n\n  return 1;\n}\n\n/* Given a sequence of varargs messages, return the number of them that the\n * parser should successfully parse, taking into account that upgraded\n * messages prevent all subsequent messages from being parsed.\n */\nsize_t\ncount_parsed_messages(const size_t nmsgs, ...) {\n  size_t i;\n  va_list ap;\n\n  va_start(ap, nmsgs);\n\n  for (i = 0; i < nmsgs; i++) {\n    struct message *m = va_arg(ap, struct message *);\n\n    if (m->upgrade) {\n      va_end(ap);\n      return i + 1;\n    }\n  }\n\n  va_end(ap);\n  return nmsgs;\n}\n\n/* Given a sequence of bytes and the number of these that we were able to\n * parse, verify that upgrade bodies are correct.\n */\nvoid\nupgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {\n  va_list ap;\n  size_t i;\n  size_t off = 0;\n\n  va_start(ap, nmsgs);\n\n  for (i = 0; i < nmsgs; i++) {\n    struct message *m = va_arg(ap, struct message *);\n\n    off += strlen(m->raw);\n\n    if (m->upgrade) {\n      off -= strlen(m->upgrade);\n\n      /* Check the portion of the response after its specified upgrade */\n      if (!check_str_eq(m, \"upgrade\", body + off, body + nread)) {\n        exit(1);\n      }\n\n      /* Fix up the response so that message_eq() will verify the beginning\n       * of the upgrade */\n      *(body + nread + strlen(m->upgrade)) = '\\0';\n      messages[num_messages -1 ].upgrade = body + nread;\n\n      va_end(ap);\n      return;\n    }\n  }\n\n  va_end(ap);\n  printf(\"\\n\\n*** Error: expected a message with upgrade ***\\n\");\n\n  exit(1);\n}\n\nstatic void\nprint_error (const char *raw, size_t error_location)\n{\n  fprintf(stderr, \"\\n*** %s:%d -- %s ***\\n\\n\",\n          \"http_parser.c\", HTTP_PARSER_ERRNO_LINE(parser),\n          http_errno_description(HTTP_PARSER_ERRNO(parser)));\n\n  int this_line = 0, char_len = 0;\n  size_t i, j, len = strlen(raw), error_location_line = 0;\n  for (i = 0; i < len; i++) {\n    if (i == error_location) this_line = 1;\n    switch (raw[i]) {\n      case '\\r':\n        char_len = 2;\n        fprintf(stderr, \"\\\\r\");\n        break;\n\n      case '\\n':\n        char_len = 2;\n        fprintf(stderr, \"\\\\n\\n\");\n\n        if (this_line) goto print;\n\n        error_location_line = 0;\n        continue;\n\n      default:\n        char_len = 1;\n        fputc(raw[i], stderr);\n        break;\n    }\n    if (!this_line) error_location_line += char_len;\n  }\n\n  fprintf(stderr, \"[eof]\\n\");\n\n print:\n  for (j = 0; j < error_location_line; j++) {\n    fputc(' ', stderr);\n  }\n  fprintf(stderr, \"^\\n\\nerror location: %u\\n\", (unsigned int)error_location);\n}\n\n\nstruct url_test {\n  const char *name;\n  const char *url;\n  int is_connect;\n  struct http_parser_url u;\n  int rv;\n};\n\nconst struct url_test url_tests[] =\n{ {.name=\"proxy request\"\n  ,.url=\"http://hostname/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  7,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 15,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"proxy request with port\"\n  ,.url=\"http://hostname:444/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)\n    ,.port=444\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  7,  8 } /* UF_HOST */\n      ,{ 16,  3 } /* UF_PORT */\n      ,{ 19,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"CONNECT request\"\n  ,.url=\"hostname:443\"\n  ,.is_connect=1\n  ,.u=\n    {.field_set=(1 << UF_HOST) | (1 << UF_PORT)\n    ,.port=443\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  0,  8 } /* UF_HOST */\n      ,{  9,  3 } /* UF_PORT */\n      ,{  0,  0 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"CONNECT request but not connect\"\n  ,.url=\"hostname:443\"\n  ,.is_connect=0\n  ,.rv=1\n  }\n\n, {.name=\"proxy ipv6 request\"\n  ,.url=\"http://[1:2::3:4]/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  8,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 17,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"proxy ipv6 request with port\"\n  ,.url=\"http://[1:2::3:4]:67/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)\n    ,.port=67\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  8,  8 } /* UF_HOST */\n      ,{ 18,  2 } /* UF_PORT */\n      ,{ 20,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"CONNECT ipv6 address\"\n  ,.url=\"[1:2::3:4]:443\"\n  ,.is_connect=1\n  ,.u=\n    {.field_set=(1 << UF_HOST) | (1 << UF_PORT)\n    ,.port=443\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  1,  8 } /* UF_HOST */\n      ,{ 11,  3 } /* UF_PORT */\n      ,{  0,  0 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"ipv4 in ipv6 address\"\n  ,.url=\"http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  8, 37 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 46,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"extra ? in query string\"\n  ,.url=\"http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,\"\n  \"fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,\"\n  \"fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  7, 10 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 17, 12 } /* UF_PATH */\n      ,{ 30,187 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"space URL encoded\"\n  ,.url=\"/toto.html?toto=a%20b\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_PATH) | (1<<UF_QUERY)\n    ,.port=0\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  0,  0 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{  0, 10 } /* UF_PATH */\n      ,{ 11, 10 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n\n, {.name=\"URL fragment\"\n  ,.url=\"/toto.html#titi\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT)\n    ,.port=0\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  0,  0 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{  0, 10 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{ 11,  4 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"complex URL fragment\"\n  ,.url=\"http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=\"\n    \"http://www.example.com/index.html?foo=bar&hello=world#midpage\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\\\n      (1<<UF_FRAGMENT)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  7, 22 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 29,  6 } /* UF_PATH */\n      ,{ 36, 69 } /* UF_QUERY */\n      ,{106,  7 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"complex URL from node js url parser doc\"\n  ,.url=\"http://host.com:8080/p/a/t/h?query=string#hash\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\\\n      (1<<UF_QUERY) | (1<<UF_FRAGMENT)\n    ,.port=8080\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  7,  8 } /* UF_HOST */\n      ,{ 16,  4 } /* UF_PORT */\n      ,{ 20,  8 } /* UF_PATH */\n      ,{ 29, 12 } /* UF_QUERY */\n      ,{ 42,  4 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"complex URL with basic auth from node js url parser doc\"\n  ,.url=\"http://a:b@host.com:8080/p/a/t/h?query=string#hash\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\\\n      (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO)\n    ,.port=8080\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{ 11,  8 } /* UF_HOST */\n      ,{ 20,  4 } /* UF_PORT */\n      ,{ 24,  8 } /* UF_PATH */\n      ,{ 33, 12 } /* UF_QUERY */\n      ,{ 46,  4 } /* UF_FRAGMENT */\n      ,{  7,  3 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"double @\"\n  ,.url=\"http://a:b@@hostname:443/\"\n  ,.is_connect=0\n  ,.rv=1\n  }\n\n, {.name=\"proxy empty host\"\n  ,.url=\"http://:443/\"\n  ,.is_connect=0\n  ,.rv=1\n  }\n\n, {.name=\"proxy empty port\"\n  ,.url=\"http://hostname:/\"\n  ,.is_connect=0\n  ,.rv=1\n  }\n\n, {.name=\"CONNECT with basic auth\"\n  ,.url=\"a:b@hostname:443\"\n  ,.is_connect=1\n  ,.rv=1\n  }\n\n, {.name=\"CONNECT empty host\"\n  ,.url=\":443\"\n  ,.is_connect=1\n  ,.rv=1\n  }\n\n, {.name=\"CONNECT empty port\"\n  ,.url=\"hostname:\"\n  ,.is_connect=1\n  ,.rv=1\n  }\n\n, {.name=\"CONNECT with extra bits\"\n  ,.url=\"hostname:443/\"\n  ,.is_connect=1\n  ,.rv=1\n  }\n\n, {.name=\"space in URL\"\n  ,.url=\"/foo bar/\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy basic auth with space url encoded\"\n  ,.url=\"http://a%20:b@host.com/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{ 14,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 22,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  7,  6 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"carriage return in URL\"\n  ,.url=\"/foo\\rbar/\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy double : in URL\"\n  ,.url=\"http://hostname::443/\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy basic auth with double :\"\n  ,.url=\"http://a::b@host.com/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{ 12,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 20,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  7,  4 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"line feed in URL\"\n  ,.url=\"/foo\\nbar/\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy empty basic auth\"\n  ,.url=\"http://@hostname/fo\"\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{  8,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 16,  3 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n, {.name=\"proxy line feed in hostname\"\n  ,.url=\"http://host\\name/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy % in hostname\"\n  ,.url=\"http://host%name/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy ; in hostname\"\n  ,.url=\"http://host;ame/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy basic auth with unreservedchars\"\n  ,.url=\"http://a!;-_!=+$@host.com/\"\n  ,.is_connect=0\n  ,.u=\n    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)\n    ,.port=0\n    ,.field_data=\n      {{  0,  4 } /* UF_SCHEMA */\n      ,{ 17,  8 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{ 25,  1 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  7,  9 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"proxy only empty basic auth\"\n  ,.url=\"http://@/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy only basic auth\"\n  ,.url=\"http://toto@/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy emtpy hostname\"\n  ,.url=\"http:///fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"proxy = in URL\"\n  ,.url=\"http://host=ame/fo\"\n  ,.rv=1 /* s_dead */\n  }\n\n#if HTTP_PARSER_STRICT_URL\n\n, {.name=\"tab in URL\"\n  ,.url=\"/foo\\tbar/\"\n  ,.rv=1 /* s_dead */\n  }\n\n, {.name=\"form feed in URL\"\n  ,.url=\"/foo\\fbar/\"\n  ,.rv=1 /* s_dead */\n  }\n\n#else /* !HTTP_PARSER_STRICT_URL */\n\n, {.name=\"tab in URL\"\n  ,.url=\"/foo\\tbar/\"\n  ,.u=\n    {.field_set=(1 << UF_PATH)\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  0,  0 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{  0,  9 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n\n, {.name=\"form feed in URL\"\n  ,.url=\"/foo\\fbar/\"\n  ,.u=\n    {.field_set=(1 << UF_PATH)\n    ,.field_data=\n      {{  0,  0 } /* UF_SCHEMA */\n      ,{  0,  0 } /* UF_HOST */\n      ,{  0,  0 } /* UF_PORT */\n      ,{  0,  9 } /* UF_PATH */\n      ,{  0,  0 } /* UF_QUERY */\n      ,{  0,  0 } /* UF_FRAGMENT */\n      ,{  0,  0 } /* UF_USERINFO */\n      }\n    }\n  ,.rv=0\n  }\n#endif\n};\n\nvoid\ndump_url (const char *url, const struct http_parser_url *u)\n{\n  unsigned int i;\n\n  printf(\"\\tfield_set: 0x%x, port: %u\\n\", u->field_set, u->port);\n  for (i = 0; i < UF_MAX; i++) {\n    if ((u->field_set & (1 << i)) == 0) {\n      printf(\"\\tfield_data[%u]: unset\\n\", i);\n      continue;\n    }\n\n    printf(\"\\tfield_data[%u]: off: %u len: %u part: \\\"%.*s\\n\\\"\",\n           i,\n           u->field_data[i].off,\n           u->field_data[i].len,\n           u->field_data[i].len,\n           url + u->field_data[i].off);\n  }\n}\n\nvoid\ntest_parse_url (void)\n{\n  struct http_parser_url u;\n  const struct url_test *test;\n  unsigned int i;\n  int rv;\n\n  for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) {\n    test = &url_tests[i];\n    memset(&u, 0, sizeof(u));\n\n#if HTTP_PARSER_STRICT_URL\n    rv = http_parser_parse_url_options(test->url,\n                                       strlen(test->url),\n                                       test->is_connect,\n                                       &u,\n                                       F_PARSE_URL_OPTIONS_URL_STRICT);\n#else\n    rv = http_parser_parse_url(test->url,\n                               strlen(test->url),\n                               test->is_connect,\n                               &u);\n#endif\n\n    if (test->rv == 0) {\n      if (rv != 0) {\n        printf(\"\\n*** http_parser_parse_url(\\\"%s\\\") \\\"%s\\\" test failed, \"\n               \"unexpected rv %d ***\\n\\n\", test->url, test->name, rv);\n        abort();\n      }\n\n      if (memcmp(&u, &test->u, sizeof(u)) != 0) {\n        printf(\"\\n*** http_parser_parse_url(\\\"%s\\\") \\\"%s\\\" failed ***\\n\",\n               test->url, test->name);\n\n        printf(\"target http_parser_url:\\n\");\n        dump_url(test->url, &test->u);\n        printf(\"result http_parser_url:\\n\");\n        dump_url(test->url, &u);\n\n        abort();\n      }\n    } else {\n      /* test->rv != 0 */\n      if (rv == 0) {\n        printf(\"\\n*** http_parser_parse_url(\\\"%s\\\") \\\"%s\\\" test failed, \"\n               \"unexpected rv %d ***\\n\\n\", test->url, test->name, rv);\n        abort();\n      }\n    }\n  }\n}\n\n\nvoid\ntest_message (const struct message *message)\n{\n  size_t raw_len = strlen(message->raw);\n  size_t msg1len;\n  for (msg1len = 0; msg1len < raw_len; msg1len++) {\n    parser_init(message->type);\n\n    size_t read;\n    const char *msg1 = message->raw;\n    const char *msg2 = msg1 + msg1len;\n    size_t msg2len = raw_len - msg1len;\n\n    if (msg1len) {\n      read = parse(msg1, msg1len);\n\n      if (message->upgrade && parser->upgrade && num_messages > 0) {\n        messages[num_messages - 1].upgrade = msg1 + read;\n        goto test;\n      }\n\n      if (read != msg1len) {\n        print_error(msg1, read);\n        exit(1);\n      }\n    }\n\n\n    read = parse(msg2, msg2len);\n\n    if (message->upgrade && parser->upgrade) {\n      messages[num_messages - 1].upgrade = msg2 + read;\n      goto test;\n    }\n\n    if (read != msg2len) {\n      print_error(msg2, read);\n      exit(1);\n    }\n\n    read = parse(NULL, 0);\n\n    if (read != 0) {\n      print_error(message->raw, read);\n      exit(1);\n    }\n\n  test:\n\n    if (num_messages != 1) {\n      printf(\"\\n*** num_messages != 1 after testing '%s' ***\\n\\n\", message->name);\n      exit(1);\n    }\n\n    if(!message_eq(0, message)) exit(1);\n\n    parser_free();\n  }\n}\n\nvoid\ntest_message_count_body (const struct message *message)\n{\n  parser_init(message->type);\n\n  size_t read;\n  size_t l = strlen(message->raw);\n  size_t i, toread;\n  size_t chunk = 4024;\n\n  for (i = 0; i < l; i+= chunk) {\n    toread = MIN(l-i, chunk);\n    read = parse_count_body(message->raw + i, toread);\n    if (read != toread) {\n      print_error(message->raw, read);\n      exit(1);\n    }\n  }\n\n\n  read = parse_count_body(NULL, 0);\n  if (read != 0) {\n    print_error(message->raw, read);\n    exit(1);\n  }\n\n  if (num_messages != 1) {\n    printf(\"\\n*** num_messages != 1 after testing '%s' ***\\n\\n\", message->name);\n    exit(1);\n  }\n\n  if(!message_eq(0, message)) exit(1);\n\n  parser_free();\n}\n\nvoid\ntest_simple (const char *buf, enum http_errno err_expected)\n{\n  parser_init(HTTP_REQUEST);\n\n  size_t parsed;\n  int pass;\n  enum http_errno err;\n\n  parsed = parse(buf, strlen(buf));\n  pass = (parsed == strlen(buf));\n  err = HTTP_PARSER_ERRNO(parser);\n  parsed = parse(NULL, 0);\n  pass &= (parsed == 0);\n\n  parser_free();\n\n  /* In strict mode, allow us to pass with an unexpected HPE_STRICT as\n   * long as the caller isn't expecting success.\n   */\n#if HTTP_PARSER_STRICT_URL || HTTP_PARSER_STRICT_HOSTNAME\n  if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) {\n    assert(pass != 0);\n#else\n  if (err_expected != err) {\n    assert(pass == 0);\n#endif\n    fprintf(stderr, \"\\n*** test_simple expected %s, but saw %s ***\\n\\n%s\\n\",\n        http_errno_name(err_expected), http_errno_name(err), buf);\n    exit(1);\n  }\n  (void)pass;\n}\n\n#if HTTP_PARSER_STRICT_URL\nvoid\ntest_lax_in_strict_mode (const char *buf, enum http_errno err_expected)\n{\n  parser_init(HTTP_REQUEST);\n  // clear strict flag\n  parser_options = 0;\n\n  size_t parsed;\n  int pass;\n  enum http_errno err;\n\n  parsed = parse(buf, strlen(buf));\n  pass = (parsed == strlen(buf));\n  err = HTTP_PARSER_ERRNO(parser);\n  parsed = parse(NULL, 0);\n  pass &= (parsed == 0);\n\n  parser_free();\n\n  /* In strict mode, allow us to pass with an unexpected HPE_STRICT as\n   * long as the caller isn't expecting success.\n   */\n  if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) {\n    assert(pass != 0);\n    fprintf(stderr, \"\\n*** test_simple expected %s, but saw %s ***\\n\\n%s\\n\",\n        http_errno_name(err_expected), http_errno_name(err), buf);\n    exit(1);\n  }\n  (void)pass;\n}\n#endif\n\nvoid\ntest_no_overflow_parse_url (void)\n{\n  int rv;\n  struct http_parser_url u;\n\n  rv = http_parser_parse_url(\"http://example.com:8001\", 22, 0, &u);\n\n  if (rv != 0) {\n    fprintf(stderr,\n            \"\\n*** test_no_overflow_parse_url invalid return value=%d\\n\",\n            rv);\n    abort();\n  }\n\n  if (u.port != 800) {\n    fprintf(stderr,\n            \"\\n*** test_no_overflow_parse_url invalid port number=%d\\n\",\n            u.port);\n    abort();\n  }\n}\n\nvoid\ntest_header_overflow_error (int req)\n{\n  http_parser parser;\n  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);\n  size_t parsed;\n  const char *buf;\n  buf = req ? \"GET / HTTP/1.1\\r\\n\" : \"HTTP/1.0 200 OK\\r\\n\";\n  parsed = http_parser_execute_options(\n    &parser, &settings_null, parser_options, buf, strlen(buf));\n  assert(parsed == strlen(buf));\n\n  buf = \"header-key: header-value\\r\\n\";\n  size_t buflen = strlen(buf);\n\n  int i;\n  for (i = 0; i < 10000; i++) {\n    parsed = http_parser_execute_options(\n      &parser, &settings_null, parser_options, buf, buflen);\n    if (parsed != buflen) {\n      //fprintf(stderr, \"error found on iter %d\\n\", i);\n      assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW);\n      return;\n    }\n  }\n\n  fprintf(stderr, \"\\n*** Error expected but none in header overflow test ***\\n\");\n  exit(1);\n}\n\nvoid\ntest_no_overflow_long_body (int req, size_t length)\n{\n  http_parser parser;\n  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);\n  size_t parsed;\n  size_t i;\n  char buf1[3000];\n  size_t buf1len = snprintf(buf1, sizeof(buf1), \"%s\\r\\nConnection: Keep-Alive\\r\\nContent-Length: %zu\\r\\n\\r\\n\",\n      req ? \"POST / HTTP/1.0\" : \"HTTP/1.0 200 OK\", length);\n  parsed = http_parser_execute_options(\n    &parser, &settings_null, parser_options, buf1, buf1len);\n  if (parsed != buf1len)\n    goto err;\n\n  for (i = 0; i < length; i++) {\n    char foo = 'a';\n    parsed = http_parser_execute_options(\n      &parser, &settings_null, parser_options, &foo, 1);\n    if (parsed != 1)\n      goto err;\n  }\n\n  parsed = http_parser_execute_options(\n    &parser, &settings_null, parser_options, buf1, buf1len);\n  if (parsed != buf1len) goto err;\n  return;\n\n err:\n  fprintf(stderr,\n          \"\\n*** error in test_no_overflow_long_body %s of length %zu ***\\n\",\n          req ? \"REQUEST\" : \"RESPONSE\",\n          length);\n  exit(1);\n}\n\nvoid\ntest_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)\n{\n  int message_count = count_parsed_messages(3, r1, r2, r3);\n\n  char total[ strlen(r1->raw)\n            + strlen(r2->raw)\n            + strlen(r3->raw)\n            + 1\n            ];\n  total[0] = '\\0';\n\n  strcat(total, r1->raw);\n  strcat(total, r2->raw);\n  strcat(total, r3->raw);\n\n  parser_init(r1->type);\n\n  size_t read;\n\n  read = parse(total, strlen(total));\n\n  if (parser->upgrade) {\n    upgrade_message_fix(total, read, 3, r1, r2, r3);\n    goto test;\n  }\n\n  if (read != strlen(total)) {\n    print_error(total, read);\n    exit(1);\n  }\n\n  read = parse(NULL, 0);\n\n  if (read != 0) {\n    print_error(total, read);\n    exit(1);\n  }\n\ntest:\n\n  if (message_count != num_messages) {\n    fprintf(stderr, \"\\n\\n*** Parser didn't see 3 messages only %d *** \\n\", num_messages);\n    exit(1);\n  }\n\n  if (!message_eq(0, r1)) exit(1);\n  if (message_count > 1 && !message_eq(1, r2)) exit(1);\n  if (message_count > 2 && !message_eq(2, r3)) exit(1);\n\n  parser_free();\n}\n\n/* SCAN through every possible breaking to make sure the\n * parser can handle getting the content in any chunks that\n * might come from the socket\n */\nvoid\ntest_scan (const struct message *r1, const struct message *r2, const struct message *r3)\n{\n  char total[80*1024] = \"\\0\";\n  char buf1[80*1024] = \"\\0\";\n  char buf2[80*1024] = \"\\0\";\n  char buf3[80*1024] = \"\\0\";\n\n  strcat(total, r1->raw);\n  strcat(total, r2->raw);\n  strcat(total, r3->raw);\n\n  size_t read;\n\n  int total_len = strlen(total);\n\n  int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;\n  int ops = 0 ;\n\n  size_t buf1_len, buf2_len, buf3_len;\n  int message_count = count_parsed_messages(3, r1, r2, r3);\n\n  int i,j,type_both;\n  for (type_both = 0; type_both < 2; type_both ++ ) {\n    for (j = 2; j < total_len; j ++ ) {\n      for (i = 1; i < j; i ++ ) {\n\n        if (ops % 1000 == 0)  {\n          printf(\"\\b\\b\\b\\b%3.0f%%\", 100 * (float)ops /(float)total_ops);\n          fflush(stdout);\n        }\n        ops += 1;\n\n        parser_init(type_both ? HTTP_BOTH : r1->type);\n\n        buf1_len = i;\n        strncpy(buf1, total, buf1_len);\n        buf1[buf1_len] = 0;\n\n        buf2_len = j - i;\n        strncpy(buf2, total+i, buf2_len);\n        buf2[buf2_len] = 0;\n\n        buf3_len = total_len - j;\n        strncpy(buf3, total+j, buf3_len);\n        buf3[buf3_len] = 0;\n\n        read = parse(buf1, buf1_len);\n\n        if (parser->upgrade) goto test;\n\n        if (read != buf1_len) {\n          print_error(buf1, read);\n          goto error;\n        }\n\n        read += parse(buf2, buf2_len);\n\n        if (parser->upgrade) goto test;\n\n        if (read != buf1_len + buf2_len) {\n          print_error(buf2, read);\n          goto error;\n        }\n\n        read += parse(buf3, buf3_len);\n        if (parser->upgrade) goto test;\n\n        if (read != buf1_len + buf2_len + buf3_len) {\n          print_error(buf3, read);\n          goto error;\n        }\n\n        parse(NULL, 0);\n\ntest:\n        if (parser->upgrade) {\n          upgrade_message_fix(total, read, 3, r1, r2, r3);\n        }\n\n        if (message_count != num_messages) {\n          fprintf(stderr, \"\\n\\nParser didn't see %d messages only %d\\n\",\n            message_count, num_messages);\n          goto error;\n        }\n\n        if (!message_eq(0, r1)) {\n          fprintf(stderr, \"\\n\\nError matching messages[0] in test_scan.\\n\");\n          goto error;\n        }\n\n        if (message_count > 1 && !message_eq(1, r2)) {\n          fprintf(stderr, \"\\n\\nError matching messages[1] in test_scan.\\n\");\n          goto error;\n        }\n\n        if (message_count > 2 && !message_eq(2, r3)) {\n          fprintf(stderr, \"\\n\\nError matching messages[2] in test_scan.\\n\");\n          goto error;\n        }\n\n        parser_free();\n      }\n    }\n  }\n  puts(\"\\b\\b\\b\\b100%\");\n  return;\n\n error:\n  fprintf(stderr, \"i=%d  j=%d\\n\", i, j);\n  fprintf(stderr, \"buf1 (%u) %s\\n\\n\", (unsigned int)buf1_len, buf1);\n  fprintf(stderr, \"buf2 (%u) %s\\n\\n\", (unsigned int)buf2_len , buf2);\n  fprintf(stderr, \"buf3 (%u) %s\\n\", (unsigned int)buf3_len, buf3);\n  exit(1);\n}\n\n// user required to free the result\n// string terminated by \\0\nchar *\ncreate_large_chunked_message (int body_size_in_kb, const char* headers)\n{\n  int i;\n  size_t wrote = 0;\n  size_t headers_len = strlen(headers);\n  size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6;\n  char * buf = malloc(bufsize);\n\n  memcpy(buf, headers, headers_len);\n  wrote += headers_len;\n\n  for (i = 0; i < body_size_in_kb; i++) {\n    // write 1kb chunk into the body.\n    memcpy(buf + wrote, \"400\\r\\n\", 5);\n    wrote += 5;\n    memset(buf + wrote, 'C', 1024);\n    wrote += 1024;\n    strcpy(buf + wrote, \"\\r\\n\");\n    wrote += 2;\n  }\n\n  memcpy(buf + wrote, \"0\\r\\n\\r\\n\", 6);\n  wrote += 6;\n  assert(wrote == bufsize);\n\n  return buf;\n}\n\n/* Verify that we can pause parsing at any of the bytes in the\n * message and still get the result that we're expecting. */\nvoid\ntest_message_pause (const struct message *msg)\n{\n  char *buf = (char*) msg->raw;\n  size_t buflen = strlen(msg->raw);\n  size_t nread;\n\n  parser_init(msg->type);\n\n  do {\n    nread = parse_pause(buf, buflen);\n\n    // We can only set the upgrade buffer once we've gotten our message\n    // completion callback.\n    if (messages[0].message_complete_cb_called &&\n        msg->upgrade &&\n        parser->upgrade) {\n      messages[0].upgrade = buf + nread;\n      goto test;\n    }\n\n    if (nread < buflen) {\n\n      // Not much do to if we failed a strict-mode check\n      if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {\n        parser_free();\n        return;\n      }\n\n      assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);\n    }\n\n    buf += nread;\n    buflen -= nread;\n    http_parser_pause(parser, 0);\n  } while (buflen > 0);\n\n  nread = parse_pause(NULL, 0);\n  assert (nread == 0);\n\ntest:\n  if (num_messages != 1) {\n    printf(\"\\n*** num_messages != 1 after testing '%s' ***\\n\\n\", msg->name);\n    exit(1);\n  }\n\n  if(!message_eq(0, msg)) exit(1);\n\n  parser_free();\n}\n\n/* Verify that on_message_begin callback is called regardless of correctness of\n * the message */\nvoid\ntest_on_message_begin_cb(const struct message *message)\n{\n  parser_init(message->type);\n  assert(messages[num_messages].message_begin_cb_called == 0);\n  parse(message->raw, strlen(message->raw));\n  assert(messages[num_messages].message_begin_cb_called == 1);\n  parser_free();\n}\n\nint\nmain (void)\n{\n  parser = NULL;\n  int i, j, k;\n  int request_count;\n  int response_count;\n  int on_message_begin_cb_count;\n\n  printf(\"sizeof(http_parser) = %u\\n\", (unsigned int)sizeof(http_parser));\n\n  for (request_count = 0; requests[request_count].name; request_count++);\n  for (response_count = 0; responses[response_count].name; response_count++);\n  for (on_message_begin_cb_count = 0;\n       on_message_begin_cb_test[on_message_begin_cb_count].name;\n       on_message_begin_cb_count++);\n\n  //// API\n  test_parse_url();\n\n  /// TESTING ON_MESSAGE_BEGIN CALLS\n  for (i = 0 ; i < on_message_begin_cb_count; i++) {\n    test_on_message_begin_cb(&on_message_begin_cb_test[i]);\n  }\n\n  //// OVERFLOW CONDITIONS\n  test_no_overflow_parse_url();\n\n  test_header_overflow_error(HTTP_REQUEST);\n  test_no_overflow_long_body(HTTP_REQUEST, 1000);\n  test_no_overflow_long_body(HTTP_REQUEST, 100000);\n\n  test_header_overflow_error(HTTP_RESPONSE);\n  test_no_overflow_long_body(HTTP_RESPONSE, 1000);\n  test_no_overflow_long_body(HTTP_RESPONSE, 100000);\n\n  //// RESPONSES\n\n  for (i = 0; i < response_count; i++) {\n    test_message(&responses[i]);\n  }\n\n  for (i = 0; i < response_count; i++) {\n    test_message_pause(&responses[i]);\n  }\n\n  for (i = 0; i < response_count; i++) {\n    if (!responses[i].should_keep_alive) continue;\n    for (j = 0; j < response_count; j++) {\n      if (!responses[j].should_keep_alive) continue;\n      for (k = 0; k < response_count; k++) {\n        test_multiple3(&responses[i], &responses[j], &responses[k]);\n      }\n    }\n  }\n\n  test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);\n  test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);\n\n  // test very large chunked response\n  {\n    char * msg = create_large_chunked_message(31337,\n      \"HTTP/1.0 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\"\n      \"Content-Type: text/plain\\r\\n\"\n      \"\\r\\n\");\n    struct message large_chunked =\n      {.name= \"large chunked\"\n      ,.type= HTTP_RESPONSE\n      ,.raw= msg\n      ,.should_keep_alive= FALSE\n      ,.message_complete_on_eof= FALSE\n      ,.http_major= 1\n      ,.http_minor= 0\n      ,.status_code= 200\n      ,.response_reason= \"OK\"\n      ,.num_headers= 2\n      ,.headers=\n        { { \"Transfer-Encoding\", \"chunked\" }\n        , { \"Content-Type\", \"text/plain\" }\n        }\n      ,.body_size= 31337*1024\n      ,.num_chunks= 31337\n      ,.num_chunks_complete= 31338\n      };\n    for (i = 0; i < MAX_CHUNKS; ++i) {\n      large_chunked.chunk_lengths[i] = 1024;\n    }\n    test_message_count_body(&large_chunked);\n    free(msg);\n  }\n\n\n\n  printf(\"response scan 1/2      \");\n  test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]\n           , &responses[NO_REASON_PHRASE]\n           , &responses[NO_HEADERS_NO_BODY_404]\n           );\n\n  printf(\"response scan 2/2      \");\n  test_scan( &responses[BONJOUR_MADAME_FR]\n           , &responses[UNDERSTORE_HEADER_KEY]\n           , &responses[NO_CARRIAGE_RET]\n           );\n\n  puts(\"responses okay\");\n\n\n  /// REQUESTS\n\n  test_simple(\"hello world\", HPE_INVALID_METHOD);\n  test_simple(\"GET / HTP/1.1\\r\\n\\r\\n\", HPE_INVALID_VERSION);\n\n\n  test_simple(\"ASDF / HTTP/1.1\\r\\n\\r\\n\", HPE_INVALID_METHOD);\n  test_simple(\"PROPPATCHA / HTTP/1.1\\r\\n\\r\\n\", HPE_INVALID_METHOD);\n  test_simple(\"GETA / HTTP/1.1\\r\\n\\r\\n\", HPE_INVALID_METHOD);\n\n  // Well-formed but incomplete\n  test_simple(\"GET / HTTP/1.1\\r\\n\"\n              \"Content-Type: text/plain\\r\\n\"\n              \"Content-Length: 6\\r\\n\"\n              \"\\r\\n\"\n              \"fooba\",\n              HPE_OK);\n\n  static const char *all_methods[] = {\n    \"DELETE\",\n    \"GET\",\n    \"HEAD\",\n    \"POST\",\n    \"PUT\",\n    //\"CONNECT\", //CONNECT can't be tested like other methods, it's a tunnel\n    \"OPTIONS\",\n    \"TRACE\",\n    \"COPY\",\n    \"LOCK\",\n    \"MKCOL\",\n    \"MOVE\",\n    \"PROPFIND\",\n    \"PROPPATCH\",\n    \"UNLOCK\",\n    \"REPORT\",\n    \"MKACTIVITY\",\n    \"CHECKOUT\",\n    \"MERGE\",\n    \"M-SEARCH\",\n    \"NOTIFY\",\n    \"SUBSCRIBE\",\n    \"UNSUBSCRIBE\",\n    \"PATCH\",\n    0 };\n  const char **this_method;\n  for (this_method = all_methods; *this_method; this_method++) {\n    char buf[200];\n    snprintf(buf, sizeof(buf), \"%s / HTTP/1.1\\r\\n\\r\\n\", *this_method);\n    test_simple(buf, HPE_OK);\n  }\n\n  static const char *bad_methods[] = {\n      \"C******\",\n      \"M****\",\n      0 };\n  for (this_method = bad_methods; *this_method; this_method++) {\n    char buf[200];\n    snprintf(buf, sizeof(buf), \"%s / HTTP/1.1\\r\\n\\r\\n\", *this_method);\n    test_simple(buf, HPE_UNKNOWN);\n  }\n\n  const char *dumbfuck2 =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"X-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\\r\\n\"\n    \"\\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\\r\\n\"\n    \"\\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\\r\\n\"\n    \"\\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\\r\\n\"\n    \"\\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\\r\\n\"\n    \"\\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\\r\\n\"\n    \"\\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\\r\\n\"\n    \"\\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\\r\\n\"\n    \"\\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\\r\\n\"\n    \"\\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\\r\\n\"\n    \"\\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\\r\\n\"\n    \"\\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\\r\\n\"\n    \"\\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\\r\\n\"\n    \"\\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\\r\\n\"\n    \"\\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\\r\\n\"\n    \"\\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\\r\\n\"\n    \"\\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\\r\\n\"\n    \"\\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\\r\\n\"\n    \"\\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\\r\\n\"\n    \"\\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\\r\\n\"\n    \"\\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\\r\\n\"\n    \"\\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\\r\\n\"\n    \"\\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\\r\\n\"\n    \"\\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\\r\\n\"\n    \"\\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\\r\\n\"\n    \"\\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\\r\\n\"\n    \"\\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\\r\\n\"\n    \"\\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\\r\\n\"\n    \"\\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\\r\\n\"\n    \"\\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\\r\\n\"\n    \"\\tRA==\\r\\n\"\n    \"\\t-----END CERTIFICATE-----\\r\\n\"\n    \"\\r\\n\";\n  test_simple(dumbfuck2, HPE_OK);\n\n  const char *corrupted_header_name =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"X-Some-Header\\r\\033\\065\\325eep-Alive\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN);\n\n  const char *header_with_space =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Foo Foo: some_value\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(header_with_space, HPE_INVALID_HEADER_TOKEN);\n\n  const char *header_with_curly_brace =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Foo}Foo: some_value\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(header_with_curly_brace, HPE_INVALID_HEADER_TOKEN);\n\n  const char *header_with_quote =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Foo\\\"Foo: some_value\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(header_with_quote, HPE_INVALID_HEADER_TOKEN);\n\n  const char *header_with_forward_slash =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Foo/Foo: some_value\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(header_with_forward_slash, HPE_INVALID_HEADER_TOKEN);\n\n  const char *header_with_trailing_space =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"X-Some-Header : some_value\\r\\n\"\n    \"\\r\\n\";\n  test_simple(header_with_trailing_space, HPE_INVALID_HEADER_TOKEN);\n\n  const char *bad_end_of_headers_1 =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example1.com\\r\\n\"\n    \"X-Some-Header: some_value\"\n    \"\\r\\n\\r*\";\n  test_simple(bad_end_of_headers_1, HPE_STRICT);\n\n  const char *bad_end_of_headers_2 =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example2.com\\r\\n\"\n    \"X-Some-Header: some_value\"\n    \"\\n\\r*\";\n  test_simple(bad_end_of_headers_2, HPE_STRICT);\n\n  const char *empty_content_length_header =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Content-Length:\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(empty_content_length_header, HPE_INVALID_CONTENT_LENGTH);\n\n  const char *empty_transfer_encoding_header =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Transfer-Encoding:\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(empty_transfer_encoding_header, HPE_INVALID_TRANSFER_ENCODING);\n\n  const char *empty_upgrade_header =\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"Upgrade:\\r\\n\"\n    \"Accept-Encoding: gzip\\r\\n\"\n    \"\\r\\n\";\n  test_simple(empty_upgrade_header, HPE_INVALID_UPGRADE);\n\n  #if 0\n  // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body\n  // until EOF.\n  //\n  // no content-length\n  // error if there is a body without content length\n  const char *bad_get_no_headers_no_body = \"GET /bad_get_no_headers_no_body/world HTTP/1.1\\r\\n\"\n                                           \"Accept: */*\\r\\n\"\n                                           \"\\r\\n\"\n                                           \"HELLO\";\n  test_simple(bad_get_no_headers_no_body, 0);\n#endif\n  /* TODO sending junk and large headers gets rejected */\n\n\n  /* check to make sure our predefined requests are okay */\n  for (i = 0; requests[i].name; i++) {\n    test_message(&requests[i]);\n  }\n\n  for (i = 0; i < request_count; i++) {\n    test_message_pause(&requests[i]);\n  }\n\n  for (i = 0; i < request_count; i++) {\n    if (!requests[i].should_keep_alive) continue;\n    for (j = 0; j < request_count; j++) {\n      if (!requests[j].should_keep_alive) continue;\n      for (k = 0; k < request_count; k++) {\n        test_multiple3(&requests[i], &requests[j], &requests[k]);\n      }\n    }\n  }\n\n  printf(\"request scan 1/4      \");\n  test_scan( &requests[GET_NO_HEADERS_NO_BODY]\n           , &requests[GET_ONE_HEADER_NO_BODY]\n           , &requests[GET_NO_HEADERS_NO_BODY]\n           );\n\n  printf(\"request scan 2/4      \");\n  test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]\n           , &requests[POST_IDENTITY_BODY_WORLD]\n           , &requests[GET_FUNKY_CONTENT_LENGTH]\n           );\n\n  printf(\"request scan 3/4      \");\n  test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]\n           , &requests[CHUNKED_W_TRAILING_HEADERS]\n           , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]\n           );\n\n  printf(\"request scan 4/4      \");\n  test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]\n           , &requests[PREFIX_NEWLINE_GET ]\n           , &requests[CONNECT_REQUEST]\n           );\n\n#if HTTP_PARSER_STRICT_URL\n  const char *high_ascii_url =\n    \"GET /high_ascii\\xff HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"\\r\\n\";\n  test_lax_in_strict_mode(high_ascii_url, HPE_OK);\n  test_simple(high_ascii_url, HPE_INVALID_PATH);\n  const char *ht_ff_url =\n    \"GET /ht_ff\\x09\\x12 HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"\\r\\n\";\n  test_lax_in_strict_mode(ht_ff_url, HPE_OK);\n  test_simple(ht_ff_url, HPE_INVALID_PATH);\n  const char *normal_url =\n    \"GET /normal HTTP/1.1\\r\\n\"\n    \"Host: www.example.com\\r\\n\"\n    \"\\r\\n\";\n  test_lax_in_strict_mode(normal_url, HPE_OK);\n  test_simple(normal_url, HPE_OK);\n#endif\n\n  puts(\"requests okay\");\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/fuzzers/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nfunction(proxygen_add_fuzzer)\n    if(NOT BUILD_FUZZERS)\n        return()\n    endif()\n\n    set(options)\n    set(one_value_args TARGET WORKING_DIRECTORY PREFIX)\n    set(multi_value_args SOURCES DEPENDS INCLUDES EXTRA_ARGS)\n    cmake_parse_arguments(PARSE_ARGV 0 PROXYGEN_FUZZ \"${options}\" \"${one_value_args}\" \"${multi_value_args}\")\n\n    # Remove any references to the main we have from gmock\n    # So we can use the one provided by the fuzzing engine\n    set(LIBGMOCK_LIBRARIES_NO_MAIN ${LIBGMOCK_LIBRARIES})\n    list(REMOVE_ITEM\n         LIBGMOCK_LIBRARIES_NO_MAIN\n         \"${GTEST_BINARY_DIR}/${CMAKE_CFG_INTDIR}/googlemock/${CMAKE_STATIC_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}\"\n         gmock_main\n         gmock_maind\n         GTest::gmock_main\n    )\n\n    if(NOT PROXYGEN_FUZZ_TARGET)\n      message(FATAL_ERROR \"The TARGET parameter is mandatory.\")\n    endif()\n\n    if(NOT PROXYGEN_FUZZ_SOURCES)\n      set(PROXYGEN_FUZZ_SOURCES \"${PROXYGEN_FUZZ_TARGET}.cpp\")\n    endif()\n\n    add_executable(${PROXYGEN_FUZZ_TARGET}\n      \"${PROXYGEN_FUZZ_SOURCES}\"\n    )\n    target_link_libraries(${PROXYGEN_FUZZ_TARGET} PRIVATE\n      \"${PROXYGEN_FUZZ_DEPENDS}\"\n    )\n    target_include_directories(${PROXYGEN_FUZZ_TARGET} PRIVATE\n      \"${PROXYGEN_FUZZ_INCLUDES}\"\n    )\n    target_compile_options(${PROXYGEN_FUZZ_TARGET} PRIVATE\n      \"${_PROXYGEN_COMMON_COMPILE_OPTIONS}\"\n    )\n    target_link_libraries(${PROXYGEN_FUZZ_TARGET} PRIVATE\n      ${LIBGMOCK_LIBRARIES_NO_MAIN}\n      ${LIB_FUZZING_ENGINE}\n    )\n    target_include_directories(${PROXYGEN_FUZZ_TARGET} SYSTEM PRIVATE\n      ${LIBGMOCK_INCLUDE_DIR}\n      ${LIBGTEST_INCLUDE_DIR}\n    )\n    target_compile_definitions(${PROXYGEN_FUZZ_TARGET} PRIVATE ${LIBGMOCK_DEFINES})\nendfunction()\n\nproxygen_add_fuzzer(TARGET ProxygenHTTP1xFuzzer\n  SOURCES\n    ProxygenHTTP1xFuzzer.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n)\n"
  },
  {
    "path": "proxygen/httpclient/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif (BUILD_SAMPLES)\n  add_subdirectory(samples)\nendif()\n"
  },
  {
    "path": "proxygen/httpclient/samples/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nadd_subdirectory(curl)\nadd_subdirectory(H3Datagram)\nadd_subdirectory(httperf2)\n"
  },
  {
    "path": "proxygen/httpclient/samples/H3Datagram/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nadd_executable(proxygen_h3datagram_client H3DatagramClient.cpp)\ntarget_compile_options(\n    proxygen_h3datagram_client PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(\n    proxygen_h3datagram_client\n    PUBLIC\n        proxygen\n        proxygencurl\n)\ninstall(\n    TARGETS proxygen_h3datagram_client\n    EXPORT proxygen-exports\n    DESTINATION bin\n)\n"
  },
  {
    "path": "proxygen/httpclient/samples/H3Datagram/H3DatagramClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/SocketAddress.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpclient/samples/curl/CurlClient.h>\n#include <proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h>\n#include <proxygen/lib/transport/ConnectUDPUtils.h>\n#include <proxygen/lib/transport/H3DatagramAsyncSocket.h>\n\nusing namespace folly;\nusing namespace proxygen;\n\nDEFINE_string(host, \"::1\", \"Remote Server hostname/IP\");\nDEFINE_int32(port, 8888, \"Remote Server port\");\n\nDEFINE_string(proxy_host, \"::1\", \"Proxy hostname/IP\");\nDEFINE_int32(proxy_port, 6666, \"Proxy port\");\n\nDEFINE_string(cert, \"\", \"Certificate file path\");\nDEFINE_string(key, \"\", \"Private key file path\");\n\nDEFINE_string(headers, \"\", \"List of N=V headers separated by ,\");\n\nDEFINE_string(\n    masque_template,\n    \"\",\n    \"URI template for RFC 9298 CONNECT-UDP (e.g., \"\n    \"https://proxy:4443/masque?h={target_host}&p={target_port}). \"\n    \"When set, uses RFC 9298 Extended CONNECT instead of legacy CONNECT-UDP.\");\n\nconstexpr size_t kMaxReadBufferSize{1232};\n\nnamespace {\n\n/*\n * This class provides a sample client that speaks HTTP/3 Datagrams.\n * It supports connecting to a remote UDP server through a proxy that supports\n * masque connect-udp.\n *\n * The sample application logic implements the following.\n * The datagram payload contains just an ASCII encoded integer number:\n *  - the client starts by sending a datagram with 0\n *  - on receiving an equally encoded datagram from the server, the client\n *    increment the received number by 1 and sends a new datagram\n *  - the client stops when reaching a maximum configured integer value\n */\nclass DatagramClient\n    : private folly::AsyncUDPSocket::ReadCallback\n    , private folly::AsyncTimeout {\n public:\n  using folly::AsyncUDPSocket::ReadCallback::OnDataAvailableParams;\n\n  ~DatagramClient() override = default;\n\n  explicit DatagramClient(folly::EventBase* evb, H3DatagramAsyncSocket& socket)\n      : folly::AsyncTimeout(evb), evb_(evb), socket_(socket) {\n  }\n\n  void start() {\n    CHECK(evb_->isInEventBaseThread());\n    try {\n      socket_.connect(SocketAddress(FLAGS_proxy_host, FLAGS_proxy_port));\n    } catch (const std::system_error& e) {\n      LOG(ERROR) << \"Failed to connect to \" << FLAGS_proxy_host << \":\"\n                 << FLAGS_proxy_port << \": \" << e.what();\n      return;\n    }\n    socket_.resumeRead(this);\n\n    sendPing();\n  }\n\n  void shutdown() {\n    CHECK(evb_->isInEventBaseThread());\n    socket_.pauseRead();\n    socket_.close();\n    closing_ = true;\n  }\n\n  void sendPing() {\n    if (n_ == 2000) {\n      shutdown();\n      return;\n    }\n\n    scheduleTimeout(1000);\n    writePing(folly::IOBuf::copyBuffer(folly::to<std::string>(n_)));\n  }\n\n  virtual void writePing(std::unique_ptr<folly::IOBuf> buf) {\n    VLOG(2) << \"Writing Datagram\";\n    auto res =\n        socket_.write(SocketAddress(FLAGS_proxy_host, FLAGS_proxy_port), buf);\n    if (res < 0) {\n      LOG(ERROR) << \"Failure to write: errno=\" << errno;\n    }\n  }\n\n  void getReadBuffer(void** buf, size_t* len) noexcept override {\n    *buf = buf_.data();\n    *len = buf_.size();\n  }\n\n  void onDataAvailable(const folly::SocketAddress& client,\n                       size_t len,\n                       bool truncated,\n                       OnDataAvailableParams) noexcept override {\n    ++pongRecvd_;\n    VLOG(4) << \"Read \" << len << \" bytes (trun:\" << truncated << \") from \"\n            << client.describe() << \" - \" << std::string(buf_.data(), len);\n    auto datagramString = std::string(buf_.data(), len);\n    auto datagramInt = folly::tryTo<uint16_t>(datagramString);\n    if (!datagramInt.hasValue()) {\n      VLOG(2) << \"Received Datagram without Integer value. Stopping. len=\"\n              << datagramString.length();\n      return;\n    }\n    VLOG(2) << \"Received Datagram with Integer value (\" << (int)*datagramInt\n            << \")\";\n    if (*datagramInt >= std::numeric_limits<uint16_t>::max()) {\n      VLOG(2) << \"Received Datagram with large Integer value. Stopping\";\n      return;\n    }\n    VLOG(2) << \"Sending Datagram with Integer value (\"\n            << (int)(*datagramInt + 1) << \")\";\n    n_ = (int)(*datagramInt + 1);\n\n    scheduleTimeout(1000);\n  }\n\n  void onReadError(const folly::AsyncSocketException& ex) noexcept override {\n    LOG(ERROR) << ex.what();\n  }\n\n  void onReadClosed() noexcept override {\n    shutdown();\n  }\n\n  void timeoutExpired() noexcept override {\n    LOG(INFO) << \"Timeout expired\";\n    if (!closing_) {\n      sendPing();\n    }\n  }\n\n private:\n  folly::EventBase* const evb_{nullptr};\n  H3DatagramAsyncSocket& socket_;\n  int pongRecvd_{0};\n  int n_{0};\n  std::array<char, kMaxReadBufferSize> buf_;\n  bool closing_{false};\n};\n}; // namespace\n\nint main(int argc, char* argv[]) {\n#if FOLLY_HAVE_LIBGFLAGS\n  // Enable glog logging to stderr by default.\n  folly::gflags::SetCommandLineOptionWithMode(\n      \"logtostderr\", \"1\", folly::gflags::SET_FLAGS_DEFAULT);\n#endif\n  const folly::Init init(&argc, &argv, false);\n\n  EventBase evb;\n\n  H3DatagramAsyncSocket::Options options;\n  options.mode_ = H3DatagramAsyncSocket::Mode::CLIENT;\n  options.txnTimeout_ = std::chrono::milliseconds(10000);\n  options.connectTimeout_ = std::chrono::milliseconds(500);\n  options.httpRequest_ = std::make_unique<HTTPMessage>();\n\n  if (!FLAGS_masque_template.empty()) {\n    // RFC 9298 Extended CONNECT mode\n    auto target = proxygen::expandConnectUDPTemplate(\n        FLAGS_masque_template, FLAGS_host, FLAGS_port);\n    options.httpRequest_->setMethod(proxygen::HTTPMethod::CONNECT);\n    options.httpRequest_->setUpgradeProtocol(\"connect-udp\");\n    options.httpRequest_->setSecure(true);\n    options.httpRequest_->setURL(target.path);\n    options.httpRequest_->getHeaders().set(proxygen::HTTP_HEADER_HOST,\n                                           target.authority);\n    options.httpRequest_->getHeaders().set(\"Capsule-Protocol\", \"?1\");\n    options.rfcMode_ = true;\n  } else {\n    // Legacy CONNECT-UDP mode\n    options.httpRequest_->setMethod(proxygen::HTTPMethod::CONNECT_UDP);\n    options.httpRequest_->setURL(fmt::format(\"{}:{}\", FLAGS_host, FLAGS_port));\n    options.httpRequest_->setMasque();\n  }\n\n  auto parsedHeaders = CurlService::CurlClient::parseHeaders(FLAGS_headers);\n  parsedHeaders.forEach(\n      [&options](const std::string& name, const std::string& value) {\n        options.httpRequest_->getHeaders().add(name, value);\n      });\n  options.certAndKey_ = std::make_pair(FLAGS_cert, FLAGS_key);\n  options.certVerifier_ = std::make_unique<\n      proxygen::InsecureVerifierDangerousDoNotUseInProduction>();\n  options.maxDatagramSize_ = kMaxReadBufferSize;\n\n  H3DatagramAsyncSocket datagramSocket(&evb, options);\n  DatagramClient client(&evb, datagramSocket);\n  client.start();\n  evb.loop();\n\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "proxygen/httpclient/samples/curl/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Manually maintained (generate_cmake.py skips this directory)\n\nproxygen_add_library(proxygencurl\n  EXCLUDE_FROM_MONOLITH\n  SRCS\n    CurlClient.cpp\n  EXPORTED_DEPS\n    proxygen\n)\n\nadd_executable(proxygen_curl CurlClientMain.cpp)\ntarget_compile_options(\n    proxygen_curl PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(\n    proxygen_curl\n    PUBLIC\n        proxygencurl\n)\ninstall(\n    TARGETS proxygen_curl\n    EXPORT proxygen-exports\n    DESTINATION bin\n)\n"
  },
  {
    "path": "proxygen/httpclient/samples/curl/CurlClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"CurlClient.h\"\n\n#include <iostream>\n#include <sys/stat.h>\n#include <utility>\n\n#include <folly/FileUtil.h>\n#include <folly/String.h>\n#include <folly/io/async/SSLContext.h>\n#include <folly/io/async/SSLOptions.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nDECLARE_int32(recv_window);\n\nnamespace CurlService {\n\nCurlClient::CurlClient(EventBase* evb,\n                       HTTPMethod httpMethod,\n                       URL url,\n                       const proxygen::URL* proxy,\n                       const HTTPHeaders& headers,\n                       string inputFilename,\n                       bool h2c,\n                       unsigned short httpMajor,\n                       unsigned short httpMinor)\n    : evb_(evb),\n      httpMethod_(httpMethod),\n      url_(std::move(url)),\n      inputFilename_(std::move(inputFilename)),\n      h2c_(h2c),\n      httpMajor_(httpMajor),\n      httpMinor_(httpMinor) {\n  if (proxy != nullptr) {\n    proxy_ = std::make_unique<URL>(proxy->getUrl());\n  }\n\n  outputStream_ = std::make_unique<std::ostream>(std::cout.rdbuf());\n  headers.forEach([this](const string& header, const string& val) {\n    request_.getHeaders().add(header, val);\n  });\n}\n\nbool CurlClient::saveResponseToFile(const std::string& outputFilename) {\n  std::streambuf* buf;\n  if (outputFilename.empty()) {\n    return false;\n  }\n  uint16_t tries = 0;\n  while (tries < std::numeric_limits<uint16_t>::max()) {\n    std::string suffix = (tries == 0) ? \"\" : folly::to<std::string>(\"_\", tries);\n    auto filename = folly::to<std::string>(outputFilename, suffix);\n    struct stat statBuf;\n    if (stat(filename.c_str(), &statBuf) == -1) {\n      outputFile_ =\n          std::make_unique<ofstream>(filename, ios::out | ios::binary);\n      if (*outputFile_ && outputFile_->good()) {\n        buf = outputFile_->rdbuf();\n        outputStream_ = std::make_unique<std::ostream>(buf);\n        return true;\n      }\n    }\n    tries++;\n  }\n  return false;\n}\n\nHTTPHeaders CurlClient::parseHeaders(const std::string& headersString) {\n  vector<StringPiece> headersList;\n  HTTPHeaders headers;\n  folly::split(',', headersString, headersList);\n  for (const auto& headerPair : headersList) {\n    vector<StringPiece> nv;\n    folly::split('=', headerPair, nv);\n    if (nv.size() > 0) {\n      if (nv[0].empty()) {\n        continue;\n      }\n      std::string value;\n      for (size_t i = 1; i < nv.size(); i++) {\n        value += folly::to<std::string>(nv[i], '=');\n      }\n      if (nv.size() > 1) {\n        value.pop_back();\n      } // trim anything else\n      headers.add(nv[0], value);\n    }\n  }\n  return headers;\n}\n\nvoid CurlClient::initializeSsl(const string& caPath,\n                               const string& nextProtos,\n                               const string& certPath,\n                               const string& keyPath) {\n  sslContext_ = std::make_shared<folly::SSLContext>();\n  sslContext_->setOptions(SSL_OP_NO_COMPRESSION);\n  folly::ssl::setCipherSuites<folly::ssl::SSLCommonOptions>(*sslContext_);\n  if (!caPath.empty()) {\n    sslContext_->loadTrustedCertificates(caPath.c_str());\n  }\n  if (!certPath.empty() && !keyPath.empty()) {\n    sslContext_->loadCertKeyPairFromFiles(certPath.c_str(), keyPath.c_str());\n  }\n  list<string> nextProtoList;\n  folly::splitTo<string>(\n      ',', nextProtos, std::inserter(nextProtoList, nextProtoList.begin()));\n  sslContext_->setAdvertisedNextProtocols(nextProtoList);\n  h2c_ = false;\n}\n\nvoid CurlClient::sslHandshakeFollowup(HTTPUpstreamSession* session) noexcept {\n  auto* sslSocket = dynamic_cast<AsyncSSLSocket*>(session->getTransport());\n\n  const unsigned char* nextProto = nullptr;\n  unsigned nextProtoLength = 0;\n  sslSocket->getSelectedNextProtocol(&nextProto, &nextProtoLength);\n  if (nextProto) {\n    VLOG(1) << \"Client selected next protocol \"\n            << string((const char*)nextProto, nextProtoLength);\n  } else {\n    VLOG(1) << \"Client did not select a next protocol\";\n  }\n\n  // Note: This ssl session can be used by defining a member and setting\n  // something like sslSession_ = sslSocket->getSSLSession() and then\n  // passing it to the connector::connectSSL() method\n}\n\nvoid CurlClient::setFlowControlSettings(int32_t recvWindow) {\n  recvWindow_ = recvWindow;\n}\n\nvoid CurlClient::connectSuccess(HTTPUpstreamSession* session) {\n\n  if (url_.isSecure()) {\n    sslHandshakeFollowup(session);\n  }\n\n  session->setFlowControl(recvWindow_, recvWindow_, recvWindow_);\n  sendRequest(session->newTransaction(this));\n  session->closeWhenIdle();\n}\n\nvoid CurlClient::setupHeaders() {\n  request_.setMethod(httpMethod_);\n  request_.setHTTPVersion(httpMajor_, httpMinor_);\n  if (proxy_) {\n    request_.setURL(url_.getUrl());\n  } else {\n    request_.setURL(url_.makeRelativeURL());\n  }\n  request_.setSecure(url_.isSecure());\n\n  if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_USER_AGENT)) {\n    request_.getHeaders().add(HTTP_HEADER_USER_AGENT, \"proxygen_curl\");\n  }\n  if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_HOST)) {\n    request_.getHeaders().add(HTTP_HEADER_HOST, url_.getHostAndPort());\n  }\n  if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_ACCEPT)) {\n    request_.getHeaders().add(\"Accept\", \"*/*\");\n  }\n  if (loggingEnabled_) {\n    request_.dumpMessage(4);\n  }\n}\n\nvoid CurlClient::sendRequest(HTTPTransaction* txn) {\n  LOG_IF(INFO, loggingEnabled_)\n      << fmt::format(\"Sending request for {}\", url_.getUrl());\n  txn_ = txn;\n  setupHeaders();\n  txnStartTime_ = std::chrono::steady_clock::now();\n  txn_->sendHeaders(request_);\n\n  if (httpMethod_ == HTTPMethod::POST) {\n    inputFile_ =\n        std::make_unique<ifstream>(inputFilename_, ios::in | ios::binary);\n    sendBodyFromFile();\n  } else {\n    txn_->sendEOM();\n  }\n}\n\nvoid CurlClient::sendBodyFromFile() {\n  const uint16_t kReadSize = 4096;\n  CHECK(inputFile_);\n  // Reading from the file by chunks\n  // Important note: It's pretty bad to call a blocking i/o function like\n  // ifstream::read() in an eventloop - but for the sake of this simple\n  // example, we'll do it.\n  // An alternative would be to put this into some folly::AsyncReader\n  // object.\n  while (inputFile_->good() && !egressPaused_) {\n    unique_ptr<IOBuf> buf = IOBuf::createCombined(kReadSize);\n    inputFile_->read((char*)buf->writableData(), kReadSize);\n    buf->append(inputFile_->gcount());\n    txn_->sendBody(std::move(buf));\n  }\n  if (!egressPaused_) {\n    txn_->sendEOM();\n  }\n}\n\nvoid CurlClient::printMessageImpl(proxygen::HTTPMessage* msg,\n                                  const std::string& tag) {\n  if (!loggingEnabled_) {\n    return;\n  }\n  cout << tag;\n  msg->dumpMessage(10);\n}\n\nvoid CurlClient::connectError(const folly::AsyncSocketException& ex) {\n  LOG_IF(ERROR, loggingEnabled_)\n      << \"Coudln't connect to \" << url_.getHostAndPort() << \":\" << ex.what();\n}\n\nvoid CurlClient::setTransaction(HTTPTransaction*) noexcept {\n}\n\nvoid CurlClient::detachTransaction() noexcept {\n}\n\nvoid CurlClient::onHeadersComplete(unique_ptr<HTTPMessage> msg) noexcept {\n  response_ = std::move(msg);\n  printMessageImpl(response_.get());\n  if (!headersLoggingEnabled_) {\n    return;\n  }\n  response_->describe(*outputStream_);\n  *outputStream_ << std::endl;\n}\n\nvoid CurlClient::onBody(std::unique_ptr<folly::IOBuf> chain) noexcept {\n  if (onBodyFunc_ && chain) {\n    onBodyFunc_.value()(request_, chain.get());\n  }\n  if (!loggingEnabled_) {\n    return;\n  }\n  CHECK(outputStream_);\n  if (chain) {\n    const IOBuf* p = chain.get();\n    do {\n      outputStream_->write((const char*)p->data(), p->length());\n      outputStream_->flush();\n      p = p->next();\n    } while (p != chain.get());\n  }\n}\n\nvoid CurlClient::onTrailers(std::unique_ptr<HTTPHeaders>) noexcept {\n  LOG_IF(INFO, loggingEnabled_) << \"Discarding trailers\";\n}\n\nvoid CurlClient::onEOM() noexcept {\n  LOG_IF(INFO, loggingEnabled_)\n      << fmt::format(\"Got EOM for {}. Txn Time= {} ms\",\n                     url_.getUrl(),\n                     std::chrono::duration_cast<std::chrono::milliseconds>(\n                         std::chrono::steady_clock::now() - txnStartTime_)\n                         .count());\n  if (eomFunc_) {\n    eomFunc_.value()();\n  }\n}\n\nvoid CurlClient::onUpgrade(UpgradeProtocol) noexcept {\n  LOG_IF(INFO, loggingEnabled_) << \"Discarding upgrade protocol\";\n}\n\nvoid CurlClient::onError(const HTTPException& error) noexcept {\n  LOG_IF(ERROR, loggingEnabled_) << \"An error occurred: \" << error.what();\n}\n\nvoid CurlClient::onEgressPaused() noexcept {\n  VLOG_IF(1, loggingEnabled_) << \"Egress paused\";\n  egressPaused_ = true;\n}\n\nvoid CurlClient::onEgressResumed() noexcept {\n  VLOG_IF(1, loggingEnabled_) << \"Egress resumed\";\n  egressPaused_ = false;\n  if (inputFile_) {\n    sendBodyFromFile();\n  }\n}\n\nvoid CurlClient::onPushedTransaction(\n    proxygen::HTTPTransaction* pushedTxn) noexcept {\n  //\n  pushTxnHandlers_.emplace_back(std::make_unique<CurlPushHandler>(this));\n  pushedTxn->setHandler(pushTxnHandlers_.back().get());\n  // Add implementation of the push transaction reception here\n}\n\nconst string& CurlClient::getServerName() const {\n  const string& res = request_.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST);\n  if (res.empty()) {\n    return url_.getHost();\n  }\n  return res;\n}\n\n// CurlPushHandler methods\nvoid CurlClient::CurlPushHandler::setTransaction(\n    proxygen::HTTPTransaction* txn) noexcept {\n  LOG_IF(INFO, parent_->loggingEnabled_) << \"Received pushed transaction\";\n  pushedTxn_ = txn;\n}\n\nvoid CurlClient::CurlPushHandler::detachTransaction() noexcept {\n  LOG_IF(INFO, parent_->loggingEnabled_) << \"Detached pushed transaction\";\n}\n\nvoid CurlClient::CurlPushHandler::onHeadersComplete(\n    std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n  if (!seenOnHeadersComplete_) {\n    seenOnHeadersComplete_ = true;\n    promise_ = std::move(msg);\n    parent_->printMessageImpl(promise_.get(), \"[PP] \");\n  } else {\n    response_ = std::move(msg);\n    parent_->printMessageImpl(response_.get(), \"[PR] \");\n  }\n}\n\nvoid CurlClient::CurlPushHandler::onBody(\n    std::unique_ptr<folly::IOBuf> chain) noexcept {\n  parent_->onBody(std::move(chain));\n}\n\nvoid CurlClient::CurlPushHandler::onEOM() noexcept {\n  LOG_IF(INFO, parent_->loggingEnabled_) << \"Got PushTxn EOM\";\n}\n\nvoid CurlClient::CurlPushHandler::onError(\n    const proxygen::HTTPException& error) noexcept {\n  parent_->onError(error);\n}\n\n} // namespace CurlService\n"
  },
  {
    "path": "proxygen/httpclient/samples/curl/CurlClient.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/SSLContext.h>\n#include <fstream>\n#include <proxygen/lib/http/HTTPConnector.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/utils/URL.h>\n\nnamespace CurlService {\n\nclass CurlClient\n    : public proxygen::HTTPConnector::Callback\n    , public proxygen::HTTPTransactionHandler {\n\n  class CurlPushHandler : public proxygen::HTTPTransactionHandler {\n\n   public:\n    explicit CurlPushHandler(CurlClient* parent) : parent_{parent} {\n    }\n\n    void setTransaction(proxygen::HTTPTransaction* /*txn*/) noexcept override;\n\n    void detachTransaction() noexcept override;\n\n    void onHeadersComplete(\n        std::unique_ptr<proxygen::HTTPMessage> /*msg*/) noexcept override;\n\n    void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override;\n\n    void onEOM() noexcept override;\n\n    void onError(const proxygen::HTTPException& /*error*/) noexcept override;\n\n    void onTrailers(\n        std::unique_ptr<proxygen::HTTPHeaders> /*trailers*/) noexcept override {\n    }\n\n    void onUpgrade(proxygen::UpgradeProtocol /*protocol*/) noexcept override {\n    }\n\n    void onEgressResumed() noexcept override {\n    }\n\n    void onEgressPaused() noexcept override {\n    }\n\n   private:\n    // hack around the ambiguous API\n    bool seenOnHeadersComplete_{false};\n    // the pushed transaction\n    proxygen::HTTPTransaction* pushedTxn_{nullptr};\n    // information about the request\n\n    std::unique_ptr<proxygen::HTTPMessage> promise_;\n    std::unique_ptr<proxygen::HTTPMessage> response_;\n    CurlClient* parent_;\n  };\n\n public:\n  CurlClient(folly::EventBase* evb,\n             proxygen::HTTPMethod httpMethod,\n             proxygen::URL url,\n             const proxygen::URL* proxy,\n             const proxygen::HTTPHeaders& headers,\n             std::string inputFilename,\n             bool h2c = false,\n             unsigned short httpMajor = 1,\n             unsigned short httpMinor = 1);\n\n  ~CurlClient() override = default;\n\n  bool saveResponseToFile(const std::string& outputFilename);\n\n  static proxygen::HTTPHeaders parseHeaders(const std::string& headersString);\n\n  // initial SSL related structures\n  void initializeSsl(const std::string& caPath,\n                     const std::string& nextProtos,\n                     const std::string& certPath = \"\",\n                     const std::string& keyPath = \"\");\n  void sslHandshakeFollowup(proxygen::HTTPUpstreamSession* session) noexcept;\n\n  // HTTPConnector methods\n  void connectSuccess(proxygen::HTTPUpstreamSession* session) override;\n  void connectError(const folly::AsyncSocketException& ex) override;\n\n  // HTTPTransactionHandler methods\n  void setTransaction(proxygen::HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(\n      std::unique_ptr<proxygen::HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(proxygen::UpgradeProtocol protocol) noexcept override;\n  void onError(const proxygen::HTTPException& error) noexcept override;\n  void onEgressPaused() noexcept override;\n  void onEgressResumed() noexcept override;\n  void onPushedTransaction(\n      proxygen::HTTPTransaction* /* pushedTxn */) noexcept override;\n\n  void sendRequest(proxygen::HTTPTransaction* txn);\n\n  // Getters\n  folly::SSLContextPtr getSSLContext() {\n    return sslContext_;\n  }\n\n  const std::string& getServerName() const;\n\n  void setFlowControlSettings(int32_t recvWindow);\n\n  const proxygen::HTTPMessage* getResponse() const {\n    return response_.get();\n  }\n\n  void setLogging(bool enabled) {\n    loggingEnabled_ = enabled;\n  }\n\n  void setHeadersLogging(bool enabled) {\n    headersLoggingEnabled_ = enabled;\n  }\n\n  void setEOMFunc(std::function<void()> eomFunc) {\n    eomFunc_ = eomFunc;\n  }\n\n  void setOnBodyFunc(\n      const std::function<void(const proxygen::HTTPMessage& request,\n                               const folly::IOBuf* chainBuf)>& onBodyFunc) {\n    onBodyFunc_ = onBodyFunc;\n  }\n\n protected:\n  void sendBodyFromFile();\n\n  void setupHeaders();\n\n  void printMessageImpl(proxygen::HTTPMessage* msg,\n                        const std::string& tag = \"\");\n\n  proxygen::HTTPTransaction* txn_{nullptr};\n  folly::EventBase* evb_{nullptr};\n  proxygen::HTTPMethod httpMethod_;\n  proxygen::URL url_;\n  std::unique_ptr<proxygen::URL> proxy_;\n  proxygen::HTTPMessage request_;\n  const std::string inputFilename_;\n  folly::SSLContextPtr sslContext_;\n  int32_t recvWindow_{0};\n  bool loggingEnabled_{true};\n  bool headersLoggingEnabled_{false};\n  bool h2c_{false};\n  unsigned short httpMajor_;\n  unsigned short httpMinor_;\n  bool egressPaused_{false};\n  std::unique_ptr<std::ifstream> inputFile_;\n  std::unique_ptr<std::ofstream> outputFile_;\n  std::unique_ptr<std::ostream> outputStream_;\n\n  std::unique_ptr<proxygen::HTTPMessage> response_;\n  std::vector<std::unique_ptr<CurlPushHandler>> pushTxnHandlers_;\n  std::chrono::time_point<std::chrono::steady_clock> txnStartTime_;\n\n  folly::Optional<std::function<void()>> eomFunc_;\n\n  folly::Optional<std::function<void(const proxygen::HTTPMessage& request,\n                                     const folly::IOBuf* chainBuf)>>\n      onBodyFunc_;\n\n  friend class CurlPushHandler;\n};\n\n} // namespace CurlService\n"
  },
  {
    "path": "proxygen/httpclient/samples/curl/CurlClientMain.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/SocketAddress.h>\n#include <folly/init/Init.h>\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/SSLContext.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpclient/samples/curl/CurlClient.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n\nusing namespace CurlService;\nusing namespace folly;\nusing namespace proxygen;\n\nDEFINE_string(http_method,\n              \"GET\",\n              \"HTTP method to use. GET or POST are supported\");\nDEFINE_string(url,\n              \"https://github.com/facebook/proxygen\",\n              \"URL to perform the HTTP method against\");\nDEFINE_string(input_filename, \"\", \"Filename to read from for POST requests\");\nDEFINE_int32(http_client_connect_timeout,\n             1000,\n             \"connect timeout in milliseconds\");\nDEFINE_int32(http_client_request_timeout,\n             5000,\n             \"request timeout in milliseconds\");\nDEFINE_string(ca_path,\n              \"/etc/ssl/certs/ca-certificates.crt\",\n              \"Path to trusted CA file\"); // default for Ubuntu 14.04\nDEFINE_string(cert_path, \"\", \"Path to client certificate file\");\nDEFINE_string(key_path, \"\", \"Path to client private key file\");\nDEFINE_string(next_protos,\n              \"h2,h2-14,spdy/3.1,spdy/3,http/1.1\",\n              \"Next protocol string for NPN/ALPN\");\nDEFINE_string(plaintext_proto, \"\", \"plaintext protocol\");\nDEFINE_int32(recv_window, 65536, \"Flow control receive window for h2/spdy\");\nDEFINE_bool(h2c, true, \"Attempt HTTP/1.1 -> HTTP/2 upgrade\");\nDEFINE_string(headers, \"\", \"List of N=V headers separated by ,\");\nDEFINE_string(proxy, \"\", \"HTTP proxy URL\");\nDEFINE_bool(log_response,\n            true,\n            \"Whether to log the response content to stderr\");\n\nint main(int argc, char* argv[]) {\n#if FOLLY_HAVE_LIBGFLAGS\n  // Enable glog logging to stderr by default.\n  folly::gflags::SetCommandLineOptionWithMode(\n      \"logtostderr\", \"1\", folly::gflags::SET_FLAGS_DEFAULT);\n#endif\n  auto _ = folly::Init(&argc, &argv, false);\n\n  EventBase evb;\n  URL url(FLAGS_url);\n  URL proxy(FLAGS_proxy);\n\n  auto httpMethod = stringToMethod(FLAGS_http_method);\n  if (!httpMethod) {\n    LOG(ERROR) << \"Unsupported http_method=\" << FLAGS_http_method;\n    return EXIT_FAILURE;\n  }\n\n  if (*httpMethod == HTTPMethod::POST) {\n    try {\n      File f(FLAGS_input_filename);\n      (void)f;\n    } catch (const std::system_error& se) {\n      LOG(ERROR) << \"Couldn't open file for POST method\";\n      LOG(ERROR) << se.what();\n      return EXIT_FAILURE;\n    }\n  }\n\n  HTTPHeaders headers = CurlClient::parseHeaders(FLAGS_headers);\n\n  CurlClient curlClient(&evb,\n                        *httpMethod,\n                        url,\n                        FLAGS_proxy.empty() ? nullptr : &proxy,\n                        headers,\n                        FLAGS_input_filename,\n                        FLAGS_h2c);\n  curlClient.setFlowControlSettings(FLAGS_recv_window);\n  curlClient.setLogging(FLAGS_log_response);\n  curlClient.setHeadersLogging(FLAGS_log_response);\n\n  SocketAddress addr;\n  if (!FLAGS_proxy.empty()) {\n    addr = SocketAddress(proxy.getHost(), proxy.getPort(), true);\n  } else {\n    addr = SocketAddress(url.getHost(), url.getPort(), true);\n  }\n  LOG(INFO) << \"Trying to connect to \" << addr;\n\n  HTTPConnector connector(\n      &curlClient,\n      WheelTimerInstance(\n          std::chrono::milliseconds(FLAGS_http_client_request_timeout), &evb));\n  if (!FLAGS_plaintext_proto.empty()) {\n    connector.setPlaintextProtocol(FLAGS_plaintext_proto);\n  }\n  static const SocketOptionMap opts{\n      {{.level = SOL_SOCKET, .optname = SO_REUSEADDR}, 1}};\n\n  if (url.isSecure()) {\n    curlClient.initializeSsl(\n        FLAGS_ca_path, FLAGS_next_protos, FLAGS_cert_path, FLAGS_key_path);\n    connector.connectSSL(\n        &evb,\n        addr,\n        curlClient.getSSLContext(),\n        nullptr,\n        std::chrono::milliseconds(FLAGS_http_client_connect_timeout),\n        opts,\n        folly::AsyncSocket::anyAddress(),\n        curlClient.getServerName());\n  } else {\n    connector.connect(\n        &evb,\n        addr,\n        std::chrono::milliseconds(FLAGS_http_client_connect_timeout),\n        opts);\n  }\n\n  evb.loop();\n\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Manually maintained (generate_cmake.py skips this directory)\n\nproxygen_add_library(libhttperf2\n  EXCLUDE_FROM_MONOLITH\n  SRCS\n    HTTPerf2.cpp\n    Client.cpp\n  EXPORTED_DEPS\n    proxygenhqloggerhelper\n    proxygen\n)\n\nadd_executable(proxygen_httperf2 Main.cpp)\ntarget_link_libraries(\n    proxygen_httperf2 PUBLIC\n    libhttperf2\n)\ntarget_compile_options(\n    proxygen_httperf2 PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\n\ninstall(\n    TARGETS proxygen_httperf2\n    EXPORT proxygen-exports\n    DESTINATION bin\n)\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/Client.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpclient/samples/httperf2/Client.h>\n\n#include <boost/cast.hpp>\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <folly/FileUtil.h>\n#include <folly/io/SocketOptionMap.h>\n#include <folly/portability/OpenSSL.h>\n#include <proxygen/httpserver/samples/hq/HQLoggerHelper.h>\n#include <proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h>\n#include <quic/QuicConstants.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nDEFINE_int32(max_outstanding_transactions,\n             6,\n             \"Maximum allowed in-flight transactions per HTTP session\");\nDEFINE_int32(req_per_loop,\n             100,\n             \"Maximum number of requests to send per event loop\");\nDEFINE_int32(connect_timeout, 120, \"Connect timeout in ms\");\nDEFINE_int32(conn_flow_control,\n             proxygen::http2::kInitialWindow,\n             \"Connection flow control\");\nDEFINE_int32(stream_flow_control,\n             proxygen::http2::kInitialWindow,\n             \"Stream flow control\");\nDEFINE_string(congestion,\n              \"cubic\",\n              \"Congestion control algorithm, cubic/newreno/bbr/none\");\nDEFINE_int32(max_receive_packet_size,\n             quic::kDefaultUDPReadBufferSize,\n             \"Max UDP packet size Quic can receive\");\nDEFINE_bool(\n    can_ignore_pmtu,\n    false,\n    \"We can ignore PTMU for the transport and use advertised recv buffer\");\nDEFINE_bool(udp_connect, true, \"Whether or not to connect the udp socket\");\nDEFINE_bool(pacing, false, \"Whether to enable pacing in Quic\");\nDEFINE_uint32(quic_batching_mode,\n              static_cast<uint32_t>(quic::QuicBatchingMode::BATCHING_MODE_GSO),\n              \"QUIC batching mode\");\nDEFINE_uint32(quic_batch_size,\n              quic::kDefaultQuicMaxBatchSize,\n              \"Maximum number of packets that can be batchedi in Quic\");\nDEFINE_bool(early_data, false, \"Enable Fizz early data\");\nDEFINE_uint32(quic_recv_batch_size,\n              16,\n              \"Number of packets to receiver per loop.\");\n\nstatic std::atomic_bool exitAllClientsSoon{false};\n\nvoid Client::exitAllSoon() {\n  exitAllClientsSoon = true;\n}\n\nClient::Client(EventBase* eventBase,\n               const WheelTimerInstance& transactionTimeouts,\n               HTTPerfStats& stats,\n               Optional<SocketAddress> bindAddr,\n               const SocketAddress& address,\n               HTTPMessage& request,\n               const std::string& requestData,\n               uint32_t n_requests,\n               FinishedCallback* callback,\n               const string& plaintextProto,\n               const string& serverName)\n    : eventBase_(eventBase),\n      stats_(stats),\n      requestsSent_(0),\n      bindAddr_(std::move(bindAddr)),\n      address_(address),\n      sslSession_(nullptr),\n      request_(request),\n      requestData_(requestData),\n      requests_(n_requests),\n      callback_(callback),\n      connector_(this, transactionTimeouts),\n      serverName_(serverName),\n      plaintextProto_(plaintextProto) {\n  CHECK_GT(requests_, 0);\n  connector_.setPlaintextProtocol(plaintextProto);\n}\n\nClient::~Client() {\n  if (isLoopCallbackScheduled()) {\n    cancelLoopCallback();\n  }\n  inDestructor_ = true;\n  collector_.stopCallbacks();\n  if (session_) {\n    // drain() might cause synchronous destruction, protect ourselves\n    // from it.\n    DelayedDestructionBase::DestructorGuard guard(session_);\n    VLOG(4) << \"shutting down session\";\n    session_->dropConnection(\"shutting down\");\n    // The above will destroy session_ and kill all the transactions\n    // But we don't need any callbacks because we're toast\n    session_->setInfoCallback(nullptr);\n    session_ = nullptr;\n  }\n}\n\nvoid Client::start() {\n  connect();\n}\n\nvoid Client::exit() {\n  if (callback_ != nullptr) {\n    FinishedCallback* callback = callback_;\n    callback_ = nullptr;\n    callback->clientFinished(this);\n  }\n}\n\nvoid Client::setSSLParameters(const folly::SSLContextPtr& sslContext,\n                              std::shared_ptr<folly::ssl::SSLSession> session) {\n  sslContext_ = sslContext;\n  sslSession_ = session;\n  if (sslSession_ != nullptr) {\n    shouldReuseSession_ = true;\n  }\n}\n\nvoid Client::setupFizzContext(std::shared_ptr<fizz::client::PskCache> pskCache,\n                              bool pskKe,\n                              const std::string& cert,\n                              const std::string& key) {\n  fizzContext_ = std::make_shared<fizz::client::FizzClientContext>();\n  fizzContext_->setSupportedAlpns({plaintextProto_});\n  if (FLAGS_early_data) {\n    fizzContext_->setSendEarlyData(true);\n  }\n\n  if (pskKe) {\n    fizzContext_->setSupportedPskModes({fizz::PskKeyExchangeMode::psk_ke});\n  }\n\n  fizzContext_->setPskCache(std::move(pskCache));\n  if (!cert.empty()) {\n    std::string certData;\n    std::string keyData;\n    folly::readFile(cert.c_str(), certData);\n    if (!key.empty()) {\n      folly::readFile(key.c_str(), keyData);\n    }\n    auto selfCert = fizz::openssl::CertUtils::makeSelfCert(certData, keyData);\n    auto certMgr = std::make_shared<fizz::client::CertManager>();\n    certMgr->addCert(std::move(selfCert));\n    fizzContext_->setClientCertManager(std::move(certMgr));\n  }\n}\n\nvoid Client::setQuicPskCache(std::shared_ptr<quic::QuicPskCache> quicPskCache) {\n  quicPskCache_ = std::move(quicPskCache);\n}\n\nvoid Client::setUseQuic(bool useQuic) {\n  useQuic_ = useQuic;\n}\n\nbool Client::supportsTickets() const {\n  return (sslContext_ &&\n          !(SSL_CTX_get_options(sslContext_->getSSLCtx()) & SSL_OP_NO_TICKET));\n}\n\nstd::shared_ptr<folly::ssl::SSLSession> Client::extractSSLSession() {\n  auto session = std::move(sslSession_);\n  sslSession_ = nullptr;\n  return session;\n}\n\nstatic quic::TransportSettings createTransportSettings() {\n  quic::TransportSettings transportSettings;\n  transportSettings.attemptEarlyData = FLAGS_early_data;\n  transportSettings.pacingEnabled = FLAGS_pacing;\n  if (folly::to<uint16_t>(FLAGS_max_receive_packet_size) <\n      quic::kDefaultUDPSendPacketLen) {\n    throw std::invalid_argument(\n        folly::to<std::string>(\"max_receive_packet_size needs to be at least \",\n                               quic::kDefaultUDPSendPacketLen));\n  }\n  transportSettings.maxRecvPacketSize = FLAGS_max_receive_packet_size;\n  transportSettings.canIgnorePathMTU = FLAGS_can_ignore_pmtu;\n  if (FLAGS_conn_flow_control != 0) {\n    transportSettings.advertisedInitialConnectionFlowControlWindow =\n        FLAGS_conn_flow_control;\n  }\n  // TODO FLAGS_stream_*\n  if (FLAGS_stream_flow_control != 0) {\n    transportSettings.advertisedInitialBidiLocalStreamFlowControlWindow =\n        FLAGS_stream_flow_control;\n    transportSettings.advertisedInitialBidiRemoteStreamFlowControlWindow =\n        FLAGS_stream_flow_control;\n    transportSettings.advertisedInitialUniStreamFlowControlWindow =\n        FLAGS_stream_flow_control;\n  }\n\n  auto ccType = quic::congestionControlStrToType(FLAGS_congestion);\n  if (!ccType) {\n    throw std::invalid_argument(folly::to<std::string>(\n        \"invalid congestion control \", FLAGS_congestion));\n  }\n  transportSettings.defaultCongestionController = *ccType;\n  transportSettings.batchingMode =\n      quic::getQuicBatchingMode(FLAGS_quic_batching_mode);\n  transportSettings.maxBatchSize = FLAGS_quic_batch_size;\n  transportSettings.shouldUseRecvmmsgForBatchRecv = true;\n  transportSettings.maxRecvBatchSize = FLAGS_quic_recv_batch_size;\n  transportSettings.connectUDP = FLAGS_udp_connect;\n  transportSettings.shouldDrain = false;\n  return transportSettings;\n}\n\n///////// Connection Setup\n\nvoid Client::connect() {\n  connector_.reset();\n  start_ = std::chrono::steady_clock::now();\n\n  static const SocketOptionMap opts{\n      {{.level = SOL_SOCKET, .optname = SO_REUSEADDR}, 1}};\n\n  if (sslContext_) {\n    connector_.connectSSL(eventBase_,\n                          address_,\n                          sslContext_,\n                          shouldReuseSession_ ? sslSession_ : nullptr,\n                          std::chrono::milliseconds(FLAGS_connect_timeout),\n                          opts);\n  } else if (fizzContext_ && !useQuic_) {\n    connector_.connectFizz(\n        eventBase_,\n        address_,\n        fizzContext_,\n        std::make_shared<\n            proxygen::InsecureVerifierDangerousDoNotUseInProduction>(),\n        std::chrono::milliseconds(FLAGS_connect_timeout),\n        std::chrono::milliseconds(0),\n        opts,\n        folly::AsyncSocket::anyAddress(),\n        serverName_);\n  } else if (fizzContext_ && useQuic_) {\n    hqConnector_ = std::make_unique<HQConnector>(\n        this, std::chrono::milliseconds(FLAGS_connect_timeout));\n    hqConnector_->setTransportSettings(createTransportSettings());\n    hqConnector_->setQuicPskCache(quicPskCache_);\n    hqConnector_->connect(\n        eventBase_,\n        bindAddr_,\n        address_,\n        fizzContext_,\n        std::make_shared<\n            proxygen::InsecureVerifierDangerousDoNotUseInProduction>(),\n        std::chrono::milliseconds(FLAGS_connect_timeout),\n        folly::emptySocketOptionMap,\n        serverName_,\n        qlogger_);\n  } else {\n    connector_.connect(eventBase_,\n                       address_,\n                       std::chrono::milliseconds(FLAGS_connect_timeout),\n                       opts);\n  }\n}\n\n///////// Request logic\n\nbool Client::shouldExit() const {\n  return exitAllClientsSoon ||\n         (requestsSent_ >= requests_ && outstandingTransactions_ == 0);\n}\n\nvoid Client::runLoopCallback() noexcept {\n  if (session_) {\n    sendRequest();\n  } else if (shouldExit()) {\n    exit();\n  } else {\n    connect();\n  }\n}\n\nvoid Client::sendRequest() {\n  if (shouldExit()) {\n    // TODO: should track responses + errors before calling finished\n    exit();\n    return;\n  }\n  if (isConnecting()) {\n    // We are in the middle of the connect process. The request will\n    // happen once the connect succeeds.\n    return;\n  }\n  CHECK(session_);\n  uint32_t requestsThisLoop = 0;\n  while ((outstandingTransactions_ <\n          uint32_t(FLAGS_max_outstanding_transactions)) &&\n         (requestsSent_ < requests_) &&\n         (requestsThisLoop++ < uint32_t(FLAGS_req_per_loop))) {\n    auto handler = new TransactionHandler(this);\n    auto txn = session_->newTransaction(handler);\n    if (!txn) {\n      // The session doesn't support any more transactions\n      delete handler;\n      break;\n    }\n    outstandingTransactions_++;\n    requestsSent_++;\n    stats_.addRequest();\n    if (requestsSent_ == requests_ && useQuic_) {\n      // We need to drain before we send the last request\n      // in quic due to the way h1q works.\n      session_->drain();\n    }\n    txn->sendHeaders(request_);\n    if (!requestData_.empty()) {\n      auto data =\n          folly::IOBuf::wrapBuffer(requestData_.data(), requestData_.length());\n      txn->sendBody(std::move(data));\n    }\n    txn->sendEOM();\n  }\n  if ((outstandingTransactions_ <\n       uint32_t(FLAGS_max_outstanding_transactions)) &&\n      (requestsSent_ < requests_)) {\n    eventBase_->runInLoop(this);\n  }\n}\n\nvoid Client::connectSuccess(proxygen::HQUpstreamSession* session) {\n  CHECK(!session_);\n  const auto transport = session->getQuicSocket();\n  auto client =\n      CHECK_NOTNULL(dynamic_cast<const quic::QuicClientTransport*>(transport));\n  if (client->isTLSResumed()) {\n    stats_.addResume();\n  } else {\n    stats_.addHandshake();\n  }\n  connectSuccessCommon(session);\n}\n\nvoid Client::connectError(const quic::QuicErrorCode&) {\n  connectErrorCommon();\n}\n\nvoid Client::connectSuccess(HTTPUpstreamSession* session) {\n  CHECK(!session_);\n  session->setByteEventTracker(nullptr);\n  if (sslContext_) {\n    auto sslSocket = dynamic_cast<AsyncSSLSocket*>(session->getTransport());\n    const unsigned char* nextProto = nullptr;\n    unsigned nextProtoLength = 0;\n    sslSocket->getSelectedNextProtocol(&nextProto, &nextProtoLength);\n    if (nextProto) {\n      VLOG(4) << \"Client selected next protocol \"\n              << string((const char*)nextProto, nextProtoLength);\n    } else {\n      VLOG(4) << \"Client did not select a next protocol\";\n    }\n    if (sslSocket->getSSLSessionReused()) {\n      stats_.addResume();\n    } else {\n      sslSession_ = sslSocket->getSSLSession();\n      stats_.addHandshake();\n    }\n  }\n  auto asyncSock =\n      session->getTransport()->getUnderlyingTransport<AsyncSocket>();\n  if (asyncSock) {\n    if (asyncSock->getNetworkSocket().toFd() >= 0) {\n      // Set linger timeout to 0 to avoid sockets in time wait state.\n      struct linger optLinger = {.l_onoff = 1, .l_linger = 0};\n      asyncSock->setSockOpt(SOL_SOCKET, SO_LINGER, &optLinger);\n    }\n  }\n  connectSuccessCommon(session);\n}\n\nvoid Client::connectError(const AsyncSocketException& /*ex*/) {\n  connectErrorCommon();\n}\n\nvoid Client::connectSuccessCommon(HTTPSessionBase* session) {\n  CHECK(!session_);\n  session_ = session;\n  session_->setInfoCallback(&collector_);\n  session->setFlowControl(FLAGS_stream_flow_control,\n                          FLAGS_stream_flow_control,\n                          FLAGS_conn_flow_control);\n  // Don't artificially limit streams\n  session->setMaxConcurrentOutgoingStreams(\n      std::numeric_limits<uint32_t>::max());\n  end_ = std::chrono::steady_clock::now();\n  stats_.addConnection(millisecondsBetween(end_, start_).count());\n  sendRequest();\n}\n\nvoid Client::connectErrorCommon() {\n  stats_.addConnectError();\n  requestsSent_++;\n  if (shouldExit()) {\n    exit();\n  } else {\n    eventBase_->runInLoop(this);\n  }\n}\n\n// HTTPTransaction::Handler callbacks\n\nvoid Client::TransactionHandler::detachTransaction() noexcept {\n  DCHECK(!waitingForResponse_);\n  DCHECK(!inMessage_);\n  if (!parent_->inDestructor_) {\n    DCHECK_GT(parent_->outstandingTransactions_, 0);\n    parent_->outstandingTransactions_--;\n    VLOG(3) << __func__ << \" requestsSent=\" << parent_->requestsSent_\n            << \" requests=\" << parent_->requests_\n            << \" outstanding=\" << parent_->outstandingTransactions_;\n    if (!parent_->isLoopCallbackScheduled()) {\n      parent_->Client::eventBase_->runInLoop(parent_);\n    }\n  }\n  delete this;\n}\n\nvoid Client::TransactionHandler::onHeadersComplete(\n    unique_ptr<HTTPMessage> msg) noexcept {\n  parent_->stats_.addResponseCode(msg->getStatusCode());\n  // TODO: the below updates happen rather late. We would ideally set this\n  // in a onMessageBegin() callback, but HTTPTransaction::Handler has no\n  // such callback\n  waitingForResponse_ = false;\n  inMessage_ = true;\n  // TODO: should count (approximate) header bytes received also\n}\n\nvoid Client::TransactionHandler::onBody(\n    unique_ptr<folly::IOBuf> chain) noexcept {\n  parent_->stats_.addBytesReceived(chain->computeChainDataLength());\n}\n\nvoid Client::TransactionHandler::onTrailers(\n    unique_ptr<HTTPHeaders> /*trailers*/) noexcept {\n}\n\nvoid Client::TransactionHandler::onEOM() noexcept {\n  inMessage_ = false;\n  parent_->stats_.addResponse(millisecondsSince(requestStart_).count());\n\n  // TODO: not always true. Could have sent partial headers and then\n  // error'd, but since we don't have a onMessageBegin(), we have to guess\n  if (waitingForResponse_) {\n    parent_->stats_.addEOFResponse();\n  } else if (inMessage_) {\n    parent_->stats_.addEOFError();\n  }\n}\n\nvoid Client::TransactionHandler::onUpgrade(\n    ::proxygen::UpgradeProtocol /*protocol*/) noexcept {\n}\n\nvoid Client::TransactionHandler::onError(\n    const ::proxygen::HTTPException& error) noexcept {\n  parent_->stats_.addErrorLat(millisecondsSince(requestStart_).count());\n  if (error.getProxygenError() == kErrorTimeout) {\n    parent_->stats_.addTimeoutError();\n  }\n  if (error.getDirection() == HTTPException::Direction::INGRESS) {\n    waitingForResponse_ = false;\n    if (inMessage_) {\n      parent_->stats_.addMessageError();\n    } else {\n      // TODO: not always correct. Could have sent partial headers and then\n      // error'd, but since we don't have a onMessageBegin(), we have to\n      // guess\n      parent_->stats_.addEOFResponse();\n    }\n  }\n  inMessage_ = false;\n}\n\n// HTTPSession::InfoCallback callbacks\nvoid Client::InfoCollector::stopCallbacks() {\n  parent_ = nullptr;\n}\n\nvoid Client::InfoCollector::onDestroy(const HTTPSessionBase& /*sess*/) {\n  if (parent_) {\n    // In parent's session object destructor. Since our parent client\n    // still exists, try to send another request.\n    parent_->session_ = nullptr;\n    parent_->Client::eventBase_->runInLoop(parent_);\n  }\n}\n\nvoid Client::setQLoggerPath(const std::string& path) {\n  // TODO: Where do i started, there are at leat 10 reasons the following line\n  // is fucked up:\n  qlogger_ = std::make_shared<quic::samples::HQLoggerHelper>(\n      path, true /* pretty */, quic::VantagePoint::Client);\n}\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/Client.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <fizz/client/PskCache.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <proxygen/httpclient/samples/httperf2/HTTPerfStats.h>\n#include <proxygen/lib/http/HQConnector.h>\n#include <proxygen/lib/http/HTTPConnectorWithFizz.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <proxygen/lib/utils/Time.h>\n\n/**\n * Client interface - will make N requests to a server, using persistent\n * connections if available.  Will attempt to reconnect if the server\n * closes.\n */\nclass Client\n    : public ::proxygen::HTTPConnector::Callback\n    , public ::proxygen::HQConnector::Callback\n    , private folly::EventBase::LoopCallback {\n public:\n  class FinishedCallback {\n   public:\n    virtual ~FinishedCallback() = default;\n\n    // Invoked when this client has received responses or errors for\n    // all N requests\n    virtual void clientFinished(Client* client) = 0;\n  };\n\n  Client(folly::EventBase* eventBase,\n         const ::proxygen::WheelTimerInstance& transactionTimeouts,\n         HTTPerfStats& stats,\n         folly::Optional<folly::SocketAddress> bindAddr,\n         const folly::SocketAddress& address,\n         ::proxygen::HTTPMessage& request,\n         const std::string& rrequestData,\n         uint32_t n_requests,\n         Client::FinishedCallback* callback,\n         const std::string& plaintextProto,\n         const std::string& serverName);\n  ~Client() override;\n  Client(const Client&) = delete;\n  Client& operator=(const Client&) = delete;\n  Client(Client&&) = delete;\n  Client& operator=(Client&&) = delete;\n\n  void start();\n  void exit();\n\n  void setSSLParameters(const folly::SSLContextPtr& sslContext,\n                        std::shared_ptr<folly::ssl::SSLSession> session);\n  void setupFizzContext(std::shared_ptr<fizz::client::PskCache>,\n                        bool pskKe,\n                        const std::string& cert,\n                        const std::string& key);\n  void setUseQuic(bool useQuic);\n  void setQuicPskCache(std::shared_ptr<quic::QuicPskCache> quicPskCache);\n  void setQLoggerPath(const std::string& path);\n\n  [[nodiscard]] bool supportsTickets() const;\n  std::shared_ptr<folly::ssl::SSLSession> extractSSLSession();\n\n  void connect();\n  [[nodiscard]] bool isConnecting() const {\n    return connector_.isBusy();\n  }\n\n  static void exitAllSoon();\n\n  // HQConnector::Callback interface\n  void connectSuccess(proxygen::HQUpstreamSession* session) override;\n  void connectError(const quic::QuicErrorCode& error) override;\n\n  // HTTPConnector::Callback interface\n  void connectSuccess(proxygen::HTTPUpstreamSession*) override;\n  void connectError(const folly::AsyncSocketException& ex) override;\n\n  void connectSuccessCommon(proxygen::HTTPSessionBase* session);\n  void connectErrorCommon();\n\n private:\n  // LoopCallback interface\n  void runLoopCallback() noexcept override;\n\n  [[nodiscard]] bool shouldExit() const;\n\n  void sendRequest();\n\n  class TransactionHandler : public ::proxygen::HTTPTransaction::Handler {\n   public:\n    explicit TransactionHandler(Client* parent) : parent_(parent) {\n    }\n    void setTransaction(proxygen::HTTPTransaction* txn) noexcept override {\n      txn_ = txn;\n    }\n    void detachTransaction() noexcept override;\n\n    void onHeadersComplete(\n        std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;\n\n    void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n\n    void onChunkHeader(size_t /*length*/) noexcept override {\n    }\n\n    void onChunkComplete() noexcept override {\n    }\n\n    void onTrailers(\n        std::unique_ptr<proxygen::HTTPHeaders> trailers) noexcept override;\n\n    void onEOM() noexcept override;\n\n    void onUpgrade(proxygen::UpgradeProtocol prot) noexcept override;\n\n    void onError(const ::proxygen::HTTPException& error) noexcept override;\n\n    void onEgressPaused() noexcept override {\n    }\n\n    void onEgressResumed() noexcept override {\n    }\n\n   private:\n    Client* parent_;\n    bool inMessage_{false};\n    bool waitingForResponse_{false};\n    ::proxygen::TimePoint requestStart_{::proxygen::getCurrentTime()};\n    ::proxygen::HTTPTransaction* txn_{nullptr};\n  };\n  friend class TransactionHandler;\n\n  class InfoCollector : public ::proxygen::HTTPSession::InfoCallback {\n   public:\n    explicit InfoCollector(Client* parent) : parent_(parent) {\n    }\n\n    void stopCallbacks();\n\n    // HTTPSession::InfoCallback callbacks\n    void onDestroy(const ::proxygen::HTTPSessionBase&) override;\n\n   private:\n    Client* parent_;\n  };\n  friend class InfoCollector;\n\n  uint32_t outstandingTransactions_{0};\n  folly::EventBase* eventBase_;\n  HTTPerfStats& stats_;\n  uint32_t requestsSent_;\n\n  folly::Optional<folly::SocketAddress> bindAddr_;\n  const folly::SocketAddress& address_;\n  std::shared_ptr<folly::ssl::SSLSession> sslSession_;\n  folly::SSLContextPtr sslContext_;\n  std::shared_ptr<fizz::client::FizzClientContext> fizzContext_;\n  std::shared_ptr<quic::QuicPskCache> quicPskCache_;\n  bool useQuic_{false};\n\n  ::proxygen::HTTPMessage& request_; // can't be const :(\n  const std::string& requestData_;\n  uint32_t requests_;\n  FinishedCallback* callback_;\n\n  ::proxygen::HTTPSessionBase* session_{nullptr};\n\n  InfoCollector collector_{this};\n  ::proxygen::HTTPConnectorWithFizz connector_;\n\n  const std::string& serverName_;\n  const std::string& plaintextProto_;\n\n  ::proxygen::TimeUtil timeUtil_;\n\n  bool inDestructor_{false};\n  bool shouldReuseSession_{false};\n\n  std::unique_ptr<proxygen::HQConnector> hqConnector_;\n\n  std::chrono::steady_clock::time_point start_{\n      std::chrono::milliseconds::zero()};\n  std::chrono::steady_clock::time_point end_{std::chrono::milliseconds::zero()};\n\n  static const std::chrono::milliseconds kConnectTimeout;\n\n  std::shared_ptr<quic::QLogger> qlogger_;\n};\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/HTTPerf2.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/**\n * httperf2: replacement for httperf\n *\n * httperf is buggy, unpredictable and has more sophistication than we need\n * to benchmark proxygen.  httperf2 is meant to be a replacement.\n *\n * That httperf2 depends on proxygen/lib/ makes it a bit awkward to\n * benchmark a change, since any improvements could also benefit httperf2.\n * Always use the same version of httperf2 to benchmark proxygen A vs.\n * proxygen B.\n */\n#include <fizz/client/PskCache.h>\n#include <folly/FileUtil.h>\n#include <folly/Random.h>\n#include <folly/String.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GFlags.h>\n#include <folly/ssl/SSLSession.h>\n#include <proxygen/httpclient/samples/httperf2/Client.h>\n#include <proxygen/httpclient/samples/httperf2/HTTPerf2.h>\n#include <proxygen/httpclient/samples/httperf2/HTTPerfStats.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n\n#include <csignal>\n#include <iostream>\n#include <memory>\n#include <random>\n\nusing folly::SocketAddress;\nusing std::chrono::milliseconds;\nusing std::chrono::system_clock;\nusing namespace proxygen;\n\n// Duration parameters\nDEFINE_int32(clients, 1, \"Number of simulated clients across all threads\");\nDEFINE_int32(clients_at_once, 1, \"Max concurrent clients across all threads\");\nDEFINE_int32(duration, 0, \"Run for N seconds, overrides -clients\");\nDEFINE_int32(threads, 1, \"Number of threads to spread clients across\");\nDEFINE_int32(delaystart, 0, \"Milliseconds to sleep between starting threads\");\nDEFINE_string(\n    wait_for_file,\n    \"\",\n    \"If specified, the test will wait for the file to exist before starting\");\n\n// Target Params\nDEFINE_string(server, \"localhost\", \"Server name\");\nDEFINE_int32(port, 8080, \"Server port\");\nDEFINE_string(ip, \"\", \"IP of server to bypass DNS for server\");\nDEFINE_bool(ssl, false, \"Use SSL\");\nDEFINE_bool(http2, false, \"Use HTTP/2 <N>\");\nDEFINE_string(\n    server_name,\n    \"\",\n    \"Overrides the default server name to advertise with the secure protocol\");\nDEFINE_bool(fizz, false, \"Use fizz (TLS 1.3)\");\nDEFINE_bool(fizz_psk_ke, false, \"Use fizz psk_ke mode on resumptions\");\nDEFINE_bool(quic, false, \"Use quic\");\nDEFINE_string(cert, \"\", \"Certificate file path\");\nDEFINE_string(key, \"\", \"Private key file path\");\n\n// Tunables\nDEFINE_int32(resume_pct, 100, \"SSL resume percentage\");\nDEFINE_int32(ticket_pct, 100, \"TLS ticket percentage\");\nDEFINE_int32(requests, 1, \"Number of requests (ie transactions) per client\");\nDEFINE_double(request_avg,\n              0,\n              \"Average number of requests per client, overrides -requests\");\nDEFINE_string(ciphers, \"HIGH\", \"OpenSSL cipher preferences\");\nDEFINE_int32(client_quic_transport_timer_resolution_ms,\n             1,\n             \"Quic transport timer resolution in ms\");\n\n// HTTP options\nDEFINE_bool(http10, false, \"Force HTTP/1.0\");\nDEFINE_string(url, \"/\", \"Request URL\");\nDEFINE_bool(nohost, false, \"Don't add Host: <servername> to HTTP/1.1 requests\");\nDEFINE_string(headers, \"\", \":: separated list of HTTP request headers\");\nDEFINE_bool(realheaders, false, \"Add default headers from Firefox circa 2012\");\nDEFINE_string(data, \"\", \"File to transfer via POST\");\n\nDEFINE_int32(request_timeout_ms, 1000, \"request timeout\");\n\n// Quic options\nDEFINE_string(quic_qlogger_path,\n              \"\",\n              \"Path to the directory where quic qlog files will be saved to.\");\n\n// Output options\nDEFINE_string(testname, \"\", \"Test name (prefixed to all the JSON keys\");\nDEFINE_bool(json, false, \"Output as JSON\");\n\nnamespace {\n\nstatic std::list<std::pair<char const*, char const*>> f_real_headers = {\n    {\"User-Agent\",\n     \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) \"\n     \"Gecko/20100101 Firefox/10.0.2\"},\n    {\"Accept\",\n     \"text/html,application/xhtml+xml,application/xml;\"\n     \"q=0.9,*/*;q=0.8\"},\n    {\"Accept-Language\", \"en-us,en;q=0.5\"},\n    {\"Accept-Encoding\", \"gzip, deflate\"},\n    {\"Connection\", \"keep-alive\"}};\n\nstd::string gRequestData;\n\n/**\n * This class drives the work for a single thread.\n */\nclass ClientRunner\n    : public folly::AsyncTimeout\n    , public Client::FinishedCallback {\n public:\n  ClientRunner(HTTPerfStats& parentStats,\n               folly::Optional<SocketAddress> bindAddr,\n               SocketAddress address,\n               size_t numClients,\n               size_t numRequests,\n               size_t clientsAtOnce,\n               int32_t quicTransportTimerResolutionMs,\n               VaryRequestParams* varyParams = nullptr);\n\n  void run();\n\n  void startClient();\n\n  void clientFinished(Client* client) override;\n\n  void timeoutExpired() noexcept override;\n\n private:\n  using SSLParams =\n      std::pair<folly::SSLContextPtr, std::shared_ptr<folly::ssl::SSLSession>>;\n\n  HTTPerfStats& parentStats_;\n  HTTPerfStats stats_;\n  size_t remainingClients_;\n  size_t numRequests_;\n  size_t clientsAtOnce_;\n  HTTPMessage request_;\n  std::string requestData_;\n  std::string plaintextProto_;\n  folly::EventBase eventBase_;\n  WheelTimerInstance transactionTimeouts_;\n  folly::Optional<SocketAddress> bindAddr_;\n  SocketAddress address_;\n  SSLParams ticketSSLParams_;\n  SSLParams sessionSSLParams_;\n  std::normal_distribution<double> requestDistribution_;\n  std::uniform_int_distribution<uint32_t> ticketDistribution_;\n  std::uniform_int_distribution<uint32_t> resumeDistribution_;\n  uint32_t clientsOutstanding_{0};\n  std::mt19937 rng_;\n  std::shared_ptr<fizz::client::BasicPskCache> pskCache_;\n  std::shared_ptr<quic::BasicQuicPskCache> quicPskCache_;\n\n  uint32_t getClientRequests();\n\n  const SSLParams& getSSLParams();\n  std::string serverName_;\n};\n\n} // namespace\n\nnamespace proxygen {\nint httperf2(folly::Optional<folly::SocketAddress> bindAddress,\n             VaryRequestParams* varyParams) {\n  if (FLAGS_threads <= 0 || FLAGS_clients <= 0 || FLAGS_clients_at_once <= 0 ||\n      (FLAGS_request_avg <= 0 && FLAGS_requests <= 0) || FLAGS_ticket_pct < 0 ||\n      FLAGS_ticket_pct > 100 || FLAGS_resume_pct < 0 ||\n      FLAGS_resume_pct > 100) {\n    std::cerr << \"Invalid arguments\" << std::endl;\n    return 1;\n  }\n  auto numRequests = FLAGS_requests;\n  if (FLAGS_request_avg > 0) {\n    std::cerr << \"Using request_avg\" << std::endl;\n    numRequests = 0;\n  }\n\n  size_t numClients;\n  if (FLAGS_duration > 0) {\n    std::cerr << \"Using duration \" << FLAGS_duration << std::endl;\n    numClients = 0;\n  } else {\n    numClients = std::max(1, FLAGS_clients / FLAGS_threads);\n  }\n\n  auto clientsAtOnce = std::max(1, FLAGS_clients_at_once / FLAGS_threads);\n\n  if (!FLAGS_wait_for_file.empty()) {\n    std::cerr << \"Waiting for file \" << FLAGS_wait_for_file << std::endl;\n    int fd = -1;\n    while (fd == -1) {\n      fd = folly::openNoInt(FLAGS_wait_for_file.c_str(), O_RDONLY);\n    }\n    if (fd != -1) {\n      folly::closeNoInt(fd);\n    }\n  }\n\n  SocketAddress address;\n  if (!FLAGS_ip.empty()) {\n    address.setFromIpPort(FLAGS_ip, FLAGS_port);\n  } else {\n    address.setFromHostPort(FLAGS_server, FLAGS_port);\n  }\n\n  if (!FLAGS_data.empty()) {\n    if (!folly::readFile(FLAGS_data.c_str(), gRequestData)) {\n      LOG(FATAL) << \"Failed to read file\";\n    }\n  }\n  std::cerr << \"Running test against \" << FLAGS_server << \":\" << FLAGS_port\n            << std::endl;\n\n  system_clock::time_point start = system_clock::now();\n  HTTPerfStats stats;\n  if (FLAGS_threads == 1) {\n    ClientRunner r(stats,\n                   std::move(bindAddress),\n                   std::move(address),\n                   numClients,\n                   numRequests,\n                   clientsAtOnce,\n                   FLAGS_client_quic_transport_timer_resolution_ms,\n                   varyParams);\n    r.run();\n  } else {\n    std::list<std::thread> threads;\n    for (int i = 0; i < FLAGS_threads; i++) {\n      auto r = std::make_shared<ClientRunner>(\n          stats,\n          bindAddress,\n          address,\n          numClients,\n          numRequests,\n          clientsAtOnce,\n          FLAGS_client_quic_transport_timer_resolution_ms,\n          varyParams);\n      threads.emplace_back([r]() { r->run(); });\n      if (FLAGS_delaystart > 0 && i + 1 < FLAGS_threads) {\n        // @lint-ignore CLANGTIDY\n        usleep(FLAGS_delaystart * 1000);\n      }\n    }\n    for (auto& t : threads) {\n      if (t.joinable()) {\n        t.join();\n      }\n    }\n  }\n  milliseconds durationMs =\n      std::chrono::duration_cast<milliseconds>(system_clock::now() - start);\n\n  if (FLAGS_json) {\n    stats.printStatsInJson(FLAGS_testname, durationMs);\n  } else {\n    stats.printStats(durationMs);\n  }\n\n  return 0;\n}\n} // namespace proxygen\n\nnamespace {\nClientRunner::ClientRunner(HTTPerfStats& parentStats,\n                           folly::Optional<SocketAddress> bindAddr,\n                           SocketAddress address,\n                           size_t numClients,\n                           size_t numRequests,\n                           size_t clientsAtOnce,\n                           int32_t quicTransportTimerResolutionMs,\n                           VaryRequestParams* varyParams)\n    : parentStats_(parentStats),\n      remainingClients_(numClients),\n      numRequests_(numRequests),\n      clientsAtOnce_(clientsAtOnce),\n      eventBase_(std::chrono::milliseconds(\n          FLAGS_quic ? quicTransportTimerResolutionMs\n                     : folly::HHWheelTimer::DEFAULT_TICK_INTERVAL)),\n      transactionTimeouts_(std::chrono::milliseconds(FLAGS_request_timeout_ms),\n                           &eventBase_),\n      bindAddr_(std::move(bindAddr)),\n      address_(std::move(address)),\n      requestDistribution_(FLAGS_request_avg, 1),\n      ticketDistribution_(0, 100),\n      resumeDistribution_(0, 100),\n\n      pskCache_(std::make_shared<fizz::client::BasicPskCache>()),\n      quicPskCache_(std::make_shared<quic::BasicQuicPskCache>()) {\n  attachEventBase(&eventBase_);\n\n  if (remainingClients_ == 0) {\n    remainingClients_ = 0x7fffffff;\n  }\n\n  ticketSSLParams_.first = std::make_shared<folly::SSLContext>();\n  ticketSSLParams_.first->setOptions(SSL_OP_NO_COMPRESSION);\n  if (FLAGS_ciphers.length() > 0) {\n    ticketSSLParams_.first->ciphers(FLAGS_ciphers);\n  }\n  ticketSSLParams_.second = nullptr;\n\n  sessionSSLParams_.first = std::make_shared<folly::SSLContext>();\n  sessionSSLParams_.first->setOptions(SSL_OP_NO_COMPRESSION | SSL_OP_NO_TICKET);\n  if (FLAGS_ciphers.length() > 0) {\n    sessionSSLParams_.first->ciphers(FLAGS_ciphers);\n  }\n  sessionSSLParams_.second = nullptr;\n  rng_.seed(folly::randomNumberSeed());\n\n  if (FLAGS_http2) {\n    std::list<std::string> protos;\n    plaintextProto_ = \"h2\";\n    ticketSSLParams_.first->setAdvertisedNextProtocols({plaintextProto_});\n    sessionSSLParams_.first->setAdvertisedNextProtocols({plaintextProto_});\n  }\n\n  if (FLAGS_quic) {\n    plaintextProto_ = kH3FBCurrentDraft;\n  }\n\n  // Set up the HTTP request\n  if (!FLAGS_data.empty()) {\n    request_.setMethod(HTTPMethod::POST);\n    requestData_ = gRequestData;\n  } else {\n    request_.setMethod(HTTPMethod::GET);\n  }\n\n  serverName_ = FLAGS_server;\n  if (!FLAGS_server_name.empty()) {\n    serverName_ = FLAGS_server_name;\n  }\n\n  if (FLAGS_http10) {\n    request_.setHTTPVersion(1, 0);\n  } else {\n    request_.setHTTPVersion(1, 1);\n    if (!FLAGS_nohost) {\n      request_.getHeaders().add(HTTP_HEADER_HOST, serverName_);\n    }\n  }\n\n  // Set URL from VaryRequestParams if provided, otherwise use default URL\n  request_.setURL(varyParams\n                      ? varyParams->getNextRequestURL().value_or(FLAGS_url)\n                      : FLAGS_url);\n\n  // Add custom headers if provided via VaryRequestParams\n  if (varyParams) {\n    if (auto headers = varyParams->getNextRequestHeaders()) {\n      for (const auto& header : *headers) {\n        request_.getHeaders().add(header.first, header.second);\n      }\n    }\n  }\n  if (FLAGS_realheaders) {\n    for (const auto& p : f_real_headers) {\n      request_.getHeaders().add(p.first, p.second);\n    }\n  }\n\n  std::vector<std::string> headers;\n  folly::split(std::string_view(\"::\"), FLAGS_headers, headers);\n  for (const auto& header : headers) {\n    if (header.length() == 0) {\n      continue;\n    }\n    auto pos = header.find(':');\n    if (pos == std::string::npos) {\n      request_.getHeaders().add(header, empty_string);\n    } else {\n      std::string headerName = header.substr(0, pos);\n      do {\n        pos++;\n      } while (pos < header.length() && isspace(header.at(pos)));\n      request_.getHeaders().add(headerName, header.substr(pos));\n    }\n  }\n}\n\nvoid ClientRunner::run() {\n  sigset_t ss;\n  sigemptyset(&ss);\n  sigaddset(&ss, SIGPIPE);\n  PCHECK(pthread_sigmask(SIG_BLOCK, &ss, nullptr) == 0);\n\n  auto clients = std::min(remainingClients_, clientsAtOnce_);\n  for (size_t i = 0; i < clients; i++) {\n    startClient();\n  }\n\n  if (FLAGS_duration > 0) {\n    scheduleTimeout(FLAGS_duration * 1000);\n  }\n  eventBase_.loop();\n\n  CHECK_EQ(clientsOutstanding_, 0);\n  parentStats_.merge(stats_);\n}\n\nvoid ClientRunner::timeoutExpired() noexcept {\n  VLOG(3) << \"Duration timeout expired\";\n  Client::exitAllSoon();\n  remainingClients_ = 0;\n}\n\nvoid ClientRunner::startClient() {\n  CHECK_GT(remainingClients_, 0);\n  Client* client = nullptr;\n  client = new Client(&eventBase_,\n                      transactionTimeouts_,\n                      stats_,\n                      bindAddr_,\n                      address_,\n                      request_,\n                      requestData_,\n                      getClientRequests(),\n                      this,\n                      plaintextProto_,\n                      serverName_);\n  auto resumePct = uint32_t(FLAGS_resume_pct);\n  if (FLAGS_ssl) {\n    const SSLParams& sslParams = getSSLParams();\n    auto session = sslParams.second;\n    if (session != nullptr && resumeDistribution_(rng_) > resumePct) {\n      session = nullptr;\n    }\n    client->setSSLParameters(sslParams.first, session);\n  } else if (FLAGS_fizz) {\n    client->setupFizzContext(resumeDistribution_(rng_) > resumePct ? nullptr\n                                                                   : pskCache_,\n                             FLAGS_fizz_psk_ke,\n                             FLAGS_cert,\n                             FLAGS_key);\n  } else if (FLAGS_quic) {\n    client->setUseQuic(true);\n    client->setupFizzContext(nullptr, FLAGS_fizz_psk_ke, FLAGS_cert, FLAGS_key);\n    client->setQuicPskCache(\n        resumeDistribution_(rng_) > resumePct ? nullptr : quicPskCache_);\n    if (!FLAGS_quic_qlogger_path.empty()) {\n      client->setQLoggerPath(FLAGS_quic_qlogger_path);\n    }\n  }\n  remainingClients_--;\n  clientsOutstanding_++;\n  client->start();\n}\n\nvoid ClientRunner::clientFinished(Client* client) {\n  if (FLAGS_ssl) {\n    // Assume that the session is valid for the length of the test\n    auto session = client->extractSSLSession();\n    if (client->supportsTickets()) {\n      if (ticketSSLParams_.second == nullptr) {\n        ticketSSLParams_.second = session;\n      }\n    } else if (sessionSSLParams_.second == nullptr) {\n      // TODO: keep multiple sessions to stress stateful cache lookups\n      sessionSSLParams_.second = session;\n    }\n  }\n  clientsOutstanding_--;\n  delete client;\n  VLOG(3) << __func__ << \" clientsOutstanding=\" << clientsOutstanding_\n          << \" remainingClients=\" << remainingClients_;\n  if (remainingClients_ > 0 && clientsOutstanding_ < clientsAtOnce_) {\n    startClient();\n  }\n}\n\nuint32_t ClientRunner::getClientRequests() {\n  if (numRequests_ > 0) {\n    return numRequests_;\n  }\n  return static_cast<uint32_t>(\n      std::max(floor(requestDistribution_(rng_) + .5), 1.0));\n}\n\nconst ClientRunner::SSLParams& ClientRunner::getSSLParams() {\n  if (ticketDistribution_(rng_) < uint32_t(FLAGS_ticket_pct)) {\n    return ticketSSLParams_;\n  } else {\n    return sessionSSLParams_;\n  }\n}\n} // namespace\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/HTTPerf2.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/SocketAddress.h>\n#include <folly/portability/GFlags.h>\n#include <string>\n#include <vector>\n\nDECLARE_string(ip);\nDECLARE_string(server);\nDECLARE_int32(port);\n\nnamespace proxygen {\n\n/**\n * Interface class for varying request parameters.\n */\nclass VaryRequestParams {\n public:\n  virtual ~VaryRequestParams() = default;\n\n  /**\n   * Get the next request URL to use.\n   * Default implementation returns an empty string, which means use the default\n   * URL.\n   */\n  virtual std::optional<std::string> getNextRequestURL() {\n    return std::nullopt;\n  }\n\n  /**\n   * Get the next request headers to use.\n   * Default implementation returns an empty vector, which means use the default\n   * headers.\n   */\n  virtual std::optional<std::vector<std::pair<std::string, std::string>>>\n  getNextRequestHeaders() {\n    return std::nullopt;\n  }\n};\n\n/**\n * Run the httperf2 benchmark.\n * @param bindAddr Optional address to bind to.\n * @param varyParams Optional parameter to vary request parameters.\n *                   If nullptr, use the default parameters from flags.\n * @return Exit code.\n */\nint httperf2(folly::Optional<folly::SocketAddress> bindAddr = folly::none,\n             VaryRequestParams* varyParams = nullptr);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/HTTPerfStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <folly/json/json.h>\n#include <iostream>\n#include <mutex>\n\n/**\n * Thread-local statistics for HTTPerf2.\n */\nclass HTTPerfStats {\n public:\n  class SumStat {\n   public:\n    SumStat() = default;\n\n    void addValue(size_t val) {\n      sum += val;\n    }\n\n    size_t sum{0};\n  };\n\n  class AvgStat {\n    struct AvgData {\n      size_t count{0};\n      size_t total{0};\n    };\n\n   public:\n    AvgStat() = default;\n\n    void addValue(size_t val, size_t num = 1) {\n      avg_.count += num;\n      avg_.total += val;\n    }\n\n    void addValue(const AvgStat& avgData) {\n      addValue(avgData.avg_.total, avgData.avg_.count);\n    }\n\n    size_t avg() {\n      if (avg_.count > 0) {\n        return size_t(double(avg_.total) / double(avg_.count));\n      } else {\n        return 0;\n      }\n    }\n\n   private:\n    AvgData avg_;\n  };\n\n  HTTPerfStats() = default;\n  ~HTTPerfStats() = default;\n\n  void addConnection(uint32_t connLat) {\n    conns_.addValue(1);\n    connLatency_.addValue(connLat);\n  }\n\n  void addHandshake() {\n    handshakes_.addValue(1);\n  }\n\n  void addResume() {\n    resumes_.addValue(1);\n  }\n\n  void addRequest() {\n    requests_.addValue(1);\n  }\n\n  void addResponse(uint32_t reqLat) {\n    responses_.addValue(1);\n    reqLatency_.addValue(reqLat);\n  }\n\n  void addErrorLat(uint32_t reqLat) {\n    reqLatency_.addValue(reqLat);\n  }\n\n  void addResponseCode(uint32_t code) {\n    if (code < 100 || code >= 600) {\n      codeOther_.addValue(1);\n    } else if (code < 200) {\n      code1xx_.addValue(1);\n    } else if (code < 300) {\n      code2xx_.addValue(1);\n    } else if (code < 400) {\n      code3xx_.addValue(1);\n    } else if (code < 500) {\n      code4xx_.addValue(1);\n    } else {\n      code5xx_.addValue(1);\n    }\n  }\n\n  void addBytesReceived(uint32_t count) {\n    bytesReceived_.addValue(count);\n  }\n\n  void addConnectError() {\n    connErrors_.addValue(1);\n  }\n\n  void addWriteError() {\n    writeErrors_.addValue(1);\n  }\n\n  void addMessageError() {\n    msgErrors_.addValue(1);\n  }\n\n  void addTimeoutError() {\n    timeoutErrors_.addValue(1);\n  }\n\n  void addEOFResponse() {\n    eofResponses_.addValue(1);\n  }\n\n  void addEOFError() {\n    eofErrors_.addValue(1);\n  }\n\n  void merge(const HTTPerfStats& stats) {\n    const std::lock_guard<std::mutex> lock(mutex_);\n    conns_.addValue(stats.conns_.sum);\n    handshakes_.addValue(stats.handshakes_.sum);\n    resumes_.addValue(stats.resumes_.sum);\n    requests_.addValue(stats.requests_.sum);\n    codeOther_.addValue(stats.codeOther_.sum);\n    code1xx_.addValue(stats.code1xx_.sum);\n    code2xx_.addValue(stats.code2xx_.sum);\n    code3xx_.addValue(stats.code3xx_.sum);\n    code4xx_.addValue(stats.code4xx_.sum);\n    code5xx_.addValue(stats.code5xx_.sum);\n    bytesReceived_.addValue(stats.bytesReceived_.sum);\n    connErrors_.addValue(stats.connErrors_.sum);\n    msgErrors_.addValue(stats.msgErrors_.sum);\n    writeErrors_.addValue(stats.writeErrors_.sum);\n    timeoutErrors_.addValue(stats.timeoutErrors_.sum);\n    eofResponses_.addValue(stats.eofResponses_.sum);\n    eofErrors_.addValue(stats.eofErrors_.sum);\n    connLatency_.addValue(stats.connLatency_);\n    reqLatency_.addValue(stats.reqLatency_);\n  }\n\n  std::map<std::string, size_t> aggregateSums() {\n    std::map<std::string, size_t> results;\n    const std::lock_guard<std::mutex> lock(mutex_);\n    results.emplace(\"HTTPerf_conns\", conns_.sum);\n    results.emplace(\"HTTPerf_ssl_hand\", handshakes_.sum);\n    results.emplace(\"HTTPerf_ssl_res\", resumes_.sum);\n    results.emplace(\"HTTPerf_reqs\", requests_.sum);\n    results.emplace(\"HTTPerf_resp\", responses_.sum);\n    results.emplace(\"HTTPerf_code_Other\", codeOther_.sum);\n    results.emplace(\"HTTPerf_code_1xx\", code1xx_.sum);\n    results.emplace(\"HTTPerf_code_2xx\", code2xx_.sum);\n    results.emplace(\"HTTPerf_code_3xx\", code3xx_.sum);\n    results.emplace(\"HTTPerf_code_4xx\", code4xx_.sum);\n    results.emplace(\"HTTPerf_code_5xx\", code5xx_.sum);\n    results.emplace(\"HTTPerf_bytes\", bytesReceived_.sum);\n    results.emplace(\"HTTPerf_conn_err\", connErrors_.sum);\n    results.emplace(\"HTTPerf_msg_err\", msgErrors_.sum);\n    results.emplace(\"HTTPerf_write_err\", writeErrors_.sum);\n    results.emplace(\"HTTPerf_timeout\", timeoutErrors_.sum);\n    results.emplace(\"HTTPerf_eof_resp\", eofResponses_.sum);\n    results.emplace(\"HTTPerf_eof_err\", eofErrors_.sum);\n    return results;\n  }\n\n  std::map<std::string, size_t> aggregateAvgs() {\n    std::map<std::string, size_t> results;\n    const std::lock_guard<std::mutex> lock(mutex_);\n    results.emplace(\"HTTPerf_conn_lat\", connLatency_.avg());\n    results.emplace(\"HTTPerf_req_lat\", reqLatency_.avg());\n    return results;\n  }\n\n  void printStats(std::chrono::milliseconds durationMs) {\n    auto results = aggregateSums();\n    for (const auto& item : results) {\n      double rate = double(item.second) * 1000.0 / double(durationMs.count());\n      if (rate > 1000000) {\n        rate /= 1000000;\n        printf(\"  %-21s: %7.2fM/sec\\n\", item.first.c_str(), rate);\n      } else {\n        printf(\"  %-21s: %8d/sec\\n\", item.first.c_str(), (int)rate);\n      }\n    }\n    results = aggregateAvgs();\n    for (const auto& item : results) {\n      printf(\"  %-21s: %7ld msec\\n\", item.first.c_str(), item.second);\n    }\n    printf(\"  %-21s: %9ld ms\\n\", \"Run time\", durationMs.count());\n  }\n\n  void printStatsInJson(const std::string& testname,\n                        std::chrono::milliseconds durationMs) {\n    auto results = aggregateSums();\n    folly::dynamic d = folly::dynamic::object;\n    for (const auto& item : results) {\n      double rate = double(item.second) * 1000.0 / double(durationMs.count());\n      d[testname + \".\" + item.first] = rate;\n    }\n    results = aggregateAvgs();\n    for (const auto& item : results) {\n      d[testname + \".\" + item.first] = item.second;\n    }\n\n    d[testname + \".runtime\"] = durationMs.count();\n    std::cout << toPrettyJson(d) << std::endl;\n  }\n\n private:\n  std::mutex mutex_;\n  SumStat conns_;\n  SumStat handshakes_;\n  SumStat resumes_;\n  SumStat requests_;\n  SumStat responses_;\n  SumStat codeOther_;\n  SumStat code1xx_;\n  SumStat code2xx_;\n  SumStat code3xx_;\n  SumStat code4xx_;\n  SumStat code5xx_;\n  SumStat bytesReceived_;\n  SumStat connErrors_;\n  SumStat msgErrors_;\n  SumStat writeErrors_;\n  SumStat timeoutErrors_;\n  SumStat eofResponses_;\n  SumStat eofErrors_;\n  AvgStat connLatency_;\n  AvgStat reqLatency_;\n};\n"
  },
  {
    "path": "proxygen/httpclient/samples/httperf2/Main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/init/Init.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpclient/samples/httperf2/HTTPerf2.h>\n\nint main(int argc, char* argv[]) {\n  gflags::SetUsageMessage(std::string(\"\\n\\nusage: httperf2 (see flags)\\n\"));\n  auto _ = folly::Init(&argc, &argv, true);\n\n  return proxygen::httperf2();\n}\n"
  },
  {
    "path": "proxygen/httpclient/samples/websocket/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nadd_library(proxygenwsclientlib WebSocketClient.cpp)\ntarget_include_directories(\n    proxygenwsclientlib PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n)\ntarget_compile_options(\n    proxygenwsclientlib PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\n\nif (BUILD_SHARED_LIBS)\n    set_property(\n        TARGET proxygenwsclientlib PROPERTY POSITION_INDEPENDENT_CODE ON\n    )\n    if (DEFINED PACKAGE_VERSION)\n        set_target_properties(\n            proxygenwsclientlib PROPERTIES VERSION ${PACKAGE_VERSION}\n        )\n    endif()\nendif()\n\ntarget_link_libraries(proxygenwsclientlib PUBLIC proxygen)\n\ninstall(\n    TARGETS proxygenwsclientlib\n    EXPORT proxygen-exports\n    ARCHIVE DESTINATION ${LIB_INSTALL_DIR}\n    LIBRARY DESTINATION ${LIB_INSTALL_DIR}\n)\n\nadd_executable(proxygen_websocket_client main.cpp)\ntarget_link_libraries(\n    proxygen_websocket_client PUBLIC\n    proxygenwsclientlib\n)\ntarget_compile_options(\n    proxygen_websocket_client PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\n\ninstall(\n    TARGETS proxygen_websocket_client\n    EXPORT proxygen-exports\n    DESTINATION bin\n)\n"
  },
  {
    "path": "proxygen/httpclient/samples/websocket/WebSocketClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"WebSocketClient.h\"\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <utility>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nnamespace websocketclient {\n\nWebSocketClient::WebSocketClient(EventBase* evb, proxygen::URL url)\n    : evb_(evb), url_(std::move(url)) {\n}\n\nvoid WebSocketClient::connectSuccess(HTTPUpstreamSession* session) {\n  sendRequest(session->newTransaction(this));\n  session->closeWhenIdle();\n}\n\nvoid WebSocketClient::setupRequest() {\n  request_.setMethod(HTTPMethod::GET);\n  request_.setHTTPVersion(1, 1);\n  request_.setURL(url_.makeRelativeURL());\n  request_.getHeaders().add(HTTP_HEADER_USER_AGENT,\n                            \"proxygen_websocket_client\");\n  request_.getHeaders().add(HTTP_HEADER_HOST, url_.getHostAndPort());\n  request_.getHeaders().add(\"Accept\", \"*/*\");\n  request_.setEgressWebsocketUpgrade();\n}\n\nvoid WebSocketClient::sendRequest(HTTPTransaction* txn) {\n  VLOG(4) << fmt::format(\"Connecting to {}\", url_.getUrl());\n  txn_ = txn;\n  setupRequest();\n  txn_->sendHeaders(request_);\n}\n\nvoid WebSocketClient::connectError(const folly::AsyncSocketException& ex) {\n  LOG(ERROR) << \"Failed to connect to \" << url_.getHostAndPort() << \":\"\n             << ex.what();\n}\n\nvoid WebSocketClient::setTransaction(HTTPTransaction*) noexcept {\n}\n\nvoid WebSocketClient::detachTransaction() noexcept {\n}\n\nvoid WebSocketClient::onHeadersComplete(unique_ptr<HTTPMessage> msg) noexcept {\n  response_ = std::move(msg);\n}\n\nvoid WebSocketClient::onBody(std::unique_ptr<folly::IOBuf> chain) noexcept {\n  LOG(INFO) << \"got server reply: \" << chain->moveToFbString();\n  // Close the connection.\n  txn_->sendEOM();\n}\n\nvoid WebSocketClient::onTrailers(std::unique_ptr<HTTPHeaders>) noexcept {\n  CHECK(false) << \"unexpected trailers\";\n}\n\nvoid WebSocketClient::onEOM() noexcept {\n  LOG(INFO) << \"connection closed by server\";\n}\n\nvoid WebSocketClient::onUpgrade(UpgradeProtocol) noexcept {\n  LOG(INFO) << \"websocket connect successful; sending data\";\n  auto data = folly::IOBuf::copyBuffer(\"websocket | framed | stream | data\");\n  txn_->sendBody(std::move(data));\n}\n\nvoid WebSocketClient::onError(const HTTPException& error) noexcept {\n  LOG(ERROR) << \"An error occurred: \" << error.what();\n}\n\n} // namespace websocketclient\n"
  },
  {
    "path": "proxygen/httpclient/samples/websocket/WebSocketClient.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/utils/URL.h>\n\nnamespace websocketclient {\n\nclass WebSocketClient\n    : public proxygen::HTTPConnector::Callback\n    , public proxygen::HTTPTransactionHandler {\n\n public:\n  WebSocketClient(folly::EventBase* evb, proxygen::URL url);\n\n  ~WebSocketClient() override = default;\n\n  // HTTPConnector methods\n  void connectSuccess(proxygen::HTTPUpstreamSession* session) override;\n  void connectError(const folly::AsyncSocketException& ex) override;\n\n  // HTTPTransactionHandler methods\n  void setTransaction(proxygen::HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(\n      std::unique_ptr<proxygen::HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(proxygen::UpgradeProtocol protocol) noexcept override;\n  void onError(const proxygen::HTTPException& error) noexcept override;\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n\n  void sendRequest(proxygen::HTTPTransaction* txn);\n\n protected:\n  void setupRequest();\n\n  proxygen::HTTPTransaction* txn_{nullptr};\n  folly::EventBase* evb_{nullptr};\n  proxygen::URL url_;\n  proxygen::HTTPMessage request_;\n  std::unique_ptr<proxygen::HTTPMessage> response_;\n};\n\n} // namespace websocketclient\n"
  },
  {
    "path": "proxygen/httpclient/samples/websocket/main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/SocketAddress.h>\n#include <folly/init/Init.h>\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/EventBase.h>\n#include <proxygen/httpclient/samples/websocket/WebSocketClient.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n#include <proxygen/lib/utils/URL.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace websocketclient;\n\nDEFINE_int32(http_client_connect_timeout,\n             1000,\n             \"connect timeout in milliseconds\");\nDEFINE_string(url,\n              \"http://localhost:11000/\",\n              \"URL to perform the HTTP method against\");\n\nint main(int argc, char* argv[]) {\n  const folly::Init init(&argc, &argv, false);\n\n  EventBase evb;\n  URL url(FLAGS_url);\n\n  WebSocketClient client(&evb, url);\n\n  auto addr = SocketAddress(url.getHost(), url.getPort(), true);\n  LOG(INFO) << \"Trying to connect to \" << addr;\n\n  // Note: HHWheelTimer is a large object and should be created at most\n  // once per thread\n  HHWheelTimer::UniquePtr timer{HHWheelTimer::newTimer(\n      &evb,\n      std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      AsyncTimeout::InternalEnum::NORMAL,\n      std::chrono::milliseconds(5000))};\n  HTTPConnector connector(&client, timer.get());\n  static const SocketOptionMap opts{\n      {{.level = SOL_SOCKET, .optname = SO_REUSEADDR}, 1}};\n\n  connector.connect(\n      &evb,\n      addr,\n      std::chrono::milliseconds(FLAGS_http_client_connect_timeout),\n      opts);\n\n  evb.loop();\n\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "proxygen/httpserver/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Manually maintained - generated base with manual additions for compat aliases\n# and BUILD_SAMPLES logic. Run generate_cmake.py to update the library targets,\n# then re-add the manual section below.\n\nproxygen_add_library(proxygen_httpserver_httpserver_headers\n  EXPORTED_DEPS\n    proxygen_http_session_http_transaction\n    proxygen_utils_compression_filter_utils\n    proxygen_utils_stream_decompressor\n    Folly::folly_expected\n    Folly::folly_function\n    Folly::folly_io_async_server_socket\n    Folly::folly_network_address\n    Folly::folly_scope_guard\n)\n\nproxygen_add_library(proxygen_httpserver_decompression_filter\n  SRCS\n    filters/DecompressionFilter.cpp\n  DEPS\n    proxygen_utils_zstd_stream_decompressor\n  EXPORTED_DEPS\n    proxygen_httpserver_httpserver_headers\n    proxygen_utils_stream_decompressor\n)\n\nproxygen_add_library(proxygen_httpserver\n  SRCS\n    HTTPServer.cpp\n    HTTPServerAcceptor.cpp\n    RequestHandlerAdaptor.cpp\n    SignalHandler.cpp\n  DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_http1x_codec\n    wangle::wangle_ssl_ssl_context_manager\n    Folly::folly_exception_string\n    Folly::folly_executors_thread_factory_named_thread_factory\n    Folly::folly_io_async_event_base_manager\n    Folly::folly_range\n    Folly::folly_system_hardware_concurrency\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_factory\n    proxygen_http_session_http_transaction\n    proxygen_http_session_server\n    proxygen_http_session_session\n    proxygen_httpserver_httpserver_headers\n    proxygen_services_acceptor_configuration\n    wangle::wangle_bootstrap_server_bootstrap\n    wangle::wangle_ssl_ssl_context_config\n    Folly::folly_executors_io_thread_pool_executor\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_signal_handler\n    Folly::folly_io_socket_option_map\n)\n\nproxygen_add_library(proxygen_httpserver_scoped_httpserver\n  EXPORTED_DEPS\n    proxygen_httpserver\n    proxygen_httpserver_httpserver_headers\n    Folly::folly_io_async_ssl_context\n    Folly::folly_system_thread_name\n)\n\nproxygen_add_library(proxygen_httpserver_mocks\n  EXPORTED_DEPS\n    proxygen_httpserver_httpserver_headers\n    Folly::folly_portability_gmock\n)\n\n# =============================================================================\n# Manually maintained section below\n# =============================================================================\n\n# Include granular HQ server targets (unconditional - needed by moxygen)\nadd_subdirectory(samples/hq)\n\n# Backwards compatibility alias: proxygenhttpserver -> proxygen_httpserver\nadd_library(proxygenhttpserver INTERFACE)\ntarget_link_libraries(proxygenhttpserver INTERFACE proxygen_httpserver)\ninstall(TARGETS proxygenhttpserver EXPORT proxygen-exports)\nadd_library(proxygen::proxygenhttpserver ALIAS proxygenhttpserver)\n\n# Backwards compatibility alias for proxygenhqserver\nadd_library(proxygenhqserver INTERFACE)\ntarget_link_libraries(proxygenhqserver INTERFACE\n  proxygen_hq_server\n  proxygen_hq_logger_helper\n)\ninstall(TARGETS proxygenhqserver EXPORT proxygen-exports)\nadd_library(proxygen::proxygenhqserver ALIAS proxygenhqserver)\n\n# Backwards compatibility alias for proxygenhqloggerhelper\nadd_library(proxygenhqloggerhelper INTERFACE)\ntarget_link_libraries(proxygenhqloggerhelper INTERFACE\n  proxygen_hq_logger_helper\n)\ninstall(TARGETS proxygenhqloggerhelper EXPORT proxygen-exports)\nadd_library(proxygen::proxygenhqloggerhelper ALIAS proxygenhqloggerhelper)\n\nif (BUILD_SAMPLES)\n  add_executable(proxygen_push\n    samples/push/PushServer.cpp\n    samples/push/PushRequestHandler.cpp\n  )\n  target_compile_options(\n      proxygen_push PRIVATE ${_PROXYGEN_COMMON_COMPILE_OPTIONS})\n  target_link_libraries(proxygen_push PUBLIC proxygen proxygen_httpserver)\n  install(TARGETS proxygen_push EXPORT proxygen-exports DESTINATION bin)\n\n  add_executable(proxygen_proxy\n    samples/proxy/ProxyServer.cpp\n    samples/proxy/ProxyHandler.cpp\n  )\n  target_compile_options(\n      proxygen_proxy PRIVATE ${_PROXYGEN_COMMON_COMPILE_OPTIONS})\n  target_link_libraries(proxygen_proxy PUBLIC proxygen proxygen_httpserver)\n  install(TARGETS proxygen_proxy EXPORT proxygen-exports DESTINATION bin)\n\n  add_executable(proxygen_static\n    samples/static/StaticServer.cpp\n    samples/static/StaticHandler.cpp\n  )\n  target_compile_options(\n      proxygen_static PRIVATE ${_PROXYGEN_COMMON_COMPILE_OPTIONS})\n  target_link_libraries(proxygen_static PUBLIC proxygen proxygen_httpserver)\n  install(TARGETS proxygen_static EXPORT proxygen-exports DESTINATION bin)\n\n  add_executable(proxygen_echo\n    samples/echo/EchoServer.cpp\n    samples/echo/EchoHandler.cpp\n  )\n  target_compile_options(\n      proxygen_echo PRIVATE ${_PROXYGEN_COMMON_COMPILE_OPTIONS})\n  target_link_libraries(proxygen_echo PUBLIC proxygen proxygen_httpserver)\n  install(TARGETS proxygen_echo EXPORT proxygen-exports DESTINATION bin)\n\n  add_executable(hq\n    samples/hq/main.cpp\n  )\n  target_compile_options(hq PRIVATE ${_PROXYGEN_COMMON_COMPILE_OPTIONS})\n  target_link_libraries(hq\n    PUBLIC\n      proxygen_hq_samples\n      proxygen_hq_server\n      proxygen_transport_persistent_quic_psk_cache\n      proxygen_httpserver\n      Folly::folly_init_init\n      Folly::folly_portability_gflags\n  )\n  install(TARGETS hq EXPORT proxygen-exports DESTINATION bin)\nendif()\n\nfile(\n  GLOB_RECURSE PROXYGEN_HTTPSERVER_HEADERS_TOINSTALL\n  RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}\n  *.h\n)\nlist(FILTER PROXYGEN_HTTPSERVER_HEADERS_TOINSTALL EXCLUDE REGEX tests/)\nforeach(header ${PROXYGEN_HTTPSERVER_HEADERS_TOINSTALL})\n  get_filename_component(header_dir ${header} DIRECTORY)\n  install(FILES ${header} DESTINATION include/proxygen/httpserver/${header_dir})\nendforeach()\n\nadd_subdirectory(tests)\nadd_subdirectory(filters/tests)\n#add_subdirectory(samples/echo/test)\n#add_subdirectory(samples/hq/devious/test)\n"
  },
  {
    "path": "proxygen/httpserver/Filters.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseHandler.h>\n\nnamespace proxygen {\n\n/**\n * Filters are a way to add functionality to HTTPServer without complicating app\n * specific RequestHandler. The basic idea is\n *\n *   App-handler <=> Filter-1 <=> Filter-2 <=> Client\n *\n * The data flows through these filters between client and handler.  They can do\n * things like modify the data flowing through them, can update stats, create\n * traces and even send direct response if they feel like.\n *\n * The default implementation just lets everything pass through.\n */\nclass Filter\n    : public RequestHandler\n    , public ResponseHandler {\n public:\n  explicit Filter(RequestHandler* upstream) : ResponseHandler(upstream) {\n  }\n\n  // Request handler\n  void setResponseHandler(ResponseHandler* handler) noexcept override {\n    // Save downstream handler and pass ourselves as downstream handler\n    downstream_ = handler;\n    txn_ = handler->getTransaction();\n    upstream_->setResponseHandler(this);\n  }\n\n  void onRequest(std::unique_ptr<HTTPMessage> headers) noexcept override {\n    upstream_->onRequest(std::move(headers));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    upstream_->onBody(std::move(body));\n  }\n\n  void onUpgrade(UpgradeProtocol protocol) noexcept override {\n    upstream_->onUpgrade(protocol);\n  }\n\n  void onEOM() noexcept override {\n    upstream_->onEOM();\n  }\n\n  void requestComplete() noexcept override {\n    downstream_ = nullptr;\n    upstream_->requestComplete();\n    delete this;\n  }\n\n  void onError(ProxygenError err) noexcept override {\n    downstream_ = nullptr;\n    upstream_->onError(err);\n    delete this;\n  }\n\n  void onGoaway(ErrorCode code) noexcept override {\n    upstream_->onGoaway(code);\n  }\n\n  void onEgressPaused() noexcept override {\n    upstream_->onEgressPaused();\n  }\n\n  void onEgressResumed() noexcept override {\n    upstream_->onEgressResumed();\n  }\n\n  bool canHandleExpect() noexcept override {\n    return upstream_->canHandleExpect();\n  }\n\n  // Response handler\n  void sendHeaders(HTTPMessage& msg) noexcept override {\n    downstream_->sendHeaders(msg);\n  }\n\n  void sendChunkHeader(size_t len) noexcept override {\n    downstream_->sendChunkHeader(len);\n  }\n\n  void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    downstream_->sendBody(std::move(body));\n  }\n\n  void sendChunkTerminator() noexcept override {\n    downstream_->sendChunkTerminator();\n  }\n\n  void sendEOM() noexcept override {\n    downstream_->sendEOM();\n  }\n\n  void sendAbort(folly::Optional<ErrorCode> errorCode) noexcept override {\n    downstream_->sendAbort(errorCode);\n  }\n\n  void refreshTimeout() noexcept override {\n    downstream_->refreshTimeout();\n  }\n\n  void pauseIngress() noexcept override {\n    downstream_->pauseIngress();\n  }\n\n  void resumeIngress() noexcept override {\n    downstream_->resumeIngress();\n  }\n\n  folly::Expected<ResponseHandler*, ProxygenError> newPushedResponse(\n      PushHandler* handler) noexcept override {\n    return downstream_->newPushedResponse(handler);\n  }\n\n  [[nodiscard]] const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept override {\n    return downstream_->getSetupTransportInfo();\n  }\n\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const override {\n    downstream_->getCurrentTransportInfo(tinfo);\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/HTTPServer.h>\n\n#include <folly/executors/thread_factory/NamedThreadFactory.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServerAcceptor.h>\n#include <proxygen/httpserver/SignalHandler.h>\n#include <proxygen/httpserver/filters/CompressionFilter.h>\n#include <proxygen/httpserver/filters/RejectConnectFilter.h>\n#include <wangle/ssl/SSLContextManager.h>\n\nusing folly::EventBaseManager;\nusing folly::IOThreadPoolExecutor;\nusing folly::IOThreadPoolExecutorBase;\nusing folly::ThreadPoolExecutor;\n\nnamespace proxygen {\n\nclass AcceptorFactory : public wangle::AcceptorFactory {\n public:\n  AcceptorFactory(std::shared_ptr<HTTPServerOptions> options,\n                  std::shared_ptr<HTTPCodecFactory> codecFactory,\n                  std::shared_ptr<const AcceptorConfiguration> config,\n                  HTTPSession::InfoCallback* sessionInfoCb)\n      : options_(options),\n        codecFactory_(codecFactory),\n        config_(std::move(config)),\n        sessionInfoCb_(sessionInfoCb) {\n  }\n  std::shared_ptr<wangle::Acceptor> newAcceptor(\n      folly::EventBase* eventBase) override {\n    auto acc = std::shared_ptr<HTTPServerAcceptor>(\n        HTTPServerAcceptor::make(config_, *options_, codecFactory_).release());\n    if (sessionInfoCb_) {\n      acc->setSessionInfoCallback(sessionInfoCb_);\n    }\n    acc->init(nullptr, eventBase);\n    return acc;\n  }\n\n private:\n  std::shared_ptr<HTTPServerOptions> options_;\n  std::shared_ptr<HTTPCodecFactory> codecFactory_;\n  std::shared_ptr<const AcceptorConfiguration> config_;\n  HTTPSession::InfoCallback* sessionInfoCb_;\n};\n\nHTTPServer::HTTPServer(HTTPServerOptions options)\n    : options_(std::make_shared<HTTPServerOptions>(std::move(options))) {\n\n  if (options_->threads == 0) {\n    options_->threads = folly::available_concurrency();\n  }\n\n  // Insert a filter to fail all the CONNECT request, if required\n  if (!options_->supportsConnect) {\n    options_->handlerFactories.insert(\n        options_->handlerFactories.begin(),\n        std::make_unique<RejectConnectFilterFactory>());\n  }\n\n  // Add Content Compression filter (gzip and maybe zstd), if needed. Should be\n  // final filter\n  if (options_->enableContentCompression) {\n    CompressionFilterFactory::Options opts;\n    opts.minimumCompressionSize = options_->contentCompressionMinimumSize;\n    opts.zlibCompressionLevel = options_->contentCompressionLevel;\n    opts.compressibleContentTypes = std::make_shared<std::set<std::string>>(\n        options_->contentCompressionTypes);\n    opts.enableGzip = options_->enableGzipCompression;\n    if (options_->enableZstdCompression) {\n      opts.enableZstd = options_->enableZstdCompression;\n      opts.independentChunks = options_->useZstdIndependentChunks;\n      opts.zstdCompressionLevel = options_->zstdContentCompressionLevel;\n    }\n    options_->handlerFactories.insert(\n        options_->handlerFactories.begin(),\n        std::make_unique<CompressionFilterFactory>(opts));\n  }\n}\n\nHTTPServer::~HTTPServer() {\n  CHECK(!mainEventBase_) << \"Forgot to stop() server?\";\n}\n\nvoid HTTPServer::bind(std::vector<IPConfig>&& addrs) {\n  addresses_ = std::move(addrs);\n}\n\nvoid HTTPServer::bind(std::vector<IPConfig> const& addrs) {\n  addresses_ = addrs;\n}\n\nclass HandlerCallbacks : public IOThreadPoolExecutorBase::IOObserver {\n public:\n  explicit HandlerCallbacks(std::shared_ptr<HTTPServerOptions> options)\n      : options_(options) {\n  }\n\n  void registerEventBase(folly::EventBase& evb) noexcept override {\n    evb.runInEventBaseThread([&evb, this]() {\n      for (auto& factory : options_->handlerFactories) {\n        factory->onServerStart(&evb);\n      }\n    });\n  }\n\n  void unregisterEventBase(folly::EventBase& evb) noexcept override {\n    evb.runInEventBaseThread([this]() {\n      for (auto& factory : options_->handlerFactories) {\n        factory->onServerStop();\n      }\n    });\n  }\n\n private:\n  std::shared_ptr<HTTPServerOptions> options_;\n};\n\nfolly::Expected<folly::Unit, std::exception_ptr> HTTPServer::startTcpServer(\n    const std::function<std::shared_ptr<wangle::AcceptorFactory>(\n        AcceptorFactoryConfig)>& getAcceptorFactory,\n    std::shared_ptr<folly::IOThreadPoolExecutorBase> ioExecutor) {\n  auto accExe = std::make_shared<IOThreadPoolExecutor>(1);\n  if (!ioExecutor) {\n    ioExecutor = std::make_shared<IOThreadPoolExecutor>(\n        options_->threads,\n        std::make_shared<folly::NamedThreadFactory>(\"HTTPSrvExec\"));\n  }\n  auto exeObserver = std::make_shared<HandlerCallbacks>(options_);\n  // Observer has to be set before bind(), so onServerStart() callbacks run\n  ioExecutor->addObserver(exeObserver);\n\n  try {\n    FOR_EACH_RANGE(i, 0, addresses_.size()) {\n      auto accConfig = HTTPServerAcceptor::makeConfig(addresses_[i], *options_);\n      auto codecFactory = addresses_[i].codecFactory;\n      auto acceptorFactory =\n          getAcceptorFactory\n              ? getAcceptorFactory({.accConfig = accConfig,\n                                    .codecFactory = std::move(codecFactory)})\n              : std::make_shared<AcceptorFactory>(\n                    options_, codecFactory, accConfig, sessionInfoCb_);\n      bootstrap_.emplace_back();\n      bootstrap_[i].childHandler(acceptorFactory);\n      bootstrap_[i].useZeroCopy(options_->useZeroCopy);\n      if (accConfig->enableTCPFastOpen) {\n        // We need to do this because wangle's bootstrap has 2 acceptor\n        // configs and the socketConfig gets passed to the SocketFactory. The\n        // number of configs should really be one, and when that happens, we\n        // can remove this code path.\n        bootstrap_[i].socketConfig.enableTCPFastOpen = true;\n        bootstrap_[i].socketConfig.fastOpenQueueSize =\n            accConfig->fastOpenQueueSize;\n      }\n      bootstrap_[i].socketConfig.enableReuseAddr = accConfig->enableReuseAddr;\n      bootstrap_[i].group(accExe, ioExecutor);\n      if (accConfig->reusePort) {\n        bootstrap_[i].setReusePort(true);\n      }\n      if (options_->preboundSockets_.size() > i) {\n        bootstrap_[i].bind(std::move(options_->preboundSockets_[i]));\n      } else {\n        bootstrap_[i].bind(addresses_[i].address);\n      }\n    }\n  } catch (const std::exception&) {\n    stop();\n\n    return folly::makeUnexpected(std::current_exception());\n  }\n\n  return folly::unit;\n}\n\nvoid HTTPServer::start(\n    std::function<void()> onSuccess,\n    std::function<void(std::exception_ptr)> onError,\n    std::function<std::shared_ptr<wangle::AcceptorFactory>(\n        AcceptorFactoryConfig)> getAcceptorFactory,\n    std::shared_ptr<folly::IOThreadPoolExecutorBase> ioExecutor) {\n  mainEventBase_ = EventBaseManager::get()->getEventBase();\n\n  auto tcpStarted = startTcpServer(getAcceptorFactory, ioExecutor);\n  if (tcpStarted.hasError()) {\n    if (onError) {\n      onError(tcpStarted.error());\n      return;\n    }\n    std::rethrow_exception(tcpStarted.error());\n  }\n\n  // Install signal handler if required\n  if (!options_->shutdownOn.empty()) {\n    signalHandler_ = std::make_unique<SignalHandler>(this);\n    signalHandler_->install(options_->shutdownOn);\n  }\n\n  // Start the main event loop.\n  if (onSuccess) {\n    mainEventBase_->runInLoop([onSuccess(std::move(onSuccess))]() {\n      // IMPORTANT: Since we may be racing with stop(), we must assume that\n      // mainEventBase_ can become null the moment that onSuccess is called,\n      // so this **has** to be queued to run from inside loopForever().\n      onSuccess();\n    });\n  }\n  mainEventBase_->loopForever();\n}\n\nvoid HTTPServer::stopListening() {\n  for (auto& bootstrap : bootstrap_) {\n    bootstrap.stop();\n  }\n}\n\nvoid HTTPServer::stop() {\n  stopListening();\n\n  for (auto& bootstrap : bootstrap_) {\n    bootstrap.join();\n  }\n\n  if (signalHandler_) {\n    signalHandler_.reset();\n  }\n\n  if (mainEventBase_) {\n    // This HTTPServer object may be destoyed by the main thread once\n    // terminateLoopSoon() is called, so terminateLoopSoon() should be the\n    // last operation here.\n    std::exchange(mainEventBase_, nullptr)->terminateLoopSoon();\n  }\n}\n\nconst std::vector<const folly::AsyncSocketBase*> HTTPServer::getSockets()\n    const {\n\n  std::vector<const folly::AsyncSocketBase*> sockets;\n  FOR_EACH_RANGE(i, 0, bootstrap_.size()) {\n    auto& bootstrapSockets = bootstrap_[i].getSockets();\n    FOR_EACH_RANGE(j, 0, bootstrapSockets.size()) {\n      sockets.push_back(bootstrapSockets[j].get());\n    }\n  }\n\n  return sockets;\n}\n\nint HTTPServer::getListenSocket() const {\n  if (bootstrap_.size() == 0) {\n    return -1;\n  }\n\n  auto& bootstrapSockets = bootstrap_[0].getSockets();\n  if (bootstrapSockets.size() == 0) {\n    return -1;\n  }\n\n  auto serverSocket =\n      std::dynamic_pointer_cast<folly::AsyncServerSocket>(bootstrapSockets[0]);\n  auto socketFds = serverSocket->getNetworkSockets();\n  if (socketFds.size() == 0) {\n    return -1;\n  }\n\n  return socketFds[0].toFd();\n}\n\nvoid HTTPServer::updateTLSCredentials() {\n  forEachAcceptor([&](wangle::Acceptor* acceptor) {\n    if (!acceptor || !acceptor->isSSL()) {\n      return;\n    }\n    auto evb = acceptor->getEventBase();\n    if (!evb) {\n      return;\n    }\n    evb->runInEventBaseThread(\n        [acceptor] { acceptor->reloadSSLContextConfigs(); });\n  });\n}\n\nvoid HTTPServer::updateTicketSeeds(wangle::TLSTicketKeySeeds seeds) {\n  forEachAcceptor([&](wangle::Acceptor* acceptor) {\n    if (!acceptor || !acceptor->isSSL()) {\n      return;\n    }\n    auto evb = acceptor->getEventBase();\n    if (!evb) {\n      return;\n    }\n    evb->runInEventBaseThread([acceptor, seeds] {\n      acceptor->setTLSTicketSecrets(\n          seeds.oldSeeds, seeds.currentSeeds, seeds.newSeeds);\n    });\n  });\n}\n\nvoid HTTPServer::forEachAcceptor(\n    const std::function<void(wangle::Acceptor* acceptor)>& fn) {\n  for (auto& bootstrap : bootstrap_) {\n    bootstrap.forEachWorker(fn);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPServer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/EventBase.h>\n#include <proxygen/httpserver/HTTPServerOptions.h>\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/services/AcceptorConfiguration.h>\n#include <thread>\n#include <wangle/bootstrap/ServerBootstrap.h>\n#include <wangle/ssl/SSLContextConfig.h>\n\nnamespace proxygen {\n\nclass SignalHandler;\nclass HTTPServerAcceptor;\n\n/**\n * HTTPServer based on proxygen http libraries\n */\nclass HTTPServer final {\n public:\n  /**\n   * Protocol identifies the default HTTP protocol implementation that an\n   * IPConfig will use for plaintext connections.\n   *\n   * Connections established through TLS will use the protocol negotiated\n   * through ALPN. If no ALPN is given, HTTP/1 (with an option to upgrade\n   * to HTTP/2) is used, and this field is ignored.\n   */\n  enum class Protocol : uint8_t {\n    HTTP,\n    HTTP2,\n  };\n\n  struct IPConfig {\n    IPConfig(folly::SocketAddress a,\n             Protocol p,\n             std::shared_ptr<HTTPCodecFactory> c = nullptr)\n        : address(a), protocol(p), codecFactory(c) {\n    }\n\n    folly::SocketAddress address;\n    Protocol protocol;\n    std::shared_ptr<HTTPCodecFactory> codecFactory;\n    std::vector<wangle::SSLContextConfig> sslConfigs;\n\n    /*\n     * Sets the initial ticket seeds to use when starting the HTTPServer.\n     * Ticket seeds are used to generate the session ticket encryption keys\n     * for ticket resumption. When using session tickets, it is important\n     * to change them and keep them updated, see updateTicketSeeds to keep\n     * seeds up to date.\n     */\n    folly::Optional<wangle::TLSTicketKeySeeds> ticketSeeds;\n\n    /*\n     * Whether to allow an insecure connection on a secure port.\n     * This should be used in very few cases where a HTTP server needs to\n     * support insecure and secure connections on the same address.\n     */\n    bool allowInsecureConnectionsOnSecureServer{false};\n    bool enableTCPFastOpen{false};\n    /**\n     * Maximum queue size of pending fast open connections.\n     */\n    uint32_t fastOpenQueueSize{10000};\n\n    /*\n     * Determines if this server does strict checking when loading SSL contexts.\n     */\n    bool strictSSL{true};\n\n    folly::Optional<folly::SocketOptionMap> acceptorSocketOptions;\n  };\n\n  struct AcceptorFactoryConfig {\n    std::shared_ptr<const AcceptorConfiguration> accConfig;\n    std::shared_ptr<HTTPCodecFactory> codecFactory;\n  };\n\n  /**\n   * Create a new HTTPServer\n   */\n  explicit HTTPServer(HTTPServerOptions options);\n  ~HTTPServer();\n\n  /**\n   * Configure server to bind to the following addresses.\n   * The addresses you bind manually and provide to HTTPServerOptions\n   * should be placed at the head of this vector before other addresses.\n   *\n   * Actual bind happens in `start` function.\n   *\n   * Can be called from any thread.\n   */\n  void bind(std::vector<IPConfig>&& addrs);\n  void bind(std::vector<IPConfig> const& addrs);\n\n  /**\n   * Start HTTPServer.\n   *\n   * Note this is a blocking call and the current thread will be used to listen\n   * for incoming connections. Throws exception if something goes wrong (say\n   * somebody else is already listening on that socket).\n   *\n   * `onSuccess` callback will be invoked from the event loop which shows that\n   * all the setup was successfully done.\n   *\n   * `onError` callback will be invoked if some errors occurs while starting the\n   * server instead of throwing exception.\n   *\n   * `getAcceptorFactory` will be used to get the acceptor factory if it is set.\n   * Otherwise, we will create an HTTPAcceptorFactory.\n   *\n   * `ioExecutor` will be used for for IO threads if it is set. Otherwise, we\n   * will create a new IOThreadPoolExectutor for IO threads.\n   */\n  void start(\n      std::function<void()> onSuccess = nullptr,\n      std::function<void(std::exception_ptr)> onError = nullptr,\n      std::function<std::shared_ptr<wangle::AcceptorFactory>(\n          AcceptorFactoryConfig)> getAcceptorFactory = nullptr,\n      std::shared_ptr<folly::IOThreadPoolExecutorBase> ioExecutor = nullptr);\n\n  /**\n   * Stop listening on bound ports. (Stop accepting new work).\n   * It does not wait for pending work to complete.\n   * You must still invoke stop() before destroying the server.\n   * You do NOT need to invoke this before calling stop().\n   * This can be called from any thread, and it is idempotent.\n   * However, it may only be called **after** start() has called onSuccess.\n   */\n  void stopListening();\n\n  /**\n   * Stop HTTPServer.\n   *\n   * Can be called from any thread, but only after start() has called\n   * onSuccess.  Server will stop listening for new connections and drop all\n   * connections immediately. Before calling stop(), you may want to make sure\n   * to properly drain and close on-going requests/connections.\n   *\n   * TODO: Separate method to do graceful shutdown?\n   */\n  void stop();\n\n  /**\n   * Get the list of addresses server is listening on. Empty if sockets are not\n   * bound yet.\n   */\n  [[nodiscard]] std::vector<IPConfig> addresses() const {\n    return addresses_;\n  }\n\n  /**\n   * Get the sockets the server is currently bound to.\n   */\n  [[nodiscard]] const std::vector<const folly::AsyncSocketBase*> getSockets()\n      const;\n\n  void setSessionInfoCallback(HTTPSession::InfoCallback* cb) {\n    sessionInfoCb_ = cb;\n  }\n\n  /**\n   * Returns a file descriptor associated with the listening socket\n   */\n  [[nodiscard]] int getListenSocket() const;\n\n  /**\n   * Re-reads the certificate / key pair for all SSL vips on all acceptors\n   */\n  void updateTLSCredentials();\n\n  /**\n   * Updates ticket seeds for the HTTPServer for all the VIPs.\n   */\n  void updateTicketSeeds(wangle::TLSTicketKeySeeds seeds);\n\n  /**\n   * Allows the caller to apply a function to each acceptor the server is\n   * responsible for\n   */\n  void forEachAcceptor(\n      const std::function<void(wangle::Acceptor* acceptor)>& fn);\n\n protected:\n  /**\n   * Start TCP HTTP server.\n   *\n   * @param getAcceptorFactory - provides the acceptor factory to use. If it is\n   * null we will create an HTTPAcceptorFactory.\n   * @param executor - io executor to use for IO threads. If it is null, we will\n   * create one to use.\n   */\n  folly::Expected<folly::Unit, std::exception_ptr> startTcpServer(\n      const std::function<std::shared_ptr<wangle::AcceptorFactory>(\n          AcceptorFactoryConfig)>& getAcceptorFactory,\n      std::shared_ptr<folly::IOThreadPoolExecutorBase> ioExecutor);\n\n private:\n  std::shared_ptr<HTTPServerOptions> options_;\n\n  /**\n   * Event base in which we binded server sockets.\n   */\n  folly::EventBase* mainEventBase_{nullptr};\n\n  /**\n   * Optional signal handlers on which we should shutdown server\n   */\n  std::unique_ptr<SignalHandler> signalHandler_;\n\n  /**\n   * Addresses we are listening on\n   */\n  std::vector<IPConfig> addresses_;\n  std::vector<wangle::ServerBootstrap<wangle::DefaultPipeline>> bootstrap_;\n\n  /**\n   * Callback for session create/destruction\n   */\n  HTTPSession::InfoCallback* sessionInfoCb_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPServerAcceptor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/HTTPServerAcceptor.h>\n\n#include <folly/ExceptionString.h>\n#include <proxygen/httpserver/RequestHandlerAdaptor.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n\nusing folly::SocketAddress;\n\nnamespace proxygen {\n\nstd::shared_ptr<const AcceptorConfiguration> HTTPServerAcceptor::makeConfig(\n    const HTTPServer::IPConfig& ipConfig, const HTTPServerOptions& opts) {\n\n  auto conf = std::make_shared<AcceptorConfiguration>();\n  conf->bindAddress = ipConfig.address;\n  conf->connectionIdleTimeout = opts.idleTimeout;\n  conf->transactionIdleTimeout = opts.idleTimeout;\n  conf->initialReceiveWindow = opts.initialReceiveWindow;\n  conf->receiveStreamWindowSize = opts.receiveStreamWindowSize;\n  conf->receiveSessionWindowSize = opts.receiveSessionWindowSize;\n  conf->acceptBacklog = opts.listenBacklog;\n  conf->maxConcurrentIncomingStreams = opts.maxConcurrentIncomingStreams;\n\n  if (ipConfig.protocol == HTTPServer::Protocol::HTTP2) {\n    conf->plaintextProtocol = http2::kProtocolCleartextString;\n  }\n\n  conf->sslContextConfigs = ipConfig.sslConfigs;\n  conf->strictSSL = ipConfig.strictSSL;\n  conf->allowInsecureConnectionsOnSecureServer =\n      ipConfig.allowInsecureConnectionsOnSecureServer;\n  conf->enableTCPFastOpen = ipConfig.enableTCPFastOpen;\n  conf->fastOpenQueueSize = ipConfig.fastOpenQueueSize;\n  conf->enableReuseAddr = opts.enableReuseAddr;\n  if (ipConfig.ticketSeeds) {\n    conf->initialTicketSeeds = *ipConfig.ticketSeeds;\n  }\n  if (ipConfig.acceptorSocketOptions.has_value()) {\n    conf->setSocketOptions(ipConfig.acceptorSocketOptions.value());\n    auto it = ipConfig.acceptorSocketOptions->find({SOL_SOCKET, SO_REUSEPORT});\n    if (it != ipConfig.acceptorSocketOptions->end() && it->second != 0) {\n      conf->reusePort = true;\n    }\n  }\n  return conf;\n}\n\nstd::unique_ptr<HTTPServerAcceptor> HTTPServerAcceptor::make(\n    std::shared_ptr<const AcceptorConfiguration> conf,\n    const HTTPServerOptions& opts,\n    const std::shared_ptr<HTTPCodecFactory>& codecFactory) {\n  // Create a copy of the filter chain in reverse order since we need to create\n  // Handlers in that order.\n  std::vector<RequestHandlerFactory*> handlerFactories;\n  handlerFactories.reserve(opts.handlerFactories.size());\n  for (auto& f : opts.handlerFactories) {\n    handlerFactories.push_back(f.get());\n  }\n  std::reverse(handlerFactories.begin(), handlerFactories.end());\n\n  return std::unique_ptr<HTTPServerAcceptor>(new HTTPServerAcceptor(\n      std::move(conf), codecFactory, handlerFactories, opts));\n}\n\nHTTPServerAcceptor::HTTPServerAcceptor(\n    std::shared_ptr<const AcceptorConfiguration> conf,\n    const std::shared_ptr<HTTPCodecFactory>& codecFactory,\n    std::vector<RequestHandlerFactory*> handlerFactories,\n    const HTTPServerOptions& options)\n    : HTTPSessionAcceptor(std::move(conf), codecFactory),\n      serverOptions_(options),\n      handlerFactories_(handlerFactories) {\n}\n\nvoid HTTPServerAcceptor::setCompletionCallback(std::function<void()> f) {\n  completionCallback_ = f;\n}\n\nHTTPServerAcceptor::~HTTPServerAcceptor() = default;\n\nHTTPTransactionHandler* HTTPServerAcceptor::newHandler(\n    HTTPTransaction& txn, HTTPMessage* msg) noexcept {\n\n  SocketAddress clientAddr, vipAddr;\n  txn.getPeerAddress(clientAddr);\n  txn.getLocalAddress(vipAddr);\n  msg->setClientAddress(clientAddr);\n  msg->setDstAddress(vipAddr);\n\n  // Create filters chain\n  RequestHandler* h = nullptr;\n  for (auto& factory : handlerFactories_) {\n    h = factory->onRequest(h, msg);\n  }\n\n  return new RequestHandlerAdaptor(h);\n}\n\nvoid HTTPServerAcceptor::onNewConnection(\n    folly::AsyncTransport::UniquePtr sock,\n    const SocketAddress* address,\n    const std::string& nextProtocolName,\n    SecureTransportType secureTransportType,\n    const wangle::TransportInfo& tinfo) {\n  auto& filter = serverOptions_.newConnectionFilter;\n  if (filter) {\n    try {\n      filter(sock.get(), address, nextProtocolName, secureTransportType, tinfo);\n    } catch (const std::exception& e) {\n      sock->closeWithReset();\n      LOG(INFO) << \"Exception filtering new socket: \" << folly::exceptionStr(e);\n      return;\n    }\n  }\n\n  const auto& func = serverOptions_.zeroCopyEnableFunc;\n  if (func && sock) {\n    sock->setZeroCopy(true);\n    sock->setZeroCopyEnableFunc(func);\n  }\n\n  HTTPSessionAcceptor::onNewConnection(\n      std::move(sock), address, nextProtocolName, secureTransportType, tinfo);\n}\n\nvoid HTTPServerAcceptor::onConnectionsDrained() {\n  if (completionCallback_) {\n    completionCallback_();\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPServerAcceptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/HTTPServerOptions.h>\n#include <proxygen/lib/http/session/HTTPSessionAcceptor.h>\n\nnamespace proxygen {\n\nclass HTTPServerAcceptor final : public HTTPSessionAcceptor {\n public:\n  static std::shared_ptr<const AcceptorConfiguration> makeConfig(\n      const HTTPServer::IPConfig& ipConfig, const HTTPServerOptions& opts);\n\n  static std::unique_ptr<HTTPServerAcceptor> make(\n      std::shared_ptr<const AcceptorConfiguration> conf,\n      const HTTPServerOptions& opts,\n      const std::shared_ptr<HTTPCodecFactory>& codecFactory = nullptr);\n\n  /**\n   * Invokes the given method when all the connections are drained\n   */\n  void setCompletionCallback(std::function<void()> f);\n\n  ~HTTPServerAcceptor() override;\n\n private:\n  HTTPServerAcceptor(std::shared_ptr<const AcceptorConfiguration> conf,\n                     const std::shared_ptr<HTTPCodecFactory>& codecFactory,\n                     std::vector<RequestHandlerFactory*> handlerFactories,\n                     const HTTPServerOptions& options);\n\n  // HTTPSessionAcceptor\n  HTTPTransaction::Handler* newHandler(HTTPTransaction& txn,\n                                       HTTPMessage* msg) noexcept override;\n\n  void onNewConnection(folly::AsyncTransport::UniquePtr sock,\n                       const folly::SocketAddress* address,\n                       const std::string& nextProtocolName,\n                       wangle::SecureTransportType secureTransportType,\n                       const wangle::TransportInfo& tinfo) override;\n\n  void onConnectionsDrained() override;\n\n  const HTTPServerOptions& serverOptions_;\n  std::function<void()> completionCallback_;\n  const std::vector<RequestHandlerFactory*> handlerFactories_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPServerOptions.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <csignal>\n#include <folly/Function.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/async/AsyncServerSocket.h>\n#include <proxygen/httpserver/Filters.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\nnamespace proxygen {\n\n/**\n * Configuration options for HTTPServer\n *\n * XXX: Provide a helper that can convert thrift/json to this config\n *      directly. We keep this object type-safe.\n */\nclass HTTPServerOptions {\n public:\n  /**\n   * Type of function to run inside onNewConnection() of acceptors.\n   * If the function throws, the socket will be closed immediately. Useful\n   * for validating client cert before processing the request.\n   */\n  using NewConnectionFilter =\n      folly::Function<void(const folly::AsyncTransport* /* sock */,\n                           const folly::SocketAddress* /* address */,\n                           const std::string& /* nextProtocolName */,\n                           SecureTransportType /* secureTransportType */,\n                           const wangle::TransportInfo& /* tinfo */) const>;\n\n  /**\n   * Number of threads to start to handle requests. Note that this excludes\n   * the thread you call `HTTPServer.start()` in. 0 means 1 thread per-cpu.\n   *\n   * XXX: Put some perf numbers to help user decide how many threads to\n   *      create.\n   */\n  size_t threads = 1;\n\n  /**\n   * Chain of RequestHandlerFactory that are used to create RequestHandler\n   * which handles requests.\n   *\n   * You can do something like -\n   *\n   * handlerFactories = RequestHandlerChain()\n   *    .addThen<StatsFilter>()\n   *    .addThen<TraceFilter>()\n   *    .addThen<AccessLogFilter>()\n   *    .addThen<AppSpecificHandler>()\n   *    .build();\n   */\n  std::vector<std::unique_ptr<RequestHandlerFactory>> handlerFactories;\n\n  /**\n   * This idle timeout serves two purposes -\n   *\n   * 1. How long to keep idle connections around before throwing them away.\n   *\n   * 2. If it takes client more than this time to send request, we fail the\n   *    request.\n   *\n   * XXX: Maybe have separate time limit for both?\n   */\n  std::chrono::milliseconds idleTimeout{60000};\n\n  /**\n   * TCP server socket backlog to start with.\n   */\n  uint32_t listenBacklog{1024};\n\n  /**\n   * --DEPRECATED-- Enable cleartext upgrades to HTTP/2\n   */\n  bool h2cEnabled{false};\n\n  /**\n   * Signals on which to shutdown the server. Mostly you will want\n   * {SIGINT, SIGTERM}. Note, if you have multiple deamons running or you want\n   * to have a separate signal handler, leave this empty and handle signals\n   * yourself.\n   */\n  std::vector<int> shutdownOn{};\n\n  /**\n   * Set to true if you want to support CONNECT request. Most likely you\n   * don't want that.\n   */\n  bool supportsConnect{false};\n\n  /**\n   * Flow control configuration for the acceptor\n   */\n  size_t initialReceiveWindow{65536};\n  size_t receiveStreamWindowSize{65536};\n  size_t receiveSessionWindowSize{65536};\n\n  /**\n   * The maximum number of transactions the remote could initiate\n   * per connection on protocols that allow multiplexing.\n   */\n  uint32_t maxConcurrentIncomingStreams{100};\n\n  /**\n   * Set to true to enable content compression. Currently false for\n   * backwards compatibility.  If enabled, by default gzip will be enabled\n   * and zstd will be disabed.\n   */\n  bool enableContentCompression{false};\n\n  /**\n   * Set to true to enable zstd compression. Currently false for\n   * backwards compatibility.\n   * Only applicable if enableContentCompression is set to true.\n   */\n  bool enableZstdCompression{false};\n\n  /**\n   * Set to true to compress independent chunks for zstd.\n   * Only applicable if enableContentCompression and enableZstdCompression are\n   * set to true.\n   */\n  bool useZstdIndependentChunks{false};\n\n  /**\n   * Set to false to disable GZIP compression.  This can be helpful for services\n   * with long-lived streams for which GZIP can result in high memory usage.\n   * NOTE: this does not override `enableContentCompression`.\n   */\n  bool enableGzipCompression{true};\n\n  /**\n   * Requests smaller than the specified number of bytes will not be compressed\n   */\n  uint64_t contentCompressionMinimumSize{1000};\n\n  /**\n   * Zlib compression level, valid values are -1(Default) to 9(Slower).\n   * 4 or 6 are a good balance between compression level and cpu usage.\n   */\n  int contentCompressionLevel{-1};\n\n  /**\n   * Zstd compression level, valid values are -5 to 22.\n   * As level increases, compression ratio improves at the cost\n   * of higher cpu usage.\n   * Default is 8, which was found to be a good balance\n   * between compression ratio and cpu usage.\n   */\n  int zstdContentCompressionLevel{8};\n\n  /**\n   * Enable support for pub-sub extension.\n   */\n  bool enableExHeaders{false};\n\n  /**\n   * Content types to compress, all entries as lowercase\n   */\n  std::set<std::string> contentCompressionTypes = {\n      \"application/javascript\",\n      \"application/json\",\n      \"application/x-javascript\",\n      \"application/xhtml+xml\",\n      \"application/xml\",\n      \"application/xml+rss\",\n      \"text/css\",\n      \"text/html\",\n      \"text/javascript\",\n      \"text/plain\",\n      \"text/xml\",\n  };\n\n  /**\n   * This holds sockets already bound to addresses that the server\n   * will listen on and will be empty once the server starts.\n   */\n  std::vector<folly::AsyncServerSocket::UniquePtr> preboundSockets_;\n\n  /**\n   * Bind to existing file descriptor(s).\n   * AsyncServerSocket can handle multiple fds and they can be provided\n   * as a vector here.\n   */\n  void useExistingSocket(folly::AsyncServerSocket::UniquePtr socket) {\n    preboundSockets_.push_back(std::move(socket));\n  }\n\n  void useExistingSocket(int socketFd) {\n    useExistingSockets({socketFd});\n  }\n\n  void useExistingSockets(const std::vector<int>& socketFds) {\n    folly::AsyncServerSocket::UniquePtr socket(new folly::AsyncServerSocket);\n\n    std::vector<folly::NetworkSocket> sockets;\n    sockets.reserve(socketFds.size());\n    for (auto s : socketFds) {\n      sockets.push_back(folly::NetworkSocket::fromFd(s));\n    }\n    socket->useExistingSockets(sockets);\n    useExistingSocket(std::move(socket));\n  }\n\n  /**\n   * Invoked after a new connection is created. Drop connection if the function\n   * throws any exception.\n   */\n  NewConnectionFilter newConnectionFilter;\n\n  /**\n   * Use zero copy socket option\n   */\n  bool useZeroCopy{false};\n\n  /**\n   * Allow multiple sockets to bind to the same address:port. Enabled by\n   * default.\n   */\n  bool enableReuseAddr{true};\n\n  /**\n   *  zerocopy enable function\n   */\n  folly::AsyncWriter::ZeroCopyEnableFunc zeroCopyEnableFunc;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/HTTPTransactionHandlerAdaptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\n/**\n * If you have an HTTPTransactionHandler, you can use it as a RequestHandler\n * by wrapping it in a HTTPTransactionHandlerAdaptor.\n */\nclass HTTPTransactionHandlerAdaptor : public RequestHandler {\n public:\n  explicit HTTPTransactionHandlerAdaptor(HTTPTransactionHandler* handler)\n      : handler_(handler) {\n  }\n\n  void onRequest(std::unique_ptr<HTTPMessage> headers) noexcept override {\n    // Note: HTTPTransactionHandlerAdaptor's will bypass any response filters.\n    // They write directly to the transaction\n    auto txn = downstream_->getTransaction();\n    // I need the non-const original handler\n    auto origHandler = const_cast<HTTPTransaction::Handler*>(txn->getHandler());\n    handler_->setTransaction(txn);\n    txn->setHandler(origHandler);\n\n    handler_->onHeadersComplete(std::move(headers));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    handler_->onBody(std::move(body));\n  }\n\n  void onUpgrade(proxygen::UpgradeProtocol prot) noexcept override {\n    handler_->onUpgrade(prot);\n  }\n\n  void onEOM() noexcept override {\n    handler_->onEOM();\n  }\n\n  void requestComplete() noexcept override {\n    handler_->detachTransaction();\n    delete this;\n  }\n\n  void onError(ProxygenError err) noexcept override {\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                     \"RequestHandler error\");\n    ex.setProxygenError(err);\n    handler_->onError(ex);\n    handler_->detachTransaction();\n    delete this;\n  }\n\n  void onEgressPaused() noexcept override {\n    handler_->onEgressPaused();\n  }\n\n  void onEgressResumed() noexcept override {\n    handler_->onEgressResumed();\n  }\n  ~HTTPTransactionHandlerAdaptor() override = default;\n\n private:\n  HTTPTransactionHandler* handler_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/Mocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n#include <proxygen/httpserver/ResponseHandler.h>\n\nnamespace proxygen {\n\n/**\n * Mocks to help with testing\n */\n\nclass MockResponseHandler : public ResponseHandler {\n public:\n  explicit MockResponseHandler(RequestHandler* h) : ResponseHandler(h) {\n  }\n#ifdef __clang__\n#pragma clang diagnostic push\n#if __clang_major__ > 3 || __clang_minor__ >= 6\n#pragma clang diagnostic ignored \"-Winconsistent-missing-override\"\n#endif\n#endif\n\n  MOCK_METHOD((void), pauseIngress, (), (noexcept));\n  MOCK_METHOD((void), refreshTimeout, (), (noexcept));\n  MOCK_METHOD((void), resumeIngress, (), (noexcept));\n  MOCK_METHOD((void),\n              sendAbort,\n              (folly::Optional<ErrorCode> errorCode),\n              (noexcept));\n  MOCK_METHOD((void), sendBody, (std::shared_ptr<folly::IOBuf>), (noexcept));\n  MOCK_METHOD((void), sendChunkHeader, (size_t), (noexcept));\n  MOCK_METHOD((void), sendChunkTerminator, (), (noexcept));\n  MOCK_METHOD((void), sendEOM, (), (noexcept));\n  MOCK_METHOD((void), sendHeaders, (HTTPMessage&), (noexcept));\n  MOCK_METHOD((void), sendTrailers, (const HTTPHeaders&), (noexcept));\n  MOCK_METHOD((folly::Expected<ResponseHandler*, ProxygenError>),\n              newPushedResponse,\n              (PushHandler*),\n              (noexcept));\n  MOCK_METHOD(void, getCurrentTransportInfo, (wangle::TransportInfo*), (const));\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept override {\n    return transportInfo;\n  }\n\n  void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    if (body) {\n      sendBody(std::shared_ptr<folly::IOBuf>(body.release()));\n    } else {\n      sendBody(std::shared_ptr<folly::IOBuf>(nullptr));\n    }\n  }\n\n  wangle::TransportInfo transportInfo;\n};\n\nclass MockRequestHandler : public RequestHandler {\n public:\n#ifdef __clang__\n#pragma clang diagnostic push\n#if __clang_major__ > 3 || __clang_minor__ >= 6\n#pragma clang diagnostic ignored \"-Winconsistent-missing-override\"\n#endif\n#endif\n  MOCK_METHOD((bool), canHandleExpect, (), (noexcept));\n  MOCK_METHOD((void), onBody, (std::shared_ptr<folly::IOBuf>), (noexcept));\n  MOCK_METHOD((void), onEOM, (), (noexcept));\n  MOCK_METHOD((void), onEgressPaused, (), (noexcept));\n  MOCK_METHOD((void), onEgressResumed, (), (noexcept));\n  MOCK_METHOD((void), onError, (ProxygenError), (noexcept));\n  MOCK_METHOD((void), onGoaway, (ErrorCode), (noexcept));\n  MOCK_METHOD((void), onRequest, (std::shared_ptr<HTTPMessage>), (noexcept));\n  MOCK_METHOD((void), onUpgrade, (UpgradeProtocol), (noexcept));\n  MOCK_METHOD((void), requestComplete, (), (noexcept));\n  MOCK_METHOD((void), setResponseHandler, (ResponseHandler*), (noexcept));\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n  void onRequest(std::unique_ptr<HTTPMessage> headers) noexcept override {\n    onRequest(std::shared_ptr<HTTPMessage>(headers.release()));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    if (body) {\n      onBody(std::shared_ptr<folly::IOBuf>(body.release()));\n    } else {\n      onBody(std::shared_ptr<folly::IOBuf>(nullptr));\n    }\n  }\n};\n\nclass MockRequestHandlerFactory : public RequestHandlerFactory {\n public:\n  ~MockRequestHandlerFactory() override = default;\n  MOCK_METHOD(void, onServerStart, (folly::EventBase*), (noexcept, override));\n  MOCK_METHOD(void, onServerStop, (), (noexcept, override));\n  MOCK_METHOD((RequestHandler*),\n              onRequest,\n              (RequestHandler*, HTTPMessage*),\n              (noexcept, override));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/PushHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/RequestHandler.h>\n\nnamespace proxygen {\n\nclass PushHandler : public RequestHandler {\n public:\n  PushHandler() : innerHandler_(*this) {\n  }\n\n  // Caller may implement these callbacks if desired\n  void requestComplete() noexcept override {\n    delete this;\n  }\n  void onError(ProxygenError /*err*/) noexcept override {\n    delete this;\n  }\n\n  HTTPPushTransactionHandler* getHandler() {\n    return &innerHandler_;\n  }\n\n private:\n  class InnerPushHandler : public HTTPPushTransactionHandler {\n   public:\n    explicit InnerPushHandler(PushHandler& handler) : handler_(handler) {\n    }\n\n    void setTransaction(HTTPTransaction* /*txn*/) noexcept override {\n    }\n    void detachTransaction() noexcept override {\n      handler_.requestComplete();\n    }\n    void onError(const HTTPException& error) noexcept override {\n      handler_.onError(error.getProxygenError());\n    }\n    void onEgressPaused() noexcept override {\n      handler_.onEgressPaused();\n    }\n    void onEgressResumed() noexcept override {\n      handler_.onEgressResumed();\n    }\n\n   private:\n    PushHandler& handler_;\n  };\n\n  void onRequest(std::unique_ptr<HTTPMessage> /*headers*/) noexcept override {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*body*/) noexcept override {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  void onUpgrade(proxygen::UpgradeProtocol /*prot*/) noexcept override {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  void onEOM() noexcept override {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  InnerPushHandler innerHandler_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/RequestHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass ResponseHandler;\n\n/**\n * Interface to be implemented by objects that handle requests from\n * client. ResponseHandler acts as the client for these objects and\n * provides methods to send back the response\n */\nclass RequestHandler {\n public:\n  /**\n   * Saves the downstream handle with itself. Implementations of this\n   * interface should use downstream_ to send back response.\n   *\n   * XXX: Only override this method if you are ABSOLUTELY sure what you\n   *      are doing. If possible, just use downstream_ variable and dont\n   *      mess with these things.\n   */\n  virtual void setResponseHandler(ResponseHandler* handler) noexcept {\n    downstream_ = CHECK_NOTNULL(handler);\n  }\n\n  /**\n   * This function is invoked when we've successfully parsed the headers from\n   * client. Usually this is the first method invoked on RequestHandler, however\n   * if an error occurs prior to receiving the downstream http headers\n   * (e.g. parsing errors, timeout, etc.) ::onError will be invoked instead.\n   */\n  virtual void onRequest(std::unique_ptr<HTTPMessage> headers) noexcept = 0;\n\n  /**\n   * Invoked when we get part of body for the request.\n   */\n  virtual void onBody(std::unique_ptr<folly::IOBuf> body) noexcept = 0;\n\n  /**\n   * Invoked when the session has been upgraded to a different protocol\n   */\n  virtual void onUpgrade(proxygen::UpgradeProtocol prot) noexcept = 0;\n\n  /**\n   * Invoked when we finish receiving the body.\n   */\n  virtual void onEOM() noexcept = 0;\n\n  /**\n   * Invoked when request processing has been completed and nothing more\n   * needs to be done. This may be a good place to log some stats and\n   * clean up resources. This is distinct from onEOM() because it is\n   * invoked after the response is fully sent. Once this callback has been\n   * received, `downstream_` should be considered invalid.\n   */\n  virtual void requestComplete() noexcept = 0;\n\n  /**\n   * Request failed. Maybe because of read/write error on socket or client\n   * not being able to send request in time.\n   *\n   * No more callbacks will be invoked after this. You should clean up after\n   * yourself.\n   */\n  virtual void onError(ProxygenError err) noexcept = 0;\n\n  /**\n   * Signals from HTTP layer when we receive GOAWAY frame.\n   */\n  virtual void onGoaway(ErrorCode /*code*/) noexcept {\n  }\n\n  /**\n   * Signals from HTTP layer when client queue is full or empty. If you are\n   * sending a streaming response, consider implementing these and acting\n   * accordingly. Saves your server from running out of memory.\n   */\n  virtual void onEgressPaused() noexcept {\n  }\n\n  virtual void onEgressResumed() noexcept {\n  }\n\n  /**\n   * Returns true if the handler is responsible for responding to Expect\n   * headers, false otherwise.\n   */\n  virtual bool canHandleExpect() noexcept {\n    return false;\n  }\n\n  virtual ResponseHandler* getDownstream() noexcept {\n    return downstream_;\n  }\n\n  virtual ~RequestHandler() = default;\n\n protected:\n  /**\n   * A place designated for the response handler. You can use this to send back\n   * the response in your RequestHandler.\n   */\n  ResponseHandler* downstream_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/RequestHandlerAdaptor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/RequestHandlerAdaptor.h>\n\n#include <folly/Range.h>\n#include <proxygen/httpserver/PushHandler.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nnamespace proxygen {\n\nRequestHandlerAdaptor::RequestHandlerAdaptor(RequestHandler* requestHandler)\n    : ResponseHandler(requestHandler) {\n}\n\nvoid RequestHandlerAdaptor::setTransaction(HTTPTransaction* txn) noexcept {\n  txn_ = txn;\n\n  // We become that transparent layer\n  upstream_->setResponseHandler(this);\n}\n\nvoid RequestHandlerAdaptor::detachTransaction() noexcept {\n  if (upstream_) {\n    auto upstream = upstream_;\n    upstream_ = nullptr;\n    upstream->requestComplete();\n  }\n\n  // Otherwise we would have got some error call back and invoked onError\n  // on RequestHandler\n  delete this;\n}\n\nnamespace {\nconstexpr folly::StringPiece k100Continue{\"100-continue\"};\n} // namespace\n\nvoid RequestHandlerAdaptor::onHeadersComplete(\n    std::unique_ptr<HTTPMessage> msg) noexcept {\n  if (!upstream_) {\n    return;\n  }\n  if (msg->getHeaders().exists(HTTP_HEADER_EXPECT) &&\n      !upstream_->canHandleExpect()) {\n    auto expectation = msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_EXPECT);\n    if (!k100Continue.equals(expectation, folly::AsciiCaseInsensitive())) {\n      setError(kErrorUnsupportedExpectation);\n\n      ResponseBuilder(this)\n          .status(417, \"Expectation Failed\")\n          .closeConnection()\n          .sendWithEOM();\n    } else {\n      ResponseBuilder(this).status(100, \"Continue\").send();\n    }\n  }\n\n  if (upstream_) {\n    upstream_->onRequest(std::move(msg));\n  }\n}\n\nvoid RequestHandlerAdaptor::onBody(std::unique_ptr<folly::IOBuf> c) noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onBody(std::move(c));\n}\n\nvoid RequestHandlerAdaptor::onChunkHeader(size_t /*length*/) noexcept {\n}\n\nvoid RequestHandlerAdaptor::onChunkComplete() noexcept {\n}\n\nvoid RequestHandlerAdaptor::onTrailers(\n    std::unique_ptr<HTTPHeaders> /*trailers*/) noexcept {\n  // XXX: Support trailers\n}\n\nvoid RequestHandlerAdaptor::onEOM() noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onEOM();\n}\n\nvoid RequestHandlerAdaptor::onUpgrade(UpgradeProtocol protocol) noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onUpgrade(protocol);\n}\n\nvoid RequestHandlerAdaptor::onError(const HTTPException& error) noexcept {\n  if (!upstream_) {\n    return;\n  }\n\n  if (error.getProxygenError() == kErrorTimeout) {\n    setError(kErrorTimeout);\n\n    if (!txn_->canSendHeaders()) {\n      sendAbort(folly::none);\n    } else {\n      ResponseBuilder(this)\n          .status(408, \"Request Timeout\")\n          .closeConnection()\n          .sendWithEOM();\n    }\n  } else if (error.getDirection() == HTTPException::Direction::INGRESS) {\n    setError(kErrorRead);\n\n    if (!txn_->canSendHeaders()) {\n      sendAbort(folly::none);\n    } else {\n      ResponseBuilder(this)\n          .status(400, \"Bad Request\")\n          .closeConnection()\n          .sendWithEOM();\n    }\n\n  } else {\n    setError(error.hasProxygenError() ? error.getProxygenError() : kErrorWrite);\n  }\n\n  // Wait for detachTransaction to clean up\n}\n\nvoid RequestHandlerAdaptor::onGoaway(ErrorCode code) noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onGoaway(code);\n}\n\nvoid RequestHandlerAdaptor::onEgressPaused() noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onEgressPaused();\n}\n\nvoid RequestHandlerAdaptor::onEgressResumed() noexcept {\n  if (!upstream_) {\n    return;\n  }\n  upstream_->onEgressResumed();\n}\n\nvoid RequestHandlerAdaptor::sendHeaders(HTTPMessage& msg) noexcept {\n  txn_->sendHeaders(msg);\n}\n\nvoid RequestHandlerAdaptor::sendChunkHeader(size_t len) noexcept {\n  txn_->sendChunkHeader(len);\n}\n\nvoid RequestHandlerAdaptor::sendBody(std::unique_ptr<folly::IOBuf> b) noexcept {\n  txn_->sendBody(std::move(b));\n}\n\nvoid RequestHandlerAdaptor::sendChunkTerminator() noexcept {\n  txn_->sendChunkTerminator();\n}\n\nvoid RequestHandlerAdaptor::sendEOM() noexcept {\n  txn_->sendEOM();\n}\n\nvoid RequestHandlerAdaptor::sendAbort(\n    folly::Optional<ErrorCode> errorCode) noexcept {\n  if (errorCode.has_value()) {\n    txn_->sendAbort(errorCode.value());\n  } else {\n    txn_->sendAbort();\n  }\n}\n\nvoid RequestHandlerAdaptor::refreshTimeout() noexcept {\n  txn_->refreshTimeout();\n}\n\nvoid RequestHandlerAdaptor::pauseIngress() noexcept {\n  txn_->pauseIngress();\n}\n\nvoid RequestHandlerAdaptor::resumeIngress() noexcept {\n  txn_->resumeIngress();\n}\n\nfolly::Expected<ResponseHandler*, ProxygenError>\nRequestHandlerAdaptor::newPushedResponse(PushHandler* pushHandler) noexcept {\n  ProxygenError error = kErrorUnknown;\n  auto pushTxn = txn_->newPushedTransaction(pushHandler->getHandler(), &error);\n  if (!pushTxn) {\n    // Codec doesn't support push\n    VLOG(4) << \"Failed to create newPushedResponse: \"\n            << static_cast<uint8_t>(error) << \" \" << getErrorString(error);\n    return folly::makeUnexpected(error);\n  }\n  auto pushHandlerAdaptor = new RequestHandlerAdaptor(pushHandler);\n  if (!pushHandlerAdaptor) {\n    VLOG(4) << \"Failed to create RequestHandlerAdaptor!\";\n    return folly::makeUnexpected(kErrorUnknown);\n  }\n  pushHandlerAdaptor->setTransaction(pushTxn);\n  return pushHandlerAdaptor;\n}\n\nconst wangle::TransportInfo& RequestHandlerAdaptor::getSetupTransportInfo()\n    const noexcept {\n  return txn_->getSetupTransportInfo();\n}\n\nvoid RequestHandlerAdaptor::getCurrentTransportInfo(\n    wangle::TransportInfo* tinfo) const {\n  txn_->getCurrentTransportInfo(tinfo);\n}\n\nvoid RequestHandlerAdaptor::setError(ProxygenError err) noexcept {\n  err_ = err;\n  auto upstream = upstream_;\n  upstream_ = nullptr;\n  upstream->onError(err);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/RequestHandlerAdaptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/ResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass RequestHandler;\nclass PushHandler;\n\n/**\n * An adaptor that converts HTTPTransactionHandler to RequestHandler.\n * Apart from that it also -\n *\n * - Handles all the error cases itself as described below. It makes a terminal\n *   call onError(...) where you are expected to log something and stop\n *   processing the request if you have started so.\n *\n *   onError - Send a direct response back if no response has started and\n *             writing is still possible. Otherwise sends an abort.\n *\n * - Handles the 100-continue case for you (by sending Continue response),\n *   if RequestHandler returns false for canHandleExpect(). Otherwise,\n *   RequestHandler is responsible for handling it.\n */\nclass RequestHandlerAdaptor\n    : public HTTPTransactionHandler\n    , public ResponseHandler {\n public:\n  explicit RequestHandlerAdaptor(RequestHandler* requestHandler);\n\n private:\n  // HTTPTransactionHandler\n  void setTransaction(HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onChunkHeader(size_t length) noexcept override;\n  void onChunkComplete() noexcept override;\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(UpgradeProtocol protocol) noexcept override;\n  void onError(const HTTPException& error) noexcept override;\n  void onGoaway(ErrorCode code) noexcept override;\n  void onEgressPaused() noexcept override;\n  void onEgressResumed() noexcept override;\n\n  // ResponseHandler\n  void sendHeaders(HTTPMessage& msg) noexcept override;\n  void sendChunkHeader(size_t len) noexcept override;\n  void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n  void sendChunkTerminator() noexcept override;\n  void sendEOM() noexcept override;\n  void sendAbort(folly::Optional<ErrorCode> errorCode) noexcept override;\n  void refreshTimeout() noexcept override;\n  void pauseIngress() noexcept override;\n  void resumeIngress() noexcept override;\n  folly::Expected<ResponseHandler*, ProxygenError> newPushedResponse(\n      PushHandler* pushHandler) noexcept override;\n  [[nodiscard]] const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept override;\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const override;\n\n  // Helper method\n  void setError(ProxygenError err) noexcept;\n\n  ProxygenError err_{kErrorNone};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/RequestHandlerFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/RequestHandler.h>\n\nnamespace proxygen {\n\n/**\n * Factory for RequestHandlers.\n */\nclass RequestHandlerFactory {\n public:\n  virtual ~RequestHandlerFactory() = default;\n\n  /**\n   * Invoked in each thread server is going to handle requests\n   * before we start handling requests. Can be used to setup\n   * thread-local setup for each thread (stats and such).\n   */\n  virtual void onServerStart(folly::EventBase* evb) noexcept = 0;\n\n  /**\n   * Invoked in each handler thread after all the connections are\n   * drained from that thread. Can be used to tear down thread-local setup.\n   */\n  virtual void onServerStop() noexcept = 0;\n\n  /**\n   * Invoked for each new request server handles. HTTPMessage is provided\n   * so that user can potentially choose among several implementation of\n   * handler based on URL or something. No need to save/copy this\n   * HTTPMessage. RequestHandler will be given the HTTPMessage\n   * in a separate callback.\n   *\n   * Some request handlers don't handle the request themselves (think filters).\n   * They do take some actions based on request/response but otherwise they\n   * just hand-off request to some other RequestHandler. This upstream\n   * RequestHandler is given as first parameter. For the terminal RequestHandler\n   * this will by nullptr.\n   */\n  virtual RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept = 0;\n};\n\n/**\n * Helper class to help beautify the way we make chains of these filters\n */\nclass RequestHandlerChain {\n public:\n  std::vector<std::unique_ptr<RequestHandlerFactory>> build() {\n    return std::move(chain_);\n  }\n\n  template <typename T, typename... Args>\n  RequestHandlerChain& addThen(Args&&... args) {\n    chain_.push_back(std::make_unique<T>(std::forward<Args>(args)...));\n    return *this;\n  }\n\n  RequestHandlerChain& addThen(std::unique_ptr<RequestHandlerFactory> h) {\n    chain_.push_back(std::move(h));\n    return *this;\n  }\n\n private:\n  std::vector<std::unique_ptr<RequestHandlerFactory>> chain_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/ResponseBuilder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/ScopeGuard.h>\n#include <proxygen/httpserver/ResponseHandler.h>\n\n#include <memory>\n\nnamespace proxygen {\n\n/**\n * Helps you make responses and send them on demand.\n *\n * NOTE: We don't do any correctness checks here, we depend on\n *       state machine in HTTPTransaction to tell us when an\n *       error occurs\n *\n * Three expected use cases are\n *\n * 1. Send all response at once. If this is an error\n *    response, most probably you also want 'closeConnection'.\n *\n * ResponseBuilder(handler)\n *    .status(200, \"OK\")\n *    .body(...)\n *    .sendWithEOM();\n *\n * 2. Sending back response in chunks.\n *\n * ResponseBuilder(handler)\n *    .status(200, \"OK\")\n *    .body(...)\n *    .send();  // Without `WithEOM` we make it chunked\n *\n * 1 or more time\n *\n * ResponseBuilder(handler)\n *    .body(...)\n *    .send();\n *\n * At last\n *\n * ResponseBuilder(handler)\n *    .body(...)\n *    .sendWithEOM();\n *\n * 3. Accept or reject Upgrade Requests\n *\n * ResponseBuilder(handler)\n *    .acceptUpgradeRequest() // send '200 OK' without EOM\n *\n * or\n *\n * ResponseBuilder(handler)\n *    .rejectUpgradeRequest() // send '400 Bad Request'\n *\n */\nclass ResponseBuilder {\n public:\n  explicit ResponseBuilder(ResponseHandler* txn) : txn_(txn) {\n  }\n\n  ResponseBuilder& promise(const std::string& url, const std::string& host) {\n    headers_ = std::make_unique<HTTPMessage>();\n    headers_->setHTTPVersion(1, 1);\n    headers_->setURL(url);\n    headers_->getHeaders().set(HTTP_HEADER_HOST, host);\n    return *this;\n  }\n\n  ResponseBuilder& promise(const std::string& url,\n                           const std::string& host,\n                           HTTPMethod method) {\n    promise(url, host);\n    headers_->setMethod(method);\n    return *this;\n  }\n\n  ResponseBuilder& status(uint16_t code, const std::string& message) {\n    headers_ = std::make_unique<HTTPMessage>();\n    headers_->setHTTPVersion(1, 1);\n    headers_->setStatusCode(code);\n    headers_->setStatusMessage(message);\n    return *this;\n  }\n\n  template <typename T>\n  ResponseBuilder& header(const std::string& headerIn, const T& value) {\n    CHECK(headers_) << \"You need to call `status` before adding headers\";\n    headers_->getHeaders().add(headerIn, value);\n    return *this;\n  }\n\n  template <typename T>\n  ResponseBuilder& header(HTTPHeaderCode code, const T& value) {\n    CHECK(headers_) << \"You need to call `status` before adding headers\";\n    headers_->getHeaders().add(code, value);\n    return *this;\n  }\n\n  ResponseBuilder& body(std::unique_ptr<folly::IOBuf> bodyIn) {\n    if (bodyIn) {\n      if (body_) {\n        body_->prependChain(std::move(bodyIn));\n      } else {\n        body_ = std::move(bodyIn);\n      }\n    }\n\n    return *this;\n  }\n\n  template <typename T>\n  ResponseBuilder& body(T&& t) {\n    return body(\n        folly::IOBuf::fromString(folly::to<std::string>(std::forward<T>(t))));\n  }\n\n  ResponseBuilder& closeConnection() {\n    return header(HTTP_HEADER_CONNECTION, \"close\");\n  }\n\n  ResponseBuilder& trailers(const HTTPHeaders& trailers) {\n    trailers_ = std::make_unique<HTTPHeaders>(trailers);\n    return *this;\n  }\n\n  void sendWithEOM() {\n    sendEOM_ = true;\n    send();\n  }\n\n  void send() {\n    // Once we send them, we don't want to send them again\n    SCOPE_EXIT {\n      headers_.reset();\n    };\n\n    // By default, chunked\n    bool chunked = true;\n\n    // If we have complete response, we can use Content-Length and get done\n    if (headers_ && sendEOM_) {\n      chunked = false;\n    }\n\n    if (headers_) {\n      // We don't need to add Content-Length or Encoding for 1xx responses\n      if (headers_->isResponse() && headers_->getStatusCode() >= 200) {\n        if (chunked) {\n          headers_->setIsChunked(true);\n        } else {\n          const auto len = body_ ? body_->computeChainDataLength() : 0;\n          headers_->getHeaders().add(HTTP_HEADER_CONTENT_LENGTH,\n                                     folly::to<std::string>(len));\n        }\n      }\n\n      txn_->sendHeaders(*headers_);\n    }\n\n    if (body_) {\n      if (chunked) {\n        auto bodyLength = body_->computeChainDataLength();\n        txn_->sendChunkHeader(bodyLength);\n        txn_->sendBody(std::move(body_));\n        txn_->sendChunkTerminator();\n      } else {\n        txn_->sendBody(std::move(body_));\n      }\n    }\n\n    if (sendEOM_) {\n      if (trailers_) {\n        auto txn = txn_->getTransaction();\n        if (txn) {\n          txn->sendTrailers(*trailers_);\n        }\n        trailers_.reset();\n      }\n      txn_->sendEOM();\n    }\n  }\n\n  enum class UpgradeType {\n    CONNECT_REQUEST = 0,\n    HTTP_UPGRADE,\n  };\n\n  void acceptUpgradeRequest(UpgradeType upgradeType,\n                            const std::string upgradeProtocol = \"\") {\n    headers_ = std::make_unique<HTTPMessage>();\n    if (upgradeType == UpgradeType::CONNECT_REQUEST) {\n      headers_->constructDirectResponse({1, 1}, 200, \"OK\");\n    } else {\n      CHECK(!upgradeProtocol.empty());\n      headers_->constructDirectResponse({1, 1}, 101, \"Switching Protocols\");\n      headers_->getHeaders().add(HTTP_HEADER_UPGRADE, upgradeProtocol);\n      headers_->getHeaders().add(HTTP_HEADER_CONNECTION, \"Upgrade\");\n    }\n    txn_->sendHeaders(*headers_);\n  }\n\n  void rejectUpgradeRequest() {\n    headers_ = std::make_unique<HTTPMessage>();\n    headers_->constructDirectResponse({1, 1}, 400, \"Bad Request\");\n    txn_->sendHeaders(*headers_);\n    txn_->sendEOM();\n  }\n\n  ResponseBuilder& setEgressWebsocketHeaders() {\n    headers_->setEgressWebsocketUpgrade();\n    return *this;\n  }\n\n  [[nodiscard]] const HTTPMessage* getHeaders() const {\n    return headers_.get();\n  }\n\n private:\n  ResponseHandler* const txn_{nullptr};\n\n  std::unique_ptr<HTTPMessage> headers_;\n  std::unique_ptr<folly::IOBuf> body_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n\n  // If true, sends EOM.\n  bool sendEOM_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/ResponseHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass RequestHandler;\nclass PushHandler;\n\n/**\n * Interface that acts as client for RequestHandler. It also has a hook\n * for the RequestHandler so that it is easy to chain these Request/Response\n * handlers and be able to modify these chains.\n *\n * The names are pretty much self explanatory. You only need\n * to get into details about this interface if you are implementing filters.\n *\n * NOTE: All the writes are done at the end of the event loop. So this is safe\n *       to do in your RequestHandler.\n *\n *       {\n *         ...\n *         downstream_->sendHeader(...);\n *         downstream_->sendEOM();\n *       }\n *\n *       You dont need to worry about any callbacks being invoked after\n *       sendHeader.\n *\n *       Consider using proxygen/httpserver/ResponseBuilder to send back the\n *       response. It will take care of chunking response if required and\n *       everything.\n */\nclass ResponseHandler {\n public:\n  explicit ResponseHandler(RequestHandler* upstream)\n      : upstream_(CHECK_NOTNULL(upstream)) {\n  }\n\n  virtual ~ResponseHandler() = default;\n\n  /**\n   * NOTE: We take response message as non-const reference, to allow filters\n   *       between your handler and client to be able to modify response\n   *       if they want to.\n   *\n   *       eg. a compression filter might want to change the content-encoding\n   */\n  virtual void sendHeaders(HTTPMessage& msg) noexcept = 0;\n\n  virtual void sendChunkHeader(size_t len) noexcept = 0;\n\n  virtual void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept = 0;\n\n  virtual void sendChunkTerminator() noexcept = 0;\n\n  virtual void sendEOM() noexcept = 0;\n\n  virtual void sendAbort(\n      folly::Optional<ErrorCode> errorCode = folly::none) noexcept = 0;\n\n  virtual void refreshTimeout() noexcept = 0;\n\n  virtual void pauseIngress() noexcept = 0;\n\n  virtual void resumeIngress() noexcept = 0;\n\n  virtual folly::Expected<ResponseHandler*, ProxygenError> newPushedResponse(\n      PushHandler* pushHandler) noexcept = 0;\n\n  // Accessors for Transport/Connection information\n  [[nodiscard]] virtual const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept = 0;\n\n  virtual void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const = 0;\n\n  [[nodiscard]] HTTPTransaction* getTransaction() const noexcept {\n    return txn_;\n  }\n\n protected:\n  RequestHandler* upstream_{nullptr};\n  HTTPTransaction* txn_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/ScopedHTTPServer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <barrier>\n#include <chrono>\n#include <folly/io/async/SSLContext.h>\n#include <folly/system/ThreadName.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nnamespace proxygen {\n\ntemplate <typename HandlerType>\nclass ScopedHandler : public RequestHandler {\n public:\n  explicit ScopedHandler(HandlerType* ptr) : handlerPtr_(ptr) {\n  }\n\n  void onRequest(std::unique_ptr<HTTPMessage> headers) noexcept override {\n    request_ = std::move(headers);\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    requestBody_.append(std::move(body));\n  }\n\n  void onUpgrade(proxygen::UpgradeProtocol) noexcept override {\n  }\n\n  void onEOM() noexcept override {\n    try {\n      ResponseBuilder r(downstream_);\n      (*handlerPtr_)(*request_, requestBody_.move(), r);\n      r.sendWithEOM();\n    } catch (const std::exception& ex) {\n      ResponseBuilder(downstream_)\n          .status(500, \"Internal Server Error\")\n          .body(ex.what())\n          .sendWithEOM();\n    } catch (...) {\n      ResponseBuilder(downstream_)\n          .status(500, \"Internal Server Error\")\n          .body(\"Unknown exception thrown\")\n          .sendWithEOM();\n    }\n  }\n\n  void requestComplete() noexcept override {\n    delete this;\n  }\n\n  void onError(ProxygenError) noexcept override {\n    delete this;\n  }\n\n private:\n  HandlerType* const handlerPtr_{nullptr};\n\n  std::unique_ptr<HTTPMessage> request_;\n  folly::IOBufQueue requestBody_;\n};\n\ntemplate <typename HandlerType>\nclass ScopedHandlerFactory : public RequestHandlerFactory {\n public:\n  explicit ScopedHandlerFactory(HandlerType handler) : handler_(handler) {\n  }\n\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new ScopedHandler<HandlerType>(&handler_);\n  }\n\n private:\n  HandlerType handler_;\n};\n\n/**\n * A basic server that can be used for testing http clients. Since most such\n * servers are short lived, this server takes care of starting and stopping\n * automatically.\n */\nclass ScopedHTTPServer final {\n public:\n  /**\n   * Start a server listening on the requested `port`.\n   * If `port` is 0, it will choose a random port.\n   */\n  template <typename HandlerType>\n  static std::unique_ptr<ScopedHTTPServer> start(\n      HandlerType handler,\n      int port = 0,\n      int numThreads = 4,\n      std::unique_ptr<wangle::SSLContextConfig> sslCfg = nullptr,\n      std::chrono::milliseconds idleTimeoutMs = std::chrono::milliseconds(0));\n\n  /**\n   * Start a server listening with the requested IPConfig and server opts\n   */\n  static std::unique_ptr<ScopedHTTPServer> start(HTTPServer::IPConfig cfg,\n                                                 HTTPServerOptions options);\n\n  /**\n   * Get the port the server is listening on. This is helpful if the port was\n   * randomly chosen.\n   */\n  [[nodiscard]] int getPort() const {\n    return getAddresses()[0].address.getPort();\n  }\n\n  /**\n   * Get the addresses for the server.\n   */\n  [[nodiscard]] std::vector<HTTPServer::IPConfig> getAddresses() const {\n    auto addresses = server_->addresses();\n    CHECK(!addresses.empty());\n    return addresses;\n  }\n\n  ~ScopedHTTPServer() {\n    server_->stop();\n    thread_.join();\n  }\n\n  ScopedHTTPServer(const ScopedHTTPServer&) = delete;\n  ScopedHTTPServer& operator=(const ScopedHTTPServer&) = delete;\n  ScopedHTTPServer(ScopedHTTPServer&&) = delete;\n  ScopedHTTPServer& operator=(ScopedHTTPServer&&) = delete;\n\n private:\n  ScopedHTTPServer(std::thread thread, std::unique_ptr<HTTPServer> server)\n      : thread_(std::move(thread)), server_(std::move(server)) {\n  }\n\n  std::thread thread_;\n  std::unique_ptr<HTTPServer> server_;\n};\n\ntemplate <typename HandlerType>\ninline std::unique_ptr<ScopedHTTPServer> ScopedHTTPServer::start(\n    HandlerType handler,\n    int port,\n    int numThreads,\n    std::unique_ptr<wangle::SSLContextConfig> sslCfg,\n    std::chrono::milliseconds idleTimeoutMs) {\n\n  std::unique_ptr<RequestHandlerFactory> f =\n      std::make_unique<ScopedHandlerFactory<HandlerType>>(handler);\n  return start(\n      std::move(f), port, numThreads, std::move(sslCfg), idleTimeoutMs);\n}\n\ntemplate <>\ninline std::unique_ptr<ScopedHTTPServer>\nScopedHTTPServer::start<std::unique_ptr<RequestHandlerFactory>>(\n    std::unique_ptr<RequestHandlerFactory> f,\n    int port,\n    int numThreads,\n    std::unique_ptr<wangle::SSLContextConfig> sslCfg,\n    std::chrono::milliseconds idleTimeoutMs) {\n  // This will handle both IPv4 and IPv6 cases\n  folly::SocketAddress addr;\n  addr.setFromLocalPort(port);\n\n  HTTPServer::IPConfig cfg{addr, HTTPServer::Protocol::HTTP};\n\n  if (sslCfg) {\n    cfg.sslConfigs.push_back(*sslCfg);\n  }\n\n  HTTPServerOptions options;\n  options.threads = numThreads;\n  options.handlerFactories.push_back(std::move(f));\n  if (idleTimeoutMs.count() > 0) {\n    options.idleTimeout = idleTimeoutMs;\n  }\n  return start(std::move(cfg), std::move(options));\n}\n\ninline std::unique_ptr<ScopedHTTPServer> ScopedHTTPServer::start(\n    HTTPServer::IPConfig cfg, HTTPServerOptions options) {\n\n  std::vector<HTTPServer::IPConfig> IPs = {std::move(cfg)};\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  server->bind(IPs);\n\n  // Start the server\n  std::exception_ptr eptr;\n  auto barrier = std::make_shared<std::barrier<>>(2);\n\n  std::thread t = std::thread([&, barrier]() {\n    server->start(\n        [&, barrier]() {\n          folly::setThreadName(\"http-acceptor\");\n          barrier->arrive_and_wait();\n        },\n        [&, barrier](std::exception_ptr ex) {\n          eptr = ex;\n          barrier->arrive_and_wait();\n        });\n  });\n\n  // Wait for server to start\n  barrier->arrive_and_wait();\n  if (eptr) {\n    t.join();\n\n    std::rethrow_exception(eptr);\n  }\n\n  return std::unique_ptr<ScopedHTTPServer>(\n      new ScopedHTTPServer(std::move(t), std::move(server)));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/SignalHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/SignalHandler.h>\n\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/httpserver/HTTPServer.h>\n\nusing folly::EventBaseManager;\n\nnamespace proxygen {\n\nSignalHandler::SignalHandler(HTTPServer* server)\n    : folly::AsyncSignalHandler(EventBaseManager::get()->getEventBase()),\n      server_(server) {\n}\n\nvoid SignalHandler::install(const std::vector<int>& signals) {\n  for (const int& signal : signals) {\n    registerSignalHandler(signal);\n  }\n}\n\nvoid SignalHandler::signalReceived(int /*signum*/) noexcept {\n  server_->stop();\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/SignalHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/AsyncSignalHandler.h>\n#include <vector>\n\nnamespace proxygen {\n\nclass HTTPServer;\n\n/**\n * Installs signal handler which will stop HTTPServer when the user presses\n * Ctrl-C. To be used if HTTPServer is the main process.\n *\n * Note: Should only be created from the thread invoking `HTTPServer::start()`.\n */\nclass SignalHandler : private folly::AsyncSignalHandler {\n public:\n  explicit SignalHandler(HTTPServer* server);\n\n  void install(const std::vector<int>& signals);\n\n private:\n  // AsyncSignalHandler\n  void signalReceived(int signum) noexcept override;\n\n  HTTPServer* const server_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/CompressionFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/Filters.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n#include <proxygen/lib/utils/CompressionFilterUtils.h>\n\nnamespace proxygen {\n\n/**\n * A Server filter to perform compression. If there are any errors it will\n * fall back to sending uncompressed responses.\n */\nclass CompressionFilter : public Filter {\n public:\n  CompressionFilter(RequestHandler* downstream,\n                    CompressionFilterUtils::FilterParams params)\n      : Filter(downstream), params_(std::move(params)) {\n  }\n\n  ~CompressionFilter() override = default;\n\n  CompressionFilter(const CompressionFilter&) = delete;\n  CompressionFilter& operator=(const CompressionFilter&) = delete;\n  CompressionFilter(CompressionFilter&&) = delete;\n  CompressionFilter& operator=(CompressionFilter&&) = delete;\n\n  void sendHeaders(HTTPMessage& msg) noexcept override {\n    DCHECK(compressor_ == nullptr);\n    DCHECK(header_ == false);\n\n    chunked_ = msg.getIsChunked();\n    compress_ = CompressionFilterUtils::shouldCompress(msg, params_);\n\n    // Add the header\n    if (compress_) {\n      auto& headers = msg.getHeaders();\n      headers.set(HTTP_HEADER_CONTENT_ENCODING, params_.headerEncoding);\n    }\n\n    // Initialize compressor\n    compressor_ = params_.compressorFactory();\n    if (!compressor_ || compressor_->hasError()) {\n      return fail();\n    }\n\n    // If it's chunked or not being compressed then the headers can be sent\n    // if it's compressed and one body, then need to calculate content length.\n    if (chunked_ || !compress_) {\n      Filter::sendHeaders(msg);\n      header_ = true;\n    } else {\n      responseMessage_ = std::make_unique<HTTPMessage>(msg);\n    }\n  }\n\n  void sendChunkHeader(size_t len) noexcept override {\n    // The headers should have always been sent since the message is chunked\n    DCHECK_EQ(header_, true) << \"Headers should have already been sent.\";\n\n    // If not compressing, pass downstream, otherwise \"swallow\" it\n    // to send after compressing the body.\n    if (!compress_) {\n      Filter::sendChunkHeader(len);\n    }\n\n    // Return without sending the chunk header.\n    return;\n  }\n\n  // Compress the body, if chunked may be called multiple times\n  void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept override {\n    // If not compressing, pass the body through\n    if (!compress_) {\n      DCHECK(header_ == true);\n      Filter::sendBody(std::move(body));\n      return;\n    }\n\n    CHECK(compressor_ && !compressor_->hasError());\n\n    // If it's chunked, never write the trailer, it will be written on EOM\n    auto compressed = compressor_->compress(body.get(), !chunked_);\n    if (compressor_->hasError()) {\n      return fail();\n    }\n\n    auto compressedBodyLength = compressed->computeChainDataLength();\n\n    if (chunked_) {\n      // Send on the swallowed chunk header.\n      Filter::sendChunkHeader(compressedBodyLength);\n    } else {\n      // Send the content length on compressed, non-chunked messages\n      DCHECK(header_ == false);\n      DCHECK(compress_ == true);\n      auto& headers = responseMessage_->getHeaders();\n      headers.set(HTTP_HEADER_CONTENT_LENGTH,\n                  folly::to<std::string>(compressedBodyLength));\n\n      Filter::sendHeaders(*responseMessage_);\n      header_ = true;\n    }\n\n    Filter::sendBody(std::move(compressed));\n  }\n\n  void sendEOM() noexcept override {\n\n    // Need to send the trailer for compressed chunked messages\n    if (compress_ && chunked_) {\n      folly::IOBuf emptyBuf{};\n      CHECK(compressor_ && !compressor_->hasError());\n      auto compressed = compressor_->compress(&emptyBuf, true);\n\n      if (compressor_->hasError()) {\n        return fail();\n      }\n\n      // \"Inject\" a chunk with the trailer.\n      Filter::sendChunkHeader(compressed->computeChainDataLength());\n      Filter::sendBody(std::move(compressed));\n      Filter::sendChunkTerminator();\n    }\n\n    Filter::sendEOM();\n  }\n\n protected:\n  void fail() {\n    Filter::sendAbort(folly::none);\n  }\n\n  std::unique_ptr<HTTPMessage> responseMessage_;\n  std::unique_ptr<StreamCompressor> compressor_{nullptr};\n  CompressionFilterUtils::FilterParams params_;\n  bool header_{false};\n  bool chunked_{false};\n  bool compress_{false};\n};\n\nclass CompressionFilterFactory : public RequestHandlerFactory {\n public:\n  using Options = CompressionFilterUtils::FactoryOptions;\n\n  CompressionFilterFactory(const Options& opts) : options_(opts) {\n  }\n\n  ~CompressionFilterFactory() override = default;\n\n  CompressionFilterFactory(const CompressionFilterFactory&) = delete;\n  CompressionFilterFactory& operator=(const CompressionFilterFactory&) = delete;\n  CompressionFilterFactory(CompressionFilterFactory&&) = delete;\n  CompressionFilterFactory& operator=(CompressionFilterFactory&&) = delete;\n\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler* h,\n                            HTTPMessage* msg) noexcept override {\n    auto filterParams = CompressionFilterUtils::getFilterParams(*msg, options_);\n    if (!filterParams) {\n      return h;\n    }\n    return new CompressionFilter(h, std::move(*filterParams));\n  }\n\n private:\n  const Options options_;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/DecompressionFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/filters/DecompressionFilter.h>\n\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\nnamespace proxygen {\n\nvoid DecompressionFilter::onRequest(std::unique_ptr<HTTPMessage> msg) noexcept {\n  auto& headers = msg->getHeaders();\n  auto contentEncoding =\n      headers.getSingleOrNullptr(HTTP_HEADER_CONTENT_ENCODING);\n  if (contentEncoding && *contentEncoding == \"zstd\") {\n    decompressor_ = std::make_unique<ZstdStreamDecompressor>();\n    headers.remove(HTTP_HEADER_CONTENT_ENCODING);\n    headers.remove(HTTP_HEADER_CONTENT_LENGTH);\n  }\n  return Filter::onRequest(std::move(msg));\n}\n\nvoid DecompressionFilter::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  if (!decompressor_) {\n    return Filter::onBody(std::move(body));\n  }\n\n  auto decompressed = decompressor_->decompress(body.get());\n\n  if (decompressor_->hasError()) {\n\n    return Filter::sendAbort(folly::none);\n  }\n\n  if (decompressed && !decompressed->empty()) {\n    Filter::onBody(std::move(decompressed));\n  }\n}\n\nvoid DecompressionFilter::onEOM() noexcept {\n  if (!decompressor_) {\n    return Filter::onEOM();\n  }\n\n  folly::IOBuf emptyBuf;\n  auto decompressed = decompressor_->decompress(&emptyBuf);\n\n  if (decompressor_->hasError()) {\n    return Filter::sendAbort(folly::none);\n  }\n\n  if (decompressed && !decompressed->empty()) {\n    Filter::onBody(std::move(decompressed));\n  }\n\n  return Filter::onEOM();\n}\n\nRequestHandler* DecompressionFilterFactory::onRequest(\n    RequestHandler* h, HTTPMessage* /*msg*/) noexcept {\n  return new DecompressionFilter(h);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/DecompressionFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/Filters.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n#include <proxygen/lib/utils/StreamDecompressor.h>\n\nnamespace proxygen {\n\n/**\n * A Server filter to perform decompression of incoming request bodies.\n * Supports zstd compression format.\n * If there are any errors it will abort the request.\n */\nclass DecompressionFilter : public Filter {\n public:\n  explicit DecompressionFilter(RequestHandler* downstream)\n      : Filter(downstream) {\n  }\n\n  ~DecompressionFilter() override = default;\n\n  DecompressionFilter(const DecompressionFilter&) = delete;\n  DecompressionFilter& operator=(const DecompressionFilter&) = delete;\n  DecompressionFilter(DecompressionFilter&&) = delete;\n  DecompressionFilter& operator=(DecompressionFilter&&) = delete;\n\n  void onRequest(std::unique_ptr<HTTPMessage> msg) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n private:\n  std::unique_ptr<StreamDecompressor> decompressor_{nullptr};\n};\n\n/**\n * Factory for creating DecompressionFilter instances.\n */\nclass DecompressionFilterFactory : public RequestHandlerFactory {\n public:\n  DecompressionFilterFactory() = default;\n\n  ~DecompressionFilterFactory() override = default;\n\n  DecompressionFilterFactory(const DecompressionFilterFactory&) = delete;\n  DecompressionFilterFactory& operator=(const DecompressionFilterFactory&) =\n      delete;\n  DecompressionFilterFactory(DecompressionFilterFactory&&) = delete;\n  DecompressionFilterFactory& operator=(DecompressionFilterFactory&&) = delete;\n\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler* h,\n                            HTTPMessage* /*msg*/) noexcept override;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/DirectResponseHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nnamespace proxygen {\n\n/**\n * Handler that sends a fixed response back.\n */\nclass DirectResponseHandler : public RequestHandler {\n public:\n  DirectResponseHandler(int code, std::string message, std::string body)\n      : code_(code),\n        message_(std::move(message)),\n        body_(folly::IOBuf::copyBuffer(body)) {\n  }\n\n  void onRequest(std::unique_ptr<HTTPMessage> /*headers*/) noexcept override {\n    ResponseBuilder(downstream_)\n        .status(code_, std::move(message_))\n        .body(std::move(body_))\n        .send();\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*body*/) noexcept override {\n  }\n\n  void onUpgrade(proxygen::UpgradeProtocol /*prot*/) noexcept override {\n  }\n\n  void onEOM() noexcept override {\n    ResponseBuilder(downstream_).sendWithEOM();\n  }\n\n  void requestComplete() noexcept override {\n    delete this;\n  }\n\n  void onError(ProxygenError /*err*/) noexcept override {\n    delete this;\n  }\n\n private:\n  const int code_;\n  std::string message_;\n  std::unique_ptr<folly::IOBuf> body_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/RejectConnectFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/Filters.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nnamespace proxygen {\n\n/**\n * A filter that rejects CONNECT/UPGRADE requests.\n */\nclass RejectConnectFilter : public Filter {\n public:\n  explicit RejectConnectFilter(RequestHandler* upstream) : Filter(upstream) {\n  }\n\n  void onRequest(std::unique_ptr<HTTPMessage> /*msg*/) noexcept override {\n    upstream_->onError(kErrorMethodNotSupported);\n    upstream_ = nullptr;\n\n    ResponseBuilder(downstream_).rejectUpgradeRequest();\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*body*/) noexcept override {\n  }\n\n  void onUpgrade(UpgradeProtocol /*protocol*/) noexcept override {\n  }\n\n  void onEOM() noexcept override {\n  }\n\n  void requestComplete() noexcept override {\n    CHECK(!upstream_);\n    delete this;\n  }\n\n  void onError(ProxygenError err) noexcept override {\n    // If onError is invoked before we forward the error\n    if (upstream_) {\n      upstream_->onError(err);\n      upstream_ = nullptr;\n    }\n\n    delete this;\n  }\n\n  void onEgressPaused() noexcept override {\n  }\n\n  void onEgressResumed() noexcept override {\n  }\n\n  // Response handler\n  void sendHeaders(HTTPMessage& /*msg*/) noexcept override {\n  }\n\n  void sendChunkHeader(size_t /*len*/) noexcept override {\n  }\n\n  void sendBody(std::unique_ptr<folly::IOBuf> /*body*/) noexcept override {\n  }\n\n  void sendChunkTerminator() noexcept override {\n  }\n\n  void sendEOM() noexcept override {\n  }\n\n  void sendAbort(folly::Optional<ErrorCode>) noexcept override {\n  }\n\n  void refreshTimeout() noexcept override {\n  }\n};\n\nclass RejectConnectFilterFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler* h,\n                            HTTPMessage* msg) noexcept override {\n\n    if (msg->getMethod() == HTTPMethod::CONNECT) {\n      return new RejectConnectFilter(h);\n    }\n\n    // No need to insert this filter\n    return h;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/filters/tests/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET HTTPServerFilterTests\n  SOURCES\n  CompressionFilterTest.cpp\n  DEPENDS\n    proxygen\n    proxygenhttpserver\n    testmain\n)\n"
  },
  {
    "path": "proxygen/httpserver/filters/tests/CompressionFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <type_traits>\n\n#include <folly/Conv.h>\n#include <folly/ScopeGuard.h>\n#include <folly/io/IOBuf.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/httpserver/Mocks.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n#include <proxygen/httpserver/filters/CompressionFilter.h>\n#include <proxygen/lib/utils/ZlibStreamCompressor.h>\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nMATCHER_P(IOBufEquals,\n          expected,\n          folly::to<std::string>(\n              \"IOBuf is \", negation ? \"not \" : \"\", \"'\", expected, \"'\")) {\n  auto iob = arg->clone();\n  auto br = iob->coalesce();\n  std::string actual(br.begin(), br.end());\n  *result_listener << \"'\" << actual << \"'\";\n  return actual == expected;\n}\n\nstruct ZlibTest {\n  static std::unique_ptr<StreamDecompressor> makeDecompressor() {\n    return std::make_unique<ZlibStreamDecompressor>(CompressionType::GZIP);\n  }\n  static std::string getExpectedEncoding() {\n    return \"gzip\";\n  }\n  static int32_t getCompressionLevel() {\n    return 4 /* default */;\n  }\n};\n\nstruct ZstdTest {\n  static std::unique_ptr<StreamDecompressor> makeDecompressor() {\n    return std::make_unique<ZstdStreamDecompressor>();\n  }\n  static std::string getExpectedEncoding() {\n    return \"zstd\";\n  }\n  static int32_t getCompressionLevel() {\n    return 4 /* default */;\n  }\n};\n\ntemplate <typename T>\nclass CompressionFilterTest : public Test {\n public:\n  using CodecType = T;\n\n  void SetUp() override {\n    // requesthandler is the server, responsehandler is the client\n    requestHandler_ = new MockRequestHandler();\n    responseHandler_ = std::make_unique<MockResponseHandler>(requestHandler_);\n    zd_ = T::makeDecompressor();\n  }\n\n  void TearDown() override {\n    Mock::VerifyAndClear(requestHandler_);\n    Mock::VerifyAndClear(responseHandler_.get());\n\n    delete requestHandler_;\n  }\n\n protected:\n  CompressionFilter* filter_{nullptr};\n  MockRequestHandler* requestHandler_;\n  std::unique_ptr<MockResponseHandler> responseHandler_;\n  std::unique_ptr<StreamDecompressor> zd_;\n  ResponseHandler* downstream_{nullptr};\n\n  void exercise_compression(bool expectCompression,\n                            std::string url,\n                            std::string acceptedEncoding,\n                            std::string expectedEncoding,\n                            std::string originalRequestBody,\n                            std::string responseContentType,\n                            std::unique_ptr<folly::IOBuf> originalResponseBody,\n                            int32_t compressionLevel = T::getCompressionLevel(),\n                            uint32_t minimumCompressionSize = 1,\n                            bool sendCompressedResponse = false,\n                            bool disableCompressionForThisEncoding = false) {\n\n    // If there is only one IOBuf, then it's not chunked.\n    bool isResponseChunked = originalResponseBody->isChained();\n    size_t chunkCount = originalResponseBody->countChainElements();\n\n    // Chunked and compressed responses will have an extra block\n    if (isResponseChunked && expectCompression) {\n      chunkCount += 1;\n    }\n\n    // Request Handler Expectations\n    EXPECT_CALL(*requestHandler_, onBody(_)).Times(1);\n    EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n\n    // Need to capture whatever the filter is for ResponseBuilder later\n    EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n        .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n    // Response Handler Expectations\n    // Headers are only sent once\n    EXPECT_CALL(*responseHandler_, sendHeaders(_))\n        .WillOnce(DoAll(Invoke([&](HTTPMessage& msg) {\n                          auto& headers = msg.getHeaders();\n                          if (expectCompression) {\n                            EXPECT_TRUE(msg.checkForHeaderToken(\n                                HTTP_HEADER_CONTENT_ENCODING,\n                                expectedEncoding.c_str(),\n                                false));\n                          }\n\n                          if (msg.getIsChunked()) {\n                            EXPECT_FALSE(headers.exists(\"Content-Length\"));\n                          } else {\n                            // Content-Length is not set on chunked messages\n                            EXPECT_TRUE(headers.exists(\"Content-Length\"));\n                          }\n                        }),\n                        Return()));\n\n    if (isResponseChunked) {\n      // The final chunk has 0 body\n      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(chunkCount);\n      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(chunkCount);\n    } else {\n      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(0);\n      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(0);\n    }\n\n    // Accumulate the body, decompressing it if it's compressed\n    std::unique_ptr<folly::IOBuf> responseBody;\n    EXPECT_CALL(*responseHandler_, sendBody(_))\n        .Times(chunkCount)\n        .WillRepeatedly(DoAll(\n            Invoke([&](std::shared_ptr<folly::IOBuf> body) {\n              std::unique_ptr<folly::IOBuf> processedBody;\n\n              if (expectCompression) {\n                processedBody = zd_->decompress(body.get());\n                ASSERT_FALSE(zd_->hasError()) << \"Failed to decompress body!\";\n              } else {\n                processedBody = folly::IOBuf::copyBuffer(\n                    body->data(), body->length(), 0, 0);\n              }\n\n              if (responseBody) {\n                responseBody->prependChain(std::move(processedBody));\n              } else {\n                responseBody = std::move(processedBody);\n              }\n            }),\n            Return()));\n\n    EXPECT_CALL(*responseHandler_, sendEOM()).Times(1);\n\n    /* Simulate Request/Response  */\n\n    HTTPMessage msg;\n    msg.setURL(url);\n    msg.getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, acceptedEncoding);\n\n    std::set<std::string> compressibleTypes = {\"text/html\"};\n\n    CompressionFilterFactory::Options opts;\n    opts.zlibCompressionLevel = compressionLevel;\n    opts.minimumCompressionSize = minimumCompressionSize;\n    opts.compressibleContentTypes =\n        std::make_shared<std::set<std::string>>(compressibleTypes);\n    opts.enableZstd = true;\n    if (disableCompressionForThisEncoding) {\n      if (CodecType::getExpectedEncoding() == \"gzip\") {\n        opts.enableGzip = false;\n      }\n      if (CodecType::getExpectedEncoding() == \"zstd\") {\n        opts.enableZstd = false;\n      }\n    }\n    auto filterFactory = std::make_unique<CompressionFilterFactory>(opts);\n\n    auto filter = filterFactory->onRequest(requestHandler_, &msg);\n    filter->setResponseHandler(responseHandler_.get());\n\n    // Send fake request\n    filter->onBody(folly::IOBuf::copyBuffer(originalRequestBody));\n    filter->onEOM();\n\n    // Send a fake Response\n    if (isResponseChunked) {\n\n      ResponseBuilder(downstream_)\n          .status(200, \"OK\")\n          .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)\n          .send();\n\n      folly::IOBuf* crtBuf;\n      crtBuf = originalResponseBody.get();\n\n      do {\n        ResponseBuilder(downstream_).body(crtBuf->cloneOne()).send();\n        crtBuf = crtBuf->next();\n      } while (crtBuf != originalResponseBody.get());\n\n      ResponseBuilder(downstream_).sendWithEOM();\n\n    } else if (sendCompressedResponse) {\n      // Send unchunked response\n      ResponseBuilder(downstream_)\n          .status(200, \"OK\")\n          .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)\n          .header(HTTP_HEADER_CONTENT_ENCODING, \"gzip\")\n          .body(originalResponseBody->clone())\n          .sendWithEOM();\n    } else {\n      // Send unchunked response\n      ResponseBuilder(downstream_)\n          .status(200, \"OK\")\n          .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)\n          .body(originalResponseBody->clone())\n          .sendWithEOM();\n    }\n\n    filter->requestComplete();\n\n    EXPECT_THAT(responseBody, IOBufEquals(originalRequestBody));\n  }\n\n  // Helper method to convert a vector of strings to an IOBuf chain\n  // specificaly create a chain because the chain pieces are chunks\n  std::unique_ptr<folly::IOBuf> createResponseChain(\n      std::vector<std::string> const& bodyStrings) {\n\n    std::unique_ptr<folly::IOBuf> responseBodyChain;\n\n    for (auto& s : bodyStrings) {\n      auto nextBody = folly::IOBuf::copyBuffer(s.c_str());\n      if (responseBodyChain) {\n        responseBodyChain->prependChain(std::move(nextBody));\n      } else {\n        responseBodyChain = std::move(nextBody);\n      }\n    }\n\n    return responseBodyChain;\n  }\n};\n\nusing CompressionCodecs = ::testing::Types<ZlibTest, ZstdTest>;\n\nTYPED_TEST_SUITE(CompressionFilterTest, CompressionCodecs);\n\n// Basic smoke test\nTYPED_TEST(CompressionFilterTest, NonchunkedCompression) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, ChunkedCompression) {\n  using Codec = typename TestFixture::CodecType;\n  std::vector<std::string> chunks = {\"Hello\", \" World\"};\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               this->createResponseChain(chunks));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, ParameterizedContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html; param1\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MixedcaseContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"Text/Html; param1\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\n// Client supports multiple possible compression encodings\nTYPED_TEST(CompressionFilterTest, MultipleAcceptedEncodings) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        true,\n        std::string(\"http://locahost/foo.compressme\"),\n        Codec::getExpectedEncoding() + \", identity, deflate\",\n        Codec::getExpectedEncoding(),\n        std::string(\"Hello World\"),\n        std::string(\"text/html\"),\n        folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\n// Server skips compressing if the response is already compressed\nTYPED_TEST(CompressionFilterTest, ResponseAlreadyCompressedTest) {\n  using Codec = typename TestFixture::CodecType;\n  auto compressor =\n      std::make_unique<ZlibStreamCompressor>(CompressionType::GZIP, 4);\n  auto fakeCompressed = folly::IOBuf::copyBuffer(\"helloimsupposedlycompressed\");\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"helloimsupposedlycompressed\"),\n                               std::string(\"text/html\"),\n                               std::move(fakeCompressed),\n                               4,\n                               1,\n                               true /*SendCompressedResponse*/);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MultipleAcceptedEncodingsQvalues) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        true,\n        std::string(\"http://locahost/foo.compressme\"),\n        Codec::getExpectedEncoding() + \"; q=.7;, identity\",\n        Codec::getExpectedEncoding(),\n        std::string(\"Hello World\"),\n        std::string(\"text/html\"),\n        folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, NoCompressibleAcceptedEncodings) {\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               std::string(\"identity; q=.7;\"),\n                               std::string(\"\"),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MissingAcceptedEncodings) {\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               std::string(\"\"),\n                               std::string(\"\"),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\n// Content is of an-uncompressible content-type\nTYPED_TEST(CompressionFilterTest, UncompressibleContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.nocompress\"),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"\"),\n                               std::string(\"Hello World\"),\n                               std::string(\"image/jpeg\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, UncompressibleContenttypeParam) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.nocompress\"),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"\"),\n                               std::string(\"Hello World\"),\n                               std::string(\"application/jpeg; param1\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"));\n  });\n}\n\n// Content is under the minimum compression size\nTYPED_TEST(CompressionFilterTest, TooSmallToCompress) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.smallfry\"),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"\"),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"),\n                               Codec::getCompressionLevel(),\n                               1000);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, SmallChunksCompress) {\n  // Expect this to compress despite being small because can't tell the content\n  // length when we're chunked\n  using Codec = typename TestFixture::CodecType;\n  std::vector<std::string> chunks = {\"Hello\", \" World\"};\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               this->createResponseChain(chunks),\n                               Codec::getCompressionLevel(),\n                               1000);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MinimumCompressSizeEqualToRequestSize) {\n  using Codec = typename TestFixture::CodecType;\n  auto requestBody = std::string(\"Hello World\");\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(true,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               requestBody,\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(requestBody),\n                               Codec::getCompressionLevel(),\n                               requestBody.length());\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, CompressionDisabledForEncoding) {\n  using Codec = typename TestFixture::CodecType;\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(false,\n                               std::string(\"http://locahost/foo.compressme\"),\n                               Codec::getExpectedEncoding(),\n                               Codec::getExpectedEncoding(),\n                               std::string(\"Hello World\"),\n                               std::string(\"text/html\"),\n                               folly::IOBuf::copyBuffer(\"Hello World\"),\n                               1,\n                               1,\n                               false,\n                               true);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, NoResponseBody) {\n  using Codec = typename TestFixture::CodecType;\n\n  std::string acceptedEncoding = Codec::getExpectedEncoding();\n  std::string expectedEncoding = Codec::getExpectedEncoding();\n  std::string url = std::string(\"http://locahost/foo.compressme\");\n  std::string responseContentType = std::string(\"text/html\");\n  int32_t compressionLevel = Codec::getCompressionLevel();\n  uint32_t minimumCompressionSize = 0;\n\n  auto& requestHandler = this->requestHandler_;\n  auto& downstream = this->downstream_;\n  auto& responseHandler = this->responseHandler_;\n\n  ASSERT_NO_FATAL_FAILURE({\n    // Request Handler Expectations\n    EXPECT_CALL(*requestHandler, onEOM()).Times(1);\n\n    // Need to capture whatever the filter is for ResponseBuilder later\n    EXPECT_CALL(*requestHandler, setResponseHandler(_))\n        .WillOnce(DoAll(SaveArg<0>(&downstream), Return()));\n\n    // Response Handler Expectations\n    // Headers are only sent once\n    EXPECT_CALL(*responseHandler, sendHeaders(_))\n        .WillOnce(DoAll(Invoke([&](HTTPMessage& msg) {\n                          auto& headers = msg.getHeaders();\n                          EXPECT_TRUE(msg.checkForHeaderToken(\n                              HTTP_HEADER_CONTENT_ENCODING,\n                              expectedEncoding.c_str(),\n                              false));\n                          if (msg.getIsChunked()) {\n                            EXPECT_FALSE(headers.exists(\"Content-Length\"));\n                          } else {\n                            // Content-Length is not set on chunked messages\n                            EXPECT_TRUE(headers.exists(\"Content-Length\"));\n                          }\n                        }),\n                        Return()));\n\n    EXPECT_CALL(*responseHandler, sendEOM()).Times(1);\n\n    /* Simulate Request/Response where no body message received */\n    HTTPMessage msg;\n    msg.setURL(url);\n    msg.getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, acceptedEncoding);\n\n    std::set<std::string> compressibleTypes = {\"text/html\"};\n\n    CompressionFilterFactory::Options opts;\n    auto& optCompressionLevel = Codec::getExpectedEncoding() == \"gzip\"\n                                    ? opts.zlibCompressionLevel\n                                    : opts.zstdCompressionLevel;\n    optCompressionLevel = compressionLevel;\n    opts.minimumCompressionSize = minimumCompressionSize;\n    opts.compressibleContentTypes =\n        std::make_shared<std::set<std::string>>(compressibleTypes);\n    opts.enableZstd = true;\n    auto filterFactory = std::make_unique<CompressionFilterFactory>(opts);\n\n    auto filter = filterFactory->onRequest(requestHandler, &msg);\n    filter->setResponseHandler(responseHandler.get());\n\n    // Send fake request\n    filter->onEOM();\n\n    ResponseBuilder(downstream)\n        .status(200, \"OK\")\n        .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)\n        .send();\n\n    ResponseBuilder(downstream).sendWithEOM();\n\n    filter->requestComplete();\n  });\n}\n"
  },
  {
    "path": "proxygen/httpserver/filters/tests/DecompressionFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/httpserver/Mocks.h>\n#include <proxygen/httpserver/filters/DecompressionFilter.h>\n#include <proxygen/lib/utils/ZstdStreamCompressor.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nnamespace {\n\n// Helper to compress data using zstd for testing\nstd::unique_ptr<folly::IOBuf> compressWithZstd(const std::string& data) {\n  ZstdStreamCompressor compressor(4 /* compression level */);\n  auto input = folly::IOBuf::copyBuffer(data);\n  return compressor.compress(input.get(), true /* last */);\n}\n\n} // namespace\n\nclass DecompressionFilterTest : public Test {\n public:\n  void SetUp() override {\n    requestHandler_ = new MockRequestHandler();\n    responseHandler_ = std::make_unique<MockResponseHandler>(requestHandler_);\n  }\n\n  void TearDown() override {\n    if (requestHandler_) {\n      Mock::VerifyAndClear(requestHandler_);\n      delete requestHandler_;\n      requestHandler_ = nullptr;\n    }\n    Mock::VerifyAndClear(responseHandler_.get());\n  }\n\n protected:\n  MockRequestHandler* requestHandler_{nullptr};\n  std::unique_ptr<MockResponseHandler> responseHandler_;\n  ResponseHandler* downstream_{nullptr};\n};\n\n// Test: Request with zstd Content-Encoding header initializes decompressor and\n// removes headers\nTEST_F(DecompressionFilterTest, ZstdContentEncodingInitializesDecompressor) {\n  // Setup expectations\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_))\n      .WillOnce([](std::shared_ptr<HTTPMessage> msg) {\n        // Content-Encoding should be removed\n        EXPECT_FALSE(msg->getHeaders().exists(HTTP_HEADER_CONTENT_ENCODING));\n        // Content-Length should be removed\n        EXPECT_FALSE(msg->getHeaders().exists(HTTP_HEADER_CONTENT_LENGTH));\n      });\n\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  // Create filter\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  // Create request with zstd Content-Encoding\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n\n  filter->onRequest(std::move(msg));\n\n  // Clean up - filter deletes itself and calls upstream requestComplete\n  filter->requestComplete();\n}\n\n// Test: Request without Content-Encoding header passes through unchanged\nTEST_F(DecompressionFilterTest, NoContentEncodingPassesThrough) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_))\n      .WillOnce([](std::shared_ptr<HTTPMessage> msg) {\n        // Headers should remain unchanged (no Content-Encoding was set)\n        EXPECT_FALSE(msg->getHeaders().exists(HTTP_HEADER_CONTENT_ENCODING));\n      });\n\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n\n  filter->onRequest(std::move(msg));\n  filter->requestComplete();\n}\n\n// Test: Body decompression with valid zstd compressed data\nTEST_F(DecompressionFilterTest, ValidZstdBodyDecompression) {\n  const std::string originalData = \"Hello, World! This is test data.\";\n\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n\n  std::string decompressedData;\n  // May be called multiple times (body + EOM remaining data)\n  EXPECT_CALL(*requestHandler_, onBody(_))\n      .WillRepeatedly([&decompressedData](std::shared_ptr<folly::IOBuf> body) {\n        if (body) {\n          auto cloned = body->clone();\n          auto data = cloned->coalesce();\n          decompressedData += std::string(\n              reinterpret_cast<const char*>(data.data()), data.size());\n        }\n      });\n\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  // Send request with zstd encoding\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  filter->onRequest(std::move(msg));\n\n  // Send compressed body\n  auto compressedBody = compressWithZstd(originalData);\n  ASSERT_TRUE(compressedBody != nullptr);\n\n  filter->onBody(std::move(compressedBody));\n  filter->onEOM();\n  filter->requestComplete();\n\n  EXPECT_EQ(decompressedData, originalData);\n}\n\n// Test: Multiple body chunks being decompressed\nTEST_F(DecompressionFilterTest, MultipleChunksDecompression) {\n  const std::string chunk1 = \"First chunk of data. \";\n  const std::string chunk2 = \"Second chunk of data. \";\n  const std::string chunk3 = \"Third and final chunk.\";\n  const std::string fullData = chunk1 + chunk2 + chunk3;\n\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n\n  std::string accumulatedData;\n  EXPECT_CALL(*requestHandler_, onBody(_))\n      .WillRepeatedly([&accumulatedData](std::shared_ptr<folly::IOBuf> body) {\n        if (body) {\n          auto cloned = body->clone();\n          auto data = cloned->coalesce();\n          accumulatedData += std::string(\n              reinterpret_cast<const char*>(data.data()), data.size());\n        }\n      });\n\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  filter->onRequest(std::move(msg));\n\n  // Compress entire data as single stream then send\n  auto compressedBody = compressWithZstd(fullData);\n  ASSERT_TRUE(compressedBody != nullptr);\n\n  filter->onBody(std::move(compressedBody));\n  filter->onEOM();\n  filter->requestComplete();\n\n  EXPECT_EQ(accumulatedData, fullData);\n}\n\n// Test: Decompression error handling with invalid compressed data\nTEST_F(DecompressionFilterTest, InvalidCompressedDataSendsAbort) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n\n  // Should not receive body on error\n  EXPECT_CALL(*requestHandler_, onBody(_)).Times(0);\n\n  // Should send abort on decompression error\n  EXPECT_CALL(*responseHandler_, sendAbort(_)).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  filter->onRequest(std::move(msg));\n\n  // Send invalid (not actually compressed) data\n  auto invalidBody = folly::IOBuf::copyBuffer(\"This is not valid zstd data!\");\n  filter->onBody(std::move(invalidBody));\n\n  // Clean up via onError since we aborted\n  filter->onError(ProxygenError::kErrorUnknown);\n}\n\n// Test: Pass through when no decompressor is set (no Content-Encoding)\nTEST_F(DecompressionFilterTest, PassThroughWithoutDecompressor) {\n  const std::string originalData = \"Uncompressed data\";\n\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n\n  std::string receivedData;\n  EXPECT_CALL(*requestHandler_, onBody(_))\n      .WillOnce([&receivedData](std::shared_ptr<folly::IOBuf> body) {\n        if (body) {\n          auto cloned = body->clone();\n          auto data = cloned->coalesce();\n          receivedData = std::string(reinterpret_cast<const char*>(data.data()),\n                                     data.size());\n        }\n      });\n\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  // No Content-Encoding header\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n\n  filter->onRequest(std::move(msg));\n\n  auto body = folly::IOBuf::copyBuffer(originalData);\n  filter->onBody(std::move(body));\n  filter->onEOM();\n  filter->requestComplete();\n\n  // Data should pass through unchanged\n  EXPECT_EQ(receivedData, originalData);\n}\n\n// Test: Content-Length header removal when decompression is active\nTEST_F(DecompressionFilterTest, ContentLengthRemovedWhenDecompressing) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_))\n      .WillOnce([](std::shared_ptr<HTTPMessage> msg) {\n        // Both Content-Encoding and Content-Length should be removed\n        EXPECT_FALSE(msg->getHeaders().exists(HTTP_HEADER_CONTENT_ENCODING));\n        EXPECT_FALSE(msg->getHeaders().exists(HTTP_HEADER_CONTENT_LENGTH));\n      });\n\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"12345\");\n\n  filter->onRequest(std::move(msg));\n  filter->requestComplete();\n}\n\n// Test: Unsupported Content-Encoding passes through unchanged\nTEST_F(DecompressionFilterTest, UnsupportedEncodingPassesThrough) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_))\n      .WillOnce([](std::shared_ptr<HTTPMessage> msg) {\n        // Unsupported encoding should remain\n        EXPECT_EQ(\n            msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_ENCODING),\n            \"gzip\");\n        // Content-Length should also remain\n        EXPECT_EQ(\n            msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH),\n            \"500\");\n      });\n\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"gzip\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"500\");\n\n  filter->onRequest(std::move(msg));\n  filter->requestComplete();\n}\n\n// Test: EOM handling without decompressor (pass through)\nTEST_F(DecompressionFilterTest, EOMPassThroughWithoutDecompressor) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n\n  filter->onRequest(std::move(msg));\n  filter->onEOM();\n  filter->requestComplete();\n}\n\n// Test: Empty body with zstd encoding\nTEST_F(DecompressionFilterTest, EmptyBodyWithZstdEncoding) {\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  filter->onRequest(std::move(msg));\n  // Don't send any body, just EOM\n  filter->onEOM();\n  filter->requestComplete();\n}\n\n// Test: DecompressionFilterFactory creates filter for zstd requests\nTEST_F(DecompressionFilterTest, FactoryCreatesFilterForZstd) {\n  DecompressionFilterFactory factory;\n\n  HTTPMessage msg;\n  msg.setURL(\"/test\");\n  msg.getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  auto* handler = factory.onRequest(requestHandler_, &msg);\n\n  // Should return a new filter wrapping the handler\n  EXPECT_NE(handler, requestHandler_);\n\n  // Clean up - need to set response handler and complete the request\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_)).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  handler->setResponseHandler(responseHandler_.get());\n  handler->requestComplete();\n}\n\n// Test: DecompressionFilterFactory creates filter for non-zstd requests\nTEST_F(DecompressionFilterTest, FactoryCreatesFilterForNonZstd) {\n  DecompressionFilterFactory factory;\n\n  HTTPMessage msg;\n  msg.setURL(\"/test\");\n\n  auto* handler = factory.onRequest(requestHandler_, &msg);\n\n  // Factory always creates a new filter wrapping the handler\n  EXPECT_NE(handler, requestHandler_);\n\n  // Clean up - need to set response handler and complete the request\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_)).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  handler->setResponseHandler(responseHandler_.get());\n  handler->requestComplete();\n}\n\n// Test: DecompressionFilterFactory creates filter for gzip requests\nTEST_F(DecompressionFilterTest, FactoryCreatesFilterForGzip) {\n  DecompressionFilterFactory factory;\n\n  HTTPMessage msg;\n  msg.setURL(\"/test\");\n  msg.getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"gzip\");\n\n  auto* handler = factory.onRequest(requestHandler_, &msg);\n\n  // Factory always creates a new filter wrapping the handler\n  EXPECT_NE(handler, requestHandler_);\n\n  // Clean up - need to set response handler and complete the request\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_)).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  handler->setResponseHandler(responseHandler_.get());\n  handler->requestComplete();\n}\n\n// Test: Large data decompression\nTEST_F(DecompressionFilterTest, LargeDataDecompression) {\n  // Create a large string (100KB)\n  std::string largeData(100UL * 1024, 'X');\n  for (size_t i = 0; i < largeData.size(); i += 100) {\n    largeData[i] = static_cast<char>('A' + (i % 26));\n  }\n\n  EXPECT_CALL(*requestHandler_, setResponseHandler(_))\n      .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));\n\n  EXPECT_CALL(*requestHandler_, onRequest(_)).Times(1);\n\n  std::string accumulatedData;\n  EXPECT_CALL(*requestHandler_, onBody(_))\n      .WillRepeatedly([&accumulatedData](std::shared_ptr<folly::IOBuf> body) {\n        if (body) {\n          auto cloned = body->clone();\n          auto data = cloned->coalesce();\n          accumulatedData += std::string(\n              reinterpret_cast<const char*>(data.data()), data.size());\n        }\n      });\n\n  EXPECT_CALL(*requestHandler_, onEOM()).Times(1);\n  EXPECT_CALL(*requestHandler_, requestComplete()).Times(1);\n\n  auto* filter = new DecompressionFilter(requestHandler_);\n  filter->setResponseHandler(responseHandler_.get());\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setURL(\"/test\");\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"zstd\");\n\n  filter->onRequest(std::move(msg));\n\n  auto compressedBody = compressWithZstd(largeData);\n  ASSERT_TRUE(compressedBody != nullptr);\n\n  filter->onBody(std::move(compressedBody));\n  filter->onEOM();\n  filter->requestComplete();\n\n  EXPECT_EQ(accumulatedData, largeData);\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/EchoHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"EchoHandler.h\"\n\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\n#include \"EchoStats.h\"\n\nusing namespace proxygen;\n\nDEFINE_bool(request_number,\n            true,\n            \"Include request sequence number in response\");\n\nnamespace EchoService {\n\nEchoHandler::EchoHandler(EchoStats* stats) : stats_(stats) {\n}\n\nvoid EchoHandler::onRequest(std::unique_ptr<HTTPMessage> req) noexcept {\n  stats_->recordRequest();\n  ResponseBuilder builder(downstream_);\n  builder.status(200, \"OK\");\n  if (FLAGS_request_number) {\n    builder.header(\"Request-Number\",\n                   folly::to<std::string>(stats_->getRequestCount()));\n  }\n  req->getHeaders().forEach(\n      [&](const std::string& name, const std::string& value) {\n        builder.header(folly::to<std::string>(\"x-echo-\", name), value);\n      });\n  builder.send();\n}\n\nvoid EchoHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  ResponseBuilder(downstream_).body(std::move(body)).send();\n}\n\nvoid EchoHandler::onEOM() noexcept {\n  ResponseBuilder(downstream_).sendWithEOM();\n}\n\nvoid EchoHandler::onUpgrade(UpgradeProtocol /*protocol*/) noexcept {\n  // handler doesn't support upgrades\n}\n\nvoid EchoHandler::requestComplete() noexcept {\n  delete this;\n}\n\nvoid EchoHandler::onError(ProxygenError /*err*/) noexcept {\n  delete this;\n}\n} // namespace EchoService\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/EchoHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <proxygen/httpserver/RequestHandler.h>\n\nnamespace proxygen {\nclass ResponseHandler;\n}\n\nnamespace EchoService {\n\nclass EchoStats;\n\nclass EchoHandler : public proxygen::RequestHandler {\n public:\n  explicit EchoHandler(EchoStats* stats);\n\n  void onRequest(\n      std::unique_ptr<proxygen::HTTPMessage> headers) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onUpgrade(proxygen::UpgradeProtocol proto) noexcept override;\n\n  void requestComplete() noexcept override;\n\n  void onError(proxygen::ProxygenError err) noexcept override;\n\n private:\n  EchoStats* const stats_{nullptr};\n};\n\n} // namespace EchoService\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/EchoServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Memory.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <folly/portability/Unistd.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\n#include \"EchoHandler.h\"\n#include \"EchoStats.h\"\n\nusing namespace EchoService;\nusing namespace proxygen;\n\nusing folly::SocketAddress;\n\nusing Protocol = HTTPServer::Protocol;\n\nDEFINE_int32(http_port, 11000, \"Port to listen on with HTTP protocol\");\nDEFINE_int32(h2_port, 11002, \"Port to listen on with HTTP/2 protocol\");\nDEFINE_string(ip, \"localhost\", \"IP/Hostname to bind to\");\nDEFINE_int32(threads,\n             0,\n             \"Number of threads to listen on. Numbers <= 0 \"\n             \"will use the number of cores on this machine.\");\n\nclass EchoHandlerFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n    stats_.reset(new EchoStats);\n  }\n\n  void onServerStop() noexcept override {\n    stats_.reset();\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new EchoHandler(stats_.get());\n  }\n\n private:\n  folly::ThreadLocalPtr<EchoStats> stats_;\n};\n\nint main(int argc, char* argv[]) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  google::InstallFailureSignalHandler();\n\n  std::vector<HTTPServer::IPConfig> IPs = {\n      {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},\n      {SocketAddress(FLAGS_ip, FLAGS_h2_port, true), Protocol::HTTP2},\n  };\n\n  if (FLAGS_threads <= 0) {\n    FLAGS_threads = folly::available_concurrency();\n    CHECK(FLAGS_threads > 0);\n  }\n\n  HTTPServerOptions options;\n  options.threads = static_cast<size_t>(FLAGS_threads);\n  options.idleTimeout = std::chrono::milliseconds(60000);\n  options.shutdownOn = {SIGINT, SIGTERM};\n  options.enableContentCompression = false;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<EchoHandlerFactory>().build();\n  // Increase the default flow control to 1MB/10MB\n  options.initialReceiveWindow = uint32_t(1 << 20);\n  options.receiveStreamWindowSize = uint32_t(1 << 20);\n  options.receiveSessionWindowSize = 10 * (1 << 20);\n\n  HTTPServer server(std::move(options));\n  server.bind(IPs);\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([&]() { server.start(); });\n\n  t.join();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/EchoStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\nnamespace EchoService {\n\n/**\n * Just some dummy class containing request count. Since we keep\n * one instance of this in each class, there is no need of\n * synchronization\n */\nclass EchoStats {\n public:\n  EchoStats() = default;\n  virtual ~EchoStats() = default;\n  EchoStats(const EchoStats&) = delete;\n  EchoStats& operator=(const EchoStats&) = delete;\n  EchoStats(EchoStats&&) = delete;\n  EchoStats& operator=(EchoStats&&) = delete;\n\n  // NOTE: We make the following methods `virtual` so that we can\n  //       mock them using Gmock for our C++ unit-tests. EchoStats\n  //       is an external dependency to handler and we should be\n  //       able to mock it.\n\n  virtual void recordRequest() {\n    ++reqCount_;\n  }\n\n  virtual uint64_t getRequestCount() {\n    return reqCount_;\n  }\n\n private:\n  uint64_t reqCount_{0};\n};\n\n} // namespace EchoService\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET EchoHandlerTests\n  SOURCES\n    EchoHandlerTest.cpp\n    ../EchoServer.cpp\n    ../EchoHandler.cpp\n  DEPENDS\n    proxygen\n    proxygenhttpserver\n    proxygencurl\n    testmain\n)\n"
  },
  {
    "path": "proxygen/httpserver/samples/echo/test/EchoHandlerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/echo/EchoHandler.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/httpserver/Mocks.h>\n#include <proxygen/httpserver/samples/echo/EchoStats.h>\n\nusing namespace EchoService;\nusing namespace proxygen;\nusing namespace testing;\n\nclass MockEchoStats : public EchoStats {\n public:\n  MOCK_METHOD(void, recordRequest, ());\n  MOCK_METHOD(uint64_t, getRequestCount, ());\n};\n\nclass EchoHandlerFixture : public testing::Test {\n public:\n  void SetUp() override {\n    handler = new EchoHandler(&stats);\n    responseHandler = std::make_unique<MockResponseHandler>(handler);\n    handler->setResponseHandler(responseHandler.get());\n  }\n\n  void TearDown() override {\n    Mock::VerifyAndClear(&stats);\n    Mock::VerifyAndClear(responseHandler.get());\n\n    // Since there is no easy way to verify that handler has deleted\n    // itself, its advised to run test binary under AddressSanitzer\n    // to verify that.\n  }\n\n protected:\n  EchoHandler* handler{nullptr};\n  StrictMock<MockEchoStats> stats;\n  std::unique_ptr<MockResponseHandler> responseHandler;\n};\n\nTEST_F(EchoHandlerFixture, OnProperRequestSendsResponse) {\n  EXPECT_CALL(stats, recordRequest()).WillOnce(Return());\n  EXPECT_CALL(stats, getRequestCount()).WillOnce(Return(5));\n\n  HTTPMessage response;\n  EXPECT_CALL(*responseHandler, sendHeaders(_))\n      .WillOnce(DoAll(SaveArg<0>(&response), Return()));\n  EXPECT_CALL(*responseHandler, sendEOM()).WillOnce(Return());\n\n  // Since we know we dont touch request, its ok to pass an empty message here.\n  handler->onRequest(std::make_unique<HTTPMessage>());\n  handler->onEOM();\n  handler->requestComplete();\n\n  EXPECT_EQ(\"5\", response.getHeaders().getSingleOrEmpty(\"Request-Number\"));\n  EXPECT_EQ(200, response.getStatusCode());\n}\n\nTEST_F(EchoHandlerFixture, ReplaysBodyProperly) {\n  EXPECT_CALL(stats, recordRequest()).WillOnce(Return());\n  EXPECT_CALL(stats, getRequestCount()).WillOnce(Return(5));\n\n  HTTPMessage response;\n  folly::fbstring body;\n\n  EXPECT_CALL(*responseHandler, sendHeaders(_))\n      .WillOnce(DoAll(SaveArg<0>(&response), Return()));\n\n  EXPECT_CALL(*responseHandler, sendBody(_))\n      .WillRepeatedly(DoAll(Invoke([&](std::shared_ptr<folly::IOBuf> b) {\n                              body += b->moveToFbString();\n                            }),\n                            Return()));\n\n  EXPECT_CALL(*responseHandler, sendEOM()).WillOnce(Return());\n\n  // Since we know we dont touch request, its ok to pass an empty message here.\n  handler->onRequest(std::make_unique<HTTPMessage>());\n  handler->onBody(folly::IOBuf::copyBuffer(\"part1\"));\n  handler->onBody(folly::IOBuf::copyBuffer(\"part2\"));\n  handler->onEOM();\n  handler->requestComplete();\n\n  EXPECT_EQ(\"5\", response.getHeaders().getSingleOrEmpty(\"Request-Number\"));\n  EXPECT_EQ(200, response.getStatusCode());\n  EXPECT_EQ(\"part1part2\", body);\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Manually maintained - granular targets for HQ server libraries\n# These break the dependency on monolithic proxygen for downstream projects (moxygen)\n# Target names are simplified; CMAKE_TARGET_OVERRIDES in generate_cmake.py maps\n# auto-generated dep names to these clean names.\n\nproxygen_add_library(proxygen_hq_logger_helper\n  SRCS\n    HQLoggerHelper.cpp\n  EXPORTED_DEPS\n    mvfst::mvfst_logging_file_qlogger\n    mvfst::mvfst_logging_qlogger\n)\n\nproxygen_add_library(proxygen_hq_insecure_verifier\n  EXPORTED_DEPS\n    fizz::fizz\n)\n\nproxygen_add_library(proxygen_hq_server\n  SRCS\n    FizzContext.cpp\n    HQParams.cpp\n    HQServer.cpp\n  DEPS\n    proxygen_hq_logger_helper\n    proxygen_http_session_hq_downstream_session\n    mvfst::mvfst_common_udpsocket_folly_async_udp_socket\n    fizz::fizz\n    Folly::folly_file_util\n    Folly::folly_random\n    Folly::folly_io_socket_option_map\n  EXPORTED_DEPS\n    proxygen_http_http_headers\n    proxygen_http_codec_codec\n    proxygen_http_session_hq_session\n    proxygen_http_session_http_transaction\n    proxygen_http_session_server\n    mvfst::mvfst_constants\n    mvfst::mvfst_api_stream_async_transport\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_congestion_control_congestion_controller_factory\n    mvfst::mvfst_fizz_client_handshake_psk_cache\n    mvfst::mvfst_server_quic_handshake_socket_holder\n    mvfst::mvfst_server_server\n    mvfst::mvfst_state_transport_settings\n    wangle::wangle_ssl_ssl_config\n    Folly::folly_network_address\n    Folly::folly_optional\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_hq_samples\n  SRCS\n    H2Server.cpp\n    HQClient.cpp\n    HQCommandLine.cpp\n    HQServerModule.cpp\n    SampleHandlers.cpp\n  DEPS\n    proxygen_hq_logger_helper\n    proxygen_hq_insecure_verifier\n    proxygen_http_synchronized_quic_lrucache\n    proxygen_http_session_hq_session\n    proxygen_webtransport\n    proxygen_transport_persistent_quic_psk_cache\n    proxygen_utils_logging\n    proxygen_utils_util_inl\n    mvfst::mvfst_constants\n    mvfst::mvfst_client_client\n    mvfst::mvfst_common_udpsocket_folly_async_udp_socket\n    mvfst::mvfst_congestion_control_congestion_controller_factory\n    mvfst::mvfst_fizz_client_handshake\n    Folly::folly_io_async_scoped_event_base_thread\n    Folly::folly_json_dynamic\n    Folly::folly_portability_gflags\n  EXPORTED_DEPS\n    proxygen_hq_server\n    proxygenhttpserver\n    proxygen_hq_devious_baton\n    proxygencurl\n    proxygen_http_http_utils\n    proxygen_http_message\n    proxygen_http_codec_codec\n    proxygen_http_session_hq_upstream_session\n    proxygen_http_session_http_transaction\n    proxygen_http_session_client\n    proxygen_utils_safe_path\n    mvfst::mvfst_api_stream_async_transport\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_common_events_highres_quic_timer\n    Folly::folly_conv\n    Folly::folly_file\n    Folly::folly_file_util\n    Folly::folly_format\n    Folly::folly_memory\n    Folly::folly_random\n    Folly::folly_string\n    Folly::folly_synchronized\n    Folly::folly_thread_local\n    Folly::folly_executors_global_executor\n    Folly::folly_futures_core\n    Folly::folly_io_iobuf\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_event_base_manager\n    ${GFLAG_DEPENDENCIES}\n    glog::glog\n)\n\nadd_subdirectory(devious)\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/ConnIdLogger.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <gflags/gflags.h>\n#include <glog/logging.h>\n\n#include <cctype>\n#include <folly/Conv.h>\n#include <folly/File.h>\n#include <folly/String.h>\n#include <folly/Synchronized.h>\n#include <map>\n#include <memory>\n#include <string>\n\nnamespace proxygen {\n\nstruct ConnIdLogSink : google::LogSink {\n  using FileEntry =\n      std::pair<folly::File, std::chrono::system_clock::time_point>;\n\n  ConnIdLogSink(std::string logDir, std::string logPrefix)\n      : logDir_(std::move(logDir)), prefix_(std::move(logPrefix)) {\n  }\n\n  void send(google::LogSeverity severity,\n            const char* /*full_filename*/,\n            const char* base_filename,\n            int line,\n            const struct ::tm* tm_time,\n            const char* message,\n            size_t message_len) override {\n    folly::StringPiece testMsg(message, message_len);\n    // The incoming string are expected to be in the format of\n    // \".* CID=([a-f0-9]+)[, ].*\"\n    folly::StringPiece pre, post; // pre will be ignored\n    folly::split(\"CID=\", testMsg, pre, post);\n    if (post.empty()) {\n      return;\n    }\n    std::vector<folly::StringPiece> cids;\n    folly::split(',', post, cids);\n    for (const auto& cidSp : cids) {\n      if (!std::all_of(cidSp.begin(), cidSp.end(), [](char c) {\n            return std::isalnum(c);\n          })) {\n        continue;\n      }\n      auto cid = cidSp.str();\n      int fd = -1;\n      {\n        auto now = std::chrono::system_clock::now();\n        auto fdMap = fdMap_.wlock();\n        auto it = fdMap->find(cid);\n        if (it == fdMap->end()) {\n          auto insert = fdMap->emplace(\n              std::piecewise_construct,\n              std::forward_as_tuple(cid),\n              std::forward_as_tuple(\n                  std::piecewise_construct,\n                  std::forward_as_tuple(\n                      folly::to<std::string>(\n                          logDir_, \"/\", prefix_, \".\", cid, \".html\"),\n                      O_CREAT | O_RDWR | O_APPEND),\n                  std::forward_as_tuple(now)));\n          fd = insert.first->second.first.fd();\n        } else {\n          fd = it->second.first.fd();\n          it->second.second = now;\n        }\n        closeOldFds(*fdMap, now);\n      }\n      char timebuf[64];\n      strftime(timebuf, sizeof(timebuf), \"%m%d %R\", tm_time);\n      auto msg = folly::to<std::string>(severityMap[severity],\n                                        timebuf,\n                                        ' ',\n                                        base_filename,\n                                        ':',\n                                        line,\n                                        ' ',\n                                        testMsg);\n      [[maybe_unused]] auto writeRes = ::write(fd, msg.c_str(), msg.size());\n      writeRes = ::write(fd, \"<br/>\", 5);\n    } // else, not for a specific CID\n  }\n\n  [[nodiscard]] bool isValid() const {\n    return !logDir_.empty() && ::access(logDir_.c_str(), W_OK) == 0;\n  }\n\n private:\n  using FdMap = std::map<std::string, FileEntry>;\n\n  void closeOldFds(FdMap& fdMap, std::chrono::system_clock::time_point now) {\n    // Close any logfiles open for > 1 min\n    for (auto it = fdMap.begin(); it != fdMap.end();) {\n      if (now > it->second.second + kMaxAge) {\n        auto eraseIt = it++;\n        fdMap.erase(eraseIt);\n      } else {\n        ++it;\n      }\n    }\n  }\n\n  std::string logDir_;\n  std::string prefix_;\n  folly::Synchronized<FdMap> fdMap_;\n  std::array<char, 5> severityMap{{'V', 'I', 'W', 'E', 'F'}};\n  const std::chrono::seconds kMaxAge{60};\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/FizzContext.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <fizz/compression/ZlibCertificateDecompressor.h>\n#include <fizz/compression/ZstdCertificateDecompressor.h>\n#include <fizz/server/AeadTicketCipher.h>\n#include <fizz/server/DefaultCertManager.h>\n#include <fizz/server/TicketCodec.h>\n#include <folly/FileUtil.h>\n#include <folly/Random.h>\n#include <string>\n\nnamespace {\nconst std::string kDefaultCertData = R\"(\n-----BEGIN CERTIFICATE-----\nMIIGGzCCBAOgAwIBAgIJAPowD79hiDyZMA0GCSqGSIb3DQEBCwUAMIGjMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVubG8gUGFy\nazERMA8GA1UECgwIUHJveHlnZW4xETAPBgNVBAsMCFByb3h5Z2VuMREwDwYDVQQD\nDAhQcm94eWdlbjExMC8GCSqGSIb3DQEJARYiZmFjZWJvb2stcHJveHlnZW5AZ29v\nZ2xlZ3JvdXBzLmNvbTAeFw0xOTA1MDgwNjU5MDBaFw0yOTA1MDUwNjU5MDBaMIGj\nMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVu\nbG8gUGFyazERMA8GA1UECgwIUHJveHlnZW4xETAPBgNVBAsMCFByb3h5Z2VuMREw\nDwYDVQQDDAhQcm94eWdlbjExMC8GCSqGSIb3DQEJARYiZmFjZWJvb2stcHJveHln\nZW5AZ29vZ2xlZ3JvdXBzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALXZs4+YnCE8aMAL5gWNjLRm2EZiFHWoKpt42on8y+SZdb1xdSZ0rx6/jl4w\n8V5aiLLNmboa1ULNWLS40mEUoqRPEUiBiN3T/3HomzMCLZ52xaaKS1sW9+ZPsSlT\nomwV4HupJWKQaxpu+inY98mxGaZjzHie3AoydovD+rWWLj4mSX9DchWbC8DYq7xu\n4qKedgHMJlsP3luYgnRSsZ+vlTEe/K41Czt+GGhViRNL8Nm3wZrxAGYqTx/zrqsT\nR8qA3gwfPPqJJH5UprtvHXDS99yiy6MYyWBr/BbZ37A5X9pWCL09aLEIrQGQWtVu\nCnBNCrQgYDgD7Y4+Q4Lfouap7I3YpuJM5cP1NO1x0Voyv2km1tmZpjUavnKyYT/v\nXUCkGrWxeuMkqm68eOnadA7A8BM9b++f6NIgaexb9+Rq8QK74MpMm7/+XMWiAS9z\n62hgKBd4mtUulJH1YxoQBIkfRa8pkB45nGiTrL2zzpIOoOirNe3/7FVI9LqPphPN\n64ojfqZsTiGrC50R/86/p2jBs0fwrXy8opWM7Kmp1h2oNPqtgOC0Zj7IcmvEp2xa\nwI6jN4XxbhDQpo3Iz/KRDxXFT4kAjdLDibWH41PccwSbHvg8zjmAGCxW6sC6bmp6\nlywMzonS1VWkp1iNQ2u4bdMeDGnsaN0hOBemBLr/p3L1ee/RAgMBAAGjUDBOMB0G\nA1UdDgQWBBSHFEM/GlCxZgg9qpi9REqm/RDkZDAfBgNVHSMEGDAWgBSHFEM/GlCx\nZgg9qpi9REqm/RDkZDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBG\nAtowRS0Wsr9cVRKVxEM/7ZxCDcrTg7gUBD/S8RYnS2bJp5ut/3SgO0FZsQKG4k8O\nCXE/dQgwIaBqxSioE3L/l+m/+gedZgqaXg7l6EJLr20sUB5PVrJoQznMIwr/FuYZ\nLG4nKK/K7eKf2m1Gn54kpeWz+BtgIRU4YPkZHGtQW3ER+wnmlPQfGDiN0JymqR80\nTTXlgg03L6jCFQpYGKCLbKpql+cBixmI6TeUtArosCsqZokUXNM7j5u7m1IhY1EL\npNpSaUMU7LmHOmfnxIHzmNzages+mxKOHJLKBbuQx0u87uGy3HInwbNK7hDHXWLF\nmXPXDhrWjBbm1RPnq8cX9nFuPS6Cd+hROEr+VB7m+Sij5QyV5pRBS0x/54tiiEv3\n8eIFl6aYqTBcCMrtlxVn8sHcA/iGrysIuidWVxQfs4wmM/apR5YgSjTvN/OAB5Mo\n/5RWdxBg3jNPGk/GzPDk6FcN5kp7yRLLyAOAnPDUQRC8CkSkyOwriOMe310CnTL4\nKCWp7UpoF/qZJEGhYffH85SORpxj09284tZUnLSthnRmIdYB2kWg9AARu3Vhugx8\nE9HGSZzTGAsPEBikDbpUimN0zWLw8VJKL+KJURl4dX4tDRe+R2u5cWm8x3HOcDUI\nj9aXkPagbL/an2g05K0hIhyANbER7HAZlJ21pJdCIQ==\n-----END CERTIFICATE-----\n)\";\n\n// The private key below is only used for test purposes\n// @lint-ignore-every PRIVATEKEY\nconst std::string kDefaultKeyData = R\"(\n-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAtdmzj5icITxowAvmBY2MtGbYRmIUdagqm3jaifzL5Jl1vXF1\nJnSvHr+OXjDxXlqIss2ZuhrVQs1YtLjSYRSipE8RSIGI3dP/ceibMwItnnbFpopL\nWxb35k+xKVOibBXge6klYpBrGm76Kdj3ybEZpmPMeJ7cCjJ2i8P6tZYuPiZJf0Ny\nFZsLwNirvG7iop52AcwmWw/eW5iCdFKxn6+VMR78rjULO34YaFWJE0vw2bfBmvEA\nZipPH/OuqxNHyoDeDB88+okkflSmu28dcNL33KLLoxjJYGv8FtnfsDlf2lYIvT1o\nsQitAZBa1W4KcE0KtCBgOAPtjj5Dgt+i5qnsjdim4kzlw/U07XHRWjK/aSbW2Zmm\nNRq+crJhP+9dQKQatbF64ySqbrx46dp0DsDwEz1v75/o0iBp7Fv35GrxArvgykyb\nv/5cxaIBL3PraGAoF3ia1S6UkfVjGhAEiR9FrymQHjmcaJOsvbPOkg6g6Ks17f/s\nVUj0uo+mE83riiN+pmxOIasLnRH/zr+naMGzR/CtfLyilYzsqanWHag0+q2A4LRm\nPshya8SnbFrAjqM3hfFuENCmjcjP8pEPFcVPiQCN0sOJtYfjU9xzBJse+DzOOYAY\nLFbqwLpuanqXLAzOidLVVaSnWI1Da7ht0x4Maexo3SE4F6YEuv+ncvV579ECAwEA\nAQKCAgBg5/5UC1NIMtTvYmfVlbThfdzKxQF6IX9zElgDKH/O9ihUJ93x/ERF8nZ/\noz08tqoZ/o5pKltzGdKnm8YgjcqOHMRtCvpQm+SIYxgxenus8kYplZDKndbFGLqj\n9zmat53EyEJv393zXChbnI+PH503mf8gWCeSF4osuOclVT6XR/fqpZpqARGmVtBN\nvhlv51mjY5Mc+7vWu9LpAhg9rGeooYatnv65WVzQXKSLb/CNVOsLElrQFsPLlyQB\nbmjXdQzfENaB/AtCdwHS6EecFBCZtvclltPZWjIgS0J0ul5mD2rgzZS4opLvPmnp\nSpateaC2lHox34X8Qxne6CX7HZo8phw1g3Lt5378cAcSOxQGyjCw3k7CS28Uwze6\n4t7VSn9VxWviYiIV+sgj0EbEyJ/K2YcRKDTG1+jY3AuuTR7lcTO35MCroaQIpk14\n4ywTKT1HSTkPV5bNYB3tD4fHAB24Q9rs7GvZgeGWWv3RQTWVTZXnx3zuy5Uh8quy\n0Nu8OAEZcKNo+Qq2iTTMf4m9F7OMkWq3aGzdeTBsiKnkaYKyYrNiSNQHgepO5jBT\njRGgJaA7LUakenb0yCexpz5u06zWWeCHu2f7STaVELFWAzvu5WfFcIZbPIY5zGDR\ngwcrOQJGAc6CKZI6QCd/h0ruwux8z0E9UAnrxHYK/oaov2Oj8QKCAQEA6FphCswr\n7ZwB+EXLIZ0eIfDEg3ms1+bEjhMxq4if7FUVS8OOJBqhn0Q1Tj+goCuGtZdwmNqA\nnTjh2A0MDYkBmqpyY+BiJRA/87qVYESPNObMs39Sk6CwKk0esHiquyiMavj1pqYw\nSje5cEdcB551MncyxL+IjC2GGojAJnolgV1doLh08Y6pHa6OkrwjmQxJc7jDBQEv\n6h/m3J9Fp1cjdkiM8A3MWW/LomZUEqQerjnW7d0YxbgKk4peGq+kymgZIESuaeaI\n36fPy9Md53XAs+eHES/YLbdM54pAQR93fta0GoxkGCc0lEr/z917ybyj5AljYwRq\nBiPDEVpyqPHeEwKCAQEAyFuMm5z4crMiE843w1vOiTo17uqG1m7x4qbpY7+TA+nd\nd491CPkt7M+eDjlCplHhDYjXWOBKrPnaijemA+GMubOJBJyitNsIq0T+wnwU20PA\nTHqm7dOuQVeBW9EEmMxLoq7YEFx6CnQMHhWP0JlCRwXTB4ksQsZX6GRUtJ5dAwaQ\nALUuydJ0nVtTFb07WudK654xlkpq5gxB1zljBInHV8hQgsRnXY0SijtGzbenHWvs\njBmXTiOeOBVGehENNxolrLB07JhsXM4/9UAtn+nxESosM0zBGJC79pW3yVb+/7FL\n0tEFi4e040ock0BlxVlOBkayAA/hAaaBvAhlUs2nCwKCAQEAosSdcojwxPUi1B9g\nW13LfA9EOq4EDQLV8okzpGyDS3WXA4ositI1InMPvI8KIOoc5hz+fbWjn3/3hfgt\n11WA0C5TD/BiEIC/rCeq+NNOVsrP33Z0DILmpdt8gjclsxKGu3FH9MQ60+MRfrwe\nlh/FDeM+p2FdcIV7ih7+LHYoy+Tx7+MH2SgNBIQB0H0HmvFmizCFPX5FaIeMnETe\n8Ik0iGnugUPJQWX1iwCQKLbb30UZcWwPLILutciaf6tHj5s47sfuPrWGcNcH1EtC\niaCNq/mnPrz7fZsIvrK0rGo0taAGbwqmG91rEe8wIReQ3hPN47NH8ldnRoHK5t8r\nr3owDQKCAQBWw/avSRn6qgKe6xYQ/wgBO3kxvtSntiIAEmJN9R+YeUWUSkbXnPk7\nbWm4JSns1taMQu9nKLKOGCGA67p0Qc/sd4hlu+NmSNiHOvjMhmmNzthPBmqV4a67\n00ZM2caQ2SAEEo21ACdFsZ2xxYqjPkuKcEZEJC5LuJNHK3PXSCFldwkTlWLuuboQ\njwT7DBjRNAqo4Lf+qrmCaFp29v4fb/8oz7G1/5H33Gjj/emamua/AgbNYSO6Dgit\npuD/abT8YNFh6ISqFRQQWK0v6xwW/XuNAGNlz95rYfpUPd/6TDdfyYrZf/VTyHAY\nYfbf+epYvWThqOnaxwWc7luOb2BZrH+jAoIBAEODPVTsGYwqh5D5wqV1QikczGz4\n/37CgGNIWkHvH/dadLDiAQ6DGuMDiJ6pvRQaZCoALdovjzFHH4JDJR6fCkZzKkQs\neaF+jB9pzq3GEXylU9JPIPs58jozC0S9HVsBN3v80jGRTfm5tRvQ6fNJhmYmuxNk\nTA+w548kYHiRLAQVGgAqDsIZ1Enx55TaKj60Dquo7d6Bt6xCb+aE4UFtEZNOfEa5\nIN+p06Nnnm2ZVTRebTx/WnnG+lTXSOuBuGAGpuOSa3yi84kFfYxBFgGcgUQt4i1M\nCzoemuHOSmcvQpU604U+J20FO2gaiYJFxz1h1v+Z/9edY9R9NCwmyFa3LfI=\n-----END RSA PRIVATE KEY-----\n)\";\n\nconst std::string kPrime256v1CertData = R\"(\n-----BEGIN CERTIFICATE-----\nMIICkDCCAjWgAwIBAgIJAOILJQbZxXtaMAoGCCqGSM49BAMCMIGjMQswCQYDVQQG\nEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVubG8gUGFyazER\nMA8GA1UECgwIUHJveHlnZW4xETAPBgNVBAsMCFByb3h5Z2VuMREwDwYDVQQDDAhQ\ncm94eWdlbjExMC8GCSqGSIb3DQEJARYiZmFjZWJvb2stcHJveHlnZW5AZ29vZ2xl\nZ3JvdXBzLmNvbTAeFw0yMDA0MDcyMDMyMDRaFw0zMDA0MDUyMDMyMDRaMIGjMQsw\nCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVubG8g\nUGFyazERMA8GA1UECgwIUHJveHlnZW4xETAPBgNVBAsMCFByb3h5Z2VuMREwDwYD\nVQQDDAhQcm94eWdlbjExMC8GCSqGSIb3DQEJARYiZmFjZWJvb2stcHJveHlnZW5A\nZ29vZ2xlZ3JvdXBzLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABB5MtBjA\nTaKYREMWTIbzK6utt7Jjb3xWcWowKeN14WFz8sDqvHcAufaN8OP2NBHRAZGi4UDs\n1thkXHtSPcc7DT+jUDBOMB0GA1UdDgQWBBSWhUXpZWkCj6YywA8iZIvl52GvzDAf\nBgNVHSMEGDAWgBSWhUXpZWkCj6YywA8iZIvl52GvzDAMBgNVHRMEBTADAQH/MAoG\nCCqGSM49BAMCA0kAMEYCIQCduzLSWUJ2RgxYvNiApmmH9Yml/s7T2bB2r6+1wlPw\nOgIhAPfLxzClQvbpPvchgQkWEJTsMgmI/CgNWX02SIzeg934\n-----END CERTIFICATE-----\n)\";\n\nconst std::string kPrime256v1KeyData = R\"(\n-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/NeWSkmQEmaO2f0T\n5ogGmfvwGId3k5i8o8hJOoV9pOuhRANCAAQeTLQYwE2imERDFkyG8yurrbeyY298\nVnFqMCnjdeFhc/LA6rx3ALn2jfDj9jQR0QGRouFA7NbYZFx7Uj3HOw0/\n-----END PRIVATE KEY-----\n)\";\n\nquic::samples::FizzServerContextPtr createFizzServerContextImpl(\n    const std::vector<std::string>& supportedAlpns,\n    fizz::server::ClientAuthMode clientAuth,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath,\n    bool insecureDefaultCert) {\n\n  std::string certData;\n  if (!certificateFilePath.empty()) {\n    folly::readFile(certificateFilePath.c_str(), certData);\n  }\n  std::string keyData;\n  if (!keyFilePath.empty()) {\n    folly::readFile(keyFilePath.c_str(), keyData);\n  }\n  if (insecureDefaultCert && certData.empty() && keyData.empty()) {\n    certData = kDefaultCertData;\n    keyData = kDefaultKeyData;\n  }\n  auto cert = fizz::openssl::CertUtils::makeSelfCert(certData, keyData);\n  auto certManager = std::make_shared<fizz::server::DefaultCertManager>();\n  certManager->addCertAndSetDefault(std::move(cert));\n\n  if (insecureDefaultCert) {\n    auto cert2 = fizz::openssl::CertUtils::makeSelfCert(kPrime256v1CertData,\n                                                        kPrime256v1KeyData);\n    certManager->addCert(std::move(cert2));\n  }\n\n  auto serverCtx = std::make_shared<fizz::server::FizzServerContext>();\n  serverCtx->setCertManager(certManager);\n  auto ticketCipher = std::make_shared<fizz::server::Aead128GCMTicketCipher<\n      fizz::server::TicketCodec<fizz::server::CertificateStorage::X509>>>(\n      serverCtx->getFactoryPtr(), std::move(certManager));\n  std::array<uint8_t, 32> ticketSeed;\n  folly::Random::secureRandom(ticketSeed.data(), ticketSeed.size());\n  ticketCipher->setTicketSecrets({{folly::range(ticketSeed)}});\n  serverCtx->setTicketCipher(ticketCipher);\n  serverCtx->setClientAuthMode(clientAuth);\n  serverCtx->setSupportedAlpns(supportedAlpns);\n  serverCtx->setAlpnMode(fizz::server::AlpnMode::Required);\n  serverCtx->setSendNewSessionTicket(false);\n  serverCtx->setEarlyDataFbOnly(false);\n  serverCtx->setVersionFallbackEnabled(false);\n\n  fizz::server::ClockSkewTolerance tolerance;\n  tolerance.before = std::chrono::minutes(-5);\n  tolerance.after = std::chrono::minutes(5);\n\n  std::shared_ptr<fizz::server::ReplayCache> replayCache =\n      std::make_shared<fizz::server::AllowAllReplayReplayCache>();\n\n  serverCtx->setEarlyDataSettings(true, tolerance, std::move(replayCache));\n\n  return serverCtx;\n}\n\n} // namespace\n\nnamespace quic::samples {\n\nFizzServerContextPtr createFizzServerContext(\n    const std::vector<std::string>& supportedAlpns,\n    fizz::server::ClientAuthMode clientAuth,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath) {\n  return createFizzServerContextImpl(\n      supportedAlpns, clientAuth, certificateFilePath, keyFilePath, false);\n}\n\nFizzServerContextPtr createFizzServerContextWithInsecureDefault(\n    const std::vector<std::string>& supportedAlpns,\n    fizz::server::ClientAuthMode clientAuth,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath) {\n  return createFizzServerContextImpl(\n      supportedAlpns, clientAuth, certificateFilePath, keyFilePath, true);\n}\n\nFizzClientContextPtr createFizzClientContext(\n    const HQBaseParams& params,\n    const std::vector<std::string>& supportedAlpns,\n    bool earlyData,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath) {\n  auto ctx = std::make_shared<fizz::client::FizzClientContext>();\n\n  std::string certData = kDefaultCertData;\n  if (!certificateFilePath.empty()) {\n    folly::readFile(certificateFilePath.c_str(), certData);\n  }\n  std::string keyData = kDefaultKeyData;\n  if (!keyFilePath.empty()) {\n    folly::readFile(keyFilePath.c_str(), keyData);\n  }\n  auto cert = fizz::openssl::CertUtils::makeSelfCert(certData, keyData);\n  auto certMgr = std::make_shared<fizz::client::CertManager>();\n  certMgr->addCert(std::move(cert));\n  ctx->setClientCertManager(std::move(certMgr));\n  ctx->setSupportedAlpns(supportedAlpns);\n  ctx->setDefaultShares(\n      {fizz::NamedGroup::x25519, fizz::NamedGroup::secp256r1});\n  ctx->setSendEarlyData(earlyData);\n  auto mgr = std::make_shared<fizz::CertDecompressionManager>();\n  mgr->setDecompressors(\n      {std::make_shared<fizz::ZstdCertificateDecompressor>(),\n       std::make_shared<fizz::ZlibCertificateDecompressor>()});\n  ctx->setCertDecompressionManager(std::move(mgr));\n  return ctx;\n}\n\nwangle::SSLContextConfig createSSLContext(\n    const HQBaseParams& params,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n  if (!certificateFilePath.empty() && !keyFilePath.empty()) {\n    sslCfg.setCertificate(certificateFilePath, keyFilePath, \"\");\n  } else {\n    sslCfg.setCertificateBuf(kDefaultCertData, kDefaultKeyData);\n  }\n  sslCfg.setNextProtocols({\"h2\"});\n  return sslCfg;\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/FizzContext.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/client/FizzClientContext.h>\n#include <fizz/server/FizzServerContext.h>\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n#include <vector>\n\n#include <wangle/ssl/SSLContextConfig.h>\n\nnamespace quic::samples {\n\nusing FizzServerContextPtr =\n    std::shared_ptr<const fizz::server::FizzServerContext>;\n\nusing FizzClientContextPtr = std::shared_ptr<fizz::client::FizzClientContext>;\n\nFizzServerContextPtr createFizzServerContext(\n    const std::vector<std::string>& supportedAlpns,\n    fizz::server::ClientAuthMode clientAuth,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath);\n\nFizzServerContextPtr createFizzServerContextWithInsecureDefault(\n    const std::vector<std::string>& supportedAlpns,\n    fizz::server::ClientAuthMode clientAuth,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath);\n\nFizzClientContextPtr createFizzClientContext(\n    const HQBaseParams& params,\n    const std::vector<std::string>& supportedAlpns,\n    bool earlyData,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath);\n\nwangle::SSLContextConfig createSSLContext(\n    const HQBaseParams& params,\n    const std::string& certificateFilePath,\n    const std::string& keyFilePath);\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/H1QDownstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/api/QuicStreamAsyncTransport.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n\nnamespace quic::samples {\n\nclass H1QDownstreamSession : public quic::QuicSocket::ConnectionCallback {\n public:\n  H1QDownstreamSession(std::shared_ptr<quic::QuicSocket> sock,\n                       proxygen::HTTPSessionController* controller,\n                       wangle::ConnectionManager* connMgr)\n      : sock_(std::move(sock)), controller_(controller), connMgr_(connMgr) {\n    sock_->setConnectionCallback(this);\n    // hold a place for this container session (HQSessionController doesn't\n    // use the arg)\n    controller_->attachSession(nullptr);\n  }\n\n  ~H1QDownstreamSession() override {\n    controller_->detachSession(nullptr);\n    if (sock_) {\n      sock_->setConnectionCallback(nullptr);\n    }\n  }\n\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override {\n    auto streamTransport =\n        quic::QuicStreamAsyncTransport::createWithExistingStream(sock_, id);\n    if (!streamTransport) {\n      LOG(ERROR) << \"Failed to create stream transport\";\n      sock_->stopSending(\n          id,\n          quic::ApplicationErrorCode(\n              proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n      sock_->resetStream(\n          id,\n          quic::ApplicationErrorCode(\n              proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n      return;\n    }\n    auto codec = std::make_unique<proxygen::HTTP1xCodec>(\n        proxygen::TransportDirection::DOWNSTREAM,\n        /*force1_1=*/false);\n    wangle::TransportInfo tinfo;\n    auto session = new proxygen::HTTPDownstreamSession(\n        proxygen::WheelTimerInstance(\n            std::chrono::seconds(5),\n            sock_->getEventBase()\n                ->getTypedEventBase<FollyQuicEventBase>()\n                ->getBackingEventBase()),\n        std::move(streamTransport),\n        sock_->getLocalAddress(),\n        sock_->getPeerAddress(),\n        controller_,\n        std::move(codec),\n        tinfo,\n        nullptr);\n    connMgr_->addConnection(session);\n    session->startNow();\n  }\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override {\n    sock_->stopSending(id,\n                       quic::ApplicationErrorCode(\n                           proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n  }\n\n  // ignore\n  void onStopSending(quic::StreamId,\n                     quic::ApplicationErrorCode) noexcept override {\n  }\n  void onConnectionEnd() noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n  void onConnectionError(quic::QuicError) noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n  void onConnectionEnd(quic::QuicError /* error */) noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n\n private:\n  std::shared_ptr<quic::QuicSocket> sock_;\n  proxygen::HTTPSessionController* controller_{nullptr};\n  wangle::ConnectionManager* connMgr_{nullptr};\n};\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/H1QUpstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/api/QuicStreamAsyncTransport.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n\nnamespace quic::samples {\n\nclass H1QUpstreamSession\n    : public quic::QuicSocket::ConnectionCallback\n    , public proxygen::HTTPSessionBase::InfoCallback {\n public:\n  explicit H1QUpstreamSession(std::shared_ptr<quic::QuicSocket> sock)\n      : sock_(std::move(sock)) {\n    sock_->setConnectionCallback(this);\n  }\n\n  ~H1QUpstreamSession() override {\n    for (auto session : sessions_) {\n      session->setInfoCallback(nullptr);\n    }\n    if (sock_) {\n      sock_->close(std::nullopt);\n      sock_->setConnectionCallback(nullptr);\n    }\n  }\n\n  proxygen::HTTPTransaction* newTransaction(\n      proxygen::HTTPTransactionHandler* handler) {\n    auto streamTransport =\n        quic::QuicStreamAsyncTransport::createWithNewStream(sock_);\n    if (!streamTransport) {\n      LOG(ERROR) << \"Failed to create stream transport\";\n      return nullptr;\n    }\n    auto codec = std::make_unique<proxygen::HTTP1xCodec>(\n        proxygen::TransportDirection::UPSTREAM,\n        /*force1_1=*/false);\n    codec->setReleaseEgressAfterRequest(true);\n    wangle::TransportInfo tinfo;\n    auto session = new proxygen::HTTPUpstreamSession(\n        proxygen::WheelTimerInstance(\n            std::chrono::seconds(5),\n            sock_->getEventBase()\n                ->getTypedEventBase<FollyQuicEventBase>()\n                ->getBackingEventBase()),\n        std::move(streamTransport),\n        sock_->getLocalAddress(),\n        sock_->getPeerAddress(),\n        std::move(codec),\n        tinfo,\n        this);\n    sessions_.insert(session);\n    session->startNow();\n    return session->newTransaction(handler);\n  }\n  void onCreate(const proxygen::HTTPSessionBase& session) override {\n  }\n\n  void onDestroy(const proxygen::HTTPSessionBase& session) override {\n    sessions_.erase(const_cast<proxygen::HTTPSessionBase*>(&session));\n    if (sessions_.empty() && draining_) {\n      delete this;\n    }\n  }\n\n  void drain() {\n    draining_ = true;\n    if (sessions_.empty()) {\n      delete this;\n    }\n  }\n\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override {\n    sock_->resetStream(id,\n                       quic::ApplicationErrorCode(\n                           proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n    sock_->stopSending(id,\n                       quic::ApplicationErrorCode(\n                           proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n  }\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override {\n    sock_->stopSending(id,\n                       quic::ApplicationErrorCode(\n                           proxygen::HTTP3::ErrorCode::HTTP_REQUEST_REJECTED));\n  }\n\n  // ignore\n  void onStopSending(quic::StreamId,\n                     quic::ApplicationErrorCode) noexcept override {\n  }\n  void onConnectionEnd() noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n  using proxygen::HTTPSessionBase::InfoCallback::onConnectionError;\n  void onConnectionError(quic::QuicError) noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n  void onConnectionEnd(quic::QuicError /* error */) noexcept override {\n    LOG(INFO) << __func__;\n    delete this;\n  }\n\n private:\n  std::shared_ptr<quic::QuicSocket> sock_;\n  std::set<proxygen::HTTPSessionBase*> sessions_;\n  bool draining_ = false;\n};\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/H2Server.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/HTTPTransactionHandlerAdaptor.h>\n\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n#include <proxygen/httpserver/samples/hq/H2Server.h>\n\nnamespace quic::samples {\n\nusing namespace proxygen;\n\nH2Server::SampleHandlerFactory::SampleHandlerFactory(\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider)\n    : httpTransactionHandlerProvider_(\n          std::move(httpTransactionHandlerProvider)) {\n}\n\nvoid H2Server::SampleHandlerFactory::onServerStart(\n    folly::EventBase* /*evb*/) noexcept {\n}\n\nvoid H2Server::SampleHandlerFactory::onServerStop() noexcept {\n}\n\nRequestHandler* H2Server::SampleHandlerFactory::onRequest(\n    RequestHandler*, HTTPMessage* msg) noexcept {\n  return new HTTPTransactionHandlerAdaptor(\n      httpTransactionHandlerProvider_(msg));\n}\n\nstd::unique_ptr<proxygen::HTTPServerOptions> H2Server::createServerOptions(\n    const HQToolServerParams& params,\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider) {\n  auto serverOptions = std::make_unique<proxygen::HTTPServerOptions>();\n\n  serverOptions->threads = params.httpServerThreads;\n  serverOptions->idleTimeout = params.httpServerIdleTimeout;\n  serverOptions->shutdownOn = params.httpServerShutdownOn;\n  serverOptions->enableContentCompression =\n      params.httpServerEnableContentCompression;\n  serverOptions->initialReceiveWindow =\n      params.transportSettings\n          .advertisedInitialBidiLocalStreamFlowControlWindow;\n  serverOptions->receiveStreamWindowSize =\n      params.transportSettings\n          .advertisedInitialBidiLocalStreamFlowControlWindow;\n  serverOptions->receiveSessionWindowSize =\n      params.transportSettings.advertisedInitialConnectionFlowControlWindow;\n  serverOptions->handlerFactories =\n      proxygen::RequestHandlerChain()\n          .addThen<SampleHandlerFactory>(\n              std::move(httpTransactionHandlerProvider))\n          .build();\n  return serverOptions;\n}\n\nstd::unique_ptr<H2Server::AcceptorConfig> H2Server::createServerAcceptorConfig(\n    const HQToolServerParams& params) {\n  auto acceptorConfig = std::make_unique<AcceptorConfig>();\n  proxygen::HTTPServer::IPConfig ipConfig(\n      params.localH2Address.value(), proxygen::HTTPServer::Protocol::HTTP2);\n  ipConfig.sslConfigs.emplace_back(\n      createSSLContext(params, params.certificateFilePath, params.keyFilePath));\n  acceptorConfig->push_back(ipConfig);\n  return acceptorConfig;\n}\n\nstd::thread H2Server::run(\n    const HQToolServerParams& params,\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider) {\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([params = folly::copy(params),\n                 httpTransactionHandlerProvider =\n                     std::move(httpTransactionHandlerProvider)]() mutable {\n    {\n      auto acceptorConfig = createServerAcceptorConfig(params);\n      auto serverOptions = createServerOptions(\n          params, std::move(httpTransactionHandlerProvider));\n      proxygen::HTTPServer server(std::move(*serverOptions));\n      server.bind(std::move(*acceptorConfig));\n      server.start();\n    }\n    // HTTPServer traps the SIGINT.  resignal HQServer\n    raise(SIGINT);\n  });\n\n  return t;\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/H2Server.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/samples/hq/HQCommandLine.h>\n#include <proxygen/httpserver/samples/hq/HQServer.h>\n\nnamespace quic::samples {\n\nclass H2Server {\n  class SampleHandlerFactory : public proxygen::RequestHandlerFactory {\n   public:\n    explicit SampleHandlerFactory(\n        HTTPTransactionHandlerProvider httpTransactionHandlerProvider);\n\n    void onServerStart(folly::EventBase* /*evb*/) noexcept override;\n\n    void onServerStop() noexcept override;\n\n    proxygen::RequestHandler* onRequest(\n        proxygen::RequestHandler* /* handler */,\n        proxygen::HTTPMessage* /* msg */) noexcept override;\n\n   private:\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider_;\n  }; // SampleHandlerFactory\n\n public:\n  static std::unique_ptr<proxygen::HTTPServerOptions> createServerOptions(\n      const HQToolServerParams& params,\n      HTTPTransactionHandlerProvider httpTransactionHandlerProvider);\n  using AcceptorConfig = std::vector<proxygen::HTTPServer::IPConfig>;\n  static std::unique_ptr<AcceptorConfig> createServerAcceptorConfig(\n      const HQToolServerParams& /* params */);\n  // Starts H2 server in a background thread\n  static std::thread run(\n      const HQToolServerParams& params,\n      HTTPTransactionHandlerProvider httpTransactionHandlerProvider);\n};\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/HQClient.h>\n\n#include <fstream>\n#include <ostream>\n#include <string>\n\n#include <folly/io/async/AsyncTimeout.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <folly/json/json.h>\n\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n#include <proxygen/httpserver/samples/hq/H1QUpstreamSession.h>\n#include <proxygen/httpserver/samples/hq/HQLoggerHelper.h>\n#include <proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h>\n#include <proxygen/lib/utils/UtilInl.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/client/QuicClientTransport.h>\n#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>\n#include <quic/congestion_control/CongestionControllerFactory.h>\n#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>\n\nnamespace quic::samples {\n\nHQClient::HQClient(const HQToolClientParams& params)\n    : params_(params), qEvb_(std::make_shared<FollyQuicEventBase>(&evb_)) {\n  if (params_.transportSettings.pacingEnabled) {\n    pacingTimer_ = std::make_shared<HighResQuicTimer>(\n        &evb_, params_.transportSettings.pacingTimerResolution);\n  }\n}\n\nint HQClient::start(const folly::SocketAddress& localAddress) {\n\n  initializeQuicClient(*params_.remoteAddress, localAddress);\n  initializeQLogger();\n\n  // TODO: turn on cert verification\n  LOG(INFO) << \"HQClient connecting to \" << params_.remoteAddress->describe();\n  quicClient_->start(this, nullptr);\n  evb_.loop();\n  return failed_ ? -1 : 0;\n}\n\nvoid HQClient::onConnectionSetupError(quic::QuicError code) noexcept {\n  LOG(ERROR) << \"Failed to establish QUIC connection: \" << code.message;\n  quicClient_->setConnectionSetupCallback(nullptr);\n  connectError(code);\n}\n\nvoid HQClient::onTransportReady() noexcept {\n  auto alpn = quicClient_->getAppProtocol();\n  if (alpn && alpn == proxygen::kHQ) {\n    h1qSession_ = new H1QUpstreamSession(quicClient_);\n    connectSuccess();\n  } else {\n    wangle::TransportInfo tinfo;\n    hqSession_ = new proxygen::HQUpstreamSession(params_.txnTimeout,\n                                                 params_.connectTimeout,\n                                                 nullptr, // controller\n                                                 tinfo,\n                                                 nullptr);\n    hqSession_->setConnectCallback(&connCb_);\n    quicClient_->setConnectionCallback(hqSession_);\n    quicClient_->setConnectionSetupCallback(hqSession_);\n    hqSession_->setSocket(quicClient_);\n    hqSession_->startNow();\n    // TODO: get rid HQUpstreamSession::ConnectCallback\n    if (replaySafe_) {\n      hqSession_->onReplaySafe();\n    }\n    hqSession_->onTransportReady(); // invokes connectSuccess()\n  }\n}\n\nproxygen::HTTPTransaction* HQClient::newTransaction(\n    proxygen::HTTPTransactionHandler* handler) {\n  proxygen::HTTPTransaction* txn = nullptr;\n  if (h1qSession_) {\n    txn = h1qSession_->newTransaction(handler);\n  } else {\n    txn = hqSession_->newTransaction(handler);\n  }\n  return txn;\n}\n\nproxygen::HTTPTransaction* FOLLY_NULLABLE\nHQClient::sendRequest(const proxygen::URL& requestUrl) {\n  std::unique_ptr<CurlService::CurlClient> client =\n      std::make_unique<CurlService::CurlClient>(&evb_,\n                                                params_.httpMethod,\n                                                requestUrl,\n                                                nullptr,\n                                                params_.httpHeaders,\n                                                params_.httpBody,\n                                                false,\n                                                params_.httpVersion.major,\n                                                params_.httpVersion.minor);\n\n  client->setLogging(params_.logResponse);\n  client->setHeadersLogging(params_.logResponseHeaders);\n  auto txn = newTransaction(client.get());\n  if (!txn) {\n    return nullptr;\n  }\n\n  if (!params_.outdir.empty()) {\n    bool canWrite = false;\n    // default output file name\n    std::string filename = \"hq.out\";\n    // try to get the name from the path\n    folly::StringPiece path = requestUrl.getPath();\n    size_t offset = proxygen::findLastOf(path, '/');\n    if (offset != std::string::npos && (offset + 1) != path.size()) {\n      filename = std::string(path.subpiece(offset + 1));\n    }\n    filename = folly::to<std::string>(params_.outdir, \"/\", filename);\n    canWrite = client->saveResponseToFile(filename);\n    if (!canWrite) {\n      LOG(ERROR) << \"Can not write output to file '\" << filename\n                 << \"' printing to stdout instead\";\n    }\n  }\n  client->sendRequest(txn);\n\n  if (onBodyFunc_) {\n    client->setOnBodyFunc(onBodyFunc_.value());\n  }\n  curls_.emplace_back(std::move(client));\n  return txn;\n}\n\nvoid HQClient::drainSession() {\n  if (h1qSession_) {\n    h1qSession_->drain();\n  } else {\n    hqSession_->drain();\n    hqSession_->closeWhenIdle();\n  }\n}\n\nvoid HQClient::sendRequests(bool closeSession, uint64_t numOpenableStreams) {\n  VLOG(10) << \"http-version:\" << params_.httpVersion;\n  do {\n    proxygen::URL requestUrl(httpPaths_.front().str(), /*secure=*/true);\n    sendRequest(requestUrl);\n    httpPaths_.pop_front();\n    numOpenableStreams--;\n  } while (!params_.sendRequestsSequentially && !httpPaths_.empty() &&\n           numOpenableStreams > 0);\n  if (closeSession && httpPaths_.empty()) {\n    drainSession();\n  }\n  // If there are still pending requests to be sent sequentially, schedule a\n  // callback on the first EOM to try to make one more request. That callback\n  // will keep scheduling itself until there are no more requests.\n  if (params_.sendRequestsSequentially && !httpPaths_.empty()) {\n    auto callSendRequestsAfterADelay = [&]() {\n      if (params_.migrateClient && httpPaths_.size() % 2 == 0) {\n        auto newSock = std::make_unique<FollyQuicAsyncUDPSocket>(qEvb_);\n        auto bindAddress = quicClient_->getPeerAddress().getIPAddress().isV4()\n                               ? \"0.0.0.0\"\n                               : \"::\";\n        auto bindRes = newSock->bind(folly::SocketAddress(bindAddress, 0));\n        CHECK(!bindRes.hasError());\n        auto startProbeRes =\n            quicClient_->startPathProbe(std::move(newSock), this);\n        CHECK(!startProbeRes.hasError()) << startProbeRes.error();\n      }\n      std::chrono::milliseconds gap = requestGaps_.front();\n      requestGaps_.pop_front();\n      if (gap.count() > 0) {\n        evb_.runAfterDelay(\n            [&]() {\n              uint64_t numOpenable =\n                  quicClient_->getNumOpenableBidirectionalStreams();\n              if (numOpenable > 0) {\n                sendRequests(true, numOpenable);\n              };\n            },\n            gap.count());\n      } else {\n        uint64_t numOpenable =\n            quicClient_->getNumOpenableBidirectionalStreams();\n        if (numOpenable > 0) {\n          sendRequests(true, numOpenable);\n        };\n      }\n    };\n    CHECK(!curls_.empty());\n    curls_.back()->setEOMFunc(callSendRequestsAfterADelay);\n  }\n}\nstatic std::function<void()> selfSchedulingRequestRunner;\n\nvoid HQClient::connectSuccess() {\n  if (params_.sendKnobFrame) {\n    sendKnobFrame(\"Hello, World from Client!\");\n  }\n  uint64_t numOpenableStreams =\n      quicClient_->getNumOpenableBidirectionalStreams();\n  CHECK_GT(numOpenableStreams, 0);\n  httpPaths_.insert(\n      httpPaths_.end(), params_.httpPaths.begin(), params_.httpPaths.end());\n  for (auto const& s : params_.requestGaps) {\n    requestGaps_.emplace_back(folly::to<uint32_t>(s));\n  }\n  if (requestGaps_.size() == 1) {\n    // #gaps must be one less than #paths, since gaps occur between downloads.\n    // Already one gap in dequeue, so copy in #paths-2 more.\n    for (int32_t i = 0; i < static_cast<int32_t>(httpPaths_.size()) - 2; ++i) {\n      requestGaps_.emplace_back(requestGaps_.front());\n    }\n  }\n  // Check that there is exactly one gap between each path download.\n  // Ignore gaps_ms flag with only one httpPath.\n  if (httpPaths_.size() > 1 && httpPaths_.size() != requestGaps_.size() + 1) {\n    throw std::runtime_error(\n        \"Number of gaps must be one (same gap between all paths) or one less \"\n        \"than number of paths.\");\n  }\n\n  sendRequests(!params_.migrateClient, numOpenableStreams);\n  // If there are still pending requests to be send in parallel, schedule a\n  // callback on the first EOM to try to make some more. That callback will\n  // keep scheduling itself until there are no more requests.\n  if (!params_.sendRequestsSequentially && !httpPaths_.empty()) {\n    selfSchedulingRequestRunner = [&]() {\n      uint64_t numOpenable = quicClient_->getNumOpenableBidirectionalStreams();\n      if (numOpenable > 0) {\n        sendRequests(true, numOpenable);\n      };\n      if (!httpPaths_.empty()) {\n        auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(\n            quicClient_->getTransportInfo().srtt);\n        evb_.timer().scheduleTimeoutFn(\n            selfSchedulingRequestRunner,\n            std::max(rtt, std::chrono::milliseconds(1)));\n      }\n    };\n    CHECK(!curls_.empty());\n    curls_.back()->setEOMFunc(selfSchedulingRequestRunner);\n  }\n}\n\nvoid HQClient::sendKnobFrame(const folly::StringPiece str) {\n  if (str.empty()) {\n    return;\n  }\n  uint64_t knobSpace = 0xfaceb00c;\n  uint64_t knobId = 100;\n  BufPtr buf(folly::IOBuf::create(str.size()));\n  memcpy(buf->writableData(), str.data(), str.size());\n  buf->append(str.size());\n  VLOG(10) << \"Sending Knob Frame to peer. KnobSpace: \" << std::hex << knobSpace\n           << \" KnobId: \" << std::dec << knobId << \" Knob Blob\" << str;\n  const auto knobSent = quicClient_->setKnob(0xfaceb00c, 100, std::move(buf));\n  if (knobSent.hasError()) {\n    LOG(ERROR) << \"Failed to send Knob frame to peer. Received error: \"\n               << knobSent.error();\n  }\n}\n\nvoid HQClient::onReplaySafe() noexcept {\n  VLOG(4) << \"Transport replay safe\";\n  replaySafe_ = true;\n}\n\nvoid HQClient::connectError(const quic::QuicError& error) {\n  LOG(ERROR) << \"HQClient failed to connect, error=\" << toString(error.code)\n             << \", msg=\" << error.message;\n  failed_ = true;\n  evb_.terminateLoopSoon();\n}\n\nvoid HQClient::initializeQuicClient(const folly::SocketAddress& remoteAddress,\n                                    const folly::SocketAddress& localAddress) {\n  auto sock = std::make_unique<FollyQuicAsyncUDPSocket>(qEvb_);\n  auto handshakeContextBuilder =\n      quic::FizzClientQuicHandshakeContext::Builder()\n          .setFizzClientContext(\n              createFizzClientContext(params_,\n                                      params_.supportedAlpns,\n                                      params_.earlyData,\n                                      params_.certificateFilePath,\n                                      params_.keyFilePath))\n          .setPskCache(params_.pskCache);\n\n  if (!params_.verifyServerCert) {\n    handshakeContextBuilder =\n        std::move(handshakeContextBuilder)\n            .setCertificateVerifier(\n                std::make_unique<\n                    proxygen::InsecureVerifierDangerousDoNotUseInProduction>());\n  }\n\n  auto client = std::make_shared<quic::QuicClientTransport>(\n      qEvb_,\n      std::move(sock),\n      std::move(handshakeContextBuilder).build(),\n      params_.clientCidLength);\n  client->setPacingTimer(std::move(pacingTimer_));\n  client->setHostname(params_.host);\n  client->addNewPeerAddress(remoteAddress);\n  client->setLocalAddress(localAddress);\n  if (params_.congestionControllerFactory) {\n    client->setCongestionControllerFactory(params_.congestionControllerFactory);\n  } else {\n    client->setCongestionControllerFactory(\n        std::make_shared<quic::DefaultCongestionControllerFactory>());\n  }\n  client->setTransportSettings(params_.transportSettings);\n  client->setSupportedVersions(params_.quicVersions);\n\n  quicClient_ = std::move(client);\n}\n\nvoid HQClient::initializeQLogger() {\n  if (!quicClient_) {\n    return;\n  }\n  // Not used immediately, but if not set\n  // the qlogger wont be able to report. Checking early\n  if (params_.qLoggerPath.empty()) {\n    return;\n  }\n\n  auto qLogger = std::make_shared<HQLoggerHelper>(\n      params_.qLoggerPath, params_.prettyJson, quic::VantagePoint::Client);\n  quicClient_->setQLogger(std::move(qLogger));\n}\n\nvoid HQClient::onPathValidationResult(const PathInfo& pathInfo) {\n  if (pathInfo.status == PathStatus::Validated) {\n    LOG(INFO) << fmt::format(\n        \"Path probe successful. Migrating connection to: local address = {}, \"\n        \"peer address = {}\",\n        pathInfo.localAddress.describe(),\n        pathInfo.peerAddress.describe());\n    auto migrationRes = quicClient_->migrateConnection(pathInfo.id);\n    if (migrationRes.hasError()) {\n      LOG(ERROR) << \"Failed to migrate connection: \" << migrationRes.error();\n    }\n  } else {\n    LOG(ERROR) << \"Path probe timed out. Deleting the path.\";\n    auto removeRes = quicClient_->removePath(pathInfo.id);\n    if (removeRes.hasError()) {\n      LOG(ERROR) << \"Failed to remove path: \" << removeRes.error();\n      failed_ = true;\n      evb_.terminateLoopSoon();\n    }\n  }\n}\n\nint startClient(const HQToolClientParams& params) {\n  HQClient client(params);\n\n  folly::SocketAddress localAddr;\n  if (params.localAddress.has_value()) {\n    localAddr = *params.localAddress;\n  } else {\n    if (params.remoteAddress.has_value() &&\n        params.remoteAddress->getFamily() == AF_INET) {\n      localAddr = folly::SocketAddress(\"0.0.0.0\", 0);\n    } else {\n      localAddr = folly::SocketAddress(\"::\", 0);\n    }\n  }\n\n  return client.start(localAddr);\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQClient.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBuf.h>\n#include <list>\n#include <memory>\n#include <proxygen/httpclient/samples/curl/CurlClient.h>\n#include <proxygen/httpserver/samples/hq/H1QUpstreamSession.h>\n#include <proxygen/httpserver/samples/hq/HQCommandLine.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n#include <quic/common/events/HighResQuicTimer.h>\n\nnamespace quic {\n\nclass QuicClientTransport;\nclass FileQLogger;\n\nnamespace samples {\n\nclass HQClient\n    : private quic::QuicSocket::ConnectionSetupCallback\n    , private quic::QuicPathManager::PathValidationCallback {\n public:\n  explicit HQClient(const HQToolClientParams& params);\n\n  ~HQClient() override = default;\n\n  int start(const folly::SocketAddress& localAddress);\n\n  void setOnBodyFunc(\n      const std::function<void(const proxygen::HTTPMessage& request,\n                               const folly::IOBuf* chainBuf)>& onBodyFunc) {\n    onBodyFunc_ = onBodyFunc;\n  }\n\n private:\n  // Conn setup callback\n  void onConnectionSetupError(quic::QuicError code) noexcept override;\n  void onTransportReady() noexcept override;\n  void onReplaySafe() noexcept override;\n\n  // Path validation callback\n  void onPathValidationResult(const PathInfo& pathInfo) override;\n\n  proxygen::HTTPTransaction* newTransaction(\n      proxygen::HTTPTransactionHandler* handler);\n\n  void drainSession();\n\n  proxygen::HTTPTransaction* sendRequest(const proxygen::URL& requestUrl);\n\n  void sendRequests(bool closeSession, uint64_t numOpenableStreams);\n\n  void sendKnobFrame(const folly::StringPiece str);\n\n  class ConnectCallback : public proxygen::HQSession::ConnectCallback {\n   public:\n    explicit ConnectCallback(HQClient& client) : client_(client) {\n    }\n    void connectSuccess() override {\n      client_.connectSuccess();\n    }\n\n    void onReplaySafe() override {\n      VLOG(4) << \"Connect Callback Replay Safe\";\n      client_.onReplaySafe();\n    }\n\n    void connectError(quic::QuicError error) override {\n      client_.connectError(error);\n    }\n\n   private:\n    HQClient& client_;\n  };\n\n  void connectSuccess();\n\n  void connectError(const quic::QuicError& error);\n\n  void initializeQuicClient(const folly::SocketAddress& remoteAddress,\n                            const folly::SocketAddress& localAddress);\n\n  void initializeQLogger();\n\n  ConnectCallback connCb_{*this};\n\n  const HQToolClientParams& params_;\n\n  std::shared_ptr<quic::QuicClientTransport> quicClient_;\n\n  QuicTimer::SharedPtr pacingTimer_;\n\n  std::shared_ptr<FollyQuicEventBase> qEvb_;\n  folly::EventBase evb_;\n\n  // H3\n  proxygen::HQUpstreamSession* hqSession_{nullptr};\n  // Interop\n  H1QUpstreamSession* h1qSession_{nullptr};\n\n  std::list<std::unique_ptr<CurlService::CurlClient>> curls_;\n\n  std::deque<folly::StringPiece> httpPaths_;\n\n  std::deque<std::chrono::milliseconds> requestGaps_;\n\n  folly::Optional<std::function<void(const proxygen::HTTPMessage& request,\n                                     const folly::IOBuf* chainBuf)>>\n      onBodyFunc_;\n\n  bool failed_{false};\n\n  bool replaySafe_{false};\n};\n\nint startClient(const HQToolClientParams& params);\n} // namespace samples\n} // namespace quic\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQCommandLine.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/HQCommandLine.h>\n\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpclient/samples/curl/CurlClient.h>\n#include <proxygen/lib/http/SynchronizedLruQuicPskCache.h>\n#include <proxygen/lib/transport/PersistentQuicPskCache.h>\n#include <quic/QuicConstants.h>\n\nDEFINE_string(host, \"::1\", \"HQ server hostname/IP\");\nDEFINE_int32(port, 6666, \"HQ server port\");\nDEFINE_int32(threads, 0, \"QUIC Server threads, 0 = nCPUs\");\nDEFINE_int32(h2port, 6667, \"HTTP/2 server port\");\nDEFINE_string(\n    local_address,\n    \"\",\n    \"Local Address to bind to. Client only. Format should be ip:port\");\nDEFINE_string(mode, \"server\", \"Mode to run in: 'client' or 'server'\");\nDEFINE_string(body, \"\", \"Filename to read from for POST requests\");\nDEFINE_string(path,\n              \"/\",\n              \"(HQClient) url-path to send the request to, \"\n              \"or a comma separated list of paths to fetch in parallel\");\nDEFINE_int32(num_requests,\n             1,\n             \"How many requests to issue to the URL(s) specified in <path>\");\nDEFINE_string(connect_to_address,\n              \"\",\n              \"(HQClient) Override IP address to connect to instead of \"\n              \"resolving the host field\");\nDEFINE_int32(connect_timeout, 2000, \"(HQClient) connect timeout in ms\");\nDEFINE_string(httpversion, \"1.1\", \"HTTP version string\");\nDEFINE_string(protocol, \"\", \"HQ protocol version e.g. h3-29 or hq-fb-05\");\nDEFINE_int64(quic_version, 0, \"QUIC version to use. 0 is default\");\nDEFINE_bool(use_version, true, \"Use set QUIC version as first version\");\nDEFINE_string(logdir, \"/tmp/logs\", \"Directory to store connection logs\");\nDEFINE_string(outdir, \"\", \"Directory to store responses\");\nDEFINE_bool(log_response,\n            true,\n            \"Whether to log the response content to stderr\");\nDEFINE_bool(log_response_headers,\n            false,\n            \"Whether to log the response headers to stderr\");\nDEFINE_bool(\n    log_run_time,\n    false,\n    \"Whether to log the duration for which the client/server was running\");\nDEFINE_bool(sequential,\n            false,\n            \"Whether to make requests sequentially or in parallel when \"\n            \"multiple paths are provided\");\nDEFINE_string(gap_ms,\n              \"0\",\n              \"Comma separated list of gaps in ms between requests\");\nDEFINE_string(congestion,\n              \"cubic\",\n              \"newreno/cubic/bbr/bbr2/bbr2modular/copa/none\");\nDEFINE_int32(conn_flow_control, 1024 * 1024 * 10, \"Connection flow control\");\nDEFINE_int32(stream_flow_control, 256 * 1024, \"Stream flow control\");\nDEFINE_int32(max_receive_packet_size,\n             quic::kDefaultUDPReadBufferSize,\n             \"Max UDP packet size Quic can receive\");\nDEFINE_int64(rate_limit, -1, \"Connection rate limit per second per thread\");\n\nDEFINE_uint32(num_gro_buffers,\n              quic::kDefaultNumGROBuffers,\n              \"Number of GRO buffers\");\n\nDEFINE_int32(txn_timeout, 120000, \"HTTP Transaction Timeout\");\nDEFINE_string(headers, \"\", \"List of N=V headers separated by ,\");\nDEFINE_bool(pacing, false, \"Whether to enable pacing on HQServer\");\nDEFINE_int32(pacing_timer_tick_interval_us, 200, \"Pacing timer resolution\");\nDEFINE_string(psk_file, \"\", \"Cache file to use for QUIC psks\");\nDEFINE_bool(early_data, false, \"Whether to use 0-rtt\");\nDEFINE_bool(verify_server_cert,\n            false,\n            \"Whether HQClient should verify server certificates\");\nDEFINE_uint32(quic_batching_mode,\n              static_cast<uint32_t>(quic::QuicBatchingMode::BATCHING_MODE_NONE),\n              \"QUIC batching mode\");\nDEFINE_uint32(quic_batch_size,\n              quic::kDefaultQuicMaxBatchSize,\n              \"Maximum number of packets that can be batched in Quic\");\nDEFINE_string(cert, \"\", \"Certificate file path\");\nDEFINE_string(key, \"\", \"Private key file path\");\nDEFINE_bool(use_insecure_default_cert,\n            true,\n            \"Use default certificate if cert/key is not provided\");\nDEFINE_string(client_auth_mode, \"none\", \"Client authentication mode\");\nDEFINE_string(qlogger_path,\n              \"\",\n              \"Path to the directory where qlog files\"\n              \"will be written. File is called <CID>.qlog\");\nDEFINE_bool(pretty_json, true, \"Whether to use pretty json for QLogger output\");\nDEFINE_bool(connect_udp, false, \"Whether or not to use connected udp sockets\");\nDEFINE_uint32(max_cwnd_mss,\n              quic::kLargeMaxCwndInMss,\n              \"Max cwnd in unit of mss\");\nDEFINE_bool(migrate_client,\n            false,\n            \"(HQClient) Should the HQClient make two sets of requests and \"\n            \"switch sockets in the middle.\");\nDEFINE_bool(use_inplace_write,\n            false,\n            \"Transport use inplace packet build and socket writing\");\n\nDEFINE_bool(send_knob_frame,\n            false,\n            \"Send a Knob Frame to the peer when a QUIC connection is \"\n            \"established successfully\");\n\nDEFINE_string(transport_knobs,\n              \"\",\n              \"If send_knob_frame is set, this is the default transport knobs\"\n              \" sent to peer\");\nDEFINE_bool(use_ack_receive_timestamps,\n            false,\n            \"Replace the ACK frame with ACK_RECEIVE_TIMESTAMPS frame\"\n            \"which carries the received packet timestamps\");\nDEFINE_uint32(\n    max_ack_receive_timestamps_to_send,\n    quic::kMaxReceivedPktsTimestampsStored,\n    \"Controls how many packet receieve timestamps the peer should send\");\nDEFINE_uint32(advertise_extended_ack_features,\n              0,\n              \"Advertise ACK_EXTENDED frame support to the peer. The following\"\n              \"bitwise values can be ORed together:\"\n              \"bit 1 - ECN support\"\n              \"bit 2 - Receive timestamps support\"\n              \"Example: 3 means both ECN and receive timestamps are supported\");\nDEFINE_uint32(enable_extended_ack_features,\n              0,\n              \"Replace the ACK frame with ACK_EXTENDED when supported by the \"\n              \"peer. The following bitwise values can be ORed together:\"\n              \"bit 1 - ECN support\"\n              \"bit 2 - Receive timestamps support\"\n              \"Example: 3 means both ECN and receive timestamps are supported\");\nDEFINE_bool(initiate_key_updates,\n            false,\n            \"Whether to initiate periodic key updates\");\nDEFINE_uint32(key_update_interval,\n              quic::kDefaultKeyUpdatePacketCountInterval,\n              \"Number of packets to be sent before initiating a key update (if \"\n              \"initiate_key_updates is true)\");\nDEFINE_bool(use_l4s_ecn, false, \"Whether to use L4S for ECN marking\");\nDEFINE_bool(enable_scone,\n            false,\n            \"Enable SCONE (RFC draft-ietf-scone-protocol)\");\nDEFINE_bool(read_ecn,\n            false,\n            \"Whether to read and echo ecn marking from ingress packets\");\nDEFINE_uint32(dscp, 0, \"DSCP value to use for outgoing packets\");\nDEFINE_uint32(client_cid_length, 0, \"Client Connection ID length in bytes\");\n\nnamespace quic::samples {\n\nstd::ostream& operator<<(std::ostream& o, const HQMode& m) {\n  o << \"mode=\";\n  switch (m) {\n    case HQMode::CLIENT:\n      o << \"client\";\n      break;\n    case HQMode::SERVER:\n      o << \"server\";\n      break;\n    default:\n      o << \"unknown (val=\" << static_cast<uint32_t>(m) << \")\";\n  }\n  return o;\n}\n\nnamespace {\n\n/*\n * Initiazliation and validation functions.\n *\n * The pattern is to collect flags into the HQToolParamsBuilderFromCmdline\n * object and then to validate it. Rationale of validating the options AFTER\n * all the options have been collected: some combinations of transport, http\n * and partial reliability options are invalid. It is simpler to collect the\n * options first and to validate the combinations later.\n *\n */\nvoid initializeCommonSettings(HQToolParams& hqParams) {\n  // General section\n  if (FLAGS_mode == \"server\") {\n    CHECK(FLAGS_local_address.empty())\n        << \"local_address only allowed in client mode\";\n    hqParams.setMode(HQMode::SERVER);\n    hqParams.logprefix = \"server\";\n    auto& serverParams = std::get<HQToolServerParams>(hqParams.params);\n    serverParams.serverThreads = FLAGS_threads;\n  } else if (FLAGS_mode == \"client\") {\n    hqParams.setMode(HQMode::CLIENT);\n    hqParams.logprefix = \"client\";\n    auto& clientParams = std::get<HQToolClientParams>(hqParams.params);\n    clientParams.host = FLAGS_host;\n    if (FLAGS_connect_to_address.empty()) {\n      clientParams.remoteAddress =\n          folly::SocketAddress(clientParams.host, FLAGS_port, true);\n    } else {\n      clientParams.remoteAddress =\n          folly::SocketAddress(FLAGS_connect_to_address, FLAGS_port, false);\n    }\n    if (!FLAGS_local_address.empty()) {\n      clientParams.localAddress = folly::SocketAddress();\n      clientParams.localAddress->setFromLocalIpPort(FLAGS_local_address);\n    }\n    clientParams.outdir = FLAGS_outdir;\n  }\n}\n\nvoid initializeTransportSettings(HQToolParams& hqUberParams) {\n  HQBaseParams& hqParams = hqUberParams.baseParams();\n  if (FLAGS_quic_version != 0) {\n    auto quicVersion = static_cast<quic::QuicVersion>(FLAGS_quic_version);\n    bool useVersionFirst = FLAGS_use_version;\n    if (useVersionFirst) {\n      hqParams.quicVersions.insert(hqParams.quicVersions.begin(), quicVersion);\n    } else {\n      hqParams.quicVersions.push_back(quicVersion);\n    }\n  }\n\n  if (!FLAGS_protocol.empty()) {\n    hqParams.protocol = FLAGS_protocol;\n    if (hqUberParams.mode == HQMode::CLIENT) {\n      std::get<HQToolClientParams>(hqUberParams.params).supportedAlpns = {\n          hqParams.protocol};\n    } else if (hqUberParams.mode == HQMode::SERVER) {\n      std::get<HQToolServerParams>(hqUberParams.params).supportedAlpns = {\n          hqParams.protocol};\n    }\n  }\n\n  hqParams.transportSettings.advertisedInitialConnectionFlowControlWindow =\n      FLAGS_conn_flow_control;\n  hqParams.transportSettings.advertisedInitialBidiLocalStreamFlowControlWindow =\n      FLAGS_stream_flow_control;\n  hqParams.transportSettings\n      .advertisedInitialBidiRemoteStreamFlowControlWindow =\n      FLAGS_stream_flow_control;\n  hqParams.transportSettings.advertisedInitialUniStreamFlowControlWindow =\n      FLAGS_stream_flow_control;\n  hqParams.congestionControlName = FLAGS_congestion;\n  hqParams.congestionControl =\n      quic::congestionControlStrToType(FLAGS_congestion);\n  if (hqParams.congestionControl) {\n    hqParams.transportSettings.defaultCongestionController =\n        hqParams.congestionControl.value();\n  }\n  hqParams.transportSettings.maxRecvPacketSize = FLAGS_max_receive_packet_size;\n  hqParams.transportSettings.numGROBuffers_ = FLAGS_num_gro_buffers;\n  hqParams.transportSettings.pacingEnabled = FLAGS_pacing;\n  if (hqParams.transportSettings.pacingEnabled) {\n    hqParams.transportSettings.pacingTickInterval =\n        std::chrono::microseconds(FLAGS_pacing_timer_tick_interval_us);\n  }\n  hqParams.transportSettings.batchingMode =\n      quic::getQuicBatchingMode(FLAGS_quic_batching_mode);\n  hqParams.transportSettings.maxBatchSize = FLAGS_quic_batch_size;\n  if (hqUberParams.mode == HQMode::CLIENT) {\n    // There is no good reason to keep the socket around for a drain period\n    // for a commandline client\n    hqParams.transportSettings.shouldDrain = false;\n    hqParams.transportSettings.attemptEarlyData = FLAGS_early_data;\n  }\n  hqParams.transportSettings.connectUDP = FLAGS_connect_udp;\n  hqParams.transportSettings.maxCwndInMss = FLAGS_max_cwnd_mss;\n  if (hqUberParams.mode == HQMode::SERVER && FLAGS_use_inplace_write) {\n    hqParams.transportSettings.dataPathType =\n        quic::DataPathType::ContinuousMemory;\n  }\n  if (FLAGS_rate_limit >= 0) {\n    CHECK(hqUberParams.mode == HQMode::SERVER);\n    std::get<HQToolServerParams>(hqUberParams.params).rateLimitPerThread =\n        FLAGS_rate_limit;\n\n    std::array<uint8_t, kRetryTokenSecretLength> secret;\n    folly::Random::secureRandom(secret.data(), secret.size());\n    hqParams.transportSettings.retryTokenSecret = secret;\n  }\n  if (hqUberParams.mode == HQMode::CLIENT) {\n    std::get<HQToolClientParams>(hqUberParams.params).connectTimeout =\n        std::chrono::milliseconds(FLAGS_connect_timeout);\n  }\n  hqParams.sendKnobFrame = FLAGS_send_knob_frame;\n  if (hqParams.sendKnobFrame) {\n    hqParams.transportSettings.knobs.push_back({kDefaultQuicTransportKnobSpace,\n                                                kDefaultQuicTransportKnobId,\n                                                FLAGS_transport_knobs});\n  }\n  hqParams.transportSettings.maxRecvBatchSize = 32;\n  hqParams.transportSettings.shouldUseRecvmmsgForBatchRecv = true;\n  hqParams.transportSettings.advertisedInitialMaxStreamsBidi = 100;\n  hqParams.transportSettings.advertisedInitialMaxStreamsUni = 100;\n\n  if (FLAGS_use_ack_receive_timestamps) {\n    hqParams.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer = {\n        .maxReceiveTimestampsPerAck = FLAGS_max_ack_receive_timestamps_to_send,\n        .receiveTimestampsExponent = kDefaultReceiveTimestampsExponent};\n  }\n  hqParams.transportSettings.datagramConfig.enabled = true;\n\n  hqParams.transportSettings.initiateKeyUpdate = FLAGS_initiate_key_updates;\n  hqParams.transportSettings.keyUpdatePacketCountInterval =\n      FLAGS_key_update_interval;\n\n  if (FLAGS_use_l4s_ecn) {\n    hqParams.transportSettings.enableEcnOnEgress = true;\n    hqParams.transportSettings.useL4sEcn = true;\n    hqParams.transportSettings.minBurstPackets = 1;\n    hqParams.transportSettings.ccaConfig.onlyGrowCwndWhenLimited = true;\n    hqParams.transportSettings.ccaConfig.leaveHeadroomForCwndLimited = true;\n  }\n\n  hqParams.transportSettings.readEcnOnIngress = FLAGS_read_ecn;\n\n  hqParams.transportSettings.dscpValue = FLAGS_dscp;\n  hqParams.transportSettings.disableMigration = false;\n  if (hqUberParams.mode == HQMode::CLIENT) {\n    std::get<HQToolClientParams>(hqUberParams.params).clientCidLength =\n        FLAGS_client_cid_length;\n  }\n\n  hqParams.transportSettings.advertisedExtendedAckFeatures =\n      FLAGS_advertise_extended_ack_features;\n  hqParams.transportSettings.enableExtendedAckFeatures =\n      FLAGS_enable_extended_ack_features;\n\n  hqParams.transportSettings.enableScone = FLAGS_enable_scone;\n} // initializeTransportSettings\n\nvoid initializeHttpServerSettings(HQToolServerParams& hqParams) {\n  // HTTP section\n  // NOTE: handler factories are assigned by H2Server class\n  // before starting.\n  hqParams.host = FLAGS_host;\n  hqParams.port = FLAGS_port;\n  hqParams.h2port = FLAGS_h2port;\n  hqParams.localH2Address =\n      folly::SocketAddress(hqParams.host, hqParams.h2port, true);\n  hqParams.httpServerThreads = FLAGS_threads;\n  hqParams.httpServerIdleTimeout = std::chrono::milliseconds(60000);\n  hqParams.httpServerShutdownOn = {SIGINT, SIGTERM};\n  hqParams.httpServerEnableContentCompression = false;\n  hqParams.httpVersion.parse(FLAGS_httpversion);\n  hqParams.txnTimeout = std::chrono::milliseconds(FLAGS_txn_timeout);\n} // initializeHttpServerSettings\n\nvoid initializeHttpClientSettings(HQToolClientParams& hqParams) {\n  folly::split(',', FLAGS_path, hqParams.httpPaths);\n\n  if (FLAGS_num_requests > 1) {\n    std::vector<std::string> multipliedPaths;\n    multipliedPaths.reserve(hqParams.httpPaths.size() * FLAGS_num_requests);\n\n    for (int i = 0; i < FLAGS_num_requests; i++) {\n      std::copy(hqParams.httpPaths.begin(),\n                hqParams.httpPaths.end(),\n                std::back_inserter(multipliedPaths));\n    }\n    hqParams.httpPaths.swap(multipliedPaths);\n  }\n\n  hqParams.httpBody = FLAGS_body;\n  hqParams.httpMethod = hqParams.httpBody.empty() ? proxygen::HTTPMethod::GET\n                                                  : proxygen::HTTPMethod::POST;\n\n  // parse HTTP headers\n  auto httpHeadersString = FLAGS_headers;\n  hqParams.httpHeaders =\n      CurlService::CurlClient::parseHeaders(httpHeadersString);\n\n  // Set the host header\n  if (!hqParams.httpHeaders.exists(proxygen::HTTP_HEADER_HOST)) {\n    hqParams.httpHeaders.set(proxygen::HTTP_HEADER_HOST, hqParams.host);\n  }\n\n  hqParams.logResponse = FLAGS_log_response;\n  hqParams.logResponseHeaders = FLAGS_log_response_headers;\n  hqParams.sendRequestsSequentially = FLAGS_sequential;\n  folly::split(',', FLAGS_gap_ms, hqParams.requestGaps);\n\n  hqParams.earlyData = FLAGS_early_data;\n  hqParams.verifyServerCert = FLAGS_verify_server_cert;\n  hqParams.migrateClient = FLAGS_migrate_client;\n  hqParams.txnTimeout = std::chrono::milliseconds(FLAGS_txn_timeout);\n  hqParams.httpVersion.parse(FLAGS_httpversion);\n} // initializeHttpClientSettings\n\nvoid initializeQLogSettings(HQBaseParams& hqParams) {\n  hqParams.qLoggerPath = FLAGS_qlogger_path;\n  hqParams.prettyJson = FLAGS_pretty_json;\n} // initializeQLogSettings\n\nvoid initializeFizzSettings(HQToolParams& toolParams) {\n  if (toolParams.mode == HQMode::CLIENT) {\n    auto& clientParams = std::get<HQToolClientParams>(toolParams.params);\n    clientParams.pskFilePath = FLAGS_psk_file;\n    if (!FLAGS_psk_file.empty()) {\n      clientParams.pskCache =\n          std::make_shared<proxygen::PersistentQuicPskCache>(\n              FLAGS_psk_file,\n              wangle::PersistentCacheConfig::Builder()\n                  .setCapacity(1000)\n                  .setSyncInterval(std::chrono::seconds(1))\n                  .build());\n    } else {\n      clientParams.pskCache =\n          std::make_shared<proxygen::SynchronizedLruQuicPskCache>(1000);\n    }\n  }\n\n  // No longer set client auth on params; handled during server startup.\n\n} // initializeFizzSettings\n\nHQInvalidParams validate(const HQToolParams& params) {\n  HQInvalidParams invalidParams;\n#define INVALID_PARAM(param, error)                                           \\\n  do {                                                                        \\\n    HQInvalidParam invalid = {.name = #param,                                 \\\n                              .value = folly::to<std::string>(FLAGS_##param), \\\n                              .errorMsg = (error)};                           \\\n    invalidParams.push_back(invalid);                                         \\\n  } while (false);\n\n  // Validate the common settings\n  if (!(params.mode == HQMode::CLIENT || params.mode == HQMode::SERVER)) {\n    INVALID_PARAM(mode, \"only client/server are supported\");\n  }\n\n  // In the client mode, host/port are required\n  if (params.mode == HQMode::CLIENT) {\n    auto& clientParams = std::get<HQToolClientParams>(params.params);\n    if (clientParams.host.empty()) {\n      INVALID_PARAM(host, \"HQClient expected --host\");\n    }\n    if (FLAGS_port == 0) {\n      INVALID_PARAM(port, \"HQClient expected --port\");\n    }\n  }\n\n  // Validate the transport section\n  if (folly::to<uint16_t>(FLAGS_max_receive_packet_size) <\n      quic::kDefaultUDPSendPacketLen) {\n    INVALID_PARAM(\n        max_receive_packet_size,\n        folly::to<std::string>(\"max_receive_packet_size needs to be at least \",\n                               quic::kDefaultUDPSendPacketLen));\n  }\n\n  auto& baseParams = params.baseParams();\n  if (!baseParams.congestionControlName.empty()) {\n    if (!baseParams.congestionControl) {\n      INVALID_PARAM(congestion, \"unrecognized congestion control\");\n    }\n  }\n  // Validate the HTTP section\n  if (params.mode == HQMode::SERVER) {\n  }\n\n  return invalidParams;\n#undef INVALID_PARAM\n}\n} // namespace\n\nHQToolParamsBuilderFromCmdline::HQToolParamsBuilderFromCmdline(\n    initializer_list initial) {\n  // Save the values of the flags, so that changing\n  // flags values is safe\n  gflags::FlagSaver saver;\n\n  for (auto& kv : initial) {\n    LOG(INFO) << \"Overriding HQToolParams \" << kv.first << \" to \" << kv.second;\n    gflags::SetCommandLineOptionWithMode(\n        kv.first.c_str(),\n        kv.second.c_str(),\n        gflags::FlagSettingMode::SET_FLAGS_VALUE);\n  }\n\n  hqParams_.logdir = FLAGS_logdir;\n  hqParams_.logRuntime = FLAGS_log_run_time;\n\n  initializeCommonSettings(hqParams_);\n\n  initializeTransportSettings(hqParams_);\n\n  if (hqParams_.mode == HQMode::CLIENT) {\n    initializeHttpClientSettings(\n        std::get<HQToolClientParams>(hqParams_.params));\n    // Populate client TLS file paths from flags\n    auto& clientParams = std::get<HQToolClientParams>(hqParams_.params);\n    clientParams.certificateFilePath = FLAGS_cert;\n    clientParams.keyFilePath = FLAGS_key;\n  } else {\n    initializeHttpServerSettings(\n        std::get<HQToolServerParams>(hqParams_.params));\n    // Populate server TLS file paths from flags\n    auto& serverParams = std::get<HQToolServerParams>(hqParams_.params);\n    serverParams.certificateFilePath = FLAGS_cert;\n    serverParams.keyFilePath = FLAGS_key;\n    serverParams.useInsecureDefaultCertificate =\n        FLAGS_use_insecure_default_cert;\n    // Set client auth mode on server params for tooling visibility\n    if (FLAGS_client_auth_mode == std::string(\"optional\")) {\n      serverParams.clientAuth = fizz::server::ClientAuthMode::Optional;\n    } else if (FLAGS_client_auth_mode == std::string(\"required\")) {\n      serverParams.clientAuth = fizz::server::ClientAuthMode::Required;\n    } else {\n      serverParams.clientAuth = fizz::server::ClientAuthMode::None;\n    }\n  }\n\n  initializeQLogSettings(hqParams_.baseParams());\n\n  initializeFizzSettings(hqParams_);\n\n  for (auto& err : validate(hqParams_)) {\n    invalidParams_.push_back(err);\n  }\n}\n\nbool HQToolParamsBuilderFromCmdline::valid() const noexcept {\n  return invalidParams_.empty();\n}\n\nconst HQInvalidParams& HQToolParamsBuilderFromCmdline::invalidParams()\n    const noexcept {\n  return invalidParams_;\n}\n\nHQToolParams HQToolParamsBuilderFromCmdline::build() noexcept {\n  return hqParams_;\n}\n\nconst folly::Expected<HQToolParams, HQInvalidParams>\ninitializeParamsFromCmdline(\n    HQToolParamsBuilderFromCmdline::initializer_list defaultValues) {\n  auto builder =\n      std::make_shared<HQToolParamsBuilderFromCmdline>(defaultValues);\n\n  // Wrap up and return\n  if (builder->valid()) {\n    return builder->build();\n  } else {\n    auto errors = builder->invalidParams();\n    return folly::makeUnexpected(errors);\n  }\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQCommandLine.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/HTTPServerOptions.h>\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n#include <variant>\n\nnamespace quic::samples {\n\nenum class HQMode { INVALID, CLIENT, SERVER };\n\nstd::ostream& operator<<(std::ostream& o, const HQMode& m);\n\nstruct HQToolClientParams : public HQClientParams {\n  std::vector<std::string> supportedAlpns{\n      quic::samples::kDefaultSupportedAlpns};\n\n  std::string outdir;\n  bool logResponse;\n  bool logResponseHeaders;\n\n  folly::Optional<folly::SocketAddress> remoteAddress;\n  bool earlyData;\n  bool verifyServerCert;\n  std::chrono::milliseconds connectTimeout;\n  proxygen::HTTPHeaders httpHeaders;\n  std::string httpBody;\n  proxygen::HTTPMethod httpMethod;\n  std::vector<std::string> httpPaths;\n  bool migrateClient{false};\n  bool sendRequestsSequentially;\n  std::vector<std::string> requestGaps;\n  std::string certificateFilePath;\n  std::string keyFilePath;\n};\n\nstruct HQToolServerParams : public HQServerParams {\n  std::vector<std::string> supportedAlpns{\n      quic::samples::kDefaultSupportedAlpns};\n\n  std::string host;\n  uint16_t port;\n  uint16_t h2port;\n  folly::Optional<folly::SocketAddress> localH2Address;\n  size_t httpServerThreads;\n  std::chrono::milliseconds httpServerIdleTimeout;\n  std::vector<int> httpServerShutdownOn;\n  bool httpServerEnableContentCompression;\n  std::string certificateFilePath;\n  std::string keyFilePath;\n  bool useInsecureDefaultCertificate{false};\n  fizz::server::ClientAuthMode clientAuth{fizz::server::ClientAuthMode::None};\n};\n\nstruct HQToolParams {\n  void setMode(HQMode m) {\n    mode = m;\n    if (mode == HQMode::CLIENT) {\n      params = HQToolClientParams();\n    } else if (mode == HQMode::SERVER) {\n      params = HQToolServerParams();\n    }\n  }\n\n  [[nodiscard]] const HQBaseParams& baseParams() const {\n    if (mode == HQMode::CLIENT) {\n      return (HQBaseParams&)std::get<HQToolClientParams>(params);\n    } else if (mode == HQMode::SERVER) {\n      return (HQBaseParams&)std::get<HQToolServerParams>(params);\n    }\n    LOG(FATAL) << \"Uninit\";\n  }\n\n  HQBaseParams& baseParams() {\n    if (mode == HQMode::CLIENT) {\n      return (HQBaseParams&)std::get<HQToolClientParams>(params);\n    } else if (mode == HQMode::SERVER) {\n      return (HQBaseParams&)std::get<HQToolServerParams>(params);\n    }\n    LOG(FATAL) << \"Uninit\";\n  }\n\n  HQMode mode{HQMode::INVALID};\n  std::string logprefix;\n  std::string logdir;\n  bool logRuntime;\n  std::variant<HQToolClientParams, HQToolServerParams> params;\n};\n\n/**\n * A Builder class for HQToolParams that will build HQToolParams from command\n * line parameters processed by GFlag.\n */\nclass HQToolParamsBuilderFromCmdline {\n public:\n  using value_type = std::map<std::string, std::string>::value_type;\n  using initializer_list = std::initializer_list<value_type>;\n\n  explicit HQToolParamsBuilderFromCmdline(initializer_list);\n\n  [[nodiscard]] bool valid() const noexcept;\n\n  explicit operator bool() const noexcept {\n    return valid();\n  }\n\n  [[nodiscard]] const HQInvalidParams& invalidParams() const noexcept;\n\n  HQToolParams build() noexcept;\n\n private:\n  HQInvalidParams invalidParams_;\n  HQToolParams hqParams_;\n};\n\n// Initialized the parameters from the cmdline flags\nconst folly::Expected<HQToolParams, HQInvalidParams>\ninitializeParamsFromCmdline(\n    HQToolParamsBuilderFromCmdline::initializer_list initial = {});\n\n// Output convenience\nstd::ostream& operator<<(std::ostream&, HQToolParams&);\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQLoggerHelper.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/HQLoggerHelper.h>\n\nusing namespace quic::samples;\n\nHQLoggerHelper::HQLoggerHelper(const std::string& path,\n                               bool pretty,\n                               quic::VantagePoint vantagePoint)\n    : quic::FileQLogger(vantagePoint,\n                        quic::kHTTP3ProtocolType,\n                        path,\n                        pretty,\n                        false /* streaming */),\n      outputPath_(path),\n      pretty_(pretty) {\n}\n\nHQLoggerHelper::~HQLoggerHelper() {\n  try {\n    outputLogsToFile(outputPath_, pretty_);\n  } catch (...) {\n  }\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQLoggerHelper.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n\n#include <quic/logging/FileQLogger.h>\n#include <quic/logging/QLogger.h>\n\n/**\n * Allows adding FileQLogger objects to transport, which will output logs\n * prior to destrution\n */\nnamespace quic::samples {\n\nclass HQLoggerHelper : public ::quic::FileQLogger {\n public:\n  HQLoggerHelper(const std::string& /* path */,\n                 bool /* pretty */,\n                 quic::VantagePoint);\n\n  ~HQLoggerHelper() override;\n\n private:\n  std::string outputPath_;\n  bool pretty_;\n};\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQParams.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n\nnamespace quic::samples {\n\nbool HTTPVersion::parse(const std::string& verString) {\n  // version, major and minor are fields of struct HTTPVersion\n  version = verString;\n  if (version.length() == 1) {\n    major = folly::to<uint16_t>(version);\n    minor = 0;\n    canonical = folly::to<std::string>(major, \".\", minor);\n    return true;\n  }\n  std::string delimiter = \".\";\n  std::size_t pos = version.find(delimiter);\n  if (pos == std::string::npos) {\n    LOG(ERROR) << \"Invalid http-version string: \" << version\n               << \", defaulting to HTTP/1.1\";\n    major = 1;\n    minor = 1;\n    canonical = folly::to<std::string>(major, \".\", minor);\n    return false;\n  }\n\n  try {\n    std::string majorVer = version.substr(0, pos);\n    std::string minorVer = version.substr(pos + delimiter.length());\n    major = folly::to<uint16_t>(majorVer);\n    minor = folly::to<uint16_t>(minorVer);\n    canonical = folly::to<std::string>(major, \".\", minor);\n    return true;\n  } catch (const folly::ConversionError&) {\n    LOG(ERROR) << \"Invalid http-version string: \" << version\n               << \", defaulting to HTTP/1.1\";\n    major = 1;\n    minor = 1;\n    canonical = folly::to<std::string>(major, \".\", minor);\n    return false;\n  }\n}\n\nstd::ostream& operator<<(std::ostream& o, const HTTPVersion& v) {\n  o << \"http-version=\" << v.major << \"/\" << v.minor << \" (orig=\" << v.version\n    << \", canonical=\" << v.canonical << \")\";\n  return o;\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQParams.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <initializer_list>\n#include <map>\n#include <string>\n#include <vector>\n\n#include <fizz/server/FizzServerContext.h>\n#include <folly/Optional.h>\n#include <folly/SocketAddress.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <proxygen/lib/http/HTTPMethod.h>\n#include <proxygen/lib/http/session/HQSession.h>\n#include <quic/QuicConstants.h>\n#include <quic/congestion_control/CongestionControllerFactory.h>\n#include <quic/fizz/client/handshake/QuicPskCache.h>\n#include <quic/state/TransportSettings.h>\n\nnamespace quic::samples {\n\nstruct HTTPVersion {\n  std::string version{\"1.1\"};\n  std::string canonical{\"http/1.1\"};\n  uint16_t major{1};\n  uint16_t minor{1};\n  bool parse(const std::string&);\n};\n\nconst std::vector<std::string> kDefaultSupportedAlpns = {\"h3\",\n                                                         \"hq-interop\",\n                                                         \"h3-fb-05\",\n                                                         \"h3-alias-01\",\n                                                         \"h3-alias-02\",\n                                                         \"h3-29\",\n                                                         \"hq-29\"};\n\nstd::ostream& operator<<(std::ostream& o, const HTTPVersion& v);\n\n/**\n * Params for both clients and servers\n */\nstruct HQBaseParams {\n  // Transport section\n  std::vector<quic::QuicVersion> quicVersions{\n      quic::QuicVersion::MVFST,\n      quic::QuicVersion::MVFST_ALIAS,\n      quic::QuicVersion::MVFST_EXPERIMENTAL,\n      quic::QuicVersion::MVFST_EXPERIMENTAL3,\n      quic::QuicVersion::QUIC_V1,\n      quic::QuicVersion::QUIC_V1_ALIAS,\n      quic::QuicVersion::QUIC_V1_ALIAS2};\n  quic::TransportSettings transportSettings;\n  std::string congestionControlName;\n  std::optional<quic::CongestionControlType> congestionControl;\n  std::shared_ptr<quic::CongestionControllerFactory>\n      congestionControllerFactory;\n  bool sendKnobFrame{false};\n\n  // HTTP section\n  std::string protocol{\"h3\"};\n  HTTPVersion httpVersion;\n\n  std::chrono::milliseconds txnTimeout{std::chrono::seconds(5)};\n\n  // QLogger section\n  std::string qLoggerPath;\n  bool prettyJson{false};\n\n  // Transport knobs\n  std::string transportKnobs;\n};\n\nstruct HQServerParams : public HQBaseParams {\n  size_t serverThreads{0};\n  std::string ccpConfig;\n  folly::Optional<int64_t> rateLimitPerThread;\n  // UDP socket buffer sizes (0 = use system default)\n  size_t udpSendBufferSize{0};\n  size_t udpRecvBufferSize{0};\n};\n\nstruct HQClientParams : public HQBaseParams {\n  std::string host;\n  uint8_t clientCidLength{0};\n  folly::Optional<folly::SocketAddress> localAddress;\n\n  std::string pskFilePath;\n  std::shared_ptr<quic::QuicPskCache> pskCache;\n};\n\nstruct HQInvalidParam {\n  std::string name;\n  std::string value;\n  std::string errorMsg;\n};\n\nusing HQInvalidParams = std::vector<HQInvalidParam>;\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/HQServer.h>\n\n#include <ostream>\n\n#include <folly/io/SocketOptionMap.h>\n#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>\n#include <string>\n#include <vector>\n\n#include <folly/io/async/EventBaseLocal.h>\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n#include <proxygen/httpserver/samples/hq/H1QDownstreamSession.h>\n#include <proxygen/httpserver/samples/hq/HQLoggerHelper.h>\n#include <proxygen/lib/http/session/HQDownstreamSession.h>\n#include <quic/server/QuicSharedUDPSocketFactory.h>\n\nusing fizz::server::FizzServerContext;\nusing quic::QuicServerTransport;\n\nnamespace {\n\nusing namespace quic::samples;\nusing namespace proxygen;\n\n/**\n * HQSessionController creates new HQSession objects\n *\n * Each HQSessionController object can create only a single session\n * object. TODO: consider changing it.\n *\n * Once the created session object finishes (and detaches), the\n * associated HQController is destroyed. There is no need to\n * explicitly keep track of these objects.\n */\nclass HQSessionController\n    : public proxygen::HTTPSessionController\n    , public proxygen::HTTPSessionBase::InfoCallback {\n public:\n  using StreamData = std::pair<folly::IOBufQueue, bool>;\n\n  explicit HQSessionController(\n      const HQServerParams& /* params */,\n      const HTTPTransactionHandlerProvider&,\n      std::function<void(HQSession*)> onTransportReadyFn = nullptr);\n\n  ~HQSessionController() override = default;\n\n  void onDestroy(const proxygen::HTTPSessionBase& /* session*/) override;\n\n  proxygen::HTTPTransactionHandler* getRequestHandler(\n      proxygen::HTTPTransaction& /*txn*/,\n      proxygen::HTTPMessage* /* msg */) override;\n\n  proxygen::HTTPTransactionHandler* getParseErrorHandler(\n      proxygen::HTTPTransaction* /*txn*/,\n      const proxygen::HTTPException& /*error*/,\n      const folly::SocketAddress& /*localAddress*/) override;\n\n  proxygen::HTTPTransactionHandler* getTransactionTimeoutHandler(\n      proxygen::HTTPTransaction* /*txn*/,\n      const folly::SocketAddress& /*localAddress*/) override;\n\n  void attachSession(proxygen::HTTPSessionBase* /*session*/) override;\n\n  // The controller instance will be destroyed after this call.\n  void detachSession(const proxygen::HTTPSessionBase* /*session*/) override;\n\n  void onTransportReady(proxygen::HTTPSessionBase* /*session*/) override;\n  void onTransportReady(const proxygen::HTTPSessionBase&) override {\n  }\n\n private:\n  // The owning session. NOTE: this must be a plain pointer to\n  // avoid circular references\n  proxygen::HQSession* session_{nullptr};\n  // Provider of HTTPTransactionHandler, owned by HQServerTransportFactory\n  const HTTPTransactionHandlerProvider& httpTransactionHandlerProvider_;\n  std::function<void(HQSession*)> onTransportReadyFn_;\n  uint64_t sessionCount_{0};\n};\n\nHQSessionController::HQSessionController(\n    const HQServerParams& params,\n    const HTTPTransactionHandlerProvider& httpTransactionHandlerProvider,\n    std::function<void(HQSession*)> onTransportReadyFn)\n    : httpTransactionHandlerProvider_(httpTransactionHandlerProvider),\n      onTransportReadyFn_(std::move(onTransportReadyFn)) {\n}\n\nvoid HQSessionController::onTransportReady(HTTPSessionBase* /*session*/) {\n  if (onTransportReadyFn_) {\n    onTransportReadyFn_(session_);\n  }\n}\n\nvoid HQSessionController::onDestroy(const HTTPSessionBase&) {\n}\n\nHTTPTransactionHandler* HQSessionController::getRequestHandler(\n    HTTPTransaction& /*txn*/, HTTPMessage* msg) {\n  return httpTransactionHandlerProvider_(msg);\n}\n\nHTTPTransactionHandler* FOLLY_NULLABLE\nHQSessionController::getParseErrorHandler(\n    HTTPTransaction* /*txn*/,\n    const HTTPException& /*error*/,\n    const folly::SocketAddress& /*localAddress*/) {\n  return nullptr;\n}\n\nHTTPTransactionHandler* FOLLY_NULLABLE\nHQSessionController::getTransactionTimeoutHandler(\n    HTTPTransaction* /*txn*/, const folly::SocketAddress& /*localAddress*/) {\n  return nullptr;\n}\n\nvoid HQSessionController::attachSession(HTTPSessionBase* /*session*/) {\n  sessionCount_++;\n}\n\nvoid HQSessionController::detachSession(const HTTPSessionBase* /*session*/) {\n  if (--sessionCount_ == 0) {\n    delete this;\n  }\n}\n\n} // namespace\n\nnamespace quic::samples {\n\nHQServerTransportFactory::HQServerTransportFactory(\n    const HQServerParams& params,\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider,\n    std::function<void(proxygen::HQSession*)> onTransportReadyFn)\n    : params_(params),\n      httpTransactionHandlerProvider_(\n          std::move(httpTransactionHandlerProvider)),\n      onTransportReadyFn_(std::move(onTransportReadyFn)) {\n  alpnHandlers_[kHQ] = [this](std::shared_ptr<quic::QuicSocket> quicSocket,\n                              wangle::ConnectionManager* connMgr) {\n    quicSocket->setConnectionSetupCallback(nullptr);\n    return new H1QDownstreamSession(\n        std::move(quicSocket),\n        new HQSessionController(\n            params_, httpTransactionHandlerProvider_, onTransportReadyFn_),\n        connMgr);\n  };\n}\n\nQuicServerTransport::Ptr HQServerTransportFactory::make(\n    folly::EventBase* evb,\n    std::unique_ptr<quic::FollyAsyncUDPSocketAlias> socket,\n    const folly::SocketAddress& /* peerAddr */,\n    quic::QuicVersion,\n    std::shared_ptr<const FizzServerContext> ctx) noexcept {\n  auto transport = quic::QuicHandshakeSocketHolder::makeServerTransport(\n      evb, std::move(socket), std::move(ctx), this);\n  if (!params_.qLoggerPath.empty()) {\n    transport->setQLogger(std::make_shared<HQLoggerHelper>(\n        params_.qLoggerPath, params_.prettyJson, quic::VantagePoint::Server));\n  }\n  return transport;\n}\n\nvoid HQServerTransportFactory::onQuicTransportReady(\n    std::shared_ptr<quic::QuicSocket> quicSocket) {\n  auto alpn = quicSocket->getAppProtocol();\n  auto it = alpnHandlers_.end();\n  if (alpn) {\n    it = alpnHandlers_.find(*alpn);\n  }\n  auto qevb = quicSocket->getEventBase();\n  folly::EventBase* evb{nullptr};\n  if (qevb) {\n    evb = qevb->getTypedEventBase<quic::FollyQuicEventBase>()\n              ->getBackingEventBase();\n  }\n\n  if (it == alpnHandlers_.end()) {\n    // by default, it's H3\n    handleHQAlpn(std::move(quicSocket), getConnectionManager(evb));\n  } else {\n    it->second(std::move(quicSocket), getConnectionManager(evb));\n  }\n}\n\nvoid HQServerTransportFactory::onConnectionSetupError(\n    std::shared_ptr<quic::QuicSocket>, quic::QuicError code) {\n  LOG(ERROR) << \"Failed to accept QUIC connection: \" << code.message;\n}\n\nwangle::ConnectionManager* HQServerTransportFactory::getConnectionManager(\n    folly::EventBase* evb) {\n  auto connMgrPtrPtr = connMgr_.get(*evb);\n  wangle::ConnectionManager* connMgr{nullptr};\n  if (connMgrPtrPtr) {\n    connMgr = (*connMgrPtrPtr).get();\n  } else {\n    auto& connMgrPtrRef = connMgr_.emplace(\n        *evb, wangle::ConnectionManager::makeUnique(evb, params_.txnTimeout));\n    connMgr = connMgrPtrRef.get();\n  }\n  return connMgr;\n}\n\nvoid HQServerTransportFactory::handleHQAlpn(\n    std::shared_ptr<quic::QuicSocket> quicSocket,\n    wangle::ConnectionManager* connMgr) {\n  wangle::TransportInfo tinfo;\n  auto controller = new HQSessionController(\n      params_, httpTransactionHandlerProvider_, onTransportReadyFn_);\n  auto session = new HQDownstreamSession(\n      connMgr->getDefaultTimeout(), controller, tinfo, controller);\n  quicSocket->setConnectionSetupCallback(session);\n  quicSocket->setConnectionCallback(session);\n  session->setSocket(std::move(quicSocket));\n  session->setEgressSettings(\n      {{proxygen::SettingsId::ENABLE_CONNECT_PROTOCOL, 1},\n       {proxygen::SettingsId::_HQ_DATAGRAM_DRAFT_8, 1},\n       {proxygen::SettingsId::_HQ_DATAGRAM, 1},\n       {proxygen::SettingsId::_HQ_DATAGRAM_RFC, 1},\n       {proxygen::SettingsId::ENABLE_WEBTRANSPORT, 1},\n       {proxygen::SettingsId::H3_WT_MAX_SESSIONS, 1}});\n\n  session->startNow();\n  session->onTransportReady();\n}\n\nHQServer::HQServer(\n    HQServerParams params,\n    HTTPTransactionHandlerProvider httpTransactionHandlerProvider,\n    std::function<void(proxygen::HQSession*)> onTransportReadyFn,\n    std::shared_ptr<const fizz::server::FizzServerContext> fizzCtx)\n    : HQServer(std::move(params),\n               std::make_unique<HQServerTransportFactory>(\n                   params_,\n                   std::move(httpTransactionHandlerProvider),\n                   std::move(onTransportReadyFn)),\n               std::move(fizzCtx)) {\n}\n\nHQServer::HQServer(HQServerParams params,\n                   std::unique_ptr<quic::QuicServerTransportFactory> factory,\n                   const std::string& certificateFilePath,\n                   const std::string& keyFilePath,\n                   fizz::server::ClientAuthMode clientAuth,\n                   const std::vector<std::string>& supportedAlpns)\n    : HQServer(\n          std::move(params),\n          std::move(factory),\n          createFizzServerContext(\n              supportedAlpns, clientAuth, certificateFilePath, keyFilePath)) {\n}\n\nHQServer::HQServer(\n    HQServerParams params,\n    std::unique_ptr<quic::QuicServerTransportFactory> factory,\n    std::shared_ptr<const fizz::server::FizzServerContext> fizzCtx)\n    : params_(std::move(params)) {\n  params_.transportSettings.datagramConfig.enabled = true;\n  params_.transportSettings.advertisedKnobFrameSupport = true;\n  server_ = quic::QuicServer::createQuicServer(params_.transportSettings);\n\n  server_->setBindV6Only(false);\n  if (params_.congestionControllerFactory) {\n    server_->setCongestionControllerFactory(\n        params_.congestionControllerFactory);\n  } else {\n    server_->setCongestionControllerFactory(\n        std::make_shared<ServerCongestionControllerFactory>());\n  }\n\n  server_->setQuicServerTransportFactory(std::move(factory));\n  server_->setQuicUDPSocketFactory(\n      std::make_unique<QuicSharedUDPSocketFactory>());\n  server_->setHealthCheckToken(\"health\");\n  server_->setSupportedVersion(params_.quicVersions);\n  server_->setFizzContext(std::move(fizzCtx));\n\n  // Apply UDP socket buffer size options if specified\n  folly::SocketOptionMap socketOptions;\n  if (params_.udpRecvBufferSize > 0) {\n    socketOptions[{\n        SOL_SOCKET, SO_RCVBUF, folly::SocketOptionKey::ApplyPos::POST_BIND}] =\n        static_cast<int>(params_.udpRecvBufferSize);\n  }\n  if (params_.udpSendBufferSize > 0) {\n    socketOptions[{\n        SOL_SOCKET, SO_SNDBUF, folly::SocketOptionKey::ApplyPos::POST_BIND}] =\n        static_cast<int>(params_.udpSendBufferSize);\n  }\n  if (!socketOptions.empty()) {\n    server_->setSocketOptions(socketOptions);\n  }\n\n  if (params_.rateLimitPerThread) {\n    server_->setRateLimit(\n        [rateLimitPerThread = params_.rateLimitPerThread.value()]() {\n          return rateLimitPerThread;\n        },\n        1s);\n  }\n  server_->setShouldRegisterKnobParamHandlerFn(\n      [](TransportKnobParamId transportKnobParamId) -> bool { return true; });\n}\n\nvoid HQServer::start(const folly::SocketAddress& localAddress,\n                     std::vector<folly::EventBase*> evbs) {\n  if (evbs.empty()) {\n    server_->start(localAddress, params_.serverThreads);\n  } else {\n    server_->initialize(localAddress, evbs, true);\n    server_->start();\n  }\n}\n\nconst folly::SocketAddress HQServer::getAddress() const {\n  server_->waitUntilInitialized();\n  const auto& boundAddr = server_->getAddress();\n  LOG(INFO) << \"HQ server started at: \" << boundAddr.describe();\n  return boundAddr;\n}\n\nvoid HQServer::stop() {\n  server_->shutdown();\n}\n\nvoid HQServer::rejectNewConnections(std::function<bool()> rejectFn) {\n  server_->rejectNewConnections(std::move(rejectFn));\n}\n\nvoid HQServer::setHostId(uint32_t hostId) {\n  server_->setHostId(hostId);\n}\n\nvoid HQServer::setProcessId(quic::ProcessId processId) {\n  server_->setProcessId(processId);\n}\n\nvoid HQServer::setConnectionIdVersion(quic::ConnectionIdVersion version) {\n  server_->setConnectionIdVersion(version);\n}\n\nvoid HQServer::waitUntilInitialized() {\n  server_->waitUntilInitialized();\n}\n\nvoid HQServer::allowBeingTakenOver(const folly::SocketAddress& addr) {\n  server_->allowBeingTakenOver(addr);\n}\n\nint HQServer::getTakeoverHandlerSocketFD() const {\n  return server_->getTakeoverHandlerSocketFD();\n}\n\nstd::vector<int> HQServer::getAllListeningSocketFDs() const {\n  return server_->getAllListeningSocketFDs();\n}\n\nvoid HQServer::setListeningFDs(const std::vector<int>& fds) {\n  server_->setListeningFDs(fds);\n}\n\nquic::ProcessId HQServer::getProcessId() const {\n  return server_->getProcessId();\n}\n\nTakeoverProtocolVersion HQServer::getTakeoverProtocolVersion() const {\n  return server_->getTakeoverProtocolVersion();\n}\n\nvoid HQServer::startPacketForwarding(const folly::SocketAddress& addr) {\n  server_->startPacketForwarding(addr);\n}\n\nvoid HQServer::pauseRead() {\n  server_->pauseRead();\n}\n\nvoid HQServer::setFizzContext(\n    std::shared_ptr<const fizz::server::FizzServerContext> ctx) {\n  server_->setFizzContext(std::move(ctx));\n}\n\nvoid HQServer::setFizzContext(\n    folly::EventBase* evb,\n    std::shared_ptr<const fizz::server::FizzServerContext> ctx) {\n  server_->setFizzContext(evb, std::move(ctx));\n}\n\nScopedHQServer::ScopedHQServer(HQServerParams params,\n                               const folly::SocketAddress& localAddress,\n                               HTTPTransactionHandlerProvider handlerProvider,\n                               const std::string& certificateFilePath,\n                               const std::string& keyFilePath,\n                               fizz::server::ClientAuthMode clientAuth,\n                               const std::vector<std::string>& supportedAlpns)\n    : server_(\n          std::move(params),\n          std::move(handlerProvider),\n          nullptr,\n          createFizzServerContext(\n              supportedAlpns, clientAuth, certificateFilePath, keyFilePath)) {\n  server_.start(localAddress);\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQServer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <iostream>\n#include <string>\n#include <vector>\n\n#include <folly/io/async/EventBaseLocal.h>\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <quic/server/QuicHandshakeSocketHolder.h>\n#include <quic/server/QuicServer.h>\n\nnamespace proxygen {\nclass HQSession;\n}\n\nnamespace quic::samples {\n\nusing HTTPTransactionHandlerProvider =\n    std::function<proxygen::HTTPTransactionHandler*(proxygen::HTTPMessage*)>;\n\nclass HQServer {\n public:\n  HQServer(HQServerParams params,\n           HTTPTransactionHandlerProvider httpTransactionHandlerProvider,\n           std::function<void(proxygen::HQSession*)> onTransportReadyFn,\n           std::shared_ptr<const fizz::server::FizzServerContext> fizzCtx);\n\n  HQServer(HQServerParams params,\n           std::unique_ptr<quic::QuicServerTransportFactory> factory,\n           const std::string& certificateFilePath,\n           const std::string& keyFilePath,\n           fizz::server::ClientAuthMode clientAuth,\n           const std::vector<std::string>& supportedAlpns);\n\n  HQServer(HQServerParams params,\n           std::unique_ptr<quic::QuicServerTransportFactory> factory,\n           std::shared_ptr<const fizz::server::FizzServerContext> fizzCtx);\n\n  // Starts the QUIC transport in background thread\n  void start(const folly::SocketAddress& localAddress,\n             std::vector<folly::EventBase*> evbs = {});\n\n  // Returns the listening address of the server\n  // NOTE: can block until the server has started\n  const folly::SocketAddress getAddress() const;\n\n  std::vector<folly::EventBase*> getWorkerEvbs() const noexcept {\n    return server_->getWorkerEvbs();\n  }\n\n  // Stops both the QUIC transport AND the HTTP server handling loop\n  void stop();\n\n  void setStatsFactory(\n      std::unique_ptr<quic::QuicTransportStatsCallbackFactory>&& statsFactory) {\n    CHECK(server_);\n    server_->setTransportStatsCallbackFactory(std::move(statsFactory));\n  }\n\n  // Takeover runtime wrapper methods - forward to underlying QuicServer\n  // Takeover part 1: Methods called on the old instance.\n  void allowBeingTakenOver(const folly::SocketAddress& addr);\n  [[nodiscard]] int getTakeoverHandlerSocketFD() const;\n  [[nodiscard]] std::vector<int> getAllListeningSocketFDs() const;\n\n  // Takeover part 2: Methods called during the initialization of the new\n  // process.\n  void setListeningFDs(const std::vector<int>& fds);\n  void setProcessId(quic::ProcessId pid);\n  void setHostId(uint32_t hostId);\n  void setConnectionIdVersion(quic::ConnectionIdVersion version);\n  void waitUntilInitialized();\n\n  // Takeover part 3: Methods called during the packet forwarding setup.\n  [[nodiscard]] quic::ProcessId getProcessId() const;\n  [[nodiscard]] TakeoverProtocolVersion getTakeoverProtocolVersion() const;\n  void startPacketForwarding(const folly::SocketAddress& addr);\n\n  // Takeover part 4: Methods called on the old instance to wind down.\n  void rejectNewConnections(std::function<bool()> rejectFn);\n  void pauseRead();\n\n  void setFizzContext(\n      std::shared_ptr<const fizz::server::FizzServerContext> ctx);\n\n  void setFizzContext(\n      folly::EventBase* evb,\n      std::shared_ptr<const fizz::server::FizzServerContext> ctx);\n\n private:\n  HQServerParams params_;\n  std::shared_ptr<quic::QuicServer> server_;\n};\n\nclass ScopedHQServer {\n public:\n  static std::unique_ptr<ScopedHQServer> start(\n      const HQServerParams& params,\n      const folly::SocketAddress& localAddress,\n      HTTPTransactionHandlerProvider handlerProvider,\n      const std::string& certificateFilePath,\n      const std::string& keyFilePath,\n      fizz::server::ClientAuthMode clientAuth,\n      const std::vector<std::string>& supportedAlpns) {\n    return std::make_unique<ScopedHQServer>(params,\n                                            localAddress,\n                                            std::move(handlerProvider),\n                                            certificateFilePath,\n                                            keyFilePath,\n                                            clientAuth,\n                                            supportedAlpns);\n  }\n\n  ScopedHQServer(HQServerParams params,\n                 const folly::SocketAddress& localAddress,\n                 HTTPTransactionHandlerProvider handlerProvider,\n                 const std::string& certificateFilePath,\n                 const std::string& keyFilePath,\n                 fizz::server::ClientAuthMode clientAuth,\n                 const std::vector<std::string>& supportedAlpns);\n\n  ~ScopedHQServer() {\n    server_.stop();\n  }\n\n  [[nodiscard]] const folly::SocketAddress getAddress() const {\n    return server_.getAddress();\n  }\n\n private:\n  HQServer server_;\n};\n\nclass HQServerTransportFactory\n    : public quic::QuicServerTransportFactory\n    , private quic::QuicHandshakeSocketHolder::Callback {\n public:\n  explicit HQServerTransportFactory(\n      const HQServerParams& params,\n      HTTPTransactionHandlerProvider httpTransactionHandlerProvider,\n      std::function<void(proxygen::HQSession*)> onTransportReadyFn_);\n  ~HQServerTransportFactory() override = default;\n\n  // Creates new quic server transport\n  quic::QuicServerTransport::Ptr make(\n      folly::EventBase* evb,\n      std::unique_ptr<quic::FollyAsyncUDPSocketAlias> socket,\n      const folly::SocketAddress& /* peerAddr */,\n      quic::QuicVersion quicVersion,\n      std::shared_ptr<const fizz::server::FizzServerContext> ctx) noexcept\n      override;\n\n  using AlpnHandlerFn = std::function<void(std::shared_ptr<quic::QuicSocket>,\n                                           wangle::ConnectionManager*)>;\n  void addAlpnHandler(const std::vector<std::string>& alpns,\n                      const AlpnHandlerFn& handler) {\n    for (auto& alpn : alpns) {\n      alpnHandlers_[alpn] = handler;\n    }\n  }\n\n private:\n  void onQuicTransportReady(\n      std::shared_ptr<quic::QuicSocket> quicSocket) override;\n  void onConnectionSetupError(std::shared_ptr<quic::QuicSocket> quicSocket,\n                              quic::QuicError code) override;\n  wangle::ConnectionManager* getConnectionManager(folly::EventBase* evb);\n  void handleHQAlpn(std::shared_ptr<quic::QuicSocket> quicSocket,\n                    wangle::ConnectionManager* connMgr);\n\n  // Configuration params\n  const HQServerParams& params_;\n  // Provider of HTTPTransactionHandler\n  HTTPTransactionHandlerProvider httpTransactionHandlerProvider_;\n  std::function<void(proxygen::HQSession*)> onTransportReadyFn_;\n  folly::EventBaseLocal<wangle::ConnectionManager::UniquePtr> connMgr_;\n  std::map<std::string, AlpnHandlerFn> alpnHandlers_;\n};\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQServerModule.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpserver/samples/hq/FizzContext.h>\n#include <proxygen/httpserver/samples/hq/H2Server.h>\n#include <proxygen/httpserver/samples/hq/HQServerModule.h>\n#include <proxygen/httpserver/samples/hq/SampleHandlers.h>\n#include <proxygen/lib/http/session/HQSession.h>\n\nusing namespace proxygen;\n\nnamespace {\nvoid sendKnobFrame(HQSession* session, const folly::StringPiece str) {\n  if (str.empty()) {\n    return;\n  }\n  uint64_t knobSpace = 0xfaceb00c;\n  uint64_t knobId = 200;\n  quic::BufPtr buf(folly::IOBuf::create(str.size()));\n  memcpy(buf->writableData(), str.data(), str.size());\n  buf->append(str.size());\n  VLOG(10) << \"Sending Knob Frame to peer. KnobSpace: \" << std::hex << knobSpace\n           << \" KnobId: \" << std::dec << knobId << \" Knob Blob\" << str;\n  const auto knobSent = session->sendKnob(0xfaceb00c, 200, std::move(buf));\n  if (knobSent.hasError()) {\n    LOG(ERROR) << \"Failed to send Knob frame to peer. Received error: \"\n               << knobSent.error();\n  }\n}\n} // namespace\n\nnamespace quic::samples {\n\nvoid startServer(\n    const HQToolServerParams& params,\n    std::unique_ptr<quic::QuicTransportStatsCallbackFactory>&& statsFactory) {\n  // Run H2 server in a separate thread\n  Dispatcher dispatcher(HandlerParams(\n      params.protocol, params.port, params.httpVersion.canonical));\n  auto dispatchFn = [&dispatcher](proxygen::HTTPMessage* request) {\n    return dispatcher.getRequestHandler(request);\n  };\n  auto h2server = H2Server::run(params, dispatchFn);\n  // Run HQ server\n  std::function<void(HQSession*)> onTransportReadyFn;\n  if (params.sendKnobFrame) {\n    onTransportReadyFn = [](HQSession* session) {\n      sendKnobFrame(session, (\"Hello, World from Server!\"));\n    };\n  }\n\n  auto ctx = params.useInsecureDefaultCertificate\n                 ? createFizzServerContextWithInsecureDefault(\n                       params.supportedAlpns,\n                       params.clientAuth,\n                       params.certificateFilePath,\n                       params.keyFilePath)\n                 : createFizzServerContext(params.supportedAlpns,\n                                           params.clientAuth,\n                                           params.certificateFilePath,\n                                           params.keyFilePath);\n\n  HQServer server(\n      params, dispatchFn, std::move(onTransportReadyFn), std::move(ctx));\n  if (statsFactory) {\n    server.setStatsFactory(std::move(statsFactory));\n  }\n\n  folly::SocketAddress localAddress(params.host, params.port, true);\n  server.start(localAddress);\n  // Wait until the quic server initializes\n  server.getAddress();\n  h2server.join();\n  server.stop();\n}\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/HQServerModule.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/httpserver/samples/hq/HQCommandLine.h>\n\nnamespace quic::samples {\n\nvoid startServer(const HQToolServerParams& params,\n                 std::unique_ptr<quic::QuicTransportStatsCallbackFactory>&&\n                     statsFactory = nullptr);\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/protocol/CertificateVerifier.h>\n\nnamespace proxygen {\n\n// This is an insecure certificate verifier and is not meant to be\n// used in production. Using it in production would mean that this will\n// leave everyone insecure.\nclass InsecureVerifierDangerousDoNotUseInProduction\n    : public fizz::CertificateVerifier {\n public:\n  ~InsecureVerifierDangerousDoNotUseInProduction() override = default;\n\n  [[nodiscard]] std::shared_ptr<const folly::AsyncTransportCertificate> verify(\n      const std::vector<std::shared_ptr<const fizz::PeerCert>>& certs)\n      const override {\n    return certs.front();\n  }\n\n  [[nodiscard]] std::vector<fizz::Extension> getCertificateRequestExtensions()\n      const override {\n    return {};\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/SampleHandlers.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/hq/SampleHandlers.h>\n#include <proxygen/lib/http/webtransport/HTTPWebTransport.h>\n\n#include <folly/String.h>\n#include <proxygen/lib/utils/Logging.h>\n#include <string>\n\nnamespace {\nstd::atomic<bool> shouldPassHealthChecks{true};\n}\n\nDEFINE_string(static_root,\n              \"\",\n              \"Path to serve static files from. Disabled if empty.\");\n\nnamespace quic::samples {\n\nusing namespace proxygen;\n\nHTTPTransactionHandler* Dispatcher::getRequestHandler(HTTPMessage* msg) {\n  DCHECK(msg);\n  auto path = msg->getPathAsStringPiece();\n  if (path == \"/\" || path == \"/echo\") {\n    return new EchoHandler(params_);\n  }\n  if (path == \"/continue\") {\n    return new ContinueHandler(params_);\n  }\n  if (path.size() > 1 && path[0] == '/' && std::isdigit(path[1])) {\n    return new RandBytesGenHandler(params_);\n  }\n  if (path == \"/status\") {\n    return new HealthCheckHandler(shouldPassHealthChecks, params_);\n  }\n  if (path == \"/status_ok\") {\n    shouldPassHealthChecks = true;\n    return new HealthCheckHandler(true, params_);\n  }\n  if (path == \"/status_fail\") {\n    shouldPassHealthChecks = false;\n    return new HealthCheckHandler(false, params_);\n  }\n  if (path == \"/wait\" || path == \"/release\") {\n    return new WaitReleaseHandler(\n        folly::EventBaseManager::get()->getEventBase(), params_);\n  }\n\n  if (path == \"/post\") {\n    return new SimplePostHandler(params_);\n  }\n\n  if (path.startsWith(\"/chunked\")) {\n    return new ChunkedHandler(params_,\n                              folly::EventBaseManager::get()->getEventBase());\n  }\n  if (path.startsWith(\"/push\")) {\n    return new ServerPushHandler(params_);\n  }\n\n  if (path.startsWith(\"/webtransport/devious-baton\")) {\n    return new DeviousBatonHandler(\n        params_, folly::EventBaseManager::get()->getEventBase());\n  }\n\n  if (!FLAGS_static_root.empty()) {\n    auto staticFileHandler =\n        StaticFileHandler::make(params_, FLAGS_static_root);\n    return staticFileHandler.get();\n  }\n  if (path.startsWith(\"/delay\")) {\n    return new DelayHandler(params_,\n                            folly::EventBaseManager::get()->getEventBase());\n  }\n  return new DummyHandler(params_);\n}\n\nclass WaitReleaseHandler;\n\nstd::unordered_map<uint, WaitReleaseHandler*>&\nWaitReleaseHandler::getWaitingHandlers() {\n  static std::unordered_map<uint, WaitReleaseHandler*> waitingHandlers;\n  return waitingHandlers;\n}\n\nstd::mutex& WaitReleaseHandler::getMutex() {\n  static std::mutex mutex;\n  return mutex;\n}\n\nvoid WaitReleaseHandler::onHeadersComplete(\n    std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n  VLOG(10) << \"WaitReleaseHandler::onHeadersComplete\";\n  msg->dumpMessage(2);\n  path_ = msg->getPath();\n  auto idstr = msg->getQueryParam(\"id\");\n\n  if (msg->getMethod() != proxygen::HTTPMethod::GET ||\n      idstr == proxygen::empty_string ||\n      (path_ != \"/wait\" && path_ != \"/release\")) {\n    sendErrorResponse(\"bad request\\n\");\n    return;\n  }\n\n  auto id = folly::tryTo<uint>(idstr);\n  if (!id.hasValue() || id.value() == 0) {\n    sendErrorResponse(\"invalid id\\n\");\n    return;\n  }\n\n  id_ = id.value();\n\n  txn_->setIdleTimeout(std::chrono::milliseconds(120000));\n  std::lock_guard<std::mutex> g(getMutex());\n  auto& waitingHandlers = getWaitingHandlers();\n  if (path_ == \"/wait\") {\n    auto waitHandler = waitingHandlers.find(id_);\n    if (waitHandler != waitingHandlers.end()) {\n      sendErrorResponse(\"id already exists\\n\");\n      return;\n    }\n    waitingHandlers.insert(std::make_pair(id_, this));\n    sendOkResponse(\"waiting\\n\", false /* eom */);\n  } else if (path_ == \"/release\") {\n    auto waitHandler = waitingHandlers.find(id.value());\n    if (waitHandler == waitingHandlers.end()) {\n      sendErrorResponse(\"id does not exist\\n\");\n      return;\n    }\n    waitHandler->second->release();\n    waitingHandlers.erase(waitHandler);\n    sendOkResponse(\"released\\n\", true /* eom */);\n  }\n}\n\nvoid WaitReleaseHandler::maybeCleanup() {\n  if (path_ == \"/wait\" && id_ != 0) {\n    std::lock_guard<std::mutex> g(getMutex());\n    auto& waitingHandlers = getWaitingHandlers();\n    auto waitHandler = waitingHandlers.find(id_);\n    if (waitHandler != waitingHandlers.end()) {\n      waitingHandlers.erase(waitHandler);\n    }\n  }\n}\n// ServerPushHandler methods\n//\n\nclass ServerPushHandler;\n\nvoid ServerPushHandler::onHeadersComplete(\n    std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n  VLOG(10) << \"ServerPushHandler::\" << __func__;\n  msg->dumpMessage(2);\n  path_ = msg->getPath();\n\n  if (msg->getMethod() != proxygen::HTTPMethod::GET) {\n    LOG(ERROR) << \"Method not supported\";\n    sendErrorResponse(\"bad request\\n\");\n    return;\n  }\n\n  VLOG(2) << \"Received GET request for \" << path_ << \" at: \"\n          << std::chrono::duration_cast<std::chrono::microseconds>(\n                 std::chrono::steady_clock::now().time_since_epoch())\n                 .count();\n\n  std::string gPushResponseBody;\n  std::vector<std::string> pathPieces;\n  folly::split('/', path_, pathPieces);\n  int responseSize = 0;\n  int numResponses = 1;\n\n  if (pathPieces.size() > 2) {\n    auto sizeFromPath = folly::tryTo<int>(pathPieces[2]);\n    responseSize = sizeFromPath.value_or(0);\n    if (responseSize != 0) {\n      VLOG(2) << \"Requested a response size of \" << responseSize;\n      gPushResponseBody = std::string(responseSize, 'a');\n    }\n  }\n\n  if (pathPieces.size() > 3) {\n    auto numResponsesFromPath = folly::tryTo<int>(pathPieces[3]);\n    numResponses = numResponsesFromPath.value_or(1);\n    VLOG(2) << \"Requested a repeat count of \" << numResponses;\n  }\n\n  for (int i = 0; i < numResponses; ++i) {\n    VLOG(2) << \"Sending push txn \" << i << \"/\" << numResponses;\n\n    // Create a URL for the pushed resource\n    auto pushedResourceUrl =\n        folly::to<std::string>(msg->getURL(), \"/\", \"pushed\", i);\n\n    // Create a pushed transaction and handler\n    auto pushedTxn = txn_->newPushedTransaction(&pushTxnHandler_);\n\n    if (!pushedTxn) {\n      LOG(ERROR) << \"Could not create push txn; stop pushing\";\n      break;\n    }\n\n    // Send a promise for the pushed resource\n    sendPushPromise(pushedTxn, pushedResourceUrl);\n\n    // Send the push response\n    sendPushResponse(\n        pushedTxn, pushedResourceUrl, gPushResponseBody, true /* eom */);\n  }\n\n  // Send the response to the original get request\n  sendOkResponse(\"I AM THE REQUEST RESPONSE AND I AM RESPONSIBLE\\n\",\n                 true /* eom */);\n}\n\nvoid ServerPushHandler::sendPushPromise(proxygen::HTTPTransaction* txn,\n                                        const std::string& pushedResourceUrl) {\n  VLOG(10) << \"ServerPushHandler::\" << __func__;\n  proxygen::HTTPMessage promise;\n  promise.setMethod(\"GET\");\n  promise.setURL(pushedResourceUrl);\n  promise.setVersionString(getHttpVersion());\n  promise.setIsChunked(true);\n  txn->sendHeaders(promise);\n\n  VLOG(2) << \"Sent push promise for \" << pushedResourceUrl << \" at: \"\n          << std::chrono::duration_cast<std::chrono::microseconds>(\n                 std::chrono::steady_clock::now().time_since_epoch())\n                 .count();\n}\n\nvoid ServerPushHandler::sendPushResponse(proxygen::HTTPTransaction* pushTxn,\n                                         const std::string& pushedResourceUrl,\n                                         const std::string& pushedResourceBody,\n                                         bool eom) {\n  VLOG(10) << \"ServerPushHandler::\" << __func__;\n  proxygen::HTTPMessage resp = createHttpResponse(200, \"OK\");\n  resp.setWantsKeepalive(true);\n  resp.setIsChunked(true);\n  pushTxn->sendHeaders(resp);\n\n  std::string responseStr =\n      \"I AM THE PUSHED RESPONSE AND I AM NOT RESPONSIBLE: \" +\n      pushedResourceBody;\n  pushTxn->sendBody(folly::IOBuf::copyBuffer(responseStr));\n\n  VLOG(2) << \"Sent push response for \" << pushedResourceUrl << \" at: \"\n          << std::chrono::duration_cast<std::chrono::microseconds>(\n                 std::chrono::steady_clock::now().time_since_epoch())\n                 .count();\n\n  if (eom) {\n    pushTxn->sendEOM();\n    VLOG(2) << \"Sent EOM for \" << pushedResourceUrl << \" at: \"\n            << std::chrono::duration_cast<std::chrono::microseconds>(\n                   std::chrono::steady_clock::now().time_since_epoch())\n                   .count();\n  }\n}\n\nvoid ServerPushHandler::sendErrorResponse(const std::string& body) {\n  proxygen::HTTPMessage resp = createHttpResponse(400, \"ERROR\");\n  resp.setWantsKeepalive(false);\n  txn_->sendHeaders(resp);\n  txn_->sendBody(folly::IOBuf::copyBuffer(body));\n  txn_->sendEOM();\n}\n\nvoid ServerPushHandler::sendOkResponse(const std::string& body, bool eom) {\n  VLOG(10) << \"ServerPushHandler::\" << __func__ << \": sending \" << body.length()\n           << \" bytes\";\n  proxygen::HTTPMessage resp = createHttpResponse(200, \"OK\");\n  resp.setWantsKeepalive(true);\n  resp.setIsChunked(true);\n  txn_->sendHeaders(resp);\n  txn_->sendBody(folly::IOBuf::copyBuffer(body));\n  if (eom) {\n    txn_->sendEOM();\n  }\n}\n\nvoid ServerPushHandler::onBody(\n    std::unique_ptr<folly::IOBuf> /*chain*/) noexcept {\n  VLOG(10) << \"ServerPushHandler::\" << __func__ << \" - ignoring\";\n}\n\nvoid ServerPushHandler::onEOM() noexcept {\n  VLOG(10) << \"ServerPushHandler::\" << __func__ << \" - ignoring\";\n}\n\nvoid ServerPushHandler::onError(const proxygen::HTTPException& error) noexcept {\n  VLOG(10) << \"ServerPushHandler::onError error=\" << error.what();\n}\n\nvoid DeviousBatonHandler::onHeadersComplete(\n    std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n  VLOG(10) << \"WebtransHandler::\" << __func__;\n  msg->dumpMessage(2);\n\n  if (msg->getMethod() != proxygen::HTTPMethod::CONNECT) {\n    LOG(ERROR) << \"Method not supported\";\n    proxygen::HTTPMessage resp;\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(400);\n    resp.setStatusMessage(\"ERROR\");\n    resp.setWantsKeepalive(false);\n    txn_->sendHeaders(resp);\n    txn_->sendEOM();\n    txn_ = nullptr;\n    return;\n  }\n\n  VLOG(2) << \"Received CONNECT request for \" << msg->getPathAsStringPiece()\n          << \" at: \"\n          << std::chrono::duration_cast<std::chrono::microseconds>(\n                 std::chrono::steady_clock::now().time_since_epoch())\n                 .count();\n\n  auto status = 500;\n  auto wt = txn_->getWebTransport();\n  if (wt) {\n    devious_.emplace(wt,\n                     devious::DeviousBaton::Mode::SERVER,\n                     [this](WebTransport::StreamReadHandle* readHandle) {\n                       readHandle->awaitNextRead(\n                           evb_,\n                           [this](auto readHandle, auto id, auto streamData) {\n                             readHandler(readHandle, id, std::move(streamData));\n                           });\n                     });\n    auto respCode = devious_->onRequest(*msg);\n    if (!respCode) {\n      status = respCode.error();\n    } else {\n      status = 200;\n    }\n  }\n\n  // Check for query param immediate=1 and store result in bool\n  bool immediate = (msg->getQueryParam(\"immediate\") == \"1\");\n\n  // Send the response to the original get request\n  proxygen::HTTPMessage resp;\n  resp.setVersionString(getHttpVersion());\n  resp.setStatusCode(status);\n  resp.setIsChunked(true);\n  if (status / 100 == 2) {\n    resp.getHeaders().add(\"sec-webtransport-http3-draft\", \"draft02\");\n    resp.setWantsKeepalive(true);\n  } else {\n    resp.setWantsKeepalive(false);\n    devious_.reset();\n  }\n  std::vector<std::string> supportedProtocols{\"deviousbaton-01\"};\n  if (auto wtAvailableProtocols =\n          HTTPWebTransport::getWTAvailableProtocols(*msg)) {\n    if (auto wtProtocol = HTTPWebTransport::negotiateWTProtocol(\n            wtAvailableProtocols.value(), supportedProtocols)) {\n      // TODO: Set the version from the negotiated protocol instead of query\n      // param\n      HTTPWebTransport::setWTProtocol(resp, wtProtocol.value());\n    } else {\n      VLOG(4) << \"Failed to negotiate WebTransport protocol\";\n      resp.setStatusCode(400);\n    }\n  }\n  resp.dumpMessage(4);\n  txn_->sendHeaders(resp);\n\n  if (devious_) {\n    if (immediate) {\n      devious_->start();\n    } else {\n      folly::EventBaseManager::get()->getEventBase()->runInLoop(\n          [this] { devious_->start(); });\n    }\n  } else {\n    txn_->sendEOM();\n    txn_ = nullptr;\n  }\n}\n\nvoid DeviousBatonHandler::readHandler(\n    WebTransport::StreamReadHandle* readHandle,\n    uint64_t id,\n    folly::Try<WebTransport::StreamData> streamData) {\n  if (streamData.hasException()) {\n    VLOG(4) << \"read error=\" << streamData.exception().what();\n    return;\n  }\n\n  VLOG(4) << \"read data id=\" << id;\n\n  devious_->onStreamData(\n      id, streams_[id], std::move(streamData->data), streamData->fin);\n  if (!readHandle) {\n    // terminal event (fin or exception), handle is no longer valid\n    return;\n  }\n  if (!readHandle->getCancelToken().isCancellationRequested()) {\n    readHandle->awaitNextRead(\n        evb_, [this](auto readHandle, auto id, auto streamData) {\n          readHandler(readHandle, id, std::move(streamData));\n        });\n  }\n}\n\nvoid DeviousBatonHandler::onWebTransportBidiStream(\n    HTTPCodec::StreamID id, WebTransport::BidiStreamHandle stream) noexcept {\n  VLOG(4) << \"New Bidi Stream=\" << id;\n  stream.readHandle->awaitNextRead(\n      evb_, [this](auto readHandle, auto id, auto streamData) {\n        readHandler(readHandle, id, std::move(streamData));\n      });\n}\n\nvoid DeviousBatonHandler::onWebTransportUniStream(\n    HTTPCodec::StreamID id,\n    WebTransport::StreamReadHandle* readHandle) noexcept {\n  VLOG(4) << \"New Uni Stream=\" << id;\n  readHandle->awaitNextRead(\n      evb_, [this](auto readHandle, auto id, auto streamData) {\n        readHandler(readHandle, id, std::move(streamData));\n      });\n}\n\nvoid DeviousBatonHandler::onWebTransportSessionClose(\n    folly::Optional<uint32_t> error) noexcept {\n  VLOG(4) << \"Session Close error=\"\n          << (error ? folly::to<std::string>(*error) : std::string(\"none\"));\n  devious_.reset();\n}\n\nvoid DeviousBatonHandler::onDatagram(\n    std::unique_ptr<folly::IOBuf> datagram) noexcept {\n  VLOG(4) << \"DeviousBatonHandler::\" << __func__;\n}\n\nvoid DeviousBatonHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  VLOG(4) << \"DeviousBatonHandler::\" << __func__;\n  VLOG(3) << IOBufPrinter::printHexFolly(body.get(), true);\n  folly::io::Cursor cursor(body.get());\n  auto leftToParse = body->computeChainDataLength();\n  while (leftToParse > 0) {\n    auto typeRes = quic::follyutils::decodeQuicInteger(cursor, leftToParse);\n    if (!typeRes) {\n      LOG(ERROR) << \"Failed to decode capsule type.\";\n      return;\n    }\n    auto [type, typeLen] = typeRes.value();\n    leftToParse -= typeLen;\n    auto capLengthRes =\n        quic::follyutils::decodeQuicInteger(cursor, leftToParse);\n    if (!capLengthRes) {\n      LOG(ERROR) << \"Failed to decode capsule length: type=\" << type;\n      return;\n    }\n    auto [capLength, capLengthLen] = capLengthRes.value();\n    leftToParse -= capLengthLen;\n    if (capLength > leftToParse) {\n      LOG(ERROR) << \"Not enough data for capsule: type=\" << type\n                 << \" length=\" << capLength;\n      return;\n    }\n  }\n}\n\nvoid DeviousBatonHandler::onEOM() noexcept {\n  VLOG(4) << \"DeviousBatonHandler::\" << __func__;\n  if (txn_ && !txn_->isEgressEOMSeen()) {\n    txn_->sendEOM();\n  }\n}\n\nvoid DeviousBatonHandler::onError(\n    const proxygen::HTTPException& error) noexcept {\n  VLOG(4) << \"DeviousBatonHandler::onError error=\" << error.what();\n}\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/SampleHandlers.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <algorithm>\n#include <chrono>\n#include <climits>\n#include <cmath>\n#include <folly/Conv.h>\n#include <folly/futures/Future.h>\n#include <folly/io/IOBuf.h>\n#include <functional>\n#include <mutex>\n#include <proxygen/lib/http/HTTPException.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <random>\n#include <vector>\n\n#include <folly/File.h>\n#include <folly/FileUtil.h>\n#include <folly/Format.h>\n#include <folly/Memory.h>\n#include <folly/Random.h>\n#include <folly/ThreadLocal.h>\n#include <folly/executors/GlobalExecutor.h>\n#include <folly/io/async/AsyncTimeout.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/httpserver/samples/hq/HQServer.h>\n#include <proxygen/httpserver/samples/hq/devious/DeviousBaton.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/utils/SafePathUtils.h>\n\nnamespace quic::samples {\n\n/**\n * The Dispatcher object is responsible for spawning\n * new request handlers, based on the path.\n */\nstruct HandlerParams {\n  std::string protocol;\n  uint16_t port;\n  std::string httpVersion;\n\n  HandlerParams(std::string pro, uint16_t po, std::string h)\n      : protocol(std::move(pro)), port(po), httpVersion(std::move(h)) {\n  }\n};\n\nclass Dispatcher {\n public:\n  explicit Dispatcher(HandlerParams params) : params_(std::move(params)) {\n  }\n\n  proxygen::HTTPTransactionHandler* getRequestHandler(\n      proxygen::HTTPMessage* /* msg */);\n\n  HandlerParams params_;\n};\n\nusing random_bytes_engine =\n    std::independent_bits_engine<std::default_random_engine,\n                                 CHAR_BIT,\n                                 unsigned char>;\n\nclass BaseSampleHandler : public proxygen::HTTPTransactionHandler {\n public:\n  BaseSampleHandler() = delete;\n\n  explicit BaseSampleHandler(const HandlerParams& params) : params_(params) {\n  }\n\n  void setTransaction(proxygen::HTTPTransaction* txn) noexcept override {\n    txn_ = txn;\n  }\n\n  void detachTransaction() noexcept override {\n    delete this;\n  }\n\n  void onChunkHeader(size_t /*length*/) noexcept override {\n  }\n\n  void onChunkComplete() noexcept override {\n  }\n\n  void onTrailers(\n      std::unique_ptr<proxygen::HTTPHeaders> /*trailers*/) noexcept override {\n  }\n\n  void onUpgrade(proxygen::UpgradeProtocol /*protocol*/) noexcept override {\n  }\n\n  void onEgressPaused() noexcept override {\n  }\n\n  void onEgressResumed() noexcept override {\n  }\n\n  void maybeAddAltSvcHeader(proxygen::HTTPMessage& msg) const {\n    if (params_.protocol.empty() || params_.port == 0) {\n      return;\n    }\n    msg.getHeaders().add(\n        proxygen::HTTP_HEADER_ALT_SVC,\n        fmt::format(\"{}=\\\":{}\\\"; ma=3600\", params_.protocol, params_.port));\n  }\n\n  // clang-format off\n  static const std::string& getH1QFooter() {\n    static const std::string footer(\n\" __    __  .___________.___________..______      ___ ___       ___    ______\\n\"\n\"|  |  |  | |           |           ||   _  \\\\    /  // _ \\\\     / _ \\\\  |      \\\\\\n\"\n\"|  |__|  | `---|  |----`---|  |----`|  |_)  |  /  /| | | |   | (_) | `----)  |\\n\"\n\"|   __   |     |  |        |  |     |   ___/  /  / | | | |    \\\\__, |     /  /\\n\"\n\"|  |  |  |     |  |        |  |     |  |     /  /  | |_| |  __  / /     |__|\\n\"\n\"|__|  |__|     |__|        |__|     | _|    /__/    \\\\___/  (__)/_/       __\\n\"\n\"                                                                        (__)\\n\"\n\"\\n\"\n\"\\n\"\n\"____    __    ____  __    __       ___   .___________.\\n\"\n\"\\\\   \\\\  /  \\\\  /   / |  |  |  |     /   \\\\  |           |\\n\"\n\" \\\\   \\\\/    \\\\/   /  |  |__|  |    /  ^  \\\\ `---|  |----`\\n\"\n\"  \\\\            /   |   __   |   /  /_\\\\  \\\\    |  |\\n\"\n\"   \\\\    /\\\\    /    |  |  |  |  /  _____  \\\\   |  |\\n\"\n\"    \\\\__/  \\\\__/     |__|  |__| /__/     \\\\__\\\\  |__|\\n\"\n\"\\n\"\n\"____    ____  _______     ___      .______\\n\"\n\"\\\\   \\\\  /   / |   ____|   /   \\\\     |   _  \\\\\\n\"\n\" \\\\   \\\\/   /  |  |__     /  ^  \\\\    |  |_)  |\\n\"\n\"  \\\\_    _/   |   __|   /  /_\\\\  \\\\   |      /\\n\"\n\"    |  |     |  |____ /  _____  \\\\  |  |\\\\  \\\\----.\\n\"\n\"    |__|     |_______/__/     \\\\__\\\\ | _| `._____|\\n\"\n\"\\n\"\n\" __       _______.    __  .___________.______\\n\"\n\"|  |     /       |   |  | |           |      \\\\\\n\"\n\"|  |    |   (----`   |  | `---|  |----`----)  |\\n\"\n\"|  |     \\\\   \\\\       |  |     |  |        /  /\\n\"\n\"|  | .----)   |      |  |     |  |       |__|\\n\"\n\"|__| |_______/       |__|     |__|        __\\n\"\n\"                                         (__)\\n\"\n    );\n    // clang-format on\n    return footer;\n  }\n\n  static uint32_t getQueryParamAsNumber(\n      std::unique_ptr<proxygen::HTTPMessage>& msg,\n      const std::string& name,\n      uint32_t defValue) noexcept {\n    return folly::tryTo<uint32_t>(msg->getQueryParam(name)).value_or(defValue);\n  }\n\n protected:\n  [[nodiscard]] const std::string& getHttpVersion() const {\n    return params_.httpVersion;\n  }\n\n  proxygen::HTTPMessage createHttpResponse(uint16_t status,\n                                           std::string_view message) {\n    proxygen::HTTPMessage resp;\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(status);\n    resp.setStatusMessage(message);\n    return resp;\n  }\n\n  proxygen::HTTPTransaction* txn_{nullptr};\n  const HandlerParams& params_;\n};\n\n/*\n** A handler which returns chunked responses spread over time\n** Generally used to simulat live video downloads where every frame\n** is delivered at 1/30 s (or similar) cadance.\n** Query parameters used:\n**  - keyFrame - size in bytes of the first chunk in the response\n**  - frame - size in bytes of all other chunks\n**  - segment - total time of the response in milliseconds.\n*/\nclass ChunkedHandler\n    : public BaseSampleHandler\n    , folly::DelayedDestruction {\n public:\n  explicit ChunkedHandler(const HandlerParams& params, folly::EventBase* evb)\n      : BaseSampleHandler(params), evb_(evb) {\n  }\n\n  ChunkedHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"ChunkedHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    resp.setIsChunked(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    firstFrameSize_ =\n        std::min(getQueryParamAsNumber(msg, \"keyFrame\", 5000), kMaxFrameSize);\n    otherFrameSize_ =\n        std::min(getQueryParamAsNumber(msg, \"frame\", 500), kMaxFrameSize);\n    auto segment = std::min(getQueryParamAsNumber(msg, \"segment\", 2000),\n                            kMaxSegmentLength);\n    totalChunkCount_ = segment / frameDelay_.count();\n  }\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"ChunkedHandler::onEOM\";\n    sleepFutureCallback();\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    txn_->sendAbort();\n    failed_ = true;\n  }\n\n private:\n  ~ChunkedHandler() override = default;\n\n  std::unique_ptr<folly::IOBuf> genRandBytes(uint32_t len) {\n    auto buffer = folly::IOBuf::create(len);\n    buffer->append(len);\n    std::generate(\n        buffer->writableData(), buffer->writableData() + len, std::ref(rbe_));\n    return buffer;\n  }\n\n  void sendChunkRandomData(uint32_t chunkSize) {\n    auto data = genRandBytes(chunkSize);\n    txn_->sendChunkHeader(chunkSize);\n    if (!failed_) {\n      txn_->sendBody(std::move(data));\n    }\n    if (!failed_) {\n      txn_->sendChunkTerminator();\n    }\n  }\n\n  void sleepFutureCallback() {\n    DestructorGuard destructorGuard(this);\n    uint32_t chunkSize = chunk_ == 0 ? firstFrameSize_ : otherFrameSize_;\n    if (failed_) {\n      return;\n    }\n\n    chunk_++;\n    if (chunk_ > totalChunkCount_) {\n      txn_->sendEOM();\n      return;\n    }\n    sendChunkRandomData(chunkSize);\n    sleepFuture_ =\n        folly::futures::sleep(frameDelay_).via(evb_).then([this](auto&&) {\n          sleepFutureCallback();\n        });\n  }\n\n  const uint32_t kMaxFrameSize{1000000};\n  const uint32_t kMaxSegmentLength{60000};\n  uint32_t firstFrameSize_{5000};\n  uint32_t otherFrameSize_{500};\n  std::chrono::milliseconds frameDelay_{33};\n  uint32_t totalChunkCount_{60};\n  uint32_t chunk_{0};\n  folly::EventBase* evb_;\n  folly::SemiFuture<folly::Unit> sleepFuture_;\n  bool failed_{false};\n  random_bytes_engine rbe_;\n};\n\nclass EchoHandler : public BaseSampleHandler {\n public:\n  explicit EchoHandler(const HandlerParams& params)\n      : BaseSampleHandler(params) {\n  }\n\n  EchoHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"EchoHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    sendFooter_ =\n        (msg->getHTTPVersion() == proxygen::HTTPMessage::kHTTPVersion09);\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    msg->getHeaders().forEach(\n        [&](const std::string& header, const std::string& val) {\n          resp.getHeaders().add(folly::to<std::string>(\"x-echo-\", header), val);\n        });\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    VLOG(10) << \"EchoHandler::onBody\";\n    txn_->sendBody(std::move(chain));\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"EchoHandler::onEOM\";\n    if (sendFooter_) {\n      auto& footer = getH1QFooter();\n      txn_->sendBody(folly::IOBuf::copyBuffer(footer.data(), footer.length()));\n    }\n    txn_->sendEOM();\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    txn_->sendAbort();\n  }\n\n private:\n  bool sendFooter_{false};\n};\n\nclass TransportCallbackBase\n    : public proxygen::HTTPTransactionTransportCallback {\n  void firstHeaderByteFlushed() noexcept override {\n  }\n\n  void firstByteFlushed() noexcept override {\n  }\n\n  void lastByteFlushed() noexcept override {\n  }\n\n  void trackedByteFlushed() noexcept override {\n  }\n\n  void lastByteAcked(\n      std::chrono::milliseconds /* latency */) noexcept override {\n  }\n\n  void headerBytesGenerated(\n      proxygen::HTTPHeaderSize& /* size */) noexcept override {\n  }\n\n  void headerBytesReceived(\n      const proxygen::HTTPHeaderSize& /* size */) noexcept override {\n  }\n\n  void bodyBytesGenerated(size_t /* nbytes */) noexcept override {\n  }\n\n  void bodyBytesReceived(size_t /* size */) noexcept override {\n  }\n};\n\nclass ContinueHandler : public EchoHandler {\n public:\n  explicit ContinueHandler(const HandlerParams& params) : EchoHandler(params) {\n  }\n\n  ContinueHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"ContinueHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    if (msg->getHeaders().getSingleOrEmpty(proxygen::HTTP_HEADER_EXPECT) ==\n        \"100-continue\") {\n      resp.setStatusCode(100);\n      resp.setStatusMessage(\"Continue\");\n      maybeAddAltSvcHeader(resp);\n      txn_->sendHeaders(resp);\n    }\n    EchoHandler::onHeadersComplete(std::move(msg));\n  }\n};\n\nclass RandBytesGenHandler : public BaseSampleHandler {\n public:\n  explicit RandBytesGenHandler(const HandlerParams& params)\n      : BaseSampleHandler(params) {\n  }\n\n  RandBytesGenHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    auto path = msg->getPathAsStringPiece();\n    VLOG(10) << \"RandBytesGenHandler::onHeadersComplete\";\n    VLOG(1) << \"Request path: \" << path;\n    CHECK_GE(path.size(), 1);\n    try {\n      respBodyLen_ = folly::to<uint64_t>(path.subpiece(1));\n    } catch (const folly::ConversionError&) {\n      auto errorMsg = folly::to<std::string>(\n          \"Invalid URL: cannot extract requested response-length from url \"\n          \"path: \",\n          path);\n      LOG(ERROR) << errorMsg;\n      sendError(errorMsg);\n      return;\n    }\n    if (respBodyLen_ > kMaxAllowedLength) {\n      sendError(kErrorMsg);\n      return;\n    }\n\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    if (msg->getMethod() == proxygen::HTTPMethod::GET) {\n      sendBodyInChunks();\n    }\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n    VLOG(10) << \"RandBytesGenHandler::onBody\";\n    sendBodyInChunks();\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"RandBytesGenHandler::onEOM\";\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    VLOG(10) << \"RandBytesGenHandler::onERROR\";\n    txn_->sendAbort();\n  }\n\n  void onEgressPaused() noexcept override {\n    paused_ = true;\n  }\n\n  void onEgressResumed() noexcept override {\n    paused_ = false;\n    sendBodyInChunks();\n  }\n\n private:\n  void sendBodyInChunks() {\n    if (error_) {\n      LOG(ERROR) << \"sendBodyInChunks no-op, error_=true\";\n      txn_->sendAbort();\n      return;\n    }\n    uint64_t iter = respBodyLen_ / kMaxChunkSize;\n    if (respBodyLen_ % kMaxChunkSize != 0) {\n      ++iter;\n    }\n    VLOG(10) << \"Sending response in \" << iter << \" chunks\";\n    for (uint64_t i = 0; i < iter && !paused_; i++) {\n      uint64_t chunkSize = std::fmin(kMaxChunkSize, respBodyLen_);\n      VLOG(10) << \"Sending \" << chunkSize << \" bytes of data\";\n      txn_->sendBody(genRandBytes(chunkSize));\n      respBodyLen_ -= chunkSize;\n    }\n    if (!paused_ && !eomSent_ && respBodyLen_ == 0) {\n      VLOG(10) << \"Sending response EOM\";\n      txn_->sendEOM();\n      eomSent_ = true;\n    }\n  }\n\n  std::unique_ptr<folly::IOBuf> randBytes(int len) {\n    static folly::ThreadLocal<std::vector<uint8_t>> data;\n    random_bytes_engine rbe;\n    auto previousSize = data->size();\n    if (previousSize < size_t(len)) {\n      data->resize(len);\n      std::generate(begin(*data) + previousSize, end(*data), std::ref(rbe));\n    }\n    return folly::IOBuf::wrapBuffer(folly::ByteRange(data->data(), len));\n  }\n\n  std::unique_ptr<folly::IOBuf> genRandBytes(int len) {\n    int contentLength = (len / 2) + 1;\n    auto randData = randBytes(contentLength);\n    auto hex = folly::hexlify(randData->coalesce());\n    hex.resize(len);\n    return folly::IOBuf::copyBuffer(hex);\n  }\n\n  void sendError(const std::string& errorMsg) {\n    proxygen::HTTPMessage resp;\n    resp.setStatusCode(400);\n    resp.setStatusMessage(\"Bad Request\");\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    txn_->sendBody(folly::IOBuf::copyBuffer(errorMsg));\n    txn_->sendEOM();\n    error_ = true;\n  }\n\n  const uint64_t kMaxAllowedLength{1ULL * 1024 * 1024 * 1024}; // 1 GB\n  const uint64_t kMaxChunkSize{100ULL * 1024};                 // 100 KB\n  const std::string kErrorMsg = folly::to<std::string>(\n      \"More than 1GB of data requested. \", \"Please request for smaller size.\");\n  uint64_t respBodyLen_;\n  bool paused_{false};\n  bool eomSent_{false};\n  bool error_{false};\n};\n\nclass DummyHandler : public BaseSampleHandler {\n public:\n  explicit DummyHandler(const HandlerParams& params)\n      : BaseSampleHandler(params) {\n  }\n\n  DummyHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"DummyHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    if (msg->getMethod() == proxygen::HTTPMethod::GET) {\n      txn_->sendBody(folly::IOBuf::copyBuffer(kDummyMessage));\n    }\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n    VLOG(10) << \"DummyHandler::onBody\";\n    txn_->sendBody(folly::IOBuf::copyBuffer(kDummyMessage));\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"DummyHandler::onEOM\";\n    txn_->sendEOM();\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    txn_->sendAbort();\n  }\n\n private:\n  const std::string kDummyMessage =\n      folly::to<std::string>(\"you reached mvfst.net, \",\n                             \"reach the /echo endpoint for an echo response \",\n                             \"query /<number> endpoints for a variable size \"\n                             \"response with random bytes\");\n};\n\nclass DelayHandler\n    : public BaseSampleHandler\n    , private folly::AsyncTimeout {\n public:\n  explicit DelayHandler(const HandlerParams& params, folly::EventBase* evb)\n      : BaseSampleHandler(params), AsyncTimeout(evb) {\n  }\n\n  DelayHandler() = delete;\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"DelayHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    VLOG(10) << \"DelayHandler::onHeadersComplete calling sendHeaders\";\n    txn_->sendHeaders(resp);\n\n    auto duration = getQueryParamAsNumber(msg, \"duration\", 0);\n    responseBody_ = fmt::format(\n        \"Response Body for: {} {}\", msg->getMethodString(), msg->getURL());\n    scheduleTimeout(std::chrono::milliseconds(duration));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n    VLOG(10) << \"DelayHandler::onBody\";\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"DelayHandler::onEOM\";\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    cancelTimeout();\n  }\n\n private:\n  void timeoutExpired() noexcept override {\n    txn_->sendBody(folly::IOBuf::copyBuffer(responseBody_));\n    txn_->sendEOM();\n  }\n\n  std::string responseBody_;\n};\n\nclass HealthCheckHandler : public BaseSampleHandler {\n public:\n  HealthCheckHandler(bool healthy, const HandlerParams& params)\n      : BaseSampleHandler(params), healthy_(healthy) {\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"HealthCheckHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    if (msg->getMethod() == proxygen::HTTPMethod::GET) {\n      resp.setStatusCode(healthy_ ? 200 : 400);\n      resp.setStatusMessage(healthy_ ? \"Ok\" : \"Not Found\");\n    } else {\n      resp.setStatusCode(405);\n      resp.setStatusMessage(\"Method not allowed\");\n    }\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n\n    txn_->sendBody(\n        folly::IOBuf::copyBuffer(healthy_ ? \"1-AM-ALIVE\" : \"1-AM-NOT-WELL\"));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n    VLOG(10) << \"HealthCheckHandler::onBody\";\n    assert(false);\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"HealthCheckHandler::onEOM\";\n    txn_->sendEOM();\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    txn_->sendAbort();\n  }\n\n private:\n  bool healthy_;\n};\n\nclass SimplePostHandler : public BaseSampleHandler {\n public:\n  explicit SimplePostHandler(const HandlerParams& params)\n      : BaseSampleHandler(params) {\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    VLOG(10) << \"SimplePostHandler::onHeadersComplete\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    if (msg->getMethod() != proxygen::HTTPMethod::POST) {\n      resp.setStatusCode(400);\n      resp.setStatusMessage(\"Use POST, it's in the name!\");\n      txn_->sendHeaders(resp);\n      txn_->sendEOM();\n    }\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    VLOG(10) << \"SimplePostHandler::onBody\";\n    numBodyBytesReceived_ += chain->computeChainDataLength();\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"SimplePostHandler::onEOM\";\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"Ok\");\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    txn_->sendBody(folly::IOBuf::copyBuffer(\n        \"Got \" + folly::to<std::string>(numBodyBytesReceived_) +\n        \" POST bytes\"));\n    txn_->sendEOM();\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    txn_->sendAbort();\n  }\n\n private:\n  uint64_t numBodyBytesReceived_{0};\n};\n\nclass WaitReleaseHandler : public BaseSampleHandler {\n public:\n  WaitReleaseHandler(folly::EventBase* evb, const HandlerParams& params)\n      : BaseSampleHandler(params), evb_(evb) {\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;\n\n  void sendErrorResponse(const std::string& body) {\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(400);\n    resp.setStatusMessage(\"ERROR\");\n    resp.setWantsKeepalive(false);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    txn_->sendBody(folly::IOBuf::copyBuffer(body));\n    txn_->sendEOM();\n  }\n\n  void sendOkResponse(const std::string& body, bool eom) {\n    proxygen::HTTPMessage resp;\n    VLOG(10) << \"Setting http-version to \" << getHttpVersion();\n    resp.setVersionString(getHttpVersion());\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"OK\");\n    resp.setWantsKeepalive(true);\n    resp.setIsChunked(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    txn_->sendBody(folly::IOBuf::copyBuffer(body));\n    if (eom) {\n      txn_->sendEOM();\n    }\n  }\n\n  void release() {\n    evb_->runImmediatelyOrRunInEventBaseThreadAndWait([this] {\n      txn_->sendBody(folly::IOBuf::copyBuffer(\"released\\n\"));\n      txn_->sendEOM();\n    });\n  }\n\n  void maybeCleanup();\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n    VLOG(10) << \"WaitReleaseHandler::onBody - ignoring\";\n  }\n\n  void onEOM() noexcept override {\n    VLOG(10) << \"WaitReleaseHandler::onEOM\";\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    maybeCleanup();\n    txn_->sendAbort();\n  }\n\n private:\n  static std::unordered_map<uint, WaitReleaseHandler*>& getWaitingHandlers();\n\n  static std::mutex& getMutex();\n\n  std::string path_;\n  uint32_t id_{0};\n  folly::EventBase* evb_;\n};\n\nnamespace {\nconstexpr auto kPushFileName = \"pusheen.txt\";\n};\n\nclass ServerPushHandler : public BaseSampleHandler {\n  class ServerPushTxnHandler : public proxygen::HTTPPushTransactionHandler {\n    void setTransaction(\n        proxygen::HTTPTransaction* /* txn */) noexcept override {\n    }\n\n    void detachTransaction() noexcept override {\n    }\n\n    void onError(const proxygen::HTTPException& /* err */) noexcept override {\n    }\n\n    void onEgressPaused() noexcept override {\n    }\n\n    void onEgressResumed() noexcept override {\n    }\n  };\n\n public:\n  explicit ServerPushHandler(const HandlerParams& params)\n      : BaseSampleHandler(params) {\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> /* msg */) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> /* chain */) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override;\n\n  void detachTransaction() noexcept override {\n  }\n\n private:\n  void sendPushPromise(proxygen::HTTPTransaction* /* pushTxn */,\n                       const std::string& /* path */);\n\n  void sendErrorResponse(const std::string& /* body */);\n\n  void sendPushResponse(proxygen::HTTPTransaction* /* pushTxn */,\n                        const std::string& /* url */,\n                        const std::string& /* body */,\n                        bool /* eom */);\n\n  void sendOkResponse(const std::string& /* body */, bool /* eom */);\n\n  std::string path_;\n  ServerPushTxnHandler pushTxnHandler_;\n};\n\nclass DeviousBatonHandler : public BaseSampleHandler {\n public:\n  explicit DeviousBatonHandler(const HandlerParams& params,\n                               folly::EventBase* evb)\n      : BaseSampleHandler(params), evb_(evb) {\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> /* msg */) noexcept override;\n\n  void onWebTransportBidiStream(\n      proxygen::HTTPCodec::StreamID id,\n      proxygen::WebTransport::BidiStreamHandle stream) noexcept override;\n  void onWebTransportUniStream(\n      proxygen::HTTPCodec::StreamID id,\n      proxygen::WebTransport::StreamReadHandle* readHandle) noexcept override;\n\n  void onWebTransportSessionClose(\n      folly::Optional<uint32_t> error) noexcept override;\n\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> /* chain */) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override;\n\n  void detachTransaction() noexcept override {\n  }\n\n  folly::Optional<devious::DeviousBaton> devious_;\n  void readHandler(proxygen::WebTransport::StreamReadHandle* readHandle,\n                   uint64_t id,\n                   folly::Try<proxygen::WebTransport::StreamData> streamData);\n  folly::EventBase* evb_{nullptr};\n  std::map<uint64_t, devious::DeviousBaton::BatonMessageState> streams_;\n};\n\nclass StaticFileHandler : public BaseSampleHandler {\n private:\n  StaticFileHandler(const HandlerParams& params, std::string staticRoot)\n      : BaseSampleHandler(params), staticRoot_(std::move(staticRoot)) {\n  }\n\n  folly::EventBase* evb() {\n    return CHECK_NOTNULL(folly::EventBaseManager::get()->getEventBase());\n  }\n\n public:\n  static std::shared_ptr<StaticFileHandler> make(const HandlerParams& params,\n                                                 std::string staticRoot) {\n    // since this class is accessed by two executors (cpu exec via ::readFile &\n    // evb via HttpTxn), this class owns a shared_ptr to extend its lifetime\n    // until work is finished on both executors\n    auto handler = std::shared_ptr<StaticFileHandler>(\n        new StaticFileHandler(params, std::move(staticRoot)));\n    handler->self_ = handler;\n    return handler;\n  }\n\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n    auto path = msg->getPathAsStringPiece();\n    VLOG(10) << \"StaticFileHandler::onHeadersComplete\";\n    VLOG(4) << \"Request path: \" << path;\n    if (path.contains(\"..\")) {\n      sendError(\"Path cannot contain ..\");\n      return;\n    }\n\n    auto filepath = folly::to<std::string>(staticRoot_, \"/\", path);\n    try {\n      auto safepath = proxygen::SafePath::getPath(filepath, staticRoot_, true);\n      file_ = std::make_unique<folly::File>(safepath);\n    } catch (...) {\n      auto errorMsg = folly::to<std::string>(\n          \"Invalid URL: cannot open requested file. \"\n          \"path: '\",\n          path,\n          \"'\");\n      LOG(ERROR) << errorMsg << \" file: '\" << filepath << \"'\";\n      sendError(errorMsg);\n      return;\n    }\n    proxygen::HTTPMessage resp = createHttpResponse(200, \"Ok\");\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    // use a CPU executor since read(2) of a file can block\n    folly::getUnsafeMutableGlobalCPUExecutor()->add(\n        [this, evb = evb(), self = self_]() { readFile(evb, self); });\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n  }\n\n  void onEOM() noexcept override {\n  }\n\n  void onError(const proxygen::HTTPException& /*error*/) noexcept override {\n    VLOG(10) << \"StaticFileHandler::onError\";\n    txn_->sendAbort();\n  }\n\n  void onEgressPaused() noexcept override {\n    VLOG(10) << \"StaticFileHandler::onEgressPaused\";\n    paused_ = true;\n  }\n\n  void onEgressResumed() noexcept override {\n    VLOG(10) << \"StaticFileHandler::onEgressResumed\";\n    paused_ = false;\n    folly::getUnsafeMutableGlobalCPUExecutor()->add(\n        [this, evb = evb(), self = self_]() { readFile(evb, self); });\n  }\n\n  void detachTransaction() noexcept override {\n    txn_ = nullptr;\n    self_.reset();\n  }\n\n private:\n  void sendAbort() {\n    if (txn_) {\n      txn_->sendAbort();\n    }\n  }\n  void sendBody(std::unique_ptr<folly::IOBuf> body) {\n    if (txn_) {\n      txn_->sendBody(std::move(body));\n    }\n  }\n  void sendEom() {\n    if (txn_) {\n      txn_->sendEOM();\n    }\n  }\n\n  void readFile(folly::EventBase* evb,\n                std::shared_ptr<StaticFileHandler> self) {\n    folly::IOBufQueue buf;\n    while (file_ && !paused_) {\n      // read 4k-ish chunks and foward each one to the client\n      auto data = buf.preallocate(4096, 4096);\n      auto rc = folly::readNoInt(file_->fd(), data.first, data.second);\n      if (rc < 0) {\n        // error\n        LOG(ERROR) << \"Read error=\" << rc;\n        file_.reset();\n        evb->runInEventBaseThread([this] { sendAbort(); });\n        break;\n      } else if (rc == 0) {\n        // done\n        file_.reset();\n        VLOG(4) << \"Read EOF\";\n        evb->runInEventBaseThread([this] { sendEom(); });\n        break;\n      } else {\n        buf.postallocate(rc);\n        evb->runInEventBaseThread(\n            [this, body = buf.move()]() mutable { sendBody(std::move(body)); });\n      }\n    }\n    evb->runInEventBaseThread([_ = std::move(self)] {});\n  }\n\n  void sendError(const std::string& errorMsg) {\n    proxygen::HTTPMessage resp = createHttpResponse(400, \"Bad Request\");\n    resp.setWantsKeepalive(true);\n    maybeAddAltSvcHeader(resp);\n    txn_->sendHeaders(resp);\n    txn_->sendBody(folly::IOBuf::copyBuffer(errorMsg));\n    txn_->sendEOM();\n  }\n\n  std::unique_ptr<folly::File> file_;\n  std::atomic<bool> paused_{false};\n  std::string staticRoot_;\n  std::shared_ptr<StaticFileHandler> self_;\n};\n\n} // namespace quic::samples\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/devious/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_library(proxygen_hq_devious_baton\n  SRCS\n    DeviousBaton.cpp\n  DEPS\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_folly_utils\n  EXPORTED_DEPS\n    proxygen_http_message\n    proxygen_webtransport\n    Folly::folly_io_iobuf\n)\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/devious/DeviousBaton.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"DeviousBaton.h\"\n\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\nusing namespace proxygen;\n\nnamespace {\nstd::unique_ptr<folly::IOBuf> makeBatonMessage(uint64_t padLen, uint8_t baton) {\n  auto buf = folly::IOBuf::create(padLen + 9);\n  folly::io::Appender cursor(buf.get(), 1);\n  (void)quic::encodeQuicInteger(padLen, [&](auto val) {\n    cursor.writeBE(folly::tag<decltype(val)>, val);\n  });\n  memset(buf->writableTail(), 'a', padLen);\n  buf->append(padLen);\n  buf->writableTail()[0] = baton;\n  buf->append(1);\n  return buf;\n}\n\nconstexpr uint64_t kStreamPadLen = 2000;\nconstexpr uint64_t kDatagramPadLen = 1000;\nconstexpr uint64_t kMaxParallelBatons = 10;\n} // namespace\n\nnamespace devious {\n\nfolly::Expected<folly::Unit, uint16_t> DeviousBaton::onRequest(\n    const HTTPMessage& request) {\n  if (request.getMethod() != HTTPMethod::CONNECT) {\n    LOG(ERROR) << \"Invalid method=\" << request.getMethodString();\n    return folly::makeUnexpected(uint16_t(400));\n  }\n  if (request.getPathAsStringPiece() != \"/webtransport/devious-baton\") {\n    LOG(ERROR) << \"Invalid path=\" << request.getPathAsStringPiece();\n    return folly::makeUnexpected(uint16_t(404));\n  }\n  try {\n    auto queryParams = request.getQueryParams();\n    uint64_t count = 1;\n    for (auto it : queryParams) {\n      if (it.first == \"version\") {\n        if (folly::to<uint64_t>(it.second) != 0) {\n          throw std::runtime_error(\"Unsupported version\");\n        }\n      }\n      if (it.first == \"count\") {\n        count = folly::to<uint64_t>(it.second);\n        if (count > kMaxParallelBatons) {\n          throw std::runtime_error(\"Exceed max parallel batons\");\n        }\n      }\n      if (it.first == \"baton\") {\n        batons_.push_back(folly::to<uint8_t>(it.second));\n        if (batons_.back() == 0) {\n          throw std::runtime_error(\"Invalid starting baton = 0\");\n        }\n      }\n    }\n    for (auto i = batons_.size(); i < count; i++) {\n      batons_.push_back(255 - i);\n    }\n    activeBatons_ = count;\n    return folly::unit;\n  } catch (const std::exception& ex) {\n    LOG(ERROR) << \"Invalid query parameters: \" << ex.what();\n    return folly::makeUnexpected(uint16_t(404));\n  }\n}\n\nvoid DeviousBaton::start() {\n  for (auto baton : batons_) {\n    auto handle = wt_->createUniStream();\n    if (!handle) {\n      wt_->closeSession(uint32_t(BatonSessionError::DA_YAMN));\n    }\n    auto id = handle.value()->getID();\n    wt_->writeStreamData(id,\n                         makeBatonMessage(kStreamPadLen, baton),\n                         /*fin=*/true,\n                         /*deliveryCallback=*/nullptr);\n  }\n}\n\nHTTPMessage DeviousBaton::makeRequest(uint64_t version,\n                                      uint64_t count,\n                                      std::vector<uint8_t> batons) {\n  HTTPMessage request;\n  request.setMethod(HTTPMethod::CONNECT);\n  request.setHTTPVersion(1, 1);\n  request.setUpgradeProtocol(\"webtransport\");\n  request.setURL(\"/webtransport/devious-baton\");\n  request.setQueryParam(\"version\", folly::to<std::string>(version));\n  request.setQueryParam(\"count\", folly::to<std::string>(count));\n  for (auto baton : batons) {\n    request.setQueryParam(\"baton\", folly::to<std::string>(baton));\n  }\n  activeBatons_ = count;\n  return request;\n}\n\nfolly::Expected<folly::Unit, BatonSessionError>\nDeviousBaton::onBatonMessageData(BatonMessageState& state,\n                                 std::unique_ptr<folly::IOBuf> data,\n                                 bool fin) {\n  state.bufQueue.append(std::move(data));\n  if (state.bufQueue.empty()) {\n    return folly::unit;\n  }\n  folly::io::Cursor cursor(state.bufQueue.front());\n  uint64_t consumed = 0;\n  bool underflow = false;\n  switch (state.state) {\n    case BatonMessageState::PAD_LEN: {\n      auto padLen = quic::follyutils::decodeQuicInteger(cursor);\n      if (!padLen) {\n        underflow = true;\n        break;\n      }\n      consumed += padLen->second;\n      state.paddingRemaining = padLen->first;\n      state.state = BatonMessageState::PAD;\n      [[fallthrough]];\n    }\n    case BatonMessageState::PAD: {\n      auto skipped = cursor.skipAtMost(state.paddingRemaining);\n      state.paddingRemaining -= skipped;\n      consumed += skipped;\n      if (state.paddingRemaining > 0) {\n        underflow = true;\n        break;\n      }\n      state.state = BatonMessageState::BATON;\n      [[fallthrough]];\n    }\n    case BatonMessageState::BATON: {\n      if (cursor.isAtEnd()) {\n        underflow = true;\n        break;\n      }\n      state.baton = cursor.read<uint8_t>();\n      LOG(INFO) << \"Parsed baton=\" << uint64_t(state.baton);\n      consumed += 1;\n      state.state = BatonMessageState::DONE;\n      [[fallthrough]];\n    }\n    case BatonMessageState::DONE:\n      if (!state.bufQueue.empty() && !cursor.isAtEnd()) {\n        return folly::makeUnexpected(BatonSessionError::BRUH);\n      }\n  };\n  if (underflow && fin) {\n    return folly::makeUnexpected(BatonSessionError::BRUH);\n  }\n  state.bufQueue.trimStartAtMost(consumed);\n  return folly::unit;\n}\n\nvoid DeviousBaton::onStreamData(uint64_t streamId,\n                                BatonMessageState& state,\n                                std::unique_ptr<folly::IOBuf> data,\n                                bool fin) {\n  if (state.state == BatonMessageState::DONE) {\n    // can only be a FIN\n    if (data && data->computeChainDataLength() > 0) {\n      closeSession(100);\n    }\n    return;\n  }\n  auto res = onBatonMessageData(state, std::move(data), fin);\n  if (res.hasError()) {\n    closeSession(uint32_t(res.error()));\n    return;\n  }\n  if (state.state == BatonMessageState::DONE) {\n    MessageSource arrivedOn{};\n    if (streamId & 0x2) {\n      arrivedOn = UNI;\n    } else if (bool(streamId & 0x01) == (mode_ == Mode::SERVER)) {\n      arrivedOn = SELF_BIDI;\n    } else {\n      arrivedOn = PEER_BIDI;\n    }\n    auto who = onBatonMessage(streamId, arrivedOn, state.baton);\n    if (who.hasError()) {\n      closeSession(uint32_t(who.error()));\n      return;\n    }\n    onBatonFinished(*who, /*reset=*/false);\n  }\n}\n\nfolly::Expected<DeviousBaton::WhoFinished, BatonSessionError>\nDeviousBaton::onBatonMessage(uint64_t inStreamId,\n                             MessageSource arrivedOn,\n                             uint8_t baton) {\n  if (baton % 7 == ((mode_ == Mode::SERVER) ? 0 : 1)) {\n    LOG(INFO) << \"Sending datagram on baton=\" << uint64_t(baton);\n    wt_->sendDatagram(makeBatonMessage(kDatagramPadLen, baton));\n  }\n  if (baton == 0) {\n    return PEER;\n  }\n  uint64_t outStreamId = 0;\n  switch (arrivedOn) {\n    case UNI: {\n      auto res = wt_->createBidiStream();\n      if (!res) {\n        return folly::makeUnexpected(BatonSessionError::DA_YAMN);\n      }\n      outStreamId = res->writeHandle->getID();\n      startReadFn_(res->readHandle);\n      break;\n    }\n    case PEER_BIDI:\n      outStreamId = inStreamId;\n      break;\n    case SELF_BIDI: {\n      auto res = wt_->createUniStream();\n      if (!res) {\n        return folly::makeUnexpected(BatonSessionError::DA_YAMN);\n      }\n      outStreamId = res.value()->getID();\n      break;\n    }\n  }\n  wt_->writeStreamData(outStreamId,\n                       makeBatonMessage(kStreamPadLen, baton + 1),\n                       /*fin=*/true,\n                       /*deliveryCallback=*/nullptr);\n  if (baton + 1 == 0) {\n    return WhoFinished::SELF;\n  }\n  return WhoFinished::NO_ONE;\n}\n\nvoid DeviousBaton::onBatonFinished(WhoFinished who, bool reset) {\n  if (who == WhoFinished::NO_ONE) {\n    return;\n  }\n  activeBatons_--;\n  if (activeBatons_ == 0) {\n    closeSession(uint32_t(BatonSessionError::BRUH));\n    return;\n  }\n  if (reset) {\n    resetBatons_++;\n  } else {\n    finishedBatons_++;\n  }\n  if (activeBatons_ == 0 && who == WhoFinished::PEER) {\n    if (finishedBatons_ > 0) {\n      closeSession(folly::none);\n    } else {\n      closeSession(uint32_t(BatonSessionError::GAME_OVER));\n    }\n  }\n}\n\n} // namespace devious\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/devious/DeviousBaton.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBufQueue.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <vector>\n\nnamespace devious {\n\nenum class BatonSessionError {\n  DA_YAMN = 0x01,   //\tInsufficient stream credit to continue the protocol\n  BRUH = 0x02,      //\tReceived a malformed Baton message\n  GAME_OVER = 0x03, //\tAll baton streams have been reset\n  BORED = 0x04,     //\tGot tired of waiting for the next message\n  SUS = 0x05,       //\tReceived an unexpected Baton message\n};\n\nenum class BatonStreamError {\n  IDC = 0x01,      // I don't care about this stream\n  WHATEVER = 0x02, // The peer asked for this\n  I_LIED = 0x03,   // Spontaneous reset\n};\n\nclass DeviousBaton {\n public:\n  enum class Mode { CLIENT, SERVER };\n  using StartReadFn =\n      std::function<void(proxygen::WebTransport::StreamReadHandle*)>;\n  DeviousBaton(proxygen::WebTransport* inWt, Mode mode, StartReadFn startReadFn)\n      : wt_(inWt), mode_(mode), startReadFn_(startReadFn) {\n  }\n\n  folly::Expected<folly::Unit, uint16_t> onRequest(\n      const proxygen::HTTPMessage& request);\n\n  void start();\n\n  proxygen::HTTPMessage makeRequest(uint64_t version,\n                                    uint64_t count,\n                                    std::vector<uint8_t> batons);\n\n  struct BatonMessageState {\n    enum State { PAD_LEN, PAD, BATON, DONE } state{PAD_LEN};\n    folly::IOBufQueue bufQueue;\n    uint64_t paddingRemaining;\n    uint8_t baton;\n  };\n\n  void onStreamData(uint64_t streamId,\n                    BatonMessageState& state,\n                    std::unique_ptr<folly::IOBuf> data,\n                    bool fin);\n\n  folly::Expected<folly::Unit, BatonSessionError> onBatonMessageData(\n      BatonMessageState& state, std::unique_ptr<folly::IOBuf> data, bool fin);\n\n  enum WhoFinished { SELF, PEER, NO_ONE };\n\n  void onBatonFinished(WhoFinished who, bool reset);\n\n  enum MessageSource { UNI, PEER_BIDI, SELF_BIDI };\n\n  folly::Expected<folly::Unit, proxygen::WebTransport::ErrorCode> closeSession(\n      folly::Optional<uint32_t> error) {\n    return wt_->closeSession(std::move(error));\n  }\n\n private:\n  proxygen::WebTransport* wt_{nullptr};\n  Mode mode_;\n  uint64_t activeBatons_{0};\n  uint64_t resetBatons_{0};\n  uint64_t finishedBatons_{0};\n  std::vector<uint8_t> batons_;\n  StartReadFn startReadFn_;\n\n  folly::Expected<WhoFinished, BatonSessionError> onBatonMessage(\n      uint64_t inStreamId, MessageSource arrivedOn, uint8_t baton);\n};\n\n} // namespace devious\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/devious/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET DeviousBatonTests\n  SOURCES\n    DeviousBatonTests.cpp\n  DEPENDS\n    proxygendeviousbaton\n    testmain\n)\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/devious/test/DeviousBatonTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/MoveWrapper.h>\n#include <folly/futures/Future.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/httpserver/samples/hq/devious/DeviousBaton.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n\nusing devious::DeviousBaton;\nusing proxygen::test::MockWebTransport;\nusing testing::_;\n\nstruct Message {\n  uint64_t id;\n  std::unique_ptr<folly::IOBuf> message;\n};\nfolly::SemiFuture<Message> expectSendMessage(MockWebTransport& wt) {\n  auto [promise, future] = folly::makePromiseContract<Message>();\n\n  EXPECT_CALL(wt, writeStreamData(_, _, _, _))\n      .WillOnce([&wt, promise = folly::MoveWrapper(std::move(promise))](\n                    uint64_t id,\n                    std::unique_ptr<folly::IOBuf> data,\n                    bool eof,\n                    proxygen::WebTransport::ByteEventCallback*\n                    /* deliveryCallback */) mutable\n                    -> folly::Expected<proxygen::WebTransport::FCState,\n                                       proxygen::WebTransport::ErrorCode> {\n        Message m;\n        m.id = id;\n        m.message = std::move(data);\n        promise->setValue(std::move(m));\n        EXPECT_TRUE(eof);\n        wt.cleanupStream(id);\n        return proxygen::WebTransport::FCState::UNBLOCKED;\n      })\n      .RetiresOnSaturation();\n\n  return std::move(future);\n}\n\nclass DeviousBatonTest : public testing::TestWithParam<uint8_t> {};\n\nTEST_P(DeviousBatonTest, Basic) {\n  MockWebTransport mockClientWt;\n  MockWebTransport mockServerWt;\n  mockServerWt.nextUniStreamId_++;\n  mockServerWt.nextBidiStreamId_++;\n  DeviousBaton client(&mockClientWt, DeviousBaton::Mode::CLIENT, [](auto) {});\n  DeviousBaton server(&mockServerWt, DeviousBaton::Mode::SERVER, [](auto) {});\n\n  auto req = client.makeRequest(0, 1, {GetParam()});\n  auto nextMessageFuture = expectSendMessage(mockServerWt);\n  auto res = server.onRequest(req);\n  EXPECT_FALSE(res.hasError());\n  server.start();\n  DeviousBaton* nextActor = &client;\n  MockWebTransport* nextActorWt = &mockClientWt;\n  for (uint8_t baton = GetParam(); baton != 0; baton++) {\n    EXPECT_TRUE(nextMessageFuture.isReady());\n    auto nextMessage = std::move(nextMessageFuture).get();\n    nextMessageFuture = expectSendMessage(*nextActorWt);\n    DeviousBaton::BatonMessageState state;\n    nextActor->onStreamData(\n        nextMessage.id, state, std::move(nextMessage.message), true);\n    nextActorWt->cleanupReadHandle(nextMessage.id);\n    EXPECT_EQ(state.state, DeviousBaton::BatonMessageState::DONE);\n    EXPECT_EQ(state.baton, baton);\n    if (nextActor == &client) {\n      nextActor = &server;\n      nextActorWt = &mockServerWt;\n    } else {\n      nextActor = &client;\n      nextActorWt = &mockClientWt;\n    }\n  }\n  EXPECT_FALSE(res.hasError());\n  EXPECT_TRUE(nextMessageFuture.isReady());\n  auto nextMessage = std::move(nextMessageFuture).get();\n  EXPECT_CALL(*nextActorWt, closeSession(_));\n  DeviousBaton::BatonMessageState state;\n  nextActor->onStreamData(\n      nextMessage.id, state, std::move(nextMessage.message), true);\n  nextActorWt->cleanupReadHandle(nextMessage.id);\n  if (nextActor == &client) {\n    mockServerWt.cleanupReadHandle(nextMessage.id);\n  } else {\n    mockClientWt.cleanupReadHandle(nextMessage.id);\n  }\n  EXPECT_EQ(state.state, DeviousBaton::BatonMessageState::DONE);\n  EXPECT_EQ(state.baton, 0);\n}\n\nINSTANTIATE_TEST_SUITE_P(DeviousBatonTest,\n                         DeviousBatonTest,\n                         testing::Values(250, 1, 255));\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GFlags.h>\n\n#include <folly/init/Init.h>\n\n#include <proxygen/httpserver/samples/hq/ConnIdLogger.h>\n#include <proxygen/httpserver/samples/hq/HQClient.h>\n#include <proxygen/httpserver/samples/hq/HQCommandLine.h>\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n#include <proxygen/httpserver/samples/hq/HQServerModule.h>\n#include <proxygen/lib/transport/PersistentQuicPskCache.h>\n\nusing namespace quic::samples;\n\nint main(int argc, char* argv[]) {\n  auto startTime = std::chrono::duration_cast<std::chrono::milliseconds>(\n                       std::chrono::steady_clock().now().time_since_epoch())\n                       .count();\n#if FOLLY_HAVE_LIBGFLAGS\n  // Enable glog logging to stderr by default.\n  gflags::SetCommandLineOptionWithMode(\n      \"logtostderr\", \"1\", gflags::SET_FLAGS_DEFAULT);\n#endif\n  const folly::Init init(&argc, &argv, false);\n  int err = 0;\n\n  auto expectedParams = initializeParamsFromCmdline();\n  if (expectedParams) {\n    auto& params = expectedParams.value();\n    // TODO: move sink to params\n    proxygen::ConnIdLogSink sink(params.logdir, params.logprefix);\n    if (sink.isValid()) {\n      AddLogSink(&sink);\n    } else if (!params.logdir.empty()) {\n      LOG(ERROR) << \"Cannot open \" << params.logdir;\n    }\n\n    switch (params.mode) {\n      case HQMode::SERVER:\n        startServer(std::get<HQToolServerParams>(params.params));\n        break;\n      case HQMode::CLIENT:\n        err = startClient(std::get<HQToolClientParams>(params.params));\n        break;\n      default:\n        LOG(ERROR) << \"Unknown mode specified: \";\n        return -1;\n    }\n    if (params.logRuntime) {\n      LOG(INFO) << \"Run time: \"\n                << std::chrono::duration_cast<std::chrono::milliseconds>(\n                       std::chrono::steady_clock().now().time_since_epoch())\n                           .count() -\n                       startTime\n                << \"ms\";\n    }\n    return err;\n  } else {\n    for (auto& param : expectedParams.error()) {\n      LOG(ERROR) << \"Invalid param: \" << param.name << \" \" << param.value << \" \"\n                 << param.errorMsg;\n    }\n    return -1;\n  }\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/pusheen.txt",
    "content": "Pusheen!!\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/quic-interop/Dockerfile",
    "content": "#\n# Full build image\n#\nFROM martenseemann/quic-network-simulator-endpoint:latest\n\nENV TZ=America/Los_Angeles\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\nRUN apt-get update\n\nRUN apt-get --yes --fix-missing update\n\n# Get and build proxygen with HTTP/3 support\nRUN apt-get install --yes wget net-tools iputils-ping tcpdump ethtool iperf git sudo cmake python3 libssl-dev m4 zlib1g-dev gcc-10 g++-10\nRUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100\nRUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100\nRUN update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-10 100\nRUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-10 100\nRUN mkdir proxygen\nCOPY . /proxygen\nRUN cd proxygen && ./getdeps.sh --no-tests --allow-system-packages\nRUN ldd /tmp/fbcode_builder_getdeps-ZproxygenZbuildZfbcode_builder-root/build/proxygen/proxygen/httpserver/hq | grep \"=> /\" | awk '{print $3}' > libs.txt\nRUN tar cvf libs.tar --dereference --files-from=libs.txt\n\n#\n# Minimal image\n#\nFROM martenseemann/quic-network-simulator-endpoint:latest\n# copy run script\nCOPY proxygen/httpserver/samples/hq/quic-interop/run_endpoint.sh .\nRUN chmod +x run_endpoint.sh\n\n# Copy HQ\nCOPY --from=0 /tmp/fbcode_builder_getdeps-ZproxygenZbuildZfbcode_builder-root/build/proxygen/proxygen/httpserver/hq /proxygen/_build/proxygen/bin/hq\n# Copy shared libs\nCOPY --from=0 libs.tar /\nRUN tar xvf libs.tar\nRUN rm libs.tar\n# Create the logs directory\nRUN mkdir /logs\n\nENTRYPOINT [ \"./run_endpoint.sh\" ]\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/quic-interop/README.md",
    "content": "mvfst+proxygen Docker image for use with the\n[quic-network-simulator](https://github.com/marten-seemann/quic-network-simulator/) and [quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner)\n\nBuild your own by running this after checking out the repo:\n```\ndocker build . -f proxygen/httpserver/samples/hq/quic-interop/Dockerfile\n```\nor download the latest image build from Github under the packages section of this repo.\n"
  },
  {
    "path": "proxygen/httpserver/samples/hq/quic-interop/run_endpoint.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Extra debugging ?\nset -x\nset -o nounset\n\nVERSION=1\nHQ_CLI=/proxygen/_build/proxygen/bin/hq\nPORT=443\nLOGLEVEL=2\n\n# Set up the routing needed for the simulation\n/setup.sh\n\n# Unless noted otherwise, test cases use HTTP/0.9 for file transfers.\nPROTOCOL=\"hq-interop\"\nHTTPVERSION=\"0.9\"\n\n# Default enormous flow control.\n\nCONN_FLOW_CONTROL=\"107374182\"\nSTREAM_FLOW_CONTROL=\"107374182\"\nINVOCATIONS=$(echo \"${REQUESTS}\" | tr \" \" \"\\n\" | awk -F '/' '{ print \"/\" $4 }' | paste -sd',')\nEARLYDATA=\"false\"\nPSK_FILE=\"\" # in memory psk\nif [ -n \"${TESTCASE}\" ]; then\n    case \"${TESTCASE}\" in\n        \"handshake\") ;;\n        \"multiconnect\") ;;\n        \"transfer\")\n            STREAM_FLOW_CONTROL=\"262144\"\n            CONN_FLOW_CONTROL=\"2621440\"\n            ;;\n        \"retry\")\n            exit 127\n            ;;\n        \"throughput\")\n            LOGLEVEL=1\n        ;;\n        \"resumption\")\n            INVOCATIONS=$(${INVOCATIONS//,/ })\n            PSK_FILE=\"/psk\"\n        ;;\n        \"zerortt\")\n            INVOCATIONS=$(${INVOCATIONS//,/ })\n            PSK_FILE=\"/psk\"\n            EARLYDATA=\"true\"\n        ;;\n        \"http3\")\n            PROTOCOL=\"h3\"\n            HTTPVERSION=\"1.1\"\n            ;;\n        *)\n            exit 127\n            ;;\n    esac\nfi\n\nif [ \"${ROLE}\" == \"client\" ]; then\n    # Wait for the simulator to start up.\n    /wait-for-it.sh sim:57832 -s -t 10\n    echo \"Starting QUIC client...\"\n    if [ -n \"${REQUESTS}\" ]; then\n        REQS=(\"${REQUESTS}\")\n        REQ=${REQS[0]}\n        SERVER=$(echo \"$REQ\" | cut -d'/' -f3 | cut -d':' -f1)\n\n        for INVOCATION in ${INVOCATIONS}; do\n\n          echo \"requesting files '${INVOCATION}'\"\n          ${HQ_CLI} \\\n              --mode=client \\\n              --host=\"${SERVER}\" \\\n              --port=${PORT} \\\n              --protocol=${PROTOCOL} \\\n              --httpversion=${HTTPVERSION} \\\n              --use_version=true \\\n              --quic-version=${VERSION} \\\n              --path=\"${INVOCATION}\" \\\n              --early_data=${EARLYDATA} \\\n              --psk_file=${PSK_FILE} \\\n              --conn_flow_control=${CONN_FLOW_CONTROL} \\\n              --stream_flow_control=${STREAM_FLOW_CONTROL} \\\n              --outdir=/downloads \\\n              --logdir=/logs \\\n              --qlogger_path=/logs \\\n              --v=${LOGLEVEL} 2>&1 | tee /logs/client.log\n        done\n        # This is the best way to troubleshoot.\n        # Just uncomment the line below, run the test, then enter containers with\n        # docker exec -it [client|server|sim] /bin/bash\n        #/bin/bash\n    fi\n\nelif [ \"$ROLE\" == \"server\" ]; then\n    echo \"Running QUIC server on [::]:${PORT}\"\n    ${HQ_CLI} \\\n        --mode=server \\\n        --cert=/certs/cert.pem \\\n        --key=/certs/priv.key \\\n        --conn_flow_control=${CONN_FLOW_CONTROL} \\\n        --stream_flow_control=${STREAM_FLOW_CONTROL} \\\n        --port=${PORT} \\\n        --httpversion=${HTTPVERSION} \\\n        --h2port=${PORT} \\\n        --static_root=/www \\\n        --logdir=/logs \\\n        --qlogger_path=/logs \\\n        --host=:: \\\n        --congestion=bbr \\\n        --pacing=true \\\n        --v=${LOGLEVEL} 2>&1 | tee /logs/server.log\nfi\n"
  },
  {
    "path": "proxygen/httpserver/samples/masque/MasqueClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <array>\n\n#include <folly/portability/Sockets.h>\n\n#include <folly/Random.h>\n#include <folly/ScopeGuard.h>\n#include <folly/SocketAddress.h>\n#include <folly/String.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h>\n#include <proxygen/lib/transport/ConnectUDPUtils.h>\n#include <proxygen/lib/transport/H3DatagramAsyncSocket.h>\n\nusing namespace folly;\nusing namespace proxygen;\n\nDEFINE_string(proxy_host, \"127.0.0.1\", \"MASQUE proxy hostname/IP\");\nDEFINE_int32(proxy_port, 4443, \"MASQUE proxy port\");\nDEFINE_string(target_host, \"127.0.0.1\", \"Target UDP server hostname/IP\");\nDEFINE_int32(target_port, 9999, \"Target UDP server port\");\nDEFINE_string(\n    masque_template,\n    \"https://{proxy_host}:{proxy_port}/masque?h={target_host}&p={target_port}\",\n    \"URI template for CONNECT-UDP (RFC 9298)\");\nDEFINE_string(cert, \"\", \"TLS certificate file path\");\nDEFINE_string(key, \"\", \"TLS private key file path\");\nDEFINE_int32(payload_size, 16, \"Size of test payload in bytes (max 1400)\");\nDEFINE_string(\n    extra_headers,\n    \"\",\n    \"Comma-separated key:value headers to add to the CONNECT-UDP request\");\nDEFINE_bool(ipv4_only, false, \"Force IPv4 address resolution\");\n\nconstexpr size_t kMaxReadBufferSize{1500};\nconstexpr size_t kMaxPayloadSize{1400};\n\nnamespace {\n\nfolly::SocketAddress resolveAddress(const std::string& host,\n                                    uint16_t port,\n                                    bool ipv4Only) {\n  if (ipv4Only) {\n    struct addrinfo hints = {};\n    hints.ai_family = AF_INET;\n    hints.ai_socktype = SOCK_DGRAM;\n    struct addrinfo* res = nullptr;\n    int err = getaddrinfo(host.c_str(), nullptr, &hints, &res);\n    if (err != 0 || res == nullptr) {\n      throw std::runtime_error(\n          folly::to<std::string>(\"Failed to resolve IPv4 address for '\",\n                                 host,\n                                 \"': \",\n                                 gai_strerror(err)));\n    }\n    SCOPE_EXIT {\n      freeaddrinfo(res);\n    };\n    folly::SocketAddress addr;\n    addr.setFromSockaddr(res->ai_addr, res->ai_addrlen);\n    addr.setPort(port);\n    return addr;\n  }\n  return {host, port, /* allowNameLookup */ true};\n}\n\nclass MasqueInteropClient\n    : private folly::AsyncUDPSocket::ReadCallback\n    , private folly::AsyncTimeout {\n public:\n  using folly::AsyncUDPSocket::ReadCallback::OnDataAvailableParams;\n\n  ~MasqueInteropClient() override = default;\n\n  explicit MasqueInteropClient(folly::EventBase* evb,\n                               H3DatagramAsyncSocket& socket,\n                               size_t payloadSize)\n      : folly::AsyncTimeout(evb),\n        evb_(evb),\n        socket_(socket),\n        payloadSize_(payloadSize) {\n  }\n\n  void start() {\n    CHECK(evb_->isInEventBaseThread());\n    try {\n      proxyAddress_ = resolveAddress(FLAGS_proxy_host,\n                                     static_cast<uint16_t>(FLAGS_proxy_port),\n                                     FLAGS_ipv4_only);\n      socket_.connect(proxyAddress_);\n    } catch (const std::exception& e) {\n      LOG(ERROR) << \"Failed to connect: \" << e.what();\n      exitCode_ = 1;\n      return;\n    }\n    socket_.resumeRead(this);\n\n    // Generate random payload\n    for (size_t i = 0; i < payloadSize_; ++i) {\n      sentPayload_[i] = static_cast<char>(folly::Random::rand32() & 0xff);\n    }\n\n    // Write immediately — H3DatagramAsyncSocket buffers datagrams until the\n    // transport is connected and the upstream HTTP 200 is received.\n    sendPayload();\n  }\n\n  [[nodiscard]] int exitCode() const {\n    return exitCode_;\n  }\n\n private:\n  void sendPayload() {\n    auto buf = folly::IOBuf::copyBuffer(sentPayload_.data(), payloadSize_);\n    auto hexStr = folly::hexlify(folly::ByteRange(\n        reinterpret_cast<const uint8_t*>(sentPayload_.data()), payloadSize_));\n    LOG(INFO) << \"Sending \" << payloadSize_ << \" bytes: \" << hexStr;\n    auto res = socket_.write(proxyAddress_, buf);\n    if (res < 0) {\n      LOG(ERROR) << \"Failed to write: errno=\" << errno;\n      exitCode_ = 1;\n      evb_->terminateLoopSoon();\n      return;\n    }\n    sent_ = true;\n    // Set timeout for response\n    scheduleTimeout(5000);\n  }\n\n  void timeoutExpired() noexcept override {\n    LOG(ERROR) << \"Timeout waiting for echo response\";\n    exitCode_ = 1;\n    evb_->terminateLoopSoon();\n  }\n\n  void getReadBuffer(void** buf, size_t* len) noexcept override {\n    *buf = readBuf_.data();\n    *len = kMaxReadBufferSize;\n  }\n\n  void onDataAvailable(const folly::SocketAddress& /*client*/,\n                       size_t len,\n                       bool /*truncated*/,\n                       OnDataAvailableParams) noexcept override {\n    try {\n      cancelTimeout();\n      auto hexStr = folly::hexlify(folly::ByteRange(\n          reinterpret_cast<const uint8_t*>(readBuf_.data()), len));\n      LOG(INFO) << \"Received \" << len << \" bytes: \" << hexStr;\n\n      if (len == payloadSize_ &&\n          memcmp(readBuf_.data(), sentPayload_.data(), payloadSize_) == 0) {\n        LOG(INFO) << \"Echo payload verified - exact match\";\n        exitCode_ = 0;\n        verified_ = true;\n      } else {\n        LOG(ERROR) << \"Echo payload MISMATCH!\";\n        exitCode_ = 1;\n      }\n      evb_->terminateLoopSoon();\n    } catch (const std::exception& e) {\n      LOG(ERROR) << \"Exception in onDataAvailable: \" << e.what();\n      exitCode_ = 1;\n      evb_->terminateLoopSoon();\n    }\n  }\n\n  void onReadError(const folly::AsyncSocketException& ex) noexcept override {\n    cancelTimeout();\n    LOG(ERROR) << \"Read error: \" << ex.what();\n    if (!verified_) {\n      exitCode_ = 1;\n    }\n    evb_->terminateLoopSoon();\n  }\n\n  void onReadClosed() noexcept override {\n    cancelTimeout();\n    LOG(INFO) << \"Read closed\";\n    if (!sent_ && !verified_) {\n      exitCode_ = 1;\n    }\n    evb_->terminateLoopSoon();\n  }\n\n  folly::EventBase* const evb_{nullptr};\n  H3DatagramAsyncSocket& socket_;\n  folly::SocketAddress proxyAddress_;\n  size_t payloadSize_;\n  std::array<char, kMaxPayloadSize> sentPayload_{};\n  std::array<char, kMaxReadBufferSize> readBuf_{};\n  bool sent_{false};\n  bool verified_{false};\n  int exitCode_{1};\n};\n\n} // namespace\n\nint main(int argc, char* argv[]) {\n#if FOLLY_HAVE_LIBGFLAGS\n  folly::gflags::SetCommandLineOptionWithMode(\n      \"logtostderr\", \"1\", folly::gflags::SET_FLAGS_DEFAULT);\n#endif\n  const folly::Init init(&argc, &argv, false);\n\n  try {\n    auto payloadSize = static_cast<size_t>(FLAGS_payload_size);\n    CHECK_GT(payloadSize, 0u) << \"payload_size must be > 0\";\n    CHECK_LE(payloadSize, kMaxPayloadSize)\n        << \"payload_size must be <= \" << kMaxPayloadSize;\n\n    // Expand the URI template with proxy address for the template itself,\n    // then with target host/port for the actual request\n    std::string expandedTemplate = FLAGS_masque_template;\n    // Replace {proxy_host} and {proxy_port} in the template\n    {\n      auto pos = expandedTemplate.find(\"{proxy_host}\");\n      while (pos != std::string::npos) {\n        expandedTemplate.replace(pos, 12, FLAGS_proxy_host);\n        pos = expandedTemplate.find(\"{proxy_host}\",\n                                    pos + FLAGS_proxy_host.size());\n      }\n    }\n    {\n      auto portStr = folly::to<std::string>(FLAGS_proxy_port);\n      auto pos = expandedTemplate.find(\"{proxy_port}\");\n      while (pos != std::string::npos) {\n        expandedTemplate.replace(pos, 12, portStr);\n        pos = expandedTemplate.find(\"{proxy_port}\", pos + portStr.size());\n      }\n    }\n\n    auto target =\n        expandConnectUDPTemplate(expandedTemplate,\n                                 FLAGS_target_host,\n                                 static_cast<uint16_t>(FLAGS_target_port));\n\n    LOG(INFO) << \"Extended CONNECT: :protocol=connect-udp\"\n              << \", :scheme=\" << target.scheme\n              << \", :authority=\" << target.authority\n              << \", :path=\" << target.path;\n\n    EventBase evb;\n\n    H3DatagramAsyncSocket::Options options;\n    options.mode_ = H3DatagramAsyncSocket::Mode::CLIENT;\n    options.txnTimeout_ = std::chrono::milliseconds(10000);\n    options.connectTimeout_ = std::chrono::milliseconds(3000);\n    options.rfcMode_ = true;\n    options.maxDatagramSize_ = kMaxReadBufferSize;\n    options.hostname_ = FLAGS_proxy_host;\n\n    options.httpRequest_ = std::make_unique<HTTPMessage>();\n    options.httpRequest_->setMethod(HTTPMethod::CONNECT);\n    options.httpRequest_->setUpgradeProtocol(\"connect-udp\");\n    options.httpRequest_->setSecure(true);\n    options.httpRequest_->setURL(target.path);\n    options.httpRequest_->getHeaders().set(HTTP_HEADER_HOST, target.authority);\n    options.httpRequest_->getHeaders().set(\"Capsule-Protocol\", \"?1\");\n\n    if (!FLAGS_cert.empty() && !FLAGS_key.empty()) {\n      options.certAndKey_ = std::make_pair(FLAGS_cert, FLAGS_key);\n    }\n    options.certVerifier_ =\n        std::make_unique<InsecureVerifierDangerousDoNotUseInProduction>();\n\n    if (!FLAGS_extra_headers.empty()) {\n      std::vector<folly::StringPiece> pairs;\n      folly::split(',', FLAGS_extra_headers, pairs);\n      for (auto& pair : pairs) {\n        auto colon = pair.find(':');\n        if (colon != folly::StringPiece::npos) {\n          auto key = folly::trimWhitespace(pair.subpiece(0, colon));\n          auto val = folly::trimWhitespace(pair.subpiece(colon + 1));\n          options.httpRequest_->getHeaders().set(key.str(), val.str());\n        }\n      }\n    }\n\n    H3DatagramAsyncSocket datagramSocket(&evb, options);\n    MasqueInteropClient client(&evb, datagramSocket, payloadSize);\n    client.start();\n    evb.loop();\n\n    // Clean shutdown: close the socket and drain pending callbacks before\n    // the EventBase destructor runs, to avoid use-after-free on session timers.\n    datagramSocket.close();\n    evb.loop();\n\n    return client.exitCode();\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Fatal error: \" << e.what();\n    return 1;\n  }\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/proxy/ProxyHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"ProxyHandler.h\"\n\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <proxygen/lib/utils/URL.h>\n\n#include \"ProxyStats.h\"\n\nusing namespace proxygen;\nusing std::string;\nusing std::unique_ptr;\n\nDEFINE_int32(proxy_connect_timeout, 1000, \"connect timeout in milliseconds\");\n\nnamespace {\nstatic const uint32_t kMinReadSize = 1460;\nstatic const uint32_t kMaxReadSize = 4000;\n\nstatic const uint8_t READS_SHUTDOWN = 1;\nstatic const uint8_t WRITES_SHUTDOWN = 2;\nstatic const uint8_t CLOSED = READS_SHUTDOWN | WRITES_SHUTDOWN;\n} // namespace\n\nnamespace ProxyService {\n\nProxyHandler::ProxyHandler(ProxyStats* stats, folly::HHWheelTimer* timer)\n    : stats_(stats), connector_{this, timer}, serverHandler_(*this) {\n}\n\nProxyHandler::~ProxyHandler() {\n  VLOG(4) << \"deleting ProxyHandler\";\n}\n\nvoid ProxyHandler::onRequest(std::unique_ptr<HTTPMessage> headers) noexcept {\n  // This HTTP proxy does not obey the rules in the spec, such as stripping\n  // hop-by-hop headers.  Example only!\n\n  stats_->recordRequest();\n  request_ = std::move(headers);\n  proxygen::URL url(request_->getURL());\n\n  folly::SocketAddress addr;\n  try {\n    // Note, this does a synchronous DNS lookup which is bad in event driven\n    // code\n    addr.setFromHostPort(url.getHost(), url.getPort());\n  } catch (...) {\n    ResponseBuilder(downstream_)\n        .status(503, \"Bad Gateway\")\n        .body(folly::to<string>(\"Could not parse server from URL: \",\n                                request_->getURL()))\n        .sendWithEOM();\n    return;\n  }\n\n  downstream_->pauseIngress();\n  LOG(INFO) << \"Trying to connect to \" << addr;\n  auto evb = folly::EventBaseManager::get()->getEventBase();\n  if (request_->getMethod() == HTTPMethod::CONNECT) {\n    upstreamSock_ = folly::AsyncSocket::newSocket(evb);\n    upstreamSock_->connect(this, addr, FLAGS_proxy_connect_timeout);\n  } else {\n    // A more sophisticated proxy would have a connection pool here\n    const folly::SocketOptionMap opts{\n        {{.level = SOL_SOCKET, .optname = SO_REUSEADDR}, 1}};\n    downstream_->pauseIngress();\n    connector_.connect(folly::EventBaseManager::get()->getEventBase(),\n                       addr,\n                       std::chrono::milliseconds(FLAGS_proxy_connect_timeout),\n                       opts);\n  }\n}\n\nvoid ProxyHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  DestructorGuard dg(this);\n  if (txn_) {\n    LOG(INFO) << \"Forwarding \" << ((body) ? body->computeChainDataLength() : 0)\n              << \" body bytes to server\";\n    txn_->sendBody(std::move(body));\n  } else if (upstreamSock_) {\n    upstreamEgressPaused_ = true;\n    upstreamSock_->writeChain(this, std::move(body));\n    if (upstreamEgressPaused_) {\n      downstreamIngressPaused_ = true;\n      onServerEgressPaused();\n    }\n  } else {\n    LOG(WARNING) << \"Dropping \" << ((body) ? body->computeChainDataLength() : 0)\n                 << \" body bytes to server\";\n  }\n}\n\nvoid ProxyHandler::onEOM() noexcept {\n  if (txn_) {\n    LOG(INFO) << \"Forwarding client EOM to server\";\n    txn_->sendEOM();\n  } else if (upstreamSock_) {\n    LOG(INFO) << \"Closing upgraded socket\";\n    sockStatus_ |= WRITES_SHUTDOWN;\n    upstreamSock_->shutdownWrite();\n  } else {\n    LOG(INFO) << \"Dropping client EOM to server\";\n  }\n}\n\nvoid ProxyHandler::connectSuccess(HTTPUpstreamSession* session) {\n  DestructorGuard dg(this);\n  LOG(INFO) << \"Established \" << *session;\n  session_ = std::make_unique<SessionWrapper>(session);\n  txn_ = session->newTransaction(&serverHandler_);\n  LOG(INFO) << \"Forwarding client request: \" << request_->getURL()\n            << \" to server\";\n  txn_->sendHeaders(*request_);\n  downstream_->resumeIngress();\n}\n\nvoid ProxyHandler::connectError(const folly::AsyncSocketException& ex) {\n  DestructorGuard dg(this);\n  LOG(ERROR) << \"Failed to connect: \" << folly::exceptionStr(ex);\n  if (!clientTerminated_) {\n    ResponseBuilder(downstream_).status(503, \"Bad Gateway\").sendWithEOM();\n  } else {\n    abortDownstream();\n    checkForShutdown();\n  }\n}\n\nvoid ProxyHandler::onServerHeadersComplete(\n    unique_ptr<HTTPMessage> msg) noexcept {\n  CHECK(!clientTerminated_);\n  LOG(INFO) << \"Forwarding \" << msg->getStatusCode() << \" response to client\";\n  downstream_->sendHeaders(*msg);\n}\n\nvoid ProxyHandler::onServerBody(std::unique_ptr<folly::IOBuf> chain) noexcept {\n  CHECK(!clientTerminated_);\n  LOG(INFO) << \"Forwarding \" << ((chain) ? chain->computeChainDataLength() : 0)\n            << \" body bytes to client\";\n  downstream_->sendBody(std::move(chain));\n}\n\nvoid ProxyHandler::onServerEOM() noexcept {\n  if (!clientTerminated_) {\n    LOG(INFO) << \"Forwarding server EOM to client\";\n    downstream_->sendEOM();\n  }\n}\n\nvoid ProxyHandler::detachServerTransaction() noexcept {\n  txn_ = nullptr;\n  checkForShutdown();\n}\n\nvoid ProxyHandler::onServerError(const HTTPException& error) noexcept {\n  LOG(ERROR) << \"Server error: \" << error;\n  abortDownstream();\n}\n\nvoid ProxyHandler::onServerEgressPaused() noexcept {\n  if (!clientTerminated_) {\n    downstream_->pauseIngress();\n  }\n}\n\nvoid ProxyHandler::onServerEgressResumed() noexcept {\n  if (!clientTerminated_) {\n    downstream_->resumeIngress();\n  }\n}\n\nvoid ProxyHandler::requestComplete() noexcept {\n  clientTerminated_ = true;\n  checkForShutdown();\n}\n\nvoid ProxyHandler::onError(ProxygenError err) noexcept {\n  LOG(ERROR) << \"Client error: \" << proxygen::getErrorString(err);\n  DestructorGuard dg(this);\n  clientTerminated_ = true;\n  if (txn_) {\n    LOG(ERROR) << \"Aborting server txn: \" << *txn_;\n    txn_->sendAbort();\n  } else if (upstreamSock_) {\n    upstreamSock_.reset();\n  }\n  checkForShutdown();\n}\n\nvoid ProxyHandler::onEgressPaused() noexcept {\n  if (txn_) {\n    txn_->pauseIngress();\n  } else if (upstreamSock_) {\n    upstreamSock_->setReadCB(nullptr);\n  }\n}\n\nvoid ProxyHandler::onEgressResumed() noexcept {\n  if (txn_) {\n    txn_->resumeIngress();\n  } else if (upstreamSock_) {\n    upstreamSock_->setReadCB(this);\n  }\n}\n\nvoid ProxyHandler::abortDownstream() {\n  if (!clientTerminated_) {\n    downstream_->sendAbort();\n  }\n}\n\nbool ProxyHandler::checkForShutdown() {\n  if (clientTerminated_ && !txn_ &&\n      (!upstreamSock_ || (sockStatus_ == CLOSED && !upstreamEgressPaused_))) {\n    destroy();\n    return true;\n  }\n  return false;\n}\n\nvoid ProxyHandler::connectSuccess() noexcept {\n  LOG(INFO) << \"Connected to upstream \" << upstreamSock_;\n  DestructorGuard dg(this);\n  ResponseBuilder(downstream_).status(200, \"OK\").send();\n  upstreamSock_->setReadCB(this);\n  downstream_->resumeIngress();\n}\n\nvoid ProxyHandler::connectErr(const folly::AsyncSocketException& ex) noexcept {\n  connectError(ex);\n}\n\nvoid ProxyHandler::getReadBuffer(void** bufReturn, size_t* lenReturn) {\n  std::pair<void*, uint32_t> readSpace =\n      body_.preallocate(kMinReadSize, kMaxReadSize);\n  *bufReturn = readSpace.first;\n  *lenReturn = readSpace.second;\n}\n\nvoid ProxyHandler::readDataAvailable(size_t len) noexcept {\n  body_.postallocate(len);\n  downstream_->sendBody(body_.move());\n}\n\nvoid ProxyHandler::readEOF() noexcept {\n  sockStatus_ |= READS_SHUTDOWN;\n  onServerEOM();\n}\n\nvoid ProxyHandler::readErr(const folly::AsyncSocketException& ex) noexcept {\n  DestructorGuard dg(this);\n  LOG(ERROR) << \"Server read error: \" << folly::exceptionStr(ex);\n  abortDownstream();\n  upstreamSock_.reset();\n  checkForShutdown();\n}\n\nvoid ProxyHandler::writeSuccess() noexcept {\n  DestructorGuard dg(this);\n  upstreamEgressPaused_ = false;\n  if (downstreamIngressPaused_) {\n    downstreamIngressPaused_ = false;\n    onServerEgressResumed();\n  }\n  checkForShutdown();\n}\n\nvoid ProxyHandler::writeErr(size_t /*bytesWritten*/,\n                            const folly::AsyncSocketException& ex) noexcept {\n  LOG(ERROR) << \"Server write error: \" << folly::exceptionStr(ex);\n  DestructorGuard dg(this);\n  upstreamEgressPaused_ = false;\n  abortDownstream();\n  upstreamSock_.reset();\n  checkForShutdown();\n}\n\n} // namespace ProxyService\n"
  },
  {
    "path": "proxygen/httpserver/samples/proxy/ProxyHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"SessionWrapper.h\"\n#include <folly/Memory.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/DelayedDestruction.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n\nnamespace proxygen {\nclass ResponseHandler;\n}\n\nnamespace ProxyService {\n\nclass ProxyStats;\n\nclass ProxyHandler\n    : public proxygen::RequestHandler\n    , public folly::DelayedDestruction\n    , private proxygen::HTTPConnector::Callback\n    , private folly::AsyncSocket::ConnectCallback\n    , private folly::AsyncReader::ReadCallback\n    , private folly::AsyncWriter::WriteCallback {\n public:\n  ProxyHandler(ProxyStats* stats, folly::HHWheelTimer* timer);\n\n  ~ProxyHandler() override;\n\n  void onRequest(\n      std::unique_ptr<proxygen::HTTPMessage> headers) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onUpgrade(proxygen::UpgradeProtocol /*proto*/) noexcept override {\n  }\n\n  void requestComplete() noexcept override;\n\n  void onError(proxygen::ProxygenError err) noexcept override;\n\n  void onEgressPaused() noexcept override;\n\n  void onEgressResumed() noexcept override;\n\n  void detachServerTransaction() noexcept;\n  void onServerHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept;\n  void onServerBody(std::unique_ptr<folly::IOBuf> chain) noexcept;\n  void onServerEOM() noexcept;\n  void onServerError(const proxygen::HTTPException& error) noexcept;\n  void onServerEgressPaused() noexcept;\n  void onServerEgressResumed() noexcept;\n\n private:\n  void connectSuccess(proxygen::HTTPUpstreamSession* session) override;\n  void connectError(const folly::AsyncSocketException& ex) override;\n\n  class ServerTransactionHandler : public proxygen::HTTPTransactionHandler {\n   public:\n    explicit ServerTransactionHandler(ProxyHandler& parent) : parent_(parent) {\n    }\n\n   private:\n    ProxyHandler& parent_;\n\n    void setTransaction(proxygen::HTTPTransaction* /*txn*/) noexcept override {\n      // no op\n    }\n    void detachTransaction() noexcept override {\n      parent_.detachServerTransaction();\n    }\n    void onHeadersComplete(\n        std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override {\n      parent_.onServerHeadersComplete(std::move(msg));\n    }\n\n    void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n      parent_.onServerBody(std::move(chain));\n    }\n\n    void onTrailers(\n        std::unique_ptr<proxygen::HTTPHeaders> /*trailers*/) noexcept override {\n      // ignore for now\n    }\n    void onEOM() noexcept override {\n      parent_.onServerEOM();\n    }\n    void onUpgrade(proxygen::UpgradeProtocol /*protocol*/) noexcept override {\n      // ignore for now\n    }\n\n    void onError(const proxygen::HTTPException& error) noexcept override {\n      parent_.onServerError(error);\n    }\n\n    void onEgressPaused() noexcept override {\n      parent_.onServerEgressPaused();\n    }\n    void onEgressResumed() noexcept override {\n      parent_.onServerEgressResumed();\n    }\n  };\n\n  // AsyncSocket::ConnectCallback\n  void connectSuccess() noexcept override;\n  void connectErr(const folly::AsyncSocketException& ex) noexcept override;\n\n  void getReadBuffer(void** bufReturn, size_t* lenReturn) override;\n  void readDataAvailable(size_t len) noexcept override;\n  void readEOF() noexcept override;\n  void readErr(const folly::AsyncSocketException& ex) noexcept override;\n\n  void writeSuccess() noexcept override;\n  void writeErr(size_t bytesWritten,\n                const folly::AsyncSocketException& ex) noexcept override;\n\n  void abortDownstream();\n  bool checkForShutdown();\n\n  ProxyStats* const stats_{nullptr};\n  proxygen::HTTPConnector connector_;\n  ServerTransactionHandler serverHandler_;\n  std::unique_ptr<SessionWrapper> session_;\n  proxygen::HTTPTransaction* txn_{nullptr};\n  bool clientTerminated_{false};\n\n  std::unique_ptr<proxygen::HTTPMessage> request_;\n\n  // Only for CONNECT\n  std::shared_ptr<folly::AsyncSocket> upstreamSock_;\n  uint8_t sockStatus_{0};\n  folly::IOBufQueue body_{folly::IOBufQueue::cacheChainLength()};\n  bool downstreamIngressPaused_{false};\n  bool upstreamEgressPaused_{false};\n};\n\n} // namespace ProxyService\n"
  },
  {
    "path": "proxygen/httpserver/samples/proxy/ProxyServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Memory.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\n#include \"ProxyHandler.h\"\n#include \"ProxyStats.h\"\n\nusing namespace ProxyService;\nusing namespace proxygen;\n\nusing folly::HHWheelTimer;\nusing folly::SocketAddress;\n\nusing Protocol = HTTPServer::Protocol;\n\nDEFINE_int32(http_port, 11000, \"Port to listen on with HTTP protocol\");\nDEFINE_int32(h2_port, 11002, \"Port to listen on with HTTP/2 protocol\");\nDEFINE_string(ip, \"localhost\", \"IP/Hostname to bind to\");\nDEFINE_int32(threads,\n             0,\n             \"Number of threads to listen on. Numbers <= 0 \"\n             \"will use the number of cores on this machine.\");\nDEFINE_int32(server_timeout,\n             60,\n             \"How long to wait for a server response (sec)\");\n\nclass ProxyHandlerFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* evb) noexcept override {\n    stats_.reset(new ProxyStats);\n    timer_->timer = HHWheelTimer::newTimer(\n        evb,\n        std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),\n        folly::AsyncTimeout::InternalEnum::NORMAL,\n        std::chrono::seconds(FLAGS_server_timeout));\n  }\n\n  void onServerStop() noexcept override {\n    stats_.reset();\n    timer_->timer.reset();\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new ProxyHandler(stats_.get(), timer_->timer.get());\n  }\n\n private:\n  struct TimerWrapper {\n    HHWheelTimer::UniquePtr timer;\n  };\n  folly::ThreadLocalPtr<ProxyStats> stats_;\n  folly::ThreadLocal<TimerWrapper> timer_;\n};\n\nint main(int argc, char* argv[]) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  google::InstallFailureSignalHandler();\n\n  std::vector<HTTPServer::IPConfig> IPs = {\n      {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},\n      {SocketAddress(FLAGS_ip, FLAGS_h2_port, true), Protocol::HTTP2},\n  };\n\n  if (FLAGS_threads <= 0) {\n    FLAGS_threads = folly::available_concurrency();\n    CHECK(FLAGS_threads > 0);\n  }\n\n  HTTPServerOptions options;\n  options.threads = static_cast<size_t>(FLAGS_threads);\n  options.idleTimeout = std::chrono::milliseconds(60000);\n  options.shutdownOn = {SIGINT, SIGTERM};\n  options.enableContentCompression = false;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<ProxyHandlerFactory>().build();\n  options.supportsConnect = true;\n\n  HTTPServer server(std::move(options));\n  server.bind(IPs);\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([&]() { server.start(); });\n\n  t.join();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/proxy/ProxyStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\nnamespace ProxyService {\n\n/**\n * Just some dummy class containing request count. Since we keep\n * one instance of this in each class, there is no need of\n * synchronization\n */\nclass ProxyStats {\n public:\n  virtual ~ProxyStats() = default;\n\n  // NOTE: We make the following methods `virtual` so that we can\n  //       mock them using Gmock for our C++ unit-tests. ProxyStats\n  //       is an external dependency to handler and we should be\n  //       able to mock it.\n\n  virtual void recordRequest() {\n    ++reqCount_;\n  }\n\n  virtual uint64_t getRequestCount() {\n    return reqCount_;\n  }\n\n private:\n  uint64_t reqCount_{0};\n};\n\n} // namespace ProxyService\n"
  },
  {
    "path": "proxygen/httpserver/samples/proxy/SessionWrapper.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\nnamespace ProxyService {\nclass SessionWrapper : public proxygen::HTTPSession::InfoCallback {\n private:\n  proxygen::HTTPUpstreamSession* session_{nullptr};\n\n public:\n  explicit SessionWrapper(proxygen::HTTPUpstreamSession* session)\n      : session_(session) {\n    session_->setInfoCallback(this);\n  }\n\n  ~SessionWrapper() override {\n    if (session_) {\n      session_->setInfoCallback(nullptr);\n      session_->drain();\n    }\n  }\n\n  proxygen::HTTPUpstreamSession* operator->() const {\n    return session_;\n  }\n\n  // Note: you must not start any asynchronous work from onDestroy()\n  void onDestroy(const proxygen::HTTPSessionBase&) override {\n    session_ = nullptr;\n  }\n};\n\n} // namespace ProxyService\n"
  },
  {
    "path": "proxygen/httpserver/samples/push/PushRequestHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"PushRequestHandler.h\"\n\n#include \"proxygen/httpserver/samples/push/PushStats.h\"\n#include <folly/FileUtil.h>\n#include <proxygen/httpserver/PushHandler.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nusing namespace proxygen;\n\nnamespace PushService {\n\nconst std::string kPushFileName(\"proxygen/httpserver/samples/push/pusheen.txt\");\nstd::string gPushBody;\n\n// Create a large body so we can give time for the push request to be made\nstd::string createLargeBody() {\n  std::string data = gPushBody;\n  while (data.size() < 1000 * 1000) {\n    data += gPushBody;\n  }\n  return data;\n}\n\nPushRequestHandler::PushRequestHandler(PushStats* stats) : stats_(stats) {\n  if (gPushBody.empty()) {\n    CHECK(folly::readFile(kPushFileName.c_str(), gPushBody))\n        << \"Failed to read push file=\" << kPushFileName;\n  }\n}\n\nvoid PushRequestHandler::onRequest(\n    std::unique_ptr<HTTPMessage> headers) noexcept {\n  stats_->recordRequest();\n  if (!headers->getHeaders().getSingleOrEmpty(\"X-PushIt\").empty()) {\n    const auto downstreamPush = downstream_->newPushedResponse(new PushHandler);\n    if (downstreamPush.hasError()) {\n      LOG(ERROR) << \"can't push: \" << getErrorString(downstreamPush.error());\n      return;\n    }\n    downstreamPush_ = downstreamPush.value();\n\n    if (headers->getPathAsStringPiece() == \"/requestLargePush\") {\n      LOG(INFO) << \"sending large push \";\n\n      ResponseBuilder(downstreamPush_)\n          .promise(\"/largePush\",\n                   headers->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n                   HTTPMethod::GET)\n          .send();\n\n      ResponseBuilder(downstreamPush_)\n          .status(200, \"OK\")\n          .body(createLargeBody())\n          .sendWithEOM();\n    } else {\n      LOG(INFO) << \"sending small push \";\n\n      ResponseBuilder(downstreamPush_)\n          .promise(\"/pusheen\",\n                   headers->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n                   HTTPMethod::GET)\n          .send();\n\n      ResponseBuilder(downstreamPush_)\n          .status(200, \"OK\")\n          .body(gPushBody)\n          .sendWithEOM();\n    }\n  }\n}\n\nvoid PushRequestHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  if (body_) {\n    body_->prependChain(std::move(body));\n  } else {\n    body_ = std::move(body);\n  }\n}\n\nvoid PushRequestHandler::onEOM() noexcept {\n  ResponseBuilder(downstream_)\n      .status(200, \"OK\")\n      .header(\"Request-Number\",\n              folly::to<std::string>(stats_->getRequestCount()))\n      .body(std::move(body_))\n      .sendWithEOM();\n}\n\nvoid PushRequestHandler::onUpgrade(UpgradeProtocol /*protocol*/) noexcept {\n  // handler doesn't support upgrades\n}\n\nvoid PushRequestHandler::requestComplete() noexcept {\n  delete this;\n}\n\nvoid PushRequestHandler::onError(ProxygenError /*err*/) noexcept {\n  delete this;\n}\n\n} // namespace PushService\n"
  },
  {
    "path": "proxygen/httpserver/samples/push/PushRequestHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <proxygen/httpserver/RequestHandler.h>\n\nnamespace proxygen {\nclass ResponseHandler;\n}\n\nnamespace PushService {\n\nclass PushStats;\n\nclass PushRequestHandler : public proxygen::RequestHandler {\n public:\n  explicit PushRequestHandler(PushStats* stats);\n\n  void onRequest(\n      std::unique_ptr<proxygen::HTTPMessage> headers) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onUpgrade(proxygen::UpgradeProtocol proto) noexcept override;\n\n  void requestComplete() noexcept override;\n\n  void onError(proxygen::ProxygenError err) noexcept override;\n\n private:\n  PushStats* const stats_{nullptr};\n\n  std::unique_ptr<folly::IOBuf> body_;\n\n  proxygen::ResponseHandler* downstreamPush_{nullptr};\n};\n\n} // namespace PushService\n"
  },
  {
    "path": "proxygen/httpserver/samples/push/PushServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Memory.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <folly/portability/Unistd.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\n#include \"PushRequestHandler.h\"\n#include \"PushStats.h\"\n\nusing namespace PushService;\nusing namespace proxygen;\n\nusing folly::SocketAddress;\n\nusing Protocol = HTTPServer::Protocol;\n\nDEFINE_int32(http_port, 11000, \"Port to listen on with HTTP protocol\");\nDEFINE_int32(h2_port, 11002, \"Port to listen on with HTTP/2 protocol\");\nDEFINE_string(ip, \"localhost\", \"IP/Hostname to bind to\");\nDEFINE_int32(threads,\n             0,\n             \"Number of threads to listen on. Numbers <= 0 \"\n             \"will use the number of cores on this machine.\");\n\nclass PushRequestHandlerFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n    stats_.reset(new PushStats);\n  }\n\n  void onServerStop() noexcept override {\n    stats_.reset();\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new PushRequestHandler(stats_.get());\n  }\n\n private:\n  folly::ThreadLocalPtr<PushStats> stats_;\n};\n\nint main(int argc, char* argv[]) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  google::InstallFailureSignalHandler();\n\n  std::vector<HTTPServer::IPConfig> IPs = {\n      {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},\n      {SocketAddress(FLAGS_ip, FLAGS_h2_port, true), Protocol::HTTP2},\n  };\n\n  if (FLAGS_threads <= 0) {\n    FLAGS_threads = folly::available_concurrency();\n    CHECK(FLAGS_threads > 0);\n  }\n\n  HTTPServerOptions options;\n  options.threads = static_cast<size_t>(FLAGS_threads);\n  options.idleTimeout = std::chrono::milliseconds(60000);\n  options.shutdownOn = {SIGINT, SIGTERM};\n  options.enableContentCompression = true;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<PushRequestHandlerFactory>().build();\n\n  HTTPServer server(std::move(options));\n  server.bind(IPs);\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([&]() { server.start(); });\n\n  t.join();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/push/PushStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\nnamespace PushService {\n\n/**\n * Just some dummy class containing request count. Since we keep\n * one instance of this in each class, there is no need of\n * synchronization\n */\nclass PushStats {\n public:\n  virtual ~PushStats() = default;\n\n  // NOTE: We make the following methods `virtual` so that we can\n  //       mock them using Gmock for our C++ unit-tests. PushStats\n  //       is an external dependency to handler and we should be\n  //       able to mock it.\n\n  virtual void recordRequest() {\n    ++reqCount_;\n  }\n\n  virtual uint64_t getRequestCount() {\n    return reqCount_;\n  }\n\n private:\n  uint64_t reqCount_{0};\n};\n\n} // namespace PushService\n"
  },
  {
    "path": "proxygen/httpserver/samples/push/pusheen.txt",
    "content": "* * * About Pusheen the cat. * * *\n\nBreed: Domestic shorthair\n\nCoat Type: Grey Tabby\n\nAge: All 9 lives left\n\nSex: Female\n\nPusheen the cat is an animated web comic series produced by Pusheen Corp\n\nhttp://www.pusheen.com/about\n"
  },
  {
    "path": "proxygen/httpserver/samples/static/StaticHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"StaticHandler.h\"\n\n#include <folly/FileUtil.h>\n#include <folly/executors/GlobalExecutor.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n#include <proxygen/lib/utils/SafePathUtils.h>\n\nusing namespace proxygen;\n\nnamespace StaticService {\n\n/**\n * Handles requests by serving the file named in path.  Only supports GET.\n * reads happen in a CPU thread pool since read(2) is blocking.\n * If egress pauses, file reading is also paused.\n */\n\nvoid StaticHandler::onRequest(std::unique_ptr<HTTPMessage> headers) noexcept {\n  error_ = false;\n  if (headers->getMethod() != HTTPMethod::GET) {\n    ResponseBuilder(downstream_)\n        .status(400, \"Bad method\")\n        .body(\"Only GET is supported\")\n        .sendWithEOM();\n    return;\n  }\n\n  try {\n    // + 1 to kill leading /\n    auto path = std::string(headers->getPathAsStringPiece().subpiece(1));\n    auto safepath = proxygen::SafePath::getPath(path, \"./\");\n\n    file_ = std::make_unique<folly::File>(safepath);\n  } catch (const std::system_error& ex) {\n    ResponseBuilder(downstream_)\n        .status(404, \"Not Found\")\n        .body(folly::to<std::string>(\"Could not find \",\n                                     headers->getPathAsStringPiece(),\n                                     \" ex=\",\n                                     folly::exceptionStr(ex)))\n        .sendWithEOM();\n    return;\n  }\n  ResponseBuilder(downstream_).status(200, \"Ok\").send();\n  // use a CPU executor since read(2) of a file can block\n  readFileScheduled_ = true;\n  folly::getUnsafeMutableGlobalCPUExecutor()->add(\n      [this, evb = folly::EventBaseManager::get()->getEventBase()] {\n        readFile(evb);\n      });\n}\n\nvoid StaticHandler::readFile(folly::EventBase* evb) {\n  folly::IOBufQueue buf;\n  while (file_ && !paused_) {\n    // read 4k-ish chunks and foward each one to the client\n    auto data = buf.preallocate(4000, 4000);\n    auto rc = folly::readNoInt(file_->fd(), data.first, data.second);\n    if (rc < 0) {\n      // error\n      VLOG(4) << \"Read error=\" << rc;\n      file_.reset();\n      evb->runInEventBaseThread([this] {\n        LOG(ERROR) << \"Error reading file\";\n        downstream_->sendAbort();\n      });\n      break;\n    } else if (rc == 0) {\n      // done\n      file_.reset();\n      VLOG(4) << \"Read EOF\";\n      evb->runInEventBaseThread([this] {\n        if (!error_) {\n          ResponseBuilder(downstream_).sendWithEOM();\n        }\n      });\n      break;\n    } else {\n      buf.postallocate(rc);\n      evb->runInEventBaseThread([this, body = buf.move()]() mutable {\n        if (!error_) {\n          ResponseBuilder(downstream_).body(std::move(body)).send();\n        }\n      });\n    }\n  }\n\n  // Notify the request thread that we terminated the readFile loop\n  evb->runInEventBaseThread([this] {\n    readFileScheduled_ = false;\n    if (!checkForCompletion() && !paused_) {\n      VLOG(4) << \"Resuming deferred readFile\";\n      onEgressResumed();\n    }\n  });\n}\n\nvoid StaticHandler::onEgressPaused() noexcept {\n  // This will terminate readFile soon\n  VLOG(4) << \"StaticHandler paused\";\n  paused_ = true;\n}\n\nvoid StaticHandler::onEgressResumed() noexcept {\n  VLOG(4) << \"StaticHandler resumed\";\n  paused_ = false;\n  // If readFileScheduled_, it will reschedule itself\n  if (!readFileScheduled_ && file_) {\n    readFileScheduled_ = true;\n    folly::getUnsafeMutableGlobalCPUExecutor()->add(\n        [this, evb = folly::EventBaseManager::get()->getEventBase()] {\n          readFile(evb);\n        });\n  } else {\n    VLOG(4) << \"Deferred scheduling readFile\";\n  }\n}\n\nvoid StaticHandler::onBody(std::unique_ptr<folly::IOBuf> /*body*/) noexcept {\n  // ignore, only support GET\n}\n\nvoid StaticHandler::onEOM() noexcept {\n}\n\nvoid StaticHandler::onUpgrade(UpgradeProtocol /*protocol*/) noexcept {\n  // handler doesn't support upgrades\n}\n\nvoid StaticHandler::requestComplete() noexcept {\n  finished_ = true;\n  paused_ = true;\n  checkForCompletion();\n}\n\nvoid StaticHandler::onError(ProxygenError /*err*/) noexcept {\n  error_ = true;\n  finished_ = true;\n  paused_ = true;\n  checkForCompletion();\n}\n\nbool StaticHandler::checkForCompletion() {\n  if (finished_ && !readFileScheduled_) {\n    VLOG(4) << \"deleting StaticHandler\";\n    delete this;\n    return true;\n  }\n  return false;\n}\n\n} // namespace StaticService\n"
  },
  {
    "path": "proxygen/httpserver/samples/static/StaticHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <atomic>\n#include <folly/File.h>\n#include <folly/Memory.h>\n#include <proxygen/httpserver/RequestHandler.h>\n\nnamespace proxygen {\nclass ResponseHandler;\n}\n\nnamespace StaticService {\n\nclass StaticHandler : public proxygen::RequestHandler {\n public:\n  void onRequest(\n      std::unique_ptr<proxygen::HTTPMessage> headers) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onUpgrade(proxygen::UpgradeProtocol proto) noexcept override;\n\n  void requestComplete() noexcept override;\n\n  void onError(proxygen::ProxygenError err) noexcept override;\n\n  void onEgressPaused() noexcept override;\n\n  void onEgressResumed() noexcept override;\n\n private:\n  void readFile(folly::EventBase* evb);\n  bool checkForCompletion();\n\n  std::unique_ptr<folly::File> file_;\n  bool readFileScheduled_{false};\n  std::atomic<bool> paused_{false};\n  bool finished_{false};\n  std::atomic<bool> error_{false};\n};\n\n} // namespace StaticService\n"
  },
  {
    "path": "proxygen/httpserver/samples/static/StaticServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Memory.h>\n#include <folly/executors/CPUThreadPoolExecutor.h>\n#include <folly/executors/GlobalExecutor.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <folly/portability/Unistd.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\n#include \"StaticHandler.h\"\n\nusing namespace StaticService;\nusing namespace proxygen;\n\nusing folly::SocketAddress;\n\nusing Protocol = HTTPServer::Protocol;\n\nDEFINE_int32(http_port, 11000, \"Port to listen on with HTTP protocol\");\nDEFINE_int32(h2_port, 11002, \"Port to listen on with HTTP/2 protocol\");\nDEFINE_string(ip, \"localhost\", \"IP/Hostname to bind to\");\nDEFINE_int32(threads,\n             0,\n             \"Number of threads to listen on. Numbers <= 0 \"\n             \"will use the number of cores on this machine.\");\n\nnamespace {\n\nclass StaticHandlerFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new StaticHandler;\n  }\n};\n\n} // namespace\n\nint main(int argc, char* argv[]) {\n  auto _ = folly::Init(&argc, &argv, true);\n\n  std::vector<HTTPServer::IPConfig> IPs = {\n      {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},\n      {SocketAddress(FLAGS_ip, FLAGS_h2_port, true), Protocol::HTTP2},\n  };\n\n  if (FLAGS_threads <= 0) {\n    FLAGS_threads = folly::available_concurrency();\n    CHECK_GT(FLAGS_threads, 0);\n  }\n\n  HTTPServerOptions options;\n  options.threads = static_cast<size_t>(FLAGS_threads);\n  options.idleTimeout = std::chrono::milliseconds(60000);\n  options.shutdownOn = {SIGINT, SIGTERM};\n  options.enableContentCompression = false;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<StaticHandlerFactory>().build();\n\n  auto diskIOThreadPool = std::make_shared<folly::CPUThreadPoolExecutor>(\n      FLAGS_threads,\n      std::make_shared<folly::NamedThreadFactory>(\"StaticDiskIOThread\"));\n  folly::setUnsafeMutableGlobalCPUExecutor(diskIOThreadPool);\n\n  HTTPServer server(std::move(options));\n  server.bind(IPs);\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([&]() { server.start(); });\n\n  t.join();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/httpserver/samples/websocket/WebSocketHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/samples/websocket/WebSocketHandler.h>\n\n#include <folly/io/async/EventBaseManager.h>\n\nusing namespace proxygen;\n\nnamespace websockethandler {\n\nconst std::string kWSKeyHeader = \"Sec-WebSocket-Key\";\nconst std::string kWSProtocolHeader = \"Sec-WebSocket-Protocol\";\nconst std::string kWSExtensionsHeader = \"Sec-WebSocket-Extensions\";\nconst std::string kWSAcceptHeader = \"Sec-WebSocket-Accept\";\nconst std::string kWSVersionHeader = \"Sec-WebSocket-Version\";\n\nconst std::string kWSVersion = \"13\";\nconst std::string kUpgradeTo = \"Websocket\";\n\nvoid WebSocketHandler::onRequest(\n    std::unique_ptr<HTTPMessage> request) noexcept {\n\n  VLOG(4) << \" New incoming request\" << *request;\n\n  // Check if Upgrade and Connection headers are present.\n  if (!request->getHeaders().exists(HTTP_HEADER_UPGRADE) ||\n      !request->getHeaders().exists(HTTP_HEADER_CONNECTION)) {\n    LOG(ERROR) << \" Missing Upgrade/Connection header\";\n    ResponseBuilder(downstream_).rejectUpgradeRequest();\n    return;\n  }\n\n  // Make sure we are requesting an upgrade to websocket.\n  const std::string& proto =\n      request->getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE);\n  if (!caseInsensitiveEqual(proto, kUpgradeTo)) {\n    LOG(ERROR) << \"Provided upgrade protocol: '\" << proto << \"', expected: '\"\n               << kUpgradeTo << \"'\";\n    ResponseBuilder(downstream_).rejectUpgradeRequest();\n    return;\n  }\n\n  // Build the upgrade response.\n  ResponseBuilder response(downstream_);\n  response.status(101, \"Switching Protocols\")\n      .setEgressWebsocketHeaders()\n      .header(kWSVersionHeader, kWSVersion)\n      .header(kWSProtocolHeader, \"websocketExampleProto\")\n      .send();\n}\n\nvoid WebSocketHandler::onEgressPaused() noexcept {\n  VLOG(4) << \"WebSocketHandler egress paused\";\n}\n\nvoid WebSocketHandler::onEgressResumed() noexcept {\n  VLOG(4) << \"WebSocketHandler resumed\";\n}\n\nvoid WebSocketHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {\n  VLOG(4) << \"WebsocketHandler::onBody\";\n  auto res = wsStream_->onData(std::move(body));\n  if (res.hasError()) {\n    ResponseBuilder response(downstream_);\n    response.status(400, \"Bad Request\");\n    response.sendWithEOM();\n  } else {\n    ResponseBuilder(downstream_).body(std::move(*res)).send();\n  }\n}\n\nvoid WebSocketHandler::onEOM() noexcept {\n  ResponseBuilder(downstream_).sendWithEOM();\n}\n\nvoid WebSocketHandler::onUpgrade(UpgradeProtocol /*protocol*/) noexcept {\n  VLOG(4) << \"WebSocketHandler onUpgrade\";\n  wsStream_ = std::make_unique<WebSocketStream>();\n}\n\nvoid WebSocketHandler::requestComplete() noexcept {\n  VLOG(4) << \" WebSocketHandler::requestComplete\";\n  delete this;\n}\n\nvoid WebSocketHandler::onError(ProxygenError err) noexcept {\n  VLOG(4) << \" WebSocketHandler::onError: \" << err;\n  delete this;\n}\n\nfolly::Expected<std::unique_ptr<folly::IOBuf>,\n                WebSocketStream::WebSocketStreamError>\nWebSocketStream::onData(std::unique_ptr<folly::IOBuf> chain) {\n  // Parse websocket framing here etc.\n  VLOG(4) << \"WebSocketStream::onData: \" << chain->clone()->moveToFbString();\n  // We just echo the bytes back.\n  return std::move(chain);\n}\n\n} // namespace websockethandler\n"
  },
  {
    "path": "proxygen/httpserver/samples/websocket/WebSocketHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <proxygen/httpserver/RequestHandler.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n\nnamespace proxygen {\nclass ResponseHandler;\n}\n\nnamespace websockethandler {\n\n/*\n * Websocket stream parser.\n */\nclass WebSocketStream {\n public:\n  enum class WebSocketStreamError {};\n  folly::Expected<std::unique_ptr<folly::IOBuf>, WebSocketStreamError> onData(\n      std::unique_ptr<folly::IOBuf> chain);\n};\n\n/*\n * Websocket acceptor.\n */\nclass WebSocketHandler : public proxygen::RequestHandler {\n public:\n  void onRequest(\n      std::unique_ptr<proxygen::HTTPMessage> request) noexcept override;\n\n  void onBody(std::unique_ptr<folly::IOBuf> body) noexcept override;\n\n  void onEOM() noexcept override;\n\n  void onUpgrade(proxygen::UpgradeProtocol proto) noexcept override;\n\n  void requestComplete() noexcept override;\n\n  void onError(proxygen::ProxygenError err) noexcept override;\n\n  void onEgressPaused() noexcept override;\n\n  void onEgressResumed() noexcept override;\n\n private:\n  std::unique_ptr<WebSocketStream> wsStream_;\n};\n\n} // namespace websockethandler\n"
  },
  {
    "path": "proxygen/httpserver/samples/websocket/main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/executors/CPUThreadPoolExecutor.h>\n#include <folly/executors/GlobalExecutor.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/RequestHandlerFactory.h>\n\n#include <proxygen/httpserver/samples/websocket/WebSocketHandler.h>\n\nusing namespace websockethandler;\nusing namespace proxygen;\n\nusing folly::SocketAddress;\n\nusing Protocol = HTTPServer::Protocol;\n\nDEFINE_int32(http_port, 11000, \"Port to listen on\");\nDEFINE_string(ip, \"localhost\", \"IP/Hostname to bind to\");\nDEFINE_int32(threads,\n             0,\n             \"Number of threads to listen on. Numbers <= 0 \"\n             \"will use the number of cores on this machine.\");\n\nnamespace {\n\nclass WebSocketHandlerFactory : public RequestHandlerFactory {\n public:\n  void onServerStart(folly::EventBase* /*evb*/) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new WebSocketHandler;\n  }\n};\n\n} // namespace\n\nint main(int argc, char* argv[]) {\n  const folly::Init init(&argc, &argv, true);\n\n  std::vector<HTTPServer::IPConfig> IPs = {\n      {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},\n  };\n\n  if (FLAGS_threads <= 0) {\n    FLAGS_threads = folly::available_concurrency();\n    CHECK_GT(FLAGS_threads, 0);\n  }\n\n  HTTPServerOptions options;\n  options.threads = static_cast<size_t>(FLAGS_threads);\n  options.idleTimeout = std::chrono::milliseconds(60000);\n  options.shutdownOn = {SIGINT, SIGTERM};\n  options.handlerFactories =\n      RequestHandlerChain().addThen<WebSocketHandlerFactory>().build();\n  options.supportsConnect = true;\n\n  auto ioThreadPool = std::make_shared<folly::CPUThreadPoolExecutor>(\n      FLAGS_threads,\n      std::make_shared<folly::NamedThreadFactory>(\"WebSocketServerIOThread\"));\n  folly::setCPUExecutor(ioThreadPool);\n\n  HTTPServer server(std::move(options));\n  server.bind(IPs);\n\n  // Start HTTPServer mainloop in a separate thread\n  std::thread t([&]() { server.start(); });\n\n  LOG(INFO) << \"Started websocket server\";\n\n  t.join();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/httpserver/tests/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET HTTPServerTests\n  SOURCES\n    HTTPServerTest.cpp\n    RequestHandlerAdaptorTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n    proxygenhttpserver\n    proxygencurl\n    testmain\n)\n"
  },
  {
    "path": "proxygen/httpserver/tests/HTTPServerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <latch>\n#include <memory>\n\n#include <folly/FileUtil.h>\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/io/async/AsyncServerSocket.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/logging/xlog.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <folly/ssl/OpenSSLCertUtils.h>\n#include <folly/ssl/OpenSSLPtrTypes.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <folly/testing/TestUtil.h>\n#include <proxygen/httpclient/samples/curl/CurlClient.h>\n#include <proxygen/httpserver/HTTPServer.h>\n#include <proxygen/httpserver/ResponseBuilder.h>\n#include <proxygen/httpserver/ScopedHTTPServer.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n#include <proxygen/lib/utils/TestUtils.h>\n#include <wangle/acceptor/Acceptor.h>\n#include <wangle/acceptor/ServerSocketConfig.h>\n#include <wangle/client/ssl/SSLSessionCallbacks.h>\n\nusing namespace folly;\nusing namespace folly::ssl;\nusing namespace proxygen;\nusing namespace CurlService;\n\nnamespace {\n\nconst std::string kTestDir = getContainingDirectory(XLOG_FILENAME).str();\n\n}\n\nclass ServerThread {\n private:\n  std::latch barrier_{2};\n  std::thread t_;\n  HTTPServer* server_{nullptr};\n\n public:\n  explicit ServerThread(HTTPServer* server) : server_(server) {\n  }\n  ~ServerThread() {\n    if (server_) {\n      server_->stop();\n    }\n    t_.join();\n  }\n\n  bool start() {\n    bool throws = false;\n    t_ = std::thread([&]() {\n      server_->start([&]() { barrier_.count_down(); },\n                     [&](std::exception_ptr /*ex*/) {\n                       throws = true;\n                       server_ = nullptr;\n                       barrier_.count_down();\n                     });\n    });\n    barrier_.arrive_and_wait();\n    return !throws;\n  }\n};\n\n/**\n * Similar to ServerThread except it waits on a folly::Baton before exit the\n * thread.\n *\n * The reason you need this is that when start() is run on one thread, the main\n * EVB inside a HTTPServer is owned by a threadlocal which dies when thread\n * exits, which is a time point you don't control. So if you have code that\n * requires the EVB doesn't die (for example, the TestRepeatStopCalls test\n * case), you'd want to delay the thread exit point until you are done with your\n * stop() calls.\n *\n * A better solution would be to change the EVB ownership inside HTTPServer.\n */\nclass WaitableServerThread {\n private:\n  std::latch barrier_{2};\n  std::thread t_;\n  HTTPServer* server_{nullptr};\n  folly::Baton<true, std::atomic> baton_;\n\n public:\n  explicit WaitableServerThread(HTTPServer* server) : server_(server) {\n  }\n\n  ~WaitableServerThread() {\n    if (server_) {\n      server_->stop();\n    }\n    t_.join();\n  }\n\n  bool start(\n      std::function<std::shared_ptr<wangle::AcceptorFactory>(\n          HTTPServer::AcceptorFactoryConfig)> getAcceptorFactory = nullptr,\n      std::shared_ptr<folly::IOThreadPoolExecutor> ioExecutor = nullptr) {\n    bool throws = false;\n    t_ = std::thread([&]() {\n      server_->start([&]() { barrier_.count_down(); },\n                     [&](std::exception_ptr /*ex*/) {\n                       throws = true;\n                       server_ = nullptr;\n                       barrier_.count_down();\n                     },\n                     getAcceptorFactory,\n                     ioExecutor);\n      baton_.wait();\n    });\n    barrier_.arrive_and_wait();\n    return !throws;\n  }\n\n  void exitThread() {\n    baton_.post();\n  }\n};\n\nTEST(MultiBind, HandlesListenFailures) {\n  SocketAddress addr(\"127.0.0.1\", 0);\n\n  auto evb = EventBaseManager::get()->getEventBase();\n  AsyncServerSocket::UniquePtr socket(new AsyncServerSocket(evb));\n  socket->bind(addr);\n\n  // Get the ephemeral port\n  socket->getAddress(&addr);\n  int port = addr.getPort();\n\n  std::vector<HTTPServer::IPConfig> ips = {\n      {folly::SocketAddress(\"127.0.0.1\", port), HTTPServer::Protocol::HTTP}};\n\n  HTTPServerOptions options;\n  options.threads = 4;\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n\n  // We have to bind both the sockets before listening on either\n  server->bind(ips);\n\n  // On kernel 2.6 trying to listen on a FD that another socket\n  // has bound to fails. While in kernel 3.2 only when one socket tries\n  // to listen on a FD that another socket is listening on fails.\n  try {\n    socket->listen(1024);\n  } catch (const std::exception&) {\n    return;\n  }\n\n  ServerThread st(server.get());\n  EXPECT_FALSE(st.start());\n}\n\nTEST(HttpServerStartStop, TestRepeatStopCalls) {\n  HTTPServerOptions options;\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<WaitableServerThread>(server.get());\n  EXPECT_TRUE(st->start());\n\n  server->stop();\n  // Calling stop again should be benign.\n  server->stop();\n  // Let the WaitableServerThread exit\n  st->exitThread();\n}\n\nTEST(HttpServerStartStop, TestUseExistingIoExecutor) {\n  HTTPServerOptions options;\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto ioExecutor = std::make_shared<folly::IOThreadPoolExecutor>(1);\n  auto st = std::make_unique<WaitableServerThread>(server.get());\n  EXPECT_TRUE(\n      st->start(/*acceptorFactory=*/nullptr, /*ioExecutor=*/ioExecutor));\n\n  server->stop();\n  // Let the WaitableServerThread exit\n  st->exitThread();\n}\n\nclass MockRequestHandlerFactory : public RequestHandlerFactory {\n public:\n  MOCK_METHOD((void), onServerStart, (folly::EventBase*), (noexcept));\n  MOCK_METHOD((void), onServerStop, (), (noexcept));\n  MOCK_METHOD((RequestHandler*),\n              onRequest,\n              (RequestHandler*, HTTPMessage*),\n              (noexcept));\n};\n\nTEST(HttpServerStartStop, TestZeroThreadsMeansNumCPUs) {\n  // Create a handler factory whose callback will\n  // be called for each worker thread.\n  std::unique_ptr<MockRequestHandlerFactory> handlerFactory =\n      std::make_unique<MockRequestHandlerFactory>();\n  MockRequestHandlerFactory* rawHandlerFactory = handlerFactory.get();\n\n  HTTPServerOptions options;\n  options.threads = 0;\n  options.handlerFactories.push_back(std::move(handlerFactory));\n\n  // threads = 0 should start num of CPUs threads\n  // each calling the handlerFactory onServerStart()\n  EXPECT_CALL(*rawHandlerFactory, onServerStart(testing::_))\n      .Times(folly::available_concurrency());\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<WaitableServerThread>(server.get());\n  EXPECT_TRUE(st->start());\n\n  server->stop();\n  // Let the WaitableServerThread exit\n  st->exitThread();\n}\n\nclass AcceptorFactoryForTest : public wangle::AcceptorFactory {\n public:\n  std::shared_ptr<wangle::Acceptor> newAcceptor(\n      folly::EventBase* /*eventBase*/) override {\n    auto config = std::make_shared<wangle::ServerSocketConfig>();\n    auto acc = std::make_shared<wangle::Acceptor>(std::move(config));\n    return acc;\n  }\n};\n\nTEST(HttpServerStartStop, TestUseExistingAcceptorFactory) {\n  HTTPServerOptions options;\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto getAcceptorFactory = [](auto) {\n    return std::make_shared<AcceptorFactoryForTest>();\n  };\n  auto st = std::make_unique<WaitableServerThread>(server.get());\n  EXPECT_TRUE(st->start(getAcceptorFactory));\n\n  server->stop();\n  // Let the WaitableServerThread exit\n  st->exitThread();\n}\n\n// Make an SSL connection to the server\nclass Cb : public folly::AsyncSocket::ConnectCallback {\n public:\n  explicit Cb(folly::AsyncSSLSocket* sock) : sock_(sock) {\n  }\n  void connectSuccess() noexcept override {\n    success = true;\n    if (auto cert = sock_->getPeerCertificate()) {\n      // keeps this alive until Cb is destroyed, even if sock is closed\n      peerCert_ = folly::OpenSSLTransportCertificate::tryExtractX509(cert);\n    }\n  }\n\n  void connectErr(const folly::AsyncSocketException&) noexcept override {\n    success = false;\n  }\n\n  const X509* getPeerCert() {\n    if (!peerCert_) {\n      return nullptr;\n    }\n    return peerCert_.get();\n  }\n\n  bool success{false};\n  folly::AsyncSSLSocket* sock_{nullptr};\n  folly::ssl::X509UniquePtr peerCert_{nullptr};\n};\n\nwangle::SSLContextConfig getSslContextConfig(bool useMultiCA) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\n      kTestDir + \"certs/test_cert1.pem\", kTestDir + \"certs/test_key1.pem\", \"\");\n  if (useMultiCA) {\n    sslCfg.clientCAFiles =\n        std::vector<std::string>{kTestDir + \"/certs/ca_cert.pem\",\n                                 kTestDir + \"/certs/client_ca_cert.pem\"};\n  } else {\n    sslCfg.clientCAFile = kTestDir + \"/certs/ca_cert.pem\";\n  }\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::IF_PRESENTED;\n  return sslCfg;\n}\n\nTEST(SSL, SSLTest) {\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  wangle::SSLContextConfig sslCfg = getSslContextConfig(false);\n  cfg.sslConfigs.push_back(sslCfg);\n\n  HTTPServerOptions options;\n  options.threads = 4;\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n\n  std::vector<HTTPServer::IPConfig> ips{cfg};\n  server->bind(ips);\n\n  ServerThread st(server.get());\n  EXPECT_TRUE(st.start());\n\n  folly::EventBase evb;\n  auto ctx = std::make_shared<SSLContext>();\n  folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n  Cb cb(sock.get());\n  sock->connect(&cb, server->addresses().front().address, 1000);\n  evb.loop();\n  EXPECT_TRUE(cb.success);\n}\n\nTEST(SSL, SSLTestWithMultiCAs) {\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  wangle::SSLContextConfig sslCfg = getSslContextConfig(true);\n  cfg.sslConfigs.push_back(sslCfg);\n\n  HTTPServerOptions options;\n  options.threads = 4;\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n\n  std::vector<HTTPServer::IPConfig> ips{cfg};\n  server->bind(ips);\n\n  ServerThread st(server.get());\n  EXPECT_TRUE(st.start());\n\n  folly::EventBase evb;\n  auto ctx = std::make_shared<SSLContext>();\n  folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n  Cb cb(sock.get());\n  sock->connect(&cb, server->addresses().front().address, 1000);\n  evb.loop();\n  EXPECT_TRUE(cb.success);\n}\n\n/**\n * A dummy filter to make RequestHandlerChain longer.\n */\nclass DummyFilterFactory : public RequestHandlerFactory {\n public:\n  class DummyFilter : public Filter {\n   public:\n    explicit DummyFilter(RequestHandler* upstream) : Filter(upstream) {\n    }\n  };\n\n  RequestHandler* onRequest(RequestHandler* h, HTTPMessage*) noexcept override {\n    return new DummyFilter(h);\n  }\n\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n  void onServerStop() noexcept override {\n  }\n};\n\nclass TestHandlerFactory : public RequestHandlerFactory {\n public:\n  class TestHandler : public RequestHandler {\n    void onRequest(std::unique_ptr<HTTPMessage>) noexcept override {\n    }\n    void onBody(std::unique_ptr<folly::IOBuf>) noexcept override {\n    }\n    void onUpgrade(UpgradeProtocol) noexcept override {\n    }\n\n    void onEOM() noexcept override {\n      std::string certHeader;\n      auto txn = CHECK_NOTNULL(downstream_->getTransaction());\n      auto& transport = txn->getTransport();\n      if (auto cert =\n              transport.getUnderlyingTransport()->getPeerCertificate()) {\n        auto x509 = folly::OpenSSLTransportCertificate::tryExtractX509(cert);\n        if (x509) {\n          certHeader = OpenSSLCertUtils::getCommonName(*x509).value_or(\"\");\n        }\n      }\n      ResponseBuilder(downstream_)\n          .status(200, \"OK\")\n          .header(\"X-Client-CN\", certHeader)\n          .body(IOBuf::copyBuffer(\"hello\"))\n          .sendWithEOM();\n    }\n\n    void requestComplete() noexcept override {\n      delete this;\n    }\n\n    void onError(ProxygenError) noexcept override {\n      delete this;\n    }\n  };\n\n  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {\n    return new TestHandler();\n  }\n\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n  void onServerStop() noexcept override {\n  }\n};\n\nstd::pair<std::unique_ptr<HTTPServer>, std::unique_ptr<ServerThread>>\nsetupServer(bool allowInsecureConnectionsOnSecureServer = false,\n            folly::Optional<wangle::TLSTicketKeySeeds> seeds = folly::none) {\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\n      kTestDir + \"certs/test_cert1.pem\", kTestDir + \"certs/test_key1.pem\", \"\");\n  sslCfg.clientCAFile = kTestDir + \"/certs/ca_cert.pem\";\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::IF_PRESENTED;\n  cfg.sslConfigs.push_back(sslCfg);\n  cfg.allowInsecureConnectionsOnSecureServer =\n      allowInsecureConnectionsOnSecureServer;\n  cfg.ticketSeeds = seeds;\n\n  HTTPServerOptions options;\n  options.threads = 4;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<TestHandlerFactory>().build();\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n\n  std::vector<HTTPServer::IPConfig> ips{cfg};\n  server->bind(ips);\n\n  auto st = std::make_unique<ServerThread>(server.get());\n  EXPECT_TRUE(st->start());\n  return std::make_pair(std::move(server), std::move(st));\n}\n\nTEST(SSL, TestAllowInsecureOnSecureServer) {\n  std::unique_ptr<HTTPServer> server;\n  std::unique_ptr<ServerThread> st;\n  std::tie(server, st) = setupServer(true);\n\n  folly::EventBase evb;\n  URL url(folly::to<std::string>(\n      \"http://localhost:\", server->addresses().front().address.getPort()));\n  HTTPHeaders headers;\n  CurlClient curl(&evb, HTTPMethod::GET, url, nullptr, headers, \"\");\n  curl.setFlowControlSettings(64 * 1024);\n  curl.setLogging(false);\n  HHWheelTimer::UniquePtr timer{new HHWheelTimer(\n      &evb,\n      std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      AsyncTimeout::InternalEnum::NORMAL,\n      std::chrono::milliseconds(1000))};\n  HTTPConnector connector(&curl, timer.get());\n  connector.connect(&evb,\n                    server->addresses().front().address,\n                    std::chrono::milliseconds(1000));\n  evb.loop();\n  auto response = curl.getResponse();\n  EXPECT_EQ(200, response->getStatusCode());\n}\n\nTEST(SSL, DisallowInsecureOnSecureServer) {\n  std::unique_ptr<HTTPServer> server;\n  std::unique_ptr<ServerThread> st;\n  std::tie(server, st) = setupServer(false);\n\n  folly::EventBase evb;\n  URL url(folly::to<std::string>(\n      \"http://localhost:\", server->addresses().front().address.getPort()));\n  HTTPHeaders headers;\n  CurlClient curl(&evb, HTTPMethod::GET, url, nullptr, headers, \"\");\n  curl.setFlowControlSettings(64 * 1024);\n  curl.setLogging(false);\n  HHWheelTimer::UniquePtr timer{new HHWheelTimer(\n      &evb,\n      std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      AsyncTimeout::InternalEnum::NORMAL,\n      std::chrono::milliseconds(1000))};\n  HTTPConnector connector(&curl, timer.get());\n  connector.connect(&evb,\n                    server->addresses().front().address,\n                    std::chrono::milliseconds(1000));\n  evb.loop();\n  auto response = curl.getResponse();\n  EXPECT_EQ(nullptr, response);\n}\n\nconstexpr size_t kBufferSize = 4096;\nconst std::string kSessionCacheContext{\"some random string\"};\n\n// A dummy callback to handle session ticket reads. With TLS 1.3, which the\n// resumption tests are configured to use, the session ticket(s) arrives after\n// the handshake is complete. We can use instances of this class to read from\n// the socket, while the corresponding EventBase is looping, and eventually\n// receive the parsed session.\nstruct SSLSessionReadCallback\n    : public wangle::SSLSessionCallbacks\n    , public folly::AsyncTransport::ReadCallback {\n  SSLSessionReadCallback() {\n    readBuffer_ = folly::IOBuf::create(kBufferSize);\n  }\n  void setSSLSession(\n      const std::string& /* ignore */,\n      folly::ssl::SSLSessionUniquePtr sessionPtr) noexcept override {\n    session = std::move(sessionPtr);\n    // without a read callback, the event base will stop looping\n    if (socket) {\n      socket->setReadCB(nullptr);\n    }\n  }\n  [[nodiscard]] folly::ssl::SSLSessionUniquePtr getSSLSession(\n      const std::string&) const noexcept override {\n    return nullptr;\n  }\n  bool removeSSLSession(const std::string&) noexcept override {\n    return true;\n  }\n  void readDataAvailable(size_t) noexcept override {\n  }\n  void getReadBuffer(void** buf, size_t* lenReturn) override {\n    *buf = readBuffer_->writableData();\n    *lenReturn = kBufferSize;\n  }\n  void readEOF() noexcept override {\n  }\n  void readErr(const folly::AsyncSocketException&) noexcept override {\n  }\n  folly::AsyncSocket* socket{nullptr};\n  folly::ssl::SSLSessionUniquePtr session;\n\n private:\n  std::unique_ptr<folly::IOBuf> readBuffer_;\n};\n\nTEST(SSL, TestResumptionWithTicketsTLS12) {\n  std::unique_ptr<HTTPServer> server;\n\n  std::unique_ptr<ServerThread> st;\n  wangle::TLSTicketKeySeeds seeds;\n  seeds.currentSeeds.push_back(hexlify(std::string(32, 'a')));\n  std::tie(server, st) = setupServer(false, seeds);\n\n  folly::EventBase evb;\n  auto ctx =\n      std::make_shared<SSLContext>(folly::SSLContext::SSLVersion::TLSv1_2);\n  // explicitly disable TLS 1.3\n  ctx->disableTLS13();\n  ctx->setSessionCacheContext(kSessionCacheContext);\n  SSLSessionReadCallback sessionCb;\n  wangle::SSLSessionCallbacks::attachCallbacksToContext(ctx.get(), &sessionCb);\n  folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n  sock->setSessionKey(kSessionCacheContext);\n  Cb cb(sock.get());\n  sock->connect(&cb, server->addresses().front().address, 1000);\n  // In TLS 1.2, the session arrives during the handshake/connect. We don't need\n  // to register a read callback. Once the connect completes, there won't be any\n  // events to process and the loop will exit.\n  evb.loop();\n  ASSERT_TRUE(cb.success);\n  ASSERT_NE(sessionCb.session.get(), nullptr);\n  ASSERT_FALSE(sock->getSSLSessionReused());\n\n  folly::AsyncSSLSocket::UniquePtr sock2(new folly::AsyncSSLSocket(ctx, &evb));\n  auto sslSession = std::move(sessionCb.session);\n  ASSERT_NE(sslSession.get(), nullptr);\n  sock2->setRawSSLSession(std::move(sslSession));\n  Cb cb2(sock2.get());\n  sock2->connect(&cb2, server->addresses().front().address, 1000);\n  evb.loop();\n  ASSERT_TRUE(cb2.success);\n  ASSERT_TRUE(sock2->getSSLSessionReused());\n}\n\nTEST(SSL, TestResumptionWithTickets) {\n  std::unique_ptr<HTTPServer> server;\n\n  std::unique_ptr<ServerThread> st;\n  wangle::TLSTicketKeySeeds seeds;\n  seeds.currentSeeds.push_back(hexlify(std::string(32, 'a')));\n  std::tie(server, st) = setupServer(false, seeds);\n\n  folly::EventBase evb;\n  auto ctx = std::make_shared<SSLContext>();\n  ctx->setSessionCacheContext(kSessionCacheContext);\n  SSLSessionReadCallback sessionCb;\n  wangle::SSLSessionCallbacks::attachCallbacksToContext(ctx.get(), &sessionCb);\n  folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n  sock->setSessionKey(kSessionCacheContext);\n  Cb cb(sock.get());\n  sock->connect(&cb, server->addresses().front().address, 1000);\n  // For TLS 1.3, we need the read callback so that we can read the\n  // NewSessionTicket message\n  sock->setReadCB(&sessionCb);\n  // attach the socket so session callback can unregister read callback\n  sessionCb.socket = sock.get();\n  evb.loop();\n  ASSERT_TRUE(cb.success);\n  ASSERT_NE(sessionCb.session.get(), nullptr);\n  ASSERT_FALSE(sock->getSSLSessionReused());\n\n  folly::AsyncSSLSocket::UniquePtr sock2(new folly::AsyncSSLSocket(ctx, &evb));\n  auto sslSession = std::move(sessionCb.session);\n  ASSERT_NE(sslSession.get(), nullptr);\n  sock2->setRawSSLSession(std::move(sslSession));\n  Cb cb2(sock2.get());\n  sock2->connect(&cb2, server->addresses().front().address, 1000);\n  evb.loop();\n  ASSERT_TRUE(cb2.success);\n  ASSERT_TRUE(sock2->getSSLSessionReused());\n}\n\nTEST(SSL, TestResumptionAfterUpdateFails) {\n  std::unique_ptr<HTTPServer> server;\n  std::unique_ptr<ServerThread> st;\n  wangle::TLSTicketKeySeeds seeds;\n  seeds.currentSeeds.push_back(hexlify(std::string(32, 'a')));\n  std::tie(server, st) = setupServer(false, seeds);\n\n  folly::EventBase evb;\n  auto ctx = std::make_shared<SSLContext>();\n  ctx->setAdvertisedNextProtocols({\"http/1.1\"});\n  ctx->setSessionCacheContext(kSessionCacheContext);\n  SSLSessionReadCallback sessionCb;\n  wangle::SSLSessionCallbacks::attachCallbacksToContext(ctx.get(), &sessionCb);\n  {\n    folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n    sock->setSessionKey(kSessionCacheContext);\n    Cb cb(sock.get());\n    sock->connect(&cb, server->addresses().front().address, 1000);\n    // For TLS 1.3, we need the read callback so that we can read the\n    // NewSessionTicket message\n    sock->setReadCB(&sessionCb);\n    // attach the socket so session callback can unregister read callback\n    sessionCb.socket = sock.get();\n    evb.loop();\n    ASSERT_TRUE(cb.success);\n    ASSERT_NE(sessionCb.session.get(), nullptr);\n    ASSERT_FALSE(sock->getSSLSessionReused());\n  }\n\n  // change ticket secrets\n  wangle::TLSTicketKeySeeds newSeeds;\n  newSeeds.currentSeeds.push_back(hexlify(std::string(32, 'b')));\n  server->updateTicketSeeds(newSeeds);\n\n  {\n    // We can't resume with the ticket received during the first connection\n    // because the server has since changed its ticket secrets. We expect this\n    // resumption to fail. We also expect to receive a new session ticket that\n    // we can use to resume a follow up connection.\n    folly::AsyncSSLSocket::UniquePtr sock2(\n        new folly::AsyncSSLSocket(ctx, &evb));\n    sock2->setRawSSLSession(std::move(sessionCb.session));\n    sock2->setSessionKey(kSessionCacheContext);\n    Cb cb2(sock2.get());\n    sock2->connect(&cb2, server->addresses().front().address, 1000);\n    sock2->setReadCB(&sessionCb);\n    // attach the socket so session callback can unregister read callback\n    sessionCb.socket = sock2.get();\n    evb.loop();\n    ASSERT_TRUE(cb2.success);\n    ASSERT_NE(sessionCb.session.get(), nullptr);\n    ASSERT_FALSE(sock2->getSSLSessionReused());\n  }\n\n  {\n    // 3rd request expected to succeed resumption (against 2nd's ticket)\n    folly::AsyncSSLSocket::UniquePtr sock3(\n        new folly::AsyncSSLSocket(ctx, &evb));\n    sock3->setRawSSLSession(std::move(sessionCb.session));\n    sock3->setSessionKey(kSessionCacheContext);\n    Cb cb3(sock3.get());\n    sock3->connect(&cb3, server->addresses().front().address, 1000);\n    sock3->setReadCB(&sessionCb);\n    // attach the socket so session callback can unregister read callback\n    sessionCb.socket = sock3.get();\n    evb.loop();\n    ASSERT_TRUE(cb3.success);\n    ASSERT_TRUE(sock3->getSSLSessionReused());\n  }\n}\n\nTEST(SSL, TestUpdateTLSCredentials) {\n  // Set up a temporary file with credentials that we will update\n  folly::test::TemporaryFile credFile;\n  auto copyCreds = [path = credFile.path()](const std::string& certFile,\n                                            const std::string& keyFile) {\n    std::string certData, keyData;\n    folly::readFile(certFile.c_str(), certData);\n    folly::writeFile(certData, path.c_str(), O_WRONLY | O_CREAT | O_TRUNC);\n    folly::writeFile(std::string(\"\\n\"), path.c_str(), O_WRONLY | O_APPEND);\n    folly::readFile(keyFile.c_str(), keyData);\n    folly::writeFile(keyData, path.c_str(), O_WRONLY | O_APPEND);\n  };\n\n  auto getCertDigest = [&](const X509* x) -> std::string {\n    unsigned int n;\n    unsigned char md[EVP_MAX_MD_SIZE];\n    const EVP_MD* dig = EVP_sha256();\n\n    if (!X509_digest(x, dig, md, &n)) {\n      throw std::runtime_error(\"Cannot calculate digest\");\n    }\n    return std::string((const char*)md, n);\n  };\n\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  copyCreds(kTestDir + \"certs/test_cert1.pem\",\n            kTestDir + \"certs/test_key1.pem\");\n  sslCfg.clientCAFile = kTestDir + \"/certs/ca_cert.pem\";\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::IF_PRESENTED;\n  sslCfg.setCertificate(credFile.path().string(), credFile.path().string(), \"\");\n  cfg.sslConfigs.push_back(sslCfg);\n\n  HTTPServer::IPConfig insecureCfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                                   HTTPServer::Protocol::HTTP};\n\n  HTTPServerOptions options;\n  options.threads = 4;\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n\n  std::vector<HTTPServer::IPConfig> ips{cfg, insecureCfg};\n  server->bind(ips);\n\n  ServerThread st(server.get());\n  EXPECT_TRUE(st.start());\n\n  // First connection which should return old cert\n  folly::EventBase evb;\n  auto ctx = std::make_shared<SSLContext>();\n  std::string certDigest1, certDigest2;\n\n  // Connect and store digest of server cert\n  auto connectAndFetchServerCert = [&]() -> std::string {\n    folly::AsyncSSLSocket::UniquePtr sock(new folly::AsyncSSLSocket(ctx, &evb));\n    Cb cb(sock.get());\n    sock->connect(&cb, server->addresses().front().address, 1000);\n    evb.loop();\n    EXPECT_TRUE(cb.success);\n\n    auto x509 = cb.getPeerCert();\n    EXPECT_NE(x509, nullptr);\n    return getCertDigest(x509);\n  };\n\n  // Original cert\n  auto cert1 = connectAndFetchServerCert();\n  EXPECT_EQ(cert1.length(), SHA256_DIGEST_LENGTH);\n\n  // Update cert/key\n  copyCreds(kTestDir + \"certs/test_cert2.pem\",\n            kTestDir + \"certs/test_key2.pem\");\n  server->updateTLSCredentials();\n  evb.loop();\n\n  // Should get new cert\n  auto cert2 = connectAndFetchServerCert();\n  EXPECT_EQ(cert2.length(), SHA256_DIGEST_LENGTH);\n  EXPECT_NE(cert1, cert2);\n}\n\nTEST(GetListenSocket, TestNoBootstrap) {\n  HTTPServerOptions options;\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<ServerThread>(server.get());\n  EXPECT_TRUE(st->start());\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_EQ(-1, socketFd);\n}\n\nTEST(GetListenSocket, TestBootstrapWithNoBinding) {\n  std::unique_ptr<HTTPServer> server;\n  std::unique_ptr<ServerThread> st;\n  wangle::TLSTicketKeySeeds seeds;\n  seeds.currentSeeds.push_back(hexlify(\"hello\"));\n  std::tie(server, st) = setupServer(false, seeds);\n\n  // Stop listening on socket\n  server->stopListening();\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_EQ(-1, socketFd);\n}\n\nTEST(GetListenSocket, TestBootstrapWithBinding) {\n  std::unique_ptr<HTTPServer> server;\n  std::unique_ptr<ServerThread> st;\n  wangle::TLSTicketKeySeeds seeds;\n  seeds.currentSeeds.push_back(hexlify(\"hello\"));\n  std::tie(server, st) = setupServer(false, seeds);\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_NE(-1, socketFd);\n}\n\nTEST(UseExistingSocket, TestWithExistingAsyncServerSocket) {\n  AsyncServerSocket::UniquePtr serverSocket(new folly::AsyncServerSocket);\n  serverSocket->bind(0);\n\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  std::vector<HTTPServer::IPConfig> ips{cfg};\n\n  HTTPServerOptions options;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<TestHandlerFactory>().build();\n  // Use the existing AsyncServerSocket for binding\n  auto existingFd = serverSocket->getNetworkSocket().toFd();\n  options.useExistingSocket(std::move(serverSocket));\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<ServerThread>(server.get());\n  server->bind(ips);\n\n  EXPECT_TRUE(st->start());\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_EQ(existingFd, socketFd);\n}\n\nTEST(UseExistingSocket, TestWithSocketFd) {\n  AsyncServerSocket::UniquePtr serverSocket(new folly::AsyncServerSocket);\n  serverSocket->bind(0);\n\n  HTTPServer::IPConfig cfg{folly::SocketAddress(\"127.0.0.1\", 0),\n                           HTTPServer::Protocol::HTTP};\n  HTTPServerOptions options;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<TestHandlerFactory>().build();\n  // Use the socket fd from the existing AsyncServerSocket for binding\n  auto existingFd = serverSocket->getNetworkSocket().toFd();\n  options.useExistingSocket(existingFd);\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<ServerThread>(server.get());\n  std::vector<HTTPServer::IPConfig> ips{cfg};\n  server->bind(ips);\n\n  EXPECT_TRUE(st->start());\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_EQ(existingFd, socketFd);\n}\n\nTEST(UseExistingSocket, TestWithMultipleSocketFds) {\n  AsyncServerSocket::UniquePtr serverSocket(new folly::AsyncServerSocket);\n  serverSocket->bind(0);\n  try {\n    serverSocket->bind(1024);\n  } catch (const std::exception&) {\n    // This is fine because we are trying to bind to multiple ports\n  }\n\n  HTTPServerOptions options;\n  options.handlerFactories =\n      RequestHandlerChain().addThen<TestHandlerFactory>().build();\n  // Use the socket fd from the existing AsyncServerSocket for binding\n  auto netSocks = serverSocket->getNetworkSockets();\n  std::vector<int> fdSocks;\n  fdSocks.reserve(netSocks.size());\n  for (auto s : netSocks) {\n    fdSocks.push_back(s.toFd());\n  }\n  options.useExistingSockets(fdSocks);\n\n  auto server = std::make_unique<HTTPServer>(std::move(options));\n  auto st = std::make_unique<ServerThread>(server.get());\n  server->bind(\n      {{folly::SocketAddress(\"127.0.0.1\", 0), HTTPServer::Protocol::HTTP},\n       {folly::SocketAddress(\"127.0.0.1\", 0), HTTPServer::Protocol::HTTP2}});\n\n  EXPECT_TRUE(st->start());\n\n  auto socketFd = server->getListenSocket();\n  ASSERT_EQ(fdSocks[0], socketFd);\n}\n\nclass ScopedServerTest : public testing::Test {\n public:\n  void SetUp() override {\n    timer_.reset(new HHWheelTimer(\n        &evb_,\n        std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),\n        AsyncTimeout::InternalEnum::NORMAL,\n        std::chrono::milliseconds(1000)));\n  }\n\n protected:\n  std::unique_ptr<ScopedHTTPServer> createScopedServer() {\n    auto opts = createDefaultOpts();\n    auto res = ScopedHTTPServer::start(cfg_, std::move(opts));\n    auto addresses = res->getAddresses();\n    address_ = addresses.front().address;\n    return res;\n  }\n\n  std::unique_ptr<CurlClient> connectSSL(const std::string& caFile = \"\",\n                                         const std::string& certFile = \"\",\n                                         const std::string& keyFile = \"\") {\n    URL url(folly::to<std::string>(\"https://localhost:\", address_.getPort()));\n    HTTPHeaders headers;\n    auto client = std::make_unique<CurlClient>(\n        &evb_, HTTPMethod::GET, url, nullptr, headers, \"\");\n    client->setFlowControlSettings(64 * 1024);\n    client->setLogging(false);\n    client->initializeSsl(caFile, \"http/1.1\", certFile, keyFile);\n    HTTPConnector connector(client.get(), timer_.get());\n    connector.connectSSL(&evb_,\n                         address_,\n                         client->getSSLContext(),\n                         nullptr,\n                         std::chrono::milliseconds(1000));\n    evb_.loop();\n    return client;\n  }\n\n  std::unique_ptr<CurlClient> connectPlainText() {\n    URL url(folly::to<std::string>(\"http://localhost:\", address_.getPort()));\n    HTTPHeaders headers;\n    auto client = std::make_unique<CurlClient>(\n        &evb_, HTTPMethod::GET, url, nullptr, headers, \"\");\n    client->setFlowControlSettings(64 * 1024);\n    client->setLogging(false);\n    HTTPConnector connector(client.get(), timer_.get());\n    connector.connect(&evb_, address_, std::chrono::milliseconds(1000));\n    evb_.loop();\n    return client;\n  }\n\n  virtual HTTPServerOptions createDefaultOpts() {\n    HTTPServerOptions res;\n    res.handlerFactories =\n        RequestHandlerChain().addThen<TestHandlerFactory>().build();\n    res.threads = 4;\n    return res;\n  }\n\n  folly::EventBase evb_;\n  folly::SocketAddress address_;\n  HHWheelTimer::UniquePtr timer_;\n  HTTPServer::IPConfig cfg_{folly::SocketAddress(\"127.0.0.1\", 0),\n                            HTTPServer::Protocol::HTTP};\n};\n\nTEST_F(ScopedServerTest, Start) {\n  auto server = createScopedServer();\n  auto client = connectPlainText();\n  auto resp = client->getResponse();\n  EXPECT_EQ(200, resp->getStatusCode());\n}\n\nTEST_F(ScopedServerTest, StartStrictSSL) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\"/path/should/not/exist\", \"/path/should/not/exist\", \"\");\n  cfg_.sslConfigs.push_back(sslCfg);\n  EXPECT_THROW(createScopedServer(), std::exception);\n}\n\nTEST_F(ScopedServerTest, StartNotStrictSSL) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\"/path/should/not/exist\", \"/path/should/not/exist\", \"\");\n  cfg_.strictSSL = false;\n  cfg_.sslConfigs.push_back(sslCfg);\n  auto server = createScopedServer();\n  auto client = connectPlainText();\n  auto resp = client->getResponse();\n  EXPECT_EQ(200, resp->getStatusCode());\n}\n\nTEST_F(ScopedServerTest, StartSSLWithInsecure) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\n      kTestDir + \"certs/test_cert1.pem\", kTestDir + \"certs/test_key1.pem\", \"\");\n  sslCfg.clientCAFile = kTestDir + \"/certs/ca_cert.pem\";\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::IF_PRESENTED;\n  cfg_.sslConfigs.push_back(sslCfg);\n  cfg_.allowInsecureConnectionsOnSecureServer = true;\n  auto server = createScopedServer();\n  auto client = connectPlainText();\n  auto resp = client->getResponse();\n  EXPECT_EQ(200, resp->getStatusCode());\n\n  client = connectSSL();\n  resp = client->getResponse();\n  EXPECT_EQ(200, resp->getStatusCode());\n}\n\nclass ConnectionFilterTest : public ScopedServerTest {\n protected:\n  HTTPServerOptions createDefaultOpts() override {\n    HTTPServerOptions options;\n    options.threads = 4;\n    options.handlerFactories =\n        RequestHandlerChain().addThen<TestHandlerFactory>().build();\n    options.handlerFactories = RequestHandlerChain()\n                                   .addThen<DummyFilterFactory>()\n                                   .addThen<TestHandlerFactory>()\n                                   .build();\n    options.newConnectionFilter =\n        [](const folly::AsyncTransport* sock,\n           const folly::SocketAddress* /* address */,\n           const std::string& /* nextProtocolName */,\n           wangle::SecureTransportType /* secureTransportType */,\n           const wangle::TransportInfo& /* tinfo */) {\n          auto cert = sock->getPeerCertificate();\n          if (!cert) {\n            throw std::runtime_error(\"Client cert is missing\");\n          }\n          auto x509 = folly::OpenSSLTransportCertificate::tryExtractX509(cert);\n          if (!x509 || OpenSSLCertUtils::getCommonName(*x509).value_or(\"\") !=\n                           \"testuser1\") {\n            throw std::runtime_error(\"Client cert is invalid.\");\n          }\n        };\n    return options;\n  }\n};\n\nTEST_F(ConnectionFilterTest, Test) {\n  wangle::SSLContextConfig sslCfg;\n  sslCfg.isDefault = true;\n  sslCfg.setCertificate(\n      kTestDir + \"certs/test_cert1.pem\", kTestDir + \"certs/test_key1.pem\", \"\");\n  sslCfg.clientCAFile = kTestDir + \"certs/client_ca_cert.pem\";\n  // Permissive client auth.\n  sslCfg.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::IF_PRESENTED;\n  cfg_.sslConfigs.push_back(sslCfg);\n\n  auto server = createScopedServer();\n  auto insecureClient = connectPlainText();\n  auto certlessClient = connectSSL();\n  auto certlessClient2 = connectSSL(kTestDir + \"certs/ca_cert.pem\");\n  auto secureClient = connectSSL(kTestDir + \"certs/ca_cert.pem\",\n                                 kTestDir + \"certs/client_cert.pem\",\n                                 kTestDir + \"certs/client_key.pem\");\n\n  // The following clients fail newConnectionFilter.\n  EXPECT_EQ(nullptr, insecureClient->getResponse());\n  EXPECT_EQ(nullptr, certlessClient->getResponse());\n  EXPECT_EQ(nullptr, certlessClient2->getResponse());\n\n  // Only secureClient passes.\n  auto response = secureClient->getResponse();\n  EXPECT_EQ(200, response->getStatusCode());\n\n  // Check the header set by TestHandler.\n  auto headers = response->getHeaders();\n  EXPECT_EQ(\"testuser1\", headers.getSingleOrEmpty(\"X-Client-CN\"));\n}\n"
  },
  {
    "path": "proxygen/httpserver/tests/RequestHandlerAdaptorTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/httpserver/RequestHandlerAdaptor.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/httpserver/Mocks.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nstruct StubRequestHandlerAdaptor : public RequestHandlerAdaptor {\n  using RequestHandlerAdaptor::RequestHandlerAdaptor;\n\n  void sendHeaders(HTTPMessage& /*msg*/) noexcept override {\n    headersSent_ = true;\n  }\n\n  void sendEOM() noexcept override {\n    // prevent using tx_\n  }\n\n  bool headersSent_{false};\n};\n\nvoid testExpectHandling(bool handlerResponds) {\n  StrictMock<MockRequestHandler> requestHandler_;\n  EXPECT_CALL(requestHandler_, canHandleExpect())\n      .WillOnce(Return(handlerResponds));\n  EXPECT_CALL(requestHandler_, onRequest(_));\n  auto adaptor = std::make_shared<StubRequestHandlerAdaptor>(&requestHandler_);\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->getHeaders().add(\"Expect\", \"100-continue\");\n  auto txHandler = std::dynamic_pointer_cast<HTTPTransactionHandler>(adaptor);\n  txHandler->onHeadersComplete(std::move(msg));\n  EXPECT_EQ(adaptor->headersSent_, !handlerResponds);\n}\n\nTEST(RequestHandlerAdaptorTest, Expect) {\n  testExpectHandling(true /* handlerResponds */);\n  testExpectHandling(false /* handlerResponds */);\n}\n\nTEST(RequestHandlerAdaptorTest, ExpectInvalid) {\n  auto requestHandler_ = std::make_unique<StrictMock<MockRequestHandler>>();\n  auto adaptor =\n      std::make_shared<StubRequestHandlerAdaptor>(requestHandler_.get());\n  EXPECT_CALL(*requestHandler_, canHandleExpect()).WillOnce(Return(false));\n  EXPECT_CALL(*requestHandler_, onError(_)).WillOnce(Invoke([&](ProxygenError) {\n    requestHandler_.reset();\n  }));\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->getHeaders().add(\"Expect\", \"INVALID\");\n  auto txHandler = std::dynamic_pointer_cast<HTTPTransactionHandler>(adaptor);\n  txHandler->onHeadersComplete(std::move(msg));\n  auto buf = proxygen::makeBuf(100);\n  txHandler->onBody(std::move(buf));\n}\n\nTEST(RequestHandlerAdaptorTest, onTimeoutError) {\n  NiceMock<MockRequestHandler> requestHandler_;\n  auto adaptor = new RequestHandlerAdaptor(&requestHandler_);\n  NiceMock<MockHTTPTransactionTransport> transport;\n  HTTP2PriorityQueue egressQueue;\n  HTTPTransaction txn(\n      TransportDirection::DOWNSTREAM, 1, 1, transport, egressQueue);\n  txn.setHandler(adaptor);\n  // egress timeout error\n  HTTPException ex(HTTPException::Direction::EGRESS, \"egress timeout\");\n  ex.setProxygenError(kErrorTimeout);\n  EXPECT_CALL(requestHandler_, onError(kErrorTimeout));\n  txn.onError(ex);\n}\n\nTEST(RequestHandlerAdaptorTest, onStreamAbortError) {\n  NiceMock<MockRequestHandler> requestHandler_;\n  auto adaptor = new RequestHandlerAdaptor(&requestHandler_);\n  NiceMock<MockHTTPTransactionTransport> transport;\n  HTTP2PriorityQueue egressQueue;\n  HTTPTransaction txn(\n      TransportDirection::DOWNSTREAM, 1, 1, transport, egressQueue);\n  txn.setHandler(adaptor);\n  // stream abort cancel error\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   \"stream abort\");\n  ex.setProxygenError(kErrorStreamAbort);\n  // expect notifying the request handler\n  EXPECT_CALL(requestHandler_, onError(kErrorStreamAbort));\n  txn.onError(ex);\n}\n\nTEST(RequestHandlerAdaptorTest, onGoaway) {\n  NiceMock<MockRequestHandler> requestHandler_;\n  auto adaptor = std::make_shared<RequestHandlerAdaptor>(&requestHandler_);\n  NiceMock<MockHTTPTransactionTransport> transport;\n  HTTP2PriorityQueue egressQueue;\n  HTTPTransaction txn(\n      TransportDirection::DOWNSTREAM, 1, 1, transport, egressQueue);\n  txn.setHandler(adaptor.get());\n  // expect goaway is fired\n  EXPECT_CALL(requestHandler_, onGoaway(ErrorCode::NO_ERROR));\n  txn.onGoaway(ErrorCode::NO_ERROR);\n}\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/ca_cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAnMSUwIwYDVQQDDBxBc294IENlcnRpZmljYXRpb24gQXV0aG9y\naXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoLhS9NHgBJtApF/8\nM1PhWI2cBn0zapkJYDWSpk8XXCId/D/VJRWT5ufU0qSUibQJIzdZ1Ejw7YfvwEAV\njERIUi7EqgT1xwyPOSiNJ92Hp4Hf4iePwHzorp5CfxAaKM4F9xiZ452dbE0t3gNO\nHvOCc1/063f02VC7Jzp/idZgln6CCipcBCJw0LWHlJlAnXM7Z/7Unnhw+IM2DZuK\nKULAeHHhDO91PyM7JiPV9l3RvhmdBXfYMVBZZcPqgix4jhW2HAQz9+2JGcbbxeLA\n8TA1E9p6ofNeyVHPv7EOynbhAdyZ5tDiMKsRGJDv4K84blR8FIlggc6E1oS+5Ya/\nLqZtTQIDAQABo4GUMIGRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNIqtvCq\nljXBfJIVsQ9JWqrFdp/ZME8GA1UdIwRIMEaAFNIqtvCqljXBfJIVsQ9JWqrFdp/Z\noSukKTAnMSUwIwYDVQQDDBxBc294IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEB\nMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAnik394vCmmp9kxOw\nxxQCBAYt1JZpCqSAvC/GFsRpYrWnBCHojUcUBkVgElB7LbuzXtGpRBP5Qmrbq1T1\n2QaEhJg24QH12JE1D7j3tdje/siNsMZyhtqxhVYPP/BzBWuCeio11qsPpFhIT6IT\nZoDh5kKrpIrbOsFcHrI8EYDrGxfzh8zfVc+AD4v8LLnJZjR5HQgB49U0WN5orMMR\nmN0wXdMO8RQJ1XubtGP6jDusyepU3noUxPK5a+1VDxJX0CTcoOPnKUWZahwvTXcI\nynyJ9z1q0cr9LguFA/35xlIips5beGbKucgkLp4MMXuXkvyLRuZ1q8tY0q7xjeRE\nW4BcCA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/ca_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCguFL00eAEm0Ck\nX/wzU+FYjZwGfTNqmQlgNZKmTxdcIh38P9UlFZPm59TSpJSJtAkjN1nUSPDth+/A\nQBWMREhSLsSqBPXHDI85KI0n3Yengd/iJ4/AfOiunkJ/EBoozgX3GJnjnZ1sTS3e\nA04e84JzX/Trd/TZULsnOn+J1mCWfoIKKlwEInDQtYeUmUCdcztn/tSeeHD4gzYN\nm4opQsB4ceEM73U/IzsmI9X2XdG+GZ0Fd9gxUFllw+qCLHiOFbYcBDP37YkZxtvF\n4sDxMDUT2nqh817JUc+/sQ7KduEB3Jnm0OIwqxEYkO/grzhuVHwUiWCBzoTWhL7l\nhr8upm1NAgMBAAECggEACkJp7T3efnzInhWQfFhM6MG1ZYalexcEzbLtxxcCZzu4\nTUtn2EOt2s4OvFmML9QhxzE310DGvGryh4OvvHH5aeGZZT8Gz2uCUwQ/Kn3ONIeq\nWn3rA78VfVwV45WMDfHE2Lal0C1WCL1Ogb8VRA2IVMQZ1tQj+aFZKRJnZM2eAc08\njIXG3uadhKJJz4fwcgriT0mX9Wvh19HE3FWy0h/vPJxyVvPDBP2MZakidQvWd7IY\n/df75OW3mqXVpQb7oh07RsWxQ3Ii3nedHIUILHlWJB4EoWgSrlkPDZGN6WzhLH6C\nt/zKS/dO1vrX7mgbJ1jZcbDrMimtFmpLYNiGp3l90QKBgQC4N/X639JrGl3QMOpL\nzrjDmEEKsU0hZVSYZyY5XLLQM2r0J2e2ZcO9Z9sVi3qW9NERzKS8Me1RGF0ID0jb\ncPx3HFRs9KfresxDUs5gskXOov7+SWzk+5A+fLZO4inCYtEgy9P0/ZxglWjYBn9P\nxYj0H/hzk08tMRTGO+jkX99oeQKBgQDfWFmPg92wRZtWeJiXfVo4hxSys+7d5aHs\nL0/yqi/YAQADe6FDvpEAxlwU0mhiYD3yLrLgdtvjWaTqiOFPJesss6/lyMccZ/e2\n8isRD9uVn5F9DduPGonTrblGSbugsbpZWLNLVC/WV6r9bqtM5WAuXdtP78J8crc+\nMxeRMVmedQKBgBtT4d+XeGV/Ac+yke8w+WO6oFtvtdtwZr48/wSfZb9RAVq6xb3f\nFsxwLaXijQr7wk/7paxtU6EmFhbt/YbgGfOpdjS7jnV2P/r0u7zIdWaT83PRfJ/z\nWZCLe5f0IPG/KvrMgeLkBGCniWgSfqxcx1Y/r+GlRqeTkltd48yNFF0xAoGBAI6D\nN5Kl191UbQw+I0/2wozzVLe6rJ6Wmj206tSy15iC+tk+F+a8QJY3/CWlJMoxExXn\nymgEL8bgOqUU9n+keG2rsn9zH+n2RVBNMkHO1pSgnxsmroSfsHaLkX9SKREelwPP\n34gfje69PfQ8m72TV03+waQ+HuF96YMc4trYnJDJAoGBAJhuNMJs/9sb7ITrr5Ry\npTHUT9+06WyNwZpKHF2eQ9jRGw071mhosTQ2+S/rJzrxbiO1a5tNOe5pIW20vm8Q\nUsxvtyTQc6hK7QuzPFb8RsTfXKHn1XJTpId04ZtW+amWeKx+iW1eJVIu11YsVGZg\nZ6qO/vfQnKCMJZ30cHvVOZ0I\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/client_ca_cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAnMSUwIwYDVQQDDBxBc294IENlcnRpZmljYXRpb24gQXV0aG9y\naXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqMdLeznOGloPM1qj\nmkvxwAklg2k5j1Knf97PDNZQSuynrSul03sA01JYSqtWEroI4yfTCMIMUynfT+hp\nnQyGdg9g//gdV5oeKncLn7840RfxzlMRnCqGPDk5OdDJYAe0ZM6y0jwgK/3Ensko\n1k30YnMs0vQ4d08ufAXpNuXzwbvZ4zVl/e7umb6YoJXKJuUv/VWC/2I6J5L9uNie\nb0gtXvhgBsQsxA6dTSXvdEM/Zpf5cGU1jOuIXB0AJ+RCubBd+8ZT4qleV8aorw7j\nLGUKw/S3ZkxrDE3kwU9Z4mXBpUCoUZjN0m61Z0S83yPEnPnCrnlRE0U6VK6kJJ+c\n898U+wIDAQABo4GUMIGRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFGW5mAAu\nvah4WzqcNFwW/jA9+6epME8GA1UdIwRIMEaAFGW5mAAuvah4WzqcNFwW/jA9+6ep\noSukKTAnMSUwIwYDVQQDDBxBc294IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEB\nMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAfs3oCDggNBPyVJ9t\noDYbfubuJI9+WQiNskqtK/XoZfL90nJ7pBTPWRG884Uif+JkCHT507XWpRlbbzRE\nbZH11+ppnvIByz5UTxZre1lU69r1+cyOvzNZiZfmoOf96JYEmVgTclTwTFb7cb4L\ntzzTDIbXSVNdBBs3zFVRU/izFFagyRInrTT1idfN/iBy9ndPQKl99vzMMVk+VGMJ\n9ZjiqqpJqPzJ50R0WX089W+3LWL0OJxfx2S/ywn/Aj99h/n0QkWsm4enSt91wpt3\nyaffCF54cLVTiGAwNfWGtcPGXE/Bf8ZjYCaJF61gBZRaVl/HV7wgA3t8TISWV/S/\nV9kPtw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/client_ca_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCox0t7Oc4aWg8z\nWqOaS/HACSWDaTmPUqd/3s8M1lBK7KetK6XTewDTUlhKq1YSugjjJ9MIwgxTKd9P\n6GmdDIZ2D2D/+B1Xmh4qdwufvzjRF/HOUxGcKoY8OTk50MlgB7RkzrLSPCAr/cSe\nySjWTfRicyzS9Dh3Ty58Bek25fPBu9njNWX97u6Zvpiglcom5S/9VYL/Yjonkv24\n2J5vSC1e+GAGxCzEDp1NJe90Qz9ml/lwZTWM64hcHQAn5EK5sF37xlPiqV5Xxqiv\nDuMsZQrD9LdmTGsMTeTBT1niZcGlQKhRmM3SbrVnRLzfI8Sc+cKueVETRTpUrqQk\nn5zz3xT7AgMBAAECggEAGMfuSsEBhHf9rF4eEF0fOUF+iw1roKITquA1iV58OowF\n0JTMi8EPLXf1M711bVl4TZX+09XeBmXq/moS+7anlXULmnm9ISwlSrng8IsclgLM\ng61JtCLAQ95pCxafQqTO9VAuDFU8qgjgEipZ/yFCBSSBC9ZE5iuvmRErHV7lC+hC\nTcsi1OU0KIFlBf4i9+rj5Lo+ZO3c8xXzX2R1qe0SAvcvNu6yjPNjJeVWYQ7YOozd\nfWfaGx9q7ZEzjW9K7KyhlmvmlGTPw3Z7GwtmKJ5q+DjPxMlIX5bop6G6pIbHC6Mc\nE6KsA6UrHRlnXh1IJpoLETrfluKPcrGb66XEW4wTNQKBgQDAqiqJx/lQlKveDJgW\ncNhHQdBCTmamBcv5fVrap7q+0TauJT/tlwrry/jGes9MfB2U9CjuTDAkoF/wxplR\n4+Q3OOmBzYG6IoHZQ80i+zjq1XpWlRBI8jxu2Pv5tBRDyuaSqAneW0AwAcrDeB03\n+KLWX/juolmIedYCk6VgyxfARwKBgQDgQvepb4RVrVgSe1MW7jPc4/SWqJh0Rk7K\n8bVrY8RKpneBAXUYmHv0nqeE710rACh6Dd8mTdjn3TQLcGGqIpN2xbI9ivz9k/XY\nyHOdeRTvzPZhkqxpl0ztkpisMioujDllQYP712eX/725FgZXwXPq0qWOOZRpEm81\nX4B0IFwzrQKBgDuwfJ1TJxqf9N9GZ5gMPfVVnn/sakDlatxQyeUUMfcMOjSZtcSW\nFvwWpWxYgADiMwgC3Ot3DzNJvG+MF9QXFo1FRJLCaH6SUkhNiTBdRLT8jnpklDcF\nVVPCRc6GzAB0zBqDlQsemjRFWdxVSjgc/9YKxcSo57Qwiu0hcQsb4K/hAoGBAIGb\nsm6mGKE7kY7830R9XlPnGtCHl7R7fcYkc2khO6y5EI/qO7Z/SgRSPRbDD+FuMfZ4\nVeNZUmaOnSmPRaKCYR9fmmefEB+th4RGNStpcwQ0PKCfmC1eWANlmf7K7z3/pJw0\nhQbjsudR0mBJrljBYTDxlOThujgPg14hV6fFnAPxAoGBAJN6aM8UDifgjxPZKiJp\nwACtutcAgiYQK8HtXYZZMVees+IjhrDwwFQ7GIhD9wa9ruDDdl7XNXYnteGUoX5K\nM2tAVp37B9ZfZE2yreBXXt+2UOgYNmuU91cBMKROCTj9JYLwcsoxY+pncBp8nmbp\n+0YdhNisk8i1J253rMnb6OnO\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/client_cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDMDCCAhigAwIBAgIBCjANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAUMRIwEAYDVQQDDAl0ZXN0dXNlcjEwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQCsJuwQ/NCdFkJ9WcR2JVBOKRYjCHcOs4EEzbp/eAzs\n3FiQyrTMLC6zLolILa6NOxFHSy9KLMPDXtlAI8gIW6GhIVFuCQHnUn2CBwu19CBc\nH8jRMw9xkJQljs5H1oIXeSzKvd6QgHeUvt4kJzMYrmHbuaGNKL7SPkRf+f6pjja6\np+w47OFWbj/S31rktDXFZXi3gusAaRixFeXxd7Sl6D1EiznkRemzVc4NDkubFXqd\nAREscX0KN5CEKCuOm0aByeMZQ5SzAqcm6Gdtq85DqYvZ1z+VnjszauylJ21jTuFR\nD5LVEzWr3e0EsNQd2SyxIuxvrAMukmQpuXM4trV1bDDhAgMBAAGjeDB2MAwGA1Ud\nEwEB/wQCMAAwHQYDVR0OBBYEFBUUGlHHCz0eUE7Yyf9pMOZ5IGJwMB8GA1UdIwQY\nMBaAFGW5mAAuvah4WzqcNFwW/jA9+6epMA4GA1UdDwEB/wQEAwIF4DAWBgNVHSUB\nAf8EDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEADG0ZnxDnrYXPpvzy\nYpmyBYJPTBtq+icSn1aAr2FXgz/RXOolDxxwHOIaODamWbdvvveB1rQNgsG64KBM\nSQ8qNeV0fhoFi4ocb8QhE5/GFdkqwXaSZWyFxrfohoGj3dml30Umr+EmsBPj2HSi\nl/dc6JXtv78AjssFaqESnMYYLyVDupPmJ74P11xoG0YvdFeHIV8keQ0nbj78GCY7\nh7U14YR1OcW+t697JI5fbwlWIf2s3uiGu6RtXO/oDDxSwM7Ei8BmB1CPytOSDrx3\nVmtYcjX3idT8IWXHOD6lyYAgaRcmXb7XSodl1lWu4RkNnmwbvFLQ/i0pPZuWxBSS\nwVtuyA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/client_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCsJuwQ/NCdFkJ9\nWcR2JVBOKRYjCHcOs4EEzbp/eAzs3FiQyrTMLC6zLolILa6NOxFHSy9KLMPDXtlA\nI8gIW6GhIVFuCQHnUn2CBwu19CBcH8jRMw9xkJQljs5H1oIXeSzKvd6QgHeUvt4k\nJzMYrmHbuaGNKL7SPkRf+f6pjja6p+w47OFWbj/S31rktDXFZXi3gusAaRixFeXx\nd7Sl6D1EiznkRemzVc4NDkubFXqdAREscX0KN5CEKCuOm0aByeMZQ5SzAqcm6Gdt\nq85DqYvZ1z+VnjszauylJ21jTuFRD5LVEzWr3e0EsNQd2SyxIuxvrAMukmQpuXM4\ntrV1bDDhAgMBAAECgf9XMgpf5a3Ejdcfpq2Gf1/YNvdUx0oxKA/cCa1s4wxTl0fv\nZBIvWDGkkEL4oYAdXiMM+V1P31yd7cIcZViW4yf6ypAe1nEgZFKDNGO6GuzzvHr6\n0Iyso45mIkRZc/fF6+/EFmekyW0dlOTvaut2lNz8GLS7ID2G6q23RJNjTk+/j850\nxRxayz/2WyxGfXQ8b5b0SQzW4fox+Qi79SbHXBkd38frtCVYhIijfmQCF7wX5on/\nADjLK2Pj2u5SeKQhLoek8TNjtiScR2Vcpb+pFaKubufxcE28gJwy+4sQe//iLdN5\nZeK56atLc9eZbkqRYy5StgiWFzqz6CPhP7Rah00CgYEA4sYn8bglSC7flV7aZ9Sa\n/pL8ghBj5uLi7nnYJZnf9zf2geqmE23DcXb53GeD4G/GzoCAs7zw+6VFw0AByWRh\ny9iX9Fx8bwPv+xHeGzmmFBtu4zlYrw+zpvcsTk1wOvKZRnSudM0EdkUseToWGoPf\n06F3b9hHhd9plB7kEJ65I2sCgYEAwlamM9B9FGuFwaTJ4uHfOOD829vAdMcO+gUh\nbgEmygtRy3cFte1kiF6+0uZGbbd9dDun9x3HNKu59XKosoz5i2vUu/s41CDSPa9L\n1ffqiKiOB2GDlFlnncu9xNVSWU2oOqJrSDGCePiICIZuZHEFu0TgSwRxZuyX6g01\nhWBwm+MCgYEAhesbP6XNbnkv1Bf1xodJGLlYoL5pILov5UDTIjFij63exw35EVQq\nrODj4QLIYEviDaTZXEthzFnnfsxXwcSj9CtuYQIVAcJDf/MnRNCggRNKQqlk88zD\n/P/OjFcyxbzqQy84rwU7c1SaVOS2lOX30hRyYfxJEIfE45HygNYa4w0CgYAkzv6p\n37phLMBn3gtzEq8R+eNKzUOVmsJmbqRXTpaHPD+g1wlY3P7mqXNhRMZgL5nucwIs\nVxKNEl2ldLMpJnR5iNIBTOMForXPgy1M+KkOe0ZzdW/ToF90zstBxdnUGlZbzLmf\nMelxejlhy/yKv7GDN9d3vf6wrO1VIm1Zttt9WwKBgQDAUp/jEANmRoeKXM5CO1TJ\nCy7Q4tlTuzRIAb3wx+xv4jNUKUY9N1CuuOC0PUO5d6UX+ocXixhZYTj/xIn/CL8b\nQ2A4NMgKgiT2vQGeBPO0gvR8xRRNdoIKu+MRaQMNdqNN3hgzfH+E5Fqs3Au54lRy\nxYPKRFzZS8boS0zzr6klTw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/generate_certs.sh",
    "content": "#!/bin/bash -ue\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nCERT_LIFETIME_DAYS=${CERT_LIFETIME_DAYS:-36500}\n\nextensions() {\n  cat << EOF\n[ca]\nbasicConstraints        = critical, CA:TRUE\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid:always, issuer:always\nkeyUsage                = critical, cRLSign, digitalSignature, keyCertSign\n\n[client]\nbasicConstraints        = critical, CA:FALSE\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid:always\nkeyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage        = critical, clientAuth\n\n[server]\nbasicConstraints        = critical, CA:FALSE\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid:always\nkeyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement\nextendedKeyUsage        = critical, serverAuth\nsubjectAltName          = IP:127.0.0.1,IP:::1\n\n[client_and_server]\nbasicConstraints        = critical, CA:FALSE\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid:always\nkeyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement\nextendedKeyUsage        = critical, serverAuth, clientAuth\nsubjectAltName          = IP:127.0.0.1,IP:::1\nEOF\n}\n\ndie() {\n  echo \"$@\" >&2\n  exit 1\n}\n\ngenerate_key() {\n  keytype=\"$1\"\n  modulus_bits=\"$2\"\n  outfile=\"$3\"\n\n  [ \"$keytype\" != \"rsa\" ] && die \"unsupported key type\"\n\n  openssl genrsa -out \"$outfile\" \"$modulus_bits\" 2>/dev/null\n}\n\nmkcsr() {\n  local key=\"$1\"\n  local name=\"$2\"\n  local output=\"$3\"\n\n  openssl req -new \\\n    -sha256 \\\n    -key \"$key\" \\\n    -keyform PEM \\\n    -subj \"/CN=$name/\" \\\n    -outform PEM \\\n    -out \"$output\" \\\n\n}\n\nselfsign() {\n  local incsr=\"$1\"\n  local inkey=\"$2\"\n  local outfile=\"$3\"\n\n  openssl x509 \\\n    -req \\\n    -set_serial 1 \\\n    -signkey \"$inkey\" \\\n    -inform PEM \\\n    -in \"$incsr\" \\\n    -outform PEM \\\n    -out \"$outfile\" \\\n    -days \"$CERT_LIFETIME_DAYS\" \\\n    -extfile <(extensions) \\\n    -extensions \"ca\" \\\n\n}\n\nsign() {\n  csr=\"$1\"\n  cacert=\"$2\"\n  cakey=\"$3\"\n  serial=\"$4\"\n  extensions=\"$5\"\n  outfile=\"$6\"\n\n\n  openssl x509 \\\n    -req \\\n    -set_serial \"$serial\" \\\n    -CA \"$cacert\" \\\n    -CAkey \"$cakey\" \\\n    -inform PEM \\\n    -in \"$csr\" \\\n    -outform PEM \\\n    -out \"$outfile\" \\\n    -days \"$CERT_LIFETIME_DAYS\" \\\n    -extfile <(extensions) \\\n    -extensions \"$extensions\" \\\n\n}\n# ca_cert.pem\ngenerate_key rsa 2048 ca_key.pem\nmkcsr ca_key.pem \"Asox Certification Authority\" ca_key.csr.pem\nselfsign ca_key.csr.pem ca_key.pem ca_cert.pem\n\n# Client CA\ngenerate_key rsa 2048 client_ca_key.pem\nmkcsr client_ca_key.pem \"Asox Certification Authority\" client_ca.csr.pem\nselfsign client_ca.csr.pem client_ca_key.pem client_ca_cert.pem\n\n# client_cert.pem: CN = testuser1, serial 10\ngenerate_key rsa 2048 client_key.pem\nmkcsr client_key.pem \"testuser1\" client_cert.csr.pem\nsign client_cert.csr.pem client_ca_cert.pem client_ca_key.pem 10 client client_cert.pem\n\n# test_cert1, CN=test_cert1, Serial number = 1, client/server\ngenerate_key rsa 2048 test_key1.pem\nmkcsr test_key1.pem \"test_cert1\" test_cert1.csr.pem\nsign test_cert1.csr.pem client_ca_cert.pem client_ca_key.pem 1 client_and_server test_cert1.pem\n\n# test_cert2, CN=test_cert2, Serial number = 1, client/server\ngenerate_key rsa 2048 test_key2.pem\nmkcsr test_key2.pem \"test_cert2\" test_cert2.csr.pem\nsign test_cert2.csr.pem client_ca_cert.pem client_ca_key.pem 1 client_and_server test_cert2.pem\n\n# Remove all of the CSRs but leave the keys in case you need to regenerate a\n# different cert with the same key.\nrm ./*.csr.pem\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/test_cert1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAVMRMwEQYDVQQDDAp0ZXN0X2NlcnQxMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEA6HpcGHdbSuMsC2w4AW65yrR5oW6NKhRILUMMqyT8\nYd16YUWVLKonmcqcJIobhvdeFe3VrPBSHj2pngPpfyPgxNRCBZm0D+ICSUoW8AYd\nESl2H4f81+/msqyClWBlJHt9ZxN91Np+cGhjgYWhGv9e9yUM/FQlDZ4mOIWp9RSy\nWeCgEUVE56vCaq7+TdMrVH5sBFCbuLpnYS/Y4AZfbCqwERbJgK3W/MyjNH+n99KK\nT7VOOP197PbTo7Nc58EWOoJrLiGsZycHl/UTirdUy5LkHg3j/MGOkl3dPhVbKE6z\nvz+QM+jauiY3ImartaYBGK12KDjrZ23kjAynTt8Dmi8I7wIDAQABo4GmMIGjMAwG\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFGi9EI4Mflu5DGvPAYiLqShkjYQnMB8GA1Ud\nIwQYMBaAFGW5mAAuvah4WzqcNFwW/jA9+6epMA4GA1UdDwEB/wQEAwID6DAgBgNV\nHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIQYDVR0RBBowGIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAGxv8aNCoHrZHvzNR\nhPacICxhnY87fNuYL5oSzbUNdceThMN2Bcni+OnQtJkmpHlW4ka5CrcHfEb7zyzI\nPJkFOCc5U9PZWg+ya0xq7UEx5xd8RiBZidYAPXCj+fzPE4jBUGQxcJFMg1HXLOKH\nV127AlwgYAB54C84x3IdutDytsdF/MJ+NeDGfDNDWvjK+mbM58uY5+XnvFb9cHCl\nMBVa+Kr82X3yDnAZjXOAtmICjDoq/9X25+alt29Xs3vqhdsQX87AXfxM90iHkkM0\n9lGhV1UZj3yznkTpG37WKqLQU9yh6K23ch5Ddj2YkNVSNFafvjgf2Fr03kx7zMbL\nJjmaQQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/test_cert2.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAVMRMwEQYDVQQDDAp0ZXN0X2NlcnQyMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAqT7JKAm5ToEjPoRI/BRvZ65iQL5jOj9aBWARG1m5\nmxbRZ4yWBL07tX/uFMYvoq6it5tsnqsIO+YnnRFJxwZsh8rLe5KXZLWSfBI+4fzF\nM9NXVa634hnJQWQtErN6RAcLKYZHQctHvij1IhMyacfLTmH/iZCtWhpplSISmTDm\nl7t0+FsE0wTy6rUwgl6JK2oZa8/pWp4n2KYojWPXmGeA2W5ov+iFoCCNk7XUikPb\nvvC38smJErJV9ooWCZKPjEOntJuHjd0/PiqcZrrFwBU51EFe9JYJQA2szAfJ1MFT\nzE+vLjrurE5PC5t67nNT9qBATSFpb6R+T9tm18RRl5a1OwIDAQABo4GmMIGjMAwG\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLEoOdkozRsNg+3DKtJdRGWnsgynMB8GA1Ud\nIwQYMBaAFGW5mAAuvah4WzqcNFwW/jA9+6epMA4GA1UdDwEB/wQEAwID6DAgBgNV\nHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIQYDVR0RBBowGIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAEuyRrGGJ+3veqAYx\nR6SG5gwgfMDX+Aik2TWtRUPOlrwvAO8ud3X5nihNRvwM8cOrggrh7LfYaLVKSqm0\nsfIT+QgDezQIGjWmFevNU7oAlCViQ/B/Ik2HgBvuOzcrg9lOsh7pIO7ZRhX9Zdwl\nCE/foDU4LUIfw64u6vxR5ErYxumifRRlhx2rxZIUrtAEEHHA9iWXC2GD+TPOC86F\nePmpXaKH56d80OsvZDDfGW0FJ3vRkZR2Fhu5vmpIDr+mT5dLTAbD+mnc9YMzlcW6\nJen4pE/Mjsd+QNUjCkeNVN0PFrTiKnJfB4Mv2AdP7jPidfyWgU8v0DyOMi5BJLBO\nNgAwOg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/test_key1.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDoelwYd1tK4ywL\nbDgBbrnKtHmhbo0qFEgtQwyrJPxh3XphRZUsqieZypwkihuG914V7dWs8FIePame\nA+l/I+DE1EIFmbQP4gJJShbwBh0RKXYfh/zX7+ayrIKVYGUke31nE33U2n5waGOB\nhaEa/173JQz8VCUNniY4han1FLJZ4KARRUTnq8Jqrv5N0ytUfmwEUJu4umdhL9jg\nBl9sKrARFsmArdb8zKM0f6f30opPtU44/X3s9tOjs1znwRY6gmsuIaxnJweX9ROK\nt1TLkuQeDeP8wY6SXd0+FVsoTrO/P5Az6Nq6JjciZqu1pgEYrXYoOOtnbeSMDKdO\n3wOaLwjvAgMBAAECggEARAuwki65Z3mH5k4GNJEiy4y4v0IbCpKGL81c02lPgBuy\nGu+hgsHJNpAh1OYhuYcFIHkNXoG3H4ff8lLl0OOBAku4iWgcVrvJ/Ia1oQrBDklq\n/D6zCMd86JZq4GsnH58PrRB898T+VkuPuJUS+1J9Cuzq2J6Oe+Zq/IdPIF+u9nQk\nu+ekbjN1tv53efkzzPlymTJ1TEbv6mevJLM1lINECSwzMmZM0yAXKCrh3lLpZZHn\naarqkScM/wIRLdm5r0v5Hw59uVCYV7StuKctDZ0kbmvipHc7Y/ZK/ptiGXxFx7Ij\nWeAYwKc2jeDLXxPeYN1OVtnkQNP/YGIzHYd9K/09dQKBgQD5wCAOuaXvt+vmM0SJ\nsrsP8Hyn7MRjFhFa9ewXx32LhUGU3fo/55KF9sqEOqZ3YQtGZPEmOxzN9jlE5rvv\no5wjeB0KzsYhKl42n/gIutLnqx0WhrXpWzkHY40zbKrOoAYRUzP02/Ipk3bOmft2\nrb8JSnVM7O3r6Dp8k6ObxJ51IwKBgQDuS5avFdy4VAUdXozRxEjcQk8+qW0n99Jl\nKrJjWiuk4k76dFv8wa9xrmV8jOyo7eLZaWZjXwIF+HAP54Slf4eKobD/eFTroPnS\nH2qoQJPdhRs2tl2kW9QFtIun2EZRy3ZxodYxd4YGYJOkmt2WeppponJuKjXEfnWQ\nAd5OAtBXxQKBgQCICQctHh/tR7/9XnV5j1nm37X2fbemxRFk+0jC1w1iHo5POO2j\nO06LG5bJmWys37YVfMwBZ/dTlEczaX56lCJrC3S+O0yeKgaxOEcQ8joIqCKq72tM\nlTyl8qqplQogi/diHnDRcbAgx0rSU/stdIv20pC7+t3JPnAFBSH7qYgqAwKBgG/x\nXHklmQmSltCEMTz9q4ilMdrq9pdXCSRzWVfo7SXJFnb7oJbtWgDaf1xyS17UWcPu\niqXV5PpvCh2+kga5ETBj4uUwXxxOA+3cylr98ziWJgGezADBDqobdZvEsswL+7lH\n4HpefrySM+mYws3FDLLhGEZ+V8HV8l5TMl6NXdhNAoGADpnhKGUEAYCtUUJ94u2S\nNUK6ZGJVni38fHi+QKi4bN24AXxbHmfiR8Si911egltz8C3/USuiHTDK3nRzGLia\nEiFjZPDJDOcAvEAlA6D0ADHhdGJyUL4BmSHvuEOWjCvsu/q5CMhGX3sIKPNtQRWk\nYUPE2G74yyeYur4QoV2Q3mg=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/httpserver/tests/certs/test_key2.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpPskoCblOgSM+\nhEj8FG9nrmJAvmM6P1oFYBEbWbmbFtFnjJYEvTu1f+4Uxi+irqK3m2yeqwg75ied\nEUnHBmyHyst7kpdktZJ8Ej7h/MUz01dVrrfiGclBZC0Ss3pEBwsphkdBy0e+KPUi\nEzJpx8tOYf+JkK1aGmmVIhKZMOaXu3T4WwTTBPLqtTCCXokrahlrz+lanifYpiiN\nY9eYZ4DZbmi/6IWgII2TtdSKQ9u+8LfyyYkSslX2ihYJko+MQ6e0m4eN3T8+Kpxm\nusXAFTnUQV70lglADazMB8nUwVPMT68uOu6sTk8Lm3ruc1P2oEBNIWlvpH5P22bX\nxFGXlrU7AgMBAAECggEAEtxDqnSQSVFWziFhHhu2NoWrvq9OJjA9JfAzYoA4kNyV\ncBwDSSVAFtUvgPJ9CHy8TAhura09o6ndrEFASYT2ae44Mt3T2o3pdG4hbSmwCDsl\ntmSxapRDu0fLRhQ+LH/kceIxkAFz+JsJMPJBYfcOjv5Dbd8ePQudzILEM9IhQF3y\n0ouqWu+jTLn78ZPSfIeXFYclGOt4fVEtU2NC1/Rlv8yb/VSxjcMa2lqLPPT55YB/\nToTxp6GSp5Y60nyc8WSiQnaZRGBPsOjEdINrxHHX8/NCEla8Wcrqt7sZ7UC7EYRf\nZ5Ud7dnZ29ZHpC/IOz7xX+z8Ovnm5nj2l3m4GVBWwQKBgQC5ceDOw0TMmQt64QRs\nPsflx6duvI9su9FSeCGz5YpERVVSdpi7xf+chsUtkxaOn73ZuvG2r7sKJMw4itd8\nIpEpBVmFuTSvzqcGUTDzZpyyDQ81Ukt+xZNZjoRw9zZl2OkgDBJGXOAtEoi/rv86\nhOW9MYUsnNjViW6GW2a1Q9/ScQKBgQDpoxYjyi7uPuBkKQllhHR1x/lGwsXLsyvo\nUFwR91wpDx6h+/w9NJxx7CbtgjYC1y/lMWhxOWu7AwqLtEOXF9pToAPVX5MVGztU\nj6pjJPPUwrapB2CrgLI0TSEkXiOPZNOiLiDklwnYlX7x78+kTz4T0iCvDJStjW9L\nE6QsrwXAawKBgQCD0V/keUcZTByt7u8O5p1/RylL/LrSprsHLR9/2cUr/EDHCkhN\nCVRF9kKIv8pD/WadM1aH7mg8sKV996tusL+Qch4NgPXjljiBtArgqWru4XuTAnlp\nlpXEDhs0lXVUdhhYUFxZKcGsKEWOQ51nAnqvvliUurUjLLqkxKnAZYve8QKBgQDl\n1U80SfK84BGxtkTOHuzJ6LyqBXS6nDk3QcYwzltU8NC7nL1YIGc+EoeA4bTsOm+d\nUWti5o+52pYHNH/BJO/bj+/1eR2hh7ZnyyRcf791r04tHVrVm7ayiKVvt0PYDeG7\nCxHEjWhcLURCEBz9kA6LRQxt5zxjNl0jR+EbK9nGnQKBgAo5CiVJBHjTW5KFvynt\nqLc39k2OzssQTqpCl8XY5phlxNrSzRgD1qNlHG7Su/C17x00/2eTGUDNpBf4HECs\nqYJAb1TsQqM+87+84Pl48wYxMLnKksFC+R4MRwNw/l6gjGdLJSuel/ZAEbQJ6SzF\nofMMxWJr/Hhi531EDP2ggh9l\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n## Run this script to (re)install proxygen and its dependencies (fbthrift\n## and folly). You must first compile all the dependencies before running this. This\n## Usually this is done by first running `deps.sh`.\n\nset -e\nstart_dir=$(pwd)\ntrap 'cd $start_dir' EXIT\n\n# Must execute from the directory containing this script\ncd \"$(dirname \"$0\")\"\n\ncd _build\n# Uninstall is expected to fail the first time\nmake uninstall || true\nmake install\n\n# Make sure the libraries are available\n# Linux only\n/sbin/ldconfig || true\n"
  },
  {
    "path": "proxygen/lib/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nfile(\n    MAKE_DIRECTORY\n    ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http\n    ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils\n)\n\nadd_custom_command(\n    OUTPUT ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http/HTTPCommonHeaders.h\n    OUTPUT ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http/HTTPCommonHeaders.cpp\n    COMMAND\n        ${CMAKE_CURRENT_SOURCE_DIR}/http/gen_HTTPCommonHeaders.sh\n        ${CMAKE_CURRENT_SOURCE_DIR}/http/HTTPCommonHeaders.txt\n        ${PROXYGEN_FBCODE_ROOT}\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http\n    DEPENDS\n        ${CMAKE_CURRENT_SOURCE_DIR}/http/gen_HTTPCommonHeaders.sh\n        ${CMAKE_CURRENT_SOURCE_DIR}/http/HTTPCommonHeaders.txt\n        ${CMAKE_CURRENT_SOURCE_DIR}/utils/gen_perfect_hash_table.sh\n        ${CMAKE_CURRENT_SOURCE_DIR}/utils/perfect_hash_table_template.h\n        ${CMAKE_CURRENT_SOURCE_DIR}/utils/perfect_hash_table_template.cpp.gperf\n    COMMENT \"Generating HTTPCommonHeaders.h and HTTPCommonHeaders.cpp\"\n)\n\nadd_custom_command(\n    OUTPUT ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/stats/StatsWrapper.h\n    COMMAND\n        ${CMAKE_CURRENT_SOURCE_DIR}/stats/gen_StatsWrapper.sh\n        ${PROXYGEN_FBCODE_ROOT}\n    DEPENDS\n        ${CMAKE_CURRENT_SOURCE_DIR}/stats/BaseStats.h\n    COMMENT \"Generating StatsWrapper.h\"\n)\n\nadd_custom_command(\n    OUTPUT\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceEventType.h\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceEventType.cpp\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceFieldType.h\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceFieldType.cpp\n    COMMAND\n        ${PROXYGEN_PYTHON}\n        ${CMAKE_CURRENT_SOURCE_DIR}/utils/gen_trace_event_constants.py\n        --output_type=cpp\n        --input_files=samples/TraceEventType.txt,samples/TraceFieldType.txt\n        --output_scope=proxygen\n        --header_path=proxygen/lib/utils\n        --install_dir=${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils\n        --fbcode_dir=${PROXYGEN_FBCODE_ROOT}\n    WORKING_DIRECTORY\n        ${CMAKE_CURRENT_SOURCE_DIR}/utils/\n    COMMENT \"Generating TraceEventType and TraceFieldType\"\n)\n\nadd_custom_target(\n    proxygen-generated\n    DEPENDS\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http/HTTPCommonHeaders.h\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http/HTTPCommonHeaders.cpp\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceEventType.h\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceEventType.cpp\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceFieldType.h\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceFieldType.cpp\n        ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/stats/StatsWrapper.h\n)\n\nset(\n    HTTP3_SOURCES\n    ${HTTP3_SOURCES}\n    http/SynchronizedLruQuicPskCache.cpp\n    http/HQConnector.cpp\n    http/codec/HTTPBinaryCodec.cpp\n    http/codec/HQControlCodec.cpp\n    http/codec/HQFramedCodec.cpp\n    http/codec/HQFramer.cpp\n    http/codec/HQStreamCodec.cpp\n    http/codec/HQUnidirectionalCodec.cpp\n    http/codec/HQUtils.cpp\n    http/session/HQByteEventTracker.cpp\n    http/session/HQDownstreamSession.cpp\n    http/session/HQSession.cpp\n    http/session/HQStreamBase.cpp\n    http/session/HQStreamDispatcher.cpp\n    http/session/HQUpstreamSession.cpp\n    transport/H3DatagramAsyncSocket.cpp\n    transport/PersistentQuicPskCache.cpp\n    transport/PersistentQuicTokenCache.cpp\n)\nset(\n  HTTP3_DEPEND_LIBS\n  ${HTTP3_DEPEND_LIBS}\n  mvfst::mvfst_transport\n  mvfst::mvfst_client\n  mvfst::mvfst_client_cached_server_tp_serialization\n  mvfst::mvfst_fizz_client\n  mvfst::mvfst_server\n  mvfst::mvfst_codec_types\n  mvfst::mvfst_state_machine\n)\n\n# =============================================================================\n# Granular library for generated code (HTTPCommonHeaders, TraceEventType, etc.)\n# This library compiles the generated sources and provides them to other targets\n# =============================================================================\nproxygen_add_library(proxygen_http_http_common_headers\n  SRCS\n    ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/http/HTTPCommonHeaders.cpp\n  EXPORTED_DEPS\n    proxygen-generated\n    Folly::folly_range\n)\nproxygen_add_library(proxygen_utils_trace_event_types\n  SRCS\n    ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceEventType.cpp\n    ${PROXYGEN_GENERATED_ROOT}/proxygen/lib/utils/TraceFieldType.cpp\n  EXPORTED_DEPS\n    proxygen-generated\n    Folly::folly_conv\n)\n# Granular library for http_parser\nproxygen_add_library(proxygen_external_http_parser\n  SRCS\n    ${PROXYGEN_FBCODE_ROOT}/proxygen/external/http_parser/http_parser_cpp.cpp\n  EXPORTED_DEPS\n    Folly::folly_portability_string\n)\n# http_parser requires strict URL checking\ntarget_compile_definitions(proxygen_external_http_parser_obj PRIVATE HTTP_PARSER_STRICT_URL=1)\n\n# =============================================================================\n# Granular library subdirectories\n# Auto-generated CMakeLists.txt using proxygen_add_library()\n# These include their own test subdirectories via BUILD_TESTS conditional\n# =============================================================================\nadd_subdirectory(dns)\nadd_subdirectory(healthcheck)\nadd_subdirectory(http)\nadd_subdirectory(pools/generators)\nadd_subdirectory(sampling)\nadd_subdirectory(services)\nadd_subdirectory(ssl)\nadd_subdirectory(transport)\nadd_subdirectory(utils)\n\n# =============================================================================\n# Create monolithic proxygen library from all OBJECT targets\n# =============================================================================\nproxygen_create_monolithic_library()\n\n# Add mvfst/HTTP3 dependencies to the monolithic library\ntarget_link_libraries(proxygen\n  PUBLIC\n    mvfst::mvfst_transport\n    mvfst::mvfst_client\n    mvfst::mvfst_fizz_client\n    mvfst::mvfst_server\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_state_machine\n)\n\n# Install the headers, excluding unit testing related headers\nfile(\n    GLOB_RECURSE PROXYGEN_HEADERS_TOINSTALL\n    RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}\n    *.h\n)\nlist(FILTER PROXYGEN_HEADERS_TOINSTALL EXCLUDE REGEX test/)\nlist(FILTER PROXYGEN_HEADERS_TOINSTALL EXCLUDE REGEX utils/TestUtils.h)\nlist(FILTER PROXYGEN_HEADERS_TOINSTALL EXCLUDE REGEX .template.h)\n# some exceptions\nlist(APPEND PROXYGEN_HEADERS_TOINSTALL http/webtransport/test/FakeSharedWebTransport.h)\n\n# cmake doesn't provide a way to install a list of relative paths to the correct\n# location (it will flatten them all into DESTINATION), so we have to manually\n# do this for PROXYGEN_HEADERS_TOINSTALL\nforeach(header ${PROXYGEN_HEADERS_TOINSTALL})\n    get_filename_component(header_dir ${header} DIRECTORY)\n    install(FILES ${header} DESTINATION include/proxygen/lib/${header_dir})\nendforeach()\n\ninstall(\n    DIRECTORY ${PROXYGEN_GENERATED_ROOT}/proxygen/\n    DESTINATION include/proxygen/\n)\ninstall(\n    TARGETS proxygen\n    EXPORT proxygen-exports\n    LIBRARY DESTINATION ${LIB_INSTALL_DIR}\n    ARCHIVE DESTINATION ${LIB_INSTALL_DIR}\n)\n\nadd_subdirectory(test)\nadd_subdirectory(http/coro/client/samples/cocurl)\n"
  },
  {
    "path": "proxygen/lib/dns/AsyncDNSStatsCollector.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/AsyncDNSStatsCollector.h\"\n\n#include <folly/Conv.h>\n\n#include <memory>\n\nusing facebook::fb303::AVG;\nusing facebook::fb303::COUNT;\nusing facebook::fb303::PERCENT;\nusing facebook::fb303::RATE;\nusing facebook::fb303::SUM;\n\nnamespace {\n\nstd::vector<std::string> rcodeNames({\n    \"NOERROR\",\n    \"NODATA\",\n    \"FORMERR\",\n    \"SERVFAIL\",\n    \"NXDOMAIN\",\n    \"NOTIMP\",\n    \"REFUSED\",\n});\n\n}\n\nnamespace proxygen {\n\nAsyncDNSStatsCollector::AsyncDNSStatsCollector(const std::string& prefix)\n    : reqs_(prefix + \"req\", RATE, SUM),\n      success_(prefix + \"success\", RATE, SUM, PERCENT),\n      error_(prefix + \"error\", RATE, SUM, PERCENT),\n      timeouts_(prefix + \"timeouts\", RATE, SUM, PERCENT),\n      nodata_(prefix + \"nodata\", RATE, SUM, PERCENT),\n      answersA_(prefix + \"answers.A\", SUM, COUNT),\n      answersAAAA_(prefix + \"answers.AAAA\", SUM, COUNT),\n      answersCNAME_(prefix + \"answers.CNAME\", SUM, COUNT),\n      latency_(prefix + \"latency\", 10, 0, 500, AVG, 50, 95, 99),\n      ttl_(prefix + \"ttl\", 900, 0, 21600, AVG, 50, 95, 99),\n      status_(static_cast<size_t>(DNSResolver::UNKNOWN) + 1),\n      rcodes_(16),\n      cacheHits_(prefix + \"cache_hit\", RATE, SUM),\n      cacheMisses_(prefix + \"cache_miss\", RATE, SUM),\n      cachePartialMisses_(prefix + \"cache_partial_miss\", RATE, SUM),\n      staleCacheHits_(prefix + \"stale_cache_hit\", RATE, SUM) {\n  for (uint8_t i = 0; i <= static_cast<size_t>(DNSResolver::UNKNOWN); ++i) {\n    auto status = static_cast<DNSResolver::ResolutionStatus>(i);\n    status_[i] = std::make_unique<BaseStats::TLTimeseries>(\n        folly::to<std::string>(prefix, \"status.\", describe(status, false)),\n        RATE,\n        SUM);\n  }\n  for (uint8_t i = 0; i < 16; ++i) {\n    if (i < rcodeNames.size()) {\n      rcodes_[i] = std::make_unique<BaseStats::TLTimeseries>(\n          folly::to<std::string>(prefix, \"rcode.\", rcodeNames[i]), RATE, SUM);\n    } else {\n      rcodes_[i] = std::make_unique<BaseStats::TLTimeseries>(\n          folly::to<std::string>(prefix, \"rcode.\", i), RATE, SUM);\n    }\n  }\n}\n\nvoid AsyncDNSStatsCollector::recordSuccess(\n    const std::vector<DNSResolver::Answer>& answers,\n    std::chrono::milliseconds latency) noexcept {\n  bool isStale = false;\n  for (auto& a : answers) {\n    if (a.ttl.count() <= 0) {\n      isStale = true;\n      break;\n    }\n  }\n  if (!isStale) {\n    reqs_.add(1);\n    success_.add(1);\n  } else {\n    reqs_.add(0);\n    success_.add(0);\n  }\n  error_.add(0);\n  timeouts_.add(0);\n  nodata_.add(0);\n  latency_.add(latency.count());\n  status_[static_cast<size_t>(DNSResolver::OK)]->add(1);\n\n  size_t countA = 0, countAAAA = 0, countCNAME = 0;\n  for (auto& ans : answers) {\n    if (ans.type == DNSResolver::Answer::AT_ADDRESS) {\n      ttl_.add(ans.ttl.count());\n\n      switch (ans.address.getFamily()) {\n        case AF_INET:\n          ++countA;\n          break;\n\n        case AF_INET6:\n          ++countAAAA;\n          break;\n\n        default:\n          LOG(INFO) << \"Ignoring unexpected address family \"\n                    << ans.address.getFamily();\n          break;\n      }\n    } else if (ans.type == DNSResolver::Answer::AT_CNAME) {\n      ttl_.add(ans.ttl.count());\n      ++countCNAME;\n    }\n  }\n\n  if (countA > 0) {\n    answersA_.add(countA);\n  }\n\n  if (countAAAA > 0) {\n    answersAAAA_.add(countAAAA);\n  }\n\n  if (countCNAME > 0) {\n    answersCNAME_.add(countCNAME);\n  }\n}\n\nvoid AsyncDNSStatsCollector::recordError(\n    const folly::exception_wrapper& ew,\n    std::chrono::milliseconds latency) noexcept {\n  DNSResolver::ResolutionStatus status = DNSResolver::UNKNOWN;\n\n  ew.with_exception([&](const DNSResolver::Exception& ex) {\n    status = ex.status();\n    LOG_EVERY_N(WARNING, 100) << status << \" : \" << ex.what();\n  });\n\n  reqs_.add(1);\n  success_.add(0);\n  if (status == DNSResolver::NODATA) {\n    // Highly probable that non-existent hostnames were used by the client.\n    nodata_.add(1);\n    error_.add(0);\n    timeouts_.add(0);\n  } else if (status == DNSResolver::TIMEOUT) {\n    // TODO(jls): Timeouts are errors, they shouldn't be ultimately excluded.\n    // Restore this to its original state once timeouts in Fwdproxy have been\n    // resolved.\n    timeouts_.add(1);\n    error_.add(0);\n    nodata_.add(0);\n  } else {\n    error_.add(1);\n    nodata_.add(0);\n    timeouts_.add(0);\n  }\n\n  latency_.add(latency.count());\n  status_[static_cast<size_t>(status)]->add(1);\n}\n\nvoid AsyncDNSStatsCollector::recordQueryResult(uint8_t rcode) noexcept {\n  // RFC1035 defines the range of rcode values as [0, 16).\n  if (rcode > 15) {\n    LOG(DFATAL) << \"Invalid rcode: \" << rcode;\n    return;\n  }\n\n  rcodes_[rcode]->add(1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/AsyncDNSStatsCollector.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include <proxygen/lib/dns/DNSResolver.h>\n#include <proxygen/lib/stats/BaseStats.h>\n\nnamespace proxygen {\n\n/**\n * A DNSResolver::StatsCollector instance that uses thread-local stats for\n * aggregation and reporting.\n *\n * DNS Stats description:\n * ----------------------\n *\n * 1) DNS Cache stats:\n * - cache_hit: Lookup successful in the DNS cache\n *\n * - cache_miss: Entry not present in the cache\n *\n * - cache_partial_miss: Entry present for the required family, but is expired\n *\n * - stale_cache_hit: Lookup performed successfully from the stale cache\n *\n * 2) DNS lookups:\n * - reqs: Total outbound DNS lookups. These are request which werent handled by\n *   the DNS cache. Typeicaly reqs = cache_miss + cache_partial_miss\n *\n * - success: Answers received successfuly from the DNS servers.\n *\n * - nodata: DNS server replied, but with no answer. This can happen if the\n *   client provided wrong hostname, or if the DNS servers were misconfigured.\n *   We use the status codes ARES_ENODATA (DNS server returned answer with\n *   no data) and ARES_ENOTFOUND (Domain name not found), to identify\n *   these responses.\n *\n * - error: Any other errors during DNS lookup.\n *\n * 3) Individual DNS queries:\n * - rcode.(0-15): The lookups could be split into individual DNS queries for\n *   A and AAAA records. These stats describe the return codes of the\n *   individual queries.\n *\n */\nclass AsyncDNSStatsCollector : public DNSResolver::StatsCollector {\n public:\n  explicit AsyncDNSStatsCollector(const std::string& prefix);\n  ~AsyncDNSStatsCollector() override = default;\n\n  // DNSResolver::StatsCollector\n  void recordSuccess(const std::vector<DNSResolver::Answer>& answers,\n                     std::chrono::milliseconds latency) noexcept override;\n  void recordError(const folly::exception_wrapper& ew,\n                   std::chrono::milliseconds latency) noexcept override;\n  void recordQueryResult(uint8_t rcode) noexcept override;\n\n  void recordCacheHit() noexcept override {\n    cacheHits_.add(1);\n  }\n\n  void recordCacheMiss() noexcept override {\n    cacheMisses_.add(1);\n  }\n\n  void recordCachePartialMiss() noexcept override {\n    cachePartialMisses_.add(1);\n  }\n\n  void recordStaleCacheHit() noexcept override {\n    staleCacheHits_.add(1);\n  }\n\n private:\n  BaseStats::TLTimeseries reqs_;\n  BaseStats::TLTimeseries success_;\n  BaseStats::TLTimeseries error_;\n  BaseStats::TLTimeseries timeouts_;\n  BaseStats::TLTimeseries nodata_;\n  BaseStats::TLTimeseries answersA_;\n  BaseStats::TLTimeseries answersAAAA_;\n  BaseStats::TLTimeseries answersCNAME_;\n  BaseStats::TLHistogram latency_;\n  BaseStats::TLHistogram ttl_;\n  std::vector<std::unique_ptr<BaseStats::TLTimeseries>> status_;\n  std::vector<std::unique_ptr<BaseStats::TLTimeseries>> rcodes_;\n\n  BaseStats::BaseStats::TLTimeseries cacheHits_;\n  BaseStats::TLTimeseries cacheMisses_;\n  BaseStats::TLTimeseries cachePartialMisses_;\n  BaseStats::TLTimeseries staleCacheHits_;\n\n  void recordStatus(DNSResolver::ResolutionStatus status);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/CAresResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/CAresResolver.h\"\n\n#include <folly/Conv.h>\n#include <folly/io/async/EventHandler.h>\n#include <folly/portability/Sockets.h>\n#include <glog/logging.h>\n#include <proxygen/lib/utils/Time.h>\n\nusing namespace proxygen;\n\nusing folly::AsyncTimeout;\nusing folly::DelayedDestruction;\nusing folly::EventBase;\nusing folly::EventHandler;\nusing folly::SocketAddress;\nusing std::list;\nusing std::make_exception_ptr;\nusing std::unique_ptr;\nusing std::vector;\n\n/* Make sure C-Ares error codes that map to DNS RCODE values are unchanged */\nstatic_assert(ARES_SUCCESS == 0 && ARES_EREFUSED == 6,\n              \"C-Ares status does not map to DNS RCODE values\");\n\n/* Convert a C-Ares status value to an DNS RCODE; -1 for unknown */\n#define ARES_TO_RCODE(x) (((x) <= ARES_EREFUSED) ? (x) : -1)\n\nnamespace {\nstruct HostentDeleter {\n  void operator()(hostent* ptr) {\n    ares_free_hostent(ptr);\n  }\n};\n\ntemplate <class T>\nstruct AresDataDeleter {\n  void operator()(T* ptr) {\n    ares_free_data(ptr);\n  }\n};\n\ntemplate <class T>\nusing AresDataUniquePtr = std::unique_ptr<T, AresDataDeleter<T>>;\n\nclass NullStatsCollector : public DNSResolver::StatsCollector {\n public:\n  NullStatsCollector() = default;\n  ~NullStatsCollector() override = default;\n  void recordSuccess(const std::vector<DNSResolver::Answer>& /*answers*/,\n                     std::chrono::milliseconds /*latency*/) noexcept override {\n  }\n  void recordError(const folly::exception_wrapper& /*ew*/,\n                   std::chrono::milliseconds /*latency*/) noexcept override {\n  }\n  void recordQueryResult(uint8_t /*rcode*/) noexcept override {\n  }\n};\n\nNullStatsCollector nullStatsCollector;\n} // namespace\n\nCAresResolver::Query::Query(CAresResolver* resolver,\n                            RecordType type,\n                            const std::string& name,\n                            bool recordStats,\n                            TraceEvent dnsEvent,\n                            const TimeUtil* timeUtil,\n                            TraceEventContext teContext)\n    : AsyncTimeout(resolver->base_),\n      resolver_(resolver),\n      type_(type),\n      name_(name),\n      recordStats_(recordStats),\n      timeUtil_(timeUtil),\n      dnsEvent_(std::move(dnsEvent)),\n      teContext_(std::move(teContext)) {\n  dnsEvent_.addMeta(TraceFieldType::HostName, name);\n}\n\nvoid CAresResolver::Query::resolve(ResolutionCallback* cb,\n                                   std::chrono::milliseconds timeout) {\n  CHECK(callback_ == nullptr);\n  CHECK(cb != nullptr);\n\n  dnsEvent_.start(*timeUtil_);\n\n  callback_ = cb;\n  callback_->insertQuery(this);\n  startTime_ = getCurrentTime();\n  if (timeout.count() > 0 && !scheduleTimeout(timeout.count())) {\n    LOG(DFATAL) << \"Failed to schedule timeout for query \" << name_\n                << \" with type \" << static_cast<int>(type_);\n  }\n  resolver_->query(name_, type_, Query::queryCallback, this);\n}\n\nvoid CAresResolver::Query::cancelResolutionImpl() {\n  callback_ = nullptr;\n  cancelTimeout();\n  resolver_->queryFinished();\n}\n\nvoid CAresResolver::Query::fail(ResolutionStatus status,\n                                const std::string& msg) {\n  if (callback_) {\n    CAresResolver* resolver = resolver_;\n    ResolutionCallback* cb = callback_;\n    std::chrono::milliseconds resolutionTime = millisecondsSince(startTime_);\n    auto ew =\n        folly::make_exception_wrapper<Exception>(status, msg + \" for \" + name_);\n\n    dnsEvent_.end(*timeUtil_);\n    dnsEvent_.addMeta(TraceFieldType::Error, msg);\n    dnsEvent_.addMeta(TraceFieldType::CNameRedirects, cnameResolutions_);\n    teContext_.traceEventAvailable(std::move(dnsEvent_));\n\n    resolver->queryFinished();\n    if (recordStats_) {\n      resolver->getStatsCollector()->recordError(ew, resolutionTime);\n    }\n\n    cb->eraseQuery(this);\n    cb->resolutionError(ew);\n  }\n\n  delete this;\n}\n\nvoid CAresResolver::Query::succeed(std::vector<Answer> answers) {\n  if (callback_) {\n    CAresResolver* resolver = resolver_;\n    ResolutionCallback* cb = callback_;\n    std::chrono::milliseconds resolutionTime = millisecondsSince(startTime_);\n\n    dnsEvent_.end(*timeUtil_);\n\n    std::map<TraceFieldType, std::string> traces;\n    if (!answers.empty()) {\n      for (const auto& answer : answers) {\n        std::string dnsResult;\n        switch (answer.type) {\n          case Answer::AnswerType::AT_TXT:\n            break;\n          case Answer::AnswerType::AT_ADDRESS:\n            dnsResult = answer.address.getAddressStr() + \" \";\n            break;\n          default:\n            dnsResult = answer.name + \" \";\n        }\n\n        TraceFieldType lookupType = TraceFieldType::HostName;\n        switch (answer.type) {\n          case Answer::AnswerType::AT_TXT:\n            lookupType = TraceFieldType::TXT;\n            break;\n          case Answer::AnswerType::AT_ADDRESS:\n            lookupType = TraceFieldType::IpAddr;\n            break;\n          default:\n            lookupType = TraceFieldType::HostName;\n        }\n        traces[lookupType] += dnsResult;\n      }\n      for (auto& trace : traces) {\n        dnsEvent_.addMeta(trace.first, trace.second);\n      }\n    } else {\n      dnsEvent_.addMeta(TraceFieldType::IpAddr, \"No Results\");\n    }\n\n    dnsEvent_.addMeta(TraceFieldType::NumberAnswers, answers.size());\n    dnsEvent_.addMeta(TraceFieldType::CNameRedirects, cnameResolutions_);\n    teContext_.traceEventAvailable(std::move(dnsEvent_));\n\n    if (recordStats_) {\n      resolver->getStatsCollector()->recordSuccess(answers, resolutionTime);\n    }\n\n    resolver->queryFinished();\n    cb->eraseQuery(this);\n    cb->resolutionSuccess(std::move(answers));\n  }\n  delete this;\n}\n\n/**\n * If the DNS response contains no A/AAAA records, check for any cname\n * aliases.  If one exists, perform a recursive DNS query.  Otherwise,\n * invoke an appropriate error or success callback.\n */\nvoid CAresResolver::Query::checkForCName(Query* self, hostent* host) {\n  // C-ARES puts the CNAME in the host->h_name field\n  if (host && host->h_name && self->name_ != host->h_name) {\n    if (self->cnameResolutions_++ >= DNSResolver::kMaxCnameResolutions) {\n      self->fail(TOO_MANY_REDIRECTS,\n                 \"Query failed due to too many CNAME redirects\");\n      return;\n    }\n\n    if (self->callback_ == nullptr) {\n      self->fail(TIMEOUT, \"Query failed due to timeout\");\n      return;\n    }\n\n    // Copy the resolver pointer before calling query() since self may be been\n    // destructed synchronously\n    // Issue a recursive query for the cname host\n    auto* resolver = self->resolver_;\n    resolver->query(host->h_name, self->type_, Query::queryCallback, self);\n    resolver->queryFinished();\n    return;\n  }\n\n  // No results are treated as \"success\"\n  self->succeed({});\n}\n\nvoid CAresResolver::Query::queryCallback(\n    void* data, int status, int /*timeouts*/, unsigned char* abuf, int alen) {\n  auto* self = static_cast<Query*>(data);\n\n  // Record the RCODE value for this query. Note that we ignore the\n  // recordStats_ stat, as that's only for aggregated end-of-resolution\n  // stats, not per-query stats\n  int8_t rcode = ARES_TO_RCODE(status);\n  if (rcode >= 0) {\n    self->resolver_->getStatsCollector()->recordQueryResult(rcode);\n  }\n\n  // ARES_ENOTFOUND and ARES_ENODATA are special cases that indicate\n  // no results. Just call it success but with an empty results list.\n  if (status == ARES_ENOTFOUND || status == ARES_ENODATA) {\n    self->succeed({});\n    return;\n  }\n\n  // All other Ares errors are fatal.\n  if (status != ARES_SUCCESS) {\n    switch (status) {\n      case ARES_ECONNREFUSED:\n        self->fail(CONN_REFUSED, ares_strerror(status));\n        break;\n      case ARES_EFORMERR:\n      case ARES_ESERVFAIL:\n      case ARES_ENOTIMP:\n      case ARES_EREFUSED:\n        self->fail(SERVER_OTHER, ares_strerror(status));\n        break;\n      case ARES_EBADQUERY:\n      case ARES_EBADNAME:\n      case ARES_EBADFAMILY:\n      case ARES_EBADRESP:\n      case ARES_EOF:\n      case ARES_EFILE:\n      case ARES_ENOMEM:\n      case ARES_EDESTRUCTION:\n      case ARES_EBADSTR:\n        self->fail(CLIENT_OTHER, ares_strerror(status));\n        break;\n      case ARES_ECANCELLED:\n        self->fail(CANCELLED, ares_strerror(status));\n        break;\n      case ARES_ETIMEOUT:\n        self->fail(TIMEOUT,\n                   folly::to<std::string>(\"Underlying c-ares timeout: \",\n                                          ares_strerror(status)));\n        break;\n      default:\n        self->fail(UNKNOWN, ares_strerror(status));\n    }\n    return;\n  }\n\n  std::vector<Answer> answers;\n\n  switch (self->type_) {\n    case RecordType::kA: {\n      ares_addrttl ttls[kMaxRecords];\n      int nttls = kMaxRecords;\n\n      hostent* hst = nullptr;\n      status = ares_parse_a_reply(abuf, alen, &hst, ttls, &nttls);\n      unique_ptr<hostent, HostentDeleter> host(hst);\n\n      if (status == ARES_SUCCESS) {\n        sockaddr_in saddr{};\n        saddr.sin_family = AF_INET;\n\n        auto cname = host && host->h_name ? std::string(host->h_name) : \"\";\n\n        for (int i = 0; i < nttls; i++) {\n          memcpy(&saddr.sin_addr, &ttls[i].ipaddr, sizeof(in_addr));\n          Answer ans(std::chrono::seconds(ttls[i].ttl),\n                     reinterpret_cast<sockaddr*>(&saddr));\n          ans.name = self->name_;\n          ans.canonicalName = cname;\n          answers.push_back(std::move(ans));\n        }\n\n        // If we got no responses, check for a CNAME\n        if (nttls == 0) {\n          checkForCName(self, host.get());\n          return;\n        }\n      } else {\n        self->fail(PARSE_ERROR,\n                   folly::to<std::string>(\"Failed to parse A answer \", status));\n        return;\n      }\n\n      break;\n    }\n\n    case RecordType::kAAAA: {\n      ares_addr6ttl ttls[kMaxRecords];\n      int nttls = kMaxRecords;\n\n      hostent* hst = nullptr;\n      status = ares_parse_aaaa_reply(abuf, alen, &hst, ttls, &nttls);\n      unique_ptr<hostent, HostentDeleter> host(hst);\n\n      if (status == ARES_SUCCESS) {\n        sockaddr_in6 saddr{};\n        saddr.sin6_family = AF_INET6;\n\n        auto cname = host && host->h_name ? std::string(host->h_name) : \"\";\n\n        for (int i = 0; i < nttls; i++) {\n          memcpy(&saddr.sin6_addr, &ttls[i].ip6addr, sizeof(ares_in6_addr));\n          Answer ans(std::chrono::seconds(ttls[i].ttl),\n                     reinterpret_cast<sockaddr*>(&saddr));\n          ans.name = self->name_;\n          ans.canonicalName = cname;\n          answers.push_back(std::move(ans));\n        }\n\n        // If we got no responses, check for a CNAME\n        if (nttls == 0) {\n          checkForCName(self, host.get());\n          return;\n        }\n      } else {\n        self->fail(\n            PARSE_ERROR,\n            folly::to<std::string>(\"Failed to parse AAAA answer \", status));\n        return;\n      }\n\n      break;\n    }\n\n    case RecordType::kPtr: {\n      hostent* hst = nullptr;\n\n      // family actually doesn't matter since addr and addrlen are empty, family\n      // is only used when the original input addr is used. At some point we\n      // should probably retain the original input address\n      status = ares_parse_ptr_reply(abuf, alen, nullptr, 0, AF_INET6, &hst);\n      unique_ptr<hostent, HostentDeleter> host(hst);\n\n      if (status == ARES_SUCCESS) {\n        for (char** aliasp = host->h_aliases; *aliasp != nullptr; aliasp++) {\n          answers.emplace_back(std::chrono::seconds(60), *aliasp);\n        }\n      } else {\n        self->fail(\n            PARSE_ERROR,\n            folly::to<std::string>(\"Failed to parse PTR answer \", status));\n        return;\n      }\n\n      break;\n    }\n\n    case RecordType::kTXT: {\n      auto result = detail::parseTxtRecords(abuf, alen);\n      if (result.hasError()) {\n        self->fail(result.error().status, result.error().msg);\n        return;\n      } else {\n        answers = std::move(result).value();\n      }\n      break;\n    }\n\n    case RecordType::kSRV: {\n      auto result = detail::parseSrvRecords(abuf, alen);\n      if (result.hasError()) {\n        self->fail(result.error().status, result.error().msg);\n        return;\n      } else {\n        answers = std::move(result).value();\n      }\n      break;\n    }\n\n    case RecordType::kMX: {\n      auto result = detail::parseMxRecords(abuf, alen);\n      if (result.hasError()) {\n        self->fail(result.error().status, result.error().msg);\n        return;\n      } else {\n        answers = std::move(result).value();\n      }\n      break;\n    }\n\n    default:\n      LOG(ERROR) << \"Couldn't handle answer for query type \"\n                 << static_cast<int>(self->type_) << \", during resolving \"\n                 << self->name_;\n      self->fail(\n          PARSE_ERROR,\n          folly::to<std::string>(\"Failed to parse answer for query type: \",\n                                 static_cast<int>(self->type_)));\n      return;\n  }\n\n  for (auto& answer : answers) {\n    answer.resolverType = ResolverType::CARES;\n  }\n\n  self->succeed(std::move(answers));\n}\n\n// Don't use fail() here, as that would delete the object and we need to\n// leave it around for the query callback to complete\nvoid CAresResolver::Query::timeoutExpired() noexcept {\n  ResolutionCallback* cb = callback_;\n\n  callback_ = nullptr;\n  resolver_->queryFinished();\n  if (!cb) {\n    return;\n  }\n\n  CAresResolver* resolver = resolver_;\n  auto ew =\n      folly::make_exception_wrapper<Exception>(TIMEOUT, \"Query timed out\");\n  std::chrono::milliseconds resolutionTime = millisecondsSince(startTime_);\n  if (recordStats_) {\n    resolver->getStatsCollector()->recordError(ew, resolutionTime);\n  }\n\n  cb->eraseQuery(this);\n  cb->resolutionError(ew);\n}\n\nnamespace proxygen {\n// Helper class for issuing a multiple queries in parallel and aggregating the\n// responses.\n//\n// Since this operates on multiple queries, the semantics for the resolution\n// callbacks are a bit different: the query terminates when all resolutions\n// have completed (timed out, succeeded, etc); the query invokes its error\n// callback only if there were no results and at least one of the requests\n// failed.\n//\n// This is used for querying both IPv4 and IPv6 addresses at the same time and\n// returning whatever results were were able to find.\nclass CAresResolver::MultiQuery\n    : public QueryBase\n    , private DNSResolver::ResolutionCallback {\n public:\n  MultiQuery(CAresResolver* resolver, std::string name)\n      : resolver_(resolver), name_(name) {\n  }\n\n  ~MultiQuery() override = default;\n\n  void resolve(ResolutionCallback* callback,\n               const std::list<Query*>& queries,\n               std::chrono::milliseconds timeout) {\n    CHECK(callback_ == nullptr);\n    CHECK(callback != nullptr);\n    CHECK_EQ(0, queries_);\n\n    callback_ = callback;\n    callback_->insertQuery(this);\n    queries_ = queries.size();\n    startTime_ = getCurrentTime();\n\n    for (auto q : queries) {\n      insertQuery(q);\n      q->resolve(this, timeout);\n    }\n  }\n\n  // QueryBase method\n  void cancelResolutionImpl() override {\n    callback_ = nullptr;\n    // Cancel all outstanding sub-queries (ResolutionCallback interface)\n    this->cancelResolution();\n\n    // Deletes this object, but any outstanding sub-queries will hang around to\n    // eat the replies\n    finish();\n  }\n\n private:\n  CAresResolver* resolver_;\n  ResolutionCallback* callback_{nullptr};\n  TimePoint startTime_;\n  folly::exception_wrapper error_;\n  std::string name_;\n  std::vector<Answer> answers_;\n  uint16_t queries_{0};\n\n  void resolutionSuccess(std::vector<Answer> a) noexcept override {\n    --queries_;\n    answers_.insert(answers_.end(), a.begin(), a.end());\n\n    // Still waiting for some queries to terminate\n    if (queries_ > 0) {\n      return;\n    }\n\n    if (answers_.empty() && !error_) {\n      error_ = folly::make_exception_wrapper<Exception>(\n          NODATA, \"No answer in MultiQuery for \" + name_);\n    }\n\n    finish();\n  }\n\n  void resolutionError(const folly::exception_wrapper& ew) noexcept override {\n    --queries_;\n\n    // NOTE this will overwrite an existing error.\n    if (ew) {\n      error_ = ew;\n    }\n\n    // Still waiting for some queries to terminate. Track what went wrong so\n    // that we can use it later\n    if (queries_ > 0) {\n      return;\n    }\n\n    finish();\n  }\n\n  void finish() {\n    if (callback_) {\n      ResolutionCallback* cb = callback_;\n      CAresResolver* resolver = resolver_;\n      std::chrono::milliseconds resolutionTime = millisecondsSince(startTime_);\n      std::vector<Answer> answers(std::move(answers_));\n      folly::exception_wrapper error = error_;\n\n      callback_ = nullptr;\n      cb->eraseQuery(this);\n\n      if (!answers.empty()) {\n        resolver->getStatsCollector()->recordSuccess(answers, resolutionTime);\n        cb->resolutionSuccess(std::move(answers));\n      } else {\n        if (!error) {\n          error = folly::make_exception_wrapper<Exception>(\n              UNKNOWN, \"Unknown error in MultiQuery for \" + name_);\n        }\n\n        resolver->getStatsCollector()->recordError(error, resolutionTime);\n        cb->resolutionError(error);\n      }\n    }\n\n    delete this;\n  }\n};\n\n// Helper class for managing DNS sockets\nclass CAresResolver::SocketHandler : public folly::EventHandler {\n public:\n  SocketHandler(CAresResolver* resolver,\n                folly::EventBase* base,\n                folly::NetworkSocket sock,\n                ares_channel channel)\n      : EventHandler(base, sock),\n        resolver_(resolver),\n        sock_(sock),\n        channel_(channel) {\n  }\n  ~SocketHandler() override = default;\n\n  void handlerReady(uint16_t events) noexcept override {\n    ares_socket_t rsock =\n        (events & EventHandler::READ) ? sock_.data : ARES_SOCKET_BAD;\n    ares_socket_t wsock =\n        (events & EventHandler::WRITE) ? sock_.data : ARES_SOCKET_BAD;\n\n    DelayedDestruction::DestructorGuard dg(resolver_);\n    ares_process_fd(channel_, rsock, wsock);\n  }\n\n private:\n  CAresResolver* resolver_;\n  folly::NetworkSocket sock_;\n  ares_channel channel_;\n};\n\nCAresResolver::CAresResolver()\n    : base_(nullptr),\n      channel_(),\n      channelRefcnt_(0),\n      socketHandlers_(),\n      servers_(),\n      port_(0),\n      statsCollector_(&nullStatsCollector) {\n}\n\nCAresResolver::~CAresResolver() {\n  if (channel_) {\n    ares_destroy(channel_);\n  }\n\n  // Once ares_destroy() has run, it should have invoked dnsSocketReady() on\n  // all of our sockets to clean them up\n  LOG_IF(DFATAL, !socketHandlers_.empty())\n      << \"Found orphaned sockets after ares_destroy()\";\n}\n\nvoid CAresResolver::attachEventBase(EventBase* base) {\n  LOG_IF(DFATAL, base_ != nullptr)\n      << \"Overwriting existing non-nullptr EventBase\";\n\n  base_ = base;\n}\n\nconst folly::EventBase* CAresResolver::getEventBase() {\n  return base_;\n}\n\nvoid CAresResolver::setSerializedServers() {\n  serializedResolvers_ = \"\";\n  for (auto& server : servers_) {\n    serializedResolvers_ += server.getAddressStr();\n    serializedResolvers_ += \";\";\n  }\n}\n\nvoid CAresResolver::setServers(const list<SocketAddress>& servers) {\n  servers_ = servers;\n}\n\nvoid CAresResolver::setPort(uint16_t port) {\n  port_ = port;\n}\n\nvoid CAresResolver::setStatsCollector(DNSResolver::StatsCollector* sc) {\n  statsCollector_ = sc;\n}\n\nDNSResolver::StatsCollector* CAresResolver::getStatsCollector() const {\n  return statsCollector_;\n}\n\nvoid CAresResolver::init() {\n  CHECK(base_ != nullptr);\n\n  // Initialize our channel\n  int optmask = 0;\n  ares_options opts{};\n\n  opts.sock_state_cb = CAresResolver::dnsSocketReady;\n  opts.sock_state_cb_data = this;\n  optmask |= ARES_OPT_SOCK_STATE_CB;\n\n  opts.lookups = const_cast<char*>(\"b\");\n  optmask |= ARES_OPT_LOOKUPS;\n\n  opts.flags = ARES_FLAG_STAYOPEN;\n  optmask |= ARES_OPT_FLAGS;\n\n  // Set the timeout to something obscenely large that will \"never\" fire since\n  // we're managing timeouts ourself on a per-request basis\n  opts.timeout = kMaxTimeout.count();\n  optmask |= ARES_OPT_TIMEOUTMS;\n\n  if (port_) {\n    opts.udp_port = opts.tcp_port = port_;\n    optmask |= ARES_OPT_UDP_PORT | ARES_OPT_TCP_PORT;\n  }\n\n  int err = ares_init_options(&channel_, &opts, optmask);\n  if (err != ARES_SUCCESS) {\n    LOG(DFATAL) << \"ares_init_options() failed: \" << ares_strerror(err);\n    return;\n  }\n\n  // Set nameservers that we want to use\n  if (!servers_.empty()) {\n    setSerializedServers();\n\n    unique_ptr<ares_addr_node[]> ares_addrs(\n        new ares_addr_node[servers_.size()]);\n\n    ares_addr_node* ares_addr = &ares_addrs[0];\n    for (const auto& server : servers_) {\n      ares_addr->next = nullptr;\n      if (ares_addr > &ares_addrs[0]) {\n        (ares_addr - 1)->next = ares_addr;\n      }\n\n      ares_addr->family = server.getFamily();\n\n      switch (ares_addr->family) {\n        case AF_INET:\n          ares_addr->addr.addr4 = server.getIPAddress().asV4().toAddr();\n          break;\n\n        case AF_INET6: {\n          in6_addr addr6 = server.getIPAddress().asV6().toAddr();\n          memcpy(&ares_addr->addr.addr6, &addr6, sizeof(addr6));\n          break;\n        }\n\n        default:\n          LOG(DFATAL) << \"Unknown address type \" << ares_addr->family\n                      << \"; failing to change nameservers\";\n          return;\n      }\n\n      ++ares_addr;\n    }\n\n    err = ares_set_servers(channel_, ares_addrs.get());\n    if (err != ARES_SUCCESS) {\n      LOG(DFATAL) << \"ares_set_servers() failed: \" << ares_strerror(err);\n      return;\n    }\n  }\n}\n\nvoid CAresResolver::resolveAddress(DNSResolver::ResolutionCallback* cb,\n                                   const SocketAddress& address,\n                                   std::chrono::milliseconds timeout) {\n  if (timeout > kMaxTimeout) {\n    LOG(WARNING) << \"Attempt to resolve \" << address.getAddressStr()\n                 << \" specified with \" << \"timeout of \" << timeout.count()\n                 << \"ms; \" << \"clamping to \" << kMaxTimeout.count() << \"ms\";\n    timeout = kMaxTimeout;\n  }\n\n  if (address.getFamily() != AF_INET && address.getFamily() != AF_INET6) {\n    LOG(ERROR) << \"Unsupported address family \" << address.getFamily();\n    auto ew = folly::make_exception_wrapper<Exception>(\n        INVALID,\n        folly::to<std::string>(\"Unsupported address family: \",\n                               address.getFamily()));\n    cb->resolutionError(ew);\n    return;\n  }\n\n  /* TraceEvent Initialization */\n  TraceEventContext teContext = TraceEventContext();\n  TraceEvent dnsEvent =\n      TraceEvent(TraceEventType::DnsResolution, teContext.parentID);\n  dnsEvent.addMeta(TraceFieldType::NumberResolvers, servers_.size());\n  dnsEvent.addMeta(TraceFieldType::RequestFamily, address.getFamily());\n\n  auto q = new Query(this,\n                     RecordType::kPtr,\n                     DNSResolver::getPtrName(address),\n                     true,\n                     std::move(dnsEvent),\n                     &timeUtil_,\n                     std::move(teContext));\n  q->resolve(cb, timeout);\n}\n\nbool CAresResolver::resolveLocalhost(ResolutionCallback* cb,\n                                     const std::string& host,\n                                     sa_family_t family) {\n  if (host != \"localhost\") {\n    return false;\n  }\n\n  // Resolve localhost because we can't use /etc/hosts\n  vector<Answer> answers;\n  const std::string preferredAddr = (family == AF_INET6) ? \"::1\" : \"127.0.0.1\";\n  const std::string otherAddr = (preferredAddr == \"::1\") ? \"127.0.0.1\" : \"::1\";\n\n  if (family == AF_UNSPEC) {\n    // Return both addresses if they are loopback\n    SocketAddress prefSAddr(preferredAddr, 0);\n    SocketAddress otherSAddr(otherAddr, 0);\n\n    if (prefSAddr.isLoopbackAddress()) {\n      Answer ans(std::chrono::seconds(kLiteralTTL), std::move(prefSAddr));\n      answers.push_back(std::move(ans));\n    }\n\n    if (otherSAddr.isLoopbackAddress()) {\n      Answer ans(std::chrono::seconds(kLiteralTTL), std::move(otherSAddr));\n      answers.push_back(std::move(ans));\n    }\n\n  } else {\n    SocketAddress addr(preferredAddr, 0);\n    if (!addr.isLoopbackAddress()) {\n      addr = SocketAddress(otherAddr, 0);\n    }\n\n    Answer ans(std::chrono::seconds(kLiteralTTL), std::move(addr));\n    answers.push_back(std::move(ans));\n  }\n\n  if (answers.empty()) {\n    auto ew = folly::make_exception_wrapper<Exception>(\n        NODATA, \"No resolution for Localhost\");\n    cb->resolutionError(ew);\n  } else {\n    cb->resolutionSuccess(std::move(answers));\n  }\n  return true;\n}\n\nvoid CAresResolver::resolveHostname(DNSResolver::ResolutionCallback* cb,\n                                    const std::string& host,\n                                    std::chrono::milliseconds timeout,\n                                    sa_family_t family,\n                                    TraceEventContext teContext) {\n  if (resolveLiterals(cb, host, family)) {\n    return;\n  }\n\n  if (resolveLocalhost(cb, host, family)) {\n    return;\n  }\n\n  if (timeout > kMaxTimeout) {\n    LOG(WARNING) << \"Attempt to resolve \" << host << \" specified with \"\n                 << \"timeout of \" << timeout.count() << \"ms; clamping to \"\n                 << kMaxTimeout.count() << \"ms\";\n    timeout = kMaxTimeout;\n  }\n\n  TraceEvent dnsEvent =\n      TraceEvent(TraceEventType::DnsResolution, teContext.parentID);\n  dnsEvent.addMeta(TraceFieldType::NumberResolvers, servers_.size());\n  dnsEvent.addMeta(TraceFieldType::ResolversSerialized, serializedResolvers_);\n  dnsEvent.addMeta(TraceFieldType::RequestFamily, family);\n\n  if (resolveSRVRecord_) {\n    auto q = new Query(this,\n                       RecordType::kSRV,\n                       host,\n                       true,\n                       std::move(dnsEvent),\n                       &timeUtil_,\n                       std::move(teContext));\n    cb->insertQuery(q);\n    q->resolve(cb, std::chrono::milliseconds(timeout));\n  }\n\n  if (family == AF_INET) {\n    auto q = new Query(this,\n                       RecordType::kA,\n                       host,\n                       true,\n                       std::move(dnsEvent),\n                       &timeUtil_,\n                       std::move(teContext));\n    cb->insertQuery(q);\n    q->resolve(cb, std::chrono::milliseconds(timeout));\n  } else if (family == AF_INET6) {\n    auto q = new Query(this,\n                       RecordType::kAAAA,\n                       host,\n                       true,\n                       std::move(dnsEvent),\n                       &timeUtil_,\n                       std::move(teContext));\n    cb->insertQuery(q);\n    q->resolve(cb, std::chrono::milliseconds(timeout));\n  } else if (family == AF_UNSPEC) {\n    auto mq = new MultiQuery(this, host);\n    cb->insertQuery(mq);\n    mq->resolve(cb,\n                {new Query(this,\n                           RecordType::kA,\n                           host,\n                           false,\n                           dnsEvent,\n                           &timeUtil_,\n                           teContext),\n                 new Query(this,\n                           RecordType::kAAAA,\n                           host,\n                           false,\n                           dnsEvent,\n                           &timeUtil_,\n                           teContext)},\n                std::chrono::milliseconds(timeout));\n  } else {\n    LOG(DFATAL) << \"Unsupported family specified: \" << family;\n    auto ew = folly::make_exception_wrapper<Exception>(\n        INVALID,\n        folly::to<std::string>(\"Unsupported address family: \", family));\n    cb->resolutionError(ew);\n  }\n}\n\nvoid CAresResolver::resolveMailExchange(DNSResolver::ResolutionCallback* cb,\n                                        const std::string& domain,\n                                        std::chrono::milliseconds timeout) {\n  if (timeout > kMaxTimeout) {\n    LOG(WARNING) << \"Attempt to resolve mail exchange info for \" << domain\n                 << \" specified with \" << \"timeout of \" << timeout.count()\n                 << \"ms; \" << \"clamping to \" << kMaxTimeout.count() << \"ms\";\n    timeout = kMaxTimeout;\n  }\n\n  /* TraceEvent Initialization */\n  TraceEventContext teContext = TraceEventContext();\n  TraceEvent dnsEvent =\n      TraceEvent(TraceEventType::DnsResolution, teContext.parentID);\n  dnsEvent.addMeta(TraceFieldType::NumberResolvers, servers_.size());\n\n  auto q = new Query(this,\n                     RecordType::kMX,\n                     domain,\n                     true,\n                     std::move(dnsEvent),\n                     &timeUtil_,\n                     std::move(teContext));\n  q->resolve(cb, timeout);\n}\n\nbool CAresResolver::resolveLiterals(DNSResolver::ResolutionCallback* cb,\n                                    const std::string& host,\n                                    sa_family_t family) {\n  addrinfo hints{};\n  hints.ai_family = family;\n  hints.ai_socktype = SOCK_STREAM;\n  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_NUMERICHOST;\n\n  addrinfo* ainfos = nullptr;\n  if (getaddrinfo(host.c_str(), nullptr, &hints, &ainfos) != 0) {\n    return false;\n  }\n\n  vector<Answer> answers;\n  for (addrinfo* ai = ainfos; ai != nullptr; ai = ai->ai_next) {\n    Answer ans(std::chrono::seconds(kLiteralTTL), ai->ai_addr);\n    answers.push_back(std::move(ans));\n  }\n\n  freeaddrinfo(ainfos);\n\n  cb->resolutionSuccess(std::move(answers));\n  return true;\n}\n\nvoid CAresResolver::query(const std::string& name,\n                          RecordType type,\n                          ares_callback cb,\n                          void* cb_data) {\n  if (++channelRefcnt_ == 1) {\n    for (auto& shp : socketHandlers_) {\n      shp.second->registerHandler(shp.second->getRegisteredEvents());\n    }\n  }\n\n  ares_query(channel_,\n             name.c_str(),\n             1 /* ns_c_in */,\n             static_cast<int>(type),\n             cb,\n             cb_data);\n}\n\nvoid CAresResolver::queryFinished() {\n  if (channelRefcnt_ == 0) {\n    LOG(ERROR) << \"Invalid channel refcount in CAresResolver::queryFinished()\";\n    return;\n  }\n  if (--channelRefcnt_ == 0) {\n    for (auto& shp : socketHandlers_) {\n      shp.second->registerInternalHandler(shp.second->getRegisteredEvents());\n    }\n  }\n}\n\nvoid CAresResolver::dnsSocketReady(void* data,\n                                   ares_socket_t sock,\n                                   int read,\n                                   int write) {\n  auto* self = static_cast<CAresResolver*>(data);\n  auto it = self->socketHandlers_.find(sock);\n\n  // Ares is done with this socket; stop watching it\n  if (!read && !write) {\n    LOG_IF(DFATAL, it == self->socketHandlers_.end())\n        << \"dnsSocketReady() asked to close a socket that we don't kow about\";\n    if (it != self->socketHandlers_.end()) {\n      self->socketHandlers_.erase(it);\n    }\n\n    return;\n  }\n\n  // Find the EventHandler that's managing our socket, creating one if it does\n  // not already exist\n  SocketHandler* shp = nullptr;\n  if (it == self->socketHandlers_.end()) {\n    shp = new SocketHandler(\n        self, self->base_, folly::NetworkSocket(sock), self->channel_);\n    self->socketHandlers_[sock].reset(shp);\n  } else {\n    shp = it->second.get();\n  }\n\n  // Update the set of events that we want to listen for\n  uint16_t events = EventHandler::PERSIST;\n  events |= (read) ? EventHandler::READ : 0;\n  events |= (write) ? EventHandler::WRITE : 0;\n  if (!shp->registerHandler(events)) {\n    LOG(DFATAL) << \"Failed to register SocketHandler\";\n  }\n}\n\nvoid CAresResolver::initGlobal() {\n  int err = ares_library_init(ARES_LIB_INIT_ALL);\n  LOG_IF(FATAL, err != 0) << \"ares_library_init() failed\";\n}\n\nvoid CAresResolver::destroyGlobal() {\n  ares_library_cleanup();\n}\n\nnamespace detail {\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseTxtRecords(\n    unsigned char* aresBuffer, int bufferLen) noexcept {\n  std::vector<DNSResolver::Answer> answers;\n  struct ares_txt_reply* txts = nullptr;\n  auto status = ares_parse_txt_reply(aresBuffer, bufferLen, &txts);\n  AresDataUniquePtr<ares_txt_reply> txtsReply(txts);\n  if (status == ARES_SUCCESS) {\n    while (txts != nullptr) {\n      auto rawTxt = std::shared_ptr<folly::IOBuf>(\n          folly::IOBuf::copyBuffer(txts->txt, txts->length));\n      const std::chrono::seconds ttl = std::chrono::seconds(0);\n      answers.emplace_back(\n          ttl, rawTxt, DNSResolver::Answer::AnswerType::AT_TXT);\n      txts = txts->next;\n    }\n  } else {\n    ParseError err;\n    err.status = DNSResolver::ResolutionStatus::PARSE_ERROR;\n    err.msg = folly::to<std::string>(\"Failed to parse TXT answer \", status);\n    return folly::makeUnexpected(std::move(err));\n  }\n\n  return folly::makeExpected<ParseError>(std::move(answers));\n}\n\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseSrvRecords(\n    unsigned char* aresBuffer, int bufferLen) noexcept {\n  std::vector<DNSResolver::Answer> answers;\n  struct ares_srv_reply* srvs = nullptr;\n  auto status = ares_parse_srv_reply(aresBuffer, bufferLen, &srvs);\n  AresDataUniquePtr<ares_srv_reply> srvsReply(srvs);\n  if (status == ARES_SUCCESS) {\n    while (srvs != nullptr) {\n      const std::chrono::seconds ttl = std::chrono::seconds(0);\n      answers.emplace_back(\n          ttl, srvs->host, srvs->port, DNSResolver::Answer::AnswerType::AT_SRV);\n      srvs = srvs->next;\n    }\n  } else {\n    ParseError err;\n    err.status = DNSResolver::ResolutionStatus::PARSE_ERROR;\n    err.msg = folly::to<std::string>(\"Failed to parse SRV answer \", status);\n    return folly::makeUnexpected(std::move(err));\n  }\n\n  return folly::makeExpected<ParseError>(std::move(answers));\n}\n\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseMxRecords(\n    unsigned char* aresBuffer, int bufferLen) noexcept {\n  std::vector<DNSResolver::Answer> answers;\n  struct ares_mx_reply* mxs = nullptr;\n  auto status = ares_parse_mx_reply(aresBuffer, bufferLen, &mxs);\n  AresDataUniquePtr<ares_mx_reply> mxsReply(mxs);\n  if (status == ARES_SUCCESS) {\n    while (mxs != nullptr) {\n      // C-ares doesn't support yet parsing the ttl for MX records\n      // (https://github.com/c-ares/c-ares/issues/387).\n      // For the time being, I'm using an arbitrary ttl of 0 seconds,\n      // thus disabling any caching for the records (no caching is\n      // bad, but only from a performance perspective. Stale records, on\n      // the other hand, would violate the ttl set on the DNS server)\n      const std::chrono::seconds ttl = std::chrono::seconds(0);\n      answers.emplace_back(ttl, mxs->priority, mxs->host);\n      mxs = mxs->next;\n    }\n  } else {\n    ParseError err;\n    err.status = DNSResolver::ResolutionStatus::PARSE_ERROR;\n    err.msg = folly::to<std::string>(\"Failed to parse MX answer \", status);\n    return folly::makeUnexpected(std::move(err));\n  }\n\n  return folly::makeExpected<ParseError>(std::move(answers));\n}\n\n} // namespace detail\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/CAresResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/Windows.h> // make sure that NOMINMAX is defined for windows\n\n#include <chrono>\n#include <list>\n#include <map>\n#include <memory>\n\n#include <ares.h>\n#include <folly/Expected.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/async/AsyncTimeout.h>\n#include <folly/io/async/EventBase.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n/**\n * Class to perform asynchronous DNS resolutions.\n *\n * Implementation notes:\n *\n *  . No caching is performed; each call to resolve() will trigger a\n *    request to the configured nameserver\n *\n *  . Hostname mappings specified in /etc/hosts are not honored. This is an\n *    implementation artifact of the underlying DNS library, which performs\n *    blocking file I/O at resolution time to honor this file.\n *\n *  . DelayedDestruction is required because the underlying DNS library is not\n *    reentrant in all cases. Specifially, a call to ares_destroy() cannot run\n *    while ares_process_fd() is executing higher up the stack.\n *\n *  . Search domains in /etc/resolv.conf are not honored. This is because\n *    we have to use the ares_query() API to get TTL information, and it does\n *    not use the search domains. We can work around this if we need to by\n *    parsing /etc/resolv.conf ourselves, or by adding an API method to Ares to\n *    expose this.\n */\nnamespace detail {\nstruct ParseError {\n  DNSResolver::ResolutionStatus status;\n  std::string msg;\n};\n\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseTxtRecords(\n    unsigned char* aresBuffer, int alen) noexcept;\n\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseSrvRecords(\n    unsigned char* aresBuffer, int alen) noexcept;\n\nfolly::Expected<std::vector<DNSResolver::Answer>, ParseError> parseMxRecords(\n    unsigned char* aresBuffer, int alen) noexcept;\n} // namespace detail\n\nclass CAresResolver : public DNSResolver {\n public:\n  using UniquePtr = std::unique_ptr<CAresResolver, CAresResolver::Destructor>;\n\n  // ns_type isn't defined on all target platforms so we define this ourselves.\n  enum class RecordType : int {\n    kA = 1,\n    kAAAA = 28,\n    kPtr = 12,\n    kTXT = 16,\n    kSRV = 33,\n    kMX = 15,\n  };\n\n  // Helper class for handling a single query / answer + timeout.\n  //\n  // This mainly exists to handle dispatching of parsing for the different query\n  // types and corresponding conversion to Answer objects.\n  //\n  // The lifecycle for this object expires once the queryCallback() method has\n  // been called. Even if a timeout has fired earlier. This is because we can't\n  // cancel individual queries in Ares -- we just have to let it complete and\n  // ignore the result. To facilitate this, we clear the callback_ field once a\n  // timeout has fired and use this as an indication that we should ignore any\n  // subsequent result.\n  class Query\n      : public DNSResolver::QueryBase\n      , private folly::AsyncTimeout {\n   public:\n    Query(CAresResolver* resolver,\n          RecordType type,\n          const std::string& name,\n          bool recordStats,\n          TraceEvent dnsEvent,\n          const TimeUtil* timeUtil = nullptr,\n          TraceEventContext teContext = TraceEventContext());\n\n    ~Query() override = default;\n\n    void resolve(ResolutionCallback* cb, std::chrono::milliseconds timeout);\n    void cancelResolutionImpl() override;\n\n   protected:\n    // Don't use fail() here, as that would delete the object and we need to\n    // leave it around for the query callback to complete\n    void timeoutExpired() noexcept override;\n    void fail(ResolutionStatus status, const std::string& msg);\n\n    ResolutionCallback* callback_ = {nullptr};\n\n   private:\n    static const size_t kMaxRecords = 64;\n\n    CAresResolver* resolver_;\n    RecordType type_;\n    std::string name_;\n    int cnameResolutions_ = {0};\n    TimePoint startTime_;\n    bool recordStats_;\n    const TimeUtil* timeUtil_;\n\n    // TraceEvent for resolution\n    TraceEvent dnsEvent_;\n    TraceEventContext teContext_;\n\n    void succeed(std::vector<Answer> answers);\n\n    /**\n     * If the DNS response contains no A/AAAA records, check for any cname\n     * aliases.  If one exists, perform a recursive DNS query.  Otherwise,\n     * invoke an appropriate error or success callback.\n     */\n    static void checkForCName(Query* self, hostent* host);\n\n    static void queryCallback(\n        void* data, int status, int timeouts, unsigned char* abuf, int alen);\n  };\n\n  template <typename... Args>\n  static UniquePtr newResolver(Args&&... args) {\n    return UniquePtr(new CAresResolver(std::forward<Args>(args)...));\n  }\n\n  CAresResolver();\n\n  /**\n   * Use the given EventBase for scheduling I/O.\n   *\n   * Must be called before init().\n   */\n  void attachEventBase(folly::EventBase* base);\n  const folly::EventBase* getEventBase();\n\n  /**\n   * Set the DNS servers to use for resolution; overrides whatever is\n   * configured via /etc/resolv.conf.\n   *\n   * Note that this *only* sets the IP addresses of servers to connect to. It\n   * does *not* change the port to use. For that, use setPort(). Note that\n   * this implies that all servers must use the same port.\n   *\n   * Must be called before init().\n   */\n  void setServers(const std::list<folly::SocketAddress>& servers);\n\n  /**\n   * Set the port to use for DNS queries. The default is 53.\n   *\n   * Must be called before init().\n   */\n  void setPort(uint16_t port);\n\n  void setResolveSRVOnly(bool resolve) {\n    resolveSRVRecord_ = resolve;\n  }\n  [[nodiscard]] bool resolveSRVOnly() const {\n    return resolveSRVRecord_;\n  }\n\n  /**\n   * Initialize the resolver.\n   */\n  virtual void init();\n\n  // DNSResolver API\n  void resolveAddress(ResolutionCallback* cb,\n                      const folly::SocketAddress& address,\n                      std::chrono::milliseconds timeout =\n                          std::chrono::milliseconds(100)) override;\n  void resolveHostname(\n      ResolutionCallback* cb,\n      const std::string& name,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(100),\n      sa_family_t family = AF_INET,\n      TraceEventContext teContext = TraceEventContext()) override;\n  void resolveMailExchange(ResolutionCallback* cb,\n                           const std::string& domain,\n                           std::chrono::milliseconds timeout =\n                               std::chrono::milliseconds(100)) override;\n  void setStatsCollector(DNSResolver::StatsCollector* statsCollector) override;\n  [[nodiscard]] DNSResolver::StatsCollector* getStatsCollector() const override;\n\n  [[nodiscard]] const TimeUtil* getTimeUtilPtr() const {\n    return &timeUtil_;\n  }\n\n protected:\n private:\n  class SocketHandler;\n  class MultiQuery;\n\n  void setSerializedServers();\n\n  folly::EventBase* base_;\n  ares_channel channel_;\n  uint16_t channelRefcnt_;\n  std::map<int, std::unique_ptr<SocketHandler>> socketHandlers_;\n  std::list<folly::SocketAddress> servers_;\n  std::string serializedResolvers_;\n  uint16_t port_;\n  StatsCollector* statsCollector_;\n  TimeUtil timeUtil_;\n  bool resolveSRVRecord_{false};\n\n  // Attempt to resolve literal IPs, invoking the callback and returning\n  // true if we succeeded.\n  bool resolveLiterals(ResolutionCallback* cb,\n                       const std::string& host,\n                       sa_family_t family);\n\n  // Attempt to resolve localhost, If Ipv6 is available, we will use\n  // that otherwise we will use ipv4. If AF_UNSPEC is requested, we\n  // will return both\n  bool resolveLocalhost(ResolutionCallback* cb,\n                        const std::string& host,\n                        sa_family_t family);\n\n  // Low-level API for issuing a query using Ares\n  virtual void query(const std::string& name,\n                     RecordType type,\n                     ares_callback cb,\n                     void* data_data);\n\n  // Callback for users of the query() API to indicate that they're done. This\n  // can be invoked whenever they no longer care about the response: either\n  // because it's been received and processed, or because they want to ignore\n  // it (though the callback will still be invoked in this case).\n  virtual void queryFinished();\n\n  // Callback for Ares socket state changes\n  static void dnsSocketReady(void* data,\n                             ares_socket_t sock,\n                             int read,\n                             int write);\n\n  // Global constructor/destructor\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#define ATTRIBUTE_CONSTRUCTOR\n#else\n#define ATTRIBUTE_CONSTRUCTOR __attribute__((__constructor__))\n#endif\n#if defined(_MSC_VER) && !defined(__clang__)\n#define ATTRIBUTE_DESTRUCTOR\n#else\n#define ATTRIBUTE_DESTRUCTOR __attribute__((__destructor__))\n#endif\n  static void initGlobal() ATTRIBUTE_CONSTRUCTOR;\n  static void destroyGlobal() ATTRIBUTE_DESTRUCTOR;\n#undef ATTRIBUTE_CONSTRUCTOR\n#undef ATTRIBUTE_DESTRUCTOR\n\n protected:\n  // Use DelayedDestruction::destroy() instead\n  ~CAresResolver() override;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_dns_dns_base\n  SRCS\n    DNSResolver.cpp\n    NaiveResolutionCallback.cpp\n  DEPS\n    Folly::folly_conv\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_utils_trace\n    Folly::folly_exception_wrapper\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_delayed_destruction\n    Folly::folly_io_iobuf\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_dns_sync_resolver\n  SRCS\n    SyncDNSResolver.cpp\n  DEPS\n    proxygen_dns_dns_module\n    proxygen_dns_rfc6724\n  EXPORTED_DEPS\n    proxygen_dns_dns_base\n)\n\nproxygen_add_library(proxygen_dns_rfc6724\n  SRCS\n    Rfc6724.cpp\n  DEPS\n    Folly::folly_portability_unistd\n    Folly::folly_portability_windows\n  EXPORTED_DEPS\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_dns\n  SRCS\n    CachingDNSResolver.cpp\n  DEPS\n    Folly::folly_random\n  EXPORTED_DEPS\n    proxygen_dns_dns_base\n    proxygen_utils_time_util\n    Folly::folly_container_evicting_cache_map\n    Folly::folly_container_f14_hash\n)\n\nproxygen_add_library(proxygen_dns_cares_dns\n  SRCS\n    CAresResolver.cpp\n  DEPS\n    proxygen_utils_time_util\n    Folly::folly_conv\n    Folly::folly_portability_sockets\n    cares\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_dns_dns_base\n    Folly::folly_expected\n    Folly::folly_io_async_async_base\n    Folly::folly_network_address\n    Folly::folly_portability_windows\n    cares\n)\n\nproxygen_add_library(proxygen_dns_future_dns\n  SRCS\n    FutureDNSResolver.cpp\n  EXPORTED_DEPS\n    proxygen_dns_dns_base\n    Folly::folly_futures_core\n    Folly::folly_io_async_async_base\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_dns_dns_module\n  SRCS\n    DNSModule.cpp\n  DEPS\n    Folly::folly_network_address\n    Folly::folly_singleton\n    ${GFLAG_DEPENDENCIES}\n  EXPORTED_DEPS\n    proxygen_dns\n    proxygen_dns_cares_dns\n    Folly::folly_memory\n)\n"
  },
  {
    "path": "proxygen/lib/dns/CachingDNSResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Random.h>\n\n#include \"proxygen/lib/dns/CachingDNSResolver.h\"\n\nusing folly::SocketAddress;\nusing std::function;\nusing std::max;\nusing std::chrono::duration;\nusing std::chrono::seconds;\n\nnamespace proxygen {\n\nvoid CachingDNSResolver::resolveHostname(DNSResolver::ResolutionCallback* cb,\n                                         const std::string& name,\n                                         std::chrono::milliseconds timeout,\n                                         sa_family_t family,\n                                         TraceEventContext teContext) {\n  std::vector<Answer> results;\n  TimePoint now = timeUtil_->now();\n  DNSCache::iterator c_iter;\n\n  if ((c_iter = cache_.find(name)) != cache_.end()) {\n    CacheEntry& entry = c_iter->second;\n    auto& res = entry.answers_;\n    bool needQuery = false;       // true if no or only expired answer exists\n    bool hasCachedAnswer = false; // with respect to TTR\n    bool isPartialMiss = false;\n    for (auto& answer : res) {\n      if (answer.type != Answer::AnswerType::AT_ADDRESS) {\n        continue;\n      }\n\n      if (family == AF_UNSPEC || answer.address.getFamily() == family) {\n        if (entry.baseTime_ + answer.ttl < now) {\n          // expired answer exists and satisfies the family type\n          needQuery = true;\n          if (answer.ttl.count() > 0) {\n            isPartialMiss = true;\n          }\n        } else {\n          Answer ans(answer);\n          ans.ttl -= secondsBetween(now, entry.baseTime_);\n          results.push_back(std::move(ans));\n        }\n      }\n\n      hasCachedAnswer |= entry.baseTime_ + answer.ttl >= now;\n    }\n\n    if (!hasCachedAnswer) {\n      cache_.erase(name);\n    }\n\n    needQuery |= results.empty();\n\n    if (!needQuery) {\n      if (statsCollector_) {\n        statsCollector_->recordCacheHit();\n      }\n\n      // The results come from an ordered set, we need to shuffle the list in\n      // order to prevent uneven load on the first result:\n      if (results.size() > 0) {\n        std::shuffle(\n            std::begin(results), std::end(results), folly::ThreadLocalPRNG{});\n      }\n      cb->resolutionSuccess(std::move(results));\n      return;\n    } else {\n      if (isPartialMiss && statsCollector_) {\n        statsCollector_->recordCachePartialMiss();\n      }\n    }\n  } else {\n    if (statsCollector_) {\n      statsCollector_->recordCacheMiss();\n    }\n  }\n\n  auto* q = new Query(cb, name, family, this);\n  cb->insertQuery(q);\n  resolver_->resolveHostname(q, name, timeout, family, std::move(teContext));\n}\n\n// add the entry into both cache_ and staleCache_ with diff TTL\nvoid CachingDNSResolver::insertCache(\n    std::string name,\n    const std::vector<Answer>& answers,\n    DNSCache& cache,\n    const std::function<seconds(seconds)>& pf) {\n  CacheEntry newEntry;\n  newEntry.baseTime_ = timeUtil_->now();\n\n  // get original answers, prune expired ones and update TTL\n  auto iter = cache.find(name);\n  if (iter != cache.end()) {\n    CacheEntry& entry = iter->second;\n    auto& res = entry.answers_;\n    for (auto& a : res) {\n      if (entry.baseTime_ + a.ttl >= newEntry.baseTime_) {\n        Answer ans(a);\n        ans.ttl -= secondsBetween(newEntry.baseTime_, entry.baseTime_);\n        newEntry.answers_.insert(ans);\n      }\n    }\n  }\n\n  // merge exsting and new answers\n  for (auto& i : answers) {\n    Answer ans(i);\n    ans.ttl = pf(ans.ttl);\n    auto rc = newEntry.answers_.insert(ans);\n    // update its TTL if the answer already exists\n    if (!rc.second && rc.first->ttl < ans.ttl) {\n      newEntry.answers_.erase(rc.first);\n      newEntry.answers_.insert(ans);\n    }\n  }\n\n  cache.set(name, std::move(newEntry));\n}\n\nvoid CachingDNSResolver::addToCache(std::string name,\n                                    const std::vector<Answer>& answers) {\n  insertCache(name, answers, cache_, [&](const seconds ttl) { return ttl; });\n}\n\nvoid CachingDNSResolver::addToStaleCache(std::string name,\n                                         const std::vector<Answer>& answers) {\n  insertCache(name, answers, staleCache_, [&](const seconds ttl) {\n    return seconds(\n        max(static_cast<uint64_t>(staleCacheTTLMin_),\n            static_cast<uint64_t>(ttl.count() * staleCacheTTLScale_)));\n  });\n}\n\nvoid CachingDNSResolver::searchCache(std::string name,\n                                     sa_family_t family,\n                                     std::vector<Answer>& answers,\n                                     DNSCache& cache) {\n  TimePoint now = timeUtil_->now();\n  auto iter = cache.find(name);\n  if (iter != cache.end()) {\n    CacheEntry& entry = iter->second;\n    auto& res = entry.answers_;\n    for (auto& answer : res) {\n      if (answer.type != Answer::AnswerType::AT_ADDRESS) {\n        continue;\n      }\n\n      if (family == AF_UNSPEC || answer.address.getFamily() == family) {\n        if (entry.baseTime_ + answer.ttl > now) {\n          Answer ans(\n              secondsBetween(now, entry.baseTime_), answer.name, answer.type);\n          ans.address = answer.address;\n          answers.push_back(ans);\n        }\n      }\n    }\n  }\n}\n\nvoid CachingDNSResolver::dumpDNSCache(\n    std::vector<std::pair<std::string, CacheEntry>>& results, DNSCache& cache) {\n  for (const auto& e : cache) {\n    results.emplace_back(e); // copy\n  }\n}\n\nbool CachingDNSResolver::searchDNSCache(std::string name,\n                                        CacheEntry& out,\n                                        DNSCache& cache) {\n  auto iter = cache.find(name);\n  if (iter != cache.end()) {\n    out = iter->second;\n    return true;\n  }\n\n  return false;\n}\n\nvoid CachingDNSResolver::flushDNSCache() {\n  cache_.clear();\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/CachingDNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <memory>\n#include <vector>\n\n#include <folly/container/EvictingCacheMap.h>\n#include <folly/container/F14Set.h>\n#include <proxygen/lib/utils/Time.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\nclass CachingDNSResolver : public DNSResolver {\n public:\n  using UniquePtr =\n      std::unique_ptr<CachingDNSResolver, CachingDNSResolver::Destructor>;\n\n  template <typename... Args>\n  static UniquePtr newResolver(Args&&... args) {\n    return UniquePtr(new CachingDNSResolver(std::forward<Args>(args)...));\n  }\n\n  explicit CachingDNSResolver(\n      DNSResolver::UniquePtr resolver,\n      size_t cacheMaxSize = 4096,\n      size_t cacheClearSize = 256,\n      size_t staleCacheSizeMultiplier = 4,\n      size_t staleCacheTTLMin = 24 * 60 * 60,\n      size_t staleCacheTTLScale = 3,\n      std::unique_ptr<TimeUtil> timeUtil = std::make_unique<TimeUtil>())\n      : resolver_(std::move(resolver)),\n        cache_(cacheMaxSize, cacheClearSize),\n        staleCache_(cacheMaxSize * staleCacheSizeMultiplier, cacheClearSize),\n        staleCacheTTLMin_(staleCacheTTLMin),\n        staleCacheTTLScale_(staleCacheTTLScale),\n        timeUtil_(std::move(timeUtil)) {\n  }\n\n  ~CachingDNSResolver() override {\n    // destroy resolver_ before others becasue it will fail all pending\n    // requests and trigger callbacks to this class\n    cache_.clear();\n    staleCache_.clear();\n    resolver_.reset();\n  }\n\n  void resolveHostname(\n      DNSResolver::ResolutionCallback* cb,\n      const std::string& name,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(100),\n      sa_family_t family = AF_INET,\n      TraceEventContext teContext = TraceEventContext()) override;\n\n  // passthroughs\n  void resolveAddress(DNSResolver::ResolutionCallback* cb,\n                      const folly::SocketAddress& address,\n                      std::chrono::milliseconds timeout =\n                          std::chrono::milliseconds(100)) override {\n    resolver_->resolveAddress(cb, address, timeout);\n  }\n\n  void resolveMailExchange(ResolutionCallback* cb,\n                           const std::string& domain,\n                           std::chrono::milliseconds timeout) override {\n    resolver_->resolveMailExchange(cb, domain, timeout);\n  }\n\n  void setStatsCollector(DNSResolver::StatsCollector* statsCollector) override {\n    statsCollector_ = statsCollector;\n    resolver_->setStatsCollector(statsCollector);\n  }\n\n  [[nodiscard]] DNSResolver::StatsCollector* getStatsCollector()\n      const override {\n    return statsCollector_;\n  }\n\n  [[nodiscard]] std::list<folly::SocketAddress> getResolverAddresses(\n      const std::string& hostname) const override {\n    return resolver_->getResolverAddresses(hostname);\n  }\n\n  struct CacheEntry {\n    // The answers are stored in a set to aid in removing duplicates when we\n    // merge in answers from the old set. We explicitly shuffle the results\n    // when we return them from the cache.\n    folly::F14FastSet<Answer, AnswerHash> answers_;\n    TimePoint baseTime_;\n  };\n\n  using DNSCache = folly::EvictingCacheMap<std::string, CacheEntry>;\n\n  // no locking, must run in eventbase thread\n  void dumpDNSCache(std::vector<std::pair<std::string, CacheEntry>>& results,\n                    DNSCache& cache);\n\n  bool searchDNSCache(std::string name, CacheEntry& out, DNSCache& cache);\n\n  DNSCache& getDNSCache() {\n    return cache_;\n  }\n\n  DNSCache& getStaleDNSCache() {\n    return staleCache_;\n  }\n\n  void flushDNSCache();\n\n protected:\n  void insertCache(\n      std::string name,\n      const std::vector<Answer>& answers,\n      DNSCache& cache,\n      const std::function<std::chrono::seconds(std::chrono::seconds)>& pf);\n\n  void addToCache(std::string name, const std::vector<Answer>& answers);\n\n  void addToStaleCache(std::string name, const std::vector<Answer>& answers);\n\n  // DNSCache is a LRUCacheMap, so do not use const& here\n  void searchCache(std::string name,\n                   sa_family_t family,\n                   std::vector<Answer>& answers,\n                   DNSCache& cache);\n\n  void lookupCache(std::string name,\n                   sa_family_t family,\n                   std::vector<Answer>& answers) {\n    searchCache(name, family, answers, cache_);\n  }\n\n  void lookupStaleCache(std::string name,\n                        sa_family_t family,\n                        std::vector<Answer>& answers) {\n    searchCache(name, family, answers, staleCache_);\n  }\n\n  class Query\n      : public DNSResolver::ResolutionCallback\n      , public DNSResolver::QueryBase {\n   public:\n    explicit Query(ResolutionCallback* cb,\n                   const std::string name,\n                   sa_family_t family,\n                   CachingDNSResolver* parent)\n        : cb_(cb), name_(name), family_(family), parent_(parent) {\n    }\n\n    void resolutionSuccess(std::vector<Answer> answers) noexcept override {\n      if (!answers.empty()) {\n        parent_->addToCache(name_, answers);\n        parent_->addToStaleCache(name_, answers);\n      }\n\n      if (cb_) {\n        cb_->eraseQuery(this);\n        cb_->resolutionSuccess(answers);\n      }\n      delete this;\n    }\n\n    void resolutionError(const folly::exception_wrapper& ew) noexcept override {\n      std::vector<Answer> answers;\n      parent_->lookupStaleCache(name_, family_, answers);\n\n      if (!answers.empty()) {\n        auto statsCollector = parent_->getStatsCollector();\n        if (statsCollector) {\n          statsCollector->recordStaleCacheHit();\n        }\n\n        if (cb_) {\n          cb_->eraseQuery(this);\n          cb_->resolutionSuccess(answers);\n        }\n      } else {\n        if (cb_) {\n          cb_->eraseQuery(this);\n          cb_->resolutionError(ew);\n        }\n      }\n      delete this;\n    }\n\n    void cancelResolutionImpl() override {\n      cb_ = nullptr;\n    }\n\n   private:\n    // current request\n    ResolutionCallback* cb_;\n    std::string name_;\n    sa_family_t family_;\n    CachingDNSResolver* parent_;\n  };\n\n  DNSResolver::UniquePtr resolver_;\n  DNSCache cache_, staleCache_;\n  size_t staleCacheTTLMin_, staleCacheTTLScale_;\n  DNSResolver::StatsCollector* statsCollector_{nullptr};\n  std::unique_ptr<TimeUtil> timeUtil_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/DNSModule.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/DNSModule.h\"\n\n#include <folly/Singleton.h>\n#include <folly/SocketAddress.h>\n#include <gflags/gflags.h>\n\nusing folly::SocketAddress;\n\nDEFINE_int32(dns_cache_size, 4096, \"DNS cache size\");\nDEFINE_int32(dns_cache_clear_size, 256, \"DNS cache clear size\");\nDEFINE_int32(stale_dns_cache_size_multiplier,\n             4,\n             \"Size multiplier for stale dns cache\");\nDEFINE_int32(stale_cache_ttl_min, 86400, \"Stale dns cache min TTL in secs\");\nDEFINE_int32(stale_cache_ttl_scale, 3, \"Stale dns cache TTL multiplier\");\n\nnamespace proxygen {\n\nstatic folly::Singleton<DNSModule> gDNSModule;\n\nstd::shared_ptr<DNSModule> DNSModule::get() {\n  return gDNSModule.try_get();\n}\n\nDNSModule::DNSModule() {\n  cacheMaxSize_ = FLAGS_dns_cache_size;\n  cacheClearSize_ = FLAGS_dns_cache_clear_size;\n  staleCacheSizeMultiplier_ = FLAGS_stale_dns_cache_size_multiplier;\n  staleCacheTTLMin_ = FLAGS_stale_cache_ttl_min;\n  staleCacheTTLScale_ = FLAGS_stale_cache_ttl_scale;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/DNSModule.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n\n#include \"proxygen/lib/dns/CAresResolver.h\"\n#include \"proxygen/lib/dns/CachingDNSResolver.h\"\n\nnamespace proxygen {\n\nclass DNSModule {\n\n public:\n  static std::shared_ptr<DNSModule> get();\n\n  DNSModule();\n  virtual ~DNSModule() = default;\n\n  /**\n   * @Provides proxygen::DNSResolver::UniquePtr\n   */\n  virtual DNSResolver::UniquePtr provideDNSResolver(\n      folly::EventBase* eventBase) {\n    auto cares = CAresResolver::newResolver();\n\n    cares->attachEventBase(eventBase);\n    cares->setPort(dnsPort_);\n    cares->setServers(dnsServers_);\n    cares->init();\n\n    auto resolver =\n        CachingDNSResolver::newResolver(DNSResolver::UniquePtr(cares.release()),\n                                        cacheMaxSize_,\n                                        cacheClearSize_);\n\n    return DNSResolver::UniquePtr(resolver.release());\n  }\n\n  /**\n   * Configure the provided DNS resolver to make requests to the given port.\n   */\n  void setDNSPort(uint16_t port) {\n    dnsPort_ = port;\n  }\n\n  /**\n   * Configure the provided DNS resolver to make requests to the given set of\n   * servers. Any port specified in these addresses is ignored in favor of the\n   * port set via setDNSPort().\n   */\n  void setDNSServers(const std::list<folly::SocketAddress>& servers) {\n    dnsServers_.clear();\n    dnsServers_.insert(dnsServers_.begin(), servers.begin(), servers.end());\n  }\n\n  /**\n   * Configure the parameters of the CachingDNSResolver cache\n   * Note: CachingDNSResolver is one instance per Acceptor\n   */\n  void setCacheParameters(size_t maxSize, size_t clearSize) {\n    cacheMaxSize_ = maxSize;\n    cacheClearSize_ = clearSize;\n  }\n\n  /**\n   * Configure the TTL of entries in CachingDNSResolver staleCache_\n   * Note: TTL = max(staleCacheTTLMin, staleCacheTTLScale * TTL)\n   */\n  void setStaleCacheTTL(size_t min, size_t factor) {\n    staleCacheTTLMin_ = min;\n    staleCacheTTLScale_ = factor;\n  }\n\n  void setStaleCacheSizeMultiplier(size_t multiplier) {\n    staleCacheSizeMultiplier_ = multiplier;\n  }\n\n protected:\n  uint16_t dnsPort_{53};\n  std::list<folly::SocketAddress> dnsServers_;\n  size_t cacheMaxSize_{4096};\n  size_t cacheClearSize_{256};\n  size_t staleCacheSizeMultiplier_{4};\n  size_t staleCacheTTLMin_{24 * 60 * 60}; // by default 24 hours;\n  size_t staleCacheTTLScale_{3};          // by default 3 times of TTL;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/DNSResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\n#include <folly/Conv.h>\n#include <glog/logging.h>\n\nusing folly::SocketAddress;\nusing proxygen::DNSResolver;\n\nnamespace {\nconstexpr std::string_view kNoAddr{\"No valid address or name found\"};\n};\n\nnamespace proxygen {\nbool DNSResolver::Answer::operator==(const Answer& rhs) const noexcept {\n  return type == rhs.type && address == rhs.address && name == rhs.name &&\n         canonicalName == rhs.canonicalName && port == rhs.port &&\n         priority == rhs.priority;\n}\n\nsize_t DNSResolver::AnswerHash::operator()(const Answer& a) const noexcept {\n  // Hash by discriminant + relevant payload used in operator<\n  return folly::hash::hash_combine(\n      a.type, a.address.hash(), a.priority, a.name, a.canonicalName, a.port);\n}\n\n// public static\nfolly::exception_wrapper DNSResolver::makeNoNameException() noexcept {\n  return folly::make_exception_wrapper<DNSResolver::Exception>(\n      DNSResolver::NODATA, std::string(kNoAddr));\n}\n\nconst std::chrono::seconds DNSResolver::kInvalidDnsTtl =\n    std::chrono::seconds(0);\n\nconst std::chrono::milliseconds DNSResolver::kMaxTimeout =\n    std::chrono::milliseconds(60 * 1000);\n\nconst std::chrono::seconds DNSResolver::kLiteralTTL = std::chrono::seconds(60);\n\nconst int DNSResolver::kMaxCnameResolutions = 9;\n\nDNSResolver::ResolutionCallback::~ResolutionCallback() {\n  this->cancelResolution();\n}\n\nvoid DNSResolver::ResolutionCallback::cancelResolution() {\n  std::set<QueryBase*> queries;\n  {\n    std::lock_guard<std::mutex> g(mutex_);\n    queries = std::move(queries_);\n    // the clear is not really needed but I'll keep it here in case\n    // we decide to use a copy assignment later\n    queries_.clear();\n  }\n  for (auto q : queries) {\n    q->cancelResolutionImpl();\n  }\n}\n\nstd::string DNSResolver::getPtrName(const SocketAddress& address) {\n  static const char kHexVals[] = {'0',\n                                  '1',\n                                  '2',\n                                  '3',\n                                  '4',\n                                  '5',\n                                  '6',\n                                  '7',\n                                  '8',\n                                  '9',\n                                  'a',\n                                  'b',\n                                  'c',\n                                  'd',\n                                  'e',\n                                  'f'};\n  static const char kIPv4Domain[] = \"in-addr.arpa.\";\n  static const char kIPv6Domain[] = \"ip6.arpa.\";\n  char buf[128];\n\n  switch (address.getFamily()) {\n    case AF_INET: {\n\n      const in_addr ia = address.getIPAddress().asV4().toAddr();\n      const in_addr* iap = &ia;\n\n#ifdef _WIN32\n#define PRIsockaddr \"%lu\"\n#else\n#define PRIsockaddr \"%d\"\n#endif\n      snprintf(buf,\n               sizeof(buf),\n               PRIsockaddr \".\" PRIsockaddr \".\" PRIsockaddr \".\" PRIsockaddr\n                           \".%s\",\n               (iap->s_addr >> 24) & 0xff,\n               (iap->s_addr >> 16) & 0xff,\n               (iap->s_addr >> 8) & 0xff,\n               iap->s_addr & 0xff,\n               kIPv4Domain);\n\n      break;\n    }\n\n    case AF_INET6: {\n      const in6_addr i6a = address.getIPAddress().asV6().toAddr();\n      const in6_addr* i6ap = &i6a;\n\n      char* bufp = buf;\n      for (int i = sizeof(i6ap->s6_addr) - 1; i >= 0; --i) {\n        *(bufp++) = kHexVals[(i6ap->s6_addr[i]) & 0xf];\n        *(bufp++) = '.';\n        *(bufp++) = kHexVals[(i6ap->s6_addr[i] >> 4) & 0xf];\n        *(bufp++) = '.';\n      }\n      *bufp = 0;\n      memcpy(bufp, kIPv6Domain, sizeof(kIPv6Domain));\n\n      break;\n    }\n\n    default:\n      LOG(FATAL) << \"Unsupported address family \" << address.getFamily()\n                 << \" could not be turned into a PTR name\";\n  }\n\n  return std::string(buf);\n}\n\nvoid DNSResolver::resolveMailExchange(ResolutionCallback* /*cb*/,\n                                      const std::string& /*domain*/,\n                                      std::chrono::milliseconds /*timeout*/) {\n}\n\nstd::string describe(const DNSResolver::ResolutionStatus status, bool details) {\n#define DNSRESOLVER_RESOLUTION_STATUS_STR(sym, descr) #sym,\n  static const char* errorTable[] = {\n      DNSRESOLVER_RESOLUTION_STATUS_GEN(DNSRESOLVER_RESOLUTION_STATUS_STR)};\n#undef DNSRESOLVER_RESOLUTION_STATUS_STR\n\n#define DNSRESOLVER_RESOLUTION_STATUS_DETAILS(sym, descr) descr,\n  static const char* detailsTable[] = {\n      DNSRESOLVER_RESOLUTION_STATUS_GEN(DNSRESOLVER_RESOLUTION_STATUS_DETAILS)};\n#undef DNSRESOLVER_RESOLUTION_STATUS_DETAILS\n\n  auto idx = static_cast<size_t>(status);\n\n  if (!details) {\n    return errorTable[idx];\n  } else {\n    return folly::to<std::string>(\n        errorTable[idx], \" (\", detailsTable[idx], \")\");\n  }\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const DNSResolver::ResolutionStatus status) {\n  os << describe(status, true);\n  return os;\n}\n\nfolly::StringPiece familyToString(sa_family_t family) {\n  switch (family) {\n    case AF_INET:\n      return \"AF_INET\";\n    case AF_INET6:\n      return \"AF_INET6\";\n    case AF_UNSPEC:\n      return \"AF_UNSPEC\";\n    default:\n      return \"\";\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/DNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <list>\n#include <map>\n#include <memory>\n#include <set>\n#include <stdexcept>\n#include <vector>\n\n#include <folly/ExceptionWrapper.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/IOBuf.h>\n#include <folly/io/async/DelayedDestruction.h>\n#include <folly/io/async/EventBase.h>\n#include <proxygen/lib/utils/TraceEvent.h>\n#include <proxygen/lib/utils/TraceEventContext.h>\n\nnamespace proxygen {\n/**\n * Interface implemented by all the DNS resolvers.\n *\n * Implementations of this intreface are free to implement a single\n * resolveHostname() or resolveAddress() call by generating multiple queries.\n * As a result, the ultimate success/failure of the request may depend on the\n * results of these multiple operations.\n *\n * Implementations have to manage their own timeouts by pass error through\n * resolutionError.\n */\nclass DNSResolver : public folly::DelayedDestruction {\n protected:\n  class QueryBase;\n\n public:\n  using UniquePtr =\n      std::unique_ptr<DNSResolver, folly::DelayedDestruction::Destructor>;\n\n  enum class ResolverType : uint32_t {\n    UNKNOWN = 0,\n    POSIX = 1,\n    CARES = 2,\n    /* Intentionally unused =  3 */\n    DOH = 4,\n  };\n\n  // Return a DNSResolver::Exception(DNSResolver::NODATA) wrapper\n  static folly::exception_wrapper makeNoNameException() noexcept;\n\n  /*\n   * This means that no ttl info is available from the resolver. User should\n   * ignore this if the returned ttl is kInvalidDnsTtl.\n   */\n  static const std::chrono::seconds kInvalidDnsTtl;\n\n  /**\n   * Maximum timeout for a single resolution.\n   */\n  static const std::chrono::milliseconds kMaxTimeout;\n\n  /**\n   * The maximum number of CNAME aliases we will resolve.  We keep\n   * this number low to avoid implementing any fancy loop detection.\n   * RFC 1034 recommends no more than one CNAME.\n   */\n  static const int kMaxCnameResolutions;\n\n  /**\n   * Status codes for resolution failures.\n   */\n  // clang-format off\n#define DNSRESOLVER_RESOLUTION_STATUS_GEN(xx)                                 \\\n  xx(OK, \"Success\")                                                           \\\n  xx(TIMEOUT, \"Timed out\")                                                    \\\n  xx(INVALID, \"An invalid operation was requested\")                           \\\n  xx(TOO_MANY_REDIRECTS, \"Followed too many CNAME records\")                   \\\n  xx(NODATA, \"Server returned no answer\")                                     \\\n  xx(PARSE_ERROR, \"DNS record parse error\")                                   \\\n  xx(CONN_REFUSED, \"Client failed to connect to server\")                      \\\n  xx(SERVER_OTHER, \"Other errors happened on server side\")                    \\\n  xx(CLIENT_OTHER, \"Other errors happened on client side\")                    \\\n  xx(CANCELLED, \"Query gets cancelled\")                                       \\\n  xx(SHUTDOWN, \"Shutting down\")                                               \\\n  xx(GETADDRINFO, \"getaddrinfo failure\")                                      \\\n  xx(THREADPOOL, \"threadPool shutting down\")                                  \\\n  xx(UNIMPLEMENTED, \"Unimplemented\")                                          \\\n                                                                              \\\n  /* Must be last */                                                          \\\n  xx(UNKNOWN, \"An unknown error occurred\")\n// clang-format off\n\n#define DNSRESOLVER_RESOLUTION_STATUS_ENUM(sym, descr)        sym,\n  enum ResolutionStatus {\n    DNSRESOLVER_RESOLUTION_STATUS_GEN(DNSRESOLVER_RESOLUTION_STATUS_ENUM)\n  };\n#undef DNSRESOLVER_RESOLUTION_STATUS_ENUM\n\n  /**\n   * Exception class for resolution failures.\n   */\n  class FOLLY_EXPORT Exception : public std::runtime_error {\n   public:\n    Exception(ResolutionStatus statusVal, const std::string& message)\n        : std::runtime_error(message), status_(statusVal) {}\n    [[nodiscard]] ResolutionStatus status() const { return status_; }\n\n   private:\n    const ResolutionStatus status_;\n  };\n\n  /**\n   * Answer type for resolutions.\n   */\n  struct Answer {\n    // TTL of the answer\n    std::chrono::seconds ttl;\n\n    // Keep the time we created the answer for caching resolvers\n    std::chrono::seconds creationTime;\n\n    // Type of answer payload below\n    enum AnswerType {\n      AT_ADDRESS = 0,\n      AT_NAME = 1,\n      AT_CNAME = 2,\n      AT_TXT = 3,\n      AT_SRV = 4,\n      AT_MX = 5,\n    };\n\n    AnswerType type;\n\n    // The answer payload\n    folly::SocketAddress address;\n    std::string name;\n    std::string canonicalName;\n    uint16_t port{0};\n    uint16_t priority{0};\n    std::shared_ptr<folly::IOBuf> rawData;\n\n    ResolverType resolverType{ResolverType::UNKNOWN};\n\n    Answer(std::chrono::seconds cs, sockaddr* sa) :\n        ttl(cs), creationTime(secondsSinceEpoch()),\n        type(AT_ADDRESS), address(), name() {\n      address.setFromSockaddr(sa);\n    }\n    Answer(std::chrono::seconds cs, const folly::SocketAddress& ta) :\n        ttl(cs), creationTime(secondsSinceEpoch()),\n        type(AT_ADDRESS), address(ta), name() {\n    }\n    Answer(std::chrono::seconds cs, const std::string& n,\n           enum AnswerType t = AT_NAME) :\n      ttl(cs), creationTime(secondsSinceEpoch()), type(t), address(), name(n) {\n    }\n    Answer(std::chrono::seconds cs, const std::string& n,\n           uint16_t p, enum AnswerType t = AT_NAME) :\n      ttl(cs), creationTime(secondsSinceEpoch()), type(t), address(), name(n), port(p) {\n    }\n    Answer(std::chrono::seconds cs, uint16_t pri, std::string  n) :\n      ttl(cs), creationTime(secondsSinceEpoch()), type(AT_MX), address(), name(std::move(n)), priority(pri) {\n    }\n    Answer(std::chrono::seconds cs,\n           std::shared_ptr<folly::IOBuf> rData,\n           enum AnswerType t = AT_NAME)\n        : ttl(cs),\n          creationTime(secondsSinceEpoch()),\n          type(t),\n          address(),\n          rawData(rData) {}\n\n    // Constructors for recreating answers\n    Answer(std::chrono::seconds cs, std::chrono::seconds creation,\n           const folly::SocketAddress& ta) :\n      ttl(cs), creationTime(creation), type(AT_ADDRESS), address(ta), name() {\n    }\n    Answer(std::chrono::seconds cs, std::chrono::seconds creation,\n           const std::string& n,\n           enum AnswerType t = AT_NAME) :\n      ttl(cs), creationTime(creation), type(t), address(), name(n) {\n    }\n\n    //default ctor\n    Answer() = default;\n\n    bool operator==(const Answer& rhs) const noexcept;\n\n  };\n\n  struct AnswerHash {\n  size_t operator()(const Answer& a) const noexcept;\n};\n\n\n\n  /**\n   * Callback interface for resolution requests.\n   */\n  class ResolutionCallback {\n   private:\n    std::set<QueryBase *> queries_;\n\n   public:\n    ResolutionCallback() = default;\n    virtual ~ResolutionCallback();\n\n    /**\n     * Cancel the resolution query.  This is a no-op if the query has\n     * already completed.  If your ResolutionCallback is destroyed before\n     * the query completes, cancel is called implicitly.\n     *\n     * if there are pending queries, the function resets its callback\n     * so it will not bother its callback function no matter what the\n     * underlying resolver return\n     *\n     * Note the request is not actually cancelled, but is allowed to complete\n     * or timeout on its own, but the callback is not invoked.\n     */\n    void cancelResolution();\n\n    /**\n     * The resolution request has succeeded.\n     *\n     * Even simple queries can result in multiple answers coming back if the\n     * server responded with multiple records.\n     * subclass should be responsible for canceling queries\n     *\n     * When this callback is called, the implementor of the callback may delete\n     * itself, resulting in the cancellation of all attached sub-queries.\n     *\n     * @param results         list of results from the resolution; will be\n     *                        empty if the resolution succeeded but found no\n     *                        records matching the query (e.g. NXDOMAIN)\n     */\n    virtual void resolutionSuccess(std::vector<Answer> answers)\n     noexcept = 0;\n\n    /**\n     * The resolution has failed.\n     *\n     * While this takes an folly::exception_wrapper type, it's likely to be an\n     * instance of DNSResolver::Exception. Implementations should re-throw if\n     * they're interested in programmatically accessing the reason for the\n     * failure in the form of a ResolutionStatus.\n     * subclass should be responsible for canceling queries\n     *\n     * When this callback is called, the implementor of the callback may delete\n     * itself, resulting in the cancellation of all attached sub-queries.\n     *\n     * @param ew              the exception that caused the failure\n     */\n    virtual void resolutionError(const folly::exception_wrapper& ew)\n     noexcept = 0;\n\n   public:\n    void insertQuery(QueryBase* query) {\n      if (nullptr == query) {\n        throw std::runtime_error(\"DNSResolver::ResolutionCallback::insertQuery must be called with non-null query.\");\n      }\n      std::lock_guard<std::mutex> g(mutex_);\n      queries_.insert(query);\n    }\n\n    void eraseQuery(QueryBase* query) {\n      std::lock_guard<std::mutex> g(mutex_);\n      queries_.erase(query);\n    }\n\n    void resetQueries() {\n      std::lock_guard<std::mutex> g(mutex_);\n      queries_.clear();\n    }\n   private:\n    std::mutex mutex_;\n  };\n\n  /**\n   * Callback interface for collecting stats related to DNS resolution.\n   */\n  class StatsCollector {\n    public:\n     virtual ~StatsCollector() = default;\n\n     /**\n      * These callbacks pertain to the resolution request as a whole and\n      * correspond 1:1 to invocations of the ResolutionCallback methods.  Even\n      * if a given resolution is implemented by multiple independent queries, a\n      * single success/error callback is made.\n      */\n\n     /**\n      * Record the result of a successful DNS resolution.\n      *\n      * @param answers           the set of answers that we got back\n      * @param latency           time that the resolution took\n      */\n     virtual void recordSuccess(\n      const std::vector<Answer>& answers,\n      std::chrono::milliseconds latency) noexcept = 0;\n\n     /**\n      * Record the result of a failed DNS resolution.\n      *\n      * @param exp               exception that triggered the failure\n      * @param latency           time that the resolution took\n      */\n     virtual void recordError(\n      const folly::exception_wrapper& ew,\n      std::chrono::milliseconds latency) noexcept = 0;\n\n     /**\n      * These callbacks pertain to the result of individual queries, multiple\n      * of which may be performed in the handling of a single resolution\n      * request.\n      */\n\n     /**\n      * Record the result of a single query.\n      *\n      * @param rcode             the RFC1035 rcode value that came back\n      *                          with the query's answer\n      */\n     virtual void recordQueryResult(uint8_t rcode) noexcept = 0;\n\n     /**\n      * Optional: record cache hit in CachingDNSResolver\n      * Basically: Hit + PartialHit + Miss = # of reqs\n      *            PartialHit + Miss = # of actually issued real query\n      */\n     virtual void recordCacheHit() noexcept {}\n\n     /**\n      * Optional: record cache full miss in CachingDNSResolver\n      */\n     virtual void recordCacheMiss() noexcept {}\n\n     /**\n      * Optional: record cache partial miss in CachingDNSResolver\n      */\n     virtual void recordCachePartialMiss() noexcept {}\n\n    /**\n      * Optional: record cache hit a stale entry in CachingDNSResolver\n      */\n     virtual void recordStaleCacheHit() noexcept {}\n};\n\n\n  /**\n   * Resolve an address to a hostname.\n   *\n   * This performs a PTR query.\n   *\n   * @param cb                callback to invoke when resolution is complete\n   * @param address           the address to reverse-resolve\n   * @param timeout           timeout after which to give up; a value of 0\n   *                          indicates no timeout and values greater than\n   *                          kMaxTimeout will be clamped\n   */\n  virtual void resolveAddress(ResolutionCallback* cb,\n   const folly::SocketAddress& address,\n   std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) = 0;\n\n  /**\n   * Resolve a hostname to an address.\n   *\n   * This does a bit more than issue a single query.\n   *\n   *  - Names which are IP address literals are recognized and passed through\n   *    to the callback immediately and synchronously.\n   *\n   *  - If the address family to resolve is AF_UNSPEC, both IPv4 and IPv6\n   *    addresses will be queried for and returned.\n   *\n   * @param cb                callback to invoke when the resolution is complete\n   * @param name              the hostname to resolve\n   * @param timeout           timeout after which to give up; a value of 0\n   *                          indicates no timeout and values greater than\n   *                          kMaxTimeout will be clamped\n   * @param family            type of address to request; if AF_UNSPEC, both\n   *                          AF_INET6 and AF_INET will be resolved. Note that\n   *                          this will require multiple DNS queries, as ares\n   *                          is not smart enough to pack multiple questions\n   *                          into a single request\n   * @param teContext         the TraceEventContext\n   */\n  virtual void resolveHostname(ResolutionCallback* cb,\n   const std::string& name,\n   std::chrono::milliseconds timeout = std::chrono::milliseconds(100),\n   sa_family_t family = AF_INET,\n   TraceEventContext teContext = TraceEventContext()) = 0;\n\n  /**\n   * Resolve mail exchange servers and priorities for the given domain name.\n   *\n   * This performs a MX query.\n   *\n   * @param cb                callback to invoke when resolution is complete\n   * @param domain            the domain name to retrieve mail exchange info for\n   * @param timeout           timeout after which to give up; a value of 0\n   *                          indicates no timeout and values greater than\n   *                          kMaxTimeout will be clamped\n   */\n  virtual void resolveMailExchange(ResolutionCallback* cb,\n      const std::string& domain,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(100));\n\n  /**\n   * Operate on the StatsCollector instance that we wish to be used to track\n   * resolution statistics.\n   */\n  virtual void setStatsCollector(\n   DNSResolver::StatsCollector* statsCollector) = 0;\n  [[nodiscard]] virtual DNSResolver::StatsCollector* getStatsCollector() const = 0;\n\n  /*\n   * Return pre-configured resolver addresses that match the hostname\n   */\n  [[nodiscard]] virtual std::list<folly::SocketAddress> getResolverAddresses(\n      const std::string& /* hostname */) const {\n    return {};\n  }\n\n  /**\n   * Get the PTR name for the given address.\n   *\n   * This is used to perform reverse resolution of addresses to hostnames by\n   * means of a PTR query. Only supports IP addresses (v4 and v6); anything\n   * else will abort the process.\n   */\n  static std::string getPtrName(\n   const folly::SocketAddress& address);\n\n protected:\n  ~DNSResolver() override = default;\n\n  class QueryBase {\n   public:\n     virtual ~QueryBase() = default;\n     virtual void cancelResolutionImpl() = 0;\n  };\n\n  /**\n   * The TTL to use when returning results for literals\n   */\n  static const std::chrono::seconds kLiteralTTL;\n};\n\nstd::string describe(\n const proxygen::DNSResolver::ResolutionStatus status, bool details);\nstd::ostream& operator<<(\n std::ostream& os, const proxygen::DNSResolver::ResolutionStatus status);\n\nfolly::StringPiece familyToString(sa_family_t);\n\n}\n"
  },
  {
    "path": "proxygen/lib/dns/FutureDNSResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/FutureDNSResolver.h\"\n\nnamespace proxygen {\nnamespace {\nclass FutureDNSResolutionCallback : public DNSResolver::ResolutionCallback {\n public:\n  explicit FutureDNSResolutionCallback(\n      folly::Promise<std::vector<DNSResolver::Answer>> promise)\n      : promise_{std::move(promise)} {\n  }\n\n  void resolutionError(const folly::exception_wrapper& ew) noexcept override {\n    promise_.setException(ew);\n    delete this;\n  }\n\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> answers) noexcept override {\n    promise_.setValue(std::move(answers));\n    delete this;\n  }\n\n private:\n  folly::Promise<std::vector<DNSResolver::Answer>> promise_;\n};\n} // namespace\n\nFutureDNSResolver::FutureDNSResolver(folly::EventBase* evb,\n                                     DNSResolver::UniquePtr resolver)\n    : evb_{evb}, resolver_{std::move(resolver)} {\n  CHECK_NE(static_cast<folly::EventBase*>(nullptr), evb_)\n      << \"EventBase must not be null\";\n  CHECK_NE(static_cast<DNSResolver*>(nullptr), resolver_.get())\n      << \"DNS resolver must not be null\";\n}\n\nFutureDNSResolver::~FutureDNSResolver() = default;\n\nfolly::Future<std::vector<DNSResolver::Answer>>\nFutureDNSResolver::resolveAddress(const folly::SocketAddress& address,\n                                  std::chrono::milliseconds timeout) {\n  folly::Promise<std::vector<DNSResolver::Answer>> promise;\n  auto future = promise.getFuture();\n  auto callback = new FutureDNSResolutionCallback(std::move(promise));\n  evb_->runInEventBaseThread(\n      [address, callback, resolver = resolver_, timeout]() {\n        resolver->resolveAddress(callback, address, timeout);\n      });\n  return future;\n}\n\nfolly::Future<std::vector<DNSResolver::Answer>>\nFutureDNSResolver::resolveHostname(const std::string& name,\n                                   std::chrono::milliseconds timeout,\n                                   sa_family_t family,\n                                   TraceEventContext teCtx) {\n  folly::Promise<std::vector<DNSResolver::Answer>> promise;\n  auto future = promise.getFuture();\n  auto callback = new FutureDNSResolutionCallback(std::move(promise));\n  evb_->runInEventBaseThread(\n      [callback, family, name, resolver = resolver_, teCtx, timeout]() {\n        resolver->resolveHostname(callback, name, timeout, family, teCtx);\n      });\n  return future;\n}\n\nfolly::Future<std::vector<DNSResolver::Answer>>\nFutureDNSResolver::resolveMailExchange(const std::string& domain,\n                                       std::chrono::milliseconds timeout) {\n  folly::Promise<std::vector<DNSResolver::Answer>> promise;\n  auto future = promise.getFuture();\n  auto callback = new FutureDNSResolutionCallback(std::move(promise));\n  evb_->runInEventBaseThread(\n      [callback, domain, resolver = resolver_, timeout]() {\n        resolver->resolveMailExchange(callback, domain, timeout);\n      });\n  return future;\n}\n\nDNSResolver::StatsCollector* FutureDNSResolver::getStatsCollector() const {\n  return resolver_->getStatsCollector();\n}\nvoid FutureDNSResolver::setStatsCollector(\n    DNSResolver::StatsCollector* collector) {\n  resolver_->setStatsCollector(collector);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/FutureDNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <string>\n#include <vector>\n\n#include <folly/SocketAddress.h>\n#include <folly/futures/Future.h>\n#include <folly/io/async/EventBase.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\n/**\n * A wrapper around a proxygen::DNSResolver that provides a Future API around\n * the resolveAddress() and resolveHostname() methods.\n */\nclass FutureDNSResolver {\n public:\n  static constexpr std::chrono::milliseconds kDefaultTimeout() {\n#if ASAN_ENABLED || TSAN_ENABLED\n    return std::chrono::milliseconds(1000);\n#else\n    return std::chrono::milliseconds(100);\n#endif\n  }\n\n  FutureDNSResolver(folly::EventBase* evb, DNSResolver::UniquePtr resolver);\n  virtual ~FutureDNSResolver();\n\n  virtual folly::Future<std::vector<DNSResolver::Answer>> resolveAddress(\n      const folly::SocketAddress& address,\n      std::chrono::milliseconds timeout = kDefaultTimeout());\n\n  virtual folly::Future<std::vector<DNSResolver::Answer>> resolveHostname(\n      const std::string& name,\n      std::chrono::milliseconds timeout = kDefaultTimeout(),\n      sa_family_t family = AF_INET,\n      proxygen::TraceEventContext teCtx = proxygen::TraceEventContext());\n\n  virtual folly::Future<std::vector<DNSResolver::Answer>> resolveMailExchange(\n      const std::string& domain,\n      std::chrono::milliseconds timeout = kDefaultTimeout());\n\n  [[nodiscard]] virtual DNSResolver::StatsCollector* getStatsCollector() const;\n  virtual void setStatsCollector(DNSResolver::StatsCollector* collector);\n\n  [[nodiscard]] folly::EventBase* getEventBase() const {\n    return evb_;\n  }\n\n private:\n  folly::EventBase* evb_;\n  std::shared_ptr<DNSResolver> resolver_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/NaiveResolutionCallback.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/NaiveResolutionCallback.h\"\n\nnamespace proxygen {\n\nusing DNSAnswer = DNSResolver::Answer;\nusing DNSAnswers = std::vector<DNSAnswer>;\n\nnamespace {\nstatic bool isDNSAnswer(const DNSAnswer& a) {\n  return (a.type == DNSAnswer::AT_ADDRESS || a.type == DNSAnswer::AT_NAME);\n}\n} // namespace\n\n// public\nvoid NaiveResolutionCallback::resolutionSuccess(DNSAnswers answers) noexcept {\n  folly::exception_wrapper ex;\n\n  bool have_answer = std::any_of(answers.begin(), answers.end(), isDNSAnswer);\n  if (!have_answer) {\n    auto err = DNSResolver::makeNoNameException();\n    ex = std::move(err);\n  }\n\n  handler_(std::move(answers), std::move(ex));\n  delete this;\n}\n\n// public\nvoid NaiveResolutionCallback::resolutionError(\n    const folly::exception_wrapper& exp) noexcept {\n  handler_({}, std::move(exp));\n  delete this;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/NaiveResolutionCallback.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <functional>\n#include <vector>\n\n#include <folly/ExceptionWrapper.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\n// Basic callback for name resolution\nclass NaiveResolutionCallback : public DNSResolver::ResolutionCallback {\n public:\n  using Handler = std::function<void(std::vector<DNSResolver::Answer> &&,\n                                     const folly::exception_wrapper &&)>;\n\n  explicit NaiveResolutionCallback(Handler handler) : handler_(handler) {\n  }\n\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> answers) noexcept override;\n  void resolutionError(const folly::exception_wrapper &exp) noexcept override;\n\n private:\n  Handler handler_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/Rfc6724.cpp",
    "content": "// @nolint\n/*\n * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. Neither the name of the project nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n#include \"proxygen/lib/dns/Rfc6724.h\"\n\n#include <algorithm>\n#include <cerrno>\n#include <climits>\n#ifdef _WIN32\n#include <folly/portability/Windows.h>\n#else\n#include <netdb.h>\n#include <netinet/in.h>\n#endif\n#include <folly/portability/Unistd.h>\n#include <string>\n#include <system_error>\n\nusing folly::SocketAddress;\nusing std::string;\nusing std::vector;\n\nnamespace {\n\nstruct SortElement {\n  SocketAddress* addr;\n  bool hasSrcAddr;\n  SocketAddress srcAddr;\n  size_t originalOrder;\n}; // struct SortElement\n\nstatic bool find_src_addr(const SocketAddress* addr, SocketAddress& srcAddr);\nstatic int get_common_prefix(const in6_addr* src, const in6_addr* dst);\nstatic int get_label(const SocketAddress* addr);\nstatic int get_scope(const SocketAddress* addr);\nstatic int get_precedence(const SocketAddress* addr);\nstatic int rfc6724_compare(const void* ptr1, const void* ptr2);\n\n} // namespace\n\nnamespace proxygen {\n\nvoid rfc6724_sort(vector<SocketAddress>& addrs,\n                  const SocketAddress* srcAddr /* = nullptr */) {\n  /** \n   * Some callers require strong exception safety guarentees. If `find_src_addr`\n   * throws an exception, the output (e.g. vector<SocketAddress>& addrs) must \n   * be left in a valid state; this is a note to prevent future developers from \n   * breaking this guarantee.\n   */\n  vector<SortElement> sortVec;\n  for (size_t i = 0; i < addrs.size(); ++i) {\n    SortElement elem;\n    elem.addr = &addrs[i];\n    if (srcAddr == nullptr) {\n      // throws if find_src_addr fails\n      elem.hasSrcAddr = find_src_addr(elem.addr, elem.srcAddr);\n    } else {\n      elem.hasSrcAddr = true;\n      elem.srcAddr = *srcAddr;\n    }\n    elem.originalOrder = i;\n    sortVec.push_back(elem);\n  }\n\n  std::qsort(\n      sortVec.data(), sortVec.size(), sizeof(SortElement), rfc6724_compare);\n  for (size_t i = 0; i < addrs.size(); i++) {\n    if (sortVec[i].originalOrder > i) {\n      std::swap(addrs[i], addrs[sortVec[i].originalOrder]);\n    }\n  }\n}\n\n} // namespace proxygen\n\nnamespace {\n\n#define IPV6_ADDR_SCOPE_NODELOCAL 0x01\n#define IPV6_ADDR_SCOPE_INTFACELOCAL 0x01\n#define IPV6_ADDR_SCOPE_LINKLOCAL 0x02\n#define IPV6_ADDR_SCOPE_SITELOCAL 0x05\n#define IPV6_ADDR_SCOPE_ORGLOCAL 0x08\n#define IPV6_ADDR_SCOPE_GLOBAL 0x0e\n\n#define IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f)\n\n/* RFC 4193. */\n#define IN6_IS_ADDR_ULA(a) (((a)->s6_addr[0] & 0xfe) == 0xfc)\n\n#ifndef IN_LOOPBACK // this macro is defined in mac headers: netinet/in.h\n#define IN_LOOPBACK(a) ((((long int)(a)) & 0xff000000) == 0x7f000000)\n#endif\n\n/* These macros are modelled after the ones in <netinet/in6.h>. */\n\n/* RFC 4380, section 2.6 */\n#define IN6_IS_ADDR_TEREDO(a) (((const uint32_t*)(a))[0] == ntohl(0x20010000))\n\n/* RFC 3056, section 2. */\n#ifdef IN6_IS_ADDR_6TO4 // this macro is defined in mac sys headers:\n                        // netinet/in.h\n#undef IN6_IS_ADDR_6TO4\n#endif\n#define IN6_IS_ADDR_6TO4(a) \\\n  (((a)->s6_addr[0] == 0x20) && ((a)->s6_addr[1] == 0x02))\n\n/* 6bone testing address area (3ffe::/16), deprecated in RFC 3701. */\n#define IN6_IS_ADDR_6BONE(a) \\\n  (((a)->s6_addr[0] == 0x3f) && ((a)->s6_addr[1] == 0xfe))\n\n// throws std::system_error on socket() fail\nstatic bool find_src_addr(const SocketAddress* addr, SocketAddress& srcAddr) {\n  int r = 0, sock = 0;\n  socklen_t len = 0;\n  sockaddr_storage sa;\n\n  switch (addr->getFamily()) {\n    case AF_INET:\n      len = sizeof(sockaddr_in);\n      break;\n    case AF_INET6:\n      len = sizeof(sockaddr_in6);\n      break;\n    default:\n      /* No known usable source address for non-INET families. */\n      return false;\n  }\n\n  sock = ::socket(addr->getFamily(), SOCK_DGRAM, IPPROTO_UDP);\n  if (sock == -1) {\n    auto err = errno;\n    if (err == EAFNOSUPPORT) {\n      return false;\n    } else {\n      throw std::system_error(err, std::system_category());\n    }\n  }\n\n  do {\n    sockaddr_storage addrStorage;\n    addr->getAddress(&addrStorage);\n    sockaddr* saddr = reinterpret_cast<sockaddr*>(&addrStorage);\n    r = ::connect(sock, saddr, len);\n  } while (r == -1 && errno == EINTR);\n\n  if (r == -1) {\n#ifdef _WIN32\n    ::closesocket(sock);\n#else\n    close(sock);\n#endif\n    return false;\n  }\n\n  r = ::getsockname(sock, reinterpret_cast<sockaddr*>(&sa), &len);\n#ifdef _WIN32\n  ::closesocket(sock);\n#else\n  close(sock);\n#endif\n\n  if (r == -1) {\n    throw std::system_error(errno, std::system_category());\n  }\n\n  try {\n    srcAddr.setFromSockaddr(reinterpret_cast<sockaddr*>(&sa), len);\n  } catch (...) {\n    throw std::system_error(EPROTONOSUPPORT, std::system_category());\n  }\n\n  return true;\n}\n\nstatic int get_scope(const SocketAddress* addr) {\n  if (addr->getFamily() == AF_INET6) {\n    const in6_addr i6a = addr->getIPAddress().asV6().toAddr();\n    const in6_addr* i6ap = &i6a;\n\n    if (IN6_IS_ADDR_MULTICAST(i6ap)) {\n      return IPV6_ADDR_MC_SCOPE(i6ap);\n    } else if (IN6_IS_ADDR_LOOPBACK(i6ap) || IN6_IS_ADDR_LINKLOCAL(i6ap)) {\n      /*\n       * RFC 4291 section 2.5.3 says loopback is to be treated as having\n       * link-local scope.\n       */\n      return IPV6_ADDR_SCOPE_LINKLOCAL;\n    } else if (IN6_IS_ADDR_SITELOCAL(i6ap)) {\n      return IPV6_ADDR_SCOPE_SITELOCAL;\n    } else {\n      return IPV6_ADDR_SCOPE_GLOBAL;\n    }\n  } else if (addr->getFamily() == AF_INET) {\n    const in_addr ia = addr->getIPAddress().asV4().toAddr();\n\n    unsigned long int na = ntohl(ia.s_addr);\n\n    if (IN_LOOPBACK(na) ||                 /* 127.0.0.0/8 */\n        (na & 0xffff0000) == 0xa9fe0000) { /* 169.254.0.0/16 */\n      return IPV6_ADDR_SCOPE_LINKLOCAL;\n    } else {\n      /*\n       * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses\n       * and shared addresses (100.64.0.0/10), are assigned global scope.\n       */\n      return IPV6_ADDR_SCOPE_GLOBAL;\n    }\n  }\n  /*\n   * This should never happen.\n   * Return a scope with low priority as a last resort.\n   */\n  return IPV6_ADDR_SCOPE_NODELOCAL;\n}\n\n/*\n * Get the label for a given IPv4/IPv6 address.\n * RFC 6724, section 2.1.\n */\nstatic int get_label(const SocketAddress* addr) {\n  if (addr->getFamily() == AF_INET) {\n    return 4;\n  } else if (addr->getFamily() == AF_INET6) {\n    const in6_addr i6a = addr->getIPAddress().asV6().toAddr();\n    const in6_addr* i6ap = &i6a;\n\n    if (IN6_IS_ADDR_LOOPBACK(i6ap)) {\n      return 0;\n    } else if (IN6_IS_ADDR_V4MAPPED(i6ap)) {\n      return 4;\n    } else if (IN6_IS_ADDR_6TO4(i6ap)) {\n      return 2;\n    } else if (IN6_IS_ADDR_TEREDO(i6ap)) {\n      return 5;\n    } else if (IN6_IS_ADDR_ULA(i6ap)) {\n      return 13;\n    } else if (IN6_IS_ADDR_V4COMPAT(i6ap)) {\n      return 3;\n    } else if (IN6_IS_ADDR_SITELOCAL(i6ap)) {\n      return 11;\n    } else if (IN6_IS_ADDR_6BONE(i6ap)) {\n      return 12;\n    } else {\n      /* All other IPv6 addresses, including global unicast addresses. */\n      return 1;\n    }\n  } else {\n    /*\n     * This should never happen.\n     * Return a semi-random label as a last resort.\n     */\n    return 1;\n  }\n}\n\n/*\n * Get the precedence for a given IPv4/IPv6 address.\n * RFC 6724, section 2.1.\n */\nstatic int get_precedence(const SocketAddress* addr) {\n  if (addr->getFamily() == AF_INET) {\n    return 35;\n  } else if (addr->getFamily() == AF_INET6) {\n    const in6_addr i6a = addr->getIPAddress().asV6().toAddr();\n    const in6_addr* i6ap = &i6a;\n\n    if (IN6_IS_ADDR_LOOPBACK(i6ap)) {\n      return 50;\n    } else if (IN6_IS_ADDR_V4MAPPED(i6ap)) {\n      return 35;\n    } else if (IN6_IS_ADDR_6TO4(i6ap)) {\n      return 30;\n    } else if (IN6_IS_ADDR_TEREDO(i6ap)) {\n      return 5;\n    } else if (IN6_IS_ADDR_ULA(i6ap)) {\n      return 3;\n    } else if (IN6_IS_ADDR_V4COMPAT(i6ap) || IN6_IS_ADDR_SITELOCAL(i6ap) ||\n               IN6_IS_ADDR_6BONE(i6ap)) {\n      return 1;\n    } else {\n      /* All other IPv6 addresses, including global unicast addresses. */\n      return 40;\n    }\n  } else {\n    return 1;\n  }\n}\n\n/*\n * Find number of matching initial bits between the two addresses a1 and a2.\n */\nstatic int get_common_prefix(const in6_addr* src, const in6_addr* dst) {\n  const char* p1 = reinterpret_cast<const char*>(src);\n  const char* p2 = reinterpret_cast<const char*>(dst);\n\n  for (unsigned i = 0; i < sizeof(*src); ++i) {\n    int x, j;\n\n    if (p1[i] == p2[i]) {\n      continue;\n    }\n    x = p1[i] ^ p2[i];\n    for (j = 0; j < CHAR_BIT; ++j) {\n      if (x & (1 << (CHAR_BIT - 1))) {\n        return i * CHAR_BIT + j;\n      }\n      x <<= 1;\n    }\n  }\n  return sizeof(*src) * CHAR_BIT;\n}\n\n/*\n * Compare two source/destination address pairs.\n * RFC 6724, section 6.\n */\nstatic int rfc6724_compare(const void* ptr1, const void* ptr2) {\n  const SortElement* l = reinterpret_cast<const SortElement*>(ptr1);\n  const SortElement* r = reinterpret_cast<const SortElement*>(ptr2);\n  int scopeSrcL, scopeDstL, scopeMatchL;\n  int scopeSrcR, scopeDstR, scopeMatchR;\n  int labelSrcL, labelDstL, labelMatchL;\n  int labelSrcR, labelDstR, labelMatchR;\n  int precedenceL, precedenceR;\n  int prefixLenL, prefixLenR;\n\n  /* Rule 1: Avoid unusable destinations. */\n  if (l->hasSrcAddr != r->hasSrcAddr) {\n    return int(r->hasSrcAddr) - int(l->hasSrcAddr);\n  }\n\n  /* Rule 2: Prefer matching scope. */\n  scopeSrcL = get_scope(&l->srcAddr);\n  scopeDstL = get_scope(l->addr);\n  scopeMatchL = (scopeSrcL == scopeDstL);\n\n  scopeSrcR = get_scope(&r->srcAddr);\n  scopeDstR = get_scope(r->addr);\n  scopeMatchR = (scopeSrcR == scopeDstR);\n\n  if (scopeMatchL != scopeMatchR) {\n    return int(scopeMatchR) - int(scopeMatchL);\n  }\n\n  /*\n   * Rule 3: Avoid deprecated addresses.\n   * XXX: We don't currently have a good way of finding this.\n   */\n\n  /*\n   * Rule 4: Prefer home addresses.\n   * XXX: We don't currently have a good way of finding this.\n   */\n\n  /* Rule 5: Prefer matching label. */\n  labelSrcL = get_label(&l->srcAddr);\n  labelDstL = get_label(l->addr);\n  labelMatchL = (labelSrcL == labelDstL);\n\n  labelSrcR = get_label(&r->srcAddr);\n  labelDstR = get_label(r->addr);\n  labelMatchR = (labelSrcR == labelDstR);\n\n  if (labelMatchL != labelMatchR) {\n    return int(labelMatchR) - int(labelMatchL);\n  }\n\n  /* Rule 6: Prefer higher precedence. */\n  precedenceL = get_precedence(l->addr);\n  precedenceR = get_precedence(r->addr);\n  if (precedenceL != precedenceR) {\n    return precedenceR - precedenceL;\n  }\n\n  /*\n   * Rule 7: Prefer native transport.\n   * XXX: We don't currently have a good way of finding this.\n   */\n\n  /* Rule 8: Prefer smaller scope. */\n  if (scopeDstL != scopeDstR) {\n    return scopeDstL - scopeDstR;\n  }\n\n  /*\n   * Rule 9: Use longest matching prefix.\n   * We implement this for IPv6 only, as the rules in RFC 6724 don't seem\n   * to work very well directly applied to IPv4. (glibc uses information from\n   * the routing table for a custom IPv4 implementation here.)\n   */\n  if (l->hasSrcAddr && l->addr->getFamily() == AF_INET6 && r->hasSrcAddr &&\n      r->addr->getFamily() == AF_INET6) {\n\n    const in6_addr addrSrcL = l->addr->getIPAddress().asV6().toAddr();\n    const in6_addr addrDstL = l->srcAddr.getIPAddress().asV6().toAddr();\n    const in6_addr addrSrcR = r->addr->getIPAddress().asV6().toAddr();\n    const in6_addr addrDstR = r->srcAddr.getIPAddress().asV6().toAddr();\n\n    prefixLenL = get_common_prefix(&addrSrcL, &addrDstL);\n    prefixLenR = get_common_prefix(&addrSrcR, &addrDstR);\n    if (prefixLenL != prefixLenR) {\n      return prefixLenR - prefixLenL;\n    }\n  }\n\n  /*\n   * Rule 10: Leave the order unchanged.\n   * We need this since qsort() is not necessarily stable.\n   */\n  return int(l->originalOrder) - int(r->originalOrder);\n}\n\n} // namespace\n"
  },
  {
    "path": "proxygen/lib/dns/Rfc6724.h",
    "content": "// @nolint\n/*\n * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. Neither the name of the project nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n#pragma once\n\n#include <folly/SocketAddress.h>\n#include <vector>\n\nnamespace proxygen {\n\n/**\n * rfc6724 sorting of IP addresses, based on port from WIDE project.\n *\n * The second argument is pretty much exclusively for unit tests.\n *\n * @author nedab - original work\n * @author bmatheny - cleanup, landing\n * @throws std::system_error if sort fails\n */\nvoid rfc6724_sort(std::vector<folly::SocketAddress>& addrs,\n                  const folly::SocketAddress* srcAddr = nullptr);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/SyncDNSResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/SyncDNSResolver.h\"\n\n#include \"proxygen/lib/dns/DNSModule.h\"\n#include \"proxygen/lib/dns/NaiveResolutionCallback.h\"\n#include \"proxygen/lib/dns/Rfc6724.h\"\n\n#include <barrier>\n\nnamespace proxygen {\n\n// public constructor\nSyncDNSResolver::SyncDNSResolver()\n    : SyncDNSResolver(DNSModule::get()->provideDNSResolver(&evb_)) {\n}\nSyncDNSResolver::SyncDNSResolver(DNSResolver::UniquePtr resolver) {\n  // Start a thread running event loop\n  auto barrier = std::make_shared<std::barrier<>>(2);\n  thread_ = std::thread([this, barrier]() mutable {\n    evb_.runInLoop([=]() mutable {\n      barrier->arrive_and_wait();\n      barrier.reset();\n    });\n\n    evb_.loopForever();\n    evb_.loop();\n  });\n\n  // Wait for event loop to start\n  barrier->arrive_and_wait();\n\n  // Create the underlying DNS resolver we are doing to use\n  resolver_ = std::move(resolver);\n}\n\n// public destructor\nSyncDNSResolver::~SyncDNSResolver() {\n  evb_.runInEventBaseThread([this]() {\n    resolver_.reset();\n    evb_.terminateLoopSoon();\n  });\n\n  thread_.join();\n}\n\n// public\nstd::vector<folly::SocketAddress> SyncDNSResolver::resolveHostname(\n    const std::string& hostname,\n    std::chrono::milliseconds timeout, // 100ms\n    sa_family_t family,                // = AF_UNSPEC,\n    bool rfc6724sort /* = true */) {\n\n  std::vector<folly::SocketAddress> addrs;\n  folly::exception_wrapper ew;\n\n  auto barrier = std::make_shared<std::barrier<>>(2);\n  evb_.runInEventBaseThread([&, barrier]() mutable {\n    auto cb = new NaiveResolutionCallback(\n        [&, barrier](std::vector<DNSResolver::Answer>&& answers,\n                     const folly::exception_wrapper&& exptr) {\n          for (const auto& answer : answers) {\n            if (answer.type == DNSResolver::Answer::AT_ADDRESS) {\n              addrs.push_back(answer.address);\n            }\n          }\n          if (addrs.empty()) {\n            if (exptr) {\n              ew = std::move(exptr);\n            } else {\n              ew = DNSResolver::makeNoNameException();\n            }\n          }\n          barrier->arrive_and_wait();\n        });\n\n    resolver_->resolveHostname(cb, hostname, timeout, family);\n  });\n\n  barrier->arrive_and_wait();\n  if (ew) {\n    ew.throw_exception();\n  }\n\n  if (rfc6724sort) {\n    rfc6724_sort(addrs);\n  }\n  return addrs;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/SyncDNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <thread>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\n/**\n * SyncDNSResolver provides a synchronous interface around the async\n * DNSResolver. Internally it runs a separate thread running event loop\n * and does all the DNS resolution there.\n *\n * Use this if you want timeout functionality, etc or if you are doing a lot\n * of lookups and want to avoid hitting the bug in `getaddrinfo` described\n * here -\n *\n *   https://sourceware.org/bugzilla/show_bug.cgi?id=15946\n */\nclass SyncDNSResolver {\n public:\n  SyncDNSResolver();\n  explicit SyncDNSResolver(DNSResolver::UniquePtr resolver);\n  ~SyncDNSResolver();\n\n  std::vector<folly::SocketAddress> resolveHostname(\n      const std::string& hostname,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(100),\n      sa_family_t family = AF_UNSPEC,\n      bool rfc6724sort = true);\n\n  // Helpful while testing\n  folly::EventBase* getEventBase() {\n    return &evb_;\n  }\n\n private:\n  std::thread thread_;\n  folly::EventBase evb_;\n  DNSResolver::UniquePtr resolver_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/CAresResolverTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include \"proxygen/lib/dns/CAresResolver.h\"\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace testing;\n\nusing folly::DelayedDestruction;\n\nclass MockCAresResolver : public CAresResolver {\n public:\n  using CAresResolver::CAresResolver;\n\n  MOCK_METHOD(void, queryFinished, ());\n  MOCK_METHOD(\n      void,\n      aresQuery,\n      (const std::string&, CAresResolver::RecordType, ares_callback, void*),\n      (const));\n\n private:\n  ~MockCAresResolver() override = default;\n};\n\nclass MockQuery : public CAresResolver::Query {\n public:\n  using CAresResolver::Query::fail;\n  using CAresResolver::Query::Query;\n  using CAresResolver::Query::timeoutExpired;\n};\n\nclass CAresResolverTest : public testing::Test {\n public:\n  void SetUp() override {\n    resolver.reset(new MockCAresResolver());\n  }\n\n  std::unique_ptr<MockCAresResolver, DelayedDestruction::Destructor> resolver;\n  const std::string name = \"test.fb.com\";\n  TraceEventContext teContext;\n};\n\nclass MockQueryWithCob : public MockQuery {\n public:\n  MockQueryWithCob(CAresResolver* resolver,\n                   CAresResolver::RecordType type,\n                   const std::string& name,\n                   bool recordStats,\n                   TraceEvent dnsEvent,\n                   const TimeUtil* timeUtil = nullptr,\n                   TraceEventContext teContext = TraceEventContext(),\n                   CAresResolver::ResolutionCallback* cb = nullptr)\n      : MockQuery(\n            resolver, type, name, recordStats, dnsEvent, timeUtil, teContext) {\n    callback_ = cb;\n  }\n};\n\nclass MockResolutionCallback : public CAresResolver::ResolutionCallback {\n public:\n  using CAresResolver::ResolutionCallback::ResolutionCallback;\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> /*answers*/) noexcept override {\n  }\n  void resolutionError(\n      const folly::exception_wrapper& /*ew*/) noexcept override {\n  }\n};\n\nTEST_F(CAresResolverTest, ScheduleFallbackOnTimeout) {\n  TraceEvent te(TraceEventType::DnsResolution);\n  auto cb = std::make_unique<MockResolutionCallback>();\n  auto query =\n      std::make_unique<MockQueryWithCob>(resolver.get(),\n                                         CAresResolver::RecordType::kTXT,\n                                         name,\n                                         true,\n                                         std::move(te),\n                                         nullptr,\n                                         std::move(teContext),\n                                         cb.get());\n  query->timeoutExpired();\n}\n\nTEST_F(CAresResolverTest, DontScheduleFallbackOnTimeoutNoCob) {\n  TraceEvent te(TraceEventType::DnsResolution);\n  auto cb = std::make_unique<MockResolutionCallback>();\n  auto query =\n      std::make_unique<MockQueryWithCob>(resolver.get(),\n                                         CAresResolver::RecordType::kTXT,\n                                         name,\n                                         true,\n                                         std::move(te),\n                                         nullptr,\n                                         std::move(teContext),\n                                         nullptr);\n  query->timeoutExpired();\n}\n\nTEST_F(CAresResolverTest, ScheduleFallbackOnFailureDnscr) {\n  TraceEvent te(TraceEventType::DnsResolution);\n  TimeUtil tu;\n  auto cb = std::make_unique<MockResolutionCallback>();\n  auto query = new MockQueryWithCob(resolver.get(),\n                                    CAresResolver::RecordType::kTXT,\n                                    name,\n                                    true,\n                                    std::move(te),\n                                    &tu,\n                                    std::move(teContext),\n                                    cb.get());\n  query->fail(static_cast<DNSResolver::ResolutionStatus>(1), \"error\");\n}\n\nTEST_F(CAresResolverTest, ScheduleFallbackOnFailureDnscrNoCob) {\n  TraceEvent te(TraceEventType::DnsResolution);\n  auto query = new MockQueryWithCob(resolver.get(),\n                                    CAresResolver::RecordType::kTXT,\n                                    name,\n                                    true,\n                                    std::move(te),\n                                    nullptr,\n                                    std::move(teContext));\n  query->fail(static_cast<DNSResolver::ResolutionStatus>(1), \"error\");\n}\n\nstatic unsigned char TWO_TXT_RESPONSE[] =\n    \"\\x56\\x14\\x85\\x80\\x00\\x01\\x00\\x02\\x00\\x00\\x00\\x01\\x01\\x32\\x0d\\x64\"\n    \"\\x6e\\x73\\x63\\x72\\x79\\x70\\x74\\x2d\\x63\\x65\\x72\\x74\\x08\\x66\\x61\\x63\"\n    \"\\x65\\x62\\x6f\\x6f\\x6b\\x03\\x63\\x6f\\x6d\\x00\\x00\\x10\\x00\\x01\\xc0\\x0c\"\n    \"\\x00\\x10\\x00\\x01\\x00\\x01\\x51\\x80\\x00\\x7d\\x7c\\x44\\x4e\\x53\\x43\\x00\"\n    \"\\x01\\x00\\x00\\x58\\x44\\x23\\xfa\\xdd\\xef\\xd5\\x7a\\x75\\xd6\\xd1\\x6d\\x7e\"\n    \"\\x5b\\xa8\\x8c\\x0f\\x25\\xfc\\x50\\x40\\x4e\\xc6\\x7e\\x87\\xf8\\x53\\x44\\x0c\"\n    \"\\x8c\\xa5\\x51\\x16\\xa7\\xd6\\x82\\x69\\xfc\\x7e\\xbb\\xfd\\x80\\x8c\\x79\\xc8\"\n    \"\\x43\\xae\\xa3\\x4d\\xe6\\x8b\\xec\\x94\\xfa\\x77\\xfe\\x40\\xda\\x28\\x3c\\x7c\"\n    \"\\x5f\\x38\\x04\\x44\\xfd\\x3a\\xe3\\x36\\x85\\xb5\\xa4\\xc7\\xa1\\x75\\x22\\x09\"\n    \"\\x08\\x28\\x0d\\x3b\\xbd\\x15\\x2f\\x34\\x69\\xce\\x04\\x84\\xe0\\x57\\xfe\\x0c\"\n    \"\\x0d\\x42\\x6a\\x44\\xfd\\x3a\\xe3\\x36\\x85\\xb5\\xa4\\x59\\xdd\\x7a\\x18\\x59\"\n    \"\\xdd\\x7a\\x18\\x5b\\xbe\\xad\\x98\\xc0\\x0c\\x00\\x10\\x00\\x01\\x00\\x01\\x51\"\n    \"\\x80\\x00\\x7d\\x7c\\x44\\x4e\\x53\\x43\\x00\\x01\\x00\\x00\\x9c\\x18\\x01\\xa3\"\n    \"\\x3d\\x01\\xab\\x07\\x2d\\x94\\x26\\xa3\\xd3\\x0a\\x52\\x7b\\xb4\\x57\\x08\\x1b\"\n    \"\\xf3\\x39\\x36\\x6a\\x84\\x51\\xa0\\x90\\x5f\\x38\\xc3\\xe3\\xce\\x52\\x0e\\xcc\"\n    \"\\x01\\x3f\\xfd\\x4e\\x25\\xd7\\x1a\\xe2\\x34\\x57\\xdd\\x73\\xab\\xfd\\x5b\\x38\"\n    \"\\x09\\x39\\x93\\x3c\\x2e\\x53\\x67\\x0b\\x82\\xc6\\xd8\\x0f\\xbd\\xc2\\xde\\x1b\"\n    \"\\x10\\x7b\\xbe\\x4e\\x83\\x87\\x7e\\xc6\\xd1\\x60\\xaa\\x26\\x26\\x4a\\xd7\\x4f\"\n    \"\\xe4\\x41\\x8a\\xc6\\x57\\x1d\\xb3\\xc0\\xb4\\x4c\\xd1\\x51\\xbd\\xc2\\xde\\x1b\"\n    \"\\x10\\x7b\\xbe\\x4e\\x59\\xdd\\x78\\x66\\x59\\xdd\\x78\\x66\\x5b\\xbe\\xab\\xe6\"\n    \"\\x00\\x00\\x29\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x00\";\n\nstatic unsigned char ONE_TXT_RESPONSE[] =\n    \"\\x15\\xbe\\x85\\x80\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x01\\x01\\x32\\x0d\\x64\"\n    \"\\x6e\\x73\\x63\\x72\\x79\\x70\\x74\\x2d\\x63\\x65\\x72\\x74\\x08\\x66\\x61\\x63\"\n    \"\\x65\\x62\\x6f\\x6f\\x6b\\x03\\x63\\x6f\\x6d\\x00\\x00\\x10\\x00\\x01\\xc0\\x0c\"\n    \"\\x00\\x10\\x00\\x01\\x00\\x01\\x51\\x80\\x00\\x7d\\x7c\\x44\\x4e\\x53\\x43\\x00\"\n    \"\\x01\\x00\\x00\\xfd\\x8f\\xb6\\xe8\\xb2\\x04\\x63\\xc7\\x52\\x90\\x13\\xbc\\x36\"\n    \"\\xd6\\xb1\\x3d\\x82\\x77\\xf8\\x62\\x7b\\x58\\x77\\xf1\\x24\\xaf\\x62\\x79\\x24\"\n    \"\\x10\\x9c\\xc0\\xc7\\x54\\x72\\x4e\\x31\\x7f\\x7f\\xc0\\xd4\\xb1\\xd8\\x97\\xd9\"\n    \"\\x89\\x37\\xfe\\x6a\\x56\\xd5\\x7a\\xde\\x68\\x0b\\x27\\x7a\\x18\\x3a\\x7a\\x20\"\n    \"\\xad\\xf8\\x09\\x68\\x71\\xbe\\xa1\\x6c\\xe1\\xb1\\xc4\\x4c\\xa9\\x65\\x98\\x93\"\n    \"\\x07\\xad\\x90\\xb2\\xff\\xae\\xe0\\x03\\x07\\x31\\x4e\\x0a\\x6c\\x09\\xc3\\x63\"\n    \"\\xf5\\x83\\x64\\x68\\x71\\xbe\\xa1\\x6c\\xe1\\xb1\\xc4\\x58\\x22\\x4d\\x2f\\x58\"\n    \"\\x22\\x4d\\x2f\\x5a\\x03\\x80\\xaf\\x00\\x00\\x29\\x10\\x00\\x00\\x00\\x00\\x00\"\n    \"\\x00\\x00\";\n\nstatic unsigned char GARBAGE[] = \"GARBAGE\";\n\n// This test is because of T22586769\nTEST_F(CAresResolverTest, TestParseTxtRecords) {\n  auto res = proxygen::detail::parseTxtRecords(&TWO_TXT_RESPONSE[0],\n                                               sizeof(TWO_TXT_RESPONSE));\n  ASSERT_TRUE(res.hasValue());\n\n  auto answers = std::move(res).value();\n  EXPECT_EQ(2, answers.size());\n\n  res = proxygen::detail::parseTxtRecords(&ONE_TXT_RESPONSE[0],\n                                          sizeof(ONE_TXT_RESPONSE));\n  ASSERT_TRUE(res.hasValue());\n\n  answers = std::move(res).value();\n  EXPECT_EQ(1, answers.size());\n\n  res = proxygen::detail::parseTxtRecords(&GARBAGE[0], sizeof(GARBAGE));\n  EXPECT_TRUE(res.hasError());\n}\n\n// Mock resolver that simulates the CNAME path where:\n// 1. First query returns a CNAME response (triggers checkForCName)\n// 2. Second query (the recursive CNAME lookup) completes synchronously,\n//    causing the Query to be deleted\n// This tests the fix for the heap-use-after-free bug where we accessed\n// self->resolver_ after self was deleted in checkForCName.\nclass CNameMockResolver : public CAresResolver {\n public:\n  using CAresResolver::CAresResolver;\n\n  void query(const std::string& /*name*/,\n             RecordType /*type*/,\n             ares_callback cb,\n             void* data) override {\n    queryCount_++;\n    if (queryCount_ == 1) {\n      // First query: return a CNAME response.\n      // This is a minimal valid A record response with a CNAME but no A\n      // records. The response has h_name = \"cname.example.com\" which differs\n      // from the original query, triggering checkForCName.\n      cb(data, ARES_SUCCESS, 0, cnameResponse_, sizeof(cnameResponse_));\n    } else {\n      // Second query (from checkForCName): return ARES_ENODATA.\n      // This triggers succeed({}) which deletes the Query object.\n      // After this callback returns, checkForCName tries to access\n      // self->resolver_ which is the bug we're testing.\n      cb(data, ARES_ENODATA, 0, nullptr, 0);\n    }\n  }\n\n  void queryFinished() override {\n    queryFinishedCount_++;\n  }\n\n  [[nodiscard]] int queryFinishedCount() const {\n    return queryFinishedCount_;\n  }\n\n private:\n  ~CNameMockResolver() override = default;\n\n  int queryCount_ = 0;\n  int queryFinishedCount_ = 0;\n\n  // Minimal DNS A record response with CNAME but no A records.\n  // This is crafted to make ares_parse_a_reply return:\n  // - status = ARES_SUCCESS\n  // - nttls = 0 (no A records)\n  // - host->h_name = \"cname.example.com\" (different from query name)\n  //\n  // DNS Response format:\n  // - Header (12 bytes)\n  // - Question section\n  // - Answer section with CNAME record only (no A records)\n  //\n  // Transaction ID: 0x1234\n  // Flags: 0x8180 (standard response, no error)\n  // Questions: 1, Answers: 1, Authority: 0, Additional: 0\n  unsigned char cnameResponse_[78] = {\n      // Header\n      0x12,\n      0x34, // Transaction ID\n      0x81,\n      0x80, // Flags: Standard response, no error\n      0x00,\n      0x01, // Questions: 1\n      0x00,\n      0x01, // Answer RRs: 1 (CNAME record)\n      0x00,\n      0x00, // Authority RRs: 0\n      0x00,\n      0x00, // Additional RRs: 0\n\n      // Question: original.example.com, type A, class IN\n      0x08,\n      'o',\n      'r',\n      'i',\n      'g',\n      'i',\n      'n',\n      'a',\n      'l', // \"original\"\n      0x07,\n      'e',\n      'x',\n      'a',\n      'm',\n      'p',\n      'l',\n      'e', // \"example\"\n      0x03,\n      'c',\n      'o',\n      'm',  // \"com\"\n      0x00, // null terminator\n      0x00,\n      0x01, // Type: A\n      0x00,\n      0x01, // Class: IN\n\n      // Answer: CNAME record pointing to cname.example.com\n      0xc0,\n      0x0c, // Name: pointer to offset 12 (original.example.com)\n      0x00,\n      0x05, // Type: CNAME (5)\n      0x00,\n      0x01, // Class: IN\n      0x00,\n      0x00,\n      0x0e,\n      0x10, // TTL: 3600\n      0x00,\n      0x13, // Data length: 19 bytes\n\n      // CNAME target: cname.example.com\n      0x05,\n      'c',\n      'n',\n      'a',\n      'm',\n      'e', // \"cname\"\n      0x07,\n      'e',\n      'x',\n      'a',\n      'm',\n      'p',\n      'l',\n      'e', // \"example\"\n      0x03,\n      'c',\n      'o',\n      'm', // \"com\"\n      0x00 // null terminator\n  };\n};\n\n// Test the CNAME path that had the UAF bug.\n// When checkForCName issues a recursive query and that query completes\n// synchronously (deleting self), we must not access self->resolver_ afterward.\nTEST_F(CAresResolverTest, CheckForCNameSynchronousCallbackNoUAF) {\n  // Create a resolver that simulates the CNAME scenario\n  std::unique_ptr<CNameMockResolver, DelayedDestruction::Destructor>\n      cnameResolver(new CNameMockResolver());\n\n  TraceEvent te(TraceEventType::DnsResolution);\n  TimeUtil timeUtil;\n  auto cb = std::make_unique<MockResolutionCallback>();\n\n  // Create a Query for an A record lookup with the name that matches\n  // the question in our crafted CNAME response.\n  auto* query = new MockQuery(cnameResolver.get(),\n                              CAresResolver::RecordType::kA,\n                              \"original.example.com\",\n                              false,\n                              std::move(te),\n                              &timeUtil,\n                              std::move(teContext));\n\n  // Call resolve with timeout=0 to skip timeout scheduling.\n  // This triggers:\n  // 1. query() -> first callback with CNAME response\n  // 2. queryCallback() parses response, sees nttls=0, calls checkForCName()\n  // 3. checkForCName() calls query() for CNAME target\n  // 4. Second query() -> callback with ARES_ENODATA -> succeed() -> delete this\n  // 5. checkForCName() tries to access self->resolver_ (the bug!)\n  //\n  // The test passes if we get here without ASAN detecting a use-after-free.\n  query->resolve(cb.get(), std::chrono::milliseconds(0));\n\n  // Verify queryFinished was called twice (once for each query)\n  EXPECT_EQ(cnameResolver->queryFinishedCount(), 2);\n}\n"
  },
  {
    "path": "proxygen/lib/dns/test/CachingDNSResolverTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Memory.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/utils/test/MockTime.h>\n\n#include \"proxygen/lib/dns/test/Dummies.h\"\n\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nstruct CachingDNSParams {\n  size_t cachingMaxSize = 4096;\n  size_t cachingClearSize = 1;\n  size_t staleCacheSizeMultiplier = 4;\n  size_t staleCacheTTLMin = 24 * 60 * 60;\n  size_t staleCacheTTLScale = 3;\n};\n\nclass CachingDNSResolverFixture : public testing::Test {\n public:\n  void SetUp() override {\n    initializeWithDefaultParams();\n  }\n\n  void initializeResolverWithParams(const CachingDNSParams& params) {\n    DNSResolver::UniquePtr p(new DummyDNSResolver());\n    auto timeUtil = std::make_unique<MockTimeUtil>();\n    underlyingResolver_ = dynamic_cast<DummyDNSResolver*>(p.get());\n    CHECK(underlyingResolver_);\n    timeUtil_ = timeUtil.get();\n\n    cachingResolver_ =\n        std::make_unique<CachingDNSResolver>(std::move(p),\n                                             params.cachingMaxSize,\n                                             params.cachingClearSize,\n                                             params.staleCacheSizeMultiplier,\n                                             params.staleCacheTTLMin,\n                                             params.staleCacheTTLScale,\n                                             std::move(timeUtil));\n  }\n\n  void initializeWithDefaultParams() {\n    initializeResolverWithParams(CachingDNSParams{});\n  }\n\n protected:\n  std::unique_ptr<CachingDNSResolver> cachingResolver_;\n  DummyDNSResolver* underlyingResolver_{nullptr};\n  MockTimeUtil* timeUtil_{nullptr};\n  DummyDNSClient cb_;\n};\n\nTEST_F(CachingDNSResolverFixture, NoCrashWhileDestructing) {\n  string hostname(\"foo.bar.com\");\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  cachingResolver_.reset(); // no crash and error during deconstructing\n}\n\nTEST_F(CachingDNSResolverFixture, ResolutionError) {\n  underlyingResolver_->setIsRunning(false);\n  cachingResolver_->resolveHostname(&cb_, \"should fail\");\n  EXPECT_EQ(cb_.getNumFailures(), 1);\n}\n\nTEST_F(CachingDNSResolverFixture, CacheHit) {\n  string hostname(\"foo.bar.com\");\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  // Underlying resolver should have been hit.\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n\n  EXPECT_EQ(cb_.getNumSuccesses(), 2);\n}\n\nTEST_F(CachingDNSResolverFixture, CacheClearAfterMax) {\n  initializeResolverWithParams(\n      CachingDNSParams{.cachingMaxSize = 5, .cachingClearSize = 2});\n\n  // Pre-fill the cache with 5 records.\n  for (int i = 0; i < 5; i++) {\n    cachingResolver_->resolveHostname(&cb_, folly::to<std::string>(i));\n  }\n  // Should evict the first two records.\n  cachingResolver_->resolveHostname(&cb_, \"6\");\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 6);\n\n  cachingResolver_->resolveHostname(&cb_, \"0\");\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 7);\n  cachingResolver_->resolveHostname(&cb_, \"1\");\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 8);\n}\n\nTEST_F(CachingDNSResolverFixture, CacheClearExpiredRecords) {\n  // Magical string that forces Dummies to have a TTL of 2.\n  string hostname(\"foo\");\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n\n  // Should hit cache.\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n\n  timeUtil_->advance(std::chrono::milliseconds(5000));\n\n  // Should be a partial miss.\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 2);\n}\n\nTEST_F(CachingDNSResolverFixture, StaleCacheHitAfterFailure) {\n  // Magical string that forces Dummies to have a TTL of 2.\n  string hostname(\"foo\");\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n\n  timeUtil_->advance(std::chrono::milliseconds(5000));\n\n  underlyingResolver_->setIsRunning(false);\n  // Should be a hit from stale cache.\n  cachingResolver_->resolveHostname(&cb_, hostname);\n  EXPECT_EQ(underlyingResolver_->getHitCount(), 1);\n  EXPECT_EQ(cb_.getNumSuccesses(), 2);\n}\n"
  },
  {
    "path": "proxygen/lib/dns/test/DNSResolverTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/SocketAddress.h>\n#include <gtest/gtest.h>\n#include <proxygen/lib/dns/DNSResolver.h>\n\n#include <unordered_set>\n\nusing folly::SocketAddress;\nusing proxygen::DNSResolver;\nusing std::chrono::seconds;\n\nnamespace {\n\nsize_t h(const DNSResolver::Answer& a) {\n  return DNSResolver::AnswerHash{}(a);\n}\n\n} // namespace\n\nTEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_Address) {\n  // Same IP/port should be equal and have equal hash regardless of TTL\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  DNSResolver::Answer a2(seconds(30), SocketAddress(\"127.0.0.1\", 80));\n\n  EXPECT_TRUE(a1 == a2);\n  EXPECT_EQ(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_Name) {\n  DNSResolver::Answer n1(\n      seconds(10), std::string(\"example.com\"), DNSResolver::Answer::AT_NAME);\n  DNSResolver::Answer n2(\n      seconds(1), std::string(\"example.com\"), DNSResolver::Answer::AT_NAME);\n\n  EXPECT_TRUE(n1 == n2);\n  EXPECT_EQ(h(n1), h(n2));\n}\n\nTEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_CName) {\n  DNSResolver::Answer c1(seconds(10),\n                         std::string(\"cname.example.com\"),\n                         DNSResolver::Answer::AT_CNAME);\n  DNSResolver::Answer c2(seconds(10),\n                         std::string(\"cname.example.com\"),\n                         DNSResolver::Answer::AT_CNAME);\n  EXPECT_TRUE(c1 == c2);\n  EXPECT_EQ(h(c1), h(c2));\n}\n\nTEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_MX) {\n  DNSResolver::Answer m1(\n      seconds(60), /*priority*/ 10, std::string(\"mail.example.com\"));\n  DNSResolver::Answer m2(\n      seconds(5), /*priority*/ 10, std::string(\"mail.example.com\"));\n  EXPECT_TRUE(m1 == m2);\n  EXPECT_EQ(h(m1), h(m2));\n}\n\nTEST(DNSResolverAnswerHashTest, DistinctAnswersBehaveInUnorderedSet) {\n  // Build a set with distinct answers of different types and payloads\n  std::unordered_set<DNSResolver::Answer, DNSResolver::AnswerHash> set;\n\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 0));\n  DNSResolver::Answer a2(seconds(5), SocketAddress(\"::1\", 0));\n  DNSResolver::Answer n1(\n      seconds(5), std::string(\"example.com\"), DNSResolver::Answer::AT_NAME);\n  DNSResolver::Answer n2(\n      seconds(5), std::string(\"www.example.com\"), DNSResolver::Answer::AT_NAME);\n  DNSResolver::Answer c1(seconds(5),\n                         std::string(\"cname.example.com\"),\n                         DNSResolver::Answer::AT_CNAME);\n  DNSResolver::Answer m1(\n      seconds(5), 5 /*priority*/, std::string(\"mail.example.com\"));\n  DNSResolver::Answer m2(\n      seconds(5), 10 /*priority*/, std::string(\"mail.example.com\"));\n\n  EXPECT_TRUE(set.insert(a1).second);\n  EXPECT_TRUE(set.insert(a2).second);\n  EXPECT_TRUE(set.insert(n1).second);\n  EXPECT_TRUE(set.insert(n2).second);\n  EXPECT_TRUE(set.insert(c1).second);\n  EXPECT_TRUE(set.insert(m1).second);\n  EXPECT_TRUE(set.insert(m2).second);\n\n  // Reinserting equal values should not increase size\n  EXPECT_FALSE(set.insert(DNSResolver::Answer(seconds(999),\n                                              SocketAddress(\"127.0.0.1\", 0)))\n                   .second);\n  EXPECT_FALSE(set.insert(DNSResolver::Answer(seconds(1),\n                                              std::string(\"example.com\"),\n                                              DNSResolver::Answer::AT_NAME))\n                   .second);\n  EXPECT_FALSE(set.insert(DNSResolver::Answer(\n                              seconds(1), 5, std::string(\"mail.example.com\")))\n                   .second);\n\n  EXPECT_EQ(set.size(), 7);\n}\n\nTEST(DNSResolverAnswerHashTest, IgnoresTTLAndCreationTime) {\n  // creationTime is set at construction time; we can't set it directly,\n  // but we can construct the two objects at different times and ensure they\n  // still compare equal and have equal hashes.\n  DNSResolver::Answer a1(seconds(1), SocketAddress(\"1.2.3.4\", 0));\n  /* Small sleep to ensure creationTime differs if measured in seconds */\n  DNSResolver::Answer a2(seconds(999), SocketAddress(\"1.2.3.4\", 0));\n\n  EXPECT_TRUE(a1 == a2);\n  EXPECT_EQ(h(a1), h(a2));\n}\n\n// Tests to ensure each field in operator== and AnswerHash is properly checked.\n// If any field is missing from the implementation, these tests will fail.\n\nTEST(DNSResolverAnswerHashTest, DifferentTypeNotEqual) {\n  DNSResolver::Answer a1(\n      seconds(5), std::string(\"example.com\"), DNSResolver::Answer::AT_NAME);\n  DNSResolver::Answer a2(\n      seconds(5), std::string(\"example.com\"), DNSResolver::Answer::AT_CNAME);\n\n  EXPECT_FALSE(a1 == a2);\n  EXPECT_NE(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, DifferentAddressNotEqual) {\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  DNSResolver::Answer a2(seconds(5), SocketAddress(\"127.0.0.2\", 80));\n\n  EXPECT_FALSE(a1 == a2);\n  EXPECT_NE(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, DifferentNameNotEqual) {\n  DNSResolver::Answer n1(\n      seconds(5), std::string(\"example.com\"), DNSResolver::Answer::AT_NAME);\n  DNSResolver::Answer n2(\n      seconds(5), std::string(\"www.example.com\"), DNSResolver::Answer::AT_NAME);\n\n  EXPECT_FALSE(n1 == n2);\n  EXPECT_NE(h(n1), h(n2));\n}\n\nTEST(DNSResolverAnswerHashTest, DifferentCanonicalNameNotEqual) {\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a1.canonicalName = \"canonical1.example.com\";\n\n  DNSResolver::Answer a2(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a2.canonicalName = \"canonical2.example.com\";\n\n  EXPECT_FALSE(a1 == a2);\n  EXPECT_NE(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, DifferentPortNotEqual) {\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 0));\n  a1.port = 8080;\n\n  DNSResolver::Answer a2(seconds(5), SocketAddress(\"127.0.0.1\", 0));\n  a2.port = 9090;\n\n  EXPECT_FALSE(a1 == a2);\n  EXPECT_NE(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, DifferentPriorityNotEqual) {\n  DNSResolver::Answer m1(\n      seconds(5), /*priority*/ 5, std::string(\"mail.example.com\"));\n  DNSResolver::Answer m2(\n      seconds(5), /*priority*/ 10, std::string(\"mail.example.com\"));\n\n  EXPECT_FALSE(m1 == m2);\n  EXPECT_NE(h(m1), h(m2));\n}\n\nTEST(DNSResolverAnswerHashTest, SameCanonicalNameAndPortEqual) {\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a1.canonicalName = \"canonical.example.com\";\n  a1.port = 8080;\n\n  DNSResolver::Answer a2(seconds(30), SocketAddress(\"127.0.0.1\", 80));\n  a2.canonicalName = \"canonical.example.com\";\n  a2.port = 8080;\n\n  EXPECT_TRUE(a1 == a2);\n  EXPECT_EQ(h(a1), h(a2));\n}\n\nTEST(DNSResolverAnswerHashTest, CanonicalNameAndPortInUnorderedSet) {\n  std::unordered_set<DNSResolver::Answer, DNSResolver::AnswerHash> set;\n\n  DNSResolver::Answer a1(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a1.canonicalName = \"canonical1.example.com\";\n  a1.port = 8080;\n\n  DNSResolver::Answer a2(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a2.canonicalName = \"canonical2.example.com\";\n  a2.port = 8080;\n\n  DNSResolver::Answer a3(seconds(5), SocketAddress(\"127.0.0.1\", 80));\n  a3.canonicalName = \"canonical1.example.com\";\n  a3.port = 9090;\n\n  EXPECT_TRUE(set.insert(a1).second);\n  EXPECT_TRUE(set.insert(a2).second);\n  EXPECT_TRUE(set.insert(a3).second);\n  EXPECT_EQ(set.size(), 3);\n\n  // Same canonicalName and port as a1 should not be inserted\n  DNSResolver::Answer a4(seconds(999), SocketAddress(\"127.0.0.1\", 80));\n  a4.canonicalName = \"canonical1.example.com\";\n  a4.port = 8080;\n\n  EXPECT_FALSE(set.insert(a4).second);\n  EXPECT_EQ(set.size(), 3);\n}\n"
  },
  {
    "path": "proxygen/lib/dns/test/Dummies.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/dns/CAresResolver.h\"\n#include \"proxygen/lib/dns/CachingDNSResolver.h\"\n\nnamespace proxygen {\n\nclass DummyDNSResolver : public DNSResolver {\n public:\n  void resolveHostname(\n      DNSResolver::ResolutionCallback* cb,\n      const std::string& name,\n      std::chrono::milliseconds /*timeout*/ = std::chrono::milliseconds(100),\n      sa_family_t family = AF_INET,\n      TraceEventContext /*teContext*/ = TraceEventContext()) override {\n\n    if (!isRunning_) {\n      cb->resolutionError(folly::make_exception_wrapper<Exception>(\n          UNKNOWN, \"dummy DNS server is down.\"));\n      return;\n    }\n\n    int ttl;\n    if (name == \"foo\") {\n      ttl = 2;\n    } else {\n      ttl = 2000;\n    }\n\n    Answer ans(std::chrono::seconds(ttl), folly::SocketAddress(\"1.2.3.4\", 0));\n    Answer ans6(std::chrono::seconds(ttl),\n                folly::SocketAddress(\"2401:db00:20:700a:face:0:17:0\", 0));\n\n    std::vector<Answer> results;\n    switch (family) {\n      case AF_INET:\n        results.push_back(ans);\n        break;\n      case AF_INET6:\n        results.push_back(ans6);\n        break;\n      default:\n        results.push_back(ans);\n        results.push_back(ans6);\n    }\n\n    hitCount_++;\n    cb->resolutionSuccess(results);\n  }\n\n  // NOPS\n  void resolveAddress(DNSResolver::ResolutionCallback* /*cb*/,\n                      const folly::SocketAddress& /*address*/,\n                      std::chrono::milliseconds /*timeout*/ =\n                          std::chrono::milliseconds(100)) override {\n  }\n\n  void resolveMailExchange(DNSResolver::ResolutionCallback* /*cb*/,\n                           const std::string& /*domain*/,\n                           std::chrono::milliseconds /*timeout*/ =\n                               std::chrono::milliseconds(100)) override {\n  }\n\n  void setStatsCollector(\n      DNSResolver::StatsCollector* /*statsCollector*/) override {\n  }\n\n  [[nodiscard]] DNSResolver::StatsCollector* getStatsCollector()\n      const override {\n    return nullptr;\n  }\n\n  [[nodiscard]] int getHitCount() const {\n    return hitCount_;\n  }\n\n  [[nodiscard]] bool isRunning() const {\n    return isRunning_;\n  }\n\n  void setIsRunning(bool value) {\n    isRunning_ = value;\n  }\n\n private:\n  int hitCount_{0};\n  bool isRunning_{true};\n};\n\nclass DummyDNSClient : public DNSResolver::ResolutionCallback {\n public:\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> answers) noexcept override {\n    fail_ = false;\n    answers_ = answers;\n    numSuccesses_++;\n  }\n\n  void resolutionError(\n      const folly::exception_wrapper& /*ew*/) noexcept override {\n    fail_ = true;\n    answers_.clear();\n    numFailures_++;\n  }\n\n  std::vector<DNSResolver::Answer>& getAnswers() {\n    return answers_;\n  }\n\n  [[nodiscard]] bool didLastResolutionFail() const {\n    return fail_;\n  }\n\n  [[nodiscard]] int getNumFailures() const {\n    return numFailures_;\n  }\n\n  [[nodiscard]] int getNumSuccesses() const {\n    return numSuccesses_;\n  }\n\n private:\n  int numFailures_{0};\n  int numSuccesses_{0};\n\n  std::vector<DNSResolver::Answer> answers_;\n  bool fail_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/FutureDNSResolverTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/FutureDNSResolver.h\"\n\n#include <map>\n#include <utility>\n#include <vector>\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GTest.h>\n\nusing std::string;\n\nnamespace proxygen {\nnamespace {\n\ntemplate <typename K, typename V>\nstd::map<V, K> invert(const std::map<K, V>& input) {\n  std::map<V, K> result;\n  for (const auto& pair : input) {\n    result[pair.second] = pair.first;\n  }\n  return result;\n}\n\nclass FakeResolverError : public std::runtime_error {\n public:\n  explicit FakeResolverError(string message)\n      : std::runtime_error(std::move(message)) {\n  }\n};\n\nclass FakeDNSResolver : public DNSResolver {\n public:\n  explicit FakeDNSResolver(\n      folly::EventBase* evb,\n      const std::map<folly::SocketAddress, string>& addrToHostMap,\n      std::map<string, std::vector<std::pair<int, string>>>\n          domainToMailExchangeMap)\n      : evb_{evb},\n        addrToHostMap_{addrToHostMap},\n        hostToAddrMap_{invert(addrToHostMap)},\n        mailExchangeMap_{std::move(domainToMailExchangeMap)} {\n    CHECK_EQ(addrToHostMap_.size(), hostToAddrMap_.size());\n  }\n\n  ~FakeDNSResolver() override = default;\n\n  void resolveAddress(ResolutionCallback* cb,\n                      const folly::SocketAddress& address,\n                      std::chrono::milliseconds /* timeout */ =\n                          FutureDNSResolver::kDefaultTimeout()) override {\n    evb_->runInEventBaseThread([cb, address, this]() {\n      auto iter = addrToHostMap_.find(address);\n      if (iter != addrToHostMap_.end()) {\n        DNSResolver::Answer answer;\n        answer.type = DNSResolver::Answer::AnswerType::AT_NAME;\n        answer.name = iter->second;\n        cb->resolutionSuccess({std::move(answer)});\n      } else {\n        FakeResolverError error{\"Cannot resolve address\"};\n        cb->resolutionError(folly::exception_wrapper{std::move(error)});\n      }\n    });\n  }\n\n  void resolveHostname(\n      ResolutionCallback* cb,\n      const std::string& name,\n      std::chrono::milliseconds /* timeout */ =\n          FutureDNSResolver::kDefaultTimeout(),\n      sa_family_t /* family */ = AF_INET,\n      TraceEventContext /* teContext */ = TraceEventContext()) override {\n    evb_->runInEventBaseThread([cb, name, this]() {\n      auto iter = hostToAddrMap_.find(name);\n      if (iter != hostToAddrMap_.end()) {\n        DNSResolver::Answer answer;\n        answer.type = DNSResolver::Answer::AnswerType::AT_ADDRESS;\n        answer.address = iter->second;\n        cb->resolutionSuccess({std::move(answer)});\n      } else {\n        FakeResolverError error{\"Cannot resolve hostname\"};\n        cb->resolutionError(folly::exception_wrapper{std::move(error)});\n      }\n    });\n  }\n\n  void resolveMailExchange(ResolutionCallback* cb,\n                           const std::string& domain,\n                           std::chrono::milliseconds /* timeout */ =\n                               FutureDNSResolver::kDefaultTimeout()) override {\n    evb_->runInEventBaseThread([cb, domain, this]() {\n      auto iter = mailExchangeMap_.find(domain);\n      if (iter != mailExchangeMap_.end()) {\n        std::vector<std::pair<int, string>> entries = iter->second;\n        std::vector<DNSResolver::Answer> answers = {};\n        for (const std::pair<int, string>& entry : entries) {\n          DNSResolver::Answer answer;\n          answer.type = DNSResolver::Answer::AnswerType::AT_MX;\n          answer.priority = entry.first;\n          answer.name = entry.second;\n          answers.push_back(std::move(answer));\n        }\n        cb->resolutionSuccess(answers);\n      } else {\n        FakeResolverError error{\"Cannot resolve mail exchange info\"};\n        cb->resolutionError(folly::exception_wrapper{std::move(error)});\n      }\n    });\n  }\n\n  [[nodiscard]] DNSResolver::StatsCollector* getStatsCollector()\n      const override {\n    return statsCollector_;\n  }\n\n  void setStatsCollector(DNSResolver::StatsCollector* collector) override {\n    statsCollector_ = collector;\n  }\n\n private:\n  folly::EventBase* evb_;\n  std::map<folly::SocketAddress, string> addrToHostMap_;\n  std::map<string, folly::SocketAddress> hostToAddrMap_;\n  std::map<string, std::vector<std::pair<int, string>>> mailExchangeMap_;\n  DNSResolver::StatsCollector* statsCollector_;\n};\n} // namespace\n\nclass FutureDNSResolverTest : public ::testing::Test {\n public:\n  ~FutureDNSResolverTest() override = default;\n\n protected:\n  void SetUp() override {\n    DNSResolver::UniquePtr proxygenResolver;\n    std::map<folly::SocketAddress, string> addrToHostMap{\n        {folly::SocketAddress(folly::IPAddress(\"1.2.3.4\"), 0), \"1234.com\"},\n        {folly::SocketAddress(folly::IPAddress(\"2.3.4.5\"), 0), \"2345.com\"},\n    };\n    std::map<string, std::vector<std::pair<int, string>>>\n        domainToMailExchangeMap{\n            {\"1234.com\",\n             {\n                 {10, \"mail1.1234.com\"},\n                 {20, \"mail2.1234.com\"},\n                 {30, \"mail3.1234.com\"},\n             }},\n            {\"2345.com\",\n             {\n                 {100, \"mailone.2345.com\"},\n                 {100, \"mailtwo.2345.com\"},\n             }},\n        };\n    proxygenResolver.reset(\n        new FakeDNSResolver(evb_, addrToHostMap, domainToMailExchangeMap));\n    resolver_ =\n        std::make_unique<FutureDNSResolver>(evb_, std::move(proxygenResolver));\n  }\n\n  using AnswerType = DNSResolver::Answer::AnswerType;\n\n  folly::EventBaseManager* ebm_{folly::EventBaseManager::get()};\n  folly::EventBase* evb_{ebm_->getEventBase()};\n  std::unique_ptr<FutureDNSResolver> resolver_;\n};\n\nTEST_F(FutureDNSResolverTest, TestResolveAddressSuccess) {\n  auto addr1 = folly::SocketAddress(folly::IPAddress(\"1.2.3.4\"), 0);\n  auto answers1 = resolver_->resolveAddress(addr1).getVia(evb_);\n  EXPECT_EQ(1, answers1.size());\n  EXPECT_EQ(AnswerType::AT_NAME, answers1[0].type);\n  EXPECT_EQ(\"1234.com\", answers1[0].name);\n\n  auto addr2 = folly::SocketAddress(folly::IPAddress(\"2.3.4.5\"), 0);\n  auto answers2 = resolver_->resolveAddress(addr2).getVia(evb_);\n  EXPECT_EQ(1, answers2.size());\n  EXPECT_EQ(AnswerType::AT_NAME, answers2[0].type);\n  EXPECT_EQ(\"2345.com\", answers2[0].name);\n}\n\nTEST_F(FutureDNSResolverTest, TestResolveAddressFail) {\n  auto addr = folly::SocketAddress(folly::IPAddress(\"6.7.8.9\"), 0);\n  EXPECT_THROW(\n      { resolver_->resolveAddress(addr).getVia(evb_); }, FakeResolverError);\n}\n\nTEST_F(FutureDNSResolverTest, TestResolveHostnameSuccess) {\n  auto addr1 = folly::SocketAddress(folly::IPAddress(\"1.2.3.4\"), 0);\n  auto answers1 = resolver_->resolveHostname(\"1234.com\").getVia(evb_);\n  EXPECT_EQ(1, answers1.size());\n  EXPECT_EQ(AnswerType::AT_ADDRESS, answers1[0].type);\n  EXPECT_EQ(addr1, answers1[0].address);\n\n  auto addr2 = folly::SocketAddress(folly::IPAddress(\"2.3.4.5\"), 0);\n  auto answers2 = resolver_->resolveHostname(\"2345.com\").getVia(evb_);\n  EXPECT_EQ(1, answers2.size());\n  EXPECT_EQ(AnswerType::AT_ADDRESS, answers2[0].type);\n  EXPECT_EQ(addr2, answers2[0].address);\n}\n\nTEST_F(FutureDNSResolverTest, TestResolveHostnameFail) {\n  EXPECT_THROW(\n      { resolver_->resolveHostname(\"unknown-host.com\").getVia(evb_); },\n      FakeResolverError);\n}\n\nTEST_F(FutureDNSResolverTest, TestResolveMailExchangeSuccess) {\n  auto host11 = \"mail1.1234.com\";\n  auto priority11 = 10;\n  auto host12 = \"mail2.1234.com\";\n  auto priority12 = 20;\n  auto host13 = \"mail3.1234.com\";\n  auto priority13 = 30;\n  auto answers1 = resolver_->resolveMailExchange(\"1234.com\").getVia(evb_);\n  EXPECT_EQ(3, answers1.size());\n  EXPECT_EQ(AnswerType::AT_MX, answers1[0].type);\n  EXPECT_EQ(host11, answers1[0].name);\n  EXPECT_EQ(priority11, answers1[0].priority);\n  EXPECT_EQ(AnswerType::AT_MX, answers1[1].type);\n  EXPECT_EQ(host12, answers1[1].name);\n  EXPECT_EQ(priority12, answers1[1].priority);\n  EXPECT_EQ(AnswerType::AT_MX, answers1[2].type);\n  EXPECT_EQ(host13, answers1[2].name);\n  EXPECT_EQ(priority13, answers1[2].priority);\n\n  auto host21 = \"mailone.2345.com\";\n  auto priority21 = 100;\n  auto host22 = \"mailtwo.2345.com\";\n  auto priority22 = 100;\n  auto answers2 = resolver_->resolveMailExchange(\"2345.com\").getVia(evb_);\n  EXPECT_EQ(2, answers2.size());\n  EXPECT_EQ(AnswerType::AT_MX, answers2[0].type);\n  EXPECT_EQ(host21, answers2[0].name);\n  EXPECT_EQ(priority21, answers2[0].priority);\n  EXPECT_EQ(AnswerType::AT_MX, answers2[1].type);\n  EXPECT_EQ(host22, answers2[1].name);\n  EXPECT_EQ(priority22, answers2[1].priority);\n}\n\nTEST_F(FutureDNSResolverTest, TestResolveMailExchangeFail) {\n  EXPECT_THROW(\n      { resolver_->resolveMailExchange(\"unknown-host.com\").getVia(evb_); },\n      FakeResolverError);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/MockDNSModule.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/test/MockDNSModule.h\"\n\nnamespace proxygen {\n\nMockDNSModule::MockDNSModule() = default;\n\nMockDNSModule::~MockDNSModule() = default;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/MockDNSModule.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/dns/DNSModule.h\"\n\nnamespace proxygen {\n\nclass MockDNSModule : public DNSModule {\n public:\n  MockDNSModule();\n\n  ~MockDNSModule() override;\n\n  void setMockDNSResolver(DNSResolver::UniquePtr r) {\n    resolver = std::move(r);\n  }\n\n  DNSResolver::UniquePtr provideDNSResolver(\n      folly::EventBase* /*eventBase*/) override {\n    return std::move(resolver);\n  }\n\n private:\n  DNSResolver::UniquePtr resolver;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/MockDNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <stdexcept>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\nclass MockDNSResolver : public proxygen::DNSResolver {\n public:\n  class MockQueryBase : public DNSResolver::QueryBase {\n   public:\n    MOCK_METHOD(void, cancelResolutionImpl, ());\n  };\n\n  MOCK_METHOD(void,\n              resolveAddress,\n              (proxygen::DNSResolver::ResolutionCallback*,\n               const folly::SocketAddress&,\n               std::chrono::milliseconds));\n\n  MOCK_METHOD(void,\n              resolveHostname,\n              (DNSResolver::ResolutionCallback*,\n               const std::string&,\n               std::chrono::milliseconds,\n               sa_family_t,\n               TraceEventContext));\n\n  MOCK_METHOD(void,\n              resolveMailExchange,\n              (DNSResolver::ResolutionCallback*,\n               const std::string&,\n               std::chrono::milliseconds));\n\n  MOCK_METHOD(void, setStatsCollector, (DNSResolver::StatsCollector*));\n  MOCK_METHOD(DNSResolver::StatsCollector*, getStatsCollector, (), (const));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/Mocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n\nnamespace proxygen {\n\nclass MockDNSClient : public DNSResolver::ResolutionCallback {\n public:\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> answers) noexcept override {\n    _resolutionSuccess(answers);\n  }\n  MOCK_METHOD(void, _resolutionSuccess, (std::vector<DNSResolver::Answer>));\n\n  void resolutionError(const folly::exception_wrapper& ex) noexcept override {\n    _resolutionError(ex);\n  }\n  MOCK_METHOD(void, _resolutionError, (const folly::exception_wrapper&));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/dns/test/Rfc6724Test.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/test/SyncDNSResolverTest.h\"\n\n#include \"proxygen/lib/dns/Rfc6724.h\"\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing folly::SocketAddress;\nusing std::chrono::seconds;\n\n// RFC 6724 section 10.2\nTEST(SyncDNSResolver, Basic102) {\n  {\n    std::vector<SocketAddress> addrs{SocketAddress(\"198.51.100.121\", 0),\n                                     SocketAddress(\"2001:db8:1::1\", 0)};\n    std::vector<SocketAddress> expected{SocketAddress(\"2001:db8:1::1\", 0),\n                                        SocketAddress(\"198.51.100.121\", 0)};\n    SocketAddress src1(\"2001:db8:1::2\", 0);\n    rfc6724_sort(addrs, &src1);\n    for (size_t i = 0; i < expected.size(); ++i) {\n      EXPECT_EQ(expected[i], addrs[i]);\n    }\n  }\n\n  {\n    std::vector<SocketAddress> addrs{SocketAddress(\"2001:db8:1::1\", 0),\n                                     SocketAddress(\"2002:c633:6401::1\", 0)};\n    std::vector<SocketAddress> expected{SocketAddress(\"2002:c633:6401::1\", 0),\n                                        SocketAddress(\"2001:db8:1::1\", 0)};\n    SocketAddress src1(\"2002:c633:6401::2\", 0);\n    rfc6724_sort(addrs, &src1);\n    for (size_t i = 0; i < expected.size(); ++i) {\n      EXPECT_EQ(expected[i], addrs[i]);\n    }\n  }\n\n  {\n    std::vector<SocketAddress> addrs{SocketAddress(\"2001:db8:1::1\", 0),\n                                     SocketAddress(\"fe80::1\", 0)};\n    std::vector<SocketAddress> expected{SocketAddress(\"fe80::1\", 0),\n                                        SocketAddress(\"2001:db8:1::1\", 0)};\n    SocketAddress src1(\"fe80::2\", 0);\n    rfc6724_sort(addrs, &src1);\n    for (size_t i = 0; i < expected.size(); ++i) {\n      EXPECT_EQ(expected[i], addrs[i]);\n    }\n  }\n}\n\nTEST_F(SyncDNSResolverTest, Sorted) {\n  std::vector<DNSResolver::Answer> results{\n      DNSResolver::Answer(seconds(5), SocketAddress(\"198.51.100.121\", 0)),\n      DNSResolver::Answer(seconds(5), SocketAddress(\"2001:db8:1::1\", 0))};\n\n  std::vector<SocketAddress> expected{SocketAddress(\"2001:db8:1::1\", 0),\n                                      SocketAddress(\"198.51.100.121\", 0)};\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        cb->resolutionSuccess(results);\n                      }),\n                      Return()));\n\n  auto actual = syncResolver_->resolveHostname(\n      \"www.facebook.com\", seconds(5), AF_UNSPEC, true);\n  ASSERT_EQ(expected.size(), actual.size());\n  for (size_t i = 0; i < expected.size(); ++i) {\n    ASSERT_EQ(expected[i], actual[i]);\n  }\n}\n\nTEST_F(SyncDNSResolverTest, SortedLocal) {\n  std::vector<DNSResolver::Answer> results{\n      DNSResolver::Answer(seconds(5), SocketAddress(\"127.0.0.1\", 0)),\n      DNSResolver::Answer(seconds(5), SocketAddress(\"::1\", 0))};\n\n  std::vector<SocketAddress> expected{SocketAddress(\"::1\", 0),\n                                      SocketAddress(\"127.0.0.1\", 0)};\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        cb->resolutionSuccess(results);\n                      }),\n                      Return()));\n\n  auto actual = syncResolver_->resolveHostname(\n      \"www.facebook.com\", seconds(5), AF_UNSPEC, true);\n  ASSERT_EQ(expected.size(), actual.size());\n  for (size_t i = 0; i < expected.size(); ++i) {\n    ASSERT_EQ(expected[i], actual[i]);\n  }\n}\n\nTEST_F(SyncDNSResolverTest, Unsorted) {\n  std::vector<DNSResolver::Answer> expected{\n      DNSResolver::Answer(seconds(5), SocketAddress(\"127.0.0.1\", 0)),\n      DNSResolver::Answer(seconds(5), SocketAddress(\"::1\", 0))};\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        cb->resolutionSuccess(expected);\n                      }),\n                      Return()));\n\n  auto actual = syncResolver_->resolveHostname(\n      \"www.facebook.com\", seconds(5), AF_UNSPEC, false);\n  ASSERT_EQ(expected.size(), actual.size());\n  for (size_t i = 0; i < expected.size(); ++i) {\n    ASSERT_EQ(expected[i].address, actual[i]);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/dns/test/SyncDNSResolverTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/dns/test/SyncDNSResolverTest.h\"\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing folly::SocketAddress;\nusing std::chrono::seconds;\n\nTEST_F(SyncDNSResolverTest, Success) {\n  std::vector<DNSResolver::Answer> expected{\n      DNSResolver::Answer(seconds(5), SocketAddress(\"::1\", 0)),\n      DNSResolver::Answer(seconds(5), SocketAddress(\"127.0.0.1\", 0)),\n      DNSResolver::Answer(seconds(5), SocketAddress(\"1.2.3.4\", 0))};\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        cb->resolutionSuccess(expected);\n                      }),\n                      Return()));\n\n  auto actual = syncResolver_->resolveHostname(\"www.facebook.com\");\n  ASSERT_EQ(expected.size(), actual.size());\n  for (size_t i = 0; i < expected.size(); ++i) {\n    ASSERT_EQ(expected[i].address, actual[i]);\n  }\n}\n\nTEST_F(SyncDNSResolverTest, EmptyResponseIsTreatedAsError) {\n  std::vector<DNSResolver::Answer> expected;\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        cb->resolutionSuccess(expected);\n                      }),\n                      Return()));\n\n  ASSERT_THROW(syncResolver_->resolveHostname(\"www.facebook.com\"),\n               proxygen::DNSResolver::Exception);\n}\n\nTEST_F(SyncDNSResolverTest, AsyncSuccess) {\n  std::vector<DNSResolver::Answer> expected{\n      DNSResolver::Answer(seconds(5), SocketAddress(\"127.0.0.1\", 0))};\n\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                 const std::string& /*hostname*/,\n                                 std::chrono::milliseconds /*timeout*/,\n                                 sa_family_t /*family*/,\n                                 TraceEventContext /*teContext*/) {\n                        evb_->runInLoop(\n                            [&, cb]() { cb->resolutionSuccess(expected); });\n                      }),\n                      Return()));\n\n  auto actual = syncResolver_->resolveHostname(\"www.facebook.com\");\n  ASSERT_EQ(expected[0].address, actual[0]);\n}\n\nTEST_F(SyncDNSResolverTest, Failure) {\n  EXPECT_CALL(*resolver_, resolveHostname(_, Eq(\"www.facebook.com\"), _, _, _))\n      .WillOnce(DoAll(\n          Invoke([&](DNSResolver::ResolutionCallback* cb,\n                     const std::string& /*hostname*/,\n                     std::chrono::milliseconds /*timeout*/,\n                     sa_family_t /*family*/,\n                     TraceEventContext /*teContext*/) {\n            auto ew = folly::make_exception_wrapper<std::runtime_error>(\"test\");\n            cb->resolutionError(ew);\n          }),\n          Return()));\n\n  ASSERT_THROW(syncResolver_->resolveHostname(\"www.facebook.com\"),\n               std::runtime_error);\n}\n"
  },
  {
    "path": "proxygen/lib/dns/test/SyncDNSResolverTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <folly/portability/GTest.h>\n\n#include \"proxygen/lib/dns/SyncDNSResolver.h\"\n#include \"proxygen/lib/dns/test/MockDNSResolver.h\"\n\nnamespace proxygen {\n\nclass SyncDNSResolverTest : public testing::Test {\n public:\n  void SetUp() override {\n    resolver_ = new MockDNSResolver();\n    syncResolver_ =\n        std::make_unique<SyncDNSResolver>(DNSResolver::UniquePtr(resolver_));\n    evb_ = syncResolver_->getEventBase();\n  }\n\n  void TearDown() override {\n    syncResolver_.reset();\n    evb_ = nullptr;\n  }\n\n protected:\n  MockDNSResolver* resolver_; // unowned\n  std::unique_ptr<SyncDNSResolver> syncResolver_;\n  folly::EventBase* evb_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/healthcheck/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_healthcheck_callback\n  SRCS\n    ServerHealthCheckerCallback.cpp\n  EXPORTED_DEPS\n    proxygen_utils_time_util\n    Folly::folly_container_f14_hash\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_healthcheck_pool_healthcheck\n  EXPORTED_DEPS\n    proxygen_healthcheck_callback\n    proxygen_utils_time_util\n    Folly::folly_io_socket_option_map\n    Folly::folly_network_address\n)\n"
  },
  {
    "path": "proxygen/lib/healthcheck/PoolHealthChecker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/SocketAddress.h>\n#include <folly/io/SocketOptionMap.h>\n#include <proxygen/lib/utils/Time.h>\n\n#include <proxygen/lib/healthcheck/ServerHealthCheckerCallback.h>\n\nnamespace proxygen {\n\n/*\n * Interface for a collection of server health checkers.\n * Public methods can be accessed from any thread.\n *\n * It is necessary to remove all servers or call deleteAllCheckers (blocking)\n * before deleting this object.\n */\nclass PoolHealthChecker {\n public:\n  /*\n   * Start/stop all the health checkers in the pools.\n   */\n  virtual void start() = 0;\n  virtual void stop() = 0;\n\n  /*\n   * Blocking method that deletes all checkers in appropriate threads and waits\n   * for them to complete.\n   */\n  virtual void deleteAllCheckers() = 0;\n\n  /**\n   * Add a new server to the healthchecker.\n   *\n   * Bind the HC socket to <bindAddress>, if provided.\n   * Add <extraSockOpts> to socket options, if provided.\n   * Replace HC address with <overrideAddress>, if provided.\n   *   Still use <address> or <name> to identify the server.\n   */\n  virtual void addServer(\n      const std::string& name,\n      const folly::SocketAddress& address,\n      bool isSecure,\n      std::shared_ptr<ServerHealthCheckerCallback> callback,\n      std::optional<folly::SocketAddress> /* bindAddress */ = std::nullopt,\n      std::optional<folly::SocketOptionMap> /*extraSockOpts */ = std::nullopt,\n      std::optional<folly::SocketAddress> /* overrideAddress */ =\n          std::nullopt) = 0;\n\n  virtual void removeServer(const folly::SocketAddress& address) = 0;\n\n  [[nodiscard]] virtual std::chrono::milliseconds getCheckInterval() const = 0;\n\n  /**\n   * If external health checking is used, set last update time here to\n   * avoid redundant local health checks.\n   */\n  virtual void setLastExternalUpdateTime(\n      std::vector<folly::SocketAddress>&& addresses, TimePoint t) {\n  }\n\n  virtual ~PoolHealthChecker() = default;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/healthcheck/ServerHealthCheckerCallback.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/healthcheck/ServerHealthCheckerCallback.h>\n\nnamespace proxygen {\n\nconst std::string serverDownInfoStr(ServerDownInfo info) {\n  switch (info) {\n    case ServerDownInfo::NONE:\n      return \"None\";\n    case ServerDownInfo::PASSIVE_HEALTHCHECK_FAIL:\n      return \"Passive HealthCheck Failed\";\n    case ServerDownInfo::HEALTHCHECK_TIMEOUT:\n      return \"Active HealthCheck Timed Out\";\n    case ServerDownInfo::HEALTHCHECK_BODY_MISMATCH:\n      return \"Active HealthCheck Body Mismatch\";\n    case ServerDownInfo::HEALTHCHECK_NON200_STATUS:\n      return \"Active HealthCheck Non-200 Status\";\n    case ServerDownInfo::HEALTHCHECK_MESSAGE_ERROR:\n      return \"Active HealthCheck Message Error\";\n    case ServerDownInfo::HEALTHCHECK_WRITE_ERROR:\n      return \"Active HealthCheck Write Error\";\n    case ServerDownInfo::HEALTHCHECK_UPGRADE_ERROR:\n      return \"Active HealthCheck Unexpected Upgrade\";\n    case ServerDownInfo::HEALTHCHECK_EOF:\n      return \"Active HealthCheck Server EOF\";\n    case ServerDownInfo::HEALTHCHECK_CONNECT_ERROR:\n      return \"Active HealthCheck Connect Failed\";\n    case ServerDownInfo::FEEDBACK_LOOP_HIGH_LOAD:\n      return \"Feedback Loop High Load\";\n    case ServerDownInfo::HEALTHCHECK_UNKNOWN_ERROR:\n      return \"Unknown HealthCheck Error\";\n    case ServerDownInfo::HEALTH_UNKNOWN:\n      return \"Server Health Unknown\";\n    default:\n      return \"[missing down info string!]\";\n  }\n}\n\n// Convert extraInfo to a map, overrides duplicate headers\nfolly::F14FastMap<std::string, std::string>\nServerHealthCheckerCallback::ExtraInfo::toMap() const {\n  folly::F14FastMap<std::string, std::string> mapOut;\n  for (const auto& it : *this) {\n    mapOut.insert(it);\n  }\n  return mapOut;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/healthcheck/ServerHealthCheckerCallback.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n#include <vector>\n\n#include <folly/SocketAddress.h>\n#include <folly/container/F14Map.h>\n\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\nclass ServerHealthChecker;\n\nusing LoadType = uint32_t;\n\nstruct ServerLoadInfo {\n  double cpuUser{-1.};\n  double cpuSys{-1.};\n  double cpuIdle{-1.};\n  LoadType queueLen{0};\n  bool operator!=(const ServerLoadInfo& rhs) {\n    return cpuUser != rhs.cpuUser || cpuSys != rhs.cpuSys ||\n           cpuIdle != rhs.cpuIdle || queueLen != rhs.queueLen;\n  }\n};\n\n// In-code definition of WWWThriftServerInfo\n// Only store relevant fields\nstruct ThriftWWWCustomHealthCheckerFields {\n  int64_t jitWeightFactor{0};\n  int64_t loadHint{0};\n  int64_t serverUpTime{0};\n};\n\nenum ServerDownInfo {\n  NONE = 0,\n\n  PASSIVE_HEALTHCHECK_FAIL = 1,\n  HEALTHCHECK_TIMEOUT = 2,\n  HEALTHCHECK_BODY_MISMATCH = 3,\n  HEALTHCHECK_NON200_STATUS = 4,\n  HEALTHCHECK_MESSAGE_ERROR = 5,\n  HEALTHCHECK_WRITE_ERROR = 6,\n  HEALTHCHECK_UPGRADE_ERROR = 7,\n  HEALTHCHECK_EOF = 8,\n  HEALTHCHECK_CONNECT_ERROR = 9,\n  FEEDBACK_LOOP_HIGH_LOAD = 10,\n  HEALTH_UNKNOWN = 11,\n\n  HEALTHCHECK_UNKNOWN_ERROR = 99,\n};\n\nconst std::string serverDownInfoStr(ServerDownInfo info);\n\n/*\n * ServerHealthCheckerCallback is the interface for receiving health check\n * responses. The caller may be from a different thread.\n */\nclass ServerHealthCheckerCallback {\n public:\n  // Additional info received from a successful healthcheck (e.g. HTTP headers)\n  // This is a vector of pairs for legacy reasons.\n  // All new usecases should be defined as struct members.\n  struct ExtraInfo : public std::vector<std::pair<std::string, std::string>> {\n    ExtraInfo() = default;\n    ~ExtraInfo() = default;\n    ExtraInfo(const ExtraInfo&) = default;\n    ExtraInfo& operator=(const ExtraInfo&) = default;\n    ExtraInfo(ExtraInfo&&) = default;\n    ExtraInfo& operator=(ExtraInfo&&) = default;\n    explicit ExtraInfo(std::vector<std::pair<std::string, std::string>> v)\n        : std::vector<std::pair<std::string, std::string>>(std::move(v)) {\n    }\n    ExtraInfo& operator=(\n        const std::vector<std::pair<std::string, std::string>>& v) {\n      std::vector<std::pair<std::string, std::string>>::operator=(v);\n      return *this;\n    }\n\n    // Converts all fields within the vector of pairs to a map.\n    [[nodiscard]] folly::F14FastMap<std::string, std::string> toMap() const;\n\n    // Custom fields for WWW healthchecks\n    std::optional<ThriftWWWCustomHealthCheckerFields> thriftWWWCustomFields;\n  };\n\n  virtual void processHealthCheckFailure(\n      const TimePoint& startTime,\n      ServerDownInfo reason,\n      const std::string& extraReasonStr = std::string(),\n      const ExtraInfo* extraInfo = nullptr) = 0;\n\n  virtual void processHealthCheckSuccess(\n      const TimePoint& startTime,\n      LoadType load,\n      const ServerLoadInfo* serverLoadInfo = nullptr,\n      const ExtraInfo* extraInfo = nullptr) = 0;\n\n  virtual ~ServerHealthCheckerCallback() = default;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http\n  EXPORTED_DEPS\n    proxygen_http_client\n    proxygen_http_session_server\n    Folly::folly_conv\n    Folly::folly_fbstring\n    Folly::folly_fbvector\n    Folly::folly_intrusive_list\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_async_async_transport\n    Folly::folly_io_iobuf\n    Folly::folly_json_dynamic\n    Folly::folly_lang_bits\n    Folly::folly_memory\n    Folly::folly_network_address\n    Folly::folly_optional\n    Folly::folly_portability\n    Folly::folly_random\n    Folly::folly_range\n    Folly::folly_thread_local\n)\n\nproxygen_add_library(proxygen_http_header_constants\n  SRCS\n    HeaderConstants.cpp\n)\n\nproxygen_add_library(proxygen_http_http_message_filters\n  SRCS\n    HTTPMessageFilters.cpp\n  EXPORTED_DEPS\n    proxygen_http_session_http_transaction\n    proxygen_http_sink_client_sink_interface\n    Folly::folly_io_async_destructor_check\n    Folly::folly_memory\n)\n\nproxygen_add_library(proxygen_http_client\n  SRCS\n    HTTPConnector.cpp\n  DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_codec_http2_codec\n    proxygen_http_session_http_transaction\n    proxygen_http_session_http_upstream_session\n    wangle::wangle_ssl_ssl_util\n    Folly::folly_io_async_async_ssl_socket\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_codec_factory\n    proxygen_utils_shared_wheel_timer\n    proxygen_utils_time_util\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_async_ssl_context\n    Folly::folly_io_socket_option_map\n    Folly::folly_ssl_ssl_session\n)\n\nproxygen_add_library(proxygen_http_client_base\n  EXPORTED_DEPS\n    proxygen_http_session_http_upstream_session\n)\n\nproxygen_add_library(proxygen_http_http_connector_with_fizz\n  SRCS\n    HTTPConnectorWithFizz.cpp\n  DEPS\n    proxygen_http_session_http_upstream_session\n  EXPORTED_DEPS\n    proxygen_http_client\n    fizz::fizz\n    Folly::folly_io_socket_option_map\n)\n\nproxygen_add_library(proxygen_http_hq_connector\n  SRCS\n    HQConnector.cpp\n  DEPS\n    proxygen_http_session_hq_session\n    mvfst::mvfst_common_udpsocket_folly_async_udp_socket\n    mvfst::mvfst_congestion_control_congestion_controller_factory\n    mvfst::mvfst_fizz_client_handshake\n    Folly::folly_io_async_async_ssl_socket\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_session_hq_upstream_session\n    mvfst::mvfst_api_loop_detector_callback\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_client\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_fizz_client_handshake_psk_cache\n    mvfst::mvfst_logging_qlogger\n    fizz::fizz\n    Folly::folly_io_socket_option_map\n)\n\nproxygen_add_library(proxygen_http_window\n  SRCS\n    Window.cpp\n  DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_http_common\n  EXPORTED_DEPS\n    proxygen_http_session_session\n)\n\nproxygen_add_library(proxygen_error\n  SRCS\n    ProxygenErrorEnum.cpp\n)\n\nproxygen_add_library(proxygen_http_status_type\n  SRCS\n    StatusTypeEnum.cpp\n)\n\nproxygen_add_library(proxygen_http_http_header_size)\n\nproxygen_add_library(proxygen_http_http_utils\n  SRCS\n    HTTPConstants.cpp\n    HTTPException.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_error_code\n    proxygen_http_h3_errors\n    proxygen_http_message\n    proxygen_utils_exception\n    Folly::folly_memory\n)\n\nproxygen_add_library(proxygen_http_http_headers\n  SRCS\n    HTTPHeaders.cpp\n    HTTPMethod.cpp\n    RFC2616.cpp\n  DEPS\n    proxygen_http_http_common_headers\n    Folly::folly_indestructible\n    Folly::folly_thread_local\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_utils_export\n    proxygen_utils_util_inl\n    Folly::folly_fbvector\n    Folly::folly_optional\n    Folly::folly_range\n    Folly::folly_small_vector\n    Folly::folly_string\n    Folly::folly_try\n    Folly::folly_utility\n)\n\nproxygen_add_library(proxygen_http_types\n  EXPORTED_DEPS\n    Folly::folly_container_f14_hash\n)\n\nproxygen_add_library(proxygen_http_message\n  SRCS\n    HTTPMessage.cpp\n  DEPS\n    Folly::folly_format\n    Folly::folly_range\n    Folly::folly_singleton_thread_local\n    Folly::folly_string\n  EXPORTED_DEPS\n    proxygen_http_header_constants\n    proxygen_http_http_header_size\n    proxygen_http_http_headers\n    proxygen_http_types\n    proxygen_utils_parse_url\n    proxygen_utils_time_util\n    Folly::folly_conv\n    Folly::folly_io_iobuf\n    Folly::folly_network_address\n    Folly::folly_optional\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_priority_functions\n  SRCS\n    HTTPPriorityFunctions.cpp\n  DEPS\n    proxygen_http_structuredheaders_decoder\n  EXPORTED_DEPS\n    proxygen_http_message\n    Folly::folly_optional\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_h3_errors\n  SRCS\n    HTTP3ErrorCode.cpp\n  DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_synchronized_quic_lrucache\n  SRCS\n    SynchronizedLruQuicPskCache.cpp\n  EXPORTED_DEPS\n    mvfst::mvfst_fizz_client_handshake_psk_cache\n    Folly::folly_container_evicting_cache_map\n    Folly::folly_synchronized\n)\n\nproxygen_add_library(proxygen_http_proxy_status\n  SRCS\n    ProxyStatus.cpp\n  DEPS\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_http_status_type\n    proxygen_http_structuredheaders_encoder\n    Folly::folly_range\n)\n\nadd_subdirectory(codec)\nadd_subdirectory(connpool)\nadd_subdirectory(coro)\nadd_subdirectory(observer)\nadd_subdirectory(session)\nadd_subdirectory(sink)\nadd_subdirectory(structuredheaders)\nadd_subdirectory(webtransport)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/HQConnector.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HQConnector.h>\n\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/session/HQSession.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>\n#include <quic/congestion_control/CongestionControllerFactory.h>\n#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>\n\nusing namespace std;\nusing namespace fizz::client;\n\nnamespace proxygen {\n\nHQConnector::HQConnector(Callback* callback,\n                         std::chrono::milliseconds transactionTimeout,\n                         bool useConnectionEndWithErrorCallback)\n    : cb_(CHECK_NOTNULL(callback)),\n      transactionTimeout_(transactionTimeout),\n      useConnectionEndWithErrorCallback_(useConnectionEndWithErrorCallback) {\n  XLOG(DBG5) << \"HQConnector\";\n}\n\nHQConnector::~HQConnector() {\n  XLOG(DBG5) << \"~HQConnector\";\n  reset();\n}\n\nstd::chrono::microseconds HQConnector::timeElapsed() {\n  if (timePointInitialized(connectStart_)) {\n    return microsecondsSince(connectStart_);\n  }\n  return std::chrono::microseconds(0);\n}\n\nvoid HQConnector::reset() {\n  XLOG(DBG5) << \"reset\";\n  if (session_) {\n    // This destroys the session\n    session_->dropConnection();\n    session_ = nullptr;\n  }\n}\n\nvoid HQConnector::setTransportSettings(\n    quic::TransportSettings transportSettings) {\n  transportSettings_ = transportSettings;\n}\n\nvoid HQConnector::setQuicPskCache(\n    std::shared_ptr<quic::QuicPskCache> quicPskCache) {\n  quicPskCache_ = std::move(quicPskCache);\n}\n\nvoid HQConnector::connect(\n    folly::EventBase* eventBase,\n    folly::Optional<folly::SocketAddress> localAddr,\n    const folly::SocketAddress& connectAddr,\n    std::shared_ptr<const FizzClientContext> fizzContext,\n    std::shared_ptr<const fizz::CertificateVerifier> verifier,\n    std::chrono::milliseconds connectTimeout,\n    const folly::SocketOptionMap& socketOptions,\n    folly::Optional<std::string> sni,\n    std::shared_ptr<quic::QLogger> qLogger,\n    std::shared_ptr<quic::LoopDetectorCallback> quicLoopDetectorCallback,\n    std::shared_ptr<quic::QuicTransportStatsCallback>\n        quicTransportStatsCallback) {\n  XLOG(DBG5) << \"connect, timeout=\" << connectTimeout.count() << \"ms\";\n  DCHECK(!isBusy());\n  auto qEvb = std::make_shared<quic::FollyQuicEventBase>(eventBase);\n  auto sock = std::make_unique<quic::FollyQuicAsyncUDPSocket>(qEvb);\n  auto quicClient = quic::QuicClientTransport::newClient(\n      std::move(qEvb),\n      std::move(sock),\n      quic::FizzClientQuicHandshakeContext::Builder()\n          .setFizzClientContext(fizzContext)\n          .setCertificateVerifier(std::move(verifier))\n          .setPskCache(quicPskCache_)\n          .build(),\n      useConnectionEndWithErrorCallback_);\n  quicClient->setHostname(sni.value_or(connectAddr.getAddressStr()));\n  quicClient->addNewPeerAddress(connectAddr);\n  if (localAddr.hasValue()) {\n    quicClient->setLocalAddress(*localAddr);\n  }\n  quicClient->setCongestionControllerFactory(\n      std::make_shared<quic::DefaultCongestionControllerFactory>());\n  quicClient->setTransportStatsCallback(std::move(quicTransportStatsCallback));\n\n  // Always use connected UDP sockets\n  transportSettings_.connectUDP = true;\n  quicClient->setTransportSettings(transportSettings_);\n  if (!quicVersions_.empty()) {\n    quicClient->setSupportedVersions(quicVersions_);\n  }\n  quicClient->setQLogger(std::move(qLogger));\n  quicClient->setLoopDetectorCallback(std::move(quicLoopDetectorCallback));\n  quicClient->setSocketOptions(socketOptions);\n  session_ = new proxygen::HQUpstreamSession(transactionTimeout_,\n                                             connectTimeout,\n                                             nullptr, // controller\n                                             wangle::TransportInfo(),\n                                             nullptr); // InfoCallback\n\n  session_->setSocket(quicClient);\n  session_->setConnectCallback(this);\n  if (h3Settings_) {\n    session_->setEgressSettings(*h3Settings_);\n  }\n  session_->startNow();\n\n  VLOG(4) << \"connecting to \" << connectAddr.describe();\n  connectStart_ = getCurrentTime();\n  quicClient->start(session_, session_);\n}\n\nvoid HQConnector::onReplaySafe() noexcept {\n  CHECK(session_);\n  if (cb_) {\n    auto session = session_;\n    session_ = nullptr;\n    cb_->connectSuccess(session);\n  }\n}\n\nvoid HQConnector::connectError(quic::QuicError error) noexcept {\n  XLOG(DBG4) << \"connectError, error=\" << error.code;\n  CHECK(session_);\n  reset();\n  if (cb_) {\n    cb_->connectError(error.code);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HQConnector.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/client/AsyncFizzClient.h>\n#include <folly/io/SocketOptionMap.h>\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n#include <quic/api/LoopDetectorCallback.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/client/QuicClientTransport.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/fizz/client/handshake/QuicPskCache.h>\n#include <quic/logging/QLogger.h>\n\nnamespace proxygen {\n\nclass HQSession;\n\n/**\n * This class establishes new connections to HTTP servers over a QUIC transport.\n * It can be reused, even to connect to different addresses, but it can only\n * service setting up one connection at a time.\n */\nclass HQConnector : public HQSession::ConnectCallback {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void connectSuccess(HQUpstreamSession* session) = 0;\n    virtual void connectError(const quic::QuicErrorCode& code) = 0;\n  };\n\n  explicit HQConnector(Callback* callback,\n                       std::chrono::milliseconds transactionTimeout,\n                       bool useConnectionEndWithErrorCallback = false);\n  ~HQConnector() override;\n\n  void setTransportSettings(quic::TransportSettings transportSettings);\n  void setSupportedQuicVersions(\n      const std::vector<quic::QuicVersion>& versions) {\n    quicVersions_ = versions;\n  }\n  void setH3Settings(SettingsList settings) {\n    h3Settings_ = std::move(settings);\n  }\n\n  void setQuicPskCache(std::shared_ptr<quic::QuicPskCache> quicPskCache);\n\n  void reset();\n\n  void connect(\n      folly::EventBase* eventBase,\n      folly::Optional<folly::SocketAddress> localAddr,\n      const folly::SocketAddress& connectAddr,\n      std::shared_ptr<const fizz::client::FizzClientContext> fizzContext,\n      std::shared_ptr<const fizz::CertificateVerifier> verifier,\n      std::chrono::milliseconds connectTimeout = std::chrono::milliseconds(0),\n      const folly::SocketOptionMap& socketOptions = folly::emptySocketOptionMap,\n      folly::Optional<std::string> sni = folly::none,\n      std::shared_ptr<quic::QLogger> qLogger = nullptr,\n      std::shared_ptr<quic::LoopDetectorCallback> quicLoopDetectorCallback =\n          nullptr,\n      std::shared_ptr<quic::QuicTransportStatsCallback>\n          quicTransportStatsCallback = nullptr);\n\n  std::chrono::microseconds timeElapsed();\n\n  [[nodiscard]] bool isBusy() const {\n    return (session_ != nullptr);\n  }\n\n  // HQSession::ConnectCallback\n  void onReplaySafe() noexcept override;\n  void connectError(quic::QuicError error) noexcept override;\n\n private:\n  Callback* cb_;\n  std::chrono::milliseconds transactionTimeout_;\n  TimePoint connectStart_;\n  HQUpstreamSession* session_{nullptr};\n  quic::TransportSettings transportSettings_;\n  std::vector<quic::QuicVersion> quicVersions_;\n  folly::Optional<SettingsList> h3Settings_;\n  std::shared_ptr<quic::QuicPskCache> quicPskCache_;\n  bool useConnectionEndWithErrorCallback_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTP3ErrorCode.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <glog/logging.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n\nnamespace proxygen {\n\nstd::string toString(HTTP3::ErrorCode code) {\n  switch (code) {\n    case HTTP3::ErrorCode::HTTP_NO_ERROR:\n      return \"HTTP: No error\";\n    case HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR:\n      return \"HTTP: General protocol error\";\n    case HTTP3::ErrorCode::HTTP_INTERNAL_ERROR:\n      return \"HTTP: Internal error\";\n    case HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR:\n      return \"HTTP: Stream creation error\";\n    case HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM:\n      return \"HTTP: Critical stream was closed\";\n    case HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED:\n      return \"HTTP: Unexpected frame\";\n    case HTTP3::ErrorCode::HTTP_FRAME_ERROR:\n      return \"HTTP: Frame error\";\n    case HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD:\n      return \"HTTP: Peer generating excessive load\";\n    case HTTP3::ErrorCode::HTTP_ID_ERROR:\n      return \"HTTP: ID error\";\n    case HTTP3::ErrorCode::HTTP_SETTINGS_ERROR:\n      return \"HTTP: Settings error\";\n    case HTTP3::ErrorCode::HTTP_MISSING_SETTINGS:\n      return \"HTTP: No SETTINGS frame received\";\n    case HTTP3::ErrorCode::HTTP_REQUEST_REJECTED:\n      return \"HTTP: Server did not process request\";\n    case HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED:\n      return \"HTTP: Data no longer needed\";\n    case HTTP3::ErrorCode::HTTP_INCOMPLETE_REQUEST:\n      return \"HTTP: Stream terminated early\";\n    case HTTP3::ErrorCode::HTTP_MESSAGE_ERROR:\n      return \"HTTP: Malformed message\";\n    case HTTP3::ErrorCode::HTTP_CONNECT_ERROR:\n      return \"HTTP: Reset or error on CONNECT request\";\n    case HTTP3::ErrorCode::HTTP_VERSION_FALLBACK:\n      return \"HTTP: Retry over HTTP/1.1\";\n    case HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED:\n      return \"HTTP: QPACK decompression failed\";\n    case HTTP3::ErrorCode::HTTP_QPACK_ENCODER_STREAM_ERROR:\n      return \"HTTP: Error on QPACK encoder stream\";\n    case HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR:\n      return \"HTTP: Error on QPACK decoder stream\";\n    case HTTP3::ErrorCode::GIVEUP_ZERO_RTT:\n      return \"Give up Zero RTT\";\n  }\n  LOG(WARNING)\n      << \"toString has unhandled ErrorCode: \"\n      << static_cast<std::underlying_type<HTTP3::ErrorCode>::type>(code);\n  return \"Unknown error\";\n}\n\nstd::vector<HTTP3::ErrorCode> getAllHTTP3ErrorCodes() {\n  std::vector<HTTP3::ErrorCode> all = {\n      HTTP3::ErrorCode::HTTP_NO_ERROR,\n      HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR,\n      HTTP3::ErrorCode::HTTP_INTERNAL_ERROR,\n      HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR,\n      HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM,\n      HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED,\n      HTTP3::ErrorCode::HTTP_FRAME_ERROR,\n      HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD,\n      HTTP3::ErrorCode::HTTP_ID_ERROR,\n      HTTP3::ErrorCode::HTTP_SETTINGS_ERROR,\n      HTTP3::ErrorCode::HTTP_MISSING_SETTINGS,\n      HTTP3::ErrorCode::HTTP_REQUEST_REJECTED,\n      HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n      HTTP3::ErrorCode::HTTP_INCOMPLETE_REQUEST,\n      HTTP3::ErrorCode::HTTP_MESSAGE_ERROR,\n      HTTP3::ErrorCode::HTTP_CONNECT_ERROR,\n      HTTP3::ErrorCode::HTTP_VERSION_FALLBACK,\n      HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED,\n      HTTP3::ErrorCode::HTTP_QPACK_ENCODER_STREAM_ERROR,\n      HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR,\n      HTTP3::ErrorCode::GIVEUP_ZERO_RTT,\n  };\n  return all;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTP3ErrorCode.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n\nnamespace HTTP3 {\nenum ErrorCode : uint64_t {\n  // HTTP/3 error codes from draft\n  HTTP_NO_ERROR = 0x100,\n  HTTP_GENERAL_PROTOCOL_ERROR = 0x101,\n  HTTP_INTERNAL_ERROR = 0x102,\n  HTTP_STREAM_CREATION_ERROR = 0x103,\n  HTTP_CLOSED_CRITICAL_STREAM = 0x104,\n  HTTP_FRAME_UNEXPECTED = 0x105,\n  HTTP_FRAME_ERROR = 0x106,\n  HTTP_EXCESSIVE_LOAD = 0x107,\n  HTTP_ID_ERROR = 0x108,\n  HTTP_SETTINGS_ERROR = 0x109,\n  HTTP_MISSING_SETTINGS = 0x10A,\n  HTTP_REQUEST_REJECTED = 0x10B,\n  HTTP_REQUEST_CANCELLED = 0x10C,\n  HTTP_INCOMPLETE_REQUEST = 0x10D,\n  HTTP_MESSAGE_ERROR = 0x10E,\n  HTTP_CONNECT_ERROR = 0x10F,\n  HTTP_VERSION_FALLBACK = 0x110,\n  // QPACK 0x200, all from draft\n  HTTP_QPACK_DECOMPRESSION_FAILED = 0x200,\n  HTTP_QPACK_ENCODER_STREAM_ERROR = 0x201,\n  HTTP_QPACK_DECODER_STREAM_ERROR = 0x202,\n\n  // Internal use only\n  GIVEUP_ZERO_RTT = 0xF2\n};\n}\ninline bool isQPACKError(HTTP3::ErrorCode err) {\n  return err == HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED ||\n         err == HTTP3::ErrorCode::HTTP_QPACK_ENCODER_STREAM_ERROR ||\n         err == HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR;\n}\n\nstd::string toString(HTTP3::ErrorCode code);\nstd::vector<HTTP3::ErrorCode> getAllHTTP3ErrorCodes();\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPCommonHeaders.txt",
    "content": ":Authority\n:Method\n:Path\n:Scheme\n:Status\n:Protocol\nAccept\nAccept-Charset\nAccept-Encoding\nAccept-Language\nAccept-Ranges\nAccess-Control-Allow-Credentials\nAccess-Control-Allow-Headers\nAccess-Control-Allow-Methods\nAccess-Control-Allow-Origin\nAccess-Control-Expose-Headers\nAccess-Control-Max-Age\nAccess-Control-Request-Headers\nAccess-Control-Request-Method\nAge\nAlt-Svc\nAuthorization\nCache-Control\nclient_timeout\nConnection\ncontent-digest\nContent-Disposition\nContent-Encoding\nContent-Language\nContent-Length\nContent-Range\ncontent-security-policy\nContent-Type\nCookie\ncross-origin-embedder-policy-report-only\ncross-origin-opener-policy\ncross-origin-resource-policy\nDate\ndocument-policy\nEdge-Control\nETag\nExpect\nExpires\nfacebook-api-version\nHost\nIf-Modified-Since\nIf-None-Match\nKeep-Alive\nLast-Modified\nLink\nLocation\nOrigin\norigin-trial\npermissions-policy\nPragma\nPriority\nProxy-Authenticate\nProxy-Authorization\nProxy-Connection\nProxy-Status\nqueue_timeout\nRange\nReferer\nreport-to\nRetry-After\nrpckind\nSec-WebSocket-Key\nSec-WebSocket-Accept\nServer\nSet-Cookie\nStrict-Transport-Security\nTE\nthrift_priority\ntiming-allow-origin\nTrailer\nTransfer-Encoding\nUpgrade\nUser-Agent\nVary\nVia\nWWW-Authenticate\nX-Content-Type-Options\nX-Forwarded-For\nX-Forwarded-Proto\nX-Frame-Options\nX-Thrift-Protocol\nX-XSS-Protection\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConnector.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <wangle/ssl/SSLUtil.h>\n\nusing namespace folly;\nusing namespace std;\n\nnamespace proxygen {\n\nHTTPConnector::HTTPConnector(Callback* callback,\n                             folly::HHWheelTimer* timeoutSet)\n    : HTTPConnector(callback, WheelTimerInstance(timeoutSet)) {\n}\n\nHTTPConnector::HTTPConnector(Callback* callback,\n                             const WheelTimerInstance& timeout)\n    : cb_(CHECK_NOTNULL(callback)),\n      timeout_(timeout),\n      httpCodecFactory_(std::make_unique<DefaultHTTPCodecFactory>()) {\n  XLOG(DBG4) << \"HTTPConnector\";\n}\n\nHTTPConnector::~HTTPConnector() {\n  XLOG(DBG4) << \"~HTTPConnector\";\n  reset(false);\n}\n\nvoid HTTPConnector::reset(bool invokeCallbacks) {\n  XLOG(DBG4) << \"reset invokeCallbacks=\" << invokeCallbacks;\n  if (socket_) {\n    auto cb = cb_;\n    if (!invokeCallbacks) {\n      cb_ = nullptr;\n    }\n    XLOG(DBG4) << \"socket_.reset()\";\n    socket_.reset(); // This invokes connectError() but will be ignored\n    cb_ = cb;\n  }\n}\n\nvoid HTTPConnector::setPlaintextProtocol(const std::string& plaintextProto) {\n  plaintextProtocol_ = plaintextProto;\n}\n\nvoid HTTPConnector::setHTTPVersionOverride(bool enabled) {\n  httpCodecFactory_->getDefaultConfig().h1.forceHTTP1xCodecTo1_1 = enabled;\n}\n\nvoid HTTPConnector::connect(EventBase* eventBase,\n                            const folly::SocketAddress& connectAddr,\n                            std::chrono::milliseconds timeoutMs,\n                            const SocketOptionMap& socketOptions,\n                            const folly::SocketAddress& bindAddr) {\n\n  XLOG(DBG4) << \"connect\";\n  DCHECK(!isBusy());\n  transportInfo_ = wangle::TransportInfo();\n  transportInfo_.secure = false;\n  auto sock = new AsyncSocket(eventBase);\n  socket_.reset(sock);\n  connectStart_ = getCurrentTime();\n  cb_->preConnect(sock);\n  sock->connect(this, connectAddr, timeoutMs.count(), socketOptions, bindAddr);\n}\n\nvoid HTTPConnector::connectSSL(EventBase* eventBase,\n                               const folly::SocketAddress& connectAddr,\n                               const shared_ptr<const SSLContext>& context,\n                               std::shared_ptr<folly::ssl::SSLSession> session,\n                               std::chrono::milliseconds timeoutMs,\n                               const SocketOptionMap& socketOptions,\n                               const folly::SocketAddress& bindAddr,\n                               const std::string& serverName) {\n\n  XLOG(DBG4) << \"connectSSL\";\n  DCHECK(!isBusy());\n  transportInfo_ = wangle::TransportInfo();\n  transportInfo_.secure = true;\n  auto sslSock = new AsyncSSLSocket(context, eventBase);\n  if (session) {\n    sslSock->setSSLSession(session);\n  }\n  sslSock->setServerName(serverName);\n  sslSock->forceCacheAddrOnFailure(true);\n  socket_.reset(sslSock);\n  connectStart_ = getCurrentTime();\n  cb_->preConnect(sslSock);\n  sslSock->connect(\n      this, connectAddr, timeoutMs.count(), socketOptions, bindAddr);\n}\n\nstd::chrono::microseconds HTTPConnector::timeElapsed() {\n  if (timePointInitialized(connectStart_)) {\n    return microsecondsSince(connectStart_);\n  }\n  return std::chrono::microseconds(0);\n}\n\n// Callback interface\n\nvoid HTTPConnector::connectSuccess() noexcept {\n  if (!cb_) {\n    return;\n  }\n\n  folly::SocketAddress localAddress;\n  folly::SocketAddress peerAddress;\n  socket_->getLocalAddress(&localAddress);\n  socket_->getPeerAddress(&peerAddress);\n\n  std::unique_ptr<HTTPCodec> codec;\n  std::string protoCopy;\n  std::string* proto{&protoCopy};\n  transportInfo_.acceptTime = getCurrentTime();\n  if (transportInfo_.secure) {\n    auto* sslSocket = socket_->getUnderlyingTransport<AsyncSSLSocket>();\n\n    if (sslSocket) {\n      transportInfo_.appProtocol =\n          std::make_shared<std::string>(socket_->getApplicationProtocol());\n      transportInfo_.sslSetupTime = millisecondsSince(connectStart_);\n      transportInfo_.sslCipher = sslSocket->getNegotiatedCipherName()\n                                     ? std::make_shared<std::string>(\n                                           sslSocket->getNegotiatedCipherName())\n                                     : nullptr;\n      transportInfo_.sslVersion = sslSocket->getSSLVersion();\n      transportInfo_.sslResume = wangle::SSLUtil::getResumeState(sslSocket);\n    }\n\n    protoCopy = socket_->getApplicationProtocol();\n  } else {\n    proto = &plaintextProtocol_;\n  }\n\n  CHECK(proto);\n  codec = httpCodecFactory_->getCodec(\n      *proto, TransportDirection::UPSTREAM, transportInfo_.secure);\n\n  if (!codec) {\n    AsyncSocketException ex(\n        AsyncSocketException::INTERNAL_ERROR,\n        folly::to<string>(\"HTTPCodecFactory failed to create codec for proto=\",\n                          *proto));\n    connectErr(ex);\n    return;\n  }\n\n  auto* session = new HTTPUpstreamSession(timeout_,\n                                          std::move(socket_),\n                                          localAddress,\n                                          peerAddress,\n                                          std::move(codec),\n                                          transportInfo_,\n                                          nullptr);\n\n  XLOG(DBG5) << \" connectSuccess, HTTPUpstreamSession \" << session;\n  cb_->connectSuccess(session);\n}\n\nvoid HTTPConnector::connectErr(const AsyncSocketException& ex) noexcept {\n  XLOG(DBG3) << \" connectErr \" << ex.what();\n  socket_.reset();\n  if (cb_) {\n    cb_->connectError(ex);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConnector.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <folly/io/async/SSLContext.h>\n#include <folly/ssl/SSLSession.h>\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/utils/Time.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\n\nclass HTTPUpstreamSession;\nextern const std::string empty_string;\n\n/**\n * This class establishes new connections to HTTP or HTTPS servers. It\n * can be reused, even to connect to different addresses, but it can only\n * service setting up one connection at a time.\n */\nclass HTTPConnector : protected folly::AsyncSocket::ConnectCallback {\n public:\n  /**\n   * This class defines the pure virtual interface on which to receive the\n   * result on.\n   */\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void connectSuccess(HTTPUpstreamSession* session) = 0;\n    virtual void connectError(const folly::AsyncSocketException& ex) = 0;\n    // Called when the transport is about to be connected - similar to\n    // AsyncSocket::ConnectCallback::preConnect but provides access to the\n    // AsyncTransport API instead of the fd\n    virtual void preConnect(folly::AsyncTransport*) {\n    }\n  };\n\n  /**\n   * Construct a HTTPConnector. The constructor arguments are those\n   * parameters HTTPConnector needs to keep a copy of through the\n   * connection process.\n   *\n   * @param callback The interface on which to receive the result.\n   *                 Whatever object is passed here MUST outlive this\n   *                 connector and MUST NOT be null.\n   * @param timeoutSet The timeout set to be used for the transactions\n   *                   that are opened on the session.\n   */\n  HTTPConnector(Callback* callback, folly::HHWheelTimer* timeoutSet);\n\n  HTTPConnector(Callback* callback, const WheelTimerInstance& timeout);\n\n  /**\n   * Clients may delete the connector at any time to cancel it. No\n   * callbacks will be received.\n   */\n  ~HTTPConnector() override;\n\n  /**\n   * Reset the object so that it can begin a new connection. Callbacks\n   * are suppressed by default, but can be enabled, when the intent is to cancel\n   * the outstanding work. After this function returns, isBusy() will return\n   * false.\n   */\n  void reset(bool invokeCallbacks = false);\n\n  /**\n   * Sets the plain text protocol to use after the connection\n   * is established.\n   */\n  void setPlaintextProtocol(const std::string& plaintextProto);\n\n  /**\n   * Overrides the HTTP version to always use the latest and greatest\n   * version we support.\n   */\n  void setHTTPVersionOverride(bool enabled);\n\n  /**\n   * Begin the process of getting a plaintext connection to the server\n   * specified by 'connectAddr'. This function immediately starts async\n   * work and may invoke functions on Callback immediately.\n   *\n   * @param eventBase The event base to put events on.\n   * @param connectAddr The address to connect to.\n   * @param timeoutMs Optional. If this value is greater than zero, then a\n   *                  connect error will be given if no connection is\n   *                  established within this amount of time.\n   * @param socketOptions Optional socket options to set on the connection.\n   * @param bindAddr Optional address to bind to locally.\n   */\n  void connect(\n      folly::EventBase* eventBase,\n      const folly::SocketAddress& connectAddr,\n      std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(0),\n      const folly::SocketOptionMap& socketOptions = folly::emptySocketOptionMap,\n      const folly::SocketAddress& bindAddr = folly::AsyncSocket::anyAddress());\n\n  /**\n   * Begin the process of getting a secure connection to the server\n   * specified by 'connectAddr'. This function immediately starts async\n   * work and may invoke functions on Callback immediately.\n   *\n   * @param eventBase The event base to put events on.\n   * @param connectAddr The address to connect to.\n   * @param ctx SSL context to use. Must not be null.\n   * @param session Optional ssl session to use.\n   * @param timeoutMs Optional. If this value is greater than zero, then a\n   *                  connect error will be given if no connection is\n   *                  established within this amount of time.\n   * @param socketOptions Optional socket options to set on the connection.\n   * @param bindAddr Optional address to bind to locally.\n   */\n  void connectSSL(\n      folly::EventBase* eventBase,\n      const folly::SocketAddress& connectAddr,\n      const std::shared_ptr<const folly::SSLContext>& ctx,\n      std::shared_ptr<folly::ssl::SSLSession> session = nullptr,\n      std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(0),\n      const folly::SocketOptionMap& socketOptions = folly::emptySocketOptionMap,\n      const folly::SocketAddress& bindAddr = folly::AsyncSocket::anyAddress(),\n      const std::string& serverName = empty_string);\n\n  /**\n   * @returns the number of milliseconds since connecting began, or\n   * zero if connecting hasn't started yet.\n   */\n  std::chrono::microseconds timeElapsed();\n\n  /**\n   * @returns true iff this connector is busy setting up a connection. If\n   * this is false, it is safe to call connect() or connectSSL() on it again.\n   */\n  [[nodiscard]] bool isBusy() const {\n    return socket_.get();\n  }\n\n  void setHTTPCodecFactory(std::unique_ptr<DefaultHTTPCodecFactory> factory) {\n    httpCodecFactory_ = std::move(factory);\n  }\n\n protected:\n  void connectSuccess() noexcept override;\n  void connectErr(const folly::AsyncSocketException& ex) noexcept override;\n\n  Callback* cb_;\n  WheelTimerInstance timeout_;\n  folly::AsyncTransport::UniquePtr socket_;\n  wangle::TransportInfo transportInfo_;\n  std::string plaintextProtocol_;\n  TimePoint connectStart_;\n  std::unique_ptr<DefaultHTTPCodecFactory> httpCodecFactory_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConnectorWithFizz.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPConnectorWithFizz.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\n#include <folly/io/SocketOptionMap.h>\n\nusing namespace fizz::client;\n\nnamespace proxygen {\n\nvoid HTTPConnectorWithFizz::connectFizz(\n    folly::EventBase* eventBase,\n    const folly::SocketAddress& connectAddr,\n    std::shared_ptr<const FizzClientContext> context,\n    std::shared_ptr<const fizz::CertificateVerifier> verifier,\n    std::chrono::milliseconds totalTimeout,\n    std::chrono::milliseconds tcpConnectTimeout,\n    const folly::SocketOptionMap& socketOptions,\n    const folly::SocketAddress& bindAddr,\n    folly::Optional<std::string> sni,\n    folly::Optional<std::string> pskIdentity) {\n  DCHECK(!isBusy());\n  transportInfo_ = wangle::TransportInfo();\n  transportInfo_.secure = true;\n\n  auto fizzClient = new AsyncFizzClient(eventBase, context);\n  socket_.reset(fizzClient);\n\n  connectStart_ = getCurrentTime();\n  cb_->preConnect(fizzClient);\n  fizzClient->connect(connectAddr,\n                      this,\n                      std::move(verifier),\n                      std::move(sni),\n                      std::move(pskIdentity),\n                      std::move(totalTimeout),\n                      std::move(tcpConnectTimeout),\n                      socketOptions,\n                      bindAddr);\n}\n\nvoid HTTPConnectorWithFizz::connectSuccess() noexcept {\n  if (!cb_) {\n    return;\n  }\n\n  auto transport = socket_->getUnderlyingTransport<AsyncFizzClient>();\n\n  if (!transport) {\n    // Not a fizz socket, fall back to the parent one.\n    HTTPConnector::connectSuccess();\n    return;\n  }\n\n  folly::SocketAddress localAddress, peerAddress;\n  socket_->getLocalAddress(&localAddress);\n  socket_->getPeerAddress(&peerAddress);\n\n  transportInfo_.acceptTime = getCurrentTime();\n  transportInfo_.appProtocol =\n      std::make_shared<std::string>(transport->getApplicationProtocol());\n  transportInfo_.sslSetupTime = millisecondsSince(connectStart_);\n  auto negotiatedCipher = transport->getState().cipher();\n  transportInfo_.sslCipher =\n      negotiatedCipher\n          ? std::make_shared<std::string>(fizz::toString(*negotiatedCipher))\n          : nullptr;\n  auto version = transport->getState().version();\n  transportInfo_.sslVersion = version ? static_cast<int>(version.value()) : 0;\n  auto pskType =\n      transport->getState().pskType().value_or(fizz::PskType::NotAttempted);\n  transportInfo_.sslResume = pskType == fizz::PskType::Resumption\n                                 ? wangle::SSLResumeEnum::RESUME_TICKET\n                                 : wangle::SSLResumeEnum::HANDSHAKE;\n  transportInfo_.securityType = transport->getSecurityProtocol();\n  std::unique_ptr<HTTPCodec> codec = httpCodecFactory_->getCodec(\n      socket_->getApplicationProtocol(), TransportDirection::UPSTREAM, true);\n  auto* session = new HTTPUpstreamSession(timeout_,\n                                          std::move(socket_),\n                                          localAddress,\n                                          peerAddress,\n                                          std::move(codec),\n                                          transportInfo_,\n                                          nullptr);\n\n  cb_->connectSuccess(session);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConnectorWithFizz.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/client/AsyncFizzClient.h>\n#include <folly/io/SocketOptionMap.h>\n#include <proxygen/lib/http/HTTPConnector.h>\n\n/**\n * Extension of the HTTPConnector that uses Fizz to\n * support TLS 1.3 connections.\n **/\n\nnamespace proxygen {\n\nclass HTTPConnectorWithFizz : public HTTPConnector {\n public:\n  using HTTPConnector::HTTPConnector;\n\n  void connectFizz(\n      folly::EventBase* eventBase,\n      const folly::SocketAddress& connectAddr,\n      std::shared_ptr<const fizz::client::FizzClientContext> context,\n      std::shared_ptr<const fizz::CertificateVerifier> verifier,\n      std::chrono::milliseconds totalTimeout = std::chrono::milliseconds(0),\n      std::chrono::milliseconds tcpConnectTimeout =\n          std::chrono::milliseconds(0),\n      const folly::SocketOptionMap& socketOptions = folly::emptySocketOptionMap,\n      const folly::SocketAddress& bindAddr = folly::AsyncSocket::anyAddress(),\n      folly::Optional<std::string> sni = folly::none,\n      folly::Optional<std::string> pskIdentity = folly::none);\n\n protected:\n  void connectSuccess() noexcept override;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConstants.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPConstants.h>\n\nnamespace proxygen {\n\n#define CONNECTION_CLOSE_REASON_STRING(e, r) r,\nconst char* connectionCloseStrings[] = {\n    CONNECTION_CLOSE_REASON_GEN(CONNECTION_CLOSE_REASON_STRING)};\n#undef CONNECTION_CLOSE_REASON_STRING\n\nconst char* getConnectionCloseReasonStringByIndex(unsigned int index) {\n  if (index >= (unsigned int)ConnectionCloseReason::kMAX_REASON) {\n    index = (unsigned int)ConnectionCloseReason::kMAX_REASON - 1;\n  }\n\n  return connectionCloseStrings[index];\n}\n\nconst char* getConnectionCloseReasonString(ConnectionCloseReason r) {\n  return connectionCloseStrings[(unsigned int)r];\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPConstants.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\nnamespace proxygen {\n\n// enum class ConnectionCloseReason : unsigned int {\n//  SHUTDOWN,               // shutdown...probably due to the short shutdown\n//                          //  time we won't be able to see any of this\n//  READ_EOF,               // received FIN from client so the connection is\n//                          //  not reusable any more\n//  GOAWAY,                 // session closed due to ingress goaway\n//  SESSION_PARSE_ERROR,    // http/spdy parse error\n//  REMOTE_ERROR,           // The various 5xx error\n//  TRANSACTION_ABORT,      // transaction sendAbort()\n//  TIMEOUT,                // read/write timeout excluding shutdown\n//  IO_READ_ERROR,          // read error\n//  IO_WRITE_ERROR,         // write error\n//  REQ_NOTREUSABLE,        // client request is not reusable (http 1.0 or\n//                          //  Connection: close)\n//  ERR_RESP,               // various 4xx error\n//  UNKNOWN,                // this probably indicate some bug that the close\n//                          //  reason is not accounted for\n//  kMAX_REASON\n//};\n// clang-format off\n#define CONNECTION_CLOSE_REASON_GEN(x) \\\n  x(SHUTDOWN, \"shutdown\") \\\n  x(READ_EOF, \"read_eof\") \\\n  x(GOAWAY, \"goaway\") \\\n  x(SESSION_PARSE_ERROR, \"session_parse_err\") \\\n  x(REMOTE_ERROR, \"remote_err\") \\\n  x(TRANSACTION_ABORT, \"transaction_abort\") \\\n  x(TIMEOUT, \"timeout\") \\\n  x(IO_READ_ERROR, \"io_read_err\") \\\n  x(IO_WRITE_ERROR, \"io_write_err\") \\\n  x(REQ_NOTREUSABLE, \"req_not_reusable\") \\\n  x(ERR_RESP, \"err_resp\") \\\n  x(UNKNOWN, \"unknown\") \\\n  x(FLOW_CONTROL, \"flow_control\") \\\n  x(kMAX_REASON, \"unset\")\n// clang-format on\n\n#define CONNECTION_CLOSE_REASON_ENUM(e, r) e,\nenum class ConnectionCloseReason {\n  CONNECTION_CLOSE_REASON_GEN(CONNECTION_CLOSE_REASON_ENUM)\n};\n#undef CONNECTION_CLOSE_REASON_ENUM\n\nextern const char* getConnectionCloseReasonStringByIndex(unsigned int i);\nextern const char* getConnectionCloseReasonString(ConnectionCloseReason r);\n\n/**\n * Protocol to which the HTTPTransaction was upgraded\n */\nenum class UpgradeProtocol : int {\n  // We only support changing to TCP after CONNECT requests\n  TCP\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPException.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <sstream>\n#include <string>\n\n#include <proxygen/lib/http/HTTPException.h>\n\nnamespace proxygen {\n\nHTTP3::ErrorCode toHTTP3ErrorCode(proxygen::ErrorCode err) {\n  switch (err) {\n    case ErrorCode::NO_ERROR:\n      return HTTP3::ErrorCode::HTTP_NO_ERROR;\n    case ErrorCode::PROTOCOL_ERROR:\n      return HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR;\n    case ErrorCode::INTERNAL_ERROR:\n      return HTTP3::ErrorCode::HTTP_INTERNAL_ERROR;\n    case ErrorCode::FLOW_CONTROL_ERROR:\n      DCHECK(false) << \"ErrorCode::FLOW_CONTROL_ERROR for QUIC\";\n#ifdef NDEBUG\n      [[fallthrough]];\n#endif\n    case ErrorCode::SETTINGS_TIMEOUT: // maybe we should keep this?\n    case ErrorCode::STREAM_CLOSED:\n      return HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR;\n    case ErrorCode::FRAME_SIZE_ERROR:\n      return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n    case ErrorCode::REFUSED_STREAM:\n      return HTTP3::ErrorCode::HTTP_REQUEST_REJECTED;\n    case ErrorCode::CANCEL:\n      return HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED;\n    case ErrorCode::COMPRESSION_ERROR:\n      return HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED;\n    case ErrorCode::CONNECT_ERROR:\n      return HTTP3::ErrorCode::HTTP_CONNECT_ERROR;\n    case ErrorCode::ENHANCE_YOUR_CALM:\n      return HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD;\n    case ErrorCode::INADEQUATE_SECURITY:\n    case ErrorCode::HTTP_1_1_REQUIRED:\n    default:\n      return HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR;\n  }\n}\n\nHTTPException::HTTPException(Direction dir, const std::string& msg)\n    : Exception(msg), dir_(dir) {\n}\n\nHTTPException::HTTPException(Direction dir, const char* msg)\n    : Exception(msg), dir_(dir) {\n}\n\nHTTPException::HTTPException(const HTTPException& ex)\n    : Exception(static_cast<const Exception&>(ex)),\n      dir_(ex.dir_),\n      httpStatusCode_(ex.httpStatusCode_),\n      http3ErrorCode_(ex.http3ErrorCode_),\n      codecStatusCode_(ex.codecStatusCode_),\n      errno_(ex.errno_) {\n  if (ex.partialMsg_) {\n    partialMsg_ = std::make_unique<HTTPMessage>(*ex.partialMsg_.get());\n  }\n}\n\nHTTPException::~HTTPException() = default;\n\nHTTP3::ErrorCode HTTPException::inferHTTP3ErrorCode() const {\n  if (hasHttpStatusCode()) {\n    return HTTP3::ErrorCode::HTTP_NO_ERROR; // does this sound right?\n  } else if (hasCodecStatusCode()) {\n    return toHTTP3ErrorCode(getCodecStatusCode());\n  }\n  return HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR;\n}\n\nHTTP3::ErrorCode HTTPException::getHttp3ErrorCode() const {\n  if (hasHttp3ErrorCode()) {\n    return *http3ErrorCode_;\n  }\n  return inferHTTP3ErrorCode();\n}\n\nstd::string HTTPException::describe() const {\n  std::stringstream ss;\n  ss << *this;\n  return ss.str();\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPException& ex) {\n  os << \"what=\\\"\" << ex.what()\n     << \"\\\", direction=\" << static_cast<int>(ex.getDirection())\n     << \", proxygenError=\" << getErrorString(ex.getProxygenError())\n     << \", codecStatusCode=\"\n     << (ex.hasCodecStatusCode() ? getErrorCodeString(ex.getCodecStatusCode())\n                                 : \"-1\")\n     << \", httpStatusCode=\" << ex.getHttpStatusCode();\n  if (ex.hasHttp3ErrorCode()) {\n    os << \", http3ErrorCode=\" << toString(ex.getHttp3ErrorCode());\n  }\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPException.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <proxygen/lib/utils/Exception.h>\n\nnamespace proxygen {\n\nHTTP3::ErrorCode toHTTP3ErrorCode(proxygen::ErrorCode err);\n\n/**\n * This class encapsulates the various errors that can occur on an\n * http session. Errors can occur at various levels: the connection can\n * be closed for reads and/or writes, the message body may fail to parse,\n * or various protocol constraints may be violated.\n */\nclass HTTPException : public proxygen::Exception {\n public:\n  ~HTTPException() override;\n\n  /**\n   * Indicates which direction of the data flow was affected by this\n   * exception. For instance, if a class receives HTTPException(INGRESS),\n   * then it should consider ingress callbacks finished (whether or not\n   * the underlying transport actually shut down). Likewise for\n   * HTTPException(EGRESS), the class should consider the write stream\n   * shut down. INGRESS_AND_EGRESS indicates both directions are finished.\n   */\n  enum class Direction {\n    INGRESS = 0,\n    EGRESS,\n    INGRESS_AND_EGRESS,\n  };\n\n  HTTPException(Direction dir, const std::string& msg);\n\n  HTTPException(Direction dir, const char* msg);\n\n  HTTPException(const HTTPException& ex);\n\n  /**\n   * Returns a string representation of this exception. This function is\n   * intended for debugging and logging only. For the true exception\n   * string, use what()\n   */\n  [[nodiscard]] std::string describe() const;\n\n  [[nodiscard]] Direction getDirection() const {\n    return dir_;\n  }\n\n  [[nodiscard]] bool isIngressException() const {\n    return dir_ == Direction::INGRESS || dir_ == Direction::INGRESS_AND_EGRESS;\n  }\n\n  [[nodiscard]] bool isEgressException() const {\n    return dir_ == Direction::EGRESS || dir_ == Direction::INGRESS_AND_EGRESS;\n  }\n\n  // Accessors for HTTP error codes\n  [[nodiscard]] bool hasHttpStatusCode() const {\n    return (httpStatusCode_ != 0);\n  }\n\n  void setHttpStatusCode(uint32_t statusCode) {\n    httpStatusCode_ = statusCode;\n  }\n\n  [[nodiscard]] uint32_t getHttpStatusCode() const {\n    return httpStatusCode_;\n  }\n\n  // Accessors for HTTP3 error codes\n  [[nodiscard]] bool hasHttp3ErrorCode() const {\n    return http3ErrorCode_.has_value();\n  }\n  void setHttp3ErrorCode(HTTP3::ErrorCode errorCode) {\n    http3ErrorCode_ = errorCode;\n  }\n  [[nodiscard]] HTTP3::ErrorCode getHttp3ErrorCode() const;\n\n  // Accessors for Codec specific status codes\n  [[nodiscard]] bool hasCodecStatusCode() const {\n    return codecStatusCode_.has_value();\n  }\n  void setCodecStatusCode(ErrorCode statusCode) {\n    codecStatusCode_ = statusCode;\n  }\n  [[nodiscard]] ErrorCode getCodecStatusCode() const {\n    CHECK(hasCodecStatusCode());\n    return *codecStatusCode_;\n  }\n\n  // Accessors for errno\n  [[nodiscard]] bool hasErrno() const {\n    return (errno_ != 0);\n  }\n  void setErrno(uint32_t e) {\n    errno_ = e;\n  }\n  [[nodiscard]] uint32_t getErrno() const {\n    return errno_;\n  }\n\n  void setPartialMsg(std::unique_ptr<HTTPMessage> partialMsg) {\n    partialMsg_ = std::move(partialMsg);\n  }\n\n  [[nodiscard]] HTTPMessage* getPartialMsg() const {\n    return partialMsg_.get();\n  }\n\n private:\n  [[nodiscard]] HTTP3::ErrorCode inferHTTP3ErrorCode() const;\n\n  Direction dir_;\n  uint32_t httpStatusCode_{0};\n  folly::Optional<HTTP3::ErrorCode> http3ErrorCode_;\n  folly::Optional<ErrorCode> codecStatusCode_;\n  uint32_t errno_{0};\n  // partial message that is being parsed\n  std::unique_ptr<HTTPMessage> partialMsg_;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPException& ex);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPHeaderSize.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <stdint.h>\n\nnamespace proxygen {\n\n/**\n * A structure that encapsulates byte counters related to the HTTP headers.\n */\nstruct HTTPHeaderSize {\n  /**\n   * The number of bytes used to represent the header after compression or\n   * before decompression. If header compression is not supported, the value\n   * is set to 0.\n   */\n  uint32_t compressed{0};\n\n  /**\n   * The number of bytes used to represent the serialized header before\n   * compression or after decompression, in plain-text format.\n   */\n  uint32_t uncompressed{0};\n\n  /**\n   * The number of bytes encoded as a compressed header block.\n   * Header compression algorithms generate a header block plus some control\n   * information. The `compressed` field accounts for both. So the control\n   * information size can be computed as `compressed` - `compressedBlock`\n   */\n  uint32_t compressedBlock{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPHeaders.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPHeaders.h>\n\n#include <glog/logging.h>\n\nusing std::bitset;\nusing std::string;\n\nnamespace {\n\n/**\n * The initial capacity of the three vectors, reserved right after\n * construction.\n */\nstatic constexpr size_t kInitialVectorReserve = 16;\nstatic constexpr size_t kRecSize =\n    (sizeof(char) + sizeof(std::string*) + sizeof(std::string));\n\n// iterate over the positions (in vector) of all headers with given code\n#define ITERATE_OVER_CODES(Code, Block)                   \\\n  {                                                       \\\n    const HTTPHeaderCode* ptr = codes();                  \\\n    while (ptr) {                                         \\\n      ptr = (HTTPHeaderCode*)memchr(                      \\\n          (void*)ptr, (Code), length_ - (ptr - codes())); \\\n      if (ptr == nullptr)                                 \\\n        break;                                            \\\n      const size_t pos = ptr - codes();                   \\\n      {Block} ptr++;                                      \\\n    }                                                     \\\n  }                                                       \\\n  static_assert(true, \"semicolon required\")\n\n// iterate over the positions of all headers with given name\n#define ITERATE_OVER_STRINGS(String, Block)               \\\n  ITERATE_OVER_CODES(HTTPHeaderCode::HTTP_HEADER_OTHER, { \\\n    if (caseInsensitiveEqual((String), *names()[pos])) {  \\\n      {                                                   \\\n        Block                                             \\\n      }                                                   \\\n    }                                                     \\\n  })\n\n// iterate over the positions of all headers with given name ignoring - and _\n#define ITERATE_OVER_STRINGS_ALL_VERSION(String, Block)            \\\n  ITERATE_OVER_CODES(HTTP_HEADER_OTHER, {                          \\\n    if (caseUnderscoreInsensitiveEqual((String), *names()[pos])) { \\\n      {                                                            \\\n        Block                                                      \\\n      }                                                            \\\n    }                                                              \\\n  })\n\n} // namespace\n\nnamespace proxygen {\n\nconst string empty_string;\n\nbitset<256>& HTTPHeaders::perHopHeaderCodes() {\n  static bitset<256> perHopHeaderCodes{[] {\n    bitset<256> bs;\n    bs[HTTP_HEADER_CONNECTION] = true;\n    bs[HTTP_HEADER_KEEP_ALIVE] = true;\n    bs[HTTP_HEADER_PROXY_AUTHENTICATE] = true;\n    bs[HTTP_HEADER_PROXY_AUTHORIZATION] = true;\n    bs[HTTP_HEADER_PROXY_CONNECTION] = true;\n    bs[HTTP_HEADER_TE] = true;\n    bs[HTTP_HEADER_TRAILER] = true;\n    bs[HTTP_HEADER_TRANSFER_ENCODING] = true;\n    bs[HTTP_HEADER_UPGRADE] = true;\n    return bs;\n  }()};\n  return perHopHeaderCodes;\n}\n\nHTTPHeaders::HTTPHeaders() : deletedCount_(0) {\n  resize(kInitialVectorReserve);\n}\n\nvoid HTTPHeaders::add(std::string_view name, std::string&& value) {\n  assert(name.size());\n  const HTTPHeaderCode code = HTTPCommonHeaders::hash(name.data(), name.size());\n  auto namePtr =\n      ((code == HTTPHeaderCode::HTTP_HEADER_OTHER)\n           ? new std::string(name.data(), name.size())\n           : (std::string*)HTTPCommonHeaders::getPointerToName(code));\n  emplace_back(code, namePtr, std::move(value));\n}\n\nvoid HTTPHeaders::add(HTTPHeaderCode code, std::string&& value) {\n  auto namePtr = (std::string*)HTTPCommonHeaders::getPointerToName(code);\n  emplace_back(code, namePtr, std::move(value));\n}\n\nvoid HTTPHeaders::add(HTTPHeaders::headers_initializer_list l) {\n  for (auto& p : l) {\n    p.first.type_ == HTTPHeaderName::CODE ? add(p.first.code_, p.second)\n                                          : add(p.first.name_, p.second);\n  }\n}\n\nbool HTTPHeaders::exists(std::string_view name) const {\n  const HTTPHeaderCode code = HTTPCommonHeaders::hash(name.data(), name.size());\n  if (code != HTTP_HEADER_OTHER) {\n    return exists(code);\n  }\n  ITERATE_OVER_STRINGS(name, { return true; });\n  return false;\n}\n\nbool HTTPHeaders::exists(HTTPHeaderCode code) const {\n  return length_ > 0 && memchr((void*)codes(), code, length_) != nullptr;\n}\n\nsize_t HTTPHeaders::getNumberOfValues(HTTPHeaderCode code) const {\n  size_t count = 0;\n  ITERATE_OVER_CODES(code, {\n    (void)pos;\n    ++count;\n  });\n  return count;\n}\n\nsize_t HTTPHeaders::getNumberOfValues(std::string_view name) const {\n  size_t count = 0;\n  forEachValueOfHeader(name, [&](std::string_view) -> bool {\n    ++count;\n    return false;\n  });\n  return count;\n}\n\nbool HTTPHeaders::remove(std::string_view name) {\n  const HTTPHeaderCode code = HTTPCommonHeaders::hash(name.data(), name.size());\n  if (code != HTTP_HEADER_OTHER) {\n    return remove(code);\n  }\n  bool removed = false;\n  ITERATE_OVER_STRINGS(name, {\n    delete names()[pos];\n    codes()[pos] = HTTP_HEADER_NONE;\n    removed = true;\n    ++deletedCount_;\n  });\n  return removed;\n}\n\nbool HTTPHeaders::remove(HTTPHeaderCode code) {\n  bool removed = false;\n  ITERATE_OVER_CODES(code, {\n    codes()[pos] = HTTP_HEADER_NONE;\n    removed = true;\n    ++deletedCount_;\n  });\n  return removed;\n}\n\nbool HTTPHeaders::removeAllVersions(HTTPHeaderCode code,\n                                    std::string_view name) {\n  bool removed = false;\n  if (code != HTTP_HEADER_OTHER) {\n    removed = remove(code);\n  }\n  ITERATE_OVER_STRINGS_ALL_VERSION(name, {\n    delete names()[pos];\n    codes()[pos] = HTTP_HEADER_NONE;\n    removed = true;\n    ++deletedCount_;\n  });\n  return removed;\n}\n\nvoid HTTPHeaders::disposeOfHeaderNames() {\n  ITERATE_OVER_CODES(HTTP_HEADER_OTHER, { delete names()[pos]; });\n}\n\nvoid HTTPHeaders::destroy() {\n  auto c = codes();\n  auto n = names();\n  auto v = values();\n  for (size_t i = 0; i < length_; ++i) {\n    if (c[i] == HTTP_HEADER_OTHER) {\n      delete n[i];\n    }\n    auto p = v + i;\n    p->~string();\n  }\n}\n\nHTTPHeaders::~HTTPHeaders() {\n  destroy();\n}\n\nHTTPHeaders::HTTPHeaders(const HTTPHeaders& hdrs)\n    : length_(0), capacity_(0), deletedCount_(hdrs.deletedCount_) {\n  copyFrom(hdrs);\n}\n\nHTTPHeaders::HTTPHeaders(HTTPHeaders&& hdrs) noexcept\n    : memory_(std::move(hdrs.memory_)),\n      length_(hdrs.length_),\n      capacity_(hdrs.capacity_),\n      deletedCount_(hdrs.deletedCount_) {\n  hdrs.length_ = 0;\n  hdrs.capacity_ = 0;\n  hdrs.deletedCount_ = 0;\n}\n\nvoid HTTPHeaders::copyFrom(const HTTPHeaders& other) {\n  ensure(other.capacity_);\n  memcpy(codes(), other.codes(), other.length_);\n  for (size_t i = 0; i < other.length_; i++) {\n    if (codes()[i] == HTTP_HEADER_OTHER) {\n      names()[i] = new std::string(*other.names()[i]);\n    } else {\n      names()[i] = other.names()[i];\n    }\n    new (values() + i) std::string(other.values()[i]);\n  }\n  length_ = other.length_;\n}\n\nHTTPHeaders& HTTPHeaders::operator=(const HTTPHeaders& hdrs) {\n  if (this != &hdrs) {\n    removeAll();\n    copyFrom(hdrs);\n  }\n  return *this;\n}\n\nHTTPHeaders& HTTPHeaders::operator=(HTTPHeaders&& hdrs) {\n  if (this != &hdrs) {\n    removeAll();\n    std::swap(memory_, hdrs.memory_);\n    std::swap(capacity_, hdrs.capacity_);\n    length_ = hdrs.length_;\n    hdrs.length_ = 0;\n    deletedCount_ = hdrs.deletedCount_;\n    hdrs.deletedCount_ = 0;\n  }\n\n  return *this;\n}\n\nvoid HTTPHeaders::removeAll() {\n  destroy();\n  length_ = 0;\n  deletedCount_ = 0;\n}\n\nsize_t HTTPHeaders::size() const {\n  return length_ - deletedCount_;\n}\n\nbool HTTPHeaders::transferHeaderIfPresent(std::string_view name,\n                                          HTTPHeaders& strippedHeaders) {\n  bool transferred = false;\n  const HTTPHeaderCode code = HTTPCommonHeaders::hash(name.data(), name.size());\n  if (code == HTTP_HEADER_OTHER) {\n    ITERATE_OVER_STRINGS(name, {\n      strippedHeaders.emplace_back(\n          HTTP_HEADER_OTHER, names()[pos], std::move(values()[pos]));\n      codes()[pos] = HTTP_HEADER_NONE;\n      transferred = true;\n      ++deletedCount_;\n    });\n  } else { // code != HTTP_HEADER_OTHER\n    ITERATE_OVER_CODES(code, {\n      strippedHeaders.emplace_back(\n          code, names()[pos], std::move(values()[pos]));\n      codes()[pos] = HTTP_HEADER_NONE;\n      transferred = true;\n      ++deletedCount_;\n    });\n  }\n  return transferred;\n}\n\nvoid HTTPHeaders::stripPerHopHeaders(HTTPHeaders& strippedHeaders,\n                                     bool stripPriority,\n                                     const HTTPHeaders* customPerHopHeaders) {\n  int len;\n  forEachValueOfHeader(\n      HTTP_HEADER_CONNECTION, [&](const std::string& value) -> bool {\n        // Remove all headers specified in Connection header\n        // look for multiple values separated by commas\n        char const* str = value.c_str();\n\n        // skip leading whitespace\n        while (isLWS(*str)) {\n          str++;\n        }\n\n        while (*str != 0) {\n          char const* pos = strchr(str, ',');\n          if (pos == nullptr) {\n            // last (or only) token, done\n\n            // count chars in the token\n            len = 0;\n            while (str[len] != 0 && !isLWS(str[len])) {\n              len++;\n            }\n            if (len > 0) {\n              string hdr(str, len);\n              if (transferHeaderIfPresent(hdr, strippedHeaders)) {\n                VLOG(3) << \"Stripped connection-named hop-by-hop header \"\n                        << hdr;\n              }\n            }\n            break;\n          }\n          len = pos - str;\n          // strip trailing whitespace\n          while (len > 0 && isLWS(str[len - 1])) {\n            len--;\n          }\n          if (len > 0) {\n            // non-empty token\n            string hdr(str, len);\n            if (transferHeaderIfPresent(hdr, strippedHeaders)) {\n              VLOG(3) << \"Stripped connection-named hop-by-hop header \" << hdr;\n            }\n          } // else empty token, no-op\n          str = pos + 1;\n\n          // skip whitespace\n          while (isLWS(*str)) {\n            str++;\n          }\n        }\n        return false; // continue processing \"connection\" headers\n      });\n\n  // Strip hop-by-hop headers\n  auto& perHopHeaders = perHopHeaderCodes();\n  for (size_t i = 0; i < length_; ++i) {\n    auto& code = codes()[i];\n    bool perHop = false;\n    if (code != HTTP_HEADER_OTHER) {\n      perHop = (perHopHeaders[code] ||\n                (stripPriority && code == HTTP_HEADER_PRIORITY) ||\n                (customPerHopHeaders && customPerHopHeaders->exists(code)));\n    } else if (customPerHopHeaders &&\n               customPerHopHeaders->exists(*names()[i])) {\n      perHop = true;\n    }\n    if (perHop) {\n      strippedHeaders.emplace_back(code, names()[i], std::move(values()[i]));\n      code = HTTP_HEADER_NONE;\n      ++deletedCount_;\n      VLOG(5) << \"Stripped hop-by-hop header \" << *names()[i];\n    }\n  }\n}\n\nvoid HTTPHeaders::copyTo(HTTPHeaders& hdrs) const {\n  hdrs.ensure(hdrs.size() + size());\n  for (size_t i = 0; i < length_; ++i) {\n    if (codes()[i] != HTTP_HEADER_NONE) {\n      hdrs.emplace_back(codes()[i],\n                        ((codes()[i] == HTTP_HEADER_OTHER)\n                             ? new string(*names()[i])\n                             : names()[i]),\n                        std::string(values()[i]));\n    }\n  }\n}\n\nHTTPHeaders::SingleOrNullptrResult HTTPHeaders::getSingleOrNullptr(\n    HTTPHeaderCode code) const noexcept {\n  SingleOrNullptrResult res;\n  forEachValueOfHeader(code, [&](const std::string& value) -> bool {\n    res.value = (res.value == nullptr) ? &value : nullptr;\n    res.exists = true;\n    return res.value == nullptr; // stop if seen before\n  });\n  return res;\n}\n\nHTTPHeaders::SingleOrNullptrResult HTTPHeaders::getSingleOrNullptr(\n    std::string_view name) const noexcept {\n  SingleOrNullptrResult res;\n  forEachValueOfHeader(name, [&](const std::string& value) -> bool {\n    res.value = (res.value == nullptr) ? &value : nullptr;\n    res.exists = true;\n    return res.value == nullptr; // stop if seen before\n  });\n  return res;\n}\n\nconst std::string& HTTPHeaders::getSingleOrEmpty(HTTPHeaderCode code) const {\n  return *getSingleOrNullptr(code);\n}\nconst std::string& HTTPHeaders::getSingleOrEmpty(std::string_view name) const {\n  return *getSingleOrNullptr(name);\n}\n\nHTTPHeaderCode* HTTPHeaders::codes() const noexcept {\n  return codes(memory_.get(), capacity_);\n}\n\nHTTPHeaderCode* HTTPHeaders::codes(const uint8_t* memory,\n                                   size_t capacity) const noexcept {\n  return (HTTPHeaderCode*)(memory + capacity * (sizeof(std::string*) +\n                                                sizeof(std::string)));\n}\n\nstd::string** HTTPHeaders::names() const noexcept {\n  return names(memory_.get(), capacity_);\n}\n\nstd::string** HTTPHeaders::names(const uint8_t* memory,\n                                 size_t capacity) const noexcept {\n  return (std::string**)(memory + capacity * sizeof(std::string));\n}\n\nstd::string* HTTPHeaders::values() const noexcept {\n  return values(memory_.get(), capacity_);\n}\n\nstd::string* HTTPHeaders::values(const uint8_t* memory, size_t) const noexcept {\n  return (std::string*)(memory);\n}\n\nvoid HTTPHeaders::ensure(size_t minCapacity) {\n  if (capacity_ >= minCapacity) {\n    return;\n  }\n\n  static_assert(kInitialVectorReserve >= 1,\n                \"This loop depends on a strictly-positive \"\n                \"kInitialVectorReserve to terminate\");\n  size_t targetCapacity = std::max(capacity_, kInitialVectorReserve);\n  while (targetCapacity < minCapacity) {\n    // targetCapacity will never be zero, so it will always grow here.\n    targetCapacity += targetCapacity / 2;\n  }\n  resize(targetCapacity);\n}\n\nvoid HTTPHeaders::resize(size_t capacity) {\n  if (capacity <= capacity_) {\n    return;\n  }\n  auto newMemory = std::make_unique<uint8_t[]>(capacity * kRecSize);\n  if (length_ > 0) {\n    memcpy(codes(newMemory.get(), capacity), codes(), length_);\n    memcpy(names(newMemory.get(), capacity),\n           names(),\n           sizeof(std::string*) * length_);\n    auto vNew = values(newMemory.get(), capacity);\n    auto v = values();\n    for (size_t i = 0; i < length_; i++) {\n      new (vNew + i) std::string(std::move(v[i]));\n    }\n  }\n  memory_ = std::move(newMemory);\n  capacity_ = capacity;\n}\n\nvoid HTTPHeaders::emplace_back(HTTPHeaderCode code,\n                               std::string* name,\n                               std::string&& value) {\n  ensure(length_ + 1);\n  codes()[length_] = code;\n  names()[length_] = name;\n  std::string* p = values() + length_++;\n  auto trimmed = folly::trimWhitespace(value);\n  if (LIKELY(trimmed.size() == value.size())) { // elide copy\n    new (p) std::string(std::move(value));\n  } else {\n    new (p) std::string(trimmed);\n  }\n}\n\nvoid HTTPHeaders::forEach(const ForEachFnT& func) const {\n  auto c = codes();\n  auto n = names();\n  auto v = values();\n  for (size_t i = 0; i < length_; ++i) {\n    if (c[i] != HTTPHeaderCode::HTTP_HEADER_NONE) {\n      func(*n[i], v[i]);\n    }\n  }\n}\n\nvoid HTTPHeaders::forEachWithCode(const ForEachWithCodeFnT& func) const {\n  auto c = codes();\n  auto n = names();\n  auto v = values();\n  for (size_t i = 0; i < length_; ++i) {\n    if (c[i] != HTTPHeaderCode::HTTP_HEADER_NONE) {\n      func(c[i], *n[i], v[i]);\n    }\n  }\n}\n\nbool HTTPHeaders::forEachValueOfHeader(\n    std::string_view name, const ForEachValueOfHeaderFnT& func) const {\n  const HTTPHeaderCode code = HTTPCommonHeaders::hash(name.data(), name.size());\n  if (code != HTTPHeaderCode::HTTP_HEADER_OTHER) {\n    return forEachValueOfHeader(code, func);\n  }\n  ITERATE_OVER_STRINGS(name, {\n    if (func(values()[pos])) {\n      return true;\n    }\n  });\n  return false;\n}\n\nbool HTTPHeaders::forEachValueOfHeader(\n    HTTPHeaderCode code, const ForEachValueOfHeaderFnT& func) const {\n  ITERATE_OVER_CODES(code, {\n    if (func(values()[pos])) {\n      return true;\n    }\n  });\n  return false;\n}\n\nbool HTTPHeaders::removeByPredicate(const RemoveByPredFnT& func) {\n  bool removed = false;\n  auto c = codes();\n  auto n = names();\n  auto v = values();\n  for (size_t i = 0; i < length_; ++i) {\n    if (c[i] == HTTPHeaderCode::HTTP_HEADER_NONE || !func(c[i], *n[i], v[i])) {\n      continue;\n    }\n\n    if (c[i] == HTTPHeaderCode::HTTP_HEADER_OTHER) {\n      delete n[i];\n      n[i] = nullptr;\n    }\n\n    c[i] = HTTPHeaderCode::HTTP_HEADER_NONE;\n    ++deletedCount_;\n    removed = true;\n  }\n\n  return removed;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPHeaders.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/FBVector.h>\n#include <folly/Range.h>\n#include <folly/String.h>\n#include <folly/Utility.h>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <proxygen/lib/utils/Export.h>\n#include <proxygen/lib/utils/UtilInl.h>\n\n#include <bitset>\n#include <cstring>\n#include <initializer_list>\n#include <string>\n#include <string_view>\n\nnamespace proxygen {\n\nextern const std::string empty_string;\n\n/**\n * Return true if the character is linear whitespace, as defined by the LWS\n * definition in RFC 2616, and false otherwise\n */\ninline bool isLWS(char c) {\n  // Technically \\r and \\n are only allowed in LWS if they appear together.\n  if (c == ' ' || c == '\\n' || c == '\\t' || c == '\\r') {\n    return true;\n  }\n  return false;\n}\n\n/**\n * A collection of HTTP headers.\n *\n * This is broken out from HTTPMessage, as it's convenient for other things to\n * be able to use collections of HTTP headers that are easy to work with. The\n * structure is optimized for real-life header collection sizes.\n *\n * Headers are stored as Name/Value pairs, in the order they are received on\n * the wire. We hash the names of all common HTTP headers (using a static\n * perfect hash function generated using gperf from HTTPCommonHeaders.gperf)\n * into 1-byte hashes (we call them \"codes\") and only store these. We search\n * them using memchr, which has an x86_64 assembly implementation with\n * complexity O(n/16) ;)\n *\n * Instead of creating strings with header names, we point to a static array\n * of strings in HTTPCommonHeaders. If the header name is not in our set of\n * common header names (this is considered unlikely, because we intend this set\n * to be very complete), then we create a new string with its name (we own that\n * pointer then). For such headers, we store the code HTTP_HEADER_OTHER.\n *\n * The code HTTP_HEADER_NONE signifies a header that has been removed.\n *\n * Most methods which take a header name have two versions: one accepting\n * a string, and one accepting a code. It is recommended to use the latter\n * if possible, as in:\n *     headers.add(HTTP_HEADER_LOCATION, location);\n * rather than:\n *     headers.add(\"Location\", location);\n */\nclass HTTPHeaders {\n public:\n  struct HTTPHeaderName {\n    enum Type { CODE, STRING };\n    union {\n      std::string_view name_;\n      HTTPHeaderCode code_;\n    };\n    Type type_;\n    /* implicit */ HTTPHeaderName(HTTPHeaderCode code)\n        : code_(code), type_(CODE) {\n    }\n    /* implicit */ HTTPHeaderName(const char* name)\n        : name_(name), type_(STRING) {\n    }\n    /* implicit */ HTTPHeaderName(std::string_view name)\n        : name_(name), type_(STRING) {\n    }\n  };\n\n  using headers_initializer_list =\n      std::initializer_list<std::pair<HTTPHeaderName, std::string_view>>;\n\n  /*\n   * separator used to concatenate multiple values of the same header\n   * check out sections 4.2 and 14.45 from rfc2616\n   */\n  static constexpr std::string_view kCombineSeparator{\", \"};\n\n  FB_EXPORT HTTPHeaders();\n  FB_EXPORT ~HTTPHeaders();\n  FB_EXPORT HTTPHeaders(const HTTPHeaders&);\n  FB_EXPORT HTTPHeaders& operator=(const HTTPHeaders&);\n  FB_EXPORT HTTPHeaders(HTTPHeaders&&) noexcept;\n  FB_EXPORT HTTPHeaders& operator=(HTTPHeaders&&);\n\n  /**\n   * Add the header 'name' with value 'value'; if other instances of this\n   * header name exist, they will be retained.\n   */\n  void add(std::string_view name, std::string&& value);\n  void add(std::string_view name, std::string_view value) {\n    add(name, std::string(value));\n  }\n  FOLLY_ALWAYS_INLINE void add(std::string_view name, const char* value) {\n    return add(name, std::string_view{value});\n  }\n\n  void add(HTTPHeaderCode code, std::string&& value);\n  void add(HTTPHeaderCode code, std::string_view value) {\n    add(code, std::string(value));\n  }\n  FOLLY_ALWAYS_INLINE void add(HTTPHeaderCode code, const char* value) {\n    return add(code, std::string_view{value});\n  }\n\n  void add(headers_initializer_list l);\n\n  /**\n   * For the header 'name', set its value to the single header 'value',\n   * removing any other instances of this header.\n   */\n  void set(std::string_view name, std::string_view value) {\n    // this could be somewhat optimized but probably not an issue yet\n    remove(name);\n    add(name, value);\n  }\n  void set(HTTPHeaderCode code, std::string_view value) {\n    remove(code);\n    add(code, value);\n  }\n\n  /**\n   * This method will set only one version of the header, it will first\n   * Remove all possible versions of header eg. if x-y-z is the\n   * argument it will remove x-y_z, x_y-z and x_y_z too and then set the given\n   * header name, value.\n   */\n  void setOneVersion(std::string_view name,\n                     HTTPHeaderCode code,\n                     std::string_view value) {\n    removeAllVersions(code, name);\n    add(name, value);\n  }\n\n  /**\n   * Do we have an instance of the given header?\n   */\n  [[nodiscard]] bool exists(std::string_view name) const;\n  [[nodiscard]] bool exists(HTTPHeaderCode code) const;\n\n  /**\n   * combine all the value for this header into a string\n   */\n  template <typename T>\n  std::string combine(\n      const T& header,\n      const std::string_view separator = kCombineSeparator) const;\n\n  /**\n   * Process the list of all headers, in the order that they were seen:\n   * for each header:value pair, the function/functor/lambda-expression\n   * given as the second parameter will be executed. It should take two\n   * const string & parameters and return void. Example use:\n   *     hdrs.forEach([&] (const string& header, const std::string& val) {\n   *       std::cout << header << \": \" << val;\n   *     });\n   */\n  using ForEachFnT =\n      std::function<void(const std::string&, const std::string&)>;\n  void forEach(const ForEachFnT& func) const;\n\n  /**\n   * Process the list of all headers, in the order that they were seen:\n   * for each header:value pair, the function/functor/lambda-expression\n   * given as the second parameter will be executed. It should take one\n   * HTTPHeaderCode (code) parameter, two const string & parameters and\n   * return void. Example use:\n   *     hdrs.forEachWithCode([&] (HTTPHeaderCode code,\n   *                               const string& header,\n   *                               const std::string& val) {\n   *       std::cout << header << \"(\" << code << \"): \" << val;\n   *     });\n   */\n  using ForEachWithCodeFnT = std::function<void(\n      HTTPHeaderCode, const std::string&, const std::string&)>;\n  void forEachWithCode(const ForEachWithCodeFnT& func) const;\n\n  /**\n   * Process the list of all headers, in the order that they were seen:\n   * for each header:value pair, the function/functor/lambda-expression\n   * given as the parameter will be executed to determine whether the\n   * header should be removed. Example use:\n   *\n   *     hdrs.removeByPredicate([&] (HTTPHeaderCode code,\n   *                                 const string& header,\n   *                                 const string& val) {\n   *       return std::regex_match(header, std::regex(\"^X-Fb-.*\"));\n   *     });\n   *\n   * return true only if one or more headers are removed.\n   */\n\n  using RemoveByPredFnT = std::function<bool(\n      HTTPHeaderCode, const std::string&, const std::string&)>;\n  bool removeByPredicate(const RemoveByPredFnT& func);\n\n  /**\n   * Returns the value of the header if it's found in the message and is the\n   * only value under the given name. If either of these is violated, returns\n   * nullptr. `exists` member can help distinguish whether nullptr was returned\n   * due to multiple ocurrences or not found.\n   *\n   * For code that follows `HTTPHeaders::exists ->\n   * HTTPHeaders::getSingleOrEmpty` pattern, it can be replaced with\n   * `HTTPHeader::getSingleOrNullptr` to elide a memchr lookup in the happy path\n   */\n  struct SingleOrNullptrResult {\n    const std::string* value{nullptr};\n    bool exists{false};\n\n    // convenience functions to replace ::exists & ::getSingleOrEmpty pattern\n    operator bool() const noexcept {\n      return exists;\n    }\n    const std::string& operator*() const noexcept {\n      return value ? *value : empty_string;\n    }\n    const std::string* operator->() const noexcept {\n      return &(operator*)();\n    }\n  };\n  [[nodiscard]] SingleOrNullptrResult getSingleOrNullptr(\n      HTTPHeaderCode code) const noexcept;\n  [[nodiscard]] SingleOrNullptrResult getSingleOrNullptr(\n      std::string_view name) const noexcept;\n\n  /**\n   * Returns the value of the header if it's found in the message and is the\n   * only value under the given name. If either of these is violated, returns\n   * empty_string.\n   */\n  [[nodiscard]] const std::string& getSingleOrEmpty(HTTPHeaderCode code) const;\n  [[nodiscard]] const std::string& getSingleOrEmpty(\n      std::string_view name) const;\n  [[nodiscard]] const std::string rawGet(const std::string& header) const {\n    return getSingleOrEmpty(header);\n  }\n\n  /**\n   * Get the number of values corresponding to a given header name.\n   */\n  [[nodiscard]] size_t getNumberOfValues(HTTPHeaderCode code) const;\n  [[nodiscard]] size_t getNumberOfValues(std::string_view name) const;\n\n  /**\n   * Process the ordered list of values for the given header name:\n   * for each value, the function/functor/lambda-expression given as the second\n   * parameter will be executed. It should take one const string & parameter\n   * and return bool (false to keep processing, true to stop it). Example use:\n   *     hdrs.forEachValueOfHeader(\"someheader\", [&] (const std::string& val) {\n   *       std::cout << val;\n   *       return false;\n   *     });\n   * This method returns true if processing was stopped (by func returning\n   * true), and false otherwise.\n   */\n  using ForEachValueOfHeaderFnT = std::function<bool(const std::string&)>;\n  bool forEachValueOfHeader(std::string_view name,\n                            const ForEachValueOfHeaderFnT& func) const;\n  bool forEachValueOfHeader(HTTPHeaderCode code,\n                            const ForEachValueOfHeaderFnT& func) const;\n\n  /**\n   * Remove all instances of the given header, returning true if anything was\n   * removed and false if this header didn't exist in our set.\n   */\n  bool remove(std::string_view name);\n  bool remove(HTTPHeaderCode code);\n  /**\n   * Remove all possible versions of header eg. if x-y-z is the\n   * argument it will remove x-y_z, x_y-z and x_y_z too.\n   */\n  bool removeAllVersions(HTTPHeaderCode code, std::string_view name);\n\n  /**\n   * Remove all headers.\n   */\n  void removeAll();\n\n  /**\n   * Remove per-hop-headers and headers named in the Connection header\n   * and place the value in strippedHeaders.\n   *\n   * Also optionally strips the Priority header.\n   * The Priority header is defined as end-to-end in the RFC, but a proxy that\n   * coalesces requests from multiple downstream connections over the same\n   * upstream connection may want to use a hop-by-hop semantics.\n   */\n  void stripPerHopHeaders(HTTPHeaders& strippedHeaders,\n                          bool stripPriority,\n                          const HTTPHeaders* customPerHopHeaders);\n\n  /**\n   * Get the total number of headers.\n   */\n  [[nodiscard]] size_t size() const;\n\n  /**\n   * Copy all headers from this to hdrs.\n   */\n  void copyTo(HTTPHeaders& hdrs) const;\n\n  /**\n   * Determines whether header with a given code is a per-hop header,\n   * which should be stripped by stripPerHopHeaders().\n   */\n  static std::bitset<256>& perHopHeaderCodes();\n\n private:\n  std::unique_ptr<uint8_t[]> memory_;\n  size_t length_{0};\n  size_t capacity_{0};\n  size_t deletedCount_;\n\n  void copyFrom(const HTTPHeaders& hdrs);\n\n  [[nodiscard]] HTTPHeaderCode* codes() const noexcept;\n  [[nodiscard]] HTTPHeaderCode* codes(const uint8_t* memory,\n                                      size_t capacity) const noexcept;\n  [[nodiscard]] std::string** names() const noexcept;\n\n  [[nodiscard]] std::string** names(const uint8_t* memory,\n                                    size_t capacity) const noexcept;\n  [[nodiscard]] std::string* values() const noexcept;\n  [[nodiscard]] std::string* values(const uint8_t* memory,\n                                    size_t) const noexcept;\n\n  /**\n   * Moves the named header and values from this group to the destination\n   * group.  No-op if the header doesn't exist.  Returns true if header(s) were\n   * moved.\n   */\n  bool transferHeaderIfPresent(std::string_view name, HTTPHeaders& dest);\n\n  // deletes the strings in headerNames_ that we own\n  void disposeOfHeaderNames();\n\n  void destroy();\n\n  void ensure(size_t minCapacity);\n\n  void resize(size_t capacity);\n\n  void emplace_back(HTTPHeaderCode code,\n                    std::string* name,\n                    std::string&& value);\n};\n\ntemplate <typename T>\nstd::string HTTPHeaders::combine(const T& header,\n                                 const std::string_view separator) const {\n  std::string combined;\n  forEachValueOfHeader(header, [&](const std::string& value) -> bool {\n    if (combined.empty()) {\n      combined.append(value);\n    } else {\n      combined.append(separator).append(value);\n    }\n    return false;\n  });\n  return combined;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMessage.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPMessage.h>\n\n#include <folly/Format.h>\n#include <folly/Range.h>\n#include <folly/SingletonThreadLocal.h>\n#include <folly/String.h>\n#include <string>\n#include <vector>\n\nusing folly::StringPiece;\nusing std::pair;\nusing std::string;\n\nnamespace proxygen {\n\nstd::string httpPriorityToString(const HTTPPriority& pri) {\n  return folly::to<std::string>(\n      \"u=\",\n      std::min(static_cast<uint8_t>(proxygen::kMaxPriority), pri.urgency),\n      pri.incremental ? \",i\" : \"\",\n      pri.orderId > 0 ? folly::to<std::string>(\",o=\", pri.orderId) : \"\",\n      pri.paused ? \",p\" : \"\");\n}\n\nstd::mutex HTTPMessage::mutexDump_;\n\nconst pair<uint8_t, uint8_t> HTTPMessage::kHTTPVersion09(0, 9);\nconst pair<uint8_t, uint8_t> HTTPMessage::kHTTPVersion10(1, 0);\nconst pair<uint8_t, uint8_t> HTTPMessage::kHTTPVersion11(1, 1);\n\nvoid HTTPMessage::stripPerHopHeaders(bool stripPriority,\n                                     const HTTPHeaders* customPerHopHeaders) {\n  // Some code paths end up recyling a single HTTPMessage instance for multiple\n  // requests, and adding their own per-hop headers each time. In that case, we\n  // don't want to accumulate these headers.\n  if (!strippedPerHopHeaders_) {\n    strippedPerHopHeaders_ = std::make_unique<HTTPHeaders>();\n  } else {\n    strippedPerHopHeaders_->removeAll();\n  }\n\n  if (!trailersAllowed_) {\n    // Because stripPerHopHeaders can be called multiple times, don't\n    // let subsequent instances clear this flag\n    trailersAllowed_ = checkForHeaderToken(HTTP_HEADER_TE, \"trailers\", false);\n  }\n\n  headers_.stripPerHopHeaders(\n      *strippedPerHopHeaders_, stripPriority, customPerHopHeaders);\n}\n\nHTTPMessage::HTTPMessage()\n    : startTime_(getCurrentTime()),\n      localIP_(),\n      versionStr_(\"1.0\"),\n      fields_(),\n      upgradeWebsocket_(HTTPMessage::WebSocketUpgrade::NONE),\n      seqNo_(-1),\n      sslVersion_(0),\n      sslCipher_(nullptr),\n      protoStr_(nullptr),\n      pri_(kDefaultHttpPriorityUrgency),\n      version_(1, 0),\n      parsedCookies_(false),\n      parsedQueryParams_(false),\n      chunked_(false),\n      upgraded_(false),\n      wantsKeepalive_(true),\n      trailersAllowed_(false) {\n}\n\nHTTPMessage::~HTTPMessage() = default;\n\nHTTPMessage::HTTPMessage(const HTTPMessage& message)\n    : startTime_(message.startTime_),\n      dstAddress_(message.dstAddress_),\n      dstIP_(message.dstIP_),\n      dstPort_(message.dstPort_),\n      localIP_(message.localIP_),\n      versionStr_(message.versionStr_),\n      fields_(message.fields_),\n      cookies_(message.cookies_),\n      queryParams_(message.queryParams_),\n      headers_(message.headers_),\n      upgradeWebsocket_(message.upgradeWebsocket_),\n      seqNo_(message.seqNo_),\n      sslVersion_(message.sslVersion_),\n      sslCipher_(message.sslCipher_),\n      protoStr_(message.protoStr_),\n      pri_(message.pri_),\n      version_(message.version_),\n      parsedCookies_(message.parsedCookies_),\n      parsedQueryParams_(message.parsedQueryParams_),\n      chunked_(message.chunked_),\n      upgraded_(message.upgraded_),\n      wantsKeepalive_(message.wantsKeepalive_),\n      trailersAllowed_(message.trailersAllowed_),\n      scheme_(message.scheme_) {\n  if (isRequest()) {\n    setURL(request().url_);\n  }\n  if (message.strippedPerHopHeaders_) {\n    strippedPerHopHeaders_ =\n        std::make_unique<HTTPHeaders>(*message.strippedPerHopHeaders_);\n  }\n  if (message.trailers_) {\n    trailers_ = std::make_unique<HTTPHeaders>(*message.trailers_);\n  }\n}\n\nHTTPMessage::HTTPMessage(HTTPMessage&& message) noexcept\n    : startTime_(message.startTime_),\n      dstAddress_(std::move(message.dstAddress_)),\n      dstIP_(std::move(message.dstIP_)),\n      dstPort_(message.dstPort_),\n      localIP_(std::move(message.localIP_)),\n      versionStr_(std::move(message.versionStr_)),\n      fields_(std::move(message.fields_)),\n      cookies_(std::move(message.cookies_)),\n      queryParams_(std::move(message.queryParams_)),\n      headers_(std::move(message.headers_)),\n      strippedPerHopHeaders_(std::move(message.strippedPerHopHeaders_)),\n      upgradeWebsocket_(message.upgradeWebsocket_),\n      trailers_(std::move(message.trailers_)),\n      seqNo_(message.seqNo_),\n      sslVersion_(message.sslVersion_),\n      sslCipher_(message.sslCipher_),\n      protoStr_(message.protoStr_),\n      pri_(message.pri_),\n      version_(message.version_),\n      parsedCookies_(message.parsedCookies_),\n      parsedQueryParams_(message.parsedQueryParams_),\n      chunked_(message.chunked_),\n      upgraded_(message.upgraded_),\n      wantsKeepalive_(message.wantsKeepalive_),\n      trailersAllowed_(message.trailersAllowed_),\n      scheme_(message.scheme_) {\n  if (isRequest()) {\n    setURL(request().url_);\n  }\n}\n\nHTTPMessage& HTTPMessage::operator=(const HTTPMessage& message) {\n  if (&message == this) {\n    return *this;\n  }\n  startTime_ = message.startTime_;\n  seqNo_ = message.seqNo_;\n  dstAddress_ = message.dstAddress_;\n  dstIP_ = message.dstIP_;\n  dstPort_ = message.dstPort_;\n  localIP_ = message.localIP_;\n  versionStr_ = message.versionStr_;\n  fields_ = message.fields_;\n  if (isRequest()) {\n    setURL(request().url_);\n  }\n  cookies_ = message.cookies_;\n  queryParams_ = message.queryParams_;\n  version_ = message.version_;\n  headers_ = message.headers_;\n  if (message.strippedPerHopHeaders_) {\n    strippedPerHopHeaders_ =\n        std::make_unique<HTTPHeaders>(*message.strippedPerHopHeaders_);\n  } else {\n    strippedPerHopHeaders_.reset();\n  }\n  sslVersion_ = message.sslVersion_;\n  sslCipher_ = message.sslCipher_;\n  protoStr_ = message.protoStr_;\n  pri_ = message.pri_;\n  parsedCookies_ = message.parsedCookies_;\n  parsedQueryParams_ = message.parsedQueryParams_;\n  chunked_ = message.chunked_;\n  upgraded_ = message.upgraded_;\n  wantsKeepalive_ = message.wantsKeepalive_;\n  trailersAllowed_ = message.trailersAllowed_;\n  scheme_ = message.scheme_;\n  upgradeWebsocket_ = message.upgradeWebsocket_;\n\n  if (message.trailers_) {\n    trailers_ = std::make_unique<HTTPHeaders>(*message.trailers_);\n  } else {\n    trailers_.reset();\n  }\n  return *this;\n}\n\nHTTPMessage& HTTPMessage::operator=(HTTPMessage&& message) {\n  if (&message == this) {\n    return *this;\n  }\n  startTime_ = message.startTime_;\n  seqNo_ = message.seqNo_;\n  dstAddress_ = std::move(message.dstAddress_);\n  dstIP_ = std::move(message.dstIP_);\n  dstPort_ = message.dstPort_;\n  localIP_ = std::move(message.localIP_);\n  versionStr_ = std::move(message.versionStr_);\n  fields_ = std::move(message.fields_);\n  if (isRequest()) {\n    setURL(request().url_);\n  }\n  cookies_ = std::move(message.cookies_);\n  queryParams_ = std::move(message.queryParams_);\n  version_ = message.version_;\n  headers_ = std::move(message.headers_);\n  strippedPerHopHeaders_ = std::move(message.strippedPerHopHeaders_);\n  sslVersion_ = message.sslVersion_;\n  sslCipher_ = message.sslCipher_;\n  protoStr_ = message.protoStr_;\n  pri_ = message.pri_;\n  parsedCookies_ = message.parsedCookies_;\n  parsedQueryParams_ = message.parsedQueryParams_;\n  chunked_ = message.chunked_;\n  upgraded_ = message.upgraded_;\n  wantsKeepalive_ = message.wantsKeepalive_;\n  trailersAllowed_ = message.trailersAllowed_;\n  scheme_ = message.scheme_;\n  upgradeWebsocket_ = message.upgradeWebsocket_;\n  trailers_ = std::move(message.trailers_);\n  return *this;\n}\n\nvoid HTTPMessage::setMethod(HTTPMethod method) {\n  Request& req = request();\n  req.method_ = method;\n}\n\nvoid HTTPMessage::setMethod(folly::StringPiece method) {\n  VLOG(9) << \"setMethod: \" << method;\n  Request& req = request();\n  folly::Optional<HTTPMethod> result = stringToMethod(method);\n  if (result) {\n    req.method_ = *result;\n  } else {\n    req.method_ = std::make_unique<std::string>(method.str());\n    auto& storedMethod = *std::get<std::unique_ptr<std::string>>(req.method_);\n    std::transform(storedMethod.begin(),\n                   storedMethod.end(),\n                   storedMethod.begin(),\n                   ::toupper);\n  }\n}\n\nfolly::Optional<HTTPMethod> HTTPMessage::getMethod() const {\n  const auto& req = request();\n  if (req.method_.index() == 2) {\n    return std::get<HTTPMethod>(req.method_);\n  }\n  return folly::none;\n}\n\n/**\n * @Returns a string representation of the request method (fpreq)\n */\nconst std::string& HTTPMessage::getMethodString() const {\n  const auto& req = request();\n  if (req.method_.index() == 1) {\n    return *std::get<std::unique_ptr<std::string>>(req.method_);\n  } else if (req.method_.index() == 2) {\n    return methodToString(std::get<HTTPMethod>(req.method_));\n  }\n  return empty_string;\n}\n\nvoid HTTPMessage::setHTTPVersion(uint8_t maj, uint8_t min) {\n  version_.first = maj;\n  version_.second = min;\n  if (version_.first >= 10 || version_.second >= 10) {\n    versionStr_ = folly::to<std::string>(maj, '.', min);\n  } else {\n    versionStr_.reserve(3);\n    versionStr_.clear();\n    versionStr_.append(1, maj + '0');\n    versionStr_.append(1, '.');\n    versionStr_.append(1, min + '0');\n  }\n}\n\nconst pair<uint8_t, uint8_t>& HTTPMessage::getHTTPVersion() const {\n  return version_;\n}\n\nint HTTPMessage::processMaxForwards() {\n  constexpr std::string_view kMaxForwards = \"Max-Forwards\";\n  if (getMethod() == HTTPMethod::TRACE || getMethod() == HTTPMethod::OPTIONS) {\n    const string& value = headers_.getSingleOrEmpty(kMaxForwards);\n    if (value.length() > 0) {\n      int64_t max_forwards = 0;\n      try {\n        max_forwards = folly::to<int64_t>(value);\n      } catch (const std::range_error&) {\n        return 400;\n      }\n\n      if (max_forwards < 0) {\n        return 400;\n      } else if (max_forwards == 0) {\n        return 501;\n      } else {\n        headers_.set(kMaxForwards, folly::to<string>(max_forwards - 1));\n      }\n    }\n  }\n  return 0;\n}\n\nbool HTTPMessage::isHTTP1_0() const {\n  return version_ == kHTTPVersion10;\n}\n\nbool HTTPMessage::isHTTP1_1() const {\n  return version_ == kHTTPVersion11;\n}\n\nnamespace {\nstruct FormattedDate {\n  time_t lastTime{0};\n  string date;\n\n  string formatDate() {\n    const auto now =\n        std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n\n    if (now != lastTime) {\n      char buff[1024];\n      tm timeTupple;\n      gmtime_r(&now, &timeTupple);\n\n      strftime(buff, 1024, \"%a, %d %b %Y %H:%M:%S %Z\", &timeTupple);\n      date = std::string(buff);\n      lastTime = now;\n    }\n    return date;\n  }\n};\n} // namespace\n\nstring HTTPMessage::formatDateHeader() {\n  struct DateTag {};\n  auto& obj = folly::SingletonThreadLocal<FormattedDate, DateTag>::get();\n  return obj.formatDate();\n}\n\nvoid HTTPMessage::ensureHostHeader() {\n  if (!headers_.exists(HTTP_HEADER_HOST)) {\n    headers_.add(HTTP_HEADER_HOST,\n                 getDstAddress().getFamily() == AF_INET6\n                     ? '[' + getDstIP() + ']'\n                     : getDstIP());\n  }\n}\n\nvoid HTTPMessage::setStatusCode(uint16_t status) {\n  response().status_ = status;\n  response().statusStr_ = folly::to<string>(status);\n}\n\nuint16_t HTTPMessage::getStatusCode() const {\n  return response().status_;\n}\n\nvoid HTTPMessage::setPushStatusCode(uint16_t status) {\n  request().pushStatus_ = status;\n}\n\nstd::string HTTPMessage::getPushStatusStr() const {\n  return folly::to<string>(request().pushStatus_);\n}\n\nuint16_t HTTPMessage::getPushStatusCode() const {\n  return request().pushStatus_;\n}\n\nvoid HTTPMessage::constructDirectResponse(const pair<uint8_t, uint8_t>& version,\n                                          const int statusCode,\n                                          const string& statusMsg,\n                                          int contentLength) {\n  setStatusCode(statusCode);\n  setStatusMessage(statusMsg);\n  constructDirectResponse(version, contentLength);\n}\n\nvoid HTTPMessage::constructDirectResponse(const pair<uint8_t, uint8_t>& version,\n                                          int contentLength) {\n  setHTTPVersion(version.first, version.second);\n\n  headers_.set(HTTP_HEADER_CONTENT_LENGTH, folly::to<string>(contentLength));\n\n  if (!headers_.exists(HTTP_HEADER_CONTENT_TYPE)) {\n    headers_.add(HTTP_HEADER_CONTENT_TYPE, \"text/plain\");\n  }\n  setIsChunked(false);\n  setIsUpgraded(false);\n}\n\nvoid HTTPMessage::parseCookies() const {\n  DCHECK(!parsedCookies_);\n  parsedCookies_ = true;\n\n  headers_.forEachValueOfHeader(\n      HTTP_HEADER_COOKIE, [&](const string& headerval) {\n        splitNameValuePieces(\n            headerval,\n            ';',\n            '=',\n            [this](StringPiece cookieName, StringPiece cookieValue) {\n              cookies_.emplace(cookieName, cookieValue);\n            });\n\n        return false; // continue processing \"cookie\" headers\n      });\n}\n\nvoid HTTPMessage::unparseCookies() const {\n  cookies_.clear();\n  parsedCookies_ = false;\n}\n\nconst StringPiece HTTPMessage::getCookie(const string& name) const {\n  // clear previous parsed cookies.  They might store raw pointers to a vector\n  // in headers_, which can resize on add()\n  // Parse the cookies if we haven't done so yet\n  unparseCookies();\n  if (!parsedCookies_) {\n    parseCookies();\n  }\n\n  auto it = cookies_.find(name);\n  if (it == cookies_.end()) {\n    return StringPiece();\n  } else {\n    return it->second;\n  }\n}\n\nvoid HTTPMessage::removeCookie(const string& name) {\n  unparseCookies();\n  if (!parsedCookies_) {\n    parseCookies();\n  }\n\n  auto it = cookies_.find(name);\n  if (it == cookies_.end()) {\n    return;\n  }\n\n  // Remove cookie\n  cookies_.erase(it);\n\n  // Reconstruct the Cookie header from remaining cookies\n  if (cookies_.empty()) {\n    // No cookies remaining\n    headers_.remove(HTTP_HEADER_COOKIE);\n  } else {\n    // Build new cookie header value\n    std::vector<std::string> cookieStrings;\n    cookieStrings.reserve(cookies_.size());\n    for (const auto& cookie : cookies_) {\n      cookieStrings.emplace_back(cookie.first.str() + \"=\" +\n                                 cookie.second.str());\n    }\n\n    // Set the new Cookie header\n    headers_.set(HTTP_HEADER_COOKIE, folly::join(\"; \", cookieStrings));\n  }\n\n  // Clear parsed cookies\n  unparseCookies();\n}\n\nvoid HTTPMessage::parseQueryParams() const {\n  DCHECK(!parsedQueryParams_);\n  const Request& req = request();\n\n  parsedQueryParams_ = true;\n  if (req.query_.empty()) {\n    return;\n  }\n\n  // We're taking the min in order to ensure that we don't over-allocate in the\n  // case that the other endpoint sends something like \"&&&&&&&\"\n  // req.query_.length() / 3 is derived from the fact that each query param\n  // takes at least 3 characters (the & the = and at least another character for\n  // the key)\n  uint32_t ampersandCount = std::count_if(\n      req.query_.begin(), req.query_.end(), [](char c) { return c == '&'; });\n  queryParams_.reserve(\n      std::min(ampersandCount + 1, uint32_t(req.query_.size() / 3) + 1));\n  splitNameValue(\n      req.query_, '&', '=', [this](string&& paramName, string&& paramValue) {\n        auto it = queryParams_.find(paramName);\n        if (it == queryParams_.end()) {\n          queryParams_.emplace(std::move(paramName), std::move(paramValue));\n        } else {\n          // We have some unit tests that make sure we always return the last\n          // value when there are duplicate parameters. I don't think this\n          // really matters, but for now we might as well maintain the same\n          // behavior.\n          it->second = std::move(paramValue);\n        }\n      });\n}\n\nvoid HTTPMessage::unparseQueryParams() {\n  queryParams_.clear();\n  parsedQueryParams_ = false;\n}\n\nconst string* HTTPMessage::getQueryParamPtr(const string& name) const {\n  // Parse the query parameters if we haven't done so yet\n  if (!parsedQueryParams_) {\n    parseQueryParams();\n  }\n\n  auto it = queryParams_.find(name);\n  if (it == queryParams_.end()) {\n    return nullptr;\n  }\n  return &it->second;\n}\n\nbool HTTPMessage::hasQueryParam(const string& name) const {\n  return getQueryParamPtr(name) != nullptr;\n}\n\nconst string& HTTPMessage::getQueryParam(const string& name) const {\n  const string* ret = getQueryParamPtr(name);\n  return ret ? *ret : empty_string;\n}\n\nint HTTPMessage::getIntQueryParam(const std::string& name) const {\n  return folly::to<int>(getQueryParam(name));\n}\n\nint HTTPMessage::getIntQueryParam(const std::string& name, int defval) const {\n  try {\n    return getIntQueryParam(name);\n  } catch (const std::exception&) {\n  }\n\n  return defval;\n}\n\nstd::string HTTPMessage::getDecodedQueryParam(const std::string& name) const {\n  auto val = getQueryParam(name);\n\n  std::string result;\n  try {\n    folly::uriUnescape(val, result, folly::UriEscapeMode::QUERY);\n  } catch (const std::exception& ex) {\n    LOG(WARNING) << \"Invalid escaped query param: \" << folly::exceptionStr(ex);\n  }\n  return result;\n}\n\nconst HTTPQueryParamMap& HTTPMessage::getQueryParams() const {\n  // Parse the query parameters if we haven't done so yet\n  if (!parsedQueryParams_) {\n    parseQueryParams();\n  }\n  return queryParams_;\n}\n\nbool HTTPMessage::setQueryString(const std::string& query, bool strict) {\n  return setQueryStringImpl(query, true, strict);\n}\n\nbool HTTPMessage::setQueryStringImpl(const std::string& query,\n                                     bool unparse,\n                                     bool strict) {\n  // No need to strictly verify the URL when reparsing it\n  auto u = ParseURL::parseURL(request().url_, /*strict=*/false);\n\n  if (u) {\n    // Recreate the URL by just changing the query string\n    auto res = setURLImpl(createUrl(u->scheme(),\n                                    u->authority(),\n                                    u->path(),\n                                    query, // new query string\n                                    u->fragment()),\n                          unparse,\n                          strict);\n    return !strict || res.valid();\n  }\n\n  DVLOG(4) << \"Error parsing URL during setQueryString: \" << request().url_;\n  return false;\n}\n\nbool HTTPMessage::removeQueryParam(const std::string& name) {\n  // Parse the query parameters if we haven't done so yet\n  if (!parsedQueryParams_) {\n    parseQueryParams();\n  }\n\n  if (!queryParams_.erase(name)) {\n    // Query param was not found.\n    return false;\n  }\n\n  auto query = createQueryString(queryParams_, request().query_.size());\n  return setQueryStringImpl(query, false, /*strict=*/false);\n}\n\nbool HTTPMessage::setQueryParam(const std::string& name,\n                                const std::string& value,\n                                bool strict) {\n  // Parse the query parameters if we haven't done so yet\n  if (!parsedQueryParams_) {\n    parseQueryParams();\n  }\n\n  queryParams_[name] = value;\n  auto query = createQueryString(queryParams_, request().query_.size());\n  return setQueryStringImpl(query, false, strict);\n}\n\nstd::string HTTPMessage::createQueryString(const HTTPQueryParamMap& params,\n                                           uint32_t maxLength) {\n  std::string query;\n  query.reserve(maxLength);\n  for (auto it = params.begin(); it != params.end(); it++) {\n    if (it != params.begin()) {\n      query.append(\"&\");\n    }\n    query.append(it->first + \"=\" + it->second);\n  }\n  query.shrink_to_fit();\n  return query;\n}\n\nstd::string HTTPMessage::createUrl(const folly::StringPiece scheme,\n                                   const folly::StringPiece authority,\n                                   const folly::StringPiece path,\n                                   const folly::StringPiece query,\n                                   const folly::StringPiece fragment) {\n  std::string url;\n  url.reserve(scheme.size() + authority.size() + path.size() + query.size() +\n              fragment.size() + 5); // 5 chars for ://,? and #\n  if (!scheme.empty()) {\n    folly::toAppend(scheme.str(), \"://\", &url);\n  }\n  folly::toAppend(authority, path, &url);\n  if (!query.empty()) {\n    folly::toAppend('?', query, &url);\n  }\n  if (!fragment.empty()) {\n    folly::toAppend('#', fragment, &url);\n  }\n  url.shrink_to_fit();\n  return url;\n}\n\nvoid HTTPMessage::splitNameValuePieces(\n    folly::StringPiece sp,\n    char pairDelim,\n    char valueDelim,\n    std::function<void(StringPiece, StringPiece)> callback) {\n\n  while (!sp.empty()) {\n    size_t pairDelimPos = sp.find(pairDelim);\n    StringPiece keyValue;\n\n    if (pairDelimPos == string::npos) {\n      keyValue = sp;\n      sp.advance(sp.size());\n    } else {\n      keyValue = sp.subpiece(0, pairDelimPos);\n      // Skip '&' char\n      sp.advance(pairDelimPos + 1);\n    }\n\n    if (keyValue.empty()) {\n      continue;\n    }\n\n    size_t valueDelimPos = keyValue.find(valueDelim);\n    if (valueDelimPos == string::npos) {\n      // Key only query param\n      callback(trim(keyValue), StringPiece());\n    } else {\n      auto name = keyValue.subpiece(0, valueDelimPos);\n      auto value = keyValue.subpiece(valueDelimPos + 1);\n      callback(trim(name), trim(value));\n    }\n  }\n}\n\nStringPiece HTTPMessage::trim(StringPiece sp) {\n  // TODO: use a library trim function?\n  for (; !sp.empty() && sp.front() == ' '; sp.pop_front()) {\n  }\n  for (; !sp.empty() && sp.back() == ' '; sp.pop_back()) {\n  }\n  return sp;\n}\n\nvoid HTTPMessage::splitNameValue(\n    folly::StringPiece input,\n    char pairDelim,\n    char valueDelim,\n    std::function<void(string&&, string&&)> callback) {\n\n  folly::StringPiece sp(input);\n  while (!sp.empty()) {\n    size_t pairDelimPos = sp.find(pairDelim);\n    folly::StringPiece keyValue;\n\n    if (pairDelimPos == string::npos) {\n      keyValue = sp;\n      sp.advance(sp.size());\n    } else {\n      keyValue = sp.subpiece(0, pairDelimPos);\n      // Skip '&' char\n      sp.advance(pairDelimPos + 1);\n    }\n\n    if (keyValue.empty()) {\n      continue;\n    }\n\n    size_t valueDelimPos = keyValue.find(valueDelim);\n    if (valueDelimPos == string::npos) {\n      // Key only query param\n      string name = folly::trimWhitespace(keyValue).str();\n      string value;\n\n      callback(std::move(name), std::move(value));\n    } else {\n      string name =\n          folly::trimWhitespace(keyValue.subpiece(0, valueDelimPos)).str();\n      string value =\n          folly::trimWhitespace(keyValue.subpiece(valueDelimPos + 1)).str();\n\n      callback(std::move(name), std::move(value));\n    }\n  }\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPMessage& msg) {\n  msg.describe(os);\n  return os;\n}\n\nvoid HTTPMessage::dumpMessage(int vlogLevel) const {\n  DVLOG(vlogLevel) << *this;\n}\n\nvoid HTTPMessage::describe(std::ostream& os) const {\n  os << \", chunked: \" << chunked_ << \", upgraded: \" << upgraded_\n     << \", scheme: \" << getScheme() << \", Fields for message:\" << std::endl;\n\n  // Common fields to both requests and responses.\n  std::vector<std::pair<const char*, folly::StringPiece>> fields{{\n      {\"local_ip\", localIP_},\n      {\"version\", versionStr_},\n      {\"dst_ip\", dstIP_},\n      {\"dst_port\", dstPort_},\n  }};\n\n  std::string pushStatusMessage;\n  if (isRequest()) {\n    // Request fields.\n    const Request& req = request();\n    pushStatusMessage = getPushStatusStr();\n    fields.insert(fields.end(),\n                  {{\"client_ip\",\n                    req.clientIPPort_ ? req.clientIPPort_->ip : empty_string},\n                   {\"client_port\",\n                    req.clientIPPort_ ? req.clientIPPort_->port : empty_string},\n                   {\"method\", getMethodString()},\n                   {\"path\", req.path_},\n                   {\"query\", req.query_},\n                   {\"url\", req.url_},\n                   {\"push_status\", pushStatusMessage}});\n\n  } else if (isResponse()) {\n    // Response fields.\n    const Response& resp = response();\n    fields.insert(\n        fields.end(),\n        {{\"status\", resp.statusStr_}, {\"status_msg\", resp.statusMsg_}});\n  }\n\n  for (auto& field : fields) {\n    if (!field.second.empty()) {\n      os << \" \" << field.first << \":\" << stripCntrlChars(field.second)\n         << std::endl;\n    }\n  }\n\n  // This little loop prints the headers and (if present) any per-hop headers\n  // that were stripped.  It executes at most twice.\n  bool first = true;\n  const HTTPHeaders* hdrs = &headers_;\n  while (hdrs) {\n    if (!first && hdrs->size() != 0) {\n      os << \"Per-Hop Headers\" << std::endl;\n    }\n    hdrs->forEach([&os](const string& h, const string& v) {\n      os << \" \" << stripCntrlChars(h) << \": \" << stripCntrlChars(v)\n         << std::endl;\n    });\n    if (first) {\n      hdrs = strippedPerHopHeaders_.get();\n      first = false;\n    } else {\n      hdrs = nullptr;\n    }\n  }\n}\n\nvoid HTTPMessage::atomicDumpMessage(int vlogLevel) const {\n  std::lock_guard<std::mutex> g(mutexDump_);\n  dumpMessage(vlogLevel);\n}\n\nvoid HTTPMessage::dumpMessageToSink(google::LogSink* logSink) const {\n  LOG_TO_SINK(logSink, INFO) << *this;\n}\n\nbool HTTPMessage::computeKeepalive() const {\n  if (version_.first < 1) {\n    return false;\n  }\n\n  // RFC 2616 isn't explicitly clear about whether \"close\" is case-sensitive.\n  // Section 2.1 states that literal tokens in the BNF are case-insensitive\n  // unless stated otherwise.  The \"close\" token isn't explicitly mentioned\n  // in the BNF, but other header fields such as the character set and\n  // content coding are explicitly called out as being case insensitive.\n  //\n  // We'll treat the \"close\" token case-insensitively.  This is the most\n  // conservative approach, since disabling keepalive when it was requested\n  // is better than enabling keepalive for a client that didn't expect it.\n  //\n  // Note that we only perform ASCII lowering here.  This is good enough,\n  // since the token we are looking for is ASCII.\n  if (checkForHeaderToken(HTTP_HEADER_CONNECTION, \"close\", false)) {\n    // The Connection header contained a \"close\" token, so keepalive\n    // is disabled.\n    return false;\n  }\n\n  const std::string kKeepAliveConnToken = \"keep-alive\";\n  if (version_ == kHTTPVersion10) {\n    // HTTP 1.0 persistent connections require a Connection: Keep-Alive\n    // header to be present for the connection to be persistent.\n    if (checkForHeaderToken(\n            HTTP_HEADER_CONNECTION, kKeepAliveConnToken.c_str(), false) ||\n        (strippedPerHopHeaders_ &&\n         doHeaderTokenCheck(*strippedPerHopHeaders_,\n                            HTTP_HEADER_CONNECTION,\n                            kKeepAliveConnToken.c_str(),\n                            false))) {\n      return true;\n    }\n    return false;\n  }\n\n  // It's a keepalive connection.\n  return true;\n}\n\nbool HTTPMessage::checkForHeaderToken(const HTTPHeaderCode headerCode,\n                                      char const* token,\n                                      bool caseSensitive) const {\n  return doHeaderTokenCheck(headers_, headerCode, token, caseSensitive);\n}\n\nbool HTTPMessage::doHeaderTokenCheck(const HTTPHeaders& headers,\n                                     const HTTPHeaderCode headerCode,\n                                     char const* token,\n                                     bool caseSensitive) const {\n  return headers.forEachValueOfHeader(headerCode, [&](const string& value) {\n    std::vector<folly::StringPiece> tokens;\n    folly::split(',', value, tokens);\n    for (auto t : tokens) {\n      t = trim(t);\n      if (caseSensitive) {\n        if (t == token) {\n          return true;\n        }\n      } else if (caseInsensitiveEqual(t, token)) {\n        return true;\n      }\n    }\n    return false;\n  });\n}\n\nconst char* HTTPMessage::getDefaultReason(uint16_t status) {\n  switch (status) {\n    case 100:\n      return \"Continue\";\n    case 101:\n      return \"Switching Protocols\";\n    case 200:\n      return \"OK\";\n    case 201:\n      return \"Created\";\n    case 202:\n      return \"Accepted\";\n    case 203:\n      return \"Non-Authoritative Information\";\n    case 204:\n      return \"No Content\";\n    case 205:\n      return \"Reset Content\";\n    case 206:\n      return \"Partial Content\";\n    case 300:\n      return \"Multiple Choices\";\n    case 301:\n      return \"Moved Permanently\";\n    case 302:\n      return \"Found\";\n    case 303:\n      return \"See Other\";\n    case 304:\n      return \"Not Modified\";\n    case 305:\n      return \"Use Proxy\";\n    case 307:\n      return \"Temporary Redirect\";\n    case 400:\n      return \"Bad Request\";\n    case 401:\n      return \"Unauthorized\";\n    case 402:\n      return \"Payment Required\";\n    case 403:\n      return \"Forbidden\";\n    case 404:\n      return \"Not Found\";\n    case 405:\n      return \"Method Not Allowed\";\n    case 406:\n      return \"Not Acceptable\";\n    case 407:\n      return \"Proxy Authentication Required\";\n    case 408:\n      return \"Request Timeout\";\n    case 409:\n      return \"Conflict\";\n    case 410:\n      return \"Gone\";\n    case 411:\n      return \"Length Required\";\n    case 412:\n      return \"Precondition Failed\";\n    case 413:\n      return \"Request Entity Too Large\";\n    case 414:\n      return \"Request-URI Too Long\";\n    case 415:\n      return \"Unsupported Media Type\";\n    case 416:\n      return \"Requested Range Not Satisfiable\";\n    case 417:\n      return \"Expectation Failed\";\n    case 418:\n      return \"I'm a teapot\";\n    case 426:\n      return \"Upgrade Required\";\n    case 428:\n      return \"Precondition Required\";\n    case 429:\n      return \"Too Many Requests\";\n    case 431:\n      return \"Request Header Fields Too Large\";\n    case 451:\n      return \"Unavailable For Legal Reasons\";\n    case 500:\n      return \"Internal Server Error\";\n    case 501:\n      return \"Not Implemented\";\n    case 502:\n      return \"Bad Gateway\";\n    case 503:\n      return \"Service Unavailable\";\n    case 504:\n      return \"Gateway Timeout\";\n    case 505:\n      return \"HTTP Version Not Supported\";\n  }\n\n  // Note: Some Microsoft clients behave badly if the reason string\n  // is left empty.  Therefore return a non-empty string here.\n  return \"-\";\n}\n\nParseURL HTTPMessage::setURLImplInternal(bool unparse, bool strict) {\n  auto& req = request();\n  auto u = ParseURL::parseURLMaybeInvalid(req.url_, strict);\n  if (u.valid()) {\n    DVLOG(9) << \"set path: \" << u.path() << \" query:\" << u.query();\n    req.path_ = u.path();\n    req.query_ = u.query();\n    if (req.path_.empty()) {\n      req.path_.reset(\"/\", 1);\n    }\n  } else {\n    DVLOG(4) << \"Error in parsing URL: \" << req.url_;\n    req.path_.clear();\n    req.query_.clear();\n  }\n  req.pathStr_.reset();\n  req.queryStr_.reset();\n  if (unparse) {\n    unparseQueryParams();\n  }\n  return u;\n}\n\nvoid HTTPMessage::setHTTPPriority(uint8_t urgency, bool incremental) {\n  headers_.set(HTTP_HEADER_PRIORITY,\n               httpPriorityToString(HTTPPriority(urgency, incremental)));\n}\n\nvoid HTTPMessage::setHTTPPriority(HTTPPriority httpPriority) {\n  headers_.set(HTTP_HEADER_PRIORITY, httpPriorityToString(httpPriority));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMessage.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <folly/Optional.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/IOBufQueue.h>\n#include <glog/logging.h>\n#include <map>\n#include <mutex>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <proxygen/lib/http/HTTPMethod.h>\n#include <proxygen/lib/http/HeaderConstants.h>\n#include <proxygen/lib/http/Types.h>\n#include <proxygen/lib/utils/ParseURL.h>\n#include <proxygen/lib/utils/Time.h>\n#include <string>\n#include <variant>\n\nnamespace proxygen {\n\n// Default urgency = 3 is from the draft. It leaves space for both higher and\n// lower urgency level which is good. We default to Incremental = True as\n// opposed to False. This is because our transport layer has been behaving\n// like that before the HTTP priority support is introduced.\nconstexpr uint8_t kDefaultHttpPriorityUrgency = 3;\n// We default incremental to True, different from the draft\nconstexpr bool kDefaultHttpPriorityIncremental = true;\nconstexpr uint64_t kDefaultOrderId = 0;\nconstexpr bool kDefaultPaused = false;\nconstexpr int8_t kMinPriority = 0;\nconstexpr int8_t kMaxPriority = 7;\n\nstruct HTTPPriority {\n  uint8_t urgency : 3;\n  bool incremental : 1;\n  uint32_t orderId;\n  bool paused : 1;\n\n  HTTPPriority()\n      : urgency(kDefaultHttpPriorityUrgency),\n        incremental(kDefaultHttpPriorityIncremental),\n        orderId(kDefaultOrderId),\n        paused(kDefaultPaused) {\n  }\n\n  HTTPPriority(uint8_t urgencyIn,\n               bool incrementalIn,\n               uint32_t orderIdIn = kDefaultOrderId,\n               bool pausedIn = kDefaultPaused)\n      : urgency(std::min(urgencyIn, static_cast<uint8_t>(kMaxPriority))),\n        incremental(incrementalIn),\n        orderId(orderIdIn),\n        paused(pausedIn) {\n  }\n};\n\ninline bool operator==(const HTTPPriority& a, const HTTPPriority& b) {\n  return a.urgency == b.urgency && a.incremental == b.incremental &&\n         a.orderId == b.orderId && a.paused == b.paused;\n}\n\n// Convert Priority to a string representation in the form of \"u=urgency[,i]\"\nstd::string httpPriorityToString(const HTTPPriority& priority);\n\nclass HTTPMessage;\n\nfolly::Optional<HTTPPriority> httpPriorityFromHTTPMessage(\n    const HTTPMessage& message);\n\n/**\n * An HTTP request or response minus the body.\n *\n * Some of the methods on this class will assert if called from the wrong\n * context since they only make sense for a request or response. Make sure\n * you know what type of HTTPMessage this is before calling such methods.\n *\n * All header names stored in this class are case-insensitive.\n */\nclass HTTPMessage {\n public:\n  enum WebSocketUpgrade {\n    NONE,\n    INGRESS,\n    EGRESS,\n  };\n\n  enum class Scheme {\n    HTTP,\n    HTTPS,\n    MASQUE,\n  };\n\n  HTTPMessage();\n  ~HTTPMessage();\n  HTTPMessage(HTTPMessage&& message) noexcept;\n  HTTPMessage(const HTTPMessage& message);\n  HTTPMessage& operator=(const HTTPMessage& message);\n  HTTPMessage& operator=(HTTPMessage&& message);\n\n  // upgradeWebsocket_ can have three states, WebSocketUpgrade::NONE by\n  // default. WebSocketUpgrade::INGRESS is used by the codec to indicate a\n  // websocket upgrade request was received from downstream or a successful\n  // upgrade finished on an upstream stream.\n  // WebSocketUpgrade::EGRESS is used by the application handler to indicate\n  // websocket upgrade headers should be sent with the outgoing\n  // request/response. Based on upstream/downstream, the codec serializes the\n  // appropriate headers.\n  void setIngressWebsocketUpgrade() {\n    upgradeWebsocket_ = WebSocketUpgrade::INGRESS;\n  }\n  void setEgressWebsocketUpgrade() {\n    upgradeWebsocket_ = WebSocketUpgrade::EGRESS;\n  }\n  bool isIngressWebsocketUpgrade() const {\n    return upgradeWebsocket_ == WebSocketUpgrade::INGRESS;\n  }\n  bool isEgressWebsocketUpgrade() const {\n    return upgradeWebsocket_ == WebSocketUpgrade::EGRESS;\n  }\n\n  /**\n   * Is this a chunked message? (fpreq, fpresp)\n   */\n  void setIsChunked(bool chunked) {\n    chunked_ = chunked;\n  }\n  bool getIsChunked() const {\n    return chunked_;\n  }\n\n  /**\n   * Is this an upgraded message? (fpreq, fpresp)\n   */\n  void setIsUpgraded(bool upgraded) {\n    upgraded_ = upgraded;\n  }\n  bool getIsUpgraded() const {\n    return upgraded_;\n  }\n\n  /**\n   * Set/Get client address\n   */\n  void setClientAddress(const folly::SocketAddress& addr,\n                        std::string ipStr = empty_string,\n                        std::string portStr = empty_string) {\n    auto& req = request();\n    req.clientAddress_ = addr;\n    if (!ipStr.empty() && !portStr.empty()) {\n      req.clientIPPort_.emplace(std::move(ipStr), std::move(portStr));\n    } else {\n      if (req.clientIPPort_) {\n        req.clientIPPort_.reset();\n      }\n    }\n  }\n\n  const folly::SocketAddress& getClientAddress() const {\n    return request().clientAddress_;\n  }\n\n  const std::string& getClientIP() const {\n    auto& req = request();\n    if (!req.clientIPPort_ || req.clientIPPort_->ip.empty()) {\n      if (req.clientAddress_.isInitialized()) {\n        req.clientIPPort_.emplace(\n            req.clientAddress_.getAddressStr(),\n            folly::to<std::string>(req.clientAddress_.getPort()));\n      } else {\n        return empty_string;\n      }\n    }\n    return req.clientIPPort_->ip;\n  }\n\n  const std::string& getClientPort() const {\n    auto& req = request();\n    if (!req.clientIPPort_ || req.clientIPPort_->port.empty()) {\n      if (req.clientAddress_.isInitialized()) {\n        req.clientIPPort_.emplace(\n            req.clientAddress_.getAddressStr(),\n            folly::to<std::string>(req.clientAddress_.getPort()));\n      } else {\n        return empty_string;\n      }\n    }\n    return req.clientIPPort_->port;\n  }\n\n  /**\n   * Set/Get destination (vip) address\n   */\n  void setDstAddress(const folly::SocketAddress& addr,\n                     std::string addressStr = empty_string,\n                     std::string portStr = empty_string) {\n    dstAddress_ = addr;\n    if (!addressStr.empty() && !portStr.empty()) {\n      dstIP_ = std::move(addressStr);\n      dstPort_ = std::move(portStr);\n    } else {\n      dstIP_.clear();\n      dstPort_.clear();\n    }\n  }\n\n  const folly::SocketAddress& getDstAddress() const {\n    return dstAddress_;\n  }\n\n  const std::string& getDstIP() const {\n    if (dstIP_.empty() && dstAddress_.isInitialized()) {\n      dstIP_ = dstAddress_.getAddressStr();\n    }\n    return dstIP_;\n  }\n\n  const std::string& getDstPort() const {\n    if (dstPort_.empty() && dstAddress_.isInitialized()) {\n      dstPort_ = folly::to<std::string>(dstAddress_.getPort());\n    }\n    return dstPort_;\n  }\n\n  /**\n   * Set/Get the local IP address\n   */\n  template <typename T> // T = string\n  void setLocalIp(T&& ip) {\n    localIP_ = std::forward<T>(ip);\n  }\n  const std::string& getLocalIp() const {\n    return localIP_;\n  }\n\n  /**\n   * Access the method (fpreq)\n   */\n  void setMethod(HTTPMethod method);\n  void setMethod(folly::StringPiece method);\n  void rawSetMethod(const std::string& method) {\n    setMethod(method);\n  }\n\n  /**\n   * @Returns an HTTPMethod enum value representing the method if it is a\n   * standard request method, or else \"none\" if it is an extension method\n   * (fpreq)\n   */\n  folly::Optional<HTTPMethod> getMethod() const;\n\n  /**\n   * @Returns a string representation of the request method (fpreq)\n   */\n  const std::string& getMethodString() const;\n\n  /**\n   * Access the URL component (fpreq)\n   *\n   * The <url> component from the initial \"METHOD <url> HTTP/...\" line. When\n   * valid, this is a full URL, not just a path.\n   */\n  template <typename T> // T = string\n  ParseURL setURL(T&& url, bool strict = false) {\n    return setURLImpl(std::forward<T>(url), true, strict);\n  }\n\n  // The template function above doesn't work with char*,\n  // so explicitly convert to a string first.\n  ParseURL setURL(const char* url, bool strict = false) {\n    return setURL(std::string(url), strict);\n  }\n  const std::string& getURL() const {\n    return request().url_;\n  }\n  void rawSetURL(const std::string& url) {\n    setURL(url);\n  }\n\n  /**\n   * Access the path component (fpreq)\n   *\n   * getPath will lazily allocate a string object, which is generally\n   * more expensive.  Prefer getPathAsStringPiece.\n   */\n  const std::string& getPath() const {\n    auto& req = request();\n    if (!req.pathStr_) {\n      req.pathStr_ =\n          std::make_unique<std::string>(req.path_.data(), req.path_.size());\n    }\n    return *req.pathStr_;\n  }\n\n  folly::StringPiece getPathAsStringPiece() const {\n    return request().path_;\n  }\n\n  /**\n   * Access the query component (fpreq)\n   *\n   * getQueryString will lazily allocate a string object, which is generally\n   * more expensive.  Prefer getQueryStringAsStringPiece.\n   */\n  const std::string& getQueryString() const {\n    auto& req = request();\n    if (!req.queryStr_) {\n      req.queryStr_ =\n          std::make_unique<std::string>(req.query_.data(), req.query_.size());\n    }\n    return *req.queryStr_;\n  }\n\n  folly::StringPiece getQueryStringAsStringPiece() const {\n    return request().query_;\n  }\n\n  /**\n   * Version constants\n   */\n  static const std::pair<uint8_t, uint8_t> kHTTPVersion09;\n  static const std::pair<uint8_t, uint8_t> kHTTPVersion10;\n  static const std::pair<uint8_t, uint8_t> kHTTPVersion11;\n\n  /**\n   * Access the HTTP version number (fpreq, fpres)\n   */\n  void setHTTPVersion(uint8_t major, uint8_t minor);\n  const std::pair<uint8_t, uint8_t>& getHTTPVersion() const;\n\n  /**\n   * Access the HTTP status message string (res)\n   */\n  template <typename T> // T = string\n  void setStatusMessage(T&& msg) {\n    response().statusMsg_ = std::forward<T>(msg);\n  }\n  const std::string& getStatusMessage() const {\n    return response().statusMsg_;\n  }\n  void rawSetStatusMessage(std::string msg) {\n    setStatusMessage(msg);\n  }\n\n  /**\n   * Get/Set the HTTP version string (like \"1.1\").\n   * XXX: Note we only support X.Y format while setting version.\n   */\n  const std::string& getVersionString() const {\n    return versionStr_;\n  }\n  void setVersionString(const std::string& ver) {\n    if (ver.size() != 3 || ver[1] != '.' || !isdigit(ver[0]) ||\n        !isdigit(ver[2])) {\n      return;\n    }\n\n    setHTTPVersion(ver[0] - '0', ver[2] - '0');\n  }\n\n  /**\n   * Access the headers (fpreq, fpres)\n   */\n  HTTPHeaders& getHeaders() {\n    return headers_;\n  }\n  const HTTPHeaders& getHeaders() const {\n    return headers_;\n  }\n\n  /**\n   * Move headers out of current message (returns rvalue ref)\n   */\n  HTTPHeaders&& extractHeaders() {\n    return std::move(headers_);\n  }\n\n  /**\n   * Access the trailers\n   */\n  HTTPHeaders* getTrailers() {\n    return trailers_.get();\n  }\n  const HTTPHeaders* getTrailers() const {\n    return trailers_.get();\n  }\n\n  /**\n   * Set the trailers, replacing any that might already be present\n   */\n  void setTrailers(std::unique_ptr<HTTPHeaders>&& trailers) {\n    trailers_ = std::move(trailers);\n  }\n\n  /**\n   * Move trailers out of current message\n   */\n  std::unique_ptr<HTTPHeaders> extractTrailers() {\n    return std::move(trailers_);\n  }\n\n  /**\n   * Decrements Max-Forwards header, when present on OPTIONS or TRACE methods.\n   *\n   * Returns HTTP status code.\n   */\n  int processMaxForwards();\n\n  /**\n   * Returns true if the version of this message is HTTP/1.0\n   */\n  bool isHTTP1_0() const;\n\n  /**\n   * Returns true if the version of this message is HTTP/1.1\n   */\n  bool isHTTP1_1() const;\n\n  /**\n   * Returns true if these are final headers.\n   */\n  bool isFinal() const {\n    return (isRequest() || !is1xxResponse() || getStatusCode() == 101);\n  }\n\n  /**\n   * Returns true if this is a 1xx response.\n   */\n  bool is1xxResponse() const {\n    return (getStatusCode() / 100) == 1;\n  }\n\n  bool is2xxResponse() const {\n    return (getStatusCode() / 100) == 2;\n  }\n\n  /**\n   * Returns true if this is a 4xx response.\n   */\n  bool is4xxResponse() const {\n    return (getStatusCode() / 100) == 4;\n  }\n\n  /**\n   * Returns true if this is a 5xx response.\n   */\n  bool is5xxResponse() const {\n    return (getStatusCode() / 100) == 5;\n  }\n\n  /**\n   * Formats the current time appropriately for a Date header\n   */\n  static std::string formatDateHeader();\n\n  /**\n   * Ensures this HTTPMessage contains a host header, adding a default one\n   * with the destination address if necessary.\n   */\n  void ensureHostHeader();\n\n  /**\n   * Indicates if this request wants the connection to be kept-alive\n   * (default true).  Not all codecs respect this option.\n   */\n  void setWantsKeepalive(bool wantsKeepaliveVal) {\n    wantsKeepalive_ = wantsKeepaliveVal;\n  }\n  bool wantsKeepalive() const {\n    return wantsKeepalive_;\n  }\n\n  /**\n   * Returns true if trailers are allowed on this message.  Trailers\n   * are not allowed on responses unless the client is expecting them.\n   */\n  bool trailersAllowed() const {\n    return trailersAllowed_;\n  }\n  /**\n   * Accessor to set whether trailers are allowed in the response\n   */\n  void setTrailersAllowed(bool trailersAllowedVal) {\n    trailersAllowed_ = trailersAllowedVal;\n  }\n\n  /**\n   * Returns true if this message has trailers that need to be serialized\n   */\n  bool hasTrailers() const {\n    return trailersAllowed_ && trailers_ && trailers_->size() > 0;\n  }\n\n  /**\n   * Access the status code (fpres)\n   */\n  void setStatusCode(uint16_t status);\n  uint16_t getStatusCode() const;\n\n  void setUpgradeProtocol(std::string protocol) {\n    upgradeProtocol_ = std::make_unique<std::string>(std::move(protocol));\n  }\n  const std::string* getUpgradeProtocol() const {\n    return upgradeProtocol_.get();\n  }\n\n  /**\n   * Access the push status code\n   */\n  void setPushStatusCode(const uint16_t status);\n  std::string getPushStatusStr() const;\n  uint16_t getPushStatusCode() const;\n\n  /**\n   * Fill in the fields for a response message header that the server will\n   * send directly to the client.\n   *\n   * @param version           HTTP version (major, minor)\n   * @param statusCode        HTTP status code to respond with\n   * @param msg               textual message to embed in \"message\" status field\n   * @param contentLength     the length of the data to be written out through\n   *                          this message\n   */\n  void constructDirectResponse(const std::pair<uint8_t, uint8_t>& version,\n                               const int statusCode,\n                               const std::string& statusMsg,\n                               int contentLength = 0);\n\n  /**\n   * Fill in the fields for a response message header that the server will\n   * send directly to the client. This function assumes the status code and\n   * status message have already been set on this HTTPMessage object\n   *\n   * @param version           HTTP version (major, minor)\n   * @param contentLength     the length of the data to be written out through\n   *                          this message\n   */\n  void constructDirectResponse(const std::pair<uint8_t, uint8_t>& version,\n                               int contentLength = 0);\n\n  /**\n   * Check if query parameter with the specified name exists.\n   */\n  bool hasQueryParam(const std::string& name) const;\n\n  /**\n   * Get the query parameter with the specified name.\n   *\n   * Returns a pointer to the query parameter value, or nullptr if there is no\n   * parameter with the specified name.  The returned value is only valid as\n   * long as this HTTPMessage object.\n   */\n  const std::string* getQueryParamPtr(const std::string& name) const;\n\n  /**\n   * Get the query parameter with the specified name.\n   *\n   * Returns a reference to the query parameter value, or\n   * proxygen::empty_string if there is no parameter with the\n   * specified name.  The returned value is only valid as long as this\n   * HTTPMessage object.\n   */\n  const std::string& getQueryParam(const std::string& name) const;\n\n  /**\n   * Get the query parameter with the specified name as int.\n   *\n   * If the conversion fails, throws exception.\n   */\n  int getIntQueryParam(const std::string& name) const;\n\n  /**\n   * Get the query parameter with the specified name as int.\n   *\n   * Returns the query parameter if it can be parsed as int otherwise the\n   * default value.\n   */\n  int getIntQueryParam(const std::string& name, int defval) const;\n\n  /**\n   * Get the query parameter with the specified name after percent decoding.\n   *\n   * Returns empty string if parameter is missing or folly::uriUnescape\n   * query param\n   */\n  std::string getDecodedQueryParam(const std::string& name) const;\n\n  /**\n   * Get all the query parameters.\n   *\n   * Returns a reference to the query parameters map.  The returned\n   * value is only valid as long as this\n   * HTTPMessage object.\n   */\n  const HTTPQueryParamMap& getQueryParams() const;\n\n  /**\n   * Set the query string to the specified value, and recreate the url_.\n   *\n   * Returns true if the query string was changed successfully.\n   */\n  bool setQueryString(const std::string& query, bool strict = false);\n\n  /**\n   * Remove the query parameter with the specified name.\n   *\n   * Returns true if the query parameter was present and deleted.\n   */\n  bool removeQueryParam(const std::string& name);\n\n  /**\n   * Sets the query parameter with the specified name to the specified value.\n   *\n   * Returns true if the query parameter was successfully set.\n   */\n  bool setQueryParam(const std::string& name,\n                     const std::string& value,\n                     bool strict = false);\n\n  /**\n   * Get the cookie with the specified name.\n   *\n   * Returns a StringPiece to the cookie value, or an empty StringPiece if\n   * there is no cookie with the specified name.  The returned cookie is\n   * only valid as long as the Cookie Header in HTTPMessage object exists.\n   * Applications should make sure they call unparseCookies() when editing\n   * the Cookie Header, so that the StringPiece references are cleared.\n   */\n  const folly::StringPiece getCookie(const std::string& name) const;\n\n  /**\n   * Remove the cookie with the specified name.\n   */\n  void removeCookie(const std::string& name);\n\n  /**\n   * Print the message out.\n   */\n  void dumpMessage(int verbosity) const;\n\n  void describe(std::ostream& os) const;\n\n  /**\n   * Print the message out, serializes through mutex\n   * Used in shutdown path\n   */\n  void atomicDumpMessage(int verbosity) const;\n\n  /**\n   * Print the message out to logSink.\n   */\n  void dumpMessageToSink(google::LogSink* logSink) const;\n\n  /**\n   * Interact with headers that are defined to be per-hop.\n   *\n   * It is expected that during request processing, stripPerHopHeaders() will\n   * be called before the message is proxied to the other connection.\n   */\n  void stripPerHopHeaders(bool stripPriority = false,\n                          const HTTPHeaders* customHeaders = nullptr);\n\n  const HTTPHeaders& getStrippedPerHopHeaders() const {\n    CHECK(strippedPerHopHeaders_) << \"call stripPerHopHeaders first\";\n    return *strippedPerHopHeaders_;\n  }\n\n  void setSecure(bool secure) {\n    if (secure && scheme_ != Scheme::MASQUE) {\n      scheme_ = Scheme::HTTPS;\n    } else if (!secure) {\n      scheme_ = Scheme::HTTP;\n    }\n  }\n\n  bool isSecure() const {\n    return (scheme_ == Scheme::HTTPS || scheme_ == Scheme::MASQUE);\n  }\n\n  void setMasque() {\n    scheme_ = Scheme::MASQUE;\n  }\n\n  bool isMasque() const {\n    return scheme_ == Scheme::MASQUE;\n  }\n\n  const std::string& getScheme() const {\n    switch (scheme_) {\n      case HTTPMessage::Scheme::HTTP:\n        return headers::kHttp;\n      case HTTPMessage::Scheme::HTTPS:\n        return headers::kHttps;\n      case HTTPMessage::Scheme::MASQUE:\n        return headers::kMasque;\n      default:\n        break;\n    }\n    return headers::kHttp;\n  }\n\n  int getSecureVersion() const {\n    return sslVersion_;\n  }\n\n  const char* getSecureCipher() const {\n    return sslCipher_;\n  }\n\n  void setSecureInfo(int ver, const char* cipher) {\n    // cipher is a static const char* provided and managed by openssl lib\n    sslVersion_ = ver;\n    sslCipher_ = cipher;\n  }\n\n  void setAdvancedProtocolString(const std::string& protocol) {\n    protoStr_ = &protocol;\n  }\n\n  bool isAdvancedProto() const {\n    return protoStr_ != nullptr;\n  }\n\n  const std::string* getAdvancedProtocolString() const {\n    return protoStr_;\n  }\n\n  /**\n   * Return the protocol string used by this HTTPMessage.\n   *\n   * If this HTTP message is using an advanced protocol, the protocol string\n   * will be the advanced protocol. If not, it will simply be the HTTP version.\n   */\n  const std::string& getProtocolString() const {\n    if (isAdvancedProto()) {\n      return *protoStr_;\n    }\n\n    return versionStr_;\n  }\n\n  /* Setter and getter for the SPDY priority value (0 - 7).  When serialized\n   * to SPDY/2, Codecs will collpase 0,1 -> 0, 2,3 -> 1, etc.\n   *\n   * Negative values of pri are interpreted much like negative array\n   * indexes in python, so -1 will be the largest numerical priority\n   * value for this SPDY version (i.e. 3 for SPDY/2 or 7 for SPDY/3),\n   * -2 the second largest (i.e. 2 for SPDY/2 or 6 for SPDY/3).\n   */\n  static uint8_t normalizePriority(int8_t pri) {\n    if (pri > kMaxPriority || pri < -kMaxPriority) {\n      // outside [-7, 7] => highest priority\n      return kMaxPriority;\n    } else if (pri < 0) {\n      return pri + kMaxPriority + 1;\n    }\n    return pri;\n  }\n\n  void setPriority(int8_t pri) {\n    pri_ = normalizePriority(pri);\n  }\n  uint8_t getPriority() const {\n    return pri_;\n  }\n\n  /**\n   * Set a Priority header on the HTTPMessage by urgency and incremental.\n   */\n  void setHTTPPriority(uint8_t urgency, bool incremental);\n\n  folly::Optional<HTTPPriority> getHTTPPriority() const noexcept {\n    return httpPriorityFromHTTPMessage(*this);\n  }\n\n  /**\n   * Set a Priority header on the HTTPMessage by httpPriority.\n   *\n   * There is no getter of HTTPPriority in a HTTPMessage.\n   * Use httpPriorityFromHTTPMessage for that.\n   */\n  void setHTTPPriority(HTTPPriority httpPriority);\n\n  using HTTP2Priority = std::tuple<uint64_t, bool, uint8_t>;\n\n  /**\n   * get and setter for transaction sequence number\n   */\n  void setSeqNo(int32_t seqNo) {\n    seqNo_ = seqNo;\n  }\n  int32_t getSeqNo() const {\n    return seqNo_;\n  }\n\n  /**\n   * getter and setter for size in serialized form\n   */\n  void setIngressHeaderSize(const HTTPHeaderSize& size) {\n    size_ = size;\n  }\n  const HTTPHeaderSize& getIngressHeaderSize() const {\n    return size_;\n  }\n\n  /**\n   * Getter and setter for the time when the first byte of the message arrived\n   */\n  TimePoint getStartTime() const {\n    return startTime_;\n  }\n  void setStartTime(const TimePoint& startTime) {\n    startTime_ = startTime;\n  }\n\n  /**\n   * Check if a particular token value is present in a header that consists of\n   * a list of comma separated tokens.  (e.g., a header with a #rule\n   * body as specified in the RFC 2616 BNF notation.)\n   */\n  bool checkForHeaderToken(const HTTPHeaderCode headerCode,\n                           char const* token,\n                           bool caseSensitive) const;\n\n  /**\n   * Forget about the parsed cookies.\n   *\n   * Ideally HTTPMessage should automatically forget about the current parsed\n   * cookie state whenever a Cookie header is changed.  However, at the moment\n   * callers have to explicitly call unparseCookies() after modifying the\n   * cookie headers.\n   */\n  void unparseCookies() const;\n\n  /**\n   * Get the default reason string for a status code.\n   *\n   * This returns the reason string for the specified status code as specified\n   * in RFC 2616.  For unknown status codes, the string \"-\" is returned.\n   */\n  static const char* getDefaultReason(uint16_t status);\n\n  /**\n   * Computes whether the state of this message is compatible with keepalive.\n   * Changing the headers, version, etc can change the result.\n   */\n  bool computeKeepalive() const;\n\n  /**\n   * @returns true if this HTTPMessage represents an HTTP request\n   */\n  bool isRequest() const {\n    return fields_.which_ == MessageType::REQUEST;\n  }\n\n  /**\n   * @returns true if this HTTPMessage represents an HTTP response\n   */\n  bool isResponse() const {\n    return fields_.which_ == MessageType::RESPONSE;\n  }\n\n  /**\n   * Assuming input contains\n   * <name><valueDelim><value>(<pairDelim><name><valueDelim><value>),\n   * invoke callback once with each name,value pair.\n   */\n  static void splitNameValuePieces(\n      folly::StringPiece input,\n      char pairDelim,\n      char valueDelim,\n      std::function<void(folly::StringPiece, folly::StringPiece)> callback);\n\n  static void splitNameValue(\n      folly::StringPiece input,\n      char pairDelim,\n      char valueDelim,\n      std::function<void(std::string&&, std::string&&)> callback);\n\n  /**\n   * Form the URL from the individual components.\n   * url -> {scheme}://{authority}{path}?{query}#{fragment}\n   */\n  static std::string createUrl(const folly::StringPiece scheme,\n                               const folly::StringPiece authority,\n                               const folly::StringPiece path,\n                               const folly::StringPiece query,\n                               const folly::StringPiece fragment);\n\n  /**\n   * Create a query string from the query parameters map\n   * containing the name-value pairs.\n   */\n  static std::string createQueryString(const HTTPQueryParamMap& params,\n                                       uint32_t maxSize);\n\n protected:\n  // Message start time, in msec since the epoch.\n  TimePoint startTime_;\n\n private:\n  void parseCookies() const;\n\n  template <typename T> // T = string\n  ParseURL setURLImpl(T&& url, bool unparse, bool strict) {\n    DVLOG(9) << \"setURL: \" << std::forward<T>(url);\n\n    // Set the URL, path, and query string parameters\n    request().url_ = std::forward<T>(url);\n    return setURLImplInternal(unparse, strict);\n  }\n\n  ParseURL setURLImplInternal(bool unparse, bool strict);\n\n  bool setQueryStringImpl(const std::string& queryString,\n                          bool unparse,\n                          bool strict);\n  void parseQueryParams() const;\n  void unparseQueryParams();\n\n  bool doHeaderTokenCheck(const HTTPHeaders& headers_,\n                          const HTTPHeaderCode headerCode,\n                          char const* token,\n                          bool caseSensitive) const;\n\n  /**\n   * Trims whitespace from the beggining and end of the StringPiece.\n   */\n  static folly::StringPiece trim(folly::StringPiece sp);\n\n  /** The 12 standard fields for HTTP messages. Use accessors.\n   * An HTTPMessage is either a Request or Response.\n   * Once an accessor for either is used, that fixes the type of HTTPMessage.\n   * If an access is then used for the other type, a DCHECK will fail.\n   */\n  struct IPPort {\n    std::string ip;\n    std::string port;\n    IPPort(std::string inIp, std::string inPort)\n        : ip(std::move(inIp)), port(std::move(inPort)) {\n    }\n  };\n  struct Request {\n    folly::SocketAddress clientAddress_;\n    mutable folly::Optional<IPPort> clientIPPort_;\n    mutable std::\n        variant<std::monostate, std::unique_ptr<std::string>, HTTPMethod>\n            method_;\n    folly::StringPiece path_;\n    folly::StringPiece query_;\n    mutable std::unique_ptr<std::string> pathStr_;\n    mutable std::unique_ptr<std::string> queryStr_;\n    std::string url_;\n\n    uint16_t pushStatus_;\n\n    Request() = default;\n\n    Request(const Request& req)\n        : clientIPPort_(req.clientIPPort_),\n          path_(req.path_),\n          query_(req.query_),\n          pathStr_(nullptr),\n          queryStr_(nullptr),\n          url_(req.url_),\n          pushStatus_(req.pushStatus_) {\n      if (req.method_.index() == 1) {\n        method_ = std::make_unique<std::string>(\n            *std::get<std::unique_ptr<std::string>>(req.method_));\n      } else if (req.method_.index() == 2) {\n        method_ = std::get<HTTPMethod>(req.method_);\n      }\n    }\n  };\n\n  struct Response {\n    uint16_t status_;\n    std::string statusStr_;\n    std::string statusMsg_;\n  };\n\n  folly::SocketAddress dstAddress_;\n  mutable std::string dstIP_;\n  mutable std::string dstPort_;\n\n  std::string localIP_;\n  std::string versionStr_;\n\n  enum class MessageType : uint8_t { NONE = 0, REQUEST = 1, RESPONSE = 2 };\n  struct Fields {\n    Fields() = default;\n    Fields(const Fields& other) {\n      copyFrom(other);\n    }\n\n    Fields& operator=(const Fields& other) {\n      clear();\n      copyFrom(other);\n      return *this;\n    }\n\n    ~Fields() {\n      clear();\n    }\n\n    void clear() {\n      switch (which_) {\n        case MessageType::REQUEST:\n          data_.request.~Request();\n          break;\n        case MessageType::RESPONSE:\n          data_.response.~Response();\n          break;\n        case MessageType::NONE:\n          break;\n        default:\n          break;\n      }\n      which_ = MessageType::NONE;\n    }\n\n    void copyFrom(const Fields& other) {\n      which_ = other.which_;\n      switch (which_) {\n        case MessageType::REQUEST:\n          new (&data_.request) Request(other.data_.request);\n          break;\n        case MessageType::RESPONSE:\n          new (&data_.response) Response(other.data_.response);\n          break;\n        case MessageType::NONE:\n          break;\n        default:\n          break;\n      }\n    }\n\n    Fields(Fields&& other) {\n      moveFrom(std::move(other));\n    }\n\n    Fields& operator=(Fields&& other) {\n      clear();\n      moveFrom(std::move(other));\n      return *this;\n    }\n\n    void moveFrom(Fields&& other) {\n      which_ = other.which_;\n      switch (which_) {\n        case MessageType::REQUEST:\n          new (&data_.request) Request(std::move(other.data_.request));\n          break;\n        case MessageType::RESPONSE:\n          new (&data_.response) Response(std::move(other.data_.response));\n          break;\n        case MessageType::NONE:\n          break;\n        default:\n          break;\n      }\n    }\n\n    mutable MessageType which_{MessageType::NONE};\n    mutable union Data {\n      Data() {\n      }\n      ~Data() {\n      }\n      Request request;\n      Response response;\n    } data_;\n  } fields_;\n\n  Request& request() {\n    DCHECK(fields_.which_ == MessageType::NONE ||\n           fields_.which_ == MessageType::REQUEST)\n        << int(fields_.which_);\n    if (fields_.which_ == MessageType::NONE) {\n      fields_.which_ = MessageType::REQUEST;\n      new (&fields_.data_.request) Request();\n    } else if (fields_.which_ == MessageType::RESPONSE) {\n      throw std::runtime_error(\"Invoked Request API on HTTP Response\");\n    }\n\n    return fields_.data_.request;\n  }\n\n  const Request& request() const {\n    auto msg = const_cast<HTTPMessage*>(this);\n    return msg->request();\n  }\n\n  Response& response() {\n    DCHECK(fields_.which_ == MessageType::NONE ||\n           fields_.which_ == MessageType::RESPONSE)\n        << int(fields_.which_);\n    if (fields_.which_ == MessageType::NONE) {\n      fields_.which_ = MessageType::RESPONSE;\n      new (&fields_.data_.response) Response();\n    } else if (fields_.which_ == MessageType::REQUEST) {\n      throw std::runtime_error(\"Invoked Response API on HTTP Request\");\n    }\n\n    return fields_.data_.response;\n  }\n\n  const Response& response() const {\n    auto msg = const_cast<HTTPMessage*>(this);\n    return msg->response();\n  }\n\n  /*\n   * Cookies and query parameters\n   * These are mutable since we parse them lazily in getCookie() and\n   * getQueryParam()\n   */\n  mutable std::map<folly::StringPiece, folly::StringPiece> cookies_;\n  // TODO: use StringPiece for queryParams_ and delete splitNameValue()\n  mutable HTTPQueryParamMap queryParams_;\n\n  HTTPHeaders headers_;\n  std::unique_ptr<HTTPHeaders> strippedPerHopHeaders_;\n  HTTPHeaderSize size_;\n  WebSocketUpgrade upgradeWebsocket_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n\n  int32_t seqNo_;\n  int sslVersion_;\n  const char* sslCipher_;\n  const std::string* protoStr_;\n  std::unique_ptr<std::string> upgradeProtocol_;\n  uint8_t pri_;\n\n  std::pair<uint8_t, uint8_t> version_;\n  mutable bool parsedCookies_ : 1;\n  mutable bool parsedQueryParams_ : 1;\n  bool chunked_ : 1;\n  bool upgraded_ : 1;\n  bool wantsKeepalive_ : 1;\n  bool trailersAllowed_ : 1;\n\n  Scheme scheme_{Scheme::HTTP};\n\n  // used by atomicDumpMessage\n  static std::mutex mutexDump_;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPMessage& msg);\n\n/**\n * Returns a std::string that has the control characters removed from the\n * input string.\n */\ntemplate <typename Str>\nstd::string stripCntrlChars(const Str& str) {\n  std::string res;\n  res.reserve(str.size());\n  for (size_t i = 0; i < str.size(); ++i) {\n    if (!(str[i] <= 0x1F || str[i] == 0x7F)) {\n      res += str[i];\n    }\n  }\n  return res;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMessageFilters.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPMessageFilters.h>\n\nnamespace proxygen {\n\nvoid HTTPMessageFilter::pause() noexcept {\n  if (nextElementIsPaused_) {\n    return;\n  }\n\n  nextElementIsPaused_ = true;\n\n  if (prev_.index() == 0) {\n    auto prev = std::get<HTTPMessageFilter*>(prev_);\n    if (prev) {\n      prev->pause();\n    }\n  } else {\n    auto prev = std::get<HTTPSink*>(prev_);\n    if (prev) {\n      prev->pauseIngress();\n    }\n  }\n}\n\nvoid HTTPMessageFilter::resume(uint64_t offset) noexcept {\n  nextElementIsPaused_ = false;\n  if (prev_.index() == 0) {\n    auto prev = std::get<HTTPMessageFilter*>(prev_);\n    if (prev) {\n      prev->resume(offset);\n    }\n  } else {\n    auto prev = std::get<HTTPSink*>(prev_);\n    if (prev) {\n      prev->resumeIngress();\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMessageFilters.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <folly/io/async/DestructorCheck.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\n#include <proxygen/lib/http/sink/HTTPSink.h>\n\n#include <string_view>\n#include <variant>\n\nnamespace proxygen {\n\nstatic const std::string kMessageFilterDefaultName_ = \"Unknown\";\n\nclass HTTPMessageFilter\n    : public HTTPTransaction::Handler\n    , public folly::DestructorCheck {\n public:\n  void setNextTransactionHandler(HTTPTransaction::Handler* next) {\n    nextTransactionHandler_ = CHECK_NOTNULL(next);\n  }\n  virtual void setPrevFilter(HTTPMessageFilter* prev) noexcept {\n    if (prev_.index() == 0 && std::get<HTTPMessageFilter*>(prev_) != prev &&\n        prev && nextElementIsPaused_) {\n      prev->pause();\n    }\n    prev_ = CHECK_NOTNULL(prev);\n  }\n  virtual void setPrevSink(HTTPSink* prev) noexcept {\n    if (prev_.index() == 1 && std::get<HTTPSink*>(prev_) != prev && prev &&\n        nextElementIsPaused_) {\n      prev->pauseIngress();\n    }\n    prev_ = CHECK_NOTNULL(prev);\n  }\n  HTTPTransaction::Handler* getNextTransactionHandler() {\n    return nextTransactionHandler_;\n  }\n\n  virtual std::unique_ptr<HTTPMessageFilter> clone() noexcept = 0;\n\n  // These HTTPTransaction::Handler callbacks may be overwritten\n  // The default behavior is to pass the call through.\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override {\n    nextTransactionHandler_->onHeadersComplete(std::move(msg));\n  }\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    nextTransactionHandler_->onBody(std::move(chain));\n  }\n  void onChunkHeader(size_t length) noexcept override {\n    nextTransactionHandler_->onChunkHeader(length);\n  }\n  void onChunkComplete() noexcept override {\n    nextTransactionHandler_->onChunkComplete();\n  }\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override {\n    nextTransactionHandler_->onTrailers(std::move(trailers));\n  }\n  void onEOM() noexcept override {\n    nextTransactionHandler_->onEOM();\n  }\n  void onUpgrade(UpgradeProtocol protocol) noexcept override {\n    nextTransactionHandler_->onUpgrade(protocol);\n  }\n  void onError(const HTTPException& error) noexcept override {\n    nextTransactionHandler_->onError(error);\n  }\n\n  void onInvariantViolation(const HTTPException& error) noexcept final {\n    nextTransactionHandler_->onInvariantViolation(error);\n  }\n\n  // These HTTPTransaction::Handler callbacks cannot be overrwritten\n  void setTransaction(HTTPTransaction* txn) noexcept final {\n    nextTransactionHandler_->setTransaction(txn);\n  }\n  void detachTransaction() noexcept final {\n    if (prev_.index() == 1) {\n      // After detachTransaction(), the HTTPTransaction will destruct itself.\n      // Set it to nullptr to avoid holding a stale pointer.\n      prev_ = static_cast<HTTPSink*>(nullptr);\n    }\n    if (nextTransactionHandler_) {\n      nextTransactionHandler_->detachTransaction();\n    }\n  }\n  void onEgressPaused() noexcept final {\n    nextTransactionHandler_->onEgressPaused();\n  }\n  void onEgressResumed() noexcept final {\n    nextTransactionHandler_->onEgressResumed();\n  }\n  void onPushedTransaction(HTTPTransaction* txn) noexcept final {\n    nextTransactionHandler_->onPushedTransaction(txn);\n  }\n\n  [[nodiscard]] virtual std::string_view getFilterName() const noexcept {\n    return kMessageFilterDefaultName_;\n  }\n\n  virtual void pause() noexcept;\n\n  virtual void resume(uint64_t offset) noexcept;\n\n  // Doesn't need to propagate down a chain, call on head filter\n  void detachHandlerFromSink(std::unique_ptr<HTTPSink> sink) noexcept {\n    CHECK_EQ(prev_.index(), 1);\n    auto prev = std::get<HTTPSink*>(prev_);\n    if (prev) {\n      // prev points to the transaction, detach the handler from the\n      // transaction.\n      CHECK_EQ(prev, sink.get());\n      prev->detachAndAbortIfIncomplete(std::move(sink));\n      // Set the pointer to nullptr. It is not safe to use the pointer since\n      // after this the transaction can be destroyed without notifying the\n      // filter.\n      prev_ = static_cast<HTTPSink*>(nullptr);\n    }\n  }\n\n protected:\n  virtual void nextOnHeadersComplete(std::unique_ptr<HTTPMessage> msg) {\n    nextTransactionHandler_->onHeadersComplete(std::move(msg));\n  }\n  virtual void nextOnBody(std::unique_ptr<folly::IOBuf> chain) {\n    nextTransactionHandler_->onBody(std::move(chain));\n  }\n  virtual void nextOnChunkHeader(size_t length) {\n    nextTransactionHandler_->onChunkHeader(length);\n  }\n  virtual void nextOnChunkComplete() {\n    nextTransactionHandler_->onChunkComplete();\n  }\n  virtual void nextOnTrailers(std::unique_ptr<HTTPHeaders> trailers) {\n    nextTransactionHandler_->onTrailers(std::move(trailers));\n  }\n  virtual void nextOnEOM() {\n    nextTransactionHandler_->onEOM();\n  }\n  virtual void nextOnError(const HTTPException& ex) {\n    nextTransactionHandler_->onError(ex);\n  }\n  HTTPTransaction::Handler* nextTransactionHandler_{nullptr};\n\n  std::variant<HTTPMessageFilter*, HTTPSink*> prev_ =\n      static_cast<HTTPSink*>(nullptr);\n\n  bool nextElementIsPaused_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMethod.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPMethod.h>\n\n#include <folly/Indestructible.h>\n#include <ostream>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <vector>\n\n#define HTTP_METHOD_STR(method) #method\n\nnamespace {\n\n// Method strings. This is indestructible because this structure is\n// accessed from multiple threads and still needs to be accessible after exit()\n// is called to avoid crashing.\nusing StringVector = std::vector<std::string>;\n\nconst StringVector& getMethodStrings() {\n  static const folly::Indestructible<StringVector> methodStrings{\n      StringVector{\"GET\",\n                   \"POST\",\n                   \"OPTIONS\",\n                   \"DELETE\",\n                   \"HEAD\",\n                   \"CONNECT\",\n                   \"CONNECT-UDP\",\n                   \"PUT\",\n                   \"TRACE\",\n                   \"PATCH\",\n                   \"SUB\",\n                   \"PUB\",\n                   \"UNSUB\"}};\n  return *methodStrings;\n}\n\n} // namespace\n\nnamespace proxygen {\n\nfolly::Optional<HTTPMethod> stringToMethod(folly::StringPiece method) {\n  const auto& strings = getMethodStrings();\n  for (size_t index = 0; index < strings.size(); ++index) {\n    const auto& cur = strings[index];\n    if (caseInsensitiveEqual(cur, method)) {\n      return HTTPMethod(index);\n    }\n  }\n  return folly::none;\n}\n\nconst std::string& methodToString(HTTPMethod method) {\n  return getMethodStrings()[static_cast<unsigned>(method)];\n}\n\nstd::ostream& operator<<(std::ostream& out, HTTPMethod method) {\n  out << methodToString(method);\n  return out;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPMethod.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <string>\n\nnamespace proxygen {\n\n/**\n * Defined in winnt.h\n * Several proxygen files include folly/portability/OpenSSL.h\n *   -> folly/portability/Windows.h -> Windows.h -> winnt.h\n */\n#if defined(_WIN32) && defined(DELETE)\n#undef DELETE\n#endif\n\n/**\n * See the definitions in RFC2616 5.1.1 for the source of this\n * list. Today, proxygen only understands the methods defined in 5.1.1 and\n * is not aware of any extension methods. If you wish to support extension\n * methods, you must handle those separately from this enum.\n */\nenum class HTTPMethod {\n  GET,\n  POST,\n  OPTIONS,\n  DELETE,\n  HEAD,\n  CONNECT,\n  CONNECT_UDP,\n  PUT,\n  TRACE,\n  PATCH,\n  SUB,\n  PUB,\n  UNSUB\n};\n\n/**\n * Returns the HTTPMethod that matches the method. Although RFC2616 5.1.1\n * says methods are case-sensitive, we ignore case here since most\n * programmers probably really meant \"GET\" not \"get\". If the method is not\n * recognized, the return value will be None\n */\nextern folly::Optional<HTTPMethod> stringToMethod(folly::StringPiece method);\n\n/**\n * Returns a string representation of the method. If EXTENSION_METHOD is\n * passed, then an empty string is returned\n */\nextern const std::string& methodToString(HTTPMethod method);\n\nstd::ostream& operator<<(std::ostream& os, HTTPMethod method);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPPriorityFunctions.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n\nnamespace proxygen {\n\nnamespace {\ntemplate <class T>\nT getWithDefault(StructuredHeaders::Dictionary& dict,\n                 const std::string& key,\n                 T value,\n                 bool& missing,\n                 bool& malformed) {\n  auto it = dict.find(key);\n  if (it == dict.end()) {\n    missing = true;\n    return value;\n  }\n\n  try {\n    return it->second.get<T>();\n  } catch (const std::bad_variant_access&) {\n    malformed = true;\n    return value;\n  }\n}\n} // namespace\n\nfolly::Optional<HTTPPriority> httpPriorityFromHTTPMessage(\n    const HTTPMessage& message) {\n  return httpPriorityFromString(\n      message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY));\n}\n\nfolly::Optional<HTTPPriority> httpPriorityFromString(\n    folly::StringPiece priority) {\n  if (priority.empty()) {\n    return folly::none;\n  }\n  bool logBadHeader = false;\n  SCOPE_EXIT {\n    if (logBadHeader) {\n      LOG_EVERY_N(ERROR, 100)\n          << \"Received ill-formated priority header=\" << priority;\n    }\n  };\n  StructuredHeadersDecoder decoder(priority);\n  StructuredHeaders::Dictionary dict;\n  auto ret = decoder.decodeDictionary(dict);\n  if (ret != StructuredHeaders::DecodeError::OK) {\n    logBadHeader = true;\n    return folly::none;\n  }\n\n  bool uMissing = false;\n  bool iMissing = false;\n  bool oMissing = false;\n  bool pMissing = false;\n  bool malformed = false;\n  auto urgency = getWithDefault<int64_t>(\n      dict, \"u\", (int64_t)kDefaultHttpPriorityUrgency, uMissing, malformed);\n  bool incremental = getWithDefault(dict, \"i\", false, iMissing, malformed);\n  auto orderId = getWithDefault<int64_t>(dict, \"o\", 0, oMissing, malformed);\n  auto paused = getWithDefault(dict, \"p\", false, pMissing, malformed);\n  if ((urgency > kMaxPriority || urgency < kMinPriority) || (orderId < 0) ||\n      (uMissing && iMissing && oMissing && pMissing) || malformed) {\n    logBadHeader = true;\n    return folly::none;\n  }\n  return HTTPPriority(static_cast<uint8_t>(urgency),\n                      incremental,\n                      static_cast<uint32_t>(orderId),\n                      paused);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HTTPPriorityFunctions.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n\nnamespace proxygen {\n\nfolly::Optional<HTTPPriority> httpPriorityFromString(\n    folly::StringPiece priority);\n\nfolly::Optional<HTTPPriority> httpPriorityFromHTTPMessage(\n    const HTTPMessage& message);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/HeaderConstants.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HeaderConstants.h>\n\nnamespace proxygen::headers {\n\nconst std::string kAuthority(\":authority\");\nconst std::string kMethod(\":method\");\nconst std::string kPath(\":path\");\nconst std::string kScheme(\":scheme\");\nconst std::string kStatus(\":status\");\nconst std::string kProtocol(\":protocol\");\n\nconst std::string kHttp(\"http\");\nconst std::string kHttps(\"https\");\nconst std::string kMasque(\"masque\");\n\nconst std::string kWebsocketString(\"websocket\");\nconst std::string kStatus200(\"200\");\n\n} // namespace proxygen::headers\n"
  },
  {
    "path": "proxygen/lib/http/HeaderConstants.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <string>\n\nnamespace proxygen::headers {\n\nextern const std::string kAuthority;\nextern const std::string kMethod;\nextern const std::string kPath;\nextern const std::string kScheme;\nextern const std::string kStatus;\nextern const std::string kProtocol;\n\nextern const std::string kHttp;\nextern const std::string kHttps;\nextern const std::string kMasque;\n\nextern const std::string kWebsocketString;\nextern const std::string kStatus200;\n\nconstexpr std::string_view kWTAvailableProtocols{\"wt-available-protocols\"};\nconstexpr std::string_view kWTProtocol{\"wt-protocol\"};\n\n} // namespace proxygen::headers\n"
  },
  {
    "path": "proxygen/lib/http/ProxyStatus.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/ProxyStatus.h>\n\n#include <glog/logging.h>\n\nnamespace {\n\nconstexpr folly::StringPiece kProxyParam{\"e_proxy\"};\nconstexpr folly::StringPiece kUpstreamIPParam{\"e_upip\"};\nconstexpr folly::StringPiece kIsProxyErrorParam{\"e_isproxyerr\"};\nconstexpr folly::StringPiece kIsClientErrorTrue{\"e_isclienterr\"};\nconstexpr folly::StringPiece kIsServerErrorTrue{\"e_isservererr\"};\nconstexpr folly::StringPiece kClientAddrParam{\"e_clientaddr\"};\nconstexpr folly::StringPiece kDetails{\"details\"};\n\n} // namespace\n\nnamespace proxygen {\n\nProxyStatus::ProxyStatus(StatusType statusType) : statusType_{statusType} {\n  pIdent_.identifier = getStatusTypeString(statusType_);\n}\n\nStatusType ProxyStatus::getStatusType() const {\n  return statusType_;\n}\n\nvoid ProxyStatus::setStatusType(StatusType statusType) {\n  statusType_ = statusType;\n  pIdent_.identifier = getStatusTypeString(statusType_);\n}\n\nProxyStatus& ProxyStatus::setProxyStatusParameter(folly::StringPiece name,\n                                                  const std::string& text) {\n  if (text.empty()) {\n    return *this;\n  }\n\n  pIdent_.parameterMap[name.str()] =\n      StructuredHeaderItem(StructuredHeaderItem::Type::STRING, text);\n  return *this;\n}\n\nProxyStatus& ProxyStatus::setProxy(const std::string& proxy) {\n  return setProxyStatusParameter(kProxyParam, proxy);\n}\n\nProxyStatus& ProxyStatus::setUpstreamIP(const std::string& upstreamIP) {\n  return setProxyStatusParameter(kUpstreamIPParam, upstreamIP);\n}\n\nProxyStatus& ProxyStatus::setProxyError(const bool isProxyError) {\n  if (isProxyError) {\n    return setProxyStatusParameter(kIsProxyErrorParam, \"true\");\n  } else {\n    return setProxyStatusParameter(kIsProxyErrorParam, \"false\");\n  }\n}\n\nProxyStatus& ProxyStatus::setServerError(const bool isServerError) {\n  if (isServerError) {\n    return setProxyStatusParameter(kIsServerErrorTrue, \"true\");\n  } else {\n    return setProxyStatusParameter(kIsServerErrorTrue, \"false\");\n  }\n}\n\nProxyStatus& ProxyStatus::setClientError(const bool isClientError) {\n  if (isClientError) {\n    return setProxyStatusParameter(kIsClientErrorTrue, \"true\");\n  } else {\n    return setProxyStatusParameter(kIsClientErrorTrue, \"false\");\n  }\n}\n\nProxyStatus& ProxyStatus::setClientAddress(const std::string& clientAddr) {\n  return setProxyStatusParameter(kClientAddrParam, clientAddr);\n}\n\nProxyStatus& ProxyStatus::setDetails(const std::string& details) {\n  return setProxyStatusParameter(kDetails, details);\n}\n\nbool ProxyStatus::hasUpstreamIP() const {\n  return pIdent_.parameterMap.find(std::string(kUpstreamIPParam)) !=\n         pIdent_.parameterMap.end();\n}\n\nstd::string ProxyStatus::toString() const {\n  StructuredHeaders::ParameterisedList plist;\n  StructuredHeadersEncoder encoder;\n  plist.emplace_back(std::move(pIdent_));\n  encoder.encodeParameterisedList(plist);\n\n  return encoder.get();\n}\n\nbool ProxyStatus::isEmpty() const {\n  return statusType_ == StatusType::ENUM_COUNT;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/ProxyStatus.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Range.h>\n#include <proxygen/lib/http/StatusTypeEnum.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h>\n\nnamespace proxygen {\n\n/**\n * Proxy status returned as a header in HTTP responses.\n *   https://tools.ietf.org/html/draft-ietf-httpbis-proxy-status-00\n *\n * Example:\n *   HTTP/1.1 504 Gateway Timeout\n *   Proxy-Status: server_timeout; proxy=twtraffic1234.prn1;\n *                 upstream_ip=fbfb:face:fbfb:face:fbfb:face:fbfb:face;\n *                 upstream_pool=livestream-proxy; tries=3\n */\nclass ProxyStatus {\n public:\n  ProxyStatus() {\n    statusType_ = StatusType::ENUM_COUNT;\n  }\n  virtual ~ProxyStatus() = default;\n  explicit ProxyStatus(StatusType statusType);\n\n  StatusType getStatusType() const;\n  bool hasUpstreamIP() const;\n\n  void setStatusType(StatusType statusType);\n  ProxyStatus& setProxy(const std::string& proxy);\n  ProxyStatus& setUpstreamIP(const std::string& upstreamIP);\n  ProxyStatus& setProxyError(const bool isProxyError);\n  ProxyStatus& setClientError(const bool isProxyError);\n  ProxyStatus& setServerError(const bool isProxyError);\n  ProxyStatus& setClientAddress(const std::string& clientAddr);\n  ProxyStatus& setDetails(const std::string& details);\n  virtual ProxyStatus& setProxyStatusParameter(folly::StringPiece name,\n                                               const std::string& text);\n\n  // Serialize ProxyStatus to std::string\n  // e.g. proxy-status: destination_unavailable;\n  // e_proxy=\"devbig623.prn2\"; e_upip=\"fe:de:fa:ce:fe:de:fa:ce\"\n  std::string toString() const;\n\n  // Check if the ProxyStatus is empty\n  bool isEmpty() const;\n\n protected:\n  StructuredHeaders::ParameterisedIdentifier pIdent_;\n  StatusType statusType_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/ProxygenErrorEnum.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n\n#define PROXYGEN_ERROR_STR(error) #error\n\nnamespace {\nstatic const char* errorStrings[] = {PROXYGEN_ERROR_GEN(PROXYGEN_ERROR_STR)};\n}\n\nnamespace proxygen {\n\nstatic_assert(kErrorMax < 1 << PROXYGEN_ERROR_BITSIZE,\n              \"ProxygenError overflow\");\n\nconst char* getErrorString(ProxygenError error) {\n  if (error < kErrorNone || error >= kErrorMax) {\n    return errorStrings[kErrorMax];\n  } else {\n    return errorStrings[error];\n  }\n}\n\nconst char* getErrorStringByIndex(int i) {\n  return errorStrings[i];\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/ProxygenErrorEnum.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\nnamespace proxygen {\n\n#define SET_PROXYGEN_ERROR_IF(errorPtr, error) \\\n  do {                                         \\\n    if (errorPtr) {                            \\\n      *errorPtr = error;                       \\\n    }                                          \\\n  } while (false)\n\n// clang-format off\n// Max must be the last one.\n#define PROXYGEN_ERROR_GEN(x)                   \\\n    x(None),                                    \\\n    x(Message),                                 \\\n    x(Connect),                                 \\\n    x(ConnectTimeout),                          \\\n    x(Read),                                    \\\n    x(Write),                                   \\\n    x(Timeout),                                 \\\n    x(Handshake),                               \\\n    x(NoServer),                                \\\n    x(MaxRedirects),                            \\\n    x(InvalidRedirect),                         \\\n    x(ResponseAction),                          \\\n    x(MaxConnects),                             \\\n    x(Dropped),                                 \\\n    x(Connection),                              \\\n    x(ConnectionReset),                         \\\n    x(ParseHeader),                             \\\n    x(ParseBody),                               \\\n    x(EOF),                                     \\\n    x(ClientRenegotiation),                     \\\n    x(Unknown),                                 \\\n    x(BadDecompress),                           \\\n    x(SSL),                                     \\\n    x(StreamAbort),                             \\\n    x(StreamUnacknowledged),                    \\\n    x(WriteTimeout),                            \\\n    x(AddressPrivate),                          \\\n    x(HeaderContentValidation),                 \\\n    x(DNSResolutionErr),                        \\\n    x(DNSNoResults),                            \\\n    x(MalformedInput),                          \\\n    x(UnsupportedExpectation),                  \\\n    x(MethodNotSupported),                      \\\n    x(UnsupportedScheme),                       \\\n    x(Shutdown),                                \\\n    x(IngressStateTransition),                  \\\n    x(ClientSilent),                            \\\n    x(Canceled),                                \\\n    x(ParseResponse),                           \\\n    x(ConnRefused),                             \\\n    x(DNSOtherServer),                          \\\n    x(DNSOtherClient),                          \\\n    x(DNSOtherCancelled),                       \\\n    x(DNSshutdown),                             \\\n    x(DNSgetaddrinfo),                          \\\n    x(DNSthreadpool),                           \\\n    x(DNSunimplemented),                        \\\n    x(Network),                                 \\\n    x(Configuration),                           \\\n    x(EarlyDataRejected),                       \\\n    x(EarlyDataFailed),                         \\\n    x(AuthRequired),                            \\\n    x(Unauthorized),                            \\\n    x(EgressEOMSeenOnParentStream),             \\\n    x(TransportIsDraining),                     \\\n    x(ParentStreamNotExist),                    \\\n    x(CreatingStream),                          \\\n    x(PushNotSupported),                        \\\n    x(MaxConcurrentOutgoingStreamLimitReached), \\\n    x(BadSocket),                               \\\n    x(DuplicatedStreamId),                      \\\n    x(ClientTransactionGone),                   \\\n    x(NetworkSwitch),                           \\\n    x(Forbidden),                               \\\n    x(InternalError),                           \\\n    x(Max)\n// clang-format on\n\n// Increase this if you add more error types and Max exceeds 127\n#define PROXYGEN_ERROR_BITSIZE 7\n\n#define PROXYGEN_ERROR_ENUM(error) kError##error\n\nenum ProxygenError { PROXYGEN_ERROR_GEN(PROXYGEN_ERROR_ENUM) };\n\n#undef PROXYGEN_ERROR_ENUM\n\nextern const char* getErrorString(ProxygenError error);\n\nextern const char* getErrorStringByIndex(int i);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/RFC2616.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/RFC2616.h>\n\n#include <cstdlib>\n\n#include <folly/String.h>\n#include <folly/ThreadLocal.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n\nnamespace {\n\n/* Wapper around strtoul(3) */\nbool strtoulWrapper(const char*& curs, const char* end, unsigned long& val) {\n  char* endptr = nullptr;\n\n  unsigned long v = strtoul(curs, &endptr, 10);\n  if (endptr == curs) {\n    return false;\n  }\n\n  if (endptr > end) {\n    return false;\n  }\n\n  curs = endptr;\n  val = v;\n\n  return true;\n}\n\nbool equalsIgnoreCase(folly::StringPiece s1, folly::StringPiece s2) {\n  if (s1.size() != s2.size()) {\n    return false;\n  }\n  return std::equal(\n      s1.begin(), s1.end(), s2.begin(), folly::AsciiCaseInsensitive());\n}\n} // namespace\n\nnamespace proxygen::RFC2616 {\n\nBodyAllowed isRequestBodyAllowed(folly::Optional<HTTPMethod> method) {\n  if (method == HTTPMethod::TRACE) {\n    return BodyAllowed::NOT_ALLOWED;\n  }\n  if (method == HTTPMethod::OPTIONS || method == HTTPMethod::POST ||\n      method == HTTPMethod::PUT) {\n    return BodyAllowed::DEFINED;\n  }\n  return BodyAllowed::NOT_DEFINED;\n}\n\nbool responseBodyMustBeEmpty(unsigned status) {\n  return (status == 304 || status == 204 || (100 <= status && status < 200));\n}\n\nbool bodyImplied(const HTTPHeaders& headers) {\n  return headers.exists(HTTP_HEADER_TRANSFER_ENCODING) ||\n         headers.exists(HTTP_HEADER_CONTENT_LENGTH);\n}\n\ndouble parseQvalue(const EncodingParams& params) {\n  double qvalue = 1.0;\n  for (const auto& paramPair : params) {\n    if (paramPair.first == \"q\") {\n      qvalue = folly::to<double>(paramPair.second);\n    }\n  }\n  return qvalue;\n}\n\nbool parseQvalues(folly::StringPiece value, TokenPairVec& output) {\n  bool success = true;\n  auto encodings = parseEncoding(value);\n  if (encodings.hasException()) {\n    return false;\n  }\n\n  for (const auto& pair : encodings.value()) {\n    double qvalue = 1.0;\n    try {\n      qvalue = parseQvalue(pair.second);\n    } catch (const std::range_error&) {\n      // q=<some garbage>\n      success = false;\n    }\n    output.emplace_back(pair.first, qvalue);\n  }\n\n  return success;\n}\n\nbool parseByteRangeSpec(folly::StringPiece value,\n                        unsigned long& outFirstByte,\n                        unsigned long& outLastByte,\n                        unsigned long& outInstanceLength) {\n  // We should start with \"bytes \"\n  if (!value.startsWith(\"bytes \")) {\n    return false;\n  }\n\n  const char* curs = value.begin() + 6 /* strlen(\"bytes \") */;\n  const char* end = value.end();\n\n  unsigned long firstByte = ULONG_MAX;\n  unsigned long lastByte = ULONG_MAX;\n  unsigned long instanceLength = ULONG_MAX;\n\n  if (!strtoulWrapper(curs, end, firstByte)) {\n    if (*curs != '*') {\n      return false;\n    }\n\n    firstByte = 0;\n    lastByte = ULONG_MAX;\n    ++curs;\n  } else {\n    if (*curs != '-') {\n      return false;\n    }\n\n    ++curs;\n\n    if (!strtoulWrapper(curs, end, lastByte)) {\n      return false;\n    }\n  }\n\n  if (*curs != '/') {\n    return false;\n  }\n\n  ++curs;\n  if (*curs != '*') {\n    if (!strtoulWrapper(curs, end, instanceLength)) {\n      return false;\n    }\n  } else {\n    ++curs;\n  }\n\n  if (curs < end && *curs != '\\0') {\n    return false;\n  }\n\n  if (lastByte < firstByte) {\n    return false;\n  }\n\n  if ((lastByte - firstByte + 1) > instanceLength) {\n    return false;\n  }\n\n  outFirstByte = firstByte;\n  outLastByte = lastByte;\n  outInstanceLength = instanceLength;\n  return true;\n}\n\nfolly::Try<EncodingList> parseEncoding(const folly::StringPiece header) {\n  EncodingList result;\n  std::vector<folly::StringPiece> topLevelTokens;\n  folly::split(',', header, topLevelTokens, true /*ignore empty*/);\n\n  if (topLevelTokens.empty()) {\n    return folly::Try<EncodingList>(\n        folly::make_exception_wrapper<std::runtime_error>(\n            \"Header value mustn't be empty.\"));\n  }\n\n  for (auto topLevelToken : topLevelTokens) {\n    std::vector<folly::StringPiece> secondLevelTokens;\n    folly::split(';', topLevelToken, secondLevelTokens, true /*ignore empty*/);\n\n    if (secondLevelTokens.empty()) {\n      return folly::Try<EncodingList>(\n          folly::make_exception_wrapper<std::runtime_error>(\n              \"Encoding must have name.\"));\n    }\n\n    auto encoding = folly::trimWhitespace(secondLevelTokens.front());\n    if (encoding.empty()) {\n      return folly::Try<EncodingList>(\n          folly::make_exception_wrapper<std::runtime_error>(\"Empty encoding!\"));\n    }\n    EncodingParams params;\n    params.reserve(secondLevelTokens.size() - 1);\n    auto it = secondLevelTokens.begin();\n    while (++it != secondLevelTokens.end()) {\n      auto val = *it;\n      auto key = val.split_step('=');\n\n      key = folly::trimWhitespace(key);\n      val = folly::trimWhitespace(val);\n\n      if (key.empty()) {\n        return folly::Try<EncodingList>(\n            folly::make_exception_wrapper<std::runtime_error>(\n                \"Param key must not be empty!\"));\n      }\n\n      params.emplace_back(key, val);\n    }\n    result.emplace_back(encoding, std::move(params));\n  }\n  return folly::Try<EncodingList>(result);\n}\n\nbool acceptsEncoding(const folly::StringPiece header,\n                     const folly::StringPiece encoding) {\n  auto encodings = parseEncoding(header);\n  if (encodings.hasException()) {\n    return false;\n  }\n  return acceptsEncoding(encodings.value(), encoding);\n}\n\nbool acceptsEncoding(const EncodingList& encodings,\n                     const folly::StringPiece encoding) {\n  for (const auto& pair : encodings) {\n    if (equalsIgnoreCase(pair.first, encoding)) {\n      try {\n        auto qval = parseQvalue(pair.second);\n        return qval > 0;\n      } catch (const std::exception&) {\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\n} // namespace proxygen::RFC2616\n"
  },
  {
    "path": "proxygen/lib/http/RFC2616.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <vector>\n\n#if !FOLLY_MOBILE\n#include <folly/small_vector.h>\n#endif\n\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <folly/Try.h>\n#include <proxygen/lib/http/HTTPMethod.h>\n#include <string>\n\nnamespace proxygen {\n\nclass HTTPHeaders;\n\nnamespace RFC2616 {\n\n/**\n * This file contains functions for determining when certain tricky parts of RFC\n * 2616 arise.\n */\n\n/**\n * The HTTP request as defined in RFC 2616 may or may not have a body. In some\n * cases they MUST NOT have a body. In other cases, the body has no semantic\n * meaning and so is not defined. Finally, for some methods, the body is well\n * defined. Please see Section 9 and 4.3 for details on this.\n */\nenum class BodyAllowed {\n  DEFINED,\n  NOT_DEFINED,\n  NOT_ALLOWED,\n};\nBodyAllowed isRequestBodyAllowed(folly::Optional<HTTPMethod> method);\n\n/**\n * Some status codes imply that there MUST NOT be a response body.  See section\n * 4.3: \"All 1xx (informational), 204 (no content), and 304 (not modified)\n * responses MUST NOT include a message-body.\"\n * @param status The code to test (100 <= status <= 999)\n */\nbool responseBodyMustBeEmpty(unsigned status);\n\n/**\n * Returns true if the headers imply that a body will follow. Note that in some\n * situations a body may come even if this function returns false (e.g. a 1.0\n * response body's length can be given implicitly by closing the connection).\n */\nbool bodyImplied(const HTTPHeaders& headers);\n\n/**\n * Parse a string containing tokens and qvalues, such as the RFC strings for\n * Accept-Charset, Accept-Encoding and Accept-Language.  It won't work for\n * complex Accept: headers because it doesn't return parameters or\n * accept-extension.\n *\n * See RFC sections 14.2, 14.3, 14.4 for definitions of these header values\n *\n * TODO: optionally sort by qvalue descending\n *\n * Return true if the string was well formed according to the RFC.  Note it can\n * return false but still populate output with best-effort parsing.\n */\nusing TokenQPair = std::pair<folly::StringPiece, double>;\n\nconstexpr size_t kTokenPairVecDefaultSize = 8;\n#if !FOLLY_MOBILE\nusing TokenPairVec =\n    folly::small_vector<TokenQPair,\n                        kTokenPairVecDefaultSize,\n                        folly::small_vector_policy::policy_size_type<uint16_t>>;\n#else\nusing TokenPairVec = std::vector<TokenQPair>;\n#endif\n\nbool parseQvalues(folly::StringPiece value, TokenPairVec& output);\n\nusing EncodingParams =\n    std::vector<std::pair<folly::StringPiece, folly::StringPiece>>;\nusing EncodingList = std::vector<std::pair<folly::StringPiece, EncodingParams>>;\n\n/*\n * Parse a request into components.\n *\n * E.g., \"gzip;q=0.5, zstd;q=0.5;wl=16, *\" into:\n * [\n *   (\"gzip\", [ (\"q\", \"0.5\") ]),\n *   (\"zstd\", [ (\"q\", \"0.5\"), (\"wl\", \"16\") ]),\n *   (\"*\", [])\n * ]\n */\nfolly::Try<EncodingList> parseEncoding(const folly::StringPiece header);\n\n/*\n * For given Accept-Encoding header, returns if encoding is accepted (is in\n * list and q > 0).\n */\nbool acceptsEncoding(const folly::StringPiece header,\n                     const folly::StringPiece encoding);\n\n/**\n * Equivalent if you've already parsed the header separately.\n */\nbool acceptsEncoding(const EncodingList& encodings,\n                     const folly::StringPiece encoding);\n\n/**\n * Parse an RFC 2616 section 14.16 \"bytes A-B/C\" string and returns them as the\n * first and last bytes and instance length, respectively.\n *\n * Wildcards are handled specially as follows: if the range is actually \"*\",\n * the first byte is parsed as 0 and last byte as ULONG_MAX; if instance length\n * is actually \"*\", it is parsed as ULONG_MAX.\n *\n * Note that is ONLY suitable for use in parsing \"Content-Range\" response\n * headers. The \"Range\" request header has different but similar syntax.\n */\nbool parseByteRangeSpec(folly::StringPiece value,\n                        unsigned long& firstByte,\n                        unsigned long& lastByte,\n                        unsigned long& instanceLength);\n\n} // namespace RFC2616\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/StatusTypeEnum.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/StatusTypeEnum.h>\n\n#define STATUS_TYPE_STR(statusType) #statusType\n\nnamespace {\nstatic const char* statusTypeStrings[] = {STATUS_TYPES_GEN(STATUS_TYPE_STR)};\n}\n\nnamespace proxygen {\n\nconst char* getStatusTypeString(StatusType statusType) {\n  int statusTypeInt = static_cast<int>(statusType);\n  if (static_cast<int>(statusType) < 0 ||\n      statusType >= StatusType::ENUM_COUNT) {\n    return statusTypeStrings[static_cast<int>(StatusType::ENUM_COUNT)];\n  } else {\n    return statusTypeStrings[statusTypeInt];\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/StatusTypeEnum.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <exception>\n\nnamespace proxygen {\n\n// Based on the IETF Proxy-Status HTTP Header Field draft in\n//   https://tools.ietf.org/html/draft-ietf-httpbis-proxy-status-00\n\n#define STATUS_TYPES_GEN(x)                                                    \\\n  /* STANDARD STATUS TYPES */                                                  \\\n  x(connection_read_timeout), x(connection_refused), x(connection_terminated), \\\n      x(connection_timeout), x(connection_write_timeout),                      \\\n      x(connnection_limit_reached), x(destination_ip_prohibited),              \\\n      x(destination_ip_unroutable), x(destination_not_found),                  \\\n      x(destination_unavailable), x(dns_resolution_error), x(dns_error),       \\\n      x(dns_timeout), x(http_protocol_error), x(http_request_denied),          \\\n      x(http_request_error), x(http_response_body_size),                       \\\n      x(http_response_content_coding), x(http_response_header_block_size),     \\\n      x(http_response_header_size), x(http_response_incomplete),               \\\n      x(http_response_status), x(http_response_timeout),                       \\\n      x(http_response_transfer_coding), x(http_upgrade_failed),                \\\n      x(proxy_internal_response), x(proxy_internal_error),                     \\\n      x(proxy_loop_detected), x(tls_error), x(tls_expired_peer_certificate),   \\\n      x(tls_handshake_error), x(tls_missing_proxy_certificate),                \\\n      x(tls_rejected_proxy_certificate), x(tls_unexpected_peer_certificate),   \\\n      x(tls_unexpected_peer_identity), x(tls_untrusted_peer_certificate),      \\\n      x(http_response_ok), /* FB STATUS TYPES */ x(async_request_error),       \\\n      x(client_read_error), x(client_timeout), x(http_body_before_headers),    \\\n      x(http_body_parsing_error), x(http_eom_before_headers),                  \\\n      x(http_headers_parsing_error), x(http_partial_reliability_disabled),     \\\n      x(http_trailers_before_headers), x(no_server_available),                 \\\n      x(proxy_adaptive_rate_limit), x(request_rate_limited),                   \\\n      x(sc_channel_invalid_argument), x(sc_channel_unknown_error),             \\\n      x(sc_eom_before_headers), x(sc_upstream_timeout),                        \\\n      x(sc_runtime_exception), x(sc_content_integrity_error),                  \\\n      x(live_head_error), x(redirect_connect_error),                           \\\n      x(redirect_limit_exceeded), x(redirect_pool_error),                      \\\n      x(redirect_request_too_large), x(server_connection_error),               \\\n      x(server_timeout), x(server_write_error), x(telephoto_error),            \\\n      x(wasm_invocation_error), x(transcode_server_error),                     \\\n      x(fbvp_channel_error), x(server_internal_error), x(invalid_pool),        \\\n      x(qoe_error), x(sc_downstream_error), x(content_integrity),              \\\n      x(bad_request),                                                          \\\n      /* APP-SPECIFIC STATUS TYPES */ x(manifest_invalid_status),              \\\n      x(manifest_is_empty), x(manifest_parsing_error),                         \\\n      x(manifest_missing_representation), x(manifest_with_0_bitrate),          \\\n      x(manifest_with_no_tracks), x(manifest_with_wrong_track),                \\\n      x(cache_lease_queue_hard_timeout), x(cache_purge), x(cache_error),       \\\n      x(takedown_direct_response),                                             \\\n      /* ENUM_COUNT MUST BE THE FINAL ENUM */ x(ENUM_COUNT)\n\n#define STATUS_TYPE_ENUM(statusType) statusType\n\nenum class StatusType { STATUS_TYPES_GEN(STATUS_TYPE_ENUM) };\n\nconst char* getStatusTypeString(StatusType statusType);\n\nclass ExceptionWithStatusType : public std::exception {\n public:\n  ExceptionWithStatusType(int statusCode, StatusType statusType)\n      : statusCode_(statusCode), statusType_(statusType) {\n  }\n  [[nodiscard]] int getStatusCode() const {\n    return statusCode_;\n  }\n  [[nodiscard]] StatusType getStatusType() const {\n    return statusType_;\n  }\n  [[nodiscard]] const char* what() const noexcept override {\n    return getStatusTypeString(statusType_);\n  }\n\n protected:\n  int statusCode_;\n  StatusType statusType_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/SynchronizedLruQuicPskCache.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/SynchronizedLruQuicPskCache.h>\n\nnamespace proxygen {\n\nSynchronizedLruQuicPskCache::SynchronizedLruQuicPskCache(uint64_t mapMax)\n    : cache_(EvictingPskMap(mapMax)) {\n}\n\nquic::Optional<quic::QuicCachedPsk> SynchronizedLruQuicPskCache::getPsk(\n    const std::string& identity) {\n  auto cacheMap = cache_.wlock();\n  auto result = cacheMap->find(identity);\n  if (result != cacheMap->end()) {\n    if (std::chrono::system_clock::now() >\n        result->second.cachedPsk.ticketExpirationTime) {\n      VLOG(1) << \"PSK expired: \" << identity << \", id: \"\n              << (result->second.cachedPsk.serverCert\n                      ? result->second.cachedPsk.serverCert->getIdentity()\n                      : \"none\");\n      cacheMap->erase(result);\n      return std::nullopt;\n    }\n    return result->second;\n  } else {\n    return std::nullopt;\n  }\n}\n\nvoid SynchronizedLruQuicPskCache::putPsk(const std::string& identity,\n                                         quic::QuicCachedPsk psk) {\n  auto cacheMap = cache_.wlock();\n  cacheMap->set(identity, std::move(psk));\n}\n\nvoid SynchronizedLruQuicPskCache::removePsk(const std::string& identity) {\n  auto cacheMap = cache_.wlock();\n  cacheMap->erase(identity);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/SynchronizedLruQuicPskCache.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Synchronized.h>\n#include <folly/container/EvictingCacheMap.h>\n#include <quic/fizz/client/handshake/QuicPskCache.h>\n\nnamespace proxygen {\n\nclass SynchronizedLruQuicPskCache : public quic::QuicPskCache {\n public:\n  ~SynchronizedLruQuicPskCache() override = default;\n\n  explicit SynchronizedLruQuicPskCache(uint64_t mapMax);\n\n  quic::Optional<quic::QuicCachedPsk> getPsk(\n      const std::string& identity) override;\n\n  void putPsk(const std::string& identity, quic::QuicCachedPsk psk) override;\n\n  void removePsk(const std::string& identity) override;\n\n private:\n  using EvictingPskMap =\n      folly::EvictingCacheMap<std::string, quic::QuicCachedPsk>;\n  folly::Synchronized<EvictingPskMap> cache_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/Types.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/container/F14Map.h>\n#include <string>\n\nnamespace proxygen {\n\nusing HTTPQueryParamMap = folly::F14FastMap<std::string, std::string>;\n\n}\n"
  },
  {
    "path": "proxygen/lib/http/Window.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/Window.h>\n\n#include <glog/logging.h>\n#include <limits>\n\nnamespace proxygen {\n\nWindow::Window(uint32_t capacity) {\n  CHECK(setCapacity(capacity));\n}\n\nint32_t Window::getSize() const {\n  return capacity_ - outstanding_;\n}\n\nuint32_t Window::getNonNegativeSize() const {\n  auto size = getSize();\n  return size > 0 ? size : 0;\n}\n\nuint32_t Window::getCapacity() const {\n  // This conversion is safe since we always ensure capacity_ > 0\n  return static_cast<uint32_t>(capacity_);\n}\n\nuint32_t Window::getOutstanding() const {\n  return outstanding_ < 0 ? 0 : outstanding_;\n}\n\nbool Window::reserve(const uint32_t amount, bool strict) {\n  if (amount > std::numeric_limits<int32_t>::max()) {\n    VLOG(3) << \"Cannot shrink window by more than 2^31 - 1. \"\n            << \"Attempted decrement of \" << amount;\n    return false;\n  }\n  const int32_t limit =\n      std::numeric_limits<int32_t>::max() - static_cast<int32_t>(amount);\n  if (outstanding_ > 0 && limit < outstanding_) {\n    VLOG(3) << \"Overflow detected. Window change failed.\";\n    return false;\n  }\n  const int32_t newOutstanding = outstanding_ + amount;\n  if (strict && newOutstanding > capacity_) {\n    VLOG(3) << \"Outstanding bytes (\" << newOutstanding << \") exceeded \"\n            << \"window capacity (\" << capacity_ << \")\";\n    return false;\n  }\n  outstanding_ = newOutstanding;\n  return true;\n}\n\nbool Window::free(const uint32_t amount) {\n  if (amount > std::numeric_limits<int32_t>::max()) {\n    VLOG(3) << \"Cannot expand window by more than 2^31 - 1. \"\n            << \"Attempted increment of \" << amount;\n    return false;\n  }\n  const int32_t limit =\n      std::numeric_limits<int32_t>::min() + static_cast<int32_t>(amount);\n  if (outstanding_ < 0 && limit > outstanding_) {\n    VLOG(3) << \"Underflow detected. Window change failed.\";\n    return false;\n  }\n  const int32_t newOutstanding = outstanding_ - amount;\n  if (newOutstanding < capacity_ - std::numeric_limits<int32_t>::max()) {\n    VLOG(3) << \"Window exceeded 2^31 - 1. Window change failed.\";\n    return false;\n  }\n  outstanding_ = newOutstanding;\n  return true;\n}\n\nbool Window::setCapacity(const uint32_t capacity) {\n  if (capacity > std::numeric_limits<int32_t>::max()) {\n    VLOG(3) << \"Cannot set initial window > 2^31 -1.\";\n    return false;\n  }\n\n  const int32_t diff = static_cast<int32_t>(capacity) - capacity_;\n  if (diff > 0) {\n    const int32_t size = getSize();\n    if (size > 0 && diff > (std::numeric_limits<int32_t>::max() - size)) {\n      VLOG(3) << \"Increasing the capacity overflowed the window\";\n      return false;\n    }\n  }\n\n  capacity_ = static_cast<int32_t>(capacity);\n  return true;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/Window.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\nnamespace proxygen {\n\n/**\n * A class that implements SPDY & HTTP/2 window management. This class\n * should be used for both connection and stream level flow control.\n */\nclass Window {\n public:\n  /**\n   * Constructs a new Window.\n   * @param capacity The initial capacity of this Window. The initial size will\n   *                 also be equal to this. This parameter must not be > 2^31 -1\n   */\n  explicit Window(uint32_t capacity);\n\n  /**\n   * Returns the number of bytes available to be consumed in this\n   * window. This could become a negative number if the initial window is\n   * set to a smaller number.\n   */\n  [[nodiscard]] int32_t getSize() const;\n\n  /**\n   * Returns the number of bytes available to be consumed in this\n   * window. If that number went negative somehow, this function clamps\n   * the return value to zero.\n   */\n  [[nodiscard]] uint32_t getNonNegativeSize() const;\n\n  /**\n   * Returns the size of the initial window. That is, the total number of\n   * bytes allowed to be outstanding on this window.\n   */\n  [[nodiscard]] uint32_t getCapacity() const;\n\n  /**\n   * Returns the number of bytes reserved in this window. If multiple\n   * calls to free() caused this number to go negative, this function\n   * returns 0.\n   */\n  [[nodiscard]] uint32_t getOutstanding() const;\n\n  /**\n   * @param amount The amount to reduce the window size by. Increases\n   *               bytes outstanding by amount.\n   * @param strict If true, reserve() will return false if there is\n   *               no space remaining in the window. If false,\n   *               reserve() will return true until the integer\n   *               overflows.\n   */\n  bool reserve(uint32_t amount, bool strict = true);\n\n  /**\n   * Increment the window size by amount. Decrease bytes outstanding by\n   * amount. The window size could become greater than the capacity here.\n   */\n  bool free(uint32_t amount);\n\n  /**\n   * Sets a new initial window size. This will also apply the delta\n   * between the current window size and the new window size. Returns false\n   * iff applying the new initial window fails.\n   */\n  bool setCapacity(uint32_t capacity);\n\n private:\n  int32_t outstanding_{0};\n  int32_t capacity_{0}; // always positive\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_codec_direction\n  SRCS\n    TransportDirection.cpp\n)\n\nproxygen_add_library(proxygen_http_codec_error_code\n  SRCS\n    ErrorCode.cpp\n  DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_codec_util\n  SRCS\n    CodecUtil.cpp\n  DEPS\n    proxygen_http_http_headers\n    Folly::folly_string\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_message\n    proxygen_utils_util_inl\n    Folly::folly_portability_windows\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_codec_codec_common\n  SRCS\n    CodecProtocol.cpp\n    HTTP2Constants.cpp\n    HTTPCodecFilter.cpp\n    HTTPCodecPrinter.cpp\n    HTTPParallelCodec.cpp\n    HTTPSettings.cpp\n    HeaderDecodeInfo.cpp\n  DEPS\n    Folly::folly_conv\n    Folly::folly_string\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_codec_compress_fast_header_name\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_codec_compress_hpack\n    proxygen_http_codec_direction\n    proxygen_http_codec_error_code\n    proxygen_http_codec_util\n    proxygen_http_header_constants\n    proxygen_http_http_header_size\n    proxygen_http_http_headers\n    proxygen_http_http_utils\n    proxygen_http_message\n    proxygen_utils_export\n    proxygen_utils_filter_chain\n    Folly::folly_io_iobuf\n    Folly::folly_lang_assume\n    Folly::folly_optional\n    Folly::folly_portability\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_codec_http1x_codec\n  SRCS\n    HTTP1xCodec.cpp\n    HTTPChecks.cpp\n  DEPS\n    proxygen_http_codec_util\n    proxygen_http_http_header_size\n    proxygen_http_http_headers\n    Folly::folly_base64\n    Folly::folly_memory\n    Folly::folly_random\n    Folly::folly_ssl_openssl_hash\n  EXPORTED_DEPS\n    proxygen_external_http_parser\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_direction\n    proxygen_http_message\n)\n\nproxygen_add_library(proxygen_http_codec_http2_codec\n  SRCS\n    FlowControlFilter.cpp\n    HTTP2Codec.cpp\n    HTTP2Framer.cpp\n  DEPS\n    proxygen_http_codec_util\n    proxygen_http_priority_functions\n    proxygen_utils_logging\n    Folly::folly_base64\n    Folly::folly_conv\n    Folly::folly_random\n    Folly::folly_tracing_scoped_trace_section\n    Folly::folly_try\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_compress_hpack\n    proxygen_http_codec_error_code\n    proxygen_http_window\n    proxygen_utils_export\n    Folly::folly_io_iobuf\n    Folly::folly_optional\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_codec_rate_limit_filters\n  SRCS\n    RateLimitFilter.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_http2_codec\n    proxygen_http_session_stats\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_codec_debug_filter\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_utils_logging\n    Folly::folly_container_evicting_cache_map\n)\n\nproxygen_add_library(proxygen_http_codec\n  EXPORTED_DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_codec_http2_codec\n)\n\nproxygen_add_library(proxygen_http_codec_codec_factory\n  SRCS\n    DefaultHTTPCodecFactory.cpp\n    HTTPCodecFactory.cpp\n  DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_codec_http2_codec\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_direction\n)\n\nproxygen_add_library(proxygen_http_codec_hq_codec\n  SRCS\n    HQControlCodec.cpp\n    HQFramedCodec.cpp\n    HQFramer.cpp\n    HQStreamCodec.cpp\n    HQUnidirectionalCodec.cpp\n    HQUtils.cpp\n  DEPS\n    proxygen_http_priority_functions\n    proxygen_utils_logging\n    Folly::folly_format\n    Folly::folly_overload\n    Folly::folly_random\n    Folly::folly_scope_guard\n    Folly::folly_tracing_scoped_trace_section\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_compress_hpack\n    proxygen_http_codec_compress_qpack\n    proxygen_http_codec_error_code\n    proxygen_http_codec_util\n    proxygen_http_h3_errors\n    proxygen_http_http_utils\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_folly_utils\n    Folly::folly_function\n    Folly::folly_io_iobuf\n    Folly::folly_lang_assume\n    Folly::folly_optional\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_codec_http_binary_codec\n  SRCS\n    HTTPBinaryCodec.cpp\n  DEPS\n    proxygen_http_codec_util\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_folly_utils\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_direction\n    proxygen_http_message\n)\n\nproxygen_add_library(proxygen_http_codec_capsule_codec\n  SRCS\n    CapsuleCodec.cpp\n  DEPS\n    mvfst::mvfst_folly_utils\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    Folly::folly_expected\n    Folly::folly_io_iobuf\n    Folly::folly_optional\n)\n\nadd_subdirectory(compress)\nadd_subdirectory(webtransport)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/codec/CapsuleCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n#include <quic/folly_utils/Utils.h>\n\nnamespace proxygen {\n\nvoid CapsuleCodec::onIngress(std::unique_ptr<folly::IOBuf> data, bool eom) {\n  ingress_.append(std::move(data));\n  size_t remainingLength = ingress_.chainLength();\n  folly::io::Cursor cursor(ingress_.front());\n  while (!connError_ && remainingLength > 0) {\n    switch (parseState_) {\n      case ParseState::CAPSULE_HEADER_TYPE: {\n        auto type = quic::follyutils::decodeQuicInteger(cursor);\n        if (!type) {\n          connError_ = ErrorCode::PARSE_UNDERFLOW;\n          break;\n        }\n        curCapsuleType_ = type->first;\n        remainingLength -= type->second;\n        parseState_ = ParseState::CAPSULE_LENGTH;\n        [[fallthrough]];\n      }\n      case ParseState::CAPSULE_LENGTH: {\n        auto length = quic::follyutils::decodeQuicInteger(cursor);\n        if (!length) {\n          connError_ = ErrorCode::PARSE_UNDERFLOW;\n          break;\n        }\n        curCapsuleLength_ = length->first;\n        remainingLength -= length->second;\n        if (callback_) {\n          callback_->onCapsule(curCapsuleType_, curCapsuleLength_);\n        }\n        if (!canParseCapsule(curCapsuleType_)) {\n          parseState_ = ParseState::SKIP_CAPSULE;\n          break;\n        } else {\n          parseState_ = ParseState::CAPSULE_PAYLOAD;\n        }\n        [[fallthrough]];\n      }\n      case ParseState::CAPSULE_PAYLOAD: {\n        if (remainingLength < curCapsuleLength_) {\n          connError_ = ErrorCode::PARSE_UNDERFLOW;\n          break;\n        }\n        auto res = parseCapsule(cursor);\n        if (res.hasError()) {\n          connError_ = res.error();\n          break;\n        }\n        parseState_ = ParseState::CAPSULE_HEADER_TYPE;\n        remainingLength -= curCapsuleLength_;\n        break;\n      }\n      case ParseState::SKIP_CAPSULE: {\n        auto skipped = cursor.skipAtMost(curCapsuleLength_);\n        curCapsuleLength_ -= skipped;\n        remainingLength -= skipped;\n        if (curCapsuleLength_ == 0) {\n          parseState_ = ParseState::CAPSULE_HEADER_TYPE;\n        }\n        break;\n      }\n    }\n  }\n  if (connError_) {\n    if (connError_.value() == ErrorCode::PARSE_UNDERFLOW && !eom) {\n      ingress_.trimStart(ingress_.chainLength() - remainingLength);\n      connError_.reset();\n      return;\n    } else {\n      if (callback_) {\n        callback_->onConnectionError(connError_.value());\n      }\n      XLOG(ERR) << \"Connection error=\" << uint32_t(*connError_);\n    }\n  }\n  ingress_.move();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CapsuleCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <folly/Expected.h>\n#include <folly/Optional.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n\nnamespace proxygen {\n\nclass CapsuleCodec {\n public:\n  enum class ErrorCode : uint32_t {\n    PARSE_UNDERFLOW = 0x0,\n    PARSE_ERROR = 0x1,\n  };\n\n  class Callback {\n   public:\n    virtual ~Callback() noexcept = default;\n\n    virtual void onCapsule(uint64_t /*capsuleType*/,\n                           uint64_t /*capsuleLength*/) noexcept {\n    }\n    virtual void onConnectionError(ErrorCode error) noexcept = 0;\n  };\n\n  explicit CapsuleCodec(Callback* callback = nullptr)\n      : parseState_(ParseState::CAPSULE_HEADER_TYPE),\n        connError_(folly::none),\n        callback_(callback) {\n  }\n  virtual ~CapsuleCodec() = default;\n\n  void onIngress(std::unique_ptr<folly::IOBuf> data, bool eom);\n\n  void setCallback(Callback* callback) {\n    callback_ = callback;\n  }\n\n protected:\n  virtual bool canParseCapsule(uint64_t /*capsuleType*/) noexcept {\n    return false;\n  }\n\n  virtual folly::Expected<folly::Unit, ErrorCode> parseCapsule(\n      folly::io::Cursor& /*cursor*/) {\n    // Override for capsule specific parsing functions\n    // Return folly::none if parsing is successful, or an error code if it fails\n    return folly::makeUnexpected(ErrorCode::PARSE_ERROR);\n  }\n\n  uint64_t curCapsuleType_{std::numeric_limits<uint64_t>::max()};\n  uint64_t curCapsuleLength_{0};\n\n private:\n  folly::IOBufQueue ingress_{folly::IOBufQueue::cacheChainLength()};\n  enum class ParseState : uint8_t {\n    CAPSULE_HEADER_TYPE,\n    CAPSULE_LENGTH,\n    CAPSULE_PAYLOAD,\n    SKIP_CAPSULE,\n  };\n  ParseState parseState_{};\n  folly::Optional<ErrorCode> connError_;\n\n protected:\n  Callback* callback_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CodecProtocol.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n\n#include <folly/String.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\nnamespace {\n\n#ifndef CLANG_LAZY_INIT_TEST\n#define CLANG_LAZY_INIT_TEST\n#endif\n\nCLANG_LAZY_INIT_TEST static const std::string http_1_1 = \"http/1.1\";\nCLANG_LAZY_INIT_TEST static const std::string http_2 = \"http/2\";\nCLANG_LAZY_INIT_TEST static const std::string hq = \"hq\";\nCLANG_LAZY_INIT_TEST static const std::string h3 = \"h3\";\nCLANG_LAZY_INIT_TEST static const std::string http_binary = \"bhttp\";\nCLANG_LAZY_INIT_TEST static const std::string tunnel_lite = \"lite\";\n} // namespace\n\nextern CodecProtocol getCodecProtocolFromStr(folly::StringPiece protocolStr) {\n  if (protocolStr == http_1_1) {\n    return CodecProtocol::HTTP_1_1;\n  } else if (protocolStr == http_2 || protocolStr == http2::kProtocolString ||\n             protocolStr == http2::kProtocolCleartextString) {\n    return CodecProtocol::HTTP_2;\n  } else if (protocolStr.starts_with(hq)) {\n    return CodecProtocol::HQ;\n  } else if (protocolStr.starts_with(h3)) {\n    return CodecProtocol::HTTP_3;\n  } else if (protocolStr.starts_with(http_binary)) {\n    return CodecProtocol::HTTP_BINARY;\n  } else if (protocolStr.starts_with(tunnel_lite)) {\n    return CodecProtocol::TUNNEL_LITE;\n  } else {\n    // return default protocol\n    return CodecProtocol::HTTP_1_1;\n  }\n}\n\nextern const std::string& getCodecProtocolString(CodecProtocol proto) {\n  switch (proto) {\n    case CodecProtocol::HTTP_1_1:\n      return http_1_1;\n    case CodecProtocol::HTTP_2:\n      return http_2;\n    case CodecProtocol::HTTP_3:\n      return h3;\n    case CodecProtocol::HQ:\n      return hq;\n    case CodecProtocol::HTTP_BINARY:\n      return http_binary;\n    case CodecProtocol::TUNNEL_LITE:\n      return tunnel_lite;\n  }\n  LOG(FATAL) << \"Unreachable\";\n}\n\nextern bool isValidCodecProtocolStr(folly::StringPiece protocolStr) {\n  return protocolStr == http_1_1 || protocolStr == http2::kProtocolString ||\n         protocolStr == http2::kProtocolCleartextString ||\n         protocolStr == http_2 || protocolStr == hq ||\n         protocolStr == http_binary;\n}\n\nextern bool isHTTP1_1CodecProtocol(CodecProtocol protocol) {\n  return protocol == CodecProtocol::HTTP_1_1;\n}\n\nextern bool isHTTP2CodecProtocol(CodecProtocol protocol) {\n  return protocol == CodecProtocol::HTTP_2;\n}\n\nextern bool isHQCodecProtocol(CodecProtocol protocol) {\n  return protocol == CodecProtocol::HQ || protocol == CodecProtocol::HTTP_3;\n}\n\nextern bool isHTTPBinaryCodecProtocol(CodecProtocol protocol) {\n  return protocol == CodecProtocol::HTTP_BINARY;\n}\n\nextern bool isParallelCodecProtocol(CodecProtocol protocol) {\n  return isHTTP2CodecProtocol(protocol);\n}\n\nextern bool serverAcceptedUpgrade(const std::string& clientUpgrade,\n                                  const std::string& serverUpgrade) {\n  if (clientUpgrade.empty() || serverUpgrade.empty()) {\n    return false;\n  }\n\n  // Should be a comma separated list of protocols, like NPN\n  std::vector<folly::StringPiece> clientProtocols;\n  folly::split(',', clientUpgrade, clientProtocols, /*ignoreEmpty=*/true);\n\n  std::vector<folly::StringPiece> serverProtocols;\n  folly::split(',', serverUpgrade, serverProtocols, /*ignoreEmpty=*/true);\n  for (auto& sp : serverProtocols) {\n    sp = folly::trimWhitespace(sp);\n  }\n\n  for (const auto& cp : clientProtocols) {\n    auto cpt = folly::trimWhitespace(cp);\n    return std::any_of(\n        serverProtocols.begin(), serverProtocols.end(), [cpt](const auto& sp) {\n          return cpt.equals(sp, folly::AsciiCaseInsensitive{});\n        });\n  }\n\n  return false;\n}\n\nconst folly::Optional<HTTPCodec::StreamID> HTTPCodec::NoStream = folly::none;\nconst folly::Optional<uint8_t> HTTPCodec::NoPadding = folly::none;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CodecProtocol.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <proxygen/lib/utils/Export.h>\n#include <string>\n\nnamespace proxygen {\n\nenum class CodecProtocol : uint8_t {\n  HTTP_1_1,\n  HTTP_2,\n  HQ,\n  HTTP_3,\n  HTTP_BINARY,\n  TUNNEL_LITE,\n};\n\n/**\n * Returns a debugging name to refer to the given protocol.\n */\nextern const std::string& getCodecProtocolString(CodecProtocol);\n\n/**\n * Check if given debugging name refers to a valid protocol.\n */\nextern bool isValidCodecProtocolStr(folly::StringPiece protocolStr);\n\n/**\n * Get the protocol from the given debugging name.\n * If it's an invalid string, return the default protocol.\n */\nextern CodecProtocol getCodecProtocolFromStr(folly::StringPiece protocolStr);\n\n/**\n * Check if the given protocol is HTTP 1.1\n */\nextern bool isHTTP1_1CodecProtocol(CodecProtocol protocol);\n\n/**\n * Check if the given protocol is HTTP2.\n */\nextern bool isHTTP2CodecProtocol(CodecProtocol protocol);\n\n/**\n * Check if the given protocol is HQ\n */\nextern bool isHQCodecProtocol(CodecProtocol protocol);\n\n/**\n * Check if the given protocol is HTTP_BINARY\n */\nextern bool isHTTPBinaryCodecProtocol(CodecProtocol protocol);\n\n/**\n * Check if the given protocol supports paraellel requests\n */\nextern bool isParallelCodecProtocol(CodecProtocol protocol);\n\n/**\n * Check whether server has accepted client's upgrade request\n */\nextern bool serverAcceptedUpgrade(const std::string& clientUpgrade,\n                                  const std::string& serverUpgrade);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CodecUtil.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\n#include <folly/String.h>\n#include <proxygen/lib/http/RFC2616.h>\n\nnamespace proxygen {\n\n/**\n * RFC 7230:\n *  token = 1*tchar\n *  tchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" /\n *          \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n *\n * Earlier versions of this table allowed ' ', '\"', '/' and '}'\n */\n// clang-format off\nconst std::array<char, 256> CodecUtil::http_tokens = {{\n/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  32 sp    33  !    34  \"    35  #    36  $    37  %    38  &    39  '  */\n        1,      '!',      1,     '#',     '$',     '%',     '&',    '\\'',\n/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */\n        0,       0,      '*',     '+',      0,      '-',     '.',      1,\n/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */\n       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',\n/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */\n       '8',     '9',      0,       0,       0,       0,       0,       0,\n/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */\n        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/*  88  X    89  Y    90  Z    91  [    92  \\    93  ]    94  ^    95  _  */\n       'x',     'y',     'z',      0,       0,       0,      '^',     '_',\n/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */\n       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */\n       'x',     'y',     'z',      0,      '|',      1,     '~',       0\n}};\n// clang-format on\n\nbool CodecUtil::hasGzipAndDeflate(const std::string& value,\n                                  bool& hasGzip,\n                                  bool& hasDeflate) {\n  RFC2616::TokenPairVec output;\n  output.reserve(RFC2616::kTokenPairVecDefaultSize);\n  hasGzip = false;\n  hasDeflate = false;\n  RFC2616::parseQvalues(value, output);\n  for (const auto& encodingQ : output) {\n    std::string lower(encodingQ.first.str());\n    folly::toLowerAscii(lower);\n    // RFC says 3 sig figs\n    if (lower == \"gzip\" && encodingQ.second >= 0.001) {\n      hasGzip = true;\n    } else if (lower == \"deflate\" && encodingQ.second >= 0.001) {\n      hasDeflate = true;\n    }\n  }\n  return hasGzip && hasDeflate;\n}\n\nbool CodecUtil::appendHeaders(const HTTPHeaders& inputHeaders,\n                              std::vector<compress::Header>& headers,\n                              HTTPHeaderCode headerToCheck) {\n  bool headerToCheckExists = false;\n  // Add the HTTP headers supplied by the caller, but skip\n  // any per-hop headers that aren't supported in HTTP/2.\n  inputHeaders.forEachWithCode([&](HTTPHeaderCode code,\n                                   const std::string& name,\n                                   const std::string& value) {\n    const auto& s_disallowedModernHTTPFields = disallowedModernHTTPFields();\n    if (s_disallowedModernHTTPFields[code] || name.size() == 0 ||\n        name[0] == ':') {\n      DCHECK_GT(name.size(), 0) << \"Empty header\";\n      DCHECK_NE(name[0], ':') << \"Invalid header=\" << name;\n      return;\n    }\n    // Note this code will not drop headers named by Connection.  That's the\n    // caller's job\n\n    // see HTTP/2 spec, 8.1.2\n    DCHECK(name != \"TE\" || value == \"trailers\");\n    if ((name.size() > 0 && name[0] != ':') && code != HTTP_HEADER_HOST) {\n      headers.emplace_back(code, name, value);\n    }\n    if (code == headerToCheck) {\n      headerToCheckExists = true;\n    }\n  });\n\n  return headerToCheckExists;\n}\n\nconst std::bitset<256>& CodecUtil::disallowedModernHTTPFields() {\n  static const std::bitset<256> s_disallowedModernHTTPFields{[] {\n    std::bitset<256> bs;\n    // HTTP/1.x per-hop headers that have no meaning in HTTP/2\n    bs[HTTP_HEADER_CONNECTION] = true;\n    bs[HTTP_HEADER_HOST] = true;\n    bs[HTTP_HEADER_KEEP_ALIVE] = true;\n    bs[HTTP_HEADER_PROXY_CONNECTION] = true;\n    bs[HTTP_HEADER_TRANSFER_ENCODING] = true;\n    bs[HTTP_HEADER_UPGRADE] = true;\n    bs[HTTP_HEADER_SEC_WEBSOCKET_KEY] = true;\n    bs[HTTP_HEADER_SEC_WEBSOCKET_ACCEPT] = true;\n    return bs;\n  }()};\n  return s_disallowedModernHTTPFields;\n}\n\nstd::string CodecUtil::debugString(const HTTPMessage& msg, uint8_t debugLevel) {\n  std::string debug;\n  if (msg.isRequest()) {\n    debug.append(\n        folly::to<std::string>(\": URL(\",\n                               msg.getURL().size(),\n                               \")\",\n                               (debugLevel > 1 ? msg.getURL() : std::string()),\n                               \", \"));\n  }\n  return debug;\n}\n\nstd::string CodecUtil::debugString(const HTTPHeaders& headers,\n                                   uint8_t debugLevel) {\n  std::string debug;\n  if (debugLevel > 0) {\n    headers.forEach([&debug, debugLevel](const auto& name, const auto& value) {\n      debug.append(\n          folly::to<std::string>(name,\n                                 \"(\",\n                                 value.size(),\n                                 \")\",\n                                 (debugLevel > 1 ? value : std::string()),\n                                 \", \"));\n    });\n  }\n  return debug;\n}\n\nvoid CodecUtil::logIfFieldSectionExceedsPeerMax(\n    const HTTPHeaderSize& encodedSize,\n    uint32_t maxHeaderListSize,\n    std::string debugStr,\n    const HTTPHeaders& fields,\n    uint8_t debugLevel) {\n  if (encodedSize.uncompressed > maxHeaderListSize) {\n    // The remote side told us they don't want headers this large, but try\n    // anyways\n    debugStr += CodecUtil::debugString(fields, debugLevel);\n    LOG(ERROR) << \"generating HEADERS frame larger than peer maximum nHeaders=\"\n               << fields.size() << \" all headers=\" << debugStr;\n  }\n}\n\nstd::unique_ptr<folly::IOBuf> CodecUtil::zeroedBuffer(uint16_t size) {\n  // Technically this is 64KiB - 1 byte, but close enough\n  static_assert(std::numeric_limits<decltype(size)>::max() <= 65535);\n  static const std::vector<uint8_t> k64KiBVec(\n      // NOLINTNEXTLINE(facebook-hte-ContextDependentStaticInit)\n      std::numeric_limits<decltype(size)>::max());\n  return folly::IOBuf::wrapBuffer(k64KiBVec.data(), size);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/CodecUtil.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <array>\n#include <assert.h>\n#include <folly/Range.h>\n#include <folly/portability/Windows.h> // for windows compatibility: STRICT maybe defined by some win headers\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/compress/Header.h>\n#include <proxygen/lib/utils/UtilInl.h>\n#include <stdint.h>\n#include <string>\n\nnamespace proxygen {\n\n/**\n * On some mobile platforms we don't always get a chance to stop proxygen\n * eventbase. That leads to static object being cleaned up by system while\n * proxygen tries to access them, which is bad. The speedup from making\n * some variable static isn't necessary on mobile clients anyway.\n */\n#ifdef FOLLY_MOBILE\n#define CODEC_STATIC\n#else\n#define CODEC_STATIC static\n#endif\n\nfolly::Optional<HTTPPriority> parseHTTPPriorityString(\n    folly::StringPiece priorityString);\n\nclass CodecUtil {\n public:\n  // If these are needed elsewhere, we can move them to a more generic\n  // namespace/class later\n  static const std::array<char, 256> http_tokens;\n\n  static bool validateURL(std::string_view url, URLValidateMode mode) {\n    return proxygen::validateURL(url, mode);\n  }\n\n  static bool validateMethod(folly::ByteRange method) {\n    for (auto p = method.begin(); p != method.end(); p++) {\n      // '-' is valid except for start and end\n      if (*p == '-' && p != method.begin() && p != method.end()) {\n        continue;\n      }\n      if (!isAlpha(*p)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  static bool validateScheme(folly::ByteRange scheme) {\n    for (auto p : scheme) {\n      if (!isAlpha(p)) {\n        // scheme can only contain alphabetic characters\n        return false;\n      }\n    }\n    return true;\n  }\n\n  enum HeaderNameValidationMode {\n    HEADER_NAME_STRICT_COMPAT,\n    HEADER_NAME_STRICT\n  };\n  static bool validateHeaderName(folly::ByteRange name,\n                                 HeaderNameValidationMode mode) {\n    if (name.size() == 0) {\n      return false;\n    }\n    for (uint8_t p : name) {\n      if (mode == HEADER_NAME_STRICT_COMPAT) {\n        // Allows ' ', '\"', '/', '}' and high ASCII\n        if (p < 0x80 && !http_tokens[p]) {\n          return false;\n        }\n      } else {\n        if ((p < 0x80 && (http_tokens[p] != p)) || p >= 0x80) {\n          return false;\n        }\n      }\n    }\n    return true;\n  }\n\n  /**\n   * RFC2616 allows certain control chars in header values if they are\n   * quoted and escaped.\n   * When mode is COMPLIANT, then this is allowed.\n   * When mode is STRICT*, no escaped CTLs are allowed\n   *\n   * An unfortunate side effect when this function moved from signed to unsigned\n   * chars, the high-ASCII check was broken.  Temporarily continue to allow this\n   * with a special mode.\n   */\n  enum CtlEscapeMode { COMPLIANT, STRICT_COMPAT, STRICT };\n\n  static bool validateHeaderValue(folly::ByteRange value, CtlEscapeMode mode) {\n    bool escape = false;\n    bool quote = false;\n    enum {\n      lws_none,\n      lws_expect_nl,\n      lws_expect_ws1,\n      lws_expect_ws2\n    } state = lws_none;\n\n    for (auto p = std::begin(value); p != std::end(value); ++p) {\n      if (escape) {\n        escape = false;\n        if (mode == COMPLIANT) {\n          // prev char escaped.  Turn off escape and go to next char\n          // COMPLIANT mode only\n          assert(quote);\n          continue;\n        }\n      }\n      switch (state) {\n        case lws_none:\n          switch (*p) {\n            case '\\\\':\n              if (quote) {\n                escape = true;\n              }\n              break;\n            case '\\\"':\n              quote = !quote;\n              break;\n            case '\\r':\n              state = lws_expect_nl;\n              break;\n            default:\n              if ((*p < 0x20 && *p != '\\t') || (*p == 0x7f) ||\n                  (*p > 0x7f && mode == STRICT)) {\n                // unexpected ctl per rfc2616, HT OK\n                return false;\n              }\n              break;\n          }\n          break;\n        case lws_expect_nl:\n          if (*p != '\\n') {\n            // unescaped \\r must be LWS\n            return false;\n          }\n          state = lws_expect_ws1;\n          break;\n        case lws_expect_ws1:\n          if (*p != ' ' && *p != '\\t') {\n            // unescaped \\r\\n must be LWS\n            return false;\n          }\n          state = lws_expect_ws2;\n          break;\n        case lws_expect_ws2:\n          if (*p != ' ' && *p != '\\t') {\n            // terminated LWS\n            state = lws_none;\n            // check this char again\n            p--;\n          }\n          break;\n        default:\n          break;\n      }\n    }\n    // Unterminated quotes are OK, since the value can be* TEXT which treats\n    // the \" like any other char.\n    // Unterminated escapes are bad because it will escape the next character\n    // when converting to HTTP\n    // Unterminated LWS (dangling \\r or \\r\\n) is bad because it could\n    // prematurely terminate the headers when converting to HTTP\n    return !escape && (state == lws_none || state == lws_expect_ws2);\n  }\n\n  static bool hasGzipAndDeflate(const std::string& value,\n                                bool& hasGzip,\n                                bool& hasDeflate);\n\n  static bool appendHeaders(const HTTPHeaders& inputHeaders,\n                            std::vector<compress::Header>& headers,\n                            HTTPHeaderCode headerToCheck);\n\n  static const std::bitset<256>& disallowedModernHTTPFields();\n\n  /**\n   * Generates debug strings that omit the header values\n   */\n  static std::string debugString(const HTTPMessage& msg, uint8_t debugLevel);\n  static std::string debugString(const HTTPHeaders& hdrs, uint8_t debugLevel);\n\n  static void logIfFieldSectionExceedsPeerMax(const HTTPHeaderSize& encodedSize,\n                                              uint32_t maxHeaderListSize,\n                                              std::string debugStr,\n                                              const HTTPHeaders& fields,\n                                              uint8_t debugLevel);\n\n  /**\n   * Clones and returns a lazily-initialized IOBuf of all zeroes, up to\n   * 64KiB (2^16 bytes).\n   */\n  static std::unique_ptr<folly::IOBuf> zeroedBuffer(uint16_t size);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/ControlMessageRateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n\nnamespace proxygen {\n\nclass ControlMessageRateLimiter : public RateLimiter {\n public:\n  static const uint32_t kDefaultMaxEventsPerInterval = 50000;\n  static const uint32_t kMaxEventsPerIntervalLowerBound = 100;\n  static constexpr std::chrono::milliseconds kDefaultTimeoutDuration{100};\n\n  explicit ControlMessageRateLimiter(folly::HHWheelTimer* timer,\n                                     HTTPSessionStats* httpSessionStats)\n      : RateLimiter(timer, httpSessionStats) {\n    maxEventsInInterval_ = kDefaultMaxEventsPerInterval;\n    timeoutDuration_ = kDefaultTimeoutDuration;\n  }\n\n  void recordNumEventsInCurrentInterval(uint32_t numEvents) override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordControlMsgsInInterval(numEvents);\n    }\n  }\n\n  void recordRateLimitBreached() override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordControlMsgRateLimited();\n    }\n  }\n\n  [[nodiscard]] uint32_t getMaxEventsPerInvervalLowerBound() const override {\n    return kMaxEventsPerIntervalLowerBound;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/DebugFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/container/EvictingCacheMap.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/utils/Logging.h>\n\nnamespace proxygen {\nclass DebugFilter : public PassThroughHTTPCodecFilter {\n public:\n  explicit DebugFilter(\n      std::string traceHeaderName,\n      size_t maxBufSize = 10000,\n      std::function<void(std::unique_ptr<folly::IOBuf>)> dumpFn = nullptr)\n      : traceHeaderName_(std::move(traceHeaderName)),\n        maxBufSize_(maxBufSize),\n        dumpFn_(std::move(dumpFn)) {\n  }\n\n private:\n  size_t onIngress(const folly::IOBuf& buf) override {\n    ingressBuffer_.append(buf.clone());\n    if (ingressBuffer_.chainLength() > maxBufSize_) {\n      ingressBuffer_.trimStart(ingressBuffer_.chainLength() - maxBufSize_);\n    }\n    return call_->onIngress(buf);\n  }\n\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         std::unique_ptr<HTTPMessage> msg) override {\n    if (shouldTrackStream(*msg)) {\n      trackingStreams_.insert(streamID, getCurrentTime());\n    }\n    callback_->onHeadersComplete(streamID, std::move(msg));\n  }\n\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID streamID,\n                           ErrorCode code) override {\n    if (shouldDumpStream(streamID)) {\n      VLOG(2) << \"generateRstStream, streamID=\" << streamID\n              << \" error=\" << getErrorCodeString(code);\n      dump();\n    }\n    return call_->generateRstStream(writeBuf, streamID, code);\n  }\n\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream = MaxStreamID,\n      ErrorCode code = ErrorCode::NO_ERROR,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override {\n    if (code != ErrorCode::NO_ERROR) {\n      VLOG(2) << \"generateGoaway, lastStream=\" << lastStream\n              << \" error=\" << getErrorCodeString(code);\n      dump();\n    }\n    return call_->generateGoaway(\n        writeBuf, lastStream, code, std::move(debugData));\n  }\n\n  bool shouldDumpStream(HTTPCodec::StreamID streamID) {\n    return (streamID == 0 ||\n            trackingStreams_.find(streamID) != trackingStreams_.end());\n  }\n\n  bool shouldTrackStream(const HTTPMessage& msg) {\n    return msg.getHeaders().exists(traceHeaderName_);\n  }\n\n  void onAbort(StreamID streamID, ErrorCode code) override {\n    if (shouldDumpStream(streamID)) {\n      VLOG(2) << \"onAbort, streamID=\" << streamID\n              << \" error=\" << getErrorCodeString(code);\n      dump();\n    }\n    callback_->onAbort(streamID, code);\n  }\n\n  void onError(HTTPCodec::StreamID streamID,\n               const HTTPException& error,\n               bool newTxn) override {\n    // newTxn\n    if (shouldDumpStream(streamID) ||\n        (error.getPartialMsg() && shouldTrackStream(*error.getPartialMsg()))) {\n      VLOG(2) << \"onError, streamID=\" << streamID << \" error=\" << error.what();\n      dump();\n    }\n    callback_->onError(streamID, error, newTxn);\n  }\n\n  void onGoaway(uint64_t lastStream,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData) override {\n    if (code != ErrorCode::NO_ERROR) {\n      VLOG(2) << \"onGoaway, lastStream=\" << lastStream\n              << \" error=\" << getErrorCodeString(code)\n              << \" debugData=\" << IOBufPrinter::printHexFolly(debugData.get());\n      dump();\n    }\n    callback_->onGoaway(lastStream, code, std::move(debugData));\n  }\n\n  void dump() {\n    auto ingress = ingressBuffer_.move();\n    if (ingress) {\n      if (dumpFn_) {\n        dumpFn_(std::move(ingress));\n      } else {\n        VLOG(2) << IOBufPrinter::printHexFolly(ingress.get());\n      }\n    }\n  }\n\n  constexpr static size_t kMaxTrackingStreams = 100;\n\n  std::string traceHeaderName_;\n  size_t maxBufSize_{10000};\n  folly::EvictingCacheMap<HTTPCodec::StreamID,\n                          std::chrono::steady_clock::time_point>\n      trackingStreams_{kMaxTrackingStreams};\n  folly::IOBufQueue ingressBuffer_{folly::IOBufQueue::cacheChainLength()};\n  std::function<void(std::unique_ptr<folly::IOBuf>)> dumpFn_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/DefaultHTTPCodecFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n\nnamespace proxygen {\n\nDefaultHTTPCodecFactory::DefaultHTTPCodecFactory(CodecConfig config)\n    : HTTPCodecFactory(config) {\n}\n\nstd::unique_ptr<HTTPCodec> DefaultHTTPCodecFactory::getCodec(\n    const std::string& chosenProto, TransportDirection direction, bool isTLS) {\n\n  auto config = configFn_();\n  auto codecProtocol = getCodecProtocolFromStr(chosenProto);\n  switch (codecProtocol) {\n    case CodecProtocol::HTTP_2: {\n      auto codec = std::make_unique<HTTP2Codec>(direction);\n      codec->setStrictValidation(config.strictValidation);\n      if (config.h2.headerIndexingStrategy) {\n        codec->setHeaderIndexingStrategy(config.h2.headerIndexingStrategy);\n      }\n      codec->setDebugLevel(config.debugLevel);\n      return codec;\n    }\n    case CodecProtocol::HQ:\n    case CodecProtocol::HTTP_3: {\n      LOG(WARNING) << __func__ << \" doesn't yet support H3\";\n      return nullptr;\n    }\n    case CodecProtocol::HTTP_BINARY:\n      LOG(WARNING) << __func__ << \" doesn't yet support HTTPBinaryCodec\";\n      return nullptr;\n    case CodecProtocol::HTTP_1_1: {\n      if (!chosenProto.empty() &&\n          !HTTP1xCodec::supportsNextProtocol(chosenProto)) {\n        LOG(ERROR) << \"Chosen protocol \\\"\" << chosenProto\n                   << \"\\\" is unimplemented. \";\n        return nullptr;\n      }\n\n      return std::make_unique<HTTP1xCodec>(\n          direction, config.h1.forceHTTP1xCodecTo1_1, config.strictValidation);\n    }\n    case CodecProtocol::TUNNEL_LITE:\n      LOG(WARNING) << __func__ << \" doesn't support TUNNEL_LITE\";\n      return nullptr;\n    default:\n      // should be unreachable, getCodecProtocolFromStr returns HTTP_1_1 by\n      // default\n      return nullptr;\n  }\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/DefaultHTTPCodecFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <list>\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n\nnamespace proxygen {\nclass HeaderIndexingStrategy;\n\nclass DefaultHTTPCodecFactory : public HTTPCodecFactory {\n public:\n  DefaultHTTPCodecFactory() = default;\n  explicit DefaultHTTPCodecFactory(CodecConfig config);\n  ~DefaultHTTPCodecFactory() override = default;\n\n  /**\n   * Get a codec instance\n   */\n  std::unique_ptr<HTTPCodec> getCodec(const std::string& nextProtocol,\n                                      TransportDirection direction,\n                                      bool isTLS) override;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/DirectErrorsRateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n\nnamespace proxygen {\n\nclass DirectErrorsRateLimiter : public RateLimiter {\n public:\n  static const uint32_t kDefaultMaxEventsPerInterval = 100;\n  static const uint32_t kMaxEventsPerIntervalLowerBound = 50;\n  static constexpr std::chrono::milliseconds kDefaultTimeoutDuration{100};\n\n  explicit DirectErrorsRateLimiter(folly::HHWheelTimer* timer,\n                                   HTTPSessionStats* httpSessionStats)\n      : RateLimiter(timer, httpSessionStats) {\n    maxEventsInInterval_ = kDefaultMaxEventsPerInterval;\n    timeoutDuration_ = kDefaultTimeoutDuration;\n  }\n\n  void recordNumEventsInCurrentInterval(uint32_t /* numEvents */) override {\n    // We don't currently record the number of direct errors in an interval\n  }\n\n  void recordRateLimitBreached() override {\n    // We don't currently record how frequenlty we breach the direct errors\n    // rate limit in an interval\n  }\n\n  [[nodiscard]] uint32_t getMaxEventsPerInvervalLowerBound() const override {\n    return kMaxEventsPerIntervalLowerBound;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/ErrorCode.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/ErrorCode.h>\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\nconst char* getErrorCodeString(ErrorCode error) {\n  switch (error) {\n    case ErrorCode::NO_ERROR:\n      return \"NO_ERROR\";\n    case ErrorCode::PROTOCOL_ERROR:\n      return \"PROTOCOL_ERROR\";\n    case ErrorCode::INTERNAL_ERROR:\n      return \"INTERNAL_ERROR\";\n    case ErrorCode::FLOW_CONTROL_ERROR:\n      return \"FLOW_CONTROL_ERROR\";\n    case ErrorCode::SETTINGS_TIMEOUT:\n      return \"SETTINGS_TIMEOUT\";\n    case ErrorCode::STREAM_CLOSED:\n      return \"STREAM_CLOSED\";\n    case ErrorCode::FRAME_SIZE_ERROR:\n      return \"FRAME_SIZE_ERROR\";\n    case ErrorCode::REFUSED_STREAM:\n      return \"REFUSED_STREAM\";\n    case ErrorCode::CANCEL:\n      return \"CANCEL\";\n    case ErrorCode::COMPRESSION_ERROR:\n      return \"COMPRESSION_ERROR\";\n    case ErrorCode::CONNECT_ERROR:\n      return \"CONNECT_ERROR\";\n    case ErrorCode::ENHANCE_YOUR_CALM:\n      return \"ENHANCE_YOUR_CALM\";\n    case ErrorCode::INADEQUATE_SECURITY:\n      return \"INADEQUATE_SECURITY\";\n    case ErrorCode::HTTP_1_1_REQUIRED:\n      return \"HTTP_1_1_REQUIRED\";\n    case ErrorCode::MAX:\n      return \"MAX\";\n  }\n  LOG(FATAL) << \"Unreachable\";\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/ErrorCode.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\n#define RETURN_IF_ERROR(err)                                       \\\n  if (err != ErrorCode::NO_ERROR) {                                \\\n    VLOG(4) << \"Returning with error=\" << getErrorCodeString(err); \\\n    return err;                                                    \\\n  }                                                                \\\n  static_assert(true, \"semicolon required\")\n\nnamespace proxygen {\n\n// Error codes are 32-bit fields that are used in RST_STREAM and GOAWAY\n// frames to convey the reasons for the stream or connection error.\n\n// We only need <1 byte to represent it in memory\nenum class ErrorCode : uint8_t {\n  NO_ERROR = 0,\n  PROTOCOL_ERROR = 1,\n  INTERNAL_ERROR = 2,\n  FLOW_CONTROL_ERROR = 3,\n  SETTINGS_TIMEOUT = 4,\n  STREAM_CLOSED = 5,\n  FRAME_SIZE_ERROR = 6,\n  REFUSED_STREAM = 7,\n  CANCEL = 8,\n  COMPRESSION_ERROR = 9,\n  CONNECT_ERROR = 10,\n  ENHANCE_YOUR_CALM = 11,\n  INADEQUATE_SECURITY = 12,\n  HTTP_1_1_REQUIRED = 13,\n  MAX,\n};\n\n/**\n * Returns a string representation of the error code.\n */\nextern const char* getErrorCodeString(ErrorCode error);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/FlowControlFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/FlowControlFilter.h>\n\nnamespace proxygen {\n\nnamespace {\nHTTPException getException(const std::string& msg) {\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, msg);\n  ex.setCodecStatusCode(ErrorCode::FLOW_CONTROL_ERROR);\n  return ex;\n}\n\n} // namespace\n\nFlowControlFilter::FlowControlFilter(Callback& callback,\n                                     folly::IOBufQueue& writeBuf,\n                                     HTTPCodec* codec,\n                                     uint32_t recvCapacity)\n    : notify_(callback),\n      recvWindow_(codec->getDefaultWindowSize()),\n      sendWindow_(codec->getDefaultWindowSize()),\n      error_(false),\n      sendsBlocked_(false) {\n  if (recvCapacity > 0) {\n    if (recvCapacity < codec->getDefaultWindowSize()) {\n      VLOG(4) << \"Ignoring low conn-level recv window size of \" << recvCapacity;\n    } else if (recvCapacity > codec->getDefaultWindowSize()) {\n      auto delta = recvCapacity - codec->getDefaultWindowSize();\n      VLOG(4) << \"Incrementing default conn-level recv window by \" << delta;\n      CHECK(recvWindow_.setCapacity(recvCapacity));\n      codec->generateWindowUpdate(writeBuf, 0, delta);\n    }\n  }\n}\n\nvoid FlowControlFilter::setReceiveWindowSize(folly::IOBufQueue& writeBuf,\n                                             uint32_t capacity) {\n  if (capacity < recvWindow_.getCapacity()) {\n    VLOG(4) << \"Ignoring low conn-level recv window size of \" << capacity;\n    return;\n  }\n  int32_t delta = capacity - recvWindow_.getCapacity();\n  if (delta < 0) {\n    // For now, we're disallowing shrinking the window, since it can lead\n    // to FLOW_CONTROL_ERRORs if there is data in flight.\n    VLOG(4) << \"Refusing to shrink the recv window\";\n    return;\n  }\n  VLOG(4) << \"Incrementing default conn-level recv window by \" << delta;\n  if (!recvWindow_.setCapacity(capacity)) {\n    VLOG(2) << \"Failed setting conn-level recv window capacity to \" << capacity;\n    return;\n  }\n  toAck_ += delta;\n  if (toAck_ > 0) {\n    call_->generateWindowUpdate(writeBuf, 0, delta);\n    toAck_ = 0;\n  }\n}\n\nbool FlowControlFilter::ingressBytesProcessed(folly::IOBufQueue& writeBuf,\n                                              uint32_t delta) {\n  toAck_ += delta;\n  bool willAck =\n      (toAck_ > 0 && uint32_t(toAck_) > recvWindow_.getCapacity() / 2);\n  VLOG(4) << \"processed \" << delta << \" toAck_=\" << toAck_\n          << \" bytes, will ack=\" << willAck;\n  if (willAck) {\n    CHECK(recvWindow_.free(toAck_));\n    call_->generateWindowUpdate(writeBuf, 0, toAck_);\n    toAck_ = 0;\n    return true;\n  }\n  return false;\n}\n\nuint32_t FlowControlFilter::getAvailableSend() const {\n  return sendWindow_.getNonNegativeSize();\n}\n\nbool FlowControlFilter::isReusable() const {\n  if (error_) {\n    return false;\n  }\n  return call_->isReusable();\n}\n\nvoid FlowControlFilter::onBody(StreamID stream,\n                               std::unique_ptr<folly::IOBuf> chain,\n                               uint16_t padding) {\n  uint64_t amount = chain->computeChainDataLength();\n  if (!recvWindow_.reserve(amount + padding)) {\n    error_ = true;\n    HTTPException ex = getException(\n        folly::to<std::string>(\"Failed to reserve receive window, window size=\",\n                               recvWindow_.getSize(),\n                               \", amount=\",\n                               amount));\n    callback_->onError(0, ex, false);\n  } else {\n    if (VLOG_IS_ON(4) && recvWindow_.getSize() == 0) {\n      VLOG(4) << \"recvWindow full\";\n    }\n    toAck_ += padding;\n    CHECK(recvWindow_.free(padding));\n    callback_->onBody(stream, std::move(chain), padding);\n  }\n}\n\nvoid FlowControlFilter::onWindowUpdate(StreamID stream, uint32_t amount) {\n  if (!stream) {\n    bool success = sendWindow_.free(amount);\n    VLOG(4) << \"Remote side ack'd \" << amount\n            << \" bytes, sendWindow=\" << sendWindow_.getSize();\n    if (!success) {\n      LOG(WARNING) << \"Remote side sent connection-level WINDOW_UPDATE \"\n                   << \"that could not be applied. Aborting session.\";\n      // If something went wrong applying the flow control change, abort\n      // the entire session.\n      error_ = true;\n      HTTPException ex = getException(\n          folly::to<std::string>(\"Failed to update send window, outstanding=\",\n                                 sendWindow_.getOutstanding(),\n                                 \", amount=\",\n                                 amount));\n      callback_->onError(stream, ex, false);\n    }\n    if (sendsBlocked_ && sendWindow_.getNonNegativeSize()) {\n      VLOG(4) << \"Send window opened\";\n      sendsBlocked_ = false;\n      notify_.onConnectionSendWindowOpen();\n    }\n    // Don't forward.\n  } else {\n    callback_->onWindowUpdate(stream, amount);\n  }\n}\n\nsize_t FlowControlFilter::generateBody(folly::IOBufQueue& writeBuf,\n                                       StreamID stream,\n                                       std::unique_ptr<folly::IOBuf> chain,\n                                       folly::Optional<uint8_t> padding,\n                                       bool eom) {\n  uint8_t padLen = padding ? *padding : 0;\n  bool success = sendWindow_.reserve(chain->computeChainDataLength() + padLen);\n  VLOG(5) << \"Sending \" << chain->computeChainDataLength()\n          << \" bytes, sendWindow=\" << sendWindow_.getSize();\n\n  // In the future, maybe make this DCHECK\n  CHECK(success) << \"Session-level send window underflowed! \"\n                 << \"Too much data sent without WINDOW_UPDATES!\";\n\n  if (sendWindow_.getNonNegativeSize() == 0) {\n    // Need to inform when the send window is no longer full\n    VLOG(4) << \"Send window closed\";\n    sendsBlocked_ = true;\n    notify_.onConnectionSendWindowClosed();\n  }\n\n  return call_->generateBody(writeBuf, stream, std::move(chain), padding, eom);\n}\n\nsize_t FlowControlFilter::generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                                               StreamID stream,\n                                               uint32_t delta) {\n  CHECK(stream) << \" someone tried to manually manipulate a conn-level window\";\n  return call_->generateWindowUpdate(writeBuf, stream, delta);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/FlowControlFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/Window.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n\nnamespace folly {\nclass IOBufQueue;\n}\n\nnamespace proxygen {\n\n/**\n * This class implements the logic for managing per-session flow\n * control. Not every codec is interested in per-session flow control, so\n * this filter can only be added in that case or else it is an error.\n */\nclass FlowControlFilter : public PassThroughHTTPCodecFilter {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    /**\n     * Notification channel to alert when the send window state changes.\n     */\n    virtual void onConnectionSendWindowOpen() = 0;\n    virtual void onConnectionSendWindowClosed() = 0;\n  };\n\n  /**\n   * Construct a flow control filter.\n   * @param callback     A channel to be notified when the window is not\n   *                     full anymore.\n   * @param writeBuf     The buffer to write egress on. This constructor\n   *                     may generate a window update frame on this buffer.\n   * @param codec        The codec implementation.\n   * @param recvCapacity The initial size of the conn-level recv window.\n   *                     It must be >= codec->getDefaultWindowSize(), or it\n   *                     will generate an immediate window update into\n   *                     writeBuf. 0 means use the codec default.\n   */\n  explicit FlowControlFilter(Callback& callback,\n                             folly::IOBufQueue& writeBuf,\n                             HTTPCodec* codec,\n                             uint32_t recvCapacity = 0);\n\n  /**\n   * Modify the session receive window\n   *\n   * @param writeBuf     The buffer to write egress on. This constructor\n   *                     may generate a window update frame on this buffer.\n   * @param capacity     The initial size of the conn-level recv window.\n   *                     It must be >= the codec default.\n   */\n  void setReceiveWindowSize(folly::IOBufQueue& writeBuf, uint32_t capacity);\n\n  /**\n   * Notify the flow control filter that some ingress bytes were\n   * processed. If the number of bytes to acknowledge exceeds half the\n   * receive window's capacity, a WINDOW_UPDATE frame will be written.\n   * @param writeBuf The buffer to write egress on. This function may\n   *                 generate a WINDOW_UPDATE on this buffer.\n   * @param delta    The number of bytes that were processed.\n   * @returns true iff we wrote a WINDOW_UPDATE frame to the write buf.\n   */\n  bool ingressBytesProcessed(folly::IOBufQueue& writeBuf, uint32_t delta);\n\n  /**\n   * @returns the number of bytes available in the connection-level send window\n   */\n  [[nodiscard]] uint32_t getAvailableSend() const;\n\n  // Filter functions\n\n  [[nodiscard]] bool isReusable() const override;\n\n  void onBody(StreamID stream,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override;\n\n  void onWindowUpdate(StreamID stream, uint32_t amount) override;\n\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n\n  size_t generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                              StreamID stream,\n                              uint32_t delta) override;\n\n  [[nodiscard]] const Window& getSendWindow() const {\n    return sendWindow_;\n  }\n\n  [[nodiscard]] const Window& getRecvWindow() const {\n    return recvWindow_;\n  }\n\n private:\n  Callback& notify_;\n  Window recvWindow_;\n  Window sendWindow_;\n  int32_t toAck_{0};\n  bool error_ : 1;\n  bool sendsBlocked_ : 1;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQControlCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n\n#include <folly/Random.h>\n\nnamespace {\nusing namespace proxygen::hq;\n\nuint64_t drainingId(proxygen::TransportDirection dir) {\n  if (dir == proxygen::TransportDirection::DOWNSTREAM) {\n    return kMaxClientBidiStreamId;\n  } else {\n    return kMaxPushId + 1;\n  }\n}\n\n} // namespace\n\nnamespace proxygen::hq {\n\nusing namespace folly::io;\n\nParseResult HQControlCodec::checkFrameAllowed(FrameType type) {\n  switch (type) {\n    case hq::FrameType::DATA:\n    case hq::FrameType::HEADERS:\n    case hq::FrameType::PUSH_PROMISE:\n    case hq::FrameType::WEBTRANSPORT_BIDI:\n      return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n    default:\n      break;\n  }\n\n  if (getStreamType() == hq::UnidirectionalStreamType::CONTROL) {\n    // SETTINGS MUST be the first frame on an HQ Control Stream\n    if (!receivedSettings_ && type != hq::FrameType::SETTINGS) {\n      return HTTP3::ErrorCode::HTTP_MISSING_SETTINGS;\n    }\n    // multiple SETTINGS frames are not allowed\n    if (receivedSettings_ && type == hq::FrameType::SETTINGS) {\n      return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n    }\n    // A client MUST treat the receipt of a MAX_PUSH_ID frame as a connection\n    // error of type HTTP_FRAME_UNEXPECTED\n    if (transportDirection_ == TransportDirection::UPSTREAM &&\n        type == hq::FrameType::MAX_PUSH_ID) {\n      return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n    }\n\n    // PRIORITY_UPDATE is downstream control codec only\n    if (transportDirection_ == TransportDirection::UPSTREAM &&\n        (type == hq::FrameType::PRIORITY_UPDATE ||\n         type == hq::FrameType::PUSH_PRIORITY_UPDATE ||\n         type == hq::FrameType::FB_PUSH_PRIORITY_UPDATE ||\n         type == hq::FrameType::FB_PRIORITY_UPDATE)) {\n      return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n    }\n  }\n\n  return folly::none;\n}\n\nParseResult HQControlCodec::parseCancelPush(Cursor& cursor,\n                                            const FrameHeader& header) {\n  PushId outPushId;\n  auto res = hq::parseCancelPush(cursor, header, outPushId);\n  return res;\n}\n\nParseResult HQControlCodec::parseSettings(Cursor& cursor,\n                                          const FrameHeader& header) {\n  VLOG(4) << \"parsing SETTINGS frame length=\" << header.length;\n  CHECK(isIngress());\n  std::deque<SettingPair> outSettings;\n  receivedSettings_ = true;\n  auto res = hq::parseSettings(cursor, header, outSettings);\n  VLOG(4) << \"Received n=\" << outSettings.size() << \" settings\";\n  if (res) {\n    return res;\n  }\n\n  CHECK(isIngress());\n  auto& ingressSettings = settings_;\n  SettingsList settingsList;\n  for (auto& setting : outSettings) {\n    switch (setting.first) {\n      case hq::SettingId::HEADER_TABLE_SIZE:\n      case hq::SettingId::MAX_HEADER_LIST_SIZE:\n      case hq::SettingId::QPACK_BLOCKED_STREAMS:\n      case hq::SettingId::H3_WT_MAX_SESSIONS:\n      case hq::SettingId::WT_INITIAL_MAX_DATA:\n        break;\n      case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n      case hq::SettingId::H3_DATAGRAM:\n      case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n      case hq::SettingId::H3_DATAGRAM_RFC:\n      case hq::SettingId::ENABLE_WEBTRANSPORT:\n        // only 0/1 are legal\n        if (setting.second > 1) {\n          return HTTP3::ErrorCode::HTTP_SETTINGS_ERROR;\n        }\n        break;\n      default:\n        continue; // ignore unknown settings\n    }\n    auto httpSettingId = hqToHttpSettingsId(setting.first);\n    ingressSettings.setSetting(*httpSettingId, setting.second);\n    settingsList.push_back(*ingressSettings.getSetting(*httpSettingId));\n  }\n\n  if (callback_) {\n    callback_->onSettings(settingsList);\n  }\n  return folly::none;\n}\n\nParseResult HQControlCodec::parseGoaway(Cursor& cursor,\n                                        const FrameHeader& header) {\n  quic::StreamId outStreamId;\n  auto res = hq::parseGoaway(cursor, header, outStreamId);\n  if (!res && callback_) {\n    callback_->onGoaway(outStreamId, ErrorCode::NO_ERROR);\n  }\n  return res;\n}\n\nParseResult HQControlCodec::parseMaxPushId(Cursor& cursor,\n                                           const FrameHeader& header) {\n  quic::StreamId outPushId;\n  auto res = hq::parseMaxPushId(cursor, header, outPushId);\n  return res;\n}\n\nParseResult HQControlCodec::parsePriorityUpdate(Cursor& cursor,\n                                                const FrameHeader& header) {\n  HTTPCodec::StreamID prioritizedElement;\n  HTTPPriority priorityUpdate;\n  auto res = hq::parsePriorityUpdate(\n      cursor, header, prioritizedElement, priorityUpdate);\n  if (!res) {\n    callback_->onPriority(folly::to<quic::StreamId>(prioritizedElement),\n                          priorityUpdate);\n  }\n  return res;\n}\n\nParseResult HQControlCodec::parsePushPriorityUpdate(Cursor& cursor,\n                                                    const FrameHeader& header) {\n  HTTPCodec::StreamID prioritizedElement;\n  HTTPPriority priorityUpdate;\n  auto res = hq::parsePriorityUpdate(\n      cursor, header, prioritizedElement, priorityUpdate);\n  if (!res) {\n    callback_->onPushPriority(folly::to<PushId>(prioritizedElement),\n                              priorityUpdate);\n  }\n  return res;\n}\n\nbool HQControlCodec::isWaitingToDrain() const {\n  return (!doubleGoaway_ && !sentGoaway_) ||\n         (doubleGoaway_ && sentGoaway_ && !sentFinalGoaway_);\n}\n\nuint64_t HQControlCodec::finalGoawayId() {\n  if (transportDirection_ == TransportDirection::DOWNSTREAM) {\n    return minUnseenStreamID_;\n  } else {\n    return minUnseenPushID_;\n  }\n}\n\nsize_t HQControlCodec::generateGoaway(\n    folly::IOBufQueue& writeBuf,\n    StreamID minUnseenId,\n    ErrorCode statusCode,\n    std::unique_ptr<folly::IOBuf> /*debugData*/) {\n  if (sentFinalGoaway_) {\n    return 0;\n  }\n  if (minUnseenId == HTTPCodec::MaxStreamID) {\n    if (statusCode != ErrorCode::NO_ERROR || isWaitingToDrain()) {\n      // Non-draining goaway, but the caller didn't know the ID\n      // HQSession doesn't use this path now\n      minUnseenId = finalGoawayId();\n      sentFinalGoaway_ = true;\n    } else {\n      // Draining goaway\n      minUnseenId = drainingId(transportDirection_);\n    }\n  } else {\n    // Non-draining goaway, caller supplied ID\n    sentFinalGoaway_ = true;\n  }\n  VLOG(4) << \"generating GOAWAY minUnseenId=\" << minUnseenId\n          << \" statusCode=\" << uint32_t(statusCode);\n\n  DCHECK_GE(egressGoawayAck_, minUnseenId);\n  egressGoawayAck_ = minUnseenId;\n  auto writeRes = hq::writeGoaway(writeBuf, minUnseenId);\n  if (writeRes.hasError()) {\n    LOG(FATAL) << \"error writing goaway with minUnseenId=\" << minUnseenId;\n  }\n  sentGoaway_ = true;\n  return *writeRes;\n}\n\nsize_t HQControlCodec::generateSettings(folly::IOBufQueue& writeBuf) {\n  CHECK(!sentSettings_);\n  sentSettings_ = true;\n  std::deque<hq::SettingPair> settings;\n  for (auto& setting : getEgressSettings()->getAllSettings()) {\n    auto id = httpToHqSettingsId(setting.id);\n    // unknown ids will return folly::none\n    if (id) {\n      switch (*id) {\n        case hq::SettingId::HEADER_TABLE_SIZE:\n        case hq::SettingId::MAX_HEADER_LIST_SIZE:\n        case hq::SettingId::QPACK_BLOCKED_STREAMS:\n        case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n        case hq::SettingId::H3_DATAGRAM:\n        case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n        case hq::SettingId::H3_DATAGRAM_RFC:\n        case hq::SettingId::ENABLE_WEBTRANSPORT:\n        case hq::SettingId::H3_WT_MAX_SESSIONS:\n        case hq::SettingId::WT_INITIAL_MAX_DATA:\n          break;\n      }\n      settings.emplace_back(*id, (SettingValue)setting.value);\n    }\n  }\n  // add a random setting from the greasing pool\n  settings.emplace_back(\n      static_cast<SettingId>(*getGreaseId(folly::Random::rand32(16))),\n      static_cast<SettingValue>(0xFACEB00C));\n  auto writeRes = writeSettings(writeBuf, settings);\n  if (writeRes.hasError()) {\n    LOG(FATAL) << \"error writing settings frame\";\n  }\n  return *writeRes;\n}\n\nsize_t HQControlCodec::generatePriority(folly::IOBufQueue& writeBuf,\n                                        StreamID stream,\n                                        HTTPPriority priority) {\n  if (priority.urgency > quic::kDefaultMaxPriority) {\n    LOG(ERROR) << \"Attempt to generate invalid priority update with urgency=\"\n               << (uint64_t)priority.urgency;\n    return 0;\n  }\n  auto updateString = folly::to<std::string>(\n      \"u=\", priority.urgency, (priority.incremental ? \",i\" : \"\"));\n  auto writeRet = hq::writePriorityUpdate(writeBuf, stream, updateString);\n  if (writeRet.hasError()) {\n    LOG(ERROR) << \"error writing priority update, stream=\" << stream\n               << \", priority=\" << updateString;\n    return 0;\n  }\n  return *writeRet;\n}\n\nsize_t HQControlCodec::generatePushPriority(folly::IOBufQueue& writeBuf,\n                                            StreamID pushId,\n                                            HTTPPriority priority) {\n  if (priority.urgency > quic::kDefaultMaxPriority) {\n    LOG(ERROR)\n        << \"Attempt to generate invalid push priority update with urgency=\"\n        << (uint64_t)priority.urgency;\n    return 0;\n  }\n  auto updateString = folly::to<std::string>(\n      \"u=\", priority.urgency, (priority.incremental ? \",i\" : \"\"));\n  auto writeRet = hq::writePushPriorityUpdate(writeBuf, pushId, updateString);\n  if (writeRet.hasError()) {\n    LOG(ERROR) << \"error writing push priority update, pushId=\" << pushId\n               << \", priority=\" << updateString;\n    return 0;\n  }\n  return *writeRet;\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQControlCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HQFramedCodec.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\n#include <folly/io/IOBuf.h>\n#include <folly/lang/Assume.h>\n\nnamespace proxygen::hq {\n\nclass HQControlCodec\n    : public HQUnidirectionalCodec\n    , public HQFramedCodec {\n\n public:\n  HQControlCodec(\n      HTTPCodec::StreamID streamId,\n      TransportDirection direction,\n      StreamDirection streamDir,\n      HTTPSettings& settings,\n      UnidirectionalStreamType streamType = UnidirectionalStreamType::CONTROL)\n      : HQUnidirectionalCodec(streamType, streamDir),\n        HQFramedCodec(streamId, direction),\n        settings_(settings) {\n    VLOG(4) << \"creating \" << getTransportDirectionString(direction)\n            << \" HQ Control codec for stream \" << streamId_;\n    egressGoawayAck_ = direction == TransportDirection::UPSTREAM\n                           ? kMaxPushId + 1\n                           : kMaxClientBidiStreamId;\n  }\n\n  ~HQControlCodec() override = default;\n\n  // HQ Unidirectional Codec API\n  std::unique_ptr<folly::IOBuf> onUnidirectionalIngress(\n      std::unique_ptr<folly::IOBuf> buf) override {\n    CHECK(buf);\n    auto consumed = onFramedIngress(*buf);\n    folly::IOBufQueue q;\n    q.append(std::move(buf));\n    q.trimStart(consumed);\n    return q.move();\n  }\n\n  void onUnidirectionalIngressEOF() override {\n    LOG(ERROR) << \"Unexpected control stream EOF\";\n    if (callback_) {\n      HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                       \"Control stream EOF\");\n      ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n      callback_->onError(streamId_, ex, false);\n    }\n  }\n\n  // HTTPCodec API\n  bool isWaitingToDrain() const override;\n\n  CodecProtocol getProtocol() const override {\n    return CodecProtocol::HQ;\n  }\n\n  size_t onIngress(const folly::IOBuf& /*buf*/) override {\n    LOG(FATAL) << __func__ << \" not supported\";\n    folly::assume_unreachable();\n  }\n\n  void onIngressEOF() override {\n    // error\n  }\n\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream,\n      ErrorCode statusCode,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  size_t generateSettings(folly::IOBufQueue& writeBuf) override;\n\n  size_t generatePriority(folly::IOBufQueue& writeBuf,\n                          StreamID stream,\n                          HTTPPriority priority) override;\n\n  size_t generatePushPriority(folly::IOBufQueue& writeBuf,\n                              StreamID pushId,\n                              HTTPPriority priority) override;\n\n  const HTTPSettings* getIngressSettings() const override {\n    CHECK(isIngress());\n    return &settings_;\n  }\n\n  HTTPSettings* getEgressSettings() override {\n    CHECK(isEgress());\n    return &settings_;\n  }\n\n  const HTTPSettings* getEgressSettings() const {\n    CHECK(isEgress());\n    return &settings_;\n  }\n\n  void enableDoubleGoawayDrain() override {\n    doubleGoaway_ = true;\n  }\n\n  void disableDoubleGoawayDrain() override {\n    doubleGoaway_ = false;\n  }\n\n  uint32_t getDefaultWindowSize() const override {\n    CHECK(false) << __func__ << \" not supported\";\n    folly::assume_unreachable();\n  }\n\n  void setHeaderCodecStats(HeaderCodec::Stats* /*hcStats*/) override {\n    CHECK(false) << __func__ << \" not supported\";\n  }\n\n  CompressionInfo getCompressionInfo() const override {\n    CHECK(false) << __func__ << \" not supported\";\n    folly::assume_unreachable();\n  }\n\n  bool receivedSettings() const {\n    return receivedSettings_;\n  }\n\n protected:\n  ParseResult checkFrameAllowed(FrameType type) override;\n  ParseResult parseCancelPush(folly::io::Cursor& cursor,\n                              const FrameHeader& header) override;\n  ParseResult parseSettings(folly::io::Cursor& cursor,\n                            const FrameHeader& header) override;\n  ParseResult parseGoaway(folly::io::Cursor& cursor,\n                          const FrameHeader& header) override;\n  ParseResult parseMaxPushId(folly::io::Cursor& cursor,\n                             const FrameHeader& header) override;\n  ParseResult parsePriorityUpdate(folly::io::Cursor& cursor,\n                                  const FrameHeader& header) override;\n  ParseResult parsePushPriorityUpdate(folly::io::Cursor& cursor,\n                                      const FrameHeader& header) override;\n\n  uint64_t finalGoawayId();\n\n protected:\n  bool doubleGoaway_{true};\n  bool sentGoaway_{false};\n  bool sentFinalGoaway_{false};\n  bool receivedSettings_{false};\n  bool sentSettings_{false};\n  quic::StreamId egressGoawayAck_;\n  quic::StreamId minUnseenStreamID_{kMaxClientBidiStreamId};\n  uint64_t minUnseenPushID_{kMaxPushId + 1};\n  HTTPSettings& settings_;\n};\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQFramedCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HQFramedCodec.h>\n\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/utils/Logging.h>\n\n#include <folly/io/Cursor.h>\n#include <folly/tracing/ScopedTraceSection.h>\n\n#include <iomanip>\n\nnamespace proxygen::hq {\n\nusing namespace folly::io;\nusing namespace folly;\n\nParseResult HQFramedCodec::parseFrame(Cursor& cursor) {\n  switch (curHeader_.type) {\n    case hq::FrameType::DATA:\n      return parseData(cursor, curHeader_);\n    case hq::FrameType::HEADERS:\n      return parseHeaders(cursor, curHeader_);\n    case hq::FrameType::CANCEL_PUSH:\n      return parseCancelPush(cursor, curHeader_);\n    case hq::FrameType::SETTINGS:\n      return parseSettings(cursor, curHeader_);\n    case hq::FrameType::PUSH_PROMISE:\n      return parsePushPromise(cursor, curHeader_);\n    case hq::FrameType::GOAWAY:\n      return parseGoaway(cursor, curHeader_);\n    case hq::FrameType::MAX_PUSH_ID:\n      return parseMaxPushId(cursor, curHeader_);\n    case hq::FrameType::PRIORITY_UPDATE:\n    case hq::FrameType::PUSH_PRIORITY_UPDATE:\n    case hq::FrameType::FB_PRIORITY_UPDATE:\n    case hq::FrameType::FB_PUSH_PRIORITY_UPDATE:\n      return parsePriorityUpdate(cursor, curHeader_);\n    default:\n      // Implementations MUST ignore and discard any frame that has a\n      // type that is unknown\n      if (callback_) {\n        callback_->onUnknownFrame(streamId_,\n                                  static_cast<uint64_t>(curHeader_.type));\n      }\n      break;\n  }\n\n  VLOG(3) << \"Skipping frame (type=\" << (uint64_t)curHeader_.type << \")\";\n  cursor.skip(curHeader_.length);\n  return folly::none;\n}\n\nsize_t HQFramedCodec::onFramedIngress(const IOBuf& buf) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HQFramedCodec - onFramedIngress\");\n  // if for some reason onFramedIngress gets called again after erroring out,\n  // skip parsing\n  if (connError_ != folly::none) {\n    return 0;\n  }\n  Cursor cursor(&buf);\n  size_t parsedTot = 0;\n  auto bufLen = cursor.totalLength();\n  while (connError_ == folly::none && bufLen > 0 && !parserPaused_) {\n    size_t parsed = 0;\n    if (frameState_ == FrameState::FRAME_HEADER_TYPE) {\n      auto type = quic::follyutils::decodeQuicInteger(cursor);\n      if (!type) {\n        break;\n      }\n      curHeader_.type = FrameType(type->first);\n      parsed += type->second;\n      auto res = checkFrameAllowed(curHeader_.type);\n      if (res) {\n        VLOG(4) << \"Frame not allowed: 0x\" << std::setfill('0')\n                << std::setw(sizeof(uint64_t) * 2) << std::hex\n                << (uint64_t)curHeader_.type << \" on streamID=\" << streamId_;\n        connError_ = res;\n        break;\n      }\n      frameState_ = FrameState::FRAME_HEADER_LENGTH;\n    } else if (frameState_ == FrameState::FRAME_HEADER_LENGTH) {\n      auto length = quic::follyutils::decodeQuicInteger(cursor);\n      if (!length) {\n        break;\n      }\n      curHeader_.length = length->first;\n      parsed += length->second;\n      if (callback_) {\n        callback_->onFrameHeader(streamId_,\n                                 0, // no flags!\n                                 curHeader_.length,\n                                 static_cast<uint64_t>(curHeader_.type));\n      }\n#ifndef NDEBUG\n      receivedFrameCount_++;\n#endif\n      pendingDataFrameBytes_ = curHeader_.length;\n      if (curHeader_.length == 0) {\n        // If the frame length is zero, call parseFrame immediately.  It is up\n        // to each frame to determine whether length 0 is valid.\n        connError_ = parseFrame(cursor);\n        frameState_ = FrameState::FRAME_HEADER_TYPE;\n      } else {\n        // For DATA frames, move to the streaming state\n        if (curHeader_.type == FrameType::DATA) {\n          frameState_ = FrameState::FRAME_PAYLOAD_STREAMING;\n        } else {\n          frameState_ = FrameState::FRAME_PAYLOAD;\n        }\n      }\n    } else if (frameState_ == FrameState::FRAME_PAYLOAD) {\n      // Already parsed the common frame header\n      const auto frameLen = curHeader_.length;\n      if (bufLen >= frameLen) {\n        connError_ = parseFrame(cursor);\n        // if connError_ is set, it means there was a frame error,\n        // so it doesn't really matter whether we have actually parsed all the\n        // data or not\n        parsed += curHeader_.length;\n        frameState_ = FrameState::FRAME_HEADER_TYPE;\n      } else {\n        break;\n      }\n    } else if (bufLen > 0 &&\n               frameState_ == FrameState::FRAME_PAYLOAD_STREAMING) {\n      FrameHeader auxDataFrameHeader;\n      auxDataFrameHeader.type = FrameType::DATA;\n      auxDataFrameHeader.length = std::min(pendingDataFrameBytes_, bufLen);\n      connError_ = parseData(cursor, auxDataFrameHeader);\n      parsed += auxDataFrameHeader.length;\n      pendingDataFrameBytes_ -= auxDataFrameHeader.length;\n      if (pendingDataFrameBytes_ == 0) {\n        frameState_ = FrameState::FRAME_HEADER_TYPE;\n      }\n    }\n    CHECK_GE(bufLen, parsed);\n    bufLen -= parsed;\n    parsedTot += parsed;\n    totalBytesParsed_ += parsed;\n  }\n  checkConnectionError(connError_, &buf);\n  return parsedTot;\n}\n\nbool HQFramedCodec::onFramedIngressEOF() {\n  if (connError_ != folly::none) {\n    return false;\n  } else if (parserPaused_) {\n    deferredEOF_ = true;\n    return false;\n  } else if (frameState_ != FrameState::FRAME_HEADER_TYPE) {\n    VLOG(3) << \"Stream ended in the middle of a frame type=\" << curHeader_.type;\n    connError_ = HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n    checkConnectionError(connError_, nullptr);\n    return false;\n  }\n  // Caller will fire onMessageComplete\n  return true;\n}\n\nbool HQFramedCodec::checkConnectionError(ParseResult err,\n                                         const folly::IOBuf* buf) {\n  if (err != folly::none) {\n    LOG(ERROR) << \"Connection error with ingress=\";\n    if (buf) {\n      VLOG(3) << IOBufPrinter::printHexFolly(buf, true);\n    }\n    setParserPaused(true);\n    if (callback_) {\n      HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                       \"Connection error\");\n      ex.setHttp3ErrorCode(err.value());\n      callback_->onError(kSessionStreamId, ex, false);\n    }\n    return true;\n  }\n  return false;\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQFramedCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\n#include <folly/lang/Assume.h>\n\nnamespace proxygen::hq {\n\n/*\n * HQFramedCodec encapsulates the core logic for interfacing with the HQ Framer,\n * so that it can be used by both Stream and Control codecs.\n * It also provides common implementations of HTTPCodec APIs where it makes\n * sense, mostly for disabling calls that do not make sense for HQ\n */\nclass HQFramedCodec : public HTTPCodec {\n public:\n  explicit HQFramedCodec(HTTPCodec::StreamID streamId,\n                         TransportDirection direction)\n      : streamId_(streamId),\n        transportDirection_(direction),\n        frameState_(FrameState::FRAME_HEADER_TYPE) {\n  }\n\n  ~HQFramedCodec() override = default;\n\n  size_t onFramedIngress(const folly::IOBuf& buf);\n\n  // HTTPCodec API\n\n  // Only implemented in the Stream Codec\n  CodecProtocol getProtocol() const override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // Only implemented in the Stream Codec\n  const std::string& getUserAgent() const override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  TransportDirection getTransportDirection() const override {\n    return transportDirection_;\n  }\n\n  // Stream multiplexing handled at the transport\n  HTTPCodec::StreamID createStream() override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  void setCallback(proxygen::HTTPCodec::Callback* callback) override {\n    callback_ = callback;\n  }\n\n  bool isBusy() const override {\n    return false;\n  }\n\n  void setParserPaused(bool paused) override {\n    bool resumed = parserPaused_ && !paused;\n    parserPaused_ = paused;\n    if (!paused && deferredEOF_) {\n      deferredEOF_ = false;\n      onIngressEOF();\n    } else if (resumed && resumeHook_) {\n      resumeHook_();\n    }\n  }\n\n  void setResumeHook(folly::Function<void()> resumeHook) {\n    resumeHook_ = std::move(resumeHook);\n  }\n\n  bool isParserPaused() const override {\n    return parserPaused_;\n  }\n\n  bool isReusable() const override {\n    return false;\n  }\n\n  bool closeOnEgressComplete() const override {\n    return false;\n  }\n\n  bool supportsParallelRequests() const override {\n    return false;\n  }\n\n  // No h2c upgrade in HQ\n  bool onIngressUpgradeMessage(const HTTPMessage& /*msg*/) override {\n    return false;\n  }\n\n  // no connection preface for HQ\n  size_t generateConnectionPreface(folly::IOBufQueue& /*writeBuf*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Stream Codec\n  void generateHeader(folly::IOBufQueue& /*writeBuf*/,\n                      StreamID /*stream*/,\n                      const HTTPMessage& /*msg*/,\n                      bool /*eom = false*/,\n                      HTTPHeaderSize* /*size = nullptr*/,\n                      const folly::Optional<HTTPHeaders>&) override {\n    LOG(FATAL) << __func__ << \" must be implemented in child class\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Stream Codec\n  void generatePushPromise(folly::IOBufQueue& /*writeBuf*/,\n                           StreamID /*stream*/,\n                           const HTTPMessage& /*msg*/,\n                           StreamID /*assocStream*/,\n                           bool /*eom = false*/,\n                           HTTPHeaderSize* /*size = nullptr*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Stream Codec\n  size_t generateBody(folly::IOBufQueue& /* writeBuf*/,\n                      StreamID /*stream*/,\n                      std::unique_ptr<folly::IOBuf> /*chain*/,\n                      folly::Optional<uint8_t> /*padding*/,\n                      bool /*eom*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // HQ has no chunk headers\n  size_t generateChunkHeader(folly::IOBufQueue& /*writeBuf*/,\n                             StreamID /*stream*/,\n                             size_t /*length*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // HQ has no chunk terminators\n  size_t generateChunkTerminator(folly::IOBufQueue& /*writeBuf*/,\n                                 StreamID /*stream*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  size_t generateTrailers(folly::IOBufQueue& /*writeBuf*/,\n                          StreamID /*stream*/,\n                          const HTTPHeaders& /*trailers*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  size_t generatePadding(folly::IOBufQueue& /*writeBuf*/,\n                         StreamID /*stream*/,\n                         uint16_t /*bytes*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // Only valid for the Stream Codec\n  size_t generateEOM(folly::IOBufQueue& /*writeBuf*/,\n                     StreamID /*stream*/) override {\n    LOG(FATAL) << __func__ << \" must be implemented in child class\";\n    folly::assume_unreachable();\n  }\n\n  // Handled at the transport layer\n  size_t generateRstStream(folly::IOBufQueue& /*writeBuf*/,\n                           StreamID /*stream*/,\n                           ErrorCode /*statusCode*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Control Codec\n  size_t generateGoaway(\n      folly::IOBufQueue& /*writeBuf*/,\n      StreamID /*lastStream*/,\n      ErrorCode /*statusCode*/,\n      std::unique_ptr<folly::IOBuf> /*debugData = nullptr*/) override {\n    LOG(FATAL) << __func__ << \" must be implemented in child class\";\n    folly::assume_unreachable();\n  }\n\n  // Handled at the transport layer\n  size_t generatePingRequest(\n      folly::IOBufQueue& /*writeBuf*/,\n      folly::Optional<uint64_t> /* data */ = folly::none) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // Handled at the transport layer\n  size_t generatePingReply(folly::IOBufQueue& /*writeBuf*/,\n                           uint64_t /* data */) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Control Codec\n  size_t generateSettings(folly::IOBufQueue& /*writeBuf*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // No Settings ACK in HQ\n  size_t generateSettingsAck(folly::IOBufQueue& /*writeBuf*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // Flow Control Handled at the transport layer\n  size_t generateWindowUpdate(folly::IOBufQueue& /*writeBuf*/,\n                              StreamID /*stream*/,\n                              uint32_t /*delta*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Control Codec\n  const HTTPSettings* getIngressSettings() const override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // only valid for the Control Codec\n  HTTPSettings* getEgressSettings() override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // Flow Control Handled at the transport layer\n  uint32_t getDefaultWindowSize() const override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  bool supportsPushTransactions() const override {\n    return true;\n  }\n\n  bool peerHasWebsockets() const {\n    return false;\n  }\n\n  bool supportsExTransactions() const override {\n    return false;\n  }\n\n  void setHeaderCodecStats(HeaderCodec::Stats* /*hcStats*/) override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n  }\n\n  // only valid on the Stream Codec\n  bool isRequest(StreamID /*id*/) const {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  // HTTPCodec has a default implementation, override that here to fail.\n  // StreamCodec returns the QPACK table info\n  CompressionInfo getCompressionInfo() const override {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n protected:\n  virtual ParseResult checkFrameAllowed(FrameType type) = 0;\n\n  virtual ParseResult parseData(folly::io::Cursor& /*cursor*/,\n                                const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parseHeaders(folly::io::Cursor& /*cursor*/,\n                                   const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parseCancelPush(folly::io::Cursor& /*cursor*/,\n                                      const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parseSettings(folly::io::Cursor& /*cursor*/,\n                                    const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parsePushPromise(folly::io::Cursor& /*cursor*/,\n                                       const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parseGoaway(folly::io::Cursor& /*cursor*/,\n                                  const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parseMaxPushId(folly::io::Cursor& /*cursor*/,\n                                     const FrameHeader& /*header*/) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parsePushPriorityUpdate(folly::io::Cursor&,\n                                              const FrameHeader&) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  virtual ParseResult parsePriorityUpdate(folly::io::Cursor&,\n                                          const FrameHeader&) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  uint64_t getCodecTotalBytesParsed() const {\n    return totalBytesParsed_;\n  }\n\n  bool onFramedIngressEOF();\n\n  HTTPCodec::StreamID streamId_;\n  TransportDirection transportDirection_;\n  HTTPCodec::Callback* callback_{nullptr};\n  hq::FrameHeader curHeader_;\n  bool parserPaused_{false};\n  bool deferredEOF_{false};\n\n private:\n  ParseResult parseFrame(folly::io::Cursor& cursor);\n  bool checkConnectionError(ParseResult err, const folly::IOBuf* buf);\n\n  // Current frame state\n  size_t pendingDataFrameBytes_{0};\n\n#ifndef NDEBUG\n  uint64_t receivedFrameCount_{0};\n#endif\n\n  enum class FrameState : uint8_t {\n    FRAME_HEADER_TYPE = 0,\n    FRAME_HEADER_LENGTH = 1,\n    FRAME_PAYLOAD = 2,\n    FRAME_PAYLOAD_STREAMING = 3,\n  };\n  FrameState frameState_ : 3;\n  ParseResult connError_{folly::none};\n  uint64_t totalBytesParsed_{0};\n  folly::Function<void()> resumeHook_;\n};\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQFramer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Random.h>\n\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\nusing namespace folly::io;\nusing namespace folly;\n\nnamespace proxygen::hq {\n\nbool isGreaseId(uint64_t id) {\n  if (id < 0x21 || id > quic::kEightByteLimit) {\n    return false;\n  }\n  return (((id - 0x21) % 0x1F) == 0);\n}\n\nfolly::Optional<uint64_t> getGreaseId(uint64_t n) {\n  if (n > kMaxGreaseIdIndex) {\n    return folly::none;\n  }\n  return (0x1F * n) + 0x21;\n}\n\nParseResult parseData(folly::io::Cursor& cursor,\n                      const FrameHeader& header,\n                      std::unique_ptr<folly::IOBuf>& outBuf) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  cursor.clone(outBuf, header.length);\n  return folly::none;\n}\n\nParseResult parseHeaders(folly::io::Cursor& cursor,\n                         const FrameHeader& header,\n                         std::unique_ptr<folly::IOBuf>& outBuf) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  // for HEADERS frame, zero-length is allowed\n  cursor.clone(outBuf, header.length);\n  return folly::none;\n}\n\nstatic ParseResult parseIdOnlyFrame(folly::io::Cursor& cursor,\n                                    const FrameHeader& header,\n                                    uint64_t& outId) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  auto frameLength = header.length;\n\n  auto id = quic::follyutils::decodeQuicInteger(cursor, frameLength);\n  if (!id) {\n    return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n  }\n  outId = id->first;\n  frameLength -= id->second;\n  if (frameLength != 0) {\n    return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n  }\n\n  return folly::none;\n}\n\nParseResult parseCancelPush(folly::io::Cursor& cursor,\n                            const FrameHeader& header,\n                            PushId& outPushId) noexcept {\n  return parseIdOnlyFrame(cursor, header, outPushId);\n}\n\nfolly::Expected<folly::Optional<SettingValue>, HTTP3::ErrorCode>\ndecodeSettingValue(folly::io::Cursor& cursor,\n                   uint64_t& frameLength,\n                   SettingId settingId) {\n\n  // read the setting value\n  auto settingValue = quic::follyutils::decodeQuicInteger(cursor, frameLength);\n  if (!settingValue) {\n    return folly::makeUnexpected(HTTP3::ErrorCode::HTTP_FRAME_ERROR);\n  }\n  auto value = settingValue->first;\n  frameLength -= settingValue->second;\n\n  // return the the value from the wire for known settings, folly::none for\n  // unknown ones\n  switch (settingId) {\n    case SettingId::HEADER_TABLE_SIZE:\n    case SettingId::MAX_HEADER_LIST_SIZE:\n    case SettingId::QPACK_BLOCKED_STREAMS:\n    case SettingId::ENABLE_CONNECT_PROTOCOL:\n    case SettingId::H3_DATAGRAM:\n    case SettingId::H3_DATAGRAM_DRAFT_8:\n    case SettingId::H3_DATAGRAM_RFC:\n    case SettingId::ENABLE_WEBTRANSPORT:\n    case SettingId::H3_WT_MAX_SESSIONS:\n    case SettingId::WT_INITIAL_MAX_DATA:\n      return value;\n  }\n  return folly::none;\n}\n\nParseResult parseSettings(folly::io::Cursor& cursor,\n                          const FrameHeader& header,\n                          std::deque<SettingPair>& settings) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  folly::IOBuf buf;\n  auto frameLength = header.length;\n\n  while (frameLength > 0) {\n    auto settingIdRes =\n        quic::follyutils::decodeQuicInteger(cursor, frameLength);\n    if (!settingIdRes) {\n      return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n    }\n    frameLength -= settingIdRes->second;\n\n    auto settingId = SettingId(settingIdRes->first);\n    auto settingValue = decodeSettingValue(cursor, frameLength, settingId);\n    if (settingValue.hasError()) {\n      return settingValue.error();\n    }\n\n    // TODO: Duped id should trigger H3_SETTINGS_ERROR\n    if (settingValue->has_value()) {\n      settings.emplace_back(settingId, settingValue->value());\n    }\n  }\n  return folly::none;\n}\n\nParseResult parsePushPromise(folly::io::Cursor& cursor,\n                             const FrameHeader& header,\n                             PushId& outPushId,\n                             std::unique_ptr<folly::IOBuf>& outBuf) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  folly::IOBuf buf;\n  auto frameLength = header.length;\n\n  auto pushId = quic::follyutils::decodeQuicInteger(cursor, frameLength);\n  if (!pushId) {\n    return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n  }\n  outPushId = pushId->first;\n  frameLength -= pushId->second;\n\n  cursor.clone(outBuf, frameLength);\n  return folly::none;\n}\n\nParseResult parseGoaway(folly::io::Cursor& cursor,\n                        const FrameHeader& header,\n                        quic::StreamId& outStreamId) noexcept {\n  return parseIdOnlyFrame(cursor, header, outStreamId);\n}\n\nParseResult parseMaxPushId(folly::io::Cursor& cursor,\n                           const FrameHeader& header,\n                           quic::StreamId& outPushId) noexcept {\n  return parseIdOnlyFrame(cursor, header, outPushId);\n}\n\nParseResult parsePriorityUpdate(folly::io::Cursor& cursor,\n                                const FrameHeader& header,\n                                HTTPCodec::StreamID& outId,\n                                HTTPPriority& priorityUpdate) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  auto length = header.length;\n  auto id = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!id) {\n    return HTTP3::ErrorCode::HTTP_ID_ERROR;\n  }\n  outId = id->first;\n  auto bufferLength = length - id->second;\n  auto buf = folly::IOBuf::create(bufferLength);\n  cursor.pull((void*)(buf->data()), bufferLength);\n  buf->append(bufferLength);\n  auto httpPriority = httpPriorityFromString(\n      folly::StringPiece(folly::ByteRange(buf->data(), buf->length())));\n  if (!httpPriority) {\n    return HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n  }\n  priorityUpdate = *httpPriority;\n  return folly::none;\n}\n\n/**\n * Generate just the common frame header. Returns the total frame header length\n */\nWriteResult writeFrameHeader(IOBufQueue& queue,\n                             FrameType type,\n                             uint64_t length) noexcept {\n  QueueAppender appender(&queue, kMaxFrameHeaderSize);\n  auto appenderOp = [appender = std::move(appender)](auto val) mutable {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  };\n  auto typeRes =\n      quic::encodeQuicInteger(static_cast<uint64_t>(type), appenderOp);\n  if (typeRes.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(typeRes.error()));\n  }\n  auto lengthRes = quic::encodeQuicInteger(length, appenderOp);\n  if (lengthRes.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(lengthRes.error()));\n  }\n  return *typeRes + *lengthRes;\n}\n\nWriteResult writeSimpleFrame(IOBufQueue& queue,\n                             FrameType type,\n                             std::unique_ptr<folly::IOBuf> data) noexcept {\n  DCHECK(data);\n  auto payloadSize = data->computeChainDataLength();\n  auto headerSize = writeFrameHeader(queue, type, payloadSize);\n  if (headerSize.hasError()) {\n    return headerSize;\n  }\n  queue.append(std::move(data));\n  return *headerSize + payloadSize;\n}\n\nWriteResult writeData(IOBufQueue& queue,\n                      std::unique_ptr<folly::IOBuf> data) noexcept {\n  return writeSimpleFrame(queue, FrameType::DATA, std::move(data));\n}\n\nWriteResult writePadding(IOBufQueue& queue,\n                         std::unique_ptr<folly::IOBuf> data) noexcept {\n  return writeSimpleFrame(queue, FrameType::PADDING, std::move(data));\n}\n\nWriteResult writeHeaders(IOBufQueue& queue,\n                         std::unique_ptr<folly::IOBuf> data) noexcept {\n  return writeSimpleFrame(queue, FrameType::HEADERS, std::move(data));\n}\n\nWriteResult writeCancelPush(folly::IOBufQueue& writeBuf,\n                            PushId pushId) noexcept {\n  auto pushIdSize = quic::getQuicIntegerSize(pushId);\n  if (pushIdSize.hasError()) {\n    return folly::makeUnexpected(pushIdSize.error());\n  }\n  IOBufQueue queue{IOBufQueue::cacheChainLength()};\n  QueueAppender appender(&queue, *pushIdSize);\n  auto encodeResult = quic::encodeQuicInteger(\n      pushId, [appender = std::move(appender)](auto val) mutable {\n        appender.writeBE(folly::tag<decltype(val)>, val);\n      });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  return writeSimpleFrame(writeBuf, FrameType::CANCEL_PUSH, queue.move());\n}\n\nWriteResult writeSettings(IOBufQueue& queue,\n                          const std::deque<SettingPair>& settings) {\n  size_t settingsSize = 0;\n  // iterate through the settings to compute the frame payload length\n  for (const auto& setting : settings) {\n    auto idSize =\n        quic::getQuicIntegerSize(static_cast<uint64_t>(setting.first));\n    if (idSize.hasError()) {\n      return folly::makeUnexpected(idSize.error());\n    }\n    auto valueSize = quic::getQuicIntegerSize(setting.second);\n    if (valueSize.hasError()) {\n      return folly::makeUnexpected(valueSize.error());\n    }\n    settingsSize += *idSize + *valueSize;\n  }\n  // write the frame header\n  auto headerSize = writeFrameHeader(queue, FrameType::SETTINGS, settingsSize);\n  if (headerSize.hasError()) {\n    return headerSize;\n  }\n  // write the frame payload\n  QueueAppender appender(&queue, settingsSize);\n  auto appenderOp = [appender = std::move(appender)](auto val) mutable {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  };\n  for (const auto& setting : settings) {\n    auto idResult = quic::encodeQuicInteger(\n        static_cast<uint64_t>(setting.first), appenderOp);\n    if (idResult.hasError()) {\n      return folly::makeUnexpected(quic::QuicError(idResult.error()));\n    }\n    auto valueResult = quic::encodeQuicInteger(setting.second, appenderOp);\n    if (valueResult.hasError()) {\n      return folly::makeUnexpected(quic::QuicError(valueResult.error()));\n    }\n  }\n  return *headerSize + settingsSize;\n}\n\nWriteResult writePushPromise(IOBufQueue& queue,\n                             PushId pushId,\n                             std::unique_ptr<folly::IOBuf> data) noexcept {\n  DCHECK(data);\n  auto pushIdSize = quic::getQuicIntegerSize(pushId);\n  if (pushIdSize.hasError()) {\n    return folly::makeUnexpected(pushIdSize.error());\n  }\n  size_t payloadSize = *pushIdSize + data->computeChainDataLength();\n  auto headerSize =\n      writeFrameHeader(queue, FrameType::PUSH_PROMISE, payloadSize);\n  if (headerSize.hasError()) {\n    return headerSize;\n  }\n  QueueAppender appender(&queue, payloadSize);\n  auto encodeResult = quic::encodeQuicInteger(pushId, [&](auto val) {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  appender.insert(std::move(data));\n  return *headerSize + payloadSize;\n}\n\nWriteResult writeGoaway(folly::IOBufQueue& writeBuf,\n                        quic::StreamId lastStreamId) noexcept {\n  auto lastStreamIdSize = quic::getQuicIntegerSize(lastStreamId);\n  if (lastStreamIdSize.hasError()) {\n    return folly::makeUnexpected(lastStreamIdSize.error());\n  }\n  IOBufQueue queue{IOBufQueue::cacheChainLength()};\n  QueueAppender appender(&queue, *lastStreamIdSize);\n  auto encodeResult = quic::encodeQuicInteger(\n      lastStreamId, [appender = std::move(appender)](auto val) mutable {\n        appender.writeBE(folly::tag<decltype(val)>, val);\n      });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  return writeSimpleFrame(writeBuf, FrameType::GOAWAY, queue.move());\n}\n\nWriteResult writeMaxPushId(folly::IOBufQueue& writeBuf,\n                           PushId maxPushId) noexcept {\n  auto maxPushIdSize = quic::getQuicIntegerSize(maxPushId);\n  if (maxPushIdSize.hasError()) {\n    return folly::makeUnexpected(maxPushIdSize.error());\n  }\n  IOBufQueue queue{IOBufQueue::cacheChainLength()};\n  QueueAppender appender(&queue, *maxPushIdSize);\n  auto encodeResult = quic::encodeQuicInteger(\n      maxPushId, [appender = std::move(appender)](auto val) mutable {\n        appender.writeBE(folly::tag<decltype(val)>, val);\n      });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  return writeSimpleFrame(writeBuf, FrameType::MAX_PUSH_ID, queue.move());\n}\n\nWriteResult writePriorityUpdate(folly::IOBufQueue& writeBuf,\n                                quic::StreamId streamId,\n                                folly::StringPiece priorityUpdate) noexcept {\n  auto type = FrameType::FB_PRIORITY_UPDATE;\n  auto streamIdSize = quic::getQuicIntegerSize(streamId);\n  if (streamIdSize.hasError()) {\n    return folly::makeUnexpected(streamIdSize.error());\n  }\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  QueueAppender appender(&queue, *streamIdSize);\n  auto encodeResult = quic::encodeQuicInteger(streamId, [&appender](auto val) {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  appender.pushAtMost((const uint8_t*)(priorityUpdate.data()),\n                      priorityUpdate.size());\n  return writeSimpleFrame(writeBuf, type, queue.move());\n}\n\nWriteResult writePushPriorityUpdate(\n    folly::IOBufQueue& writeBuf,\n    hq::PushId pushId,\n    folly::StringPiece priorityUpdate) noexcept {\n  auto type = FrameType::FB_PUSH_PRIORITY_UPDATE;\n  auto streamIdSize = quic::getQuicIntegerSize(pushId);\n  if (streamIdSize.hasError()) {\n    return folly::makeUnexpected(streamIdSize.error());\n  }\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  QueueAppender appender(&queue, *streamIdSize);\n  auto encodeResult = quic::encodeQuicInteger(pushId, [&appender](auto val) {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  appender.pushAtMost((const uint8_t*)(priorityUpdate.data()),\n                      priorityUpdate.size());\n  return writeSimpleFrame(writeBuf, type, queue.move());\n}\n\nWriteResult writeStreamPreface(folly::IOBufQueue& writeBuf,\n                               uint64_t streamPreface) noexcept {\n  auto streamPrefaceSize = quic::getQuicIntegerSize(streamPreface);\n  if (streamPrefaceSize.hasError()) {\n    return folly::makeUnexpected(streamPrefaceSize.error());\n  }\n  QueueAppender appender(&writeBuf, *streamPrefaceSize);\n  auto encodeResult =\n      quic::encodeQuicInteger(streamPreface, [&appender](auto val) {\n        appender.writeBE(folly::tag<decltype(val)>, val);\n      });\n  if (encodeResult.hasError()) {\n    return folly::makeUnexpected(quic::QuicError(encodeResult.error()));\n  }\n  return *streamPrefaceSize;\n}\n\nconst char* getFrameTypeString(FrameType type) {\n  switch (type) {\n    case FrameType::DATA:\n      return \"DATA\";\n    case FrameType::HEADERS:\n      return \"HEADERS\";\n    case FrameType::CANCEL_PUSH:\n      return \"CANCEL_PUSH\";\n    case FrameType::SETTINGS:\n      return \"SETTINGS\";\n    case FrameType::PUSH_PROMISE:\n      return \"PUSH_PROMISE\";\n    case FrameType::GOAWAY:\n      return \"GOAWAY\";\n    case FrameType::MAX_PUSH_ID:\n      return \"MAX_PUSH_ID\";\n    case FrameType::PRIORITY_UPDATE:\n    case FrameType::FB_PRIORITY_UPDATE:\n      return \"PRIORITY_UPDATE\";\n    case FrameType::PUSH_PRIORITY_UPDATE:\n    case FrameType::FB_PUSH_PRIORITY_UPDATE:\n      return \"PUSH_PRIORITY_UPDATE\";\n    default:\n      if (isGreaseId(static_cast<uint64_t>(type))) {\n        return \"GREASE\";\n      }\n      // can happen when type was cast from uint8_t\n      return \"Unknown\";\n  }\n  LOG(FATAL) << \"Unreachable\";\n}\n\nstd::ostream& operator<<(std::ostream& os, FrameType type) {\n  os << getFrameTypeString(type);\n  return os;\n}\n\nWriteResult writeGreaseFrame(folly::IOBufQueue& writeBuf) noexcept {\n  auto greaseId = getGreaseId(folly::Random::rand32(16));\n  if (!greaseId) {\n    return folly::makeUnexpected(quic::QuicError(\n        quic::TransportErrorCode::INTERNAL_ERROR, \"Invalid grease id\"));\n  }\n  uint64_t uiFrameType = *greaseId;\n  auto frameTypeSize = quic::getQuicIntegerSize(uiFrameType);\n  if (frameTypeSize.hasError()) {\n    return folly::makeUnexpected(frameTypeSize.error());\n  }\n  return writeFrameHeader(writeBuf, static_cast<FrameType>(uiFrameType), 0);\n}\n\nWriteResult writeWTStreamPreface(folly::IOBufQueue& writeBuf,\n                                 WebTransportStreamType streamType,\n                                 uint64_t wtSessionId) {\n  static const std::array<uint64_t, 2> streamTypes{\n      folly::to_underlying(UnidirectionalStreamType::WEBTRANSPORT),\n      folly::to_underlying(BidirectionalStreamType::WEBTRANSPORT)};\n  auto idx = folly::to_underlying(streamType);\n  CHECK_GE(idx, 0);\n  CHECK_LT(idx, streamTypes.size());\n  QueueAppender appender(&writeBuf, 64);\n  size_t prefaceSize = 0;\n  auto res = quic::encodeQuicInteger(streamTypes[idx], [&appender](auto val) {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  });\n  if (!res) {\n    return folly::makeUnexpected(quic::QuicError(res.error()));\n  }\n  prefaceSize += res.value();\n  res = quic::encodeQuicInteger(wtSessionId, [&appender](auto val) {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  });\n  if (!res) {\n    return folly::makeUnexpected(quic::QuicError(res.error()));\n  }\n  prefaceSize += res.value();\n  return prefaceSize;\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQFramer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <deque>\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <folly/io/Cursor.h>\n\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/SettingsId.h>\n#include <quic/codec/Types.h>\n#include <quic/folly_utils/Utils.h>\n\nnamespace proxygen::hq {\n\n//////// Constants ////////\n// Frame headers have a variable length between 2 and 16 Bytes\nconst size_t kMaxFrameHeaderSize = 16;\n// Index for the maximum GREASE ID allowed on the wire\nconst uint64_t kMaxGreaseIdIndex = 0x210842108421083;\n\n// Unframed body DATA frame length.\nconst size_t kUnframedDataFrameLen = 0;\n\n//////// Types ////////\n\nusing PushId = uint64_t;\n\nusing ParseResult = folly::Optional<HTTP3::ErrorCode>;\nusing WriteResult = folly::Expected<size_t, quic::QuicError>;\n\nenum class UnidirectionalStreamType : uint64_t {\n  CONTROL = 0x00,\n  PUSH = 0x01,\n  QPACK_ENCODER = 0x02,\n  QPACK_DECODER = 0x03,\n  GREASE = 0x21,\n  WEBTRANSPORT = 0x54,\n};\n\nenum class BidirectionalStreamType : uint64_t {\n  REQUEST = 0x00, // Can be any reserved frame type valid on a bidi stream\n  WEBTRANSPORT = 0x41,\n};\n\nenum class FrameType : uint64_t {\n  DATA = 0x00,\n  HEADERS = 0x01,\n  CANCEL_PUSH = 0x03,\n  SETTINGS = 0x04,\n  PUSH_PROMISE = 0x05,\n  // 0x06 reserved\n  GOAWAY = 0x07,\n  // 0x08 reserved\n  // 0x09 reserved\n  MAX_PUSH_ID = 0x0D,\n  PRIORITY_UPDATE = 0xF0700,\n  PUSH_PRIORITY_UPDATE = 0xF0701,\n  // TODO T110695366: clean these up once clients have sufficiently migrated to\n  // the RFC compliant frame types.\n  FB_PRIORITY_UPDATE = 0xF700,\n  FB_PUSH_PRIORITY_UPDATE = 0xF701,\n\n  // https://datatracker.ietf.org/doc/html/rfc9114#frame-reserved\n  // \"Frame types of the format 0x1f * N + 0x21 for non-negative integer values\n  // of N are reserved to exercise the requirement that unknown types be\n  // ignored\" \"Endpoints MUST NOT consider these frames to have any meaning upon\n  // receipt.\"\n  //\n  // Therefore, we can use the unregistered frame type (0x1f + 0x21) = 0x40 for\n  // padding. The recipient will ignore it, per the spec.\n  PADDING = 0x40,\n\n  // THIS IS NOT A FRAME TYPE, but we treat it like one\n  WEBTRANSPORT_BIDI = 0x41,\n\n};\n\nstd::ostream& operator<<(std::ostream& os, FrameType type);\n\nstruct FrameHeader {\n  FrameType type;\n  uint64_t length;\n};\n\nenum class SettingId : uint64_t {\n  HEADER_TABLE_SIZE = 0x01,\n  MAX_HEADER_LIST_SIZE = 0x06,\n  QPACK_BLOCKED_STREAMS = 0x07,\n  ENABLE_CONNECT_PROTOCOL = 0x08,\n  H3_DATAGRAM = 0x276, // DRAFT_0\n  H3_DATAGRAM_DRAFT_8 = 0xffd277,\n  H3_DATAGRAM_RFC = 0x33,\n  ENABLE_WEBTRANSPORT = 0x2b603742,\n  H3_WT_MAX_SESSIONS = 0x14e9cd29,\n  WT_INITIAL_MAX_DATA = 0x2b61,\n};\n\nusing SettingValue = uint64_t;\nusing SettingPair = std::pair<SettingId, SettingValue>;\n\n//////// Functions ////////\nfolly::Optional<uint64_t> getGreaseId(uint64_t n);\nbool isGreaseId(uint64_t id);\n\n//// Parsing ////\n\n/**\n * This function parses the section of the DATA frame after the common\n * frame header and returns the body data in outBuf.\n * It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor  The cursor to pull data from.\n * @param header  The frame header for the frame being parsed.\n * @param outBuf  The buf to fill with body data.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseData(folly::io::Cursor& cursor,\n                      const FrameHeader& header,\n                      std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the HEADERS frame after the common\n * frame header and returns the header data in outBuf.\n * It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outBuf The buf to fill with header data.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseHeaders(folly::io::Cursor& cursor,\n                         const FrameHeader& header,\n                         std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the CANCEL_PUSH frame after the common\n * frame header. It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outPushId On success, filled with the push ID to cancel\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseCancelPush(folly::io::Cursor& cursor,\n                            const FrameHeader& header,\n                            PushId& outPushId) noexcept;\n\n/**\n * This function parses the section of the SETTINGS frame after the\n * common frame header. It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param settings The settings received in this frame.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseSettings(folly::io::Cursor& cursor,\n                          const FrameHeader& header,\n                          std::deque<SettingPair>& settings) noexcept;\n\n/**\n * This function parses the section of the PUSH_PROMISE frame after the\n * common frame header. It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outPushId the Push ID of the server push request.\n * @param outBuf The buffer to fill with header data.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parsePushPromise(folly::io::Cursor& cursor,\n                             const FrameHeader& header,\n                             PushId& outPushId,\n                             std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the GOAWAY frame after the common\n * frame header.  It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outStreamID The last stream ID accepted by the remote.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseGoaway(folly::io::Cursor& cursor,\n                        const FrameHeader& header,\n                        quic::StreamId& outStreamId) noexcept;\n\n/**\n * This function parses the section of the MAX_PUSH_ID frame after the common\n * frame header.  It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outPushID the maximum value for a Push ID.\n * @return folly::none for successful parse or the quic application error code.\n */\nParseResult parseMaxPushId(folly::io::Cursor& cursor,\n                           const FrameHeader& header,\n                           PushId& outPushId) noexcept;\n\n/**\n * This API parses PRIORITY_UPDATE or PUSH_PRIORITY_UPDATE frames.\n *\n * @param cursor The cursor to pull input data from.\n * @param header The frame header for the frame being parsed.\n * @param outId The prioritized element. It's either a stream id or a push id.\n *              This is an output parameter.\n * @param priorityUpdate The Priority Field Value parsed into a\n *                       HTTPPriority struct. This is an output parameter.\n * @return folly::none if parsing is successful, otherwise a http error code.\n */\nParseResult parsePriorityUpdate(folly::io::Cursor& cursor,\n                                const FrameHeader& header,\n                                HTTPCodec::StreamID& outId,\n                                HTTPPriority& priorityUpdate) noexcept;\n\n//// Egress ////\n\n/**\n * Generate just the common frame header. Returns the total frame header length\n *\n * @param queue   Queue to write to.\n * @param type    Header type.\n * @param length  Length of the payload for the header.\n */\nWriteResult writeFrameHeader(folly::IOBufQueue& queue,\n                             FrameType type,\n                             uint64_t length) noexcept;\n\n/**\n * Generate an entire DATA frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param data The body data to write out, cannot be nullptr\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeData(folly::IOBufQueue& writeBuf,\n                      std::unique_ptr<folly::IOBuf> data) noexcept;\n\n/**\n * Generate an entire (unofficial) PADDING frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param data The padding data to write out, cannot be nullptr\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writePadding(folly::IOBufQueue& writeBuf,\n                         std::unique_ptr<folly::IOBuf> data) noexcept;\n\n/**\n * Generate an entire HEADER frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param data The body data to write out, cannot be nullptr\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeHeaders(folly::IOBufQueue& writeBuf,\n                         std::unique_ptr<folly::IOBuf> data) noexcept;\n\n/**\n * Generate an entire CANCEL_PUSH frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param pushId The identifier of the  the server push that is being cancelled.\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeCancelPush(folly::IOBufQueue& writeBuf,\n                            PushId pushId) noexcept;\n\n/**\n * Generate an entire SETTINGS frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param settings The settings to send\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeSettings(folly::IOBufQueue& writeBuf,\n                          const std::deque<SettingPair>& settings);\n\n/**\n * Generate an entire PUSH_PROMISE frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param pushId the identifier of the server push request\n * @param data The body data to write out, cannot be nullptr\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writePushPromise(folly::IOBufQueue& writeBuf,\n                             PushId pushId,\n                             std::unique_ptr<folly::IOBuf> data) noexcept;\n\n/**\n * Generate an entire GOAWAY frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param lastStreamId The identifier of the last stream accepted.\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeGoaway(folly::IOBufQueue& writeBuf,\n                        quic::StreamId lastStreamId) noexcept;\n\n/**\n * Generate an entire MAX_PUSH_ID frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param maxPushId The identifier of the maximum value for a Push ID that the\n * server can use.\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeMaxPushId(folly::IOBufQueue& writeBuf,\n                           PushId maxPushId) noexcept;\n\n/**\n * Write a PRIORITY_UPDATE frame on the writeBuf.\n */\nWriteResult writePriorityUpdate(folly::IOBufQueue& writeBuf,\n                                quic::StreamId streamId,\n                                folly::StringPiece priorityUpdate) noexcept;\n\n/**\n * Write a PUSH_PRIORITY_UPDATE frame on the writeBuf.\n */\nWriteResult writePushPriorityUpdate(folly::IOBufQueue& writeBuf,\n                                    hq::PushId pushId,\n                                    folly::StringPiece priorityUpdate) noexcept;\n\nWriteResult writeStreamPreface(folly::IOBufQueue& writeBuf,\n                               uint64_t streamPreface) noexcept;\n\nenum class WebTransportStreamType : uint8_t { UNI = 0, BIDI = 1 };\nWriteResult writeWTStreamPreface(folly::IOBufQueue& writeBuf,\n                                 WebTransportStreamType streamType,\n                                 uint64_t wtSessionId);\n/**\n * Generate a grease frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @return The number of bytes written to writeBuf if successful, a quic error\n * otherwise\n */\nWriteResult writeGreaseFrame(folly::IOBufQueue& writeBuf) noexcept;\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQMultiCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\nnamespace proxygen::hq {\n\nclass HQMultiCodec : public HQControlCodec {\n\n public:\n  class Factory {\n   public:\n    static std::unique_ptr<HQMultiCodec> getCodec(\n        TransportDirection direction,\n        bool useStrictValidation,\n        const HeaderIndexingStrategy* strat) {\n      auto codec = std::make_unique<HQMultiCodec>(direction);\n      codec->setStrictValidation(useStrictValidation);\n      codec->getQPACKCodec().setHeaderIndexingStrategy(strat);\n      return codec;\n    }\n  };\n\n  explicit HQMultiCodec(TransportDirection direction)\n      : HQControlCodec(HTTPCodec::MaxStreamID,\n                       direction,\n                       StreamDirection::INGRESS, /* to match settings */\n                       ingressSettings_,\n                       UnidirectionalStreamType::CONTROL) {\n    VLOG(4) << \"creating \" << getTransportDirectionString(direction)\n            << \" HQMultiCodec for stream \" << streamId_;\n    // Has to be explicitly enabled\n    doubleGoaway_ = false;\n    minUnseenStreamID_ = 0;\n    minUnseenPushID_ = 0;\n  }\n\n  ~HQMultiCodec() override = default;\n\n  void setControlStreamID(StreamID controlID) {\n    streamId_ = controlID;\n  }\n\n  void setQPACKEncoderMaxDataFn(std::function<uint64_t()> qpackEncoderMaxData) {\n    qpackEncoderMaxDataFn_ = std::move(qpackEncoderMaxData);\n  }\n\n  bool setCurrentStream(StreamID currentStream) {\n    if (codecs_.find(currentStream) == codecs_.end()) {\n      return false;\n    }\n    currentStream_ = currentStream;\n    return true;\n  }\n\n  void setStrictValidation(bool strict) {\n    strictValidation_ = strict;\n  }\n\n  bool isStreamIngressEgressAllowed(StreamID streamId) const {\n    CHECK(transportDirection_ == TransportDirection::DOWNSTREAM);\n    return streamId < egressGoawayAck_;\n  }\n\n  HTTPCodec& addCodec(StreamID streamId) {\n    if (transportDirection_ == TransportDirection::DOWNSTREAM &&\n        (streamId & 0x3) == 0 && streamId >= minUnseenStreamID_) {\n      CHECK_LT(streamId, egressGoawayAck_)\n          << \"Don't addCodec for refused stream\";\n      // only bump for client initiated bidi streams, for now\n      minUnseenStreamID_ = streamId + 4;\n    }\n    auto res =\n        codecs_.emplace(streamId,\n                        std::make_unique<HQStreamCodec>(streamId,\n                                                        transportDirection_,\n                                                        qpackCodec_,\n                                                        qpackEncoderWriteBuf_,\n                                                        qpackDecoderWriteBuf_,\n                                                        qpackEncoderMaxDataFn_,\n                                                        settings_));\n    auto& codec = res.first->second;\n    codec->setCallback(callback_);\n    codec->setStrictValidation(strictValidation_);\n    codec->setDebugLevel(debugLevel_);\n    return *codec;\n  }\n\n  void removeCodec(StreamID streamId) {\n    codecs_.erase(streamId);\n  }\n\n  void setResumeHook(StreamID streamId,\n                     folly::Function<void()> hook = nullptr) {\n    getCodec(streamId).setResumeHook(std::move(hook));\n  }\n\n  QPACKCodec& getQPACKCodec() {\n    return qpackCodec_;\n  }\n\n  folly::IOBufQueue& getQPACKEncoderWriteBuf() {\n    return qpackEncoderWriteBuf_;\n  }\n\n  folly::IOBufQueue& getQPACKDecoderWriteBuf() {\n    return qpackDecoderWriteBuf_;\n  }\n\n  void encodeCancelStream(quic::StreamId id) {\n    auto cancel = qpackCodec_.encodeCancelStream(id);\n    qpackDecoderWriteBuf_.append(std::move(cancel));\n  }\n\n  bool encodeInsertCountIncrement() {\n    auto ici = qpackCodec_.encodeInsertCountInc();\n    if (ici) {\n      qpackDecoderWriteBuf_.append(std::move(ici));\n      return true;\n    }\n    return false;\n  }\n\n  void setCallback(proxygen::HTTPCodec::Callback* callback) override {\n    HQControlCodec::setCallback(callback);\n    for (const auto& codec : codecs_) {\n      codec.second->setCallback(callback);\n    }\n  }\n\n  const std::string& getUserAgent() const override {\n    // TODO\n    static const std::string empty;\n    return empty;\n  }\n\n  size_t onIngress(const folly::IOBuf& buf) override {\n    auto res = getCurrentCodec().onIngress(buf);\n    currentStream_ = HTTPCodec::MaxStreamID;\n    return res;\n  }\n\n  void onIngressEOF() override {\n    getCurrentCodec().onIngressEOF();\n    currentStream_ = HTTPCodec::MaxStreamID;\n  }\n\n  bool isReusable() const override {\n    return !sentGoaway_;\n  }\n\n  bool isParserPaused() const override {\n    auto res = getCurrentCodec().isParserPaused();\n    currentStream_ = HTTPCodec::MaxStreamID;\n    return res;\n  }\n\n  bool supportsParallelRequests() const override {\n    return true;\n  }\n\n  size_t generateConnectionPreface(folly::IOBufQueue& /*writeBuf*/) override {\n    return 0;\n  }\n\n  size_t generateSettingsAck(folly::IOBufQueue& /*writeBuf*/) override {\n    return 0;\n  }\n\n  // It is possible to make HQStreamCodec egress stateless and avoid the\n  // hashtable lookup in the generate* functions.\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override {\n    getCodec(stream).generateHeader(\n        writeBuf, stream, msg, eom, size, extraHeaders);\n  }\n\n  void generatePushPromise(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           const HTTPMessage& msg,\n                           StreamID pushID,\n                           bool eom = false,\n                           HTTPHeaderSize* size = nullptr) override {\n    getCodec(stream).generatePushPromise(\n        writeBuf, stream, msg, pushID, eom, size);\n  }\n\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override {\n    return getCodec(stream).generateBody(\n        writeBuf, stream, std::move(chain), padding, eom);\n  }\n\n  size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                          StreamID stream,\n                          const HTTPHeaders& trailers) override {\n    return getCodec(stream).generateTrailers(writeBuf, stream, trailers);\n  }\n\n  size_t generatePadding(folly::IOBufQueue& writeBuf,\n                         StreamID stream,\n                         uint16_t bytes) override {\n    return getCodec(stream).generatePadding(writeBuf, stream, bytes);\n  }\n\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) override {\n    return getCodec(stream).generateEOM(writeBuf, stream);\n  }\n\n  void setHeaderCodecStats(HeaderCodec::Stats* hcStats) override {\n    qpackCodec_.setStats(hcStats);\n  }\n\n  CompressionInfo getCompressionInfo() const override {\n    return qpackCodec_.getCompressionInfo();\n  }\n\n  // HTTPCodec API\n  uint32_t getDefaultWindowSize() const override {\n    return std::numeric_limits<uint32_t>::max();\n  }\n\n  HTTPSettings* getEgressSettings() override {\n    return &egressSettings_;\n  }\n\n  uint64_t nextPushID() {\n    CHECK_EQ(transportDirection_, TransportDirection::DOWNSTREAM);\n    return nextPushID_++;\n  }\n\n  void onIngressPushId(uint64_t pushId) {\n    minUnseenPushID_ = std::max(minUnseenPushID_, pushId + 1);\n  }\n\n  bool supportsWebTransport() const {\n    return ingressSettings_.getSetting(SettingsId::ENABLE_WEBTRANSPORT, 0) &&\n           egressSettings_.getSetting(SettingsId::ENABLE_WEBTRANSPORT, 0);\n  }\n\n protected:\n  HTTPCodec& getCurrentCodec() {\n    return getCodec(currentStream_);\n  }\n\n  const HTTPCodec& getCurrentCodec() const {\n    return getCodec(currentStream_);\n  }\n\n  HQStreamCodec& getCodec(StreamID stream) {\n    auto it = codecs_.find(stream);\n    CHECK(it != codecs_.end()) << \"stream=\" << stream;\n    return *it->second;\n  }\n\n  const HQStreamCodec& getCodec(StreamID stream) const {\n    auto it = codecs_.find(stream);\n    CHECK(it != codecs_.end()) << \"stream=\" << stream;\n    return *it->second;\n  }\n\n  HTTPSettings ingressSettings_;\n  // Turn peer's QPACK dynamic table on by default\n  HTTPSettings egressSettings_{\n      {SettingsId::HEADER_TABLE_SIZE, kDefaultEgressHeaderTableSize},\n      {SettingsId::MAX_HEADER_LIST_SIZE, kDefaultEgressMaxHeaderListSize},\n      {SettingsId::_HQ_QPACK_BLOCKED_STREAMS,\n       hq::kDefaultEgressQpackBlockedStream}};\n  mutable StreamID currentStream_{HTTPCodec::MaxStreamID};\n  folly::F14FastMap<StreamID, std::unique_ptr<HQStreamCodec>> codecs_;\n  QPACKCodec qpackCodec_;\n  folly::IOBufQueue qpackEncoderWriteBuf_{\n      folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue qpackDecoderWriteBuf_{\n      folly::IOBufQueue::cacheChainLength()};\n  std::function<uint64_t()> qpackEncoderMaxDataFn_;\n  uint64_t nextPushID_{0};\n  bool strictValidation_{true};\n  bool debugLevel_{false};\n};\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQStreamCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n\n#include <folly/Format.h>\n#include <folly/ScopeGuard.h>\n#include <folly/io/Cursor.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\nnamespace proxygen::hq {\n\nusing namespace folly;\nusing namespace folly::io;\n\nHQStreamCodec::HQStreamCodec(StreamID streamId,\n                             TransportDirection direction,\n                             QPACKCodec& headerCodec,\n                             folly::IOBufQueue& encoderWriteBuf,\n                             folly::IOBufQueue& decoderWriteBuf,\n                             folly::Function<uint64_t()> qpackEncoderMaxData,\n                             HTTPSettings& ingressSettings)\n    : HQFramedCodec(streamId, direction),\n      headerCodec_(headerCodec),\n      qpackEncoderWriteBuf_(encoderWriteBuf),\n      qpackDecoderWriteBuf_(decoderWriteBuf),\n      qpackEncoderMaxDataFn_(std::move(qpackEncoderMaxData)),\n      ingressSettings_(ingressSettings) {\n  VLOG(4) << \"creating \" << getTransportDirectionString(direction)\n          << \" HQ stream codec for stream \" << streamId_;\n}\n\nHQStreamCodec::~HQStreamCodec() = default;\n\nParseResult HQStreamCodec::checkFrameAllowed(FrameType type) {\n  if (isConnect_ && type != hq::FrameType::DATA) {\n    return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n  }\n  switch (type) {\n    case hq::FrameType::SETTINGS:\n    case hq::FrameType::GOAWAY:\n    case hq::FrameType::MAX_PUSH_ID:\n    case hq::FrameType::CANCEL_PUSH:\n    case hq::FrameType::PRIORITY_UPDATE:\n    case hq::FrameType::PUSH_PRIORITY_UPDATE:\n    case hq::FrameType::FB_PRIORITY_UPDATE:\n    case hq::FrameType::FB_PUSH_PRIORITY_UPDATE:\n    case hq::FrameType::WEBTRANSPORT_BIDI:\n      return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n    case hq::FrameType::PUSH_PROMISE:\n      if (transportDirection_ == TransportDirection::DOWNSTREAM) {\n        return HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED;\n      }\n      break;\n    default:\n      break;\n  }\n  return folly::none;\n}\n\nParseResult HQStreamCodec::parseData(Cursor& cursor,\n                                     const FrameHeader& header) {\n  // NOTE: If an error path is added to this method, it needs to setParserPaused\n\n  // It's possible the data is in the wrong place per HTTP semantics, but it\n  // will be caught by HTTPTransaction\n  std::unique_ptr<IOBuf> outData;\n  VLOG(10) << \"parsing all frame DATA bytes for stream=\" << streamId_\n           << \" length=\" << header.length;\n  auto res = hq::parseData(cursor, header, outData);\n  CHECK(!res);\n\n  // no need to do deliverCallbackIfAllowed\n  // the HQSession can trap this and stop reading.\n  // i.e we can immediately reset in onNewStream if we get a stream id\n  // higher than MAXID advertised in the goaway\n  if (callback_ && (outData && !outData->empty())) {\n    callback_->onBody(streamId_, std::move(outData), 0);\n  }\n  return res;\n}\n\nParseResult HQStreamCodec::parseHeaders(Cursor& cursor,\n                                        const FrameHeader& header) {\n  setParserPaused(true);\n  if (finalIngressHeadersSeen_) {\n    if (parsingTrailers_) {\n      VLOG(4) << \"Unexpected HEADERS frame for stream=\" << streamId_;\n      if (callback_) {\n        HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                         \"Invalid HEADERS frame\");\n        ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n        callback_->onError(streamId_, ex, false);\n      }\n      return folly::none;\n    } else {\n      parsingTrailers_ = true;\n    }\n  }\n  std::unique_ptr<IOBuf> outHeaderData;\n  auto res = hq::parseHeaders(cursor, header, outHeaderData);\n  if (res) {\n    VLOG(4) << \"Invalid HEADERS frame for stream=\" << streamId_;\n    return res;\n  }\n  VLOG(4) << \"Parsing HEADERS frame for stream=\" << streamId_\n          << \" length=\" << outHeaderData->computeChainDataLength();\n  if (callback_ && !parsingTrailers_) {\n    // H2 performs the decompression/semantic validation first.  Also, this\n    // should really only be called once per this whole codec, not per header\n    // block -- think info status. This behavior mirrors HTTP2Codec at present.\n    callback_->onMessageBegin(streamId_, nullptr);\n  }\n  decodeInfo_.init(transportDirection_ == TransportDirection::DOWNSTREAM,\n                   parsingTrailers_,\n                   /*validate=*/true,\n                   strictValidation_,\n                   /*allowEmptyPath=*/false);\n  headerCodec_.decodeStreaming(\n      streamId_, std::move(outHeaderData), header.length, this);\n  // decodeInfo_.msg gets moved in onHeadersComplete.  If it is still around,\n  // parsing is incomplete, leave the parser paused.\n  if (!decodeInfo_.msg) {\n    setParserPaused(false);\n  }\n  return res;\n}\n\nParseResult HQStreamCodec::parsePushPromise(Cursor& cursor,\n                                            const FrameHeader& header) {\n  setParserPaused(true);\n  PushId outPushId;\n  std::unique_ptr<IOBuf> outHeaderData;\n  auto res = hq::parsePushPromise(cursor, header, outPushId, outHeaderData);\n  if (res) {\n    return res;\n  }\n\n  // Notify the callback on beginning of a push promise.\n  // The callback will be further notified when the header block\n  // is fully parsed, via a call to `onHeadersComplete`.\n  // It is up to the callback to match the push promise\n  // with the headers block, via using same stream id\n  if (callback_) {\n    callback_->onPushMessageBegin(outPushId, streamId_, nullptr);\n  }\n\n  decodeInfo_.init(true /* isReq */,\n                   false /* isRequestTrailers */,\n                   /*validate=*/true,\n                   strictValidation_,\n                   /*allowEmptyPath=*/false);\n  auto headerDataLength = outHeaderData->computeChainDataLength();\n  headerCodec_.decodeStreaming(\n      streamId_, std::move(outHeaderData), headerDataLength, this);\n  if (!decodeInfo_.msg) {\n    setParserPaused(false);\n  } // else parsing incomplete, see comment in parseHeaders\n\n  return res;\n}\n\nvoid HQStreamCodec::onHeader(const HPACKHeaderName& name,\n                             const folly::fbstring& value) {\n  if (decodeInfo_.onHeader(name, value)) {\n    if (userAgent_.empty() && name.getHeaderCode() == HTTP_HEADER_USER_AGENT) {\n      userAgent_ = value.toStdString();\n    }\n  } else {\n    VLOG(4) << \"dir=\" << uint32_t(transportDirection_)\n            << decodeInfo_.parsingError << \" codec=\" << headerCodec_;\n  }\n}\n\nvoid HQStreamCodec::onHeadersComplete(HTTPHeaderSize decodedSize,\n                                      bool acknowledge) {\n  CHECK(parserPaused_);\n  decodeInfo_.onHeadersComplete(decodedSize);\n  auto resumeParser = folly::makeGuard([this] { setParserPaused(false); });\n  auto g2 = folly::makeGuard(activationHook_());\n\n  // Check parsing error\n  DCHECK_EQ(decodeInfo_.decodeError, HPACK::DecodeError::NONE);\n  // Leave msg in decodeInfo_ for now, to keep the parser paused\n  if (!decodeInfo_.parsingError.empty()) {\n    LOG(ERROR) << \"Failed parsing header list for stream=\" << streamId_\n               << \", error=\" << decodeInfo_.parsingError;\n    if (!decodeInfo_.headerErrorValue.empty()) {\n      DVLOG(4) << \" value=\" << decodeInfo_.headerErrorValue;\n    }\n    HTTPException err(\n        HTTPException::Direction::INGRESS,\n        fmt::format(\"HQStreamCodec stream error: stream={} status={} error:{}\",\n                    streamId_,\n                    400,\n                    decodeInfo_.parsingError));\n    if (parsingTrailers_) {\n      err.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_MESSAGE_ERROR);\n    } else {\n      err.setHttpStatusCode(400);\n    }\n    err.setProxygenError(decodeInfo_.proxygenError.value_or(kErrorParseHeader));\n    // Have to clone it\n    err.setPartialMsg(std::make_unique<HTTPMessage>(*decodeInfo_.msg));\n    callback_->onError(streamId_, err, true);\n    resumeParser.dismiss();\n    return;\n  }\n  std::unique_ptr<HTTPMessage> msg = std::move(decodeInfo_.msg);\n  msg->setAdvancedProtocolString(getCodecProtocolString(CodecProtocol::HQ));\n\n  if (curHeader_.type == hq::FrameType::HEADERS) {\n    if (!finalIngressHeadersSeen_ && msg->isFinal()) {\n      finalIngressHeadersSeen_ = true;\n    }\n  }\n\n  if (transportDirection_ == TransportDirection::DOWNSTREAM &&\n      msg->getMethod() == HTTPMethod::CONNECT) {\n    isConnect_ = true;\n  }\n\n  if (acknowledge) {\n    qpackDecoderWriteBuf_.append(headerCodec_.encodeHeaderAck(streamId_));\n  }\n  // Report back what we've parsed\n  if (callback_) {\n    if (parsingTrailers_) {\n      auto trailerHeaders =\n          std::make_unique<HTTPHeaders>(msg->extractHeaders());\n      callback_->onTrailersComplete(streamId_, std::move(trailerHeaders));\n    } else {\n      // TODO: should we treat msg as chunked like H2?\n      callback_->onHeadersComplete(streamId_, std::move(msg));\n    }\n  }\n}\n\nvoid HQStreamCodec::onDecodeError(HPACK::DecodeError decodeError) {\n  // the parser may be paused, but this codec is dead.\n  CHECK(parserPaused_);\n  decodeInfo_.decodeError = decodeError;\n  DCHECK_NE(decodeInfo_.decodeError, HPACK::DecodeError::NONE);\n  LOG(ERROR) << \"Failed decoding header block for stream=\" << streamId_\n             << \" decodeError=\" << uint32_t(decodeError);\n\n  auto& msg = decodeInfo_.msg;\n  if (decodeInfo_.decodeError == HPACK::DecodeError::HEADERS_TOO_LARGE &&\n      debugLevel_ > 0 && msg) {\n    LOG(ERROR) << \"QPACK Headers too large\"\n               << CodecUtil::debugString(*msg, debugLevel_)\n               << CodecUtil::debugString(msg->getHeaders(), debugLevel_);\n  }\n\n  if (msg) {\n    // print the partial message\n    msg->dumpMessage(3);\n  }\n\n  if (callback_) {\n    auto g = folly::makeGuard(activationHook_());\n    HTTPException ex(\n        HTTPException::Direction::INGRESS,\n        folly::to<std::string>(\"Stream headers decompression error=\",\n                               uint32_t(decodeError)));\n    ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n    // HEADERS_TOO_LARGE is a stream error, everything else is a session error\n    callback_->onError(decodeError == HPACK::DecodeError::HEADERS_TOO_LARGE\n                           ? streamId_\n                           : kSessionStreamId,\n                       ex,\n                       false);\n  }\n  // leave the partial msg in decodeInfo, it keeps the parser paused\n}\n\nvoid HQStreamCodec::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    bool /*eom*/,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  DCHECK_EQ(stream, streamId_);\n  generateHeaderImpl(writeBuf, msg, folly::none, size, extraHeaders);\n\n  // For requests, set final header seen flag right away.\n  // For responses, header is final only if response code is >= 200.\n  if (msg.isRequest() || (msg.isResponse() && msg.getStatusCode() >= 200)) {\n    finalEgressHeadersSeen_ = true;\n  }\n}\n\nvoid HQStreamCodec::generatePushPromise(folly::IOBufQueue& writeBuf,\n                                        StreamID stream,\n                                        const HTTPMessage& msg,\n                                        StreamID pushId,\n                                        bool /*eom*/,\n                                        HTTPHeaderSize* size) {\n  DCHECK_EQ(stream, streamId_);\n  DCHECK(transportDirection_ == TransportDirection::DOWNSTREAM);\n  generateHeaderImpl(\n      writeBuf, msg, pushId, size, folly::none /* extraHeaders */);\n}\nvoid HQStreamCodec::generateHeaderImpl(\n    folly::IOBufQueue& writeBuf,\n    const HTTPMessage& msg,\n    folly::Optional<StreamID> pushId,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  auto result = headerCodec_.encodeHTTP(qpackEncoderWriteBuf_,\n                                        msg,\n                                        true,\n                                        streamId_,\n                                        maxEncoderStreamData(),\n                                        extraHeaders);\n  if (size) {\n    *size = headerCodec_.getEncodedSize();\n  }\n\n  CodecUtil::logIfFieldSectionExceedsPeerMax(\n      headerCodec_.getEncodedSize(),\n      ingressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE,\n                                  std::numeric_limits<uint32_t>::max()),\n      CodecUtil::debugString(msg, debugLevel_),\n      msg.getHeaders(),\n      debugLevel_);\n\n  // HTTP/2 serializes priority here, but HQ priorities need to go on the\n  // control stream\n\n  auto res = [&]() -> WriteResult {\n    if (pushId) {\n      return hq::writePushPromise(writeBuf, *pushId, std::move(result));\n    } else {\n      return hq::writeHeaders(writeBuf, std::move(result));\n    }\n  }();\n\n  if (res.hasError()) {\n    LOG(ERROR) << __func__ << \": failed to write \"\n               << ((pushId) ? \"push promise: \" : \"headers: \") << res.error();\n  }\n}\n\nsize_t HQStreamCodec::generateBodyImpl(folly::IOBufQueue& writeBuf,\n                                       std::unique_ptr<folly::IOBuf> chain) {\n  auto result = hq::writeData(writeBuf, std::move(chain));\n  if (result) {\n    return *result;\n  }\n  LOG(FATAL) << \"frame exceeded 2^62-1 limit\";\n}\n\nsize_t HQStreamCodec::generateBody(folly::IOBufQueue& writeBuf,\n                                   StreamID stream,\n                                   std::unique_ptr<folly::IOBuf> chain,\n                                   folly::Optional<uint8_t> /*padding*/,\n                                   bool /*eom*/) {\n  DCHECK_EQ(stream, streamId_);\n\n  size_t bytesWritten = generateBodyImpl(writeBuf, std::move(chain));\n\n  return bytesWritten;\n}\n\nsize_t HQStreamCodec::generateBodyDSR(StreamID stream,\n                                      size_t length,\n                                      folly::Optional<uint8_t> /*padding*/,\n                                      bool /*eom*/) {\n  DCHECK_EQ(stream, streamId_);\n\n  // Assuming we have generated a single DATA frame.\n  return length;\n}\nsize_t HQStreamCodec::generateTrailers(folly::IOBufQueue& writeBuf,\n                                       StreamID stream,\n                                       const HTTPHeaders& trailers) {\n  DCHECK_EQ(stream, streamId_);\n  std::vector<compress::Header> allTrailers;\n  CodecUtil::appendHeaders(trailers, allTrailers, HTTP_HEADER_NONE);\n  auto encodeRes =\n      headerCodec_.encode(allTrailers, streamId_, maxEncoderStreamData());\n  qpackEncoderWriteBuf_.append(std::move(encodeRes.control));\n\n  CodecUtil::logIfFieldSectionExceedsPeerMax(\n      headerCodec_.getEncodedSize(),\n      ingressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE,\n                                  std::numeric_limits<uint32_t>::max()),\n      std::string(),\n      trailers,\n      debugLevel_);\n  auto res = [&]() -> WriteResult {\n    return hq::writeHeaders(writeBuf, std::move(encodeRes.stream));\n  }();\n\n  if (res.hasError()) {\n    LOG(ERROR) << __func__ << \": failed to write trailers: \" << res.error();\n    return 0;\n  }\n  return *res;\n}\n\nsize_t HQStreamCodec::generatePadding(folly::IOBufQueue& writeBuf,\n                                      StreamID stream,\n                                      uint16_t padding) {\n  DCHECK_EQ(stream, streamId_);\n  auto buf = CodecUtil::zeroedBuffer(padding);\n  auto res = hq::writePadding(writeBuf, std::move(buf));\n  if (res.hasError()) {\n    LOG(ERROR) << __func__ << \": failed to write padding: \" << res.error();\n    return 0;\n  }\n  return *res;\n}\n\nsize_t HQStreamCodec::generateEOM(folly::IOBufQueue& /*writeBuf*/,\n                                  StreamID stream) {\n  // Generate EOM is a no-op\n  DCHECK_EQ(stream, streamId_);\n  return 0;\n}\n\nCompressionInfo HQStreamCodec::getCompressionInfo() const {\n  return headerCodec_.getCompressionInfo();\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQStreamCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Function.h>\n\n#include <proxygen/lib/http/codec/HQFramedCodec.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HeaderDecodeInfo.h>\n#include <proxygen/lib/http/codec/compress/HPACKStreamingCallback.h>\n\nnamespace proxygen {\n\nclass QPACKCodec;\n\nnamespace hq {\n\nclass HQStreamCodec\n    : public HQFramedCodec\n    , public HPACK::StreamingCallback {\n public:\n  HQStreamCodec() = delete;\n  HQStreamCodec(StreamID streamId,\n                TransportDirection direction,\n                QPACKCodec& headerCodec,\n                folly::IOBufQueue& encoderWriteBuf,\n                folly::IOBufQueue& decoderWriteBuf,\n                folly::Function<uint64_t()> qpackEncoderMaxData,\n                HTTPSettings& ingressSettings);\n  ~HQStreamCodec() override;\n\n  void setStrictValidation(bool strict) {\n    strictValidation_ = strict;\n  }\n\n  void setDebugLevel(uint8_t debugLevel) {\n    debugLevel_ = debugLevel;\n  }\n\n  void setActivationHook(folly::Function<folly::Function<void()>()> hook) {\n    activationHook_ = std::move(hook);\n  }\n\n  StreamID getStreamID() const {\n    return streamId_;\n  }\n\n  // HTTPCodec API\n  HTTPCodec::StreamID createStream() override {\n    // prevent calling more than once?\n    return streamId_;\n  }\n\n  CodecProtocol getProtocol() const override {\n    return CodecProtocol::HQ;\n  }\n\n  const std::string& getUserAgent() const override {\n    return userAgent_;\n  }\n\n  bool isWaitingToDrain() const override {\n    // This should never get called on a data stream codec\n    // But it does from HQStreamTransport::generateGoaway\n    return false;\n  }\n\n  size_t onIngress(const folly::IOBuf& buf) override {\n    return onFramedIngress(buf);\n  }\n\n  void onIngressEOF() override {\n    if (onFramedIngressEOF() && callback_) {\n      auto g = folly::makeGuard(activationHook_());\n      callback_->onMessageComplete(streamId_, false);\n    } // else the conn was in error or paused\n  }\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override;\n\n  void generatePushPromise(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           const HTTPMessage& msg,\n                           StreamID assocStream,\n                           bool eom = false,\n                           HTTPHeaderSize* size = nullptr) override;\n\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n\n  size_t generateBodyDSR(StreamID stream,\n                         size_t length,\n                         folly::Optional<uint8_t> padding,\n                         bool eom) override;\n\n  size_t generateChunkHeader(folly::IOBufQueue& /*writeBuf*/,\n                             StreamID /*stream*/,\n                             size_t /*length*/) override {\n    // no op\n    return 0;\n  }\n\n  // HQ has no chunk terminators\n  size_t generateChunkTerminator(folly::IOBufQueue& /*writeBuf*/,\n                                 StreamID /*stream*/) override {\n    // no op\n    return 0;\n  }\n\n  size_t generateTrailers(folly::IOBufQueue& /*writeBuf*/,\n                          StreamID /*stream*/,\n                          const HTTPHeaders& /*trailers*/) override;\n\n  size_t generatePadding(folly::IOBufQueue& writeBuf,\n                         StreamID stream,\n                         uint16_t bytes) override;\n\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) override;\n\n  uint32_t getDefaultWindowSize() const override {\n    CHECK(false) << __func__ << \" not supported\";\n    return 0;\n  }\n\n  bool peerHasWebsockets() const {\n    return false;\n  }\n\n  bool isRequest(StreamID /*id*/) const {\n    CHECK(false) << __func__ << \" not implemented yet\";\n    return false;\n  }\n\n  CompressionInfo getCompressionInfo() const override;\n\n  void onHeader(const HPACKHeaderName& name,\n                const folly::fbstring& value) override;\n  void onHeadersComplete(HTTPHeaderSize decodedSize, bool acknowledge) override;\n  void onDecodeError(HPACK::DecodeError decodeError) override;\n\n protected:\n  ParseResult checkFrameAllowed(FrameType type) override;\n  ParseResult parseData(folly::io::Cursor& cursor,\n                        const FrameHeader& header) override;\n  ParseResult parseHeaders(folly::io::Cursor& cursor,\n                           const FrameHeader& header) override;\n  ParseResult parsePushPromise(folly::io::Cursor& cursor,\n                               const FrameHeader& header) override;\n\n private:\n  void generateHeaderImpl(folly::IOBufQueue& writeBuf,\n                          const HTTPMessage& msg,\n                          folly::Optional<StreamID> pushId,\n                          HTTPHeaderSize* size,\n                          const folly::Optional<HTTPHeaders>& extraHeaders);\n\n  uint64_t maxEncoderStreamData() {\n    auto maxData = qpackEncoderMaxDataFn_ ? qpackEncoderMaxDataFn_() : 0;\n    if (qpackEncoderWriteBuf_.chainLength() >= maxData) {\n      return 0;\n    }\n    return maxData - qpackEncoderWriteBuf_.chainLength();\n  }\n\n  size_t generateBodyImpl(folly::IOBufQueue& writeBuf,\n                          std::unique_ptr<folly::IOBuf> chain);\n\n  std::string userAgent_;\n  HeaderDecodeInfo decodeInfo_;\n  QPACKCodec& headerCodec_;\n  folly::IOBufQueue& qpackEncoderWriteBuf_;\n  folly::IOBufQueue& qpackDecoderWriteBuf_;\n  folly::Function<uint64_t()> qpackEncoderMaxDataFn_;\n  // Default false for now to match existing behavior\n  bool strictValidation_{false};\n  bool finalIngressHeadersSeen_{false};\n  bool parsingTrailers_{false};\n  bool finalEgressHeadersSeen_{false};\n  bool isConnect_{false};\n  uint8_t debugLevel_{0};\n  folly::Function<folly::Function<void()>()> activationHook_{\n      [] { return [] {}; }};\n  HTTPSettings& ingressSettings_;\n};\n} // namespace hq\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQUnidirectionalCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n\nnamespace proxygen::hq {\n\nstd::ostream& operator<<(std::ostream& os, UnidirectionalStreamType type) {\n  switch (type) {\n    case UnidirectionalStreamType::CONTROL:\n      os << \"control\";\n      break;\n    case UnidirectionalStreamType::QPACK_ENCODER:\n      os << \"QPACK encoder\";\n      break;\n    case UnidirectionalStreamType::QPACK_DECODER:\n      os << \"QPACK decoder\";\n      break;\n    case UnidirectionalStreamType::PUSH:\n      os << \"push\";\n      break;\n    default:\n      os << \"unknown\";\n      break;\n  }\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, StreamDirection direction) {\n  switch (direction) {\n    case StreamDirection::INGRESS:\n      os << \"ingress\";\n      break;\n    case StreamDirection::EGRESS:\n      os << \"egress\";\n      break;\n    default:\n      os << \"unknown\";\n      break;\n  }\n  return os;\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQUnidirectionalCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBuf.h>\n#include <folly/io/IOBufQueue.h>\n#include <proxygen/lib/http/HTTPException.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\nnamespace proxygen::hq {\n\nusing StreamTypeType = std::underlying_type<UnidirectionalStreamType>::type;\nstd::ostream& operator<<(std::ostream& os, UnidirectionalStreamType type);\n\n// Safe application onto the type.\ntemplate <typename Ret>\nusing UnidirectionalTypeFunctor =\n    std::function<folly::Optional<Ret>(UnidirectionalStreamType)>;\n\nusing UnidirectionalTypeF = UnidirectionalTypeFunctor<UnidirectionalStreamType>;\n\ntemplate <typename Ret>\nfolly::Optional<Ret> withType(uint64_t typeval,\n                              UnidirectionalTypeFunctor<Ret> functor) {\n  auto casted = static_cast<UnidirectionalStreamType>(typeval);\n\n  switch (casted) {\n    case UnidirectionalStreamType::CONTROL:\n    case UnidirectionalStreamType::PUSH:\n    case UnidirectionalStreamType::QPACK_ENCODER:\n    case UnidirectionalStreamType::QPACK_DECODER:\n    case UnidirectionalStreamType::WEBTRANSPORT:\n      return functor(casted);\n    case UnidirectionalStreamType::GREASE:\n      [[fallthrough]];\n    default:\n      if (isGreaseId(typeval)) {\n        return functor(UnidirectionalStreamType::GREASE);\n      }\n      return folly::none;\n  }\n}\n\nenum class StreamDirection : uint8_t { INGRESS, EGRESS };\nstd::ostream& operator<<(std::ostream& os, StreamDirection direction);\n\nclass HQUnidirectionalCodec {\n\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void onError(HTTPCodec::StreamID streamID,\n                         const HTTPException& error,\n                         bool newTxn) = 0;\n  };\n\n  HQUnidirectionalCodec(UnidirectionalStreamType streamType,\n                        StreamDirection streamDir)\n      : streamType_(streamType), streamDir_(streamDir) {\n  }\n\n  /**\n   * Parse ingress data.\n   * @param  buf   An IOBuf chain to parse\n   * @return any unparsed data\n   */\n  virtual std::unique_ptr<folly::IOBuf> onUnidirectionalIngress(\n      std::unique_ptr<folly::IOBuf> ingress) = 0;\n\n  /**\n   * Finish parsing when the ingress stream has ended.\n   */\n  virtual void onUnidirectionalIngressEOF() = 0;\n\n  virtual ~HQUnidirectionalCodec() = default;\n\n  [[nodiscard]] StreamDirection getStreamDirection() const {\n    return streamDir_;\n  }\n\n  [[nodiscard]] UnidirectionalStreamType getStreamType() const {\n    return streamType_;\n  }\n\n  [[nodiscard]] bool isIngress() const {\n    return streamDir_ == StreamDirection::INGRESS;\n  }\n  [[nodiscard]] bool isEgress() const {\n    return streamDir_ == StreamDirection::EGRESS;\n  }\n\n private:\n  UnidirectionalStreamType streamType_;\n  StreamDirection streamDir_;\n};\n\n} // namespace proxygen::hq\n\nnamespace std {\ntemplate <>\nstruct hash<proxygen::hq::UnidirectionalStreamType> {\n  uint64_t operator()(\n      const proxygen::hq::UnidirectionalStreamType& type) const {\n    return static_cast<uint64_t>(type);\n  }\n};\n} // namespace std\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HQUtils.h>\n\n#include <folly/Overload.h>\n\nnamespace proxygen::hq {\n\nconst quic::StreamId kSessionStreamId = std::numeric_limits<uint64_t>::max();\n\n// Ingress Sett.hngs Default Values\nconst uint64_t kDefaultIngressHeaderTableSize = 0;\nconst uint64_t kDefaultIngressNumPlaceHolders = 0;\nconst uint64_t kDefaultIngressMaxHeaderListSize = 1u << 17;\nconst uint64_t kDefaultIngressQpackBlockedStream = 0;\n\n// Egress Settings Default Values\nconst uint64_t kDefaultEgressHeaderTableSize = 4096;\nconst uint64_t kDefaultEgressNumPlaceHolders = 16;\nconst uint64_t kDefaultEgressMaxHeaderListSize = 1u << 17;\nconst uint64_t kDefaultEgressQpackBlockedStream = 100;\n\nproxygen::ErrorCode hqToHttpErrorCode(HTTP3::ErrorCode err) {\n  switch (err) {\n    case HTTP3::ErrorCode::HTTP_NO_ERROR:\n      return ErrorCode::NO_ERROR;\n    case HTTP3::ErrorCode::HTTP_REQUEST_REJECTED:\n      return ErrorCode::REFUSED_STREAM;\n    case HTTP3::ErrorCode::HTTP_INTERNAL_ERROR:\n      return ErrorCode::INTERNAL_ERROR;\n    case HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED:\n      return ErrorCode::CANCEL;\n    case HTTP3::ErrorCode::HTTP_CONNECT_ERROR:\n      return ErrorCode::CONNECT_ERROR;\n    case HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD:\n      return ErrorCode::ENHANCE_YOUR_CALM;\n    case HTTP3::ErrorCode::HTTP_VERSION_FALLBACK:\n      return ErrorCode::INTERNAL_ERROR;\n    case HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM:\n    case HTTP3::ErrorCode::HTTP_MISSING_SETTINGS:\n    case HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED:\n    case HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR:\n    case HTTP3::ErrorCode::HTTP_FRAME_ERROR:\n    case HTTP3::ErrorCode::HTTP_ID_ERROR:\n    case HTTP3::ErrorCode::HTTP_SETTINGS_ERROR:\n    case HTTP3::ErrorCode::HTTP_INCOMPLETE_REQUEST:\n    case HTTP3::ErrorCode::HTTP_MESSAGE_ERROR:\n      return ErrorCode::PROTOCOL_ERROR;\n    default:\n      return ErrorCode::INTERNAL_ERROR;\n  }\n}\n\nProxygenError toProxygenError(quic::QuicErrorCode error, bool fromPeer) {\n  switch (error.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode: {\n      auto h3error = HTTP3::ErrorCode(*error.asApplicationErrorCode());\n      if (h3error == HTTP3::HTTP_NO_ERROR) {\n        return kErrorNone;\n      } else if (isQPACKError(h3error)) {\n        return kErrorBadDecompress;\n      } else if (fromPeer) {\n        return kErrorConnectionReset;\n      } else {\n        return kErrorConnection;\n      }\n    }\n    case quic::QuicErrorCode::Type::LocalErrorCode:\n      return kErrorShutdown;\n    case quic::QuicErrorCode::Type::TransportErrorCode:\n      return kErrorConnectionReset;\n  }\n  folly::assume_unreachable();\n}\n\nfolly::Optional<hq::SettingId> httpToHqSettingsId(proxygen::SettingsId id) {\n  switch (id) {\n    case proxygen::SettingsId::HEADER_TABLE_SIZE:\n      return hq::SettingId::HEADER_TABLE_SIZE;\n    case proxygen::SettingsId::MAX_HEADER_LIST_SIZE:\n      return hq::SettingId::MAX_HEADER_LIST_SIZE;\n    case proxygen::SettingsId::ENABLE_CONNECT_PROTOCOL:\n      return hq::SettingId::ENABLE_CONNECT_PROTOCOL;\n    case proxygen::SettingsId::_HQ_QPACK_BLOCKED_STREAMS:\n      return hq::SettingId::QPACK_BLOCKED_STREAMS;\n    case proxygen::SettingsId::_HQ_DATAGRAM:\n      return hq::SettingId::H3_DATAGRAM;\n    case proxygen::SettingsId::_HQ_DATAGRAM_DRAFT_8:\n      return hq::SettingId::H3_DATAGRAM_DRAFT_8;\n    case proxygen::SettingsId::_HQ_DATAGRAM_RFC:\n      return hq::SettingId::H3_DATAGRAM_RFC;\n    case proxygen::SettingsId::ENABLE_WEBTRANSPORT:\n      return hq::SettingId::ENABLE_WEBTRANSPORT;\n    case proxygen::SettingsId::H3_WT_MAX_SESSIONS:\n      return hq::SettingId::H3_WT_MAX_SESSIONS;\n    case proxygen::SettingsId::WT_INITIAL_MAX_DATA:\n      return hq::SettingId::WT_INITIAL_MAX_DATA;\n    default:\n      return folly::none; // this setting has no meaning in HQ\n  }\n}\n\nfolly::Optional<proxygen::SettingsId> hqToHttpSettingsId(hq::SettingId id) {\n  switch (id) {\n    case hq::SettingId::HEADER_TABLE_SIZE:\n      return proxygen::SettingsId::HEADER_TABLE_SIZE;\n    case hq::SettingId::MAX_HEADER_LIST_SIZE:\n      return proxygen::SettingsId::MAX_HEADER_LIST_SIZE;\n    case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n      return proxygen::SettingsId::ENABLE_CONNECT_PROTOCOL;\n    case hq::SettingId::QPACK_BLOCKED_STREAMS:\n      return proxygen::SettingsId::_HQ_QPACK_BLOCKED_STREAMS;\n    case hq::SettingId::H3_DATAGRAM:\n      return proxygen::SettingsId::_HQ_DATAGRAM;\n    case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n      return proxygen::SettingsId::_HQ_DATAGRAM_DRAFT_8;\n    case hq::SettingId::H3_DATAGRAM_RFC:\n      return proxygen::SettingsId::_HQ_DATAGRAM_RFC;\n    case hq::SettingId::ENABLE_WEBTRANSPORT:\n      return proxygen::SettingsId::ENABLE_WEBTRANSPORT;\n    case hq::SettingId::H3_WT_MAX_SESSIONS:\n      return proxygen::SettingsId::H3_WT_MAX_SESSIONS;\n    case hq::SettingId::WT_INITIAL_MAX_DATA:\n      return proxygen::SettingsId::WT_INITIAL_MAX_DATA;\n  }\n  return folly::none;\n}\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HQUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/HTTPException.h>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/codec/Types.h>\n\nnamespace proxygen::hq {\n\n// stream ID used for session-level callbacks from the codec\nextern const quic::StreamId kSessionStreamId;\n\n// Ingress Settings Default Values\nextern const uint64_t kDefaultIngressHeaderTableSize;\nextern const uint64_t kDefaultIngressNumPlaceHolders;\nextern const uint64_t kDefaultIngressMaxHeaderListSize;\nextern const uint64_t kDefaultIngressQpackBlockedStream;\n\n// Settings Default Values\nextern const uint64_t kDefaultEgressHeaderTableSize;\nextern const uint64_t kDefaultEgressNumPlaceHolders;\nextern const uint64_t kDefaultEgressMaxHeaderListSize;\nextern const uint64_t kDefaultEgressQpackBlockedStream;\n\n// The maximum client initiated bidirectional stream id in a quic varint\nconstexpr uint64_t kMaxClientBidiStreamId = quic::kEightByteLimit - 3;\n// The maximum server initiated push id in a quic varint\nconstexpr uint64_t kMaxPushId = quic::kEightByteLimit - 1;\n\nproxygen::ErrorCode hqToHttpErrorCode(HTTP3::ErrorCode err);\n\n/**\n * Conver a quic error to the appropriate proxygen error.\n *\n * Our convention is:\n *  Application error from peer -> kErrorConnectionReset\n *  Application error generated locally -> kErrorConnection\n *  Transport error (must be from peer?) -> kErrorConnectionReset\n *  Local error -> kErrorShutdown\n */\nProxygenError toProxygenError(quic::QuicErrorCode error, bool fromPeer = false);\n\nfolly::Optional<hq::SettingId> httpToHqSettingsId(proxygen::SettingsId id);\n\nfolly::Optional<proxygen::SettingsId> hqToHttpSettingsId(hq::SettingId id);\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP1xCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n\n#include <folly/Memory.h>\n#include <folly/Random.h>\n#include <folly/base64.h>\n#include <folly/ssl/OpenSSLHash.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/RFC2616.h>\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\n#include <memory>\n\nusing folly::IOBuf;\nusing folly::IOBufQueue;\nusing folly::StringPiece;\nusing std::string;\nusing std::unique_ptr;\n\nnamespace {\n\nconstexpr std::string_view kChunked = \"chunked\";\nconst char CRLF[] = \"\\r\\n\";\n\n/**\n * Write an ASCII decimal representation of an integer value\n * @note This function does -not- append a trailing null byte.\n * @param value  Integer value to write.\n * @param dst    Location to which the value will be written.\n * @return Number of bytes written.\n */\nunsigned u64toa(uint64_t value, char* dst) {\n  // Write backwards.\n  char* start = dst;\n  do {\n    *dst++ = '0' + (value % 10);\n    value /= 10;\n  } while (value != 0);\n  unsigned length = dst - start;\n\n  // Reverse in-place.\n  dst--;\n  while (dst > start) {\n    char swap = *dst;\n    *dst = *start;\n    *start = swap;\n    dst--;\n    start++;\n  }\n  return length;\n}\n\nvoid appendUint(IOBufQueue& queue, size_t& len, uint64_t value) {\n  char buf[32];\n  size_t encodedLen = u64toa(value, buf);\n  queue.append(buf, encodedLen);\n  len += encodedLen;\n}\n\n#define appendLiteral(queue, len, str) \\\n  (len) += (sizeof(str) - 1);          \\\n  (queue).append(str, sizeof(str) - 1)\n\nvoid appendString(IOBufQueue& queue, size_t& len, StringPiece str) {\n  queue.append(str.data(), str.size());\n  len += str.size();\n}\n\nusing namespace proxygen;\n// discard messages with multiple content-length headers, if they differ\n// (t12767790)\nbool validateContentLen(const HTTPHeaders& hdrs) noexcept {\n  const std::string* contentLen{nullptr};\n  bool ok = !hdrs.forEachValueOfHeader(\n      HTTP_HEADER_CONTENT_LENGTH, [&](const std::string& value) -> bool {\n        if (!contentLen) {\n          contentLen = &value;\n          return false; // continue\n        }\n        return *contentLen != value; // stop if different\n      });\n  LOG_IF(ERROR, !ok) << \"Invalid message, multiple Content-Length headers\";\n  return ok;\n}\n\n// only supports transfer-encoding of \"chunked\" (identical to http_parser.cpp)\nbool validateTransferEncoding(const HTTPHeaders& hdrs) noexcept {\n  bool ok = !hdrs.forEachValueOfHeader(\n      HTTP_HEADER_TRANSFER_ENCODING, [&](folly::StringPiece value) -> bool {\n        bool err = !value.equals(kChunked, folly::AsciiCaseInsensitive{});\n        LOG_IF(ERROR, err) << \"invalid transfer-encoding val=\" << value;\n        return err; // stop on err\n      });\n  return ok;\n}\n\n} // anonymous namespace\n\nnamespace proxygen {\n\nHTTP1xCodec::HTTP1xCodec(TransportDirection direction,\n                         bool force1_1,\n                         bool strictValidation)\n    : callback_(nullptr),\n      ingressTxnID_(0),\n      egressTxnID_(0),\n      currentIngressBuf_(nullptr),\n      headerParseState_(HeaderParseState::kParsingHeaderIdle),\n      transportDirection_(direction),\n      keepaliveRequested_(KeepaliveRequested::UNSET),\n      force1_1_(force1_1),\n      strictValidation_(strictValidation),\n      parserActive_(false),\n      pendingEOF_(false),\n      parserPaused_(false),\n      parserError_(false),\n      requestPending_(false),\n      responsePending_(false),\n      egressChunked_(false),\n      inChunk_(false),\n      lastChunkWritten_(false),\n      keepalive_(true),\n      disableKeepalivePending_(false),\n      connectRequest_(false),\n      headRequest_(false),\n      expectNoResponseBody_(false),\n      mayChunkEgress_(false),\n      is1xxResponse_(false),\n      inRecvLastChunk_(false),\n      ingressUpgrade_(false),\n      ingressUpgradeComplete_(false),\n      egressUpgrade_(false),\n      headersComplete_(false),\n      releaseEgressAfterRequest_(false) {\n  switch (direction) {\n    case TransportDirection::DOWNSTREAM:\n      http_parser_init(&parser_, HTTP_REQUEST);\n      break;\n    case TransportDirection::UPSTREAM:\n      http_parser_init(&parser_, HTTP_RESPONSE);\n      break;\n    default:\n      LOG(FATAL) << \"Unknown transport direction.\";\n  }\n  parser_.data = this;\n}\n\nHTTP1xCodec::~HTTP1xCodec() {\n  // This code used to throw a parse error there were unterminated headers\n  // being parsed.  None of the cases where this can happen relied on the parse\n  // error.\n}\n\nHTTPCodec::StreamID HTTP1xCodec::createStream() {\n  return isDownstream(transportDirection_) ? ++ingressTxnID_ : ++egressTxnID_;\n}\n\nvoid HTTP1xCodec::setParserPaused(bool paused) {\n  if ((paused == parserPaused_) || parserError_) {\n    // If we're bailing early, we better be paused already\n    DCHECK(parserError_ ||\n           (HTTP_PARSER_ERRNO(&parser_) == HPE_PAUSED) == paused);\n    return;\n  }\n  if (paused) {\n    if (HTTP_PARSER_ERRNO(&parser_) == HPE_OK) {\n      http_parser_pause(&parser_, 1);\n    }\n  } else {\n    http_parser_pause(&parser_, 0);\n  }\n  parserPaused_ = paused;\n}\n\nconst http_parser_settings* HTTP1xCodec::getParserSettings() {\n  static http_parser_settings parserSettings = [] {\n    http_parser_settings st;\n    st.on_message_begin = HTTP1xCodec::onMessageBeginCB;\n    st.on_url = HTTP1xCodec::onUrlCB;\n    st.on_header_field = HTTP1xCodec::onHeaderFieldCB;\n    st.on_header_value = HTTP1xCodec::onHeaderValueCB;\n    st.on_headers_complete = HTTP1xCodec::onHeadersCompleteCB;\n    st.on_body = HTTP1xCodec::onBodyCB;\n    st.on_message_complete = HTTP1xCodec::onMessageCompleteCB;\n    st.on_reason = HTTP1xCodec::onReasonCB;\n    st.on_chunk_header = HTTP1xCodec::onChunkHeaderCB;\n    st.on_chunk_complete = HTTP1xCodec::onChunkCompleteCB;\n    return st;\n  }();\n  return &parserSettings;\n}\n\nsize_t HTTP1xCodec::onIngress(const IOBuf& buf) {\n  folly::io::Cursor cursor(&buf);\n\n  size_t totalBytesParsed = 0;\n  while (!cursor.isAtEnd()) {\n    folly::IOBuf currentReadBuf;\n    auto bufSize = cursor.peekBytes().size();\n    cursor.cloneAtMost(currentReadBuf, bufSize);\n    size_t bytesParsed = onIngressImpl(currentReadBuf);\n    if (bytesParsed == 0) {\n      // If the codec didn't make any progress with current input, we\n      // wait for more data until this is called again\n      break;\n    }\n    totalBytesParsed += bytesParsed;\n    if (bufSize > bytesParsed) {\n      cursor.retreat(bufSize - bytesParsed);\n    }\n    if (isParserPaused()) {\n      break;\n    }\n  }\n  return totalBytesParsed;\n}\n\nsize_t HTTP1xCodec::onIngressImpl(const IOBuf& buf) {\n  if (parserError_) {\n    return 0;\n  } else if (ingressUpgradeComplete_) {\n    callback_->onBody(ingressTxnID_, buf.clone(), 0);\n    return buf.computeChainDataLength();\n  } else {\n    // Callers responsibility to prevent calling onIngress from a callback\n    CHECK(!parserActive_);\n    parserActive_ = true;\n    currentIngressBuf_ = &buf;\n    if (isUpstream(transportDirection_) && parser_.http_major == 0 &&\n        parser_.http_minor == 9) {\n      // HTTP/0.9 responses have no header block, so create a fake 200 response\n      // and put the codec in upgrade mode\n      onMessageBegin();\n      parser_.status_code = 200;\n      msg_->setStatusCode(200);\n      onHeadersComplete(0);\n      parserActive_ = false;\n      ingressUpgradeComplete_ = true;\n      return onIngressImpl(buf);\n    }\n    size_t bytesParsed = http_parser_execute_options(\n        &parser_,\n        getParserSettings(),\n        strictValidation_ ? F_HTTP_PARSER_OPTIONS_URL_STRICT : 0,\n        (const char*)buf.data(),\n        buf.length());\n    // in case we parsed a section of the headers but we're not done parsing\n    // the headers we need to keep accounting of it for total header size\n    if (!headersComplete_) {\n      headerSize_.uncompressed += bytesParsed;\n      headerSize_.compressed += bytesParsed;\n    }\n    parserActive_ = false;\n    parserError_ = (HTTP_PARSER_ERRNO(&parser_) != HPE_OK) &&\n                   (HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED);\n    if (parserError_) {\n      onParserError();\n    }\n    if (currentHeaderName_.empty() && !currentHeaderNameStringPiece_.empty()) {\n      // we currently are storing a chunk of header name via pointers in\n      // currentHeaderNameStringPiece_, but the currentIngressBuf_ is about to\n      // vanish and so we need to copy over that data to currentHeaderName_\n      currentHeaderName_.assign(currentHeaderNameStringPiece_.begin(),\n                                currentHeaderNameStringPiece_.size());\n    }\n    currentIngressBuf_ = nullptr;\n    if (pendingEOF_) {\n      onIngressEOF();\n      pendingEOF_ = false;\n    }\n    return bytesParsed;\n  }\n}\n\nvoid HTTP1xCodec::onIngressEOF() {\n  if (parserError_) {\n    return;\n  }\n  if (parserActive_) {\n    pendingEOF_ = true;\n    return;\n  }\n  if (ingressUpgradeComplete_) {\n    callback_->onMessageComplete(ingressTxnID_, false);\n    return;\n  }\n  parserActive_ = true;\n  if (http_parser_execute(&parser_, getParserSettings(), nullptr, 0) != 0) {\n    parserError_ = true;\n  } else {\n    parserError_ = (HTTP_PARSER_ERRNO(&parser_) != HPE_OK) &&\n                   (HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED);\n  }\n  parserActive_ = false;\n  if (parserError_) {\n    onParserError();\n  }\n}\n\nvoid HTTP1xCodec::onParserError(const char* what) {\n  inRecvLastChunk_ = false;\n  http_errno parser_errno = HTTP_PARSER_ERRNO(&parser_);\n  HTTPException error(\n      HTTPException::Direction::INGRESS,\n      what ? what\n           : folly::to<std::string>(\"Error parsing message: \",\n                                    http_errno_description(parser_errno)));\n  // generate a string of parsed headers so that we can pass it to callback\n  if (msg_) {\n    error.setPartialMsg(std::move(msg_));\n  }\n  if (isDownstream(transportDirection_) && egressTxnID_ < ingressTxnID_) {\n    error.setHttpStatusCode(400);\n  } // else we've already egressed a response for this txn, don't attempt a 400\n  // See http_parser.h for what these error codes mean\n  if (parser_errno == HPE_INVALID_EOF_STATE) {\n    error.setProxygenError(kErrorEOF);\n  } else if (parser_errno == HPE_HEADER_OVERFLOW ||\n             parser_errno == HPE_INVALID_CONSTANT ||\n             (parser_errno >= HPE_INVALID_VERSION &&\n              parser_errno <= HPE_HUGE_CONTENT_LENGTH) ||\n             parser_errno == HPE_CB_header_field ||\n             parser_errno == HPE_CB_header_value ||\n             parser_errno == HPE_CB_headers_complete) {\n    error.setProxygenError(validationError_.value_or(kErrorParseHeader));\n  } else if (parser_errno == HPE_INVALID_CHUNK_SIZE ||\n             parser_errno == HPE_HUGE_CHUNK_SIZE ||\n             parser_errno == HPE_STRICT) {\n    error.setProxygenError(kErrorParseBody);\n  } else {\n    error.setProxygenError(kErrorUnknown);\n  }\n  callback_->onError(ingressTxnID_, error);\n}\n\nbool HTTP1xCodec::isReusable() const {\n  return keepalive_ && !egressUpgrade_ && !ingressUpgrade_ && !parserError_ &&\n         websockAcceptKey_.empty();\n}\n\nbool HTTP1xCodec::isBusy() const {\n  return requestPending_ || responsePending_;\n}\n\nvoid HTTP1xCodec::addDateHeader(IOBufQueue& writeBuf, size_t& len) {\n  appendLiteral(writeBuf, len, \"Date: \");\n  appendString(writeBuf, len, HTTPMessage::formatDateHeader());\n  appendLiteral(writeBuf, len, CRLF);\n}\n\nconstexpr folly::StringPiece kUpgradeToken = \"websocket\";\nconstexpr folly::StringPiece kUpgradeConnectionToken = \"Upgrade\";\n// websocket/http1.1 draft.\nconstexpr folly::StringPiece kWSMagicString =\n    \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\nstd::string HTTP1xCodec::generateWebsocketKey() const {\n  std::array<unsigned char, 16> arr;\n  folly::Random::secureRandom(arr.data(), arr.size());\n  return folly::base64Encode(std::string_view((char*)arr.data(), arr.size()));\n}\n\nstd::string HTTP1xCodec::generateWebsocketAccept(const std::string& key) const {\n  folly::ssl::OpenSSLHash::Digest digest;\n  digest.hash_init(EVP_sha1());\n  digest.hash_update(folly::StringPiece(key));\n  digest.hash_update(kWSMagicString);\n  std::array<unsigned char, 20> arr;\n  folly::MutableByteRange accept(arr.data(), arr.size());\n  digest.hash_final(accept);\n  return folly::base64Encode(\n      std::string_view((char*)accept.data(), accept.size()));\n}\n\nvoid HTTP1xCodec::serializeWebsocketHeader(IOBufQueue& writeBuf,\n                                           size_t& len,\n                                           bool upstream) {\n  if (upstream) {\n    appendLiteral(writeBuf, len, \"Upgrade: \");\n    appendString(writeBuf, len, kUpgradeToken.str());\n    appendLiteral(writeBuf, len, CRLF);\n    upgradeHeader_ = kUpgradeToken.str();\n\n    auto key = generateWebsocketKey();\n    appendLiteral(writeBuf, len, \"Sec-WebSocket-Key: \");\n    appendString(writeBuf, len, key);\n    appendLiteral(writeBuf, len, CRLF);\n    DCHECK(websockAcceptKey_.empty());\n    websockAcceptKey_ = generateWebsocketAccept(key);\n  } else {\n    appendLiteral(writeBuf, len, \"Upgrade: \");\n    appendString(writeBuf, len, kUpgradeToken.str());\n    appendLiteral(writeBuf, len, CRLF);\n\n    appendLiteral(writeBuf, len, \"Sec-WebSocket-Accept: \");\n    appendString(writeBuf, len, websockAcceptKey_);\n    appendLiteral(writeBuf, len, CRLF);\n  }\n}\n\nvoid HTTP1xCodec::generateHeader(\n    IOBufQueue& writeBuf,\n    StreamID txn,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  if (keepalive_ && disableKeepalivePending_) {\n    keepalive_ = false;\n  }\n  const bool upstream = isUpstream(transportDirection_);\n  const bool downstream = !upstream;\n  if (upstream) {\n    DCHECK_EQ(txn, egressTxnID_);\n    requestPending_ = true;\n    responsePending_ = true;\n    connectRequest_ = (msg.getMethod() == HTTPMethod::CONNECT);\n    headRequest_ = (msg.getMethod() == HTTPMethod::HEAD);\n    expectNoResponseBody_ = connectRequest_ || headRequest_;\n  } else {\n    // In HTTP, transactions must be egressed sequentially -- no out of order\n    // responses.  So txn must be egressTxnID_ + 1.  Furthermore, we shouldn't\n    // ever egress a response before we see a request, so txn can't\n    // be > ingressTxnID_\n    if ((txn != egressTxnID_ + 1 && !(txn == egressTxnID_ && is1xxResponse_)) ||\n        (txn > ingressTxnID_)) {\n      LOG(DFATAL) << \"Out of order, duplicate or premature HTTP response\";\n    }\n    if (!is1xxResponse_) {\n      ++egressTxnID_;\n    }\n    is1xxResponse_ = msg.is1xxResponse() || msg.isEgressWebsocketUpgrade();\n\n    expectNoResponseBody_ =\n        connectRequest_ || headRequest_ ||\n        RFC2616::responseBodyMustBeEmpty(msg.getStatusCode());\n  }\n\n  int statusCode = 0;\n  StringPiece statusMessage;\n  if (downstream) {\n    statusCode = msg.getStatusCode();\n    statusMessage = msg.getStatusMessage();\n    // If a response to a websocket upgrade is being sent out, it must be 101.\n    // This is required since the application may not have changed the status,\n    // particularly when proxying between a H2 hop and a H1 hop.\n    if (msg.isEgressWebsocketUpgrade()) {\n      statusCode = 101;\n      statusMessage = HTTPMessage::getDefaultReason(101);\n    }\n    if (connectRequest_ && (statusCode >= 200 && statusCode < 300)) {\n      // Set egress upgrade flag if we are sending a 200 response\n      // to a CONNECT request we received earlier.\n      egressUpgrade_ = true;\n    } else if (statusCode == 101) {\n      // Set the upgrade flags if we upgraded after the request from client.\n      ingressUpgrade_ = true;\n      egressUpgrade_ = true;\n    } else if (connectRequest_ && ingressUpgrade_) {\n      // Disable upgrade when rejecting CONNECT request\n      ingressUpgrade_ = false;\n\n      // This codec/session is no longer useful as we might have\n      // forwarded some data before receiving the 200.\n      keepalive_ = false;\n    }\n  } else {\n    if (connectRequest_ || msg.isEgressWebsocketUpgrade()) {\n      // Sending a CONNECT request or a websocket upgrade request to an upstream\n      // server. This is used to determine the chunked setting below.\n      egressUpgrade_ = true;\n    }\n  }\n\n  egressChunked_ = msg.getIsChunked() && !egressUpgrade_;\n  lastChunkWritten_ = false;\n  std::pair<uint8_t, uint8_t> version = msg.getHTTPVersion();\n  if (version > HTTPMessage::kHTTPVersion11) {\n    version = HTTPMessage::kHTTPVersion11;\n  }\n\n  size_t len = 0;\n  switch (transportDirection_) {\n    case TransportDirection::DOWNSTREAM:\n      DCHECK_NE(statusCode, 0);\n      if (version == HTTPMessage::kHTTPVersion09) {\n        return;\n      }\n      if (force1_1_ && version < HTTPMessage::kHTTPVersion11) {\n        version = HTTPMessage::kHTTPVersion11;\n      }\n      appendLiteral(writeBuf, len, \"HTTP/\");\n      appendUint(writeBuf, len, version.first);\n      appendLiteral(writeBuf, len, \".\");\n      appendUint(writeBuf, len, version.second);\n      appendLiteral(writeBuf, len, \" \");\n      appendUint(writeBuf, len, statusCode);\n      appendLiteral(writeBuf, len, \" \");\n      appendString(writeBuf, len, statusMessage);\n      break;\n    case TransportDirection::UPSTREAM:\n      if (force1_1_ && version < HTTPMessage::kHTTPVersion11) {\n        version = HTTPMessage::kHTTPVersion11;\n      }\n      if (msg.isEgressWebsocketUpgrade()) {\n        appendString(writeBuf, len, methodToString(HTTPMethod::GET));\n      } else {\n        appendString(writeBuf, len, msg.getMethodString());\n      }\n      appendLiteral(writeBuf, len, \" \");\n      if (connectRequest_ && msg.getURL().empty()) {\n        appendString(\n            writeBuf, len, msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST));\n      } else {\n        appendString(writeBuf, len, msg.getURL());\n      }\n      if (version != HTTPMessage::kHTTPVersion09) {\n        appendLiteral(writeBuf, len, \" HTTP/\");\n        appendUint(writeBuf, len, version.first);\n        appendLiteral(writeBuf, len, \".\");\n        appendUint(writeBuf, len, version.second);\n      }\n      mayChunkEgress_ = (version.first == 1) && (version.second >= 1);\n      if (!upgradeHeader_.empty()) {\n        LOG(DFATAL)\n            << \"Attempted to pipeline HTTP request with pending upgrade\";\n        upgradeHeader_.clear();\n      }\n      break;\n  }\n  appendLiteral(writeBuf, len, CRLF);\n\n  if (keepalive_ && (!msg.wantsKeepalive() || version.first < 1 ||\n                     (downstream && version == HTTPMessage::kHTTPVersion10 &&\n                      keepaliveRequested_ != KeepaliveRequested::ENABLED))) {\n    // Disable keepalive if\n    //  - the message asked to turn it off\n    //  - it's HTTP/0.9\n    //  - this is a response to a 1.0 request that didn't say keep-alive\n    keepalive_ = false;\n  }\n  egressChunked_ &= mayChunkEgress_;\n  if (version == HTTPMessage::kHTTPVersion09) {\n    parser_.http_major = 0;\n    parser_.http_minor = 9;\n    return;\n  }\n  folly::StringPiece deferredContentLength;\n  bool hasTransferEncodingChunked = false;\n  bool hasDateHeader = false;\n  bool hasUpgradeHeader = false;\n  std::vector<StringPiece> connectionTokens;\n  size_t lastConnectionToken = 0;\n  bool egressWebsocketUpgrade = msg.isEgressWebsocketUpgrade();\n  bool hasUpgradeTokeninConnection = false;\n  auto headerEncoder = [&](HTTPHeaderCode code,\n                           folly::StringPiece header,\n                           folly::StringPiece value) {\n    if (code == HTTP_HEADER_CONTENT_LENGTH) {\n      // Write the Content-Length last (t1071703)\n      deferredContentLength = value;\n      return; // continue\n    } else if (code == HTTP_HEADER_CONNECTION &&\n               (!is1xxResponse_ || egressWebsocketUpgrade)) {\n      static const string kClose = \"close\";\n      static const string kKeepAlive = \"keep-alive\";\n      folly::split(',', value, connectionTokens);\n      for (auto curConnectionToken = lastConnectionToken;\n           curConnectionToken < connectionTokens.size();\n           curConnectionToken++) {\n        auto token = trimWhitespace(connectionTokens[curConnectionToken]);\n        if (caseInsensitiveEqual(token, \"upgrade\")) {\n          hasUpgradeTokeninConnection = true;\n        }\n        if (caseInsensitiveEqual(token, kClose)) {\n          keepalive_ = false;\n        } else if (!caseInsensitiveEqual(token, kKeepAlive)) {\n          connectionTokens[lastConnectionToken++] = token;\n        } // else eat the keep-alive token\n      }\n      connectionTokens.resize(lastConnectionToken);\n      // We'll generate a new Connection header based on the keepalive_\n      // state\n      return;\n    } else if (code == HTTP_HEADER_UPGRADE) {\n      hasUpgradeHeader = true;\n      if (upstream) {\n        // save in case we get a 101 Switching Protocols\n        upgradeHeader_ = value.str();\n      }\n    } else if (!hasTransferEncodingChunked &&\n               code == HTTP_HEADER_TRANSFER_ENCODING) {\n      if (!caseInsensitiveEqual(value, kChunked)) {\n        return;\n      }\n      hasTransferEncodingChunked = true;\n      if (!mayChunkEgress_) {\n        return;\n      }\n    } else if (!hasDateHeader && code == HTTP_HEADER_DATE) {\n      hasDateHeader = true;\n    } else if (egressWebsocketUpgrade &&\n               code == HTTP_HEADER_SEC_WEBSOCKET_KEY) {\n      // will generate our own key per hop, not client's.\n      return;\n    } else if (egressWebsocketUpgrade &&\n               code == HTTP_HEADER_SEC_WEBSOCKET_ACCEPT) {\n      // will generate our own accept per hop, not client's.\n      return;\n    }\n    if (value.find_first_of(\"\\r\\n\") != std::string::npos) {\n      return;\n    }\n    size_t lineLen = header.size() + value.size() + 4; // 4 for \": \" + CRLF\n    auto writable =\n        writeBuf.preallocate(lineLen, std::max(lineLen, size_t(2000)));\n    char* dst = (char*)writable.first;\n    memcpy(dst, header.data(), header.size());\n    dst += header.size();\n    *dst++ = ':';\n    *dst++ = ' ';\n    memcpy(dst, value.data(), value.size());\n    dst += value.size();\n    *dst++ = '\\r';\n    *dst = '\\n';\n    DCHECK_EQ(size_t(++dst - (char*)writable.first), lineLen);\n    writeBuf.postallocate(lineLen);\n    len += lineLen;\n  };\n  msg.getHeaders().forEachWithCode(headerEncoder);\n  if (extraHeaders) {\n    extraHeaders->forEachWithCode(headerEncoder);\n  }\n\n  bool bodyCheck =\n      (downstream && !expectNoResponseBody_ && !egressUpgrade_) ||\n      // auto chunk POSTs and any request that came to us chunked\n      (upstream && ((msg.getMethod() == HTTPMethod::POST) || egressChunked_));\n  // TODO: 400 a 1.0 POST with no content-length\n  // clear egressChunked_ if the header wasn't actually set\n  egressChunked_ &= hasTransferEncodingChunked;\n  if (bodyCheck && !egressChunked_ && deferredContentLength.empty()) {\n    // We're being asked to send a response message with no Content-Length,\n    // no chunked encoding, and no special circumstances that would eliminate\n    // the need for a response body. If the client supports chunking, turn\n    // on chunked encoding now.  Otherwise, turn off keepalives on this\n    // connection.\n    if (!hasTransferEncodingChunked && mayChunkEgress_) {\n      appendLiteral(writeBuf, len, \"Transfer-Encoding: chunked\\r\\n\");\n      egressChunked_ = true;\n    } else {\n      keepalive_ = false;\n    }\n  }\n  if (downstream && !hasDateHeader) {\n    addDateHeader(writeBuf, len);\n  }\n\n  // websocket headers\n  if (msg.isEgressWebsocketUpgrade()) {\n    if (!hasUpgradeHeader) {\n      // upgradeHeader_ is set in serializeWwebsocketHeader for requests.\n      serializeWebsocketHeader(writeBuf, len, upstream);\n      if (!hasUpgradeTokeninConnection) {\n        connectionTokens.push_back(kUpgradeConnectionToken);\n        lastConnectionToken++;\n      }\n    } else {\n      LOG(ERROR) << folly::to<string>(\n          \"Not serializing headers. \"\n          \"Upgrade headers present/txn: \",\n          hasUpgradeHeader,\n          txn);\n    }\n  }\n\n  if (!is1xxResponse_ || upstream || !connectionTokens.empty()) {\n    // We don't seem to add keep-alive/close and let the application add any\n    // for 1xx responses.\n    appendLiteral(writeBuf, len, \"Connection: \");\n    if (connectionTokens.size() > 0) {\n      appendString(writeBuf, len, folly::join(\", \", connectionTokens));\n    }\n    if (!is1xxResponse_) {\n      if (connectionTokens.size() > 0) {\n        appendString(writeBuf, len, \", \");\n      }\n      if (keepalive_) {\n        appendLiteral(writeBuf, len, \"keep-alive\");\n      } else {\n        appendLiteral(writeBuf, len, \"close\");\n      }\n    }\n    appendLiteral(writeBuf, len, \"\\r\\n\");\n  }\n\n  if (!deferredContentLength.empty()) {\n    appendLiteral(writeBuf, len, \"Content-Length: \");\n    appendString(writeBuf, len, deferredContentLength);\n    appendLiteral(writeBuf, len, CRLF);\n  }\n  appendLiteral(writeBuf, len, CRLF);\n  if (eom) {\n    len += generateEOM(writeBuf, txn);\n  }\n\n  if (size) {\n    size->compressed = len;\n    size->uncompressed = len;\n  }\n}\n\nsize_t HTTP1xCodec::generateBody(IOBufQueue& writeBuf,\n                                 StreamID txn,\n                                 unique_ptr<IOBuf> chain,\n                                 folly::Optional<uint8_t> /*padding*/,\n                                 bool eom) {\n  DCHECK_EQ(txn, egressTxnID_);\n  size_t buflen = 0;\n  size_t totLen = 0;\n  if (chain) {\n    buflen = chain->computeChainDataLength();\n    totLen = buflen;\n  }\n  if (totLen == 0) {\n    if (eom) {\n      totLen += generateEOM(writeBuf, txn);\n    }\n    return totLen;\n  }\n\n  if (egressChunked_ && !inChunk_) {\n    char chunkLenBuf[32];\n    int rc = snprintf(chunkLenBuf, sizeof(chunkLenBuf), \"%zx\\r\\n\", buflen);\n    CHECK_GT(rc, 0);\n    CHECK_LT(size_t(rc), sizeof(chunkLenBuf));\n\n    writeBuf.append(chunkLenBuf, rc);\n    totLen += rc;\n\n    writeBuf.append(std::move(chain));\n    writeBuf.append(\"\\r\\n\", 2);\n    totLen += 2;\n  } else {\n    writeBuf.append(std::move(chain));\n  }\n  if (eom) {\n    totLen += generateEOM(writeBuf, txn);\n  }\n\n  return totLen;\n}\n\nsize_t HTTP1xCodec::generateChunkHeader(IOBufQueue& writeBuf,\n                                        StreamID /*txn*/,\n                                        size_t length) {\n  // TODO: Format directly into the IOBuf, rather than copying after the fact.\n  // IOBufQueue::append() currently forces us to copy.\n\n  CHECK(length) << \"use sendEOM to terminate the message using the \"\n                << \"standard zero-length chunk. Don't \"\n                << \"send zero-length chunks using this API.\";\n  if (egressChunked_) {\n    CHECK(!inChunk_);\n    inChunk_ = true;\n    char chunkLenBuf[32];\n    int rc = snprintf(chunkLenBuf, sizeof(chunkLenBuf), \"%zx\\r\\n\", length);\n    CHECK_GT(rc, 0);\n    CHECK_LT(size_t(rc), sizeof(chunkLenBuf));\n\n    writeBuf.append(chunkLenBuf, rc);\n    return rc;\n  }\n\n  return 0;\n}\n\nsize_t HTTP1xCodec::generateChunkTerminator(IOBufQueue& writeBuf,\n                                            StreamID /*txn*/) {\n  if (egressChunked_ && inChunk_) {\n    inChunk_ = false;\n    writeBuf.append(\"\\r\\n\", 2);\n    return 2;\n  }\n\n  return 0;\n}\n\nsize_t HTTP1xCodec::generateTrailers(IOBufQueue& writeBuf,\n                                     StreamID txn,\n                                     const HTTPHeaders& trailers) {\n  DCHECK_EQ(txn, egressTxnID_);\n  size_t len = 0;\n  if (egressChunked_) {\n    CHECK(!inChunk_);\n    appendLiteral(writeBuf, len, \"0\\r\\n\");\n    lastChunkWritten_ = true;\n    trailers.forEach([&](const string& trailer, const string& value) {\n      appendString(writeBuf, len, trailer);\n      appendLiteral(writeBuf, len, \": \");\n      appendString(writeBuf, len, value);\n      appendLiteral(writeBuf, len, CRLF);\n    });\n    len += generateEOM(writeBuf, txn);\n  }\n  return len;\n}\n\nsize_t HTTP1xCodec::generateEOM(IOBufQueue& writeBuf, StreamID txn) {\n  DCHECK_EQ(txn, egressTxnID_);\n  size_t len = 0;\n  if (egressChunked_) {\n    CHECK(!inChunk_);\n    if (headRequest_ && isDownstream(transportDirection_)) {\n      lastChunkWritten_ = true;\n    } else {\n      // appending a 0\\r\\n only if it's not a HEAD and downstream request\n      if (!lastChunkWritten_) {\n        lastChunkWritten_ = true;\n        appendLiteral(writeBuf, len, \"0\\r\\n\");\n      }\n      appendLiteral(writeBuf, len, CRLF);\n    }\n  }\n  switch (transportDirection_) {\n    case TransportDirection::DOWNSTREAM:\n      responsePending_ = false;\n      break;\n    case TransportDirection::UPSTREAM:\n      requestPending_ = false;\n      break;\n  }\n  return len;\n}\n\nsize_t HTTP1xCodec::generateRstStream(IOBufQueue& /*writeBuf*/,\n                                      StreamID /*txn*/,\n                                      ErrorCode /*statusCode*/) {\n  // statusCode ignored for HTTP/1.1\n  // We won't be able to send anything else on the transport after this.\n  keepalive_ = false;\n  return 0;\n}\n\nsize_t HTTP1xCodec::generateGoaway(IOBufQueue&,\n                                   StreamID lastStream,\n                                   ErrorCode error,\n                                   std::unique_ptr<folly::IOBuf>) {\n  // statusCode ignored for HTTP/1.1\n  // We won't be able to send anything else on the transport after this.\n  // For clients, immediately mark keepalive_ false.  For servers, wait until\n  // we've flushed the next headers.\n  if (isUpstream(transportDirection_) || disableKeepalivePending_ ||\n      lastStream != HTTPCodec::MaxStreamID || error != ErrorCode::NO_ERROR) {\n    keepalive_ = false;\n  } else {\n    disableKeepalivePending_ = true;\n  }\n  return 0;\n}\n\nint HTTP1xCodec::onMessageBegin() {\n  headersComplete_ = false;\n  headerSize_.uncompressed = 0;\n  headerSize_.compressed = 0;\n  headerParseState_ = HeaderParseState::kParsingHeaderStart;\n  msg_ = std::make_unique<HTTPMessage>();\n  trailers_.reset();\n  if (isDownstream(transportDirection_)) {\n    requestPending_ = true;\n    responsePending_ = true;\n  }\n  // If there was a 1xx on this connection, don't increment the ingress txn id\n  if (isDownstream(transportDirection_) || !is1xxResponse_) {\n    ++ingressTxnID_;\n  }\n  if (isUpstream(transportDirection_)) {\n    is1xxResponse_ = false;\n  }\n  callback_->onMessageBegin(ingressTxnID_, msg_.get());\n  return 0;\n}\n\nint HTTP1xCodec::onURL(const char* buf, size_t len) {\n  url_.append(buf, len);\n  return 0;\n}\n\nint HTTP1xCodec::onReason(const char* buf, size_t len) {\n  reason_.append(buf, len);\n  return 0;\n}\n\nbool HTTP1xCodec::pushHeaderNameAndValue(HTTPHeaders& hdrs) {\n  // Header names are strictly validated by http_parser, however, it allows\n  // quoted+escaped CTLs so run our stricter check here.\n  if (strictValidation_) {\n    folly::StringPiece headerName(currentHeaderName_.empty()\n                                      ? currentHeaderNameStringPiece_\n                                      : currentHeaderName_);\n    bool compatValidate = false;\n    if (!CodecUtil::validateHeaderValue(\n            folly::StringPiece(currentHeaderValue_),\n            compatValidate ? CodecUtil::CtlEscapeMode::STRICT_COMPAT\n                           : CodecUtil::CtlEscapeMode::STRICT)) {\n      validationError_ = kErrorHeaderContentValidation;\n      LOG(ERROR) << \"Invalid header name=\" << headerName;\n      DVLOG(4) << \" value=\" << currentHeaderValue_;\n      // Hexlify the value for debuggability in case it contains non-printable\n      // characters like \\n, \\t, etc.\n      DVLOG(4) << \" value in hex=\" << folly::hexlify(currentHeaderValue_);\n      return false;\n    }\n  }\n  auto headerName = currentHeaderName_.empty()\n                        ? currentHeaderNameStringPiece_\n                        : folly::StringPiece(currentHeaderName_);\n  hdrs.add(headerName, std::move(currentHeaderValue_));\n  currentHeaderName_.clear();\n  currentHeaderNameStringPiece_.clear();\n  currentHeaderValue_.clear();\n\n  return true;\n}\n\nint HTTP1xCodec::onHeaderField(const char* buf, size_t len) {\n  bool valid = true;\n  if (headerParseState_ == HeaderParseState::kParsingHeaderValue) {\n    valid = pushHeaderNameAndValue(msg_->getHeaders());\n  } else if (headerParseState_ == HeaderParseState::kParsingTrailerValue) {\n    if (!trailers_) {\n      trailers_ = std::make_unique<HTTPHeaders>();\n    }\n    valid = pushHeaderNameAndValue(*trailers_);\n  }\n  if (!valid) {\n    return -1;\n  }\n\n  if (isParsingHeaderOrTrailerName()) {\n\n    // we're already parsing a header name\n    if (currentHeaderName_.empty()) {\n      // but we've been keeping it in currentHeaderNameStringPiece_ until now\n      if (currentHeaderNameStringPiece_.end() == buf) {\n        // the header name we are currently reading is contiguous in memory,\n        // and so we just adjust the right end of our StringPiece;\n        // this is likely because onIngress() hasn't been called since we got\n        // the previous chunk (otherwise currentHeaderName_ would be nonempty)\n        currentHeaderNameStringPiece_.advance(len);\n      } else {\n        // this is just for safety - if for any reason there is a discontinuity\n        // even though we are during the same onIngress() call,\n        // we fall back to currentHeaderName_\n        currentHeaderName_.assign(currentHeaderNameStringPiece_.begin(),\n                                  currentHeaderNameStringPiece_.size());\n        currentHeaderName_.append(buf, len);\n      }\n    } else {\n      // we had already fallen back to currentHeaderName_ before\n      currentHeaderName_.append(buf, len);\n    }\n\n  } else {\n    // we're not yet parsing a header name - this is the first chunk\n    // (typically, there is only one)\n    currentHeaderNameStringPiece_.reset(buf, len);\n\n    if (headerParseState_ >= HeaderParseState::kParsingHeadersComplete) {\n      headerParseState_ = HeaderParseState::kParsingTrailerName;\n    } else {\n      headerParseState_ = HeaderParseState::kParsingHeaderName;\n    }\n  }\n  return 0;\n}\n\nint HTTP1xCodec::onHeaderValue(const char* buf, size_t len) {\n  if (isParsingHeaders()) {\n    headerParseState_ = HeaderParseState::kParsingHeaderValue;\n  } else {\n    headerParseState_ = HeaderParseState::kParsingTrailerValue;\n  }\n  currentHeaderValue_.append(buf, len);\n  return 0;\n}\n\nint HTTP1xCodec::onHeadersComplete(size_t len) {\n  if (headerParseState_ == HeaderParseState::kParsingHeaderValue) {\n    if (!pushHeaderNameAndValue(msg_->getHeaders())) {\n      return -1;\n    }\n  }\n\n  HTTPHeaders& hdrs = msg_->getHeaders();\n  if (!validateContentLen(hdrs)) {\n    return -1;\n  }\n  if (!validateTransferEncoding(hdrs)) {\n    return -1;\n  }\n\n  // Update the HTTPMessage with the values parsed from the header\n  msg_->setHTTPVersion(parser_.http_major, parser_.http_minor);\n  msg_->setIsChunked((parser_.flags & F_CHUNKED));\n\n  if (isDownstream(transportDirection_)) {\n    // Set the method type\n    msg_->setMethod(http_method_str(static_cast<http_method>(parser_.method)));\n\n    connectRequest_ = (msg_->getMethod() == HTTPMethod::CONNECT);\n\n    // If this is a headers-only request, we shouldn't send\n    // an entity-body in the response.\n    headRequest_ = (msg_->getMethod() == HTTPMethod::HEAD);\n\n    ParseURL parseUrl = msg_->setURL(std::move(url_), strictValidation_);\n    if (strictValidation_ && !parseUrl.valid()) {\n      LOG(ERROR) << \"Invalid URL: \" << msg_->getURL();\n      return -1;\n    }\n    url_.clear();\n\n    if (parseUrl.hasHost()) {\n      // RFC 2616 5.2.1 states \"If Request-URI is an absoluteURI, the host\n      // is part of the Request-URI. Any Host header field value in the\n      // request MUST be ignored.\"\n      auto hostAndPort = parseUrl.hostAndPort();\n      VLOG(4) << \"Adding inferred host header: \" << hostAndPort;\n      msg_->getHeaders().set(HTTP_HEADER_HOST, hostAndPort);\n    }\n\n    // If the client sent us an HTTP/1.x with x >= 1, we may send\n    // chunked responses.\n    mayChunkEgress_ = ((parser_.http_major == 1) && (parser_.http_minor >= 1));\n  } else {\n    msg_->setStatusCode(parser_.status_code);\n    msg_->setStatusMessage(std::move(reason_));\n    reason_.clear();\n  }\n\n  auto g = folly::makeGuard([this] {\n    // Always clear the outbound upgrade header after we receive a response\n    if (isUpstream(transportDirection_) && parser_.status_code != 100) {\n      upgradeHeader_.clear();\n    }\n  });\n  headerParseState_ = HeaderParseState::kParsingHeadersComplete;\n  if (isUpstream(transportDirection_)) {\n    if (connectRequest_ &&\n        (parser_.status_code >= 200 && parser_.status_code < 300)) {\n      // Enable upgrade if this is a 200 response to a CONNECT\n      // request we sent earlier\n      ingressUpgrade_ = true;\n    } else if (parser_.status_code == 101) {\n      // Set the upgrade flags if the server has upgraded.\n      const std::string& serverUpgrade =\n          msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE);\n      if (!serverAcceptedUpgrade(upgradeHeader_, serverUpgrade)) {\n        LOG(ERROR) << \"Invalid 101 response, client/server upgrade mismatch \"\n                      \"client=\"\n                   << upgradeHeader_ << \" server=\" << serverUpgrade;\n        return -1;\n      }\n      ingressUpgrade_ = egressUpgrade_ = true;\n    } else if (parser_.upgrade || parser_.flags & F_UPGRADE) {\n      // Ignore upgrade header for upstream response messages with status code\n      // different from 101 in case if it was not a response to CONNECT.\n      parser_.upgrade = false;\n      parser_.flags &= ~F_UPGRADE;\n    }\n  } else {\n    if (connectRequest_) {\n      // Enable upgrade by default for the CONNECT requests.\n      // If we locally reject CONNECT, we will disable this flag while\n      // sending the reject response. If we forward the req to upstream proxy,\n      // we will start forwarding data to the proxy without waiting for\n      // the response from the proxy server.\n      ingressUpgrade_ = true;\n    }\n  }\n  msg_->setIsUpgraded(ingressUpgrade_);\n\n  const std::string& upgrade = hdrs.getSingleOrEmpty(HTTP_HEADER_UPGRADE);\n  if (kUpgradeToken.equals(upgrade, folly::AsciiCaseInsensitive())) {\n    msg_->setIngressWebsocketUpgrade();\n    if (isUpstream(transportDirection_)) {\n      // response.\n      const std::string& accept =\n          hdrs.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT);\n      if (accept != websockAcceptKey_) {\n        LOG(ERROR) << \"Mismatch in expected ws accept key: \" << \"upstream: \"\n                   << accept << \" expected: \" << websockAcceptKey_;\n        return -1;\n      }\n    } else {\n      // request.\n      // If the websockAcceptKey is already set, we error out.\n      // Currently, websockAcceptKey is never cleared, which means\n      // that only one Websocket upgrade attempt can be made on the\n      // connection. If that upgrade request is not successful for any\n      // reason, the connection is no longer usable. At some point, we\n      // may want to change this to clear the websockAcceptKey if\n      // the request doesn't succeed keeping the connection usable.\n      if (!websockAcceptKey_.empty()) {\n        LOG(ERROR) << \"ws accept key already set: '\" << websockAcceptKey_\n                   << \"'\";\n        return -1;\n      }\n      auto key = hdrs.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY);\n      websockAcceptKey_ = generateWebsocketAccept(key);\n    }\n  }\n\n  bool msgKeepalive = msg_->computeKeepalive();\n  if (!msgKeepalive) {\n    keepalive_ = false;\n  }\n  if (isDownstream(transportDirection_)) {\n    // Remember whether this was an HTTP 1.0 request with keepalive enabled\n    if (msgKeepalive && msg_->isHTTP1_0() &&\n        (keepaliveRequested_ == KeepaliveRequested::UNSET ||\n         keepaliveRequested_ == KeepaliveRequested::ENABLED)) {\n      keepaliveRequested_ = KeepaliveRequested::ENABLED;\n    } else {\n      keepaliveRequested_ = KeepaliveRequested::DISABLED;\n    }\n  }\n\n  // Determine whether the HTTP parser should ignore any headers\n  // that indicate the presence of a message body.  This is needed,\n  // for example, if the message is a response to a request with\n  // method==HEAD.\n  bool ignoreBody;\n  if (isDownstream(transportDirection_)) {\n    ignoreBody = false;\n  } else {\n    is1xxResponse_ = msg_->is1xxResponse();\n    if (expectNoResponseBody_) {\n      ignoreBody = true;\n    } else {\n      ignoreBody = RFC2616::responseBodyMustBeEmpty(msg_->getStatusCode());\n    }\n  }\n\n  headersComplete_ = true;\n  headerSize_.uncompressed += len;\n  headerSize_.compressed += len;\n  msg_->setIngressHeaderSize(headerSize_);\n\n  if (userAgent_.empty()) {\n    userAgent_ = msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_USER_AGENT);\n  }\n  callback_->onHeadersComplete(ingressTxnID_, std::move(msg_));\n\n  // 1 is a magic value that tells the http_parser not to expect a\n  // message body even if the message header implied the presence\n  // of one (e.g., via a Content-Length)\n  return (ignoreBody) ? 1 : 0;\n}\n\nint HTTP1xCodec::onBody(const char* buf, size_t len) {\n  DCHECK(!isParsingHeaders());\n  DCHECK(!inRecvLastChunk_);\n  CHECK_NOTNULL(currentIngressBuf_);\n  const char* dataStart = (const char*)currentIngressBuf_->data();\n  const char* dataEnd = dataStart + currentIngressBuf_->length();\n  DCHECK_GE(buf, dataStart);\n  DCHECK_LE(buf + len, dataEnd);\n  unique_ptr<IOBuf> clone(currentIngressBuf_->cloneOne());\n  clone->trimStart(buf - dataStart);\n  clone->trimEnd(dataEnd - (buf + len));\n  DCHECK_EQ(len, clone->computeChainDataLength());\n  callback_->onBody(ingressTxnID_, std::move(clone), 0);\n  return 0;\n}\n\nint HTTP1xCodec::onChunkHeader(size_t len) {\n  if (len > 0) {\n    callback_->onChunkHeader(ingressTxnID_, len);\n  } else {\n    VLOG(5) << \"Suppressed onChunkHeader callback for final zero length \"\n            << \"chunk\";\n    inRecvLastChunk_ = true;\n  }\n  return 0;\n}\n\nint HTTP1xCodec::onChunkComplete() {\n  if (inRecvLastChunk_) {\n    inRecvLastChunk_ = false;\n  } else {\n    callback_->onChunkComplete(ingressTxnID_);\n  }\n  return 0;\n}\n\nint HTTP1xCodec::onMessageComplete() {\n  DCHECK(!isParsingHeaders());\n  DCHECK(!inRecvLastChunk_);\n  if (headerParseState_ == HeaderParseState::kParsingTrailerValue) {\n    if (!trailers_) {\n      trailers_ = std::make_unique<HTTPHeaders>();\n    }\n    if (!pushHeaderNameAndValue(*trailers_)) {\n      return -1;\n    }\n  }\n\n  headerParseState_ = HeaderParseState::kParsingHeaderIdle;\n  if (trailers_) {\n    callback_->onTrailersComplete(ingressTxnID_, std::move(trailers_));\n  }\n\n  switch (transportDirection_) {\n    case TransportDirection::DOWNSTREAM: {\n      requestPending_ = false;\n      break;\n    }\n    case TransportDirection::UPSTREAM:\n      responsePending_ = is1xxResponse_;\n      if (is1xxResponse_ && !ingressUpgrade_) {\n        // Some other 1xx status code, which doesn't terminate the message\n        return 0;\n      }\n  }\n\n  callback_->onMessageComplete(ingressTxnID_, ingressUpgrade_);\n\n  if (ingressUpgrade_) {\n    ingressUpgradeComplete_ = true;\n    // If upgrade is complete, any pending data should not be parsed.\n    // It must be forwarded directly to the handler.\n    setParserPaused(true);\n  }\n\n  return 0;\n}\n\nint HTTP1xCodec::onMessageBeginCB(http_parser* parser) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onMessageBegin();\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onUrlCB(http_parser* parser, const char* buf, size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onURL(buf, len);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onReasonCB(http_parser* parser, const char* buf, size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onReason(buf, len);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onHeaderFieldCB(http_parser* parser,\n                                 const char* buf,\n                                 size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onHeaderField(buf, len);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onHeaderValueCB(http_parser* parser,\n                                 const char* buf,\n                                 size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onHeaderValue(buf, len);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onHeadersCompleteCB(http_parser* parser,\n                                     const char* /*buf*/,\n                                     size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onHeadersComplete(len);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 3;\n  }\n}\n\nint HTTP1xCodec::onBodyCB(http_parser* parser, const char* buf, size_t len) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onBody(buf, len);\n  } catch (const std::exception& ex) {\n    // Note: http_parser appears to completely ignore the return value from the\n    // on_body() callback.  There seems to be no way to abort parsing after an\n    // error in on_body().\n    //\n    // We handle this by checking if error_ is set after each call to\n    // http_parser_execute().\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onChunkHeaderCB(http_parser* parser) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onChunkHeader(parser->content_length);\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onChunkCompleteCB(http_parser* parser) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onChunkComplete();\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nint HTTP1xCodec::onMessageCompleteCB(http_parser* parser) {\n  auto* codec = static_cast<HTTP1xCodec*>(parser->data);\n  DCHECK(codec != nullptr);\n  DCHECK_EQ(&codec->parser_, parser);\n\n  try {\n    return codec->onMessageComplete();\n  } catch (const std::exception& ex) {\n    codec->onParserError(ex.what());\n    return 1;\n  }\n}\n\nbool HTTP1xCodec::supportsNextProtocol(folly::StringPiece alpn) {\n  return alpn.size() == 8 && (alpn == \"http/1.0\" || alpn == \"http/1.1\");\n}\n\nHTTP1xCodec HTTP1xCodec::makeResponseCodec(bool mayChunkEgress) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  codec.mayChunkEgress_ = mayChunkEgress;\n  return codec;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP1xCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <string>\n\n#include <proxygen/external/http_parser/http_parser.h>\n\nnamespace proxygen {\n\nclass HTTP1xCodec : public HTTPCodec {\n public:\n  // Default strictValidation to false for now to match existing behavior\n  explicit HTTP1xCodec(TransportDirection direction,\n                       bool force1_1 = false,\n                       bool strictValidation = false);\n  ~HTTP1xCodec() override;\n\n  HTTP1xCodec(HTTP1xCodec&&) = default;\n  HTTP1xCodec& operator=(HTTP1xCodec&&) = default;\n  HTTP1xCodec(const HTTP1xCodec&) = delete;\n  HTTP1xCodec& operator=(const HTTP1xCodec&) = delete;\n\n  // Returns codec for response generation, allowing to set flags that are\n  // normally set during request processing.\n  // Normally codec processes request/response pair, but is also used for\n  // serialization and processes single message.\n  static HTTP1xCodec makeResponseCodec(bool mayChunkEgress);\n\n  // HTTPCodec API\n  CodecProtocol getProtocol() const override {\n    return CodecProtocol::HTTP_1_1;\n  }\n\n  const std::string& getUserAgent() const override {\n    return userAgent_;\n  }\n\n  TransportDirection getTransportDirection() const override {\n    return transportDirection_;\n  }\n  StreamID createStream() override;\n  void setCallback(Callback* callback) override {\n    callback_ = callback;\n  }\n  bool isBusy() const override;\n  void setParserPaused(bool paused) override;\n  bool isParserPaused() const override {\n    return parserPaused_;\n  }\n  size_t onIngress(const folly::IOBuf& buf) override;\n  size_t onIngressImpl(const folly::IOBuf& buf);\n  void onIngressEOF() override;\n  bool isReusable() const override;\n  bool isWaitingToDrain() const override {\n    return disableKeepalivePending_ && keepalive_;\n  }\n  bool isEgressBusy() const {\n    return ((transportDirection_ == TransportDirection::DOWNSTREAM &&\n             responsePending_) ||\n            // count egress busy for non-upgraded upstream codecs with a\n            // pending response.  HTTP/1.x servers are inconsistent in how they\n            // interpret an EOF with a pending response, so don't trigger one\n            // unless the connection was upgraded or if it's explicitly\n            // required.\n            (transportDirection_ == TransportDirection::UPSTREAM &&\n             (requestPending_ || (!egressUpgrade_ && responsePending_ &&\n                                  !releaseEgressAfterRequest_))));\n  }\n  // True if the session requires an EOF (or RST) to terminate the message\n  bool closeOnEgressComplete() const override {\n    return !isEgressBusy() && !isReusable();\n  }\n  bool supportsParallelRequests() const override {\n    return false;\n  }\n  bool supportsPushTransactions() const override {\n    return false;\n  }\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID txn,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override;\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID txn,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n  size_t generateChunkHeader(folly::IOBufQueue& writeBuf,\n                             StreamID txn,\n                             size_t length) override;\n  size_t generateChunkTerminator(folly::IOBufQueue& writeBuf,\n                                 StreamID txn) override;\n  size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                          StreamID txn,\n                          const HTTPHeaders& trailers) override;\n  size_t generatePadding(folly::IOBufQueue& /* writeBuf */,\n                         StreamID /* stream */,\n                         uint16_t /* bytes */) override {\n    return 0;\n  }\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID txn) override;\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID txn,\n                           ErrorCode statusCode) override;\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream,\n      ErrorCode statusCode,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  size_t generateImmediateGoaway(folly::IOBufQueue&,\n                                 ErrorCode,\n                                 std::unique_ptr<folly::IOBuf>) override {\n    keepalive_ = false;\n    return 0;\n  }\n\n  void setStrictValidation(bool strict) {\n    strictValidation_ = strict;\n  }\n\n  void setReleaseEgressAfterRequest(bool releaseEgress) {\n    releaseEgressAfterRequest_ = releaseEgress;\n  }\n\n  /**\n   * @returns true if the codec supports the given ALPN protocol.\n   */\n  static bool supportsNextProtocol(folly::StringPiece alpn);\n\n private:\n  /** Simple state model used to track the parsing of HTTP headers */\n  enum class HeaderParseState : uint8_t {\n    kParsingHeaderIdle,\n    kParsingHeaderStart,\n    kParsingHeaderName,\n    kParsingHeaderValue,\n    kParsingHeadersComplete,\n    kParsingTrailerName,\n    kParsingTrailerValue\n  };\n\n  std::string generateWebsocketKey() const;\n  std::string generateWebsocketAccept(const std::string& acceptKey) const;\n  mutable std::string websockAcceptKey_;\n\n  /** Used to keep track of whether a client requested keep-alive. This is\n   * only useful to support HTTP 1.0 keep-alive for a downstream connection\n   * where keep-alive is disabled unless the client requested it. */\n  enum class KeepaliveRequested : uint8_t {\n    UNSET,\n    ENABLED,  // incoming message requested keepalive\n    DISABLED, // incoming message disabled keepalive\n  };\n\n  void addDateHeader(folly::IOBufQueue& writeBuf, size_t& len);\n\n  /** Check whether we're currently parsing ingress message headers */\n  bool isParsingHeaders() const {\n    return (headerParseState_ > HeaderParseState::kParsingHeaderIdle) &&\n           (headerParseState_ < HeaderParseState::kParsingHeadersComplete);\n  }\n\n  /** Check whether we're currently parsing ingress header-or-trailer name */\n  bool isParsingHeaderOrTrailerName() const {\n    return (headerParseState_ == HeaderParseState::kParsingHeaderName) ||\n           (headerParseState_ == HeaderParseState::kParsingTrailerName);\n  }\n\n  /** Invoked when a parsing error occurs. It will send an exception to\n      the callback object to report the error and do any other cleanup\n      needed. It optionally takes a message to pass to the generated\n      HTTPException passed to callback_. */\n  void onParserError(const char* what = nullptr);\n\n  /** Push out header name-value pair to hdrs and clear currentHeader*_ */\n  bool pushHeaderNameAndValue(HTTPHeaders& hdrs);\n\n  /** Serialize websocket headers into a buffer **/\n  void serializeWebsocketHeader(folly::IOBufQueue& writeBuf,\n                                size_t& len,\n                                bool upstream);\n\n  // Parser callbacks\n  int onMessageBegin();\n  int onURL(const char* buf, size_t len);\n  int onReason(const char* buf, size_t len);\n  int onHeaderField(const char* buf, size_t len);\n  int onHeaderValue(const char* buf, size_t len);\n  int onHeadersComplete(size_t len);\n  int onBody(const char* buf, size_t len);\n  int onChunkHeader(size_t len);\n  int onChunkComplete();\n  int onMessageComplete();\n\n  HTTPCodec::Callback* callback_;\n  StreamID ingressTxnID_;\n  StreamID egressTxnID_;\n  http_parser parser_;\n  const folly::IOBuf* currentIngressBuf_;\n  std::unique_ptr<HTTPMessage> msg_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n  std::string currentHeaderName_;\n  folly::StringPiece currentHeaderNameStringPiece_;\n  std::string currentHeaderValue_;\n  std::string url_;\n  std::string userAgent_;\n  std::string reason_;\n  std::string upgradeHeader_; // last sent/received client upgrade header\n  HTTPHeaderSize headerSize_;\n  HeaderParseState headerParseState_;\n  TransportDirection transportDirection_;\n  KeepaliveRequested keepaliveRequested_; // only used in DOWNSTREAM mode\n  std::pair<CodecProtocol, std::string> upgradeResult_; // DOWNSTREAM only\n  folly::Optional<ProxygenError> validationError_;\n  bool force1_1_ : 1; // Use HTTP/1.1 even if msg is 1.0\n  bool strictValidation_ : 1;\n  bool parserActive_ : 1;\n  bool pendingEOF_ : 1;\n  bool parserPaused_ : 1;\n  bool parserError_ : 1;\n  bool requestPending_ : 1;\n  bool responsePending_ : 1;\n  bool egressChunked_ : 1;\n  bool inChunk_ : 1;\n  bool lastChunkWritten_ : 1;\n  bool keepalive_ : 1;\n  bool disableKeepalivePending_ : 1;\n  // TODO: replace the 2 booleans below with an enum \"request method\"\n  bool connectRequest_ : 1;\n  bool headRequest_ : 1;\n  bool expectNoResponseBody_ : 1;\n  bool mayChunkEgress_ : 1;\n  bool is1xxResponse_ : 1;\n  bool inRecvLastChunk_ : 1;\n  bool ingressUpgrade_ : 1;\n  bool ingressUpgradeComplete_ : 1;\n  bool egressUpgrade_ : 1;\n  bool nativeUpgrade_ : 1;\n  bool headersComplete_ : 1;\n  bool releaseEgressAfterRequest_ : 1;\n\n  // C-callable wrappers for the http_parser callbacks\n  static int onMessageBeginCB(http_parser* parser);\n  static int onPathCB(http_parser* parser, const char* buf, size_t len);\n  static int onQueryStringCB(http_parser* parser, const char* buf, size_t len);\n  static int onUrlCB(http_parser* parser, const char* buf, size_t len);\n  static int onReasonCB(http_parser* parser, const char* buf, size_t len);\n  static int onHeaderFieldCB(http_parser* parser, const char* buf, size_t len);\n  static int onHeaderValueCB(http_parser* parser, const char* buf, size_t len);\n  static int onHeadersCompleteCB(http_parser* parser,\n                                 const char* buf,\n                                 size_t len);\n  static int onBodyCB(http_parser* parser, const char* buf, size_t len);\n  static int onChunkHeaderCB(http_parser* parser);\n  static int onChunkCompleteCB(http_parser* parser);\n  static int onMessageCompleteCB(http_parser* parser);\n\n  static const http_parser_settings* getParserSettings();\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Codec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n\n#include <folly/base64.h>\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/utils/Logging.h>\n\n#include <folly/Conv.h>\n#include <folly/Random.h>\n#include <folly/Try.h>\n#include <folly/io/Cursor.h>\n#include <folly/tracing/ScopedTraceSection.h>\n#include <type_traits>\n\nusing namespace folly::io;\nusing namespace folly;\n\nusing std::string;\n\nnamespace {\nconst size_t kDefaultGrowth = 4000;\n} // namespace\n\nnamespace proxygen {\n\nHTTP2Codec::HTTP2Codec(TransportDirection direction)\n    : HTTPParallelCodec(direction),\n      headerCodec_(direction),\n      frameState_(isDownstream(direction)\n                      ? FrameState::UPSTREAM_CONNECTION_PREFACE\n                      : FrameState::EXPECT_FIRST_SETTINGS) {\n\n  const auto maxHeaderListSize =\n      egressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE);\n  if (maxHeaderListSize) {\n    headerCodec_.setMaxUncompressed(maxHeaderListSize->value);\n  }\n\n  VLOG(4) << \"creating \" << getTransportDirectionString(direction)\n          << \" HTTP/2 codec\";\n}\n\nHTTP2Codec::~HTTP2Codec() = default;\n\n// HTTPCodec API\n\nsize_t HTTP2Codec::onIngress(const folly::IOBuf& buf) noexcept {\n  // TODO: ensure only 1 parse at a time on stack.\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTP2Codec - onIngress\");\n\n  Cursor cursor(&buf);\n  size_t parsed = 0;\n  ErrorCode connError = ErrorCode::NO_ERROR;\n  auto bufLen = cursor.totalLength();\n  while (connError == ErrorCode::NO_ERROR) {\n    size_t remaining = bufLen - parsed;\n    if (frameState_ == FrameState::UPSTREAM_CONNECTION_PREFACE) {\n      if (remaining >= http2::kConnectionPreface.length()) {\n        auto test = cursor.readFixedString(http2::kConnectionPreface.length());\n        parsed += http2::kConnectionPreface.length();\n        if (test != http2::kConnectionPreface) {\n          goawayErrorMessage_ = \"missing connection preface\";\n          VLOG(4) << goawayErrorMessage_;\n          connError = ErrorCode::PROTOCOL_ERROR;\n        }\n        frameState_ = FrameState::EXPECT_FIRST_SETTINGS;\n      } else {\n        break;\n      }\n    } else if (frameState_ == FrameState::FRAME_HEADER ||\n               frameState_ == FrameState::EXPECT_FIRST_SETTINGS) {\n      // Waiting to parse the common frame header\n      if (remaining >= http2::kFrameHeaderSize) {\n        connError = parseFrameHeader(cursor, curHeader_);\n        parsed += http2::kFrameHeaderSize;\n        if (frameState_ == FrameState::EXPECT_FIRST_SETTINGS &&\n            curHeader_.type != http2::FrameType::SETTINGS) {\n          goawayErrorMessage_ = folly::to<string>(\n              \"GOAWAY error: got invalid connection preface frame type=\",\n              getFrameTypeString(curHeader_.type),\n              \"(\",\n              curHeader_.type,\n              \")\",\n              \" for streamID=\",\n              curHeader_.stream);\n          VLOG(4) << goawayErrorMessage_;\n          connError = ErrorCode::PROTOCOL_ERROR;\n        }\n        if (curHeader_.length > maxRecvFrameSize()) {\n          VLOG(4) << \"Excessively large frame len=\" << curHeader_.length;\n          connError = ErrorCode::FRAME_SIZE_ERROR;\n        }\n\n        if (callback_) {\n          callback_->onFrameHeader(curHeader_.stream,\n                                   curHeader_.flags,\n                                   curHeader_.length,\n                                   static_cast<uint8_t>(curHeader_.type));\n        }\n\n        if (curHeader_.type == http2::FrameType::DATA) {\n          frameState_ = FrameState::DATA_FRAME_DATA;\n          pendingDataFrameBytes_ = curHeader_.length;\n          pendingDataFramePaddingBytes_ = 0;\n        } else {\n          frameState_ = FrameState::FRAME_DATA;\n        }\n#ifndef NDEBUG\n        receivedFrameCount_++;\n#endif\n      } else {\n        break;\n      }\n    } else if (frameState_ == FrameState::DATA_FRAME_DATA && remaining > 0 &&\n               (remaining < curHeader_.length ||\n                pendingDataFrameBytes_ < curHeader_.length)) {\n      // FrameState::DATA_FRAME_DATA with partial data only\n      size_t dataParsed = 0;\n      connError = parseDataFrameData(cursor, remaining, dataParsed);\n      if (dataParsed == 0 && pendingDataFrameBytes_ > 0) {\n        // We received only the padding byte, we will wait for more\n        break;\n      } else {\n        parsed += dataParsed;\n        if (pendingDataFrameBytes_ == 0) {\n          frameState_ = FrameState::FRAME_HEADER;\n        }\n      }\n    } else { // FrameState::FRAME_DATA\n             // or FrameState::DATA_FRAME_DATA with all data available\n      // Already parsed the common frame header\n      const auto frameLen = curHeader_.length;\n      if (remaining >= frameLen) {\n        connError = parseFrame(cursor);\n        parsed += curHeader_.length;\n        frameState_ = FrameState::FRAME_HEADER;\n      } else {\n        break;\n      }\n    }\n  }\n  checkConnectionError(connError, &buf);\n  return parsed;\n}\n\nErrorCode HTTP2Codec::parseFrame(folly::io::Cursor& cursor) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTP2Codec - parseFrame\");\n  if (expectedContinuationStream_ != 0 &&\n      (curHeader_.type != http2::FrameType::CONTINUATION ||\n       expectedContinuationStream_ != curHeader_.stream)) {\n    goawayErrorMessage_ = folly::to<string>(\n        \"GOAWAY error: while expected CONTINUATION with stream=\",\n        expectedContinuationStream_,\n        \", received streamID=\",\n        curHeader_.stream,\n        \" of type=\",\n        getFrameTypeString(curHeader_.type));\n    VLOG(4) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (expectedContinuationStream_ == 0 &&\n      curHeader_.type == http2::FrameType::CONTINUATION) {\n    goawayErrorMessage_ = folly::to<string>(\n        \"GOAWAY error: unexpected CONTINUATION received with streamID=\",\n        curHeader_.stream);\n    VLOG(4) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (frameAffectsCompression(curHeader_.type) &&\n      curHeaderBlock_.chainLength() + curHeader_.length >\n          egressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE, 0)) {\n    // this may be off by up to the padding length (max 255), but\n    // these numbers are already so generous, and we're comparing the\n    // max-uncompressed to the actual compressed size.  Let's fail\n    // before buffering.\n\n    // TODO(t6513634): it would be nicer to stream-process this header\n    // block to keep the connection state consistent without consuming\n    // memory, and fail just the request per the HTTP/2 spec (section\n    // 10.3)\n    goawayErrorMessage_ = folly::to<string>(\n        \"Failing connection due to excessively large headers\");\n    LOG(ERROR) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n\n  if (curHeader_.type == http2::FrameType::CONTINUATION) {\n    if (continuationFramesLeftInHeaderBlock_ == 0) {\n      goawayErrorMessage_ = folly::to<string>(\n          \"GOAWAY error: too many CONTINUATION frames for stream=\",\n          curHeader_.stream);\n      LOG(ERROR) << goawayErrorMessage_;\n      return ErrorCode::ENHANCE_YOUR_CALM;\n    }\n    --continuationFramesLeftInHeaderBlock_;\n  } else {\n    continuationFramesLeftInHeaderBlock_ = kMaxContinuationFramesPerHeaderBlock;\n  }\n\n  expectedContinuationStream_ = (frameAffectsCompression(curHeader_.type) &&\n                                 !(curHeader_.flags & http2::END_HEADERS))\n                                    ? curHeader_.stream\n                                    : 0;\n\n  switch (curHeader_.type) {\n    case http2::FrameType::DATA:\n      return parseAllData(cursor);\n    case http2::FrameType::HEADERS:\n      return parseHeaders(cursor);\n    case http2::FrameType::RST_STREAM:\n      return parseRstStream(cursor);\n    case http2::FrameType::SETTINGS:\n      return parseSettings(cursor);\n    case http2::FrameType::PUSH_PROMISE:\n      return parsePushPromise(cursor);\n    case http2::FrameType::PING:\n      return parsePing(cursor);\n    case http2::FrameType::GOAWAY:\n      return parseGoaway(cursor);\n    case http2::FrameType::WINDOW_UPDATE:\n      return parseWindowUpdate(cursor);\n    case http2::FrameType::CONTINUATION:\n      return parseContinuation(cursor);\n    case http2::FrameType::ALTSVC:\n      // fall through, unimplemented\n      break;\n    case http2::FrameType::RFC9218_PRIORITY:\n      return parseRFC9218Priority(cursor);\n    case http2::FrameType::CERTIFICATE_REQUEST:\n      return parseCertificateRequest(cursor);\n    case http2::FrameType::CERTIFICATE:\n      return parseCertificate(cursor);\n    // The following frame types are defined in the enum but not handled here:\n    case http2::FrameType::PRIORITY:\n    case http2::FrameType::PADDING:\n    case http2::FrameType::CERTIFICATE_NEEDED:\n    case http2::FrameType::USE_CERTIFICATE:\n    default:\n      // Implementations MUST ignore and discard any frame that has a\n      // type that is unknown or unhandled\n      break;\n  }\n\n  // Landing here means unknown, unimplemented or ignored frame.\n  // PRIORITY frames are deprecated in RFC 9113/9218 but still sent by legacy H2\n  // implementations. Skip logging these frames to reduce noise.\n  if (curHeader_.type != http2::FrameType::PRIORITY) {\n    VLOG(2) << \"Skipping frame (type=\" << static_cast<int>(curHeader_.type)\n            << \")\";\n  }\n  cursor.skip(curHeader_.length);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::handleEndStream() {\n  if (curHeader_.type != http2::FrameType::HEADERS &&\n      curHeader_.type != http2::FrameType::CONTINUATION &&\n      curHeader_.type != http2::FrameType::DATA) {\n    return ErrorCode::NO_ERROR;\n  }\n\n  // do we need to handle case where this stream has already aborted via\n  // another callback (onHeadersComplete/onBody)?\n  pendingEndStreamHandling_ |= (curHeader_.flags & http2::END_STREAM);\n\n  // with a websocket upgrade, we need to send message complete cb to\n  // mirror h1x codec's behavior. when the stream closes, we need to\n  // send another callback to clean up the stream's resources.\n  if (ingressWebsocketUpgrade_) {\n    ingressWebsocketUpgrade_ = false;\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageComplete,\n                             \"onMessageComplete\",\n                             curHeader_.stream,\n                             true);\n  }\n\n  if (pendingEndStreamHandling_ && expectedContinuationStream_ == 0) {\n    pendingEndStreamHandling_ = false;\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageComplete,\n                             \"onMessageComplete\",\n                             curHeader_.stream,\n                             false);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseAllData(Cursor& cursor) {\n  std::unique_ptr<IOBuf> outData;\n  uint16_t padding = 0;\n  VLOG(10) << \"parsing all frame DATA bytes for stream=\" << curHeader_.stream\n           << \" length=\" << curHeader_.length;\n  auto ret = http2::parseData(cursor, curHeader_, outData, padding);\n  RETURN_IF_ERROR(ret);\n\n  if (callback_ && (padding > 0 || (outData && !outData->empty()))) {\n    if (!outData) {\n      outData = std::make_unique<IOBuf>();\n    }\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onBody,\n                             \"onBody\",\n                             curHeader_.stream,\n                             std::move(outData),\n                             padding);\n  }\n  return handleEndStream();\n}\n\nErrorCode HTTP2Codec::parseDataFrameData(Cursor& cursor,\n                                         size_t bufLen,\n                                         size_t& parsed) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTP2Codec - parseDataFrameData\");\n  if (bufLen == 0) {\n    VLOG(10) << \"No data to parse\";\n    return ErrorCode::NO_ERROR;\n  }\n\n  std::unique_ptr<IOBuf> outData;\n  uint16_t padding = 0;\n  VLOG(10) << \"parsing DATA frame data for stream=\" << curHeader_.stream\n           << \" frame data length=\" << curHeader_.length\n           << \" pendingDataFrameBytes_=\" << pendingDataFrameBytes_\n           << \" pendingDataFramePaddingBytes_=\" << pendingDataFramePaddingBytes_\n           << \" bufLen=\" << bufLen << \" parsed=\" << parsed;\n  // Parse the padding information only the first time\n  if (pendingDataFrameBytes_ == curHeader_.length &&\n      pendingDataFramePaddingBytes_ == 0) {\n    if (frameHasPadding(curHeader_) && bufLen == 1) {\n      // We need to wait for more bytes otherwise we won't be able to pass\n      // the correct padding to the first onBody call\n      return ErrorCode::NO_ERROR;\n    }\n    const auto ret = http2::parseDataBegin(cursor, curHeader_, parsed, padding);\n    RETURN_IF_ERROR(ret);\n    if (padding > 0) {\n      pendingDataFramePaddingBytes_ = padding - 1;\n      pendingDataFrameBytes_--;\n      bufLen--;\n      parsed++;\n    }\n    VLOG(10)\n        << \"out padding=\" << padding\n        << \" pendingDataFrameBytes_=\" << pendingDataFrameBytes_\n        << \" pendingDataFramePaddingBytes_=\" << pendingDataFramePaddingBytes_\n        << \" bufLen=\" << bufLen << \" parsed=\" << parsed;\n  }\n  if (bufLen > 0) {\n    // Check if we have application data to parse\n    if (pendingDataFrameBytes_ > pendingDataFramePaddingBytes_) {\n      const size_t pendingAppData =\n          pendingDataFrameBytes_ - pendingDataFramePaddingBytes_;\n      const size_t toClone = std::min(pendingAppData, bufLen);\n      cursor.clone(outData, toClone);\n      bufLen -= toClone;\n      pendingDataFrameBytes_ -= toClone;\n      parsed += toClone;\n      VLOG(10) << \"parsed some app data, pendingDataFrameBytes_=\"\n               << pendingDataFrameBytes_ << \" pendingDataFramePaddingBytes_=\"\n               << pendingDataFramePaddingBytes_ << \" bufLen=\" << bufLen\n               << \" parsed=\" << parsed;\n    }\n    // Check if we have padding bytes to parse\n    if (bufLen > 0 && pendingDataFramePaddingBytes_ > 0) {\n      size_t toSkip = 0;\n      auto ret = http2::parseDataEnd(\n          cursor, bufLen, pendingDataFramePaddingBytes_, toSkip);\n      RETURN_IF_ERROR(ret);\n      pendingDataFrameBytes_ -= toSkip;\n      pendingDataFramePaddingBytes_ -= toSkip;\n      parsed += toSkip;\n      VLOG(10) << \"parsed some padding, pendingDataFrameBytes_=\"\n               << pendingDataFrameBytes_ << \" pendingDataFramePaddingBytes_=\"\n               << pendingDataFramePaddingBytes_ << \" bufLen=\" << bufLen\n               << \" parsed=\" << parsed;\n    }\n  }\n\n  if (callback_ && (padding > 0 || (outData && !outData->empty()))) {\n    if (!outData) {\n      outData = std::make_unique<IOBuf>();\n    }\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onBody,\n                             \"onBody\",\n                             curHeader_.stream,\n                             std::move(outData),\n                             padding);\n  }\n  return (pendingDataFrameBytes_ > 0) ? ErrorCode::NO_ERROR : handleEndStream();\n}\n\nErrorCode HTTP2Codec::parseHeaders(Cursor& cursor) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTP2Codec - parseHeaders\");\n  std::unique_ptr<IOBuf> headerBuf;\n  VLOG(4) << \"parsing HEADERS frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  auto err = http2::parseHeaders(cursor, curHeader_, headerBuf);\n  RETURN_IF_ERROR(err);\n  if (isDownstream(transportDirection_)) {\n    RETURN_IF_ERROR(\n        checkNewStream(curHeader_.stream, true /* trailersAllowed */));\n  }\n  err = parseHeadersImpl(cursor, std::move(headerBuf), folly::none);\n  return err;\n}\n\nErrorCode HTTP2Codec::parseContinuation(Cursor& cursor) {\n  std::unique_ptr<IOBuf> headerBuf;\n  VLOG(4) << \"parsing CONTINUATION frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  auto err = http2::parseContinuation(cursor, curHeader_, headerBuf);\n  RETURN_IF_ERROR(err);\n  err = parseHeadersImpl(cursor, std::move(headerBuf), folly::none);\n  return err;\n}\n\nErrorCode HTTP2Codec::parseHeadersImpl(\n    Cursor& /*cursor*/,\n    std::unique_ptr<IOBuf> headerBuf,\n    const folly::Optional<uint32_t>& promisedStream) {\n  if (curHeader_.type == http2::FrameType::CONTINUATION && headerBuf) {\n    constexpr size_t kMinTailroomForUnshare = 1024;\n    if (!curHeaderBlock_.empty() &&\n        curHeaderBlock_.front()->prev()->isSharedOne() &&\n        headerBuf->prev()->tailroom() > kMinTailroomForUnshare) {\n      // tail of the current block is shared, but tail of new block has ample\n      // space.  Unshare tail of the new buf to allow future packing\n      headerBuf->prev()->unshareOne();\n    }\n  }\n  curHeaderBlock_.append(\n      std::move(headerBuf), /*pack=*/true, /*allowTailReuse=*/false);\n  std::unique_ptr<HTTPMessage> msg;\n  uint32_t headersCompleteStream = curHeader_.stream;\n\n  // if we're not parsing CONTINUATION, then it's start of new header block\n  if (curHeader_.type != http2::FrameType::CONTINUATION) {\n    headerBlockFrameType_ = curHeader_.type;\n    if (promisedStream) {\n      parsingReq_ = true;\n    } else {\n      parsingReq_ = isDownstream(transportDirection_);\n    }\n  } else if (headerBlockFrameType_ == http2::FrameType::PUSH_PROMISE) {\n    CHECK(promisedStream_.hasValue());\n    headersCompleteStream = *promisedStream_;\n  }\n\n  DeferredParseError parseError;\n  if (curHeader_.flags & http2::END_HEADERS) {\n    auto parseRes = parseHeadersDecodeFrames();\n    if (parseRes.hasError()) {\n      parseError = std::move(parseRes.error());\n      if (parseError.connectionError) {\n        return parseError.errorCode;\n      }\n    } else {\n      msg = std::move(*parseRes);\n    }\n  }\n\n  // Report back what we've parsed\n  auto concurError = parseHeadersCheckConcurrentStreams();\n  if (concurError.has_value()) {\n    return concurError.value();\n  }\n\n  bool trailers = parsingTrailers();\n  bool allHeaderFramesReceived =\n      (curHeader_.flags & http2::END_HEADERS) &&\n      (headerBlockFrameType_ == http2::FrameType::HEADERS);\n  if (allHeaderFramesReceived && !trailers) {\n    // Only deliver onMessageBegin once per stream.\n    // For responses with CONTINUATION, this will be delayed until\n    // the frame with the END_HEADERS flag set.\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageBegin,\n                             \"onMessageBegin\",\n                             curHeader_.stream,\n                             msg.get());\n  } else if (curHeader_.type == http2::FrameType::PUSH_PROMISE) {\n    DCHECK(promisedStream);\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onPushMessageBegin,\n                             \"onPushMessageBegin\",\n                             *promisedStream,\n                             curHeader_.stream,\n                             msg.get());\n    promisedStream_ = *promisedStream;\n    headersCompleteStream = *promisedStream;\n  }\n\n  if (curHeader_.flags & http2::END_HEADERS) {\n    if (!msg) {\n      deliverDeferredParseError(parseError);\n      return ErrorCode::NO_ERROR;\n    }\n    if (!(curHeader_.flags & http2::END_STREAM)) {\n      // If it there are DATA frames coming, consider it chunked\n      msg->setIsChunked(true);\n    }\n    if (trailers) {\n      VLOG(4) << \"Trailers complete for streamId=\" << headersCompleteStream\n              << \" direction=\" << transportDirection_;\n      auto trailerHeaders =\n          std::make_unique<HTTPHeaders>(msg->extractHeaders());\n      msg.reset();\n      deliverCallbackIfAllowed(&HTTPCodec::Callback::onTrailersComplete,\n                               \"onTrailersComplete\",\n                               headersCompleteStream,\n                               std::move(trailerHeaders));\n    } else {\n      if (isUpstream(transportDirection_) && curHeader_.stream & 0x01 &&\n          curHeader_.stream >= nextEgressStreamID_) {\n        goawayErrorMessage_ = folly::to<std::string>(\n            \"HEADERS on idle upstream stream=\", curHeader_.stream);\n        LOG(ERROR) << goawayErrorMessage_;\n        return ErrorCode::PROTOCOL_ERROR;\n      }\n      deliverCallbackIfAllowed(&HTTPCodec::Callback::onHeadersComplete,\n                               \"onHeadersComplete\",\n                               headersCompleteStream,\n                               std::move(msg));\n      promisedStream_ = folly::none;\n    }\n  }\n  return handleEndStream();\n}\n\nfolly::Expected<std::unique_ptr<HTTPMessage>, HTTP2Codec::DeferredParseError>\nHTTP2Codec::parseHeadersDecodeFrames() {\n  // decompress headers\n  Cursor headerCursor(curHeaderBlock_.front());\n\n  // DO NOT return from this method until after the call to decodeStreaming\n  // unless you return a connection error.  Otherwise the HPACK state will\n  // get messed up.\n  decodeInfo_.init(parsingReq_,\n                   parsingDownstreamTrailers_,\n                   validateHeaders_,\n                   strictValidation_,\n                   false);\n\n  headerCodec_.decodeStreaming(\n      headerCursor, curHeaderBlock_.chainLength(), this);\n  auto msg = std::move(decodeInfo_.msg);\n  // Saving this in case we need to log it on error\n  auto g = folly::makeGuard([this] { curHeaderBlock_.move(); });\n  // Check decoding error\n  if (decodeInfo_.decodeError != HPACK::DecodeError::NONE) {\n    constexpr std::string_view kDecodeErrorMessage =\n        \"Failed decoding header block for stream=\";\n    // Avoid logging header blocks that have failed decoding due to being\n    // excessively large.\n    if (decodeInfo_.decodeError != HPACK::DecodeError::HEADERS_TOO_LARGE) {\n      goawayErrorMessage_ =\n          folly::to<std::string>(kDecodeErrorMessage,\n                                 curHeader_.stream,\n                                 \": decompression error=\",\n                                 uint32_t(decodeInfo_.decodeError));\n      LOG(ERROR) << goawayErrorMessage_\n                 << (VLOG_IS_ON(3) ? \", header block=\" : \"\");\n      VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);\n    } else {\n      goawayErrorMessage_ = folly::to<std::string>(\n          kDecodeErrorMessage, curHeader_.stream, \": headers too large\");\n      if (debugLevel_ > 0 && msg) {\n        LOG(ERROR) << \"HPACK Headers too large\"\n                   << CodecUtil::debugString(*msg, debugLevel_)\n                   << CodecUtil::debugString(msg->getHeaders(), debugLevel_);\n      }\n    }\n\n    if (msg) {\n      // print the partial message\n      msg->dumpMessage(3);\n    }\n    return folly::makeUnexpected(DeferredParseError(\n        ErrorCode::COMPRESSION_ERROR, true, empty_string, std::move(msg)));\n  }\n\n  // Check parsing error\n  if (!decodeInfo_.parsingError.empty()) {\n    // This is \"malformed\" per the RFC\n    LOG(ERROR) << \"Failed parsing header list for stream=\" << curHeader_.stream\n               << \", error=\" << decodeInfo_.parsingError;\n    if (!decodeInfo_.headerErrorValue.empty()) {\n      DVLOG(4) << \" value=\" << decodeInfo_.headerErrorValue;\n    }\n    VLOG(3) << \"Header block=\"\n            << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);\n    if (isDownstream(transportDirection_) && parsingHeaders() &&\n        !parsingTrailers()) {\n      return folly::makeUnexpected(\n          DeferredParseError(ErrorCode::NO_ERROR,\n                             false,\n                             folly::to<std::string>(\"HTTP2Codec stream error: \",\n                                                    \"stream=\",\n                                                    curHeader_.stream,\n                                                    \" status=\",\n                                                    400,\n                                                    \" error: \",\n                                                    decodeInfo_.parsingError),\n                             std::move(msg)));\n    } else {\n      // Upstream, PUSH_PROMISE or trailers parsing failed:\n      // PROTOCOL_ERROR\n      return folly::makeUnexpected(DeferredParseError(\n          ErrorCode::PROTOCOL_ERROR,\n          false,\n          folly::to<string>(\"Field section parsing failed txn=\",\n                            curHeader_.stream),\n          std::move(msg)));\n    }\n  }\n\n  return msg;\n}\n\nvoid HTTP2Codec::deliverDeferredParseError(\n    const DeferredParseError& parseError) {\n  CHECK(!parseError.connectionError);\n  if (parseError.errorCode != ErrorCode::NO_ERROR) {\n    streamError(parseError.errorMessage,\n                parseError.errorCode,\n                parsingHeaders(),\n                folly::none,\n                std::move(parseError.partialMessage));\n    if (promisedStream_) {\n      streamError(\"Malformed PUSH_PROMISE\",\n                  ErrorCode::REFUSED_STREAM,\n                  false,\n                  *promisedStream_);\n      promisedStream_ = folly::none;\n    }\n  } else {\n    HTTPException err(HTTPException::Direction::INGRESS,\n                      parseError.errorMessage);\n    err.setHttpStatusCode(400);\n    err.setProxygenError(decodeInfo_.proxygenError.value_or(kErrorParseHeader));\n    err.setPartialMsg(std::move(parseError.partialMessage));\n    deliverCallbackIfAllowed(&HTTPCodec::Callback::onError,\n                             \"onError\",\n                             curHeader_.stream,\n                             err,\n                             parsingHeaders());\n  }\n}\n\nfolly::Optional<ErrorCode> HTTP2Codec::parseHeadersCheckConcurrentStreams() {\n  if (!isInitiatedStream(curHeader_.stream) &&\n      (curHeader_.type == http2::FrameType::HEADERS)) {\n\n    // callback checks total number of streams is smaller than settings max\n    if (callback_ &&\n        callback_->numIncomingStreams() >=\n            egressSettings_.getSetting(SettingsId::MAX_CONCURRENT_STREAMS,\n                                       std::numeric_limits<int32_t>::max())) {\n      streamError(folly::to<string>(\"Exceeded max_concurrent_streams\"),\n                  ErrorCode::REFUSED_STREAM,\n                  true);\n      return ErrorCode::NO_ERROR;\n    }\n  }\n  return folly::none;\n}\n\nvoid HTTP2Codec::onHeader(const HPACKHeaderName& name,\n                          const folly::fbstring& value) {\n  if (decodeInfo_.onHeader(name, value)) {\n    if (userAgent_.empty() && name.getHeaderCode() == HTTP_HEADER_USER_AGENT) {\n      userAgent_ = value.toStdString();\n    }\n  } else {\n    VLOG(4) << \"dir=\" << uint32_t(transportDirection_)\n            << decodeInfo_.parsingError << \" codec=\" << headerCodec_;\n  }\n}\n\nvoid HTTP2Codec::onHeadersComplete(HTTPHeaderSize decodedSize,\n                                   bool /*acknowledge*/) {\n  decodeInfo_.onHeadersComplete(decodedSize);\n  decodeInfo_.msg->setAdvancedProtocolString(http2::kProtocolString);\n\n  HTTPMessage* msg = decodeInfo_.msg.get();\n  HTTPRequestVerifier& verifier = decodeInfo_.verifier;\n  if (isDownstream(transportDirection_) && verifier.hasUpgradeProtocol() &&\n      (*msg->getUpgradeProtocol() == headers::kWebsocketString) &&\n      msg->getMethod() == HTTPMethod::CONNECT) {\n    msg->setIngressWebsocketUpgrade();\n    ingressWebsocketUpgrade_ = true;\n  } else if (!upgradedStreams_.empty()) {\n    auto it = upgradedStreams_.find(curHeader_.stream);\n    if (it != upgradedStreams_.end()) {\n      upgradedStreams_.erase(curHeader_.stream);\n      // a websocket upgrade was sent on this stream.\n      if (msg->getStatusCode() != 200) {\n        return;\n      }\n      msg->setIngressWebsocketUpgrade();\n    }\n  }\n}\n\nvoid HTTP2Codec::onDecodeError(HPACK::DecodeError decodeError) {\n  decodeInfo_.decodeError = decodeError;\n}\n\nErrorCode HTTP2Codec::parseRFC9218Priority(Cursor& cursor) {\n  VLOG(4) << \"parsing RFC 9218 PRIORITY_UPDATE frame for stream=\"\n          << curHeader_.stream << \" length=\" << curHeader_.length;\n  std::string pri;\n  uint32_t priStream = 0;\n  auto err = http2::parseRFC9218Priority(cursor, curHeader_, priStream, pri);\n  RETURN_IF_ERROR(err);\n  auto httpPri = httpPriorityFromString(pri);\n  auto onPriFunc =\n      static_cast<void (HTTPCodec::Callback::*)(StreamID, const HTTPPriority&)>(\n          &HTTPCodec::Callback::onPriority);\n  deliverCallbackIfAllowed(\n      onPriFunc, \"onPriority\", priStream, httpPri.value_or(HTTPPriority{}));\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseRstStream(Cursor& cursor) {\n  // rst for stream in idle state - protocol error\n  VLOG(4) << \"parsing RST_STREAM frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  upgradedStreams_.erase(curHeader_.stream);\n  ErrorCode statusCode = ErrorCode::NO_ERROR;\n  auto err = http2::parseRstStream(cursor, curHeader_, statusCode);\n  RETURN_IF_ERROR(err);\n  if (statusCode == ErrorCode::PROTOCOL_ERROR) {\n    VLOG(3) << \"RST_STREAM with code=\" << getErrorCodeString(statusCode)\n            << \" for streamID=\" << curHeader_.stream\n            << \" user-agent=\" << userAgent_;\n  }\n  deliverCallbackIfAllowed(\n      &HTTPCodec::Callback::onAbort, \"onAbort\", curHeader_.stream, statusCode);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseSettings(Cursor& cursor) {\n  VLOG(4) << \"parsing SETTINGS frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  std::deque<SettingPair> settings;\n  auto err = http2::parseSettings(cursor, curHeader_, settings);\n  RETURN_IF_ERROR(err);\n  if (curHeader_.flags & http2::ACK) {\n    handleSettingsAck();\n    return ErrorCode::NO_ERROR;\n  }\n  return handleSettings(settings);\n}\n\nvoid HTTP2Codec::handleSettingsAck() {\n  if (pendingTableMaxSize_) {\n    headerCodec_.setDecoderHeaderTableMaxSize(*pendingTableMaxSize_);\n    pendingTableMaxSize_ = folly::none;\n  }\n  if (callback_) {\n    callback_->onSettingsAck();\n  }\n}\n\nErrorCode HTTP2Codec::handleSettings(const std::deque<SettingPair>& settings) {\n  SettingsList settingsList;\n  for (auto& setting : settings) {\n    switch (setting.first) {\n      case SettingsId::HEADER_TABLE_SIZE: {\n        uint32_t tableSize = setting.second;\n        if (setting.second > http2::kMaxHeaderTableSize) {\n          VLOG(2) << \"Limiting table size from \" << tableSize << \" to \"\n                  << http2::kMaxHeaderTableSize;\n          tableSize = http2::kMaxHeaderTableSize;\n        }\n        headerCodec_.setEncoderHeaderTableSize(tableSize);\n      } break;\n      case SettingsId::ENABLE_PUSH:\n        if ((setting.second != 0 && setting.second != 1) ||\n            (setting.second == 1 && isUpstream(transportDirection_))) {\n          goawayErrorMessage_ =\n              folly::to<string>(\"GOAWAY error: ENABLE_PUSH invalid setting=\",\n                                setting.second,\n                                \" for streamID=\",\n                                curHeader_.stream);\n          VLOG(4) << goawayErrorMessage_;\n          return ErrorCode::PROTOCOL_ERROR;\n        }\n        break;\n      case SettingsId::MAX_CONCURRENT_STREAMS:\n        break;\n      case SettingsId::INITIAL_WINDOW_SIZE:\n        if (setting.second > http2::kMaxWindowUpdateSize) {\n          goawayErrorMessage_ = folly::to<string>(\n              \"GOAWAY error: INITIAL_WINDOW_SIZE invalid size=\",\n              setting.second,\n              \" for streamID=\",\n              curHeader_.stream);\n          VLOG(4) << goawayErrorMessage_;\n          return ErrorCode::PROTOCOL_ERROR;\n        }\n        break;\n      case SettingsId::MAX_FRAME_SIZE:\n        if (setting.second < http2::kMaxFramePayloadLengthMin ||\n            setting.second > http2::kMaxFramePayloadLength) {\n          goawayErrorMessage_ =\n              folly::to<string>(\"GOAWAY error: MAX_FRAME_SIZE invalid size=\",\n                                setting.second,\n                                \" for streamID=\",\n                                curHeader_.stream);\n          VLOG(4) << goawayErrorMessage_;\n          return ErrorCode::PROTOCOL_ERROR;\n        }\n        ingressSettings_.setSetting(SettingsId::MAX_FRAME_SIZE, setting.second);\n        break;\n      case SettingsId::MAX_HEADER_LIST_SIZE:\n        break;\n      case SettingsId::ENABLE_CONNECT_PROTOCOL:\n        if (setting.second > 1) {\n          goawayErrorMessage_ = folly::to<string>(\n              \"GOAWAY error: ENABLE_CONNECT_PROTOCOL invalid number=\",\n              setting.second,\n              \" for streamID=\",\n              curHeader_.stream);\n          VLOG(4) << goawayErrorMessage_;\n          return ErrorCode::PROTOCOL_ERROR;\n        }\n        break;\n      case SettingsId::THRIFT_CHANNEL_ID:\n      case SettingsId::THRIFT_CHANNEL_ID_DEPRECATED:\n        break;\n      case SettingsId::SETTINGS_HTTP_CERT_AUTH:\n        break;\n      case SettingsId::WT_MAX_SESSIONS:\n      case SettingsId::_HQ_QPACK_BLOCKED_STREAMS:\n      case SettingsId::_HQ_DATAGRAM:\n      case SettingsId::_HQ_DATAGRAM_DRAFT_8:\n      case SettingsId::_HQ_DATAGRAM_RFC:\n      case SettingsId::ENABLE_WEBTRANSPORT:\n      case SettingsId::H3_WT_MAX_SESSIONS:\n      case SettingsId::WT_INITIAL_MAX_DATA:\n        // These are not handled, fall through to default\n      default:\n        continue; // ignore unknown setting\n    }\n    ingressSettings_.setSetting(setting.first, setting.second);\n    settingsList.push_back(*ingressSettings_.getSetting(setting.first));\n  }\n  if (callback_) {\n    callback_->onSettings(settingsList);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parsePushPromise(Cursor& cursor) {\n  // stream id must be idle - protocol error\n  // assoc-stream-id=closed/unknown - protocol error, unless rst_stream sent\n\n  /*\n   * What does \"must handle\" mean in the following context?  I have to\n   * accept this as a valid pushed resource?\n\n    However, an endpoint that has sent RST_STREAM on the associated\n    stream MUST handle PUSH_PROMISE frames that might have been\n    created before the RST_STREAM frame is received and processed.\n  */\n  if (isDownstream(transportDirection_)) {\n    goawayErrorMessage_ =\n        folly::to<string>(\"Received PUSH_PROMISE on DOWNSTREAM codec\");\n    VLOG(2) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (egressSettings_.getSetting(SettingsId::ENABLE_PUSH, -1) != 1) {\n    goawayErrorMessage_ =\n        folly::to<string>(\"Received PUSH_PROMISE on codec with push disabled\");\n    VLOG(2) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  VLOG(4) << \"parsing PUSH_PROMISE frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  uint32_t promisedStream;\n  std::unique_ptr<IOBuf> headerBlockFragment;\n  auto err = http2::parsePushPromise(\n      cursor, curHeader_, promisedStream, headerBlockFragment);\n  RETURN_IF_ERROR(err);\n  RETURN_IF_ERROR(checkNewStream(promisedStream, false /* trailersAllowed */));\n  err =\n      parseHeadersImpl(cursor, std::move(headerBlockFragment), promisedStream);\n  return err;\n}\n\nErrorCode HTTP2Codec::parsePing(Cursor& cursor) {\n  VLOG(4) << \"parsing PING frame length=\" << curHeader_.length;\n  uint64_t opaqueData = 0;\n  auto err = http2::parsePing(cursor, curHeader_, opaqueData);\n  RETURN_IF_ERROR(err);\n  if (callback_) {\n    if (curHeader_.flags & http2::ACK) {\n      callback_->onPingReply(opaqueData);\n    } else {\n      callback_->onPingRequest(opaqueData);\n    }\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseGoaway(Cursor& cursor) {\n  VLOG(4) << \"parsing GOAWAY frame length=\" << curHeader_.length;\n  uint32_t lastGoodStream = 0;\n  ErrorCode statusCode = ErrorCode::NO_ERROR;\n  std::unique_ptr<IOBuf> debugData;\n\n  auto err = http2::parseGoaway(\n      cursor, curHeader_, lastGoodStream, statusCode, debugData);\n  if (statusCode != ErrorCode::NO_ERROR) {\n    VLOG(3) << \"Goaway error statusCode=\" << getErrorCodeString(statusCode)\n            << \" lastStream=\" << lastGoodStream << \" user-agent=\" << userAgent_\n            << \" debugData=\"\n            << ((debugData) ? debugData->to<std::string>() : empty_string);\n  }\n  RETURN_IF_ERROR(err);\n  if (lastGoodStream < ingressGoawayAck_) {\n    ingressGoawayAck_ = lastGoodStream;\n    // Drain all streams <= lastGoodStream\n    // and abort streams > lastGoodStream\n    if (callback_) {\n      callback_->onGoaway(lastGoodStream, statusCode, std::move(debugData));\n    }\n  } else {\n    LOG(WARNING) << \"Received multiple GOAWAY with increasing ack\";\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseWindowUpdate(Cursor& cursor) {\n  VLOG(4) << \"parsing WINDOW_UPDATE frame for stream=\" << curHeader_.stream\n          << \" length=\" << curHeader_.length;\n  uint32_t delta = 0;\n  auto err = http2::parseWindowUpdate(cursor, curHeader_, delta);\n  RETURN_IF_ERROR(err);\n  if (delta == 0) {\n    VLOG(4) << \"Invalid 0 length delta for stream=\" << curHeader_.stream;\n    if (curHeader_.stream == 0) {\n      goawayErrorMessage_ = folly::to<string>(\n          \"GOAWAY error: invalid/0 length delta for streamID=\",\n          curHeader_.stream);\n      return ErrorCode::PROTOCOL_ERROR;\n    } else {\n      // Parsing a zero delta window update should cause a protocol error\n      // and send a rst stream\n      goawayErrorMessage_ = folly::to<std::string>(\n          \"streamID=\", curHeader_.stream, \" with window update delta=\", delta);\n      VLOG(4) << goawayErrorMessage_;\n      streamError(goawayErrorMessage_, ErrorCode::PROTOCOL_ERROR);\n      // Stream error and protocol error\n      return ErrorCode::PROTOCOL_ERROR;\n    }\n  }\n  // if window exceeds 2^31-1, connection/stream error flow control error\n  // must be checked in session/txn\n  deliverCallbackIfAllowed(&HTTPCodec::Callback::onWindowUpdate,\n                           \"onWindowUpdate\",\n                           curHeader_.stream,\n                           delta);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseCertificateRequest(Cursor& cursor) {\n  VLOG(4) << \"parsing CERTIFICATE_REQUEST frame length=\" << curHeader_.length;\n  uint16_t requestId = 0;\n  std::unique_ptr<IOBuf> authRequest;\n\n  auto err = http2::parseCertificateRequest(\n      cursor, curHeader_, requestId, authRequest);\n  RETURN_IF_ERROR(err);\n  if (callback_) {\n    callback_->onCertificateRequest(requestId, std::move(authRequest));\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::parseCertificate(Cursor& cursor) {\n  VLOG(4) << \"parsing CERTIFICATE frame length=\" << curHeader_.length;\n  uint16_t certId = 0;\n  std::unique_ptr<IOBuf> authData;\n  auto err = http2::parseCertificate(cursor, curHeader_, certId, authData);\n  RETURN_IF_ERROR(err);\n  if (curAuthenticatorBlock_.empty()) {\n    curCertId_ = certId;\n  } else if (certId != curCertId_) {\n    // Received CERTIFICATE frame with different Cert-ID.\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  curAuthenticatorBlock_.append(std::move(authData));\n  if (curAuthenticatorBlock_.chainLength() > http2::kMaxAuthenticatorBufSize) {\n    // Received excessively long authenticator.\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (!(curHeader_.flags & http2::TO_BE_CONTINUED)) {\n    auto authenticator = curAuthenticatorBlock_.move();\n    if (callback_) {\n      callback_->onCertificate(certId, std::move(authenticator));\n    } else {\n      curAuthenticatorBlock_.reset();\n    }\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode HTTP2Codec::checkNewStream(uint32_t streamId, bool trailersAllowed) {\n  bool existingStream = (streamId <= lastStreamID_);\n  if (streamId == 0 || (!trailersAllowed && existingStream)) {\n    goawayErrorMessage_ =\n        folly::to<string>(\"GOAWAY error: received streamID=\",\n                          streamId,\n                          \" as invalid new stream for lastStreamID_=\",\n                          lastStreamID_);\n    VLOG(4) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  parsingDownstreamTrailers_ = trailersAllowed && existingStream;\n  if (parsingDownstreamTrailers_) {\n    VLOG(4) << \"Parsing downstream trailers streamId=\" << streamId;\n  }\n\n  if (sessionClosing_ != ClosingState::CLOSED && !existingStream) {\n    lastStreamID_ = streamId;\n  }\n\n  if (isInitiatedStream(streamId)) {\n    // this stream should be initiated by us, not by peer\n    goawayErrorMessage_ = folly::to<string>(\n        \"GOAWAY error: invalid new stream received with streamID=\", streamId);\n    VLOG(4) << goawayErrorMessage_;\n    return ErrorCode::PROTOCOL_ERROR;\n  } else {\n    return ErrorCode::NO_ERROR;\n  }\n}\n\nsize_t HTTP2Codec::generateConnectionPreface(folly::IOBufQueue& writeBuf) {\n  if (isUpstream(transportDirection_)) {\n    VLOG(4) << \"generating connection preface\";\n    writeBuf.append(http2::kConnectionPreface);\n    return http2::kConnectionPreface.length();\n  }\n  return 0;\n}\n\nbool HTTP2Codec::onIngressUpgradeMessage(const HTTPMessage& msg) {\n  if (!HTTPParallelCodec::onIngressUpgradeMessage(msg)) {\n    return false;\n  }\n  if (msg.getHeaders().getNumberOfValues(http2::kProtocolSettingsHeader) != 1) {\n    VLOG(4) << __func__ << \" with no HTTP2-Settings\";\n    return false;\n  }\n\n  const auto& settingsHeader =\n      msg.getHeaders().getSingleOrEmpty(http2::kProtocolSettingsHeader);\n  if (settingsHeader.empty()) {\n    return true;\n  }\n\n  auto decoded = folly::makeTryWith([&settingsHeader] {\n                   return folly::base64URLDecode(settingsHeader);\n                 }).value_or(std::string());\n\n  // Must be well formed Base64Url and not too large\n  if (decoded.empty() || decoded.length() > http2::kMaxFramePayloadLength) {\n    VLOG(4) << __func__ << \" failed to decode HTTP2-Settings\";\n    return false;\n  }\n  std::unique_ptr<IOBuf> decodedBuf =\n      IOBuf::wrapBuffer(decoded.data(), decoded.length());\n  IOBufQueue settingsQueue{IOBufQueue::cacheChainLength()};\n  settingsQueue.append(std::move(decodedBuf));\n  Cursor c(settingsQueue.front());\n  std::deque<SettingPair> settings;\n  // downcast is ok because of above length check\n  http2::FrameHeader frameHeader{.length =\n                                     (uint32_t)settingsQueue.chainLength(),\n                                 .stream = 0,\n                                 .type = http2::FrameType::SETTINGS,\n                                 .flags = 0,\n                                 .unused = 0};\n  auto err = http2::parseSettings(c, frameHeader, settings);\n  if (err != ErrorCode::NO_ERROR) {\n    VLOG(4) << __func__ << \" bad settings frame\";\n    return false;\n  }\n\n  if (handleSettings(settings) != ErrorCode::NO_ERROR) {\n    VLOG(4) << __func__ << \" handleSettings failed\";\n    return false;\n  }\n\n  return true;\n}\n\nvoid HTTP2Codec::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  generateHeaderImpl(writeBuf,\n                     stream,\n                     msg,\n                     folly::none, /* assocStream */\n                     eom,\n                     size,\n                     extraHeaders);\n}\n\nvoid HTTP2Codec::generatePushPromise(folly::IOBufQueue& writeBuf,\n                                     StreamID stream,\n                                     const HTTPMessage& msg,\n                                     StreamID assocStream,\n                                     bool eom,\n                                     HTTPHeaderSize* size) {\n  generateHeaderImpl(writeBuf,\n                     stream,\n                     msg,\n                     assocStream,\n                     eom,\n                     size,\n                     folly::none /* extraHeaders */);\n}\n\nsize_t HTTP2Codec::splitCompressed(size_t compressed,\n                                   uint32_t remainingFrameSize,\n                                   folly::IOBufQueue& writeBuf,\n                                   folly::IOBufQueue& queue) {\n  CHECK_GT(compressed, 0) << \"compressed block must be at least 1 byte\";\n  auto chunkLen = compressed;\n  if (chunkLen > remainingFrameSize) {\n    // There's more here than fits in one frame.  Put the remainder in queue\n    chunkLen = remainingFrameSize;\n    auto tailSize = compressed - remainingFrameSize;\n    auto head = writeBuf.split(writeBuf.chainLength() - tailSize);\n    queue.append(writeBuf.move());\n    writeBuf.append(std::move(head));\n  }\n  return chunkLen;\n}\n\nvoid HTTP2Codec::generateHeaderImpl(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    const folly::Optional<StreamID>& assocStream,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  HTTPHeaderSize localSize;\n  if (!size) {\n    size = &localSize;\n  }\n  if (assocStream) {\n    VLOG(4) << \"generating PUSH_PROMISE for stream=\" << stream;\n  } else {\n    VLOG(4) << \"generating HEADERS for stream=\" << stream;\n  }\n\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"Suppressing HEADERS/PROMISE for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    if (size) {\n      size->uncompressed = 0;\n      size->compressed = 0;\n    }\n    return;\n  }\n\n  if (msg.isRequest()) {\n    DCHECK(isUpstream(transportDirection_) || assocStream);\n    if (msg.isEgressWebsocketUpgrade()) {\n      upgradedStreams_.insert(stream);\n    }\n  } else {\n    DCHECK(isDownstream(transportDirection_));\n  }\n\n  auto headerSize =\n      http2::calculatePreHeaderBlockSize(assocStream.has_value(), false, false);\n  auto maxFrameSize = maxSendFrameSize();\n  uint32_t remainingFrameSize =\n      maxFrameSize - headerSize + http2::kFrameHeaderSize;\n  auto frameHeader = writeBuf.preallocate(headerSize, kDefaultGrowth);\n  writeBuf.postallocate(headerSize);\n  headerCodec_.encodeHTTP(msg, writeBuf, addDateToResponse_, extraHeaders);\n  *size = headerCodec_.getEncodedSize();\n  CodecUtil::logIfFieldSectionExceedsPeerMax(\n      headerCodec_.getEncodedSize(),\n      ingressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE,\n                                  std::numeric_limits<uint32_t>::max()),\n      CodecUtil::debugString(msg, debugLevel_),\n      msg.getHeaders(),\n      debugLevel_);\n\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  auto chunkLen =\n      splitCompressed(size->compressed, remainingFrameSize, writeBuf, queue);\n  bool endHeaders = queue.chainLength() == 0;\n  if (assocStream) {\n    DCHECK_EQ(transportDirection_, TransportDirection::DOWNSTREAM);\n    DCHECK(!eom);\n    generateHeaderCallbackWrapper(\n        stream,\n        http2::FrameType::PUSH_PROMISE,\n        http2::writePushPromise((uint8_t*)frameHeader.first,\n                                frameHeader.second,\n                                writeBuf,\n                                *assocStream,\n                                stream,\n                                chunkLen,\n                                http2::kNoPadding,\n                                endHeaders));\n  } else {\n    generateHeaderCallbackWrapper(\n        stream,\n        http2::FrameType::HEADERS,\n        http2::writeHeaders((uint8_t*)frameHeader.first,\n                            frameHeader.second,\n                            writeBuf,\n                            chunkLen,\n                            stream,\n                            http2::kNoPadding,\n                            eom,\n                            endHeaders));\n  }\n\n  if (!endHeaders) {\n    generateContinuation(\n        writeBuf, queue, assocStream ? *assocStream : stream, maxFrameSize);\n  }\n}\n\nvoid HTTP2Codec::generateContinuation(folly::IOBufQueue& writeBuf,\n                                      folly::IOBufQueue& queue,\n                                      StreamID stream,\n                                      size_t maxFrameSize) {\n  bool endHeaders = false;\n  while (!endHeaders) {\n    auto chunk = queue.split(std::min(maxFrameSize, queue.chainLength()));\n    endHeaders = (queue.chainLength() == 0);\n    VLOG(4) << \"generating CONTINUATION for stream=\" << stream;\n    generateHeaderCallbackWrapper(\n        stream,\n        http2::FrameType::CONTINUATION,\n        http2::writeContinuation(\n            writeBuf, stream, endHeaders, std::move(chunk)));\n  }\n}\n\nvoid HTTP2Codec::encodeHeaders(folly::IOBufQueue& writeBuf,\n                               const HTTPHeaders& headers,\n                               std::vector<compress::Header>& allHeaders,\n                               HTTPHeaderSize* size) {\n  headerCodec_.encode(allHeaders, writeBuf);\n  if (size) {\n    *size = headerCodec_.getEncodedSize();\n  }\n  CodecUtil::logIfFieldSectionExceedsPeerMax(\n      headerCodec_.getEncodedSize(),\n      ingressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE,\n                                  std::numeric_limits<uint32_t>::max()),\n      std::string(),\n      headers,\n      debugLevel_);\n}\n\nsize_t HTTP2Codec::generateHeaderCallbackWrapper(StreamID stream,\n                                                 http2::FrameType type,\n                                                 size_t length) {\n  if (callback_) {\n    callback_->onGenerateFrameHeader(\n        stream, static_cast<uint8_t>(type), length);\n  }\n  return length;\n}\n\nsize_t HTTP2Codec::generateBody(folly::IOBufQueue& writeBuf,\n                                StreamID stream,\n                                std::unique_ptr<folly::IOBuf> chain,\n                                folly::Optional<uint8_t> padding,\n                                bool eom) {\n  // todo: generate random padding for everything?\n  size_t written = 0;\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"Suppressing DATA for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    return 0;\n  }\n  VLOG(4) << \"generating DATA for stream=\" << stream\n          << \" size=\" << (chain ? chain->computeChainDataLength() : 0);\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  queue.append(std::move(chain));\n  size_t maxFrameSize = maxSendFrameSize();\n  while (queue.chainLength() > maxFrameSize) {\n    auto chunk = queue.split(maxFrameSize);\n    written += generateHeaderCallbackWrapper(\n        stream,\n        http2::FrameType::DATA,\n        http2::writeData(writeBuf,\n                         std::move(chunk),\n                         stream,\n                         padding,\n                         false,\n                         reuseIOBufHeadroomForData_));\n  }\n\n  return written + generateHeaderCallbackWrapper(\n                       stream,\n                       http2::FrameType::DATA,\n                       http2::writeData(writeBuf,\n                                        queue.move(),\n                                        stream,\n                                        padding,\n                                        eom,\n                                        reuseIOBufHeadroomForData_));\n}\n\nsize_t HTTP2Codec::generateChunkHeader(folly::IOBufQueue& /*writeBuf*/,\n                                       StreamID /*stream*/,\n                                       size_t /*length*/) {\n  // HTTP/2 has no chunk headers\n  return 0;\n}\n\nsize_t HTTP2Codec::generateChunkTerminator(folly::IOBufQueue& /*writeBuf*/,\n                                           StreamID /*stream*/) {\n  // HTTP/2 has no chunk terminators\n  return 0;\n}\n\nsize_t HTTP2Codec::generateTrailers(folly::IOBufQueue& writeBuf,\n                                    StreamID stream,\n                                    const HTTPHeaders& trailers) {\n  VLOG(4) << \"generating TRAILERS for stream=\" << stream;\n  std::vector<compress::Header> allHeaders;\n  CodecUtil::appendHeaders(trailers, allHeaders, HTTP_HEADER_NONE);\n  if (allHeaders.size() == 0) {\n    // No point in sending an empty trailer block, convert to EOM.\n    return generateEOM(writeBuf, stream);\n  }\n\n  HTTPHeaderSize size{.compressed = 0, .uncompressed = 0, .compressedBlock = 0};\n  uint8_t headerSize = http2::kFrameHeaderSize;\n  auto remainingFrameSize = maxSendFrameSize();\n  auto frameHeader = writeBuf.preallocate(headerSize, kDefaultGrowth);\n  writeBuf.postallocate(headerSize);\n  encodeHeaders(writeBuf, trailers, allHeaders, &size);\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  auto chunkLen =\n      splitCompressed(size.compressed, remainingFrameSize, writeBuf, queue);\n  bool endHeaders = queue.chainLength() == 0;\n  generateHeaderCallbackWrapper(stream,\n                                http2::FrameType::HEADERS,\n                                http2::writeHeaders((uint8_t*)frameHeader.first,\n                                                    frameHeader.second,\n                                                    writeBuf,\n                                                    chunkLen,\n                                                    stream,\n                                                    http2::kNoPadding,\n                                                    true /*eom*/,\n                                                    endHeaders));\n\n  if (!endHeaders) {\n    generateContinuation(writeBuf, queue, stream, remainingFrameSize);\n  }\n\n  return size.compressed;\n}\n\nsize_t HTTP2Codec::generatePadding(folly::IOBufQueue& writeBuf,\n                                   StreamID stream,\n                                   uint16_t padding) {\n  // https://datatracker.ietf.org/doc/html/rfc7540#section-4.1 states:\n  // \"Implementations MUST ignore and discard any frame that has a type that is\n  // unknown.\"\n  //\n  // Therefore, we can use the unregistered frame type 0xbb, from the reserved\n  // range. We will have plenty of heads up before this frame type is used.\n  // HTTP/2 frame types aren't added very frequently.\n  size_t written = 0;\n  VLOG(4) << \"generating padding=\" << padding << \" for stream=\" << stream;\n  auto maxFrameSizeFull = maxSendFrameSize();\n  auto maxFrameSize = static_cast<uint16_t>(std::min<std::size_t>(\n      maxFrameSizeFull, (std::numeric_limits<uint16_t>::max)()));\n  while (written < padding) {\n    auto cur = std::min(maxFrameSize, padding);\n    written += generateHeaderCallbackWrapper(\n        stream,\n        http2::FrameType::PADDING,\n        http2::writePadding(writeBuf, stream, cur));\n  }\n  return written;\n}\n\nsize_t HTTP2Codec::generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) {\n  VLOG(4) << \"sending EOM for stream=\" << stream;\n  upgradedStreams_.erase(stream);\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"suppressed EOM for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    return 0;\n  }\n  return generateHeaderCallbackWrapper(\n      stream,\n      http2::FrameType::DATA,\n      http2::writeData(writeBuf,\n                       nullptr,\n                       stream,\n                       http2::kNoPadding,\n                       true,\n                       reuseIOBufHeadroomForData_));\n}\n\nsize_t HTTP2Codec::generateRstStream(folly::IOBufQueue& writeBuf,\n                                     StreamID stream,\n                                     ErrorCode statusCode) {\n  VLOG(4) << \"sending RST_STREAM for stream=\" << stream\n          << \" with code=\" << getErrorCodeString(statusCode);\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"suppressed RST_STREAM for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    return 0;\n  }\n  // Suppress any EOM callback for the current frame.\n  if (stream == curHeader_.stream) {\n    curHeader_.flags &= ~http2::END_STREAM;\n    pendingEndStreamHandling_ = false;\n    ingressWebsocketUpgrade_ = false;\n  }\n  upgradedStreams_.erase(stream);\n\n  if (statusCode == ErrorCode::PROTOCOL_ERROR) {\n    VLOG(2) << \"sending RST_STREAM with code=\" << getErrorCodeString(statusCode)\n            << \" for stream=\" << stream << \" user-agent=\" << userAgent_;\n  }\n  return generateHeaderCallbackWrapper(\n      stream,\n      http2::FrameType::RST_STREAM,\n      http2::writeRstStream(writeBuf, stream, statusCode));\n}\n\nsize_t HTTP2Codec::generateGoaway(folly::IOBufQueue& writeBuf,\n                                  StreamID lastStream,\n                                  ErrorCode statusCode,\n                                  std::unique_ptr<folly::IOBuf> debugData) {\n  if (sessionClosing_ == ClosingState::CLOSED) {\n    VLOG(4) << \"Not sending GOAWAY for closed session\";\n    return 0;\n  }\n\n  // If the caller didn't specify a last stream, choose the correct one\n  // If there's an error or this is the final GOAWAY, use last received stream\n  if (lastStream == HTTPCodec::MaxStreamID) {\n    if (statusCode != ErrorCode::NO_ERROR || !isReusable() ||\n        isWaitingToDrain()) {\n      lastStream = getLastIncomingStreamID();\n    } else {\n      lastStream = http2::kMaxStreamID;\n    }\n  }\n  DCHECK_LE(lastStream, egressGoawayAck_) << \"Cannot increase last good stream\";\n  egressGoawayAck_ = lastStream;\n  switch (sessionClosing_) {\n    case ClosingState::OPEN:\n    case ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED:\n      if (lastStream == http2::kMaxStreamID &&\n          statusCode == ErrorCode::NO_ERROR) {\n        sessionClosing_ = ClosingState::FIRST_GOAWAY_SENT;\n      } else {\n        // The user of this codec decided not to do the double goaway\n        // drain, or this is not a graceful shutdown\n        sessionClosing_ = ClosingState::CLOSED;\n      }\n      break;\n    case ClosingState::FIRST_GOAWAY_SENT:\n      sessionClosing_ = ClosingState::CLOSED;\n      break;\n    case ClosingState::CLOSED:\n      LOG(FATAL) << \"unreachable\";\n    default:\n      LOG(FATAL) << \"unhandled state\";\n  }\n\n  VLOG(4) << \"Sending GOAWAY with last acknowledged stream=\" << lastStream\n          << \" with code=\" << getErrorCodeString(statusCode);\n  if (statusCode == ErrorCode::PROTOCOL_ERROR) {\n    VLOG(2) << \"sending GOAWAY with last acknowledged stream=\" << lastStream\n            << \" with code=\" << getErrorCodeString(statusCode)\n            << \" user-agent=\" << userAgent_;\n  }\n\n  return generateHeaderCallbackWrapper(\n      0,\n      http2::FrameType::GOAWAY,\n      http2::writeGoaway(\n          writeBuf, lastStream, statusCode, std::move(debugData)));\n}\n\nsize_t HTTP2Codec::generatePingRequest(folly::IOBufQueue& writeBuf,\n                                       folly::Optional<uint64_t> data) {\n  // should probably let the caller specify when integrating with session\n  // we know HTTPSession sets up events to track ping latency\n  if (!data.has_value()) {\n    data = folly::Random::rand64();\n  }\n  VLOG(4) << \"Generating ping request with data=\" << *data;\n  return generateHeaderCallbackWrapper(\n      0,\n      http2::FrameType::PING,\n      http2::writePing(writeBuf, *data, false /* no ack */));\n}\n\nsize_t HTTP2Codec::generatePingReply(folly::IOBufQueue& writeBuf,\n                                     uint64_t data) {\n  VLOG(4) << \"Generating ping reply with data=\" << data;\n  return generateHeaderCallbackWrapper(\n      0,\n      http2::FrameType::PING,\n      http2::writePing(writeBuf, data, true /* ack */));\n}\n\nsize_t HTTP2Codec::generateSettings(folly::IOBufQueue& writeBuf) {\n  std::deque<SettingPair> settings;\n  for (auto& setting : egressSettings_.getAllSettings()) {\n    switch (setting.id) {\n      case SettingsId::HEADER_TABLE_SIZE:\n        if (pendingTableMaxSize_) {\n          LOG(ERROR) << \"Can't have more than one settings in flight, skipping\";\n          continue;\n        } else {\n          pendingTableMaxSize_ = setting.value;\n        }\n        break;\n      case SettingsId::ENABLE_PUSH:\n        if (isDownstream(transportDirection_)) {\n          // HTTP/2 spec says downstream must not send this flag\n          // HTTP2Codec uses it to determine if push features are enabled\n          continue;\n        } else {\n          CHECK(setting.value == 0 || setting.value == 1);\n        }\n        break;\n      case SettingsId::MAX_CONCURRENT_STREAMS:\n      case SettingsId::INITIAL_WINDOW_SIZE:\n      case SettingsId::MAX_FRAME_SIZE:\n        break;\n      case SettingsId::MAX_HEADER_LIST_SIZE:\n        headerCodec_.setMaxUncompressed(setting.value);\n        break;\n      case SettingsId::ENABLE_CONNECT_PROTOCOL:\n        if (setting.value == 0) {\n          continue;\n        }\n        break;\n      case SettingsId::THRIFT_CHANNEL_ID:\n      case SettingsId::THRIFT_CHANNEL_ID_DEPRECATED:\n        break;\n      case SettingsId::SETTINGS_HTTP_CERT_AUTH:\n      case SettingsId::WT_MAX_SESSIONS:\n      case SettingsId::_HQ_QPACK_BLOCKED_STREAMS:\n      case SettingsId::_HQ_DATAGRAM:\n      case SettingsId::_HQ_DATAGRAM_DRAFT_8:\n      case SettingsId::_HQ_DATAGRAM_RFC:\n      case SettingsId::ENABLE_WEBTRANSPORT:\n      case SettingsId::H3_WT_MAX_SESSIONS:\n      case SettingsId::WT_INITIAL_MAX_DATA:\n        // These are not handled, fall through to default\n      default:\n        LOG(ERROR) << \"ignore unknown settingsId=\"\n                   << std::underlying_type<SettingsId>::type(setting.id)\n                   << \" value=\" << setting.value;\n        continue;\n    }\n\n    settings.emplace_back(setting.id, setting.value);\n  }\n  VLOG(4) << getTransportDirectionString(getTransportDirection())\n          << \" generating \" << (unsigned)settings.size() << \" settings\";\n  return generateHeaderCallbackWrapper(\n      0, http2::FrameType::SETTINGS, http2::writeSettings(writeBuf, settings));\n}\n\nsize_t HTTP2Codec::generateDefaultSettings(folly::IOBufQueue& writeBuf) {\n  static HTTP2Codec defaultCodec{TransportDirection::UPSTREAM};\n  return defaultCodec.generateSettings(writeBuf);\n}\n\nsize_t HTTP2Codec::generateSettingsAck(folly::IOBufQueue& writeBuf) {\n  VLOG(4) << getTransportDirectionString(getTransportDirection())\n          << \" generating settings ack\";\n  return generateHeaderCallbackWrapper(\n      0, http2::FrameType::SETTINGS, http2::writeSettingsAck(writeBuf));\n}\n\nsize_t HTTP2Codec::generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                                        StreamID stream,\n                                        uint32_t delta) {\n  VLOG(4) << \"generating window update for stream=\" << stream << \": Processed \"\n          << delta << \" bytes\";\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"suppressed WINDOW_UPDATE for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    return 0;\n  }\n  return generateHeaderCallbackWrapper(\n      stream,\n      http2::FrameType::WINDOW_UPDATE,\n      http2::writeWindowUpdate(writeBuf, stream, delta));\n}\n\nsize_t HTTP2Codec::generatePriority(folly::IOBufQueue& writeBuf,\n                                    StreamID stream,\n                                    HTTPPriority pri) {\n  VLOG(4) << \"generating priority for stream=\" << stream;\n  if (!isStreamIngressEgressAllowed(stream)) {\n    VLOG(2) << \"suppressed PRIORITY for stream=\" << stream\n            << \" ingressGoawayAck_=\" << ingressGoawayAck_;\n    return 0;\n  }\n  auto httpPri = httpPriorityToString(pri);\n  return generateHeaderCallbackWrapper(\n      stream,\n      http2::FrameType::RFC9218_PRIORITY,\n      http2::writeRFC9218Priority(writeBuf, stream, httpPri));\n}\n\nsize_t HTTP2Codec::generateCertificateRequest(\n    folly::IOBufQueue& writeBuf,\n    uint16_t requestId,\n    std::unique_ptr<folly::IOBuf> certificateRequestData) {\n  VLOG(4) << \"generating CERTIFICATE_REQUEST with Request-ID=\" << requestId;\n  return http2::writeCertificateRequest(\n      writeBuf, requestId, std::move(certificateRequestData));\n}\n\nsize_t HTTP2Codec::generateCertificate(folly::IOBufQueue& writeBuf,\n                                       uint16_t certId,\n                                       std::unique_ptr<folly::IOBuf> certData) {\n  size_t written = 0;\n  VLOG(4) << \"sending CERTIFICATE with Cert-ID=\" << certId << \"for stream=0\";\n  IOBufQueue queue(IOBufQueue::cacheChainLength());\n  queue.append(std::move(certData));\n  // The maximum size of an autenticator fragment, combined with the Cert-ID can\n  // not exceed the maximal allowable size of a sent frame.\n  size_t maxChunkSize = maxSendFrameSize() - sizeof(certId);\n  while (queue.chainLength() > maxChunkSize) {\n    auto chunk = queue.splitAtMost(maxChunkSize);\n    written +=\n        http2::writeCertificate(writeBuf, certId, std::move(chunk), true);\n  }\n  return written +\n         http2::writeCertificate(writeBuf, certId, queue.move(), false);\n}\n\nbool HTTP2Codec::checkConnectionError(ErrorCode err, const folly::IOBuf* buf) {\n  if (err != ErrorCode::NO_ERROR) {\n    std::string errorDescription =\n        goawayErrorMessage_.empty() ? \"Connection error\" : goawayErrorMessage_;\n    LOG(ERROR) << \"Connection error \" << getErrorCodeString(err) << \" \"\n               << errorDescription << \" with ingress=\";\n    VLOG(3) << IOBufPrinter::printHexFolly(buf, true);\n    if (callback_) {\n      HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                       errorDescription);\n      ex.setCodecStatusCode(err);\n      callback_->onError(0, ex, false);\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid HTTP2Codec::streamError(const std::string& msg,\n                             ErrorCode code,\n                             bool newTxn,\n                             folly::Optional<HTTPCodec::StreamID> streamId,\n                             std::unique_ptr<HTTPMessage> partialMessage) {\n  HTTPException error(HTTPException::Direction::INGRESS_AND_EGRESS, msg);\n  error.setCodecStatusCode(code);\n  if (partialMessage) {\n    error.setPartialMsg(std::move(partialMessage));\n  }\n  deliverCallbackIfAllowed(&HTTPCodec::Callback::onError,\n                           \"onError\",\n                           streamId ? *streamId : curHeader_.stream,\n                           error,\n                           newTxn);\n}\n\nbool HTTP2Codec::parsingHeaders() const {\n  return (curHeader_.type == http2::FrameType::HEADERS ||\n          (curHeader_.type == http2::FrameType::CONTINUATION &&\n           headerBlockFrameType_ == http2::FrameType::HEADERS));\n}\n\nbool HTTP2Codec::parsingTrailers() const {\n  // HEADERS frame is used for request/response headers and trailers.\n  // Per spec, specific role of HEADERS frame is determined by it's postion\n  // within the stream. We don't keep full stream state in this codec,\n  // thus using heuristics to distinguish between headers/trailers.\n  // For DOWNSTREAM case, request headers HEADERS frame would be creating\n  // new stream, thus HEADERS on existing stream ID are considered trailers\n  // (see checkNewStream).\n  // For UPSTREAM case, response headers are required to have status code,\n  // thus if no status code we consider that trailers.\n  if (parsingHeaders()) {\n    if (isDownstream(transportDirection_)) {\n      return parsingDownstreamTrailers_;\n    } else {\n      // We *might* be parsing trailers even if we couldn't decode the block.\n      // We probably aren't, because trailers are rare, but if we do,\n      // we might miscategorize as headers and give a second onMessageBegin.\n      return decodeInfo_.parsingError.empty() && !decodeInfo_.hasStatus();\n    }\n  }\n  return false;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Codec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPParallelCodec.h>\n#include <proxygen/lib/http/codec/HTTPRequestVerifier.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/codec/HeaderDecodeInfo.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n\nnamespace proxygen {\n\n/**\n * An implementation of the framing layer for HTTP/2. Instances of this\n * class must not be used from multiple threads concurrently.\n */\nclass HTTP2Codec\n    : public HTTPParallelCodec\n    , HPACK::StreamingCallback {\n public:\n  void onHeader(const HPACKHeaderName& name,\n                const folly::fbstring& value) override;\n  void onHeadersComplete(HTTPHeaderSize decodedSize, bool acknowledge) override;\n  void onDecodeError(HPACK::DecodeError decodeError) override;\n\n  explicit HTTP2Codec(TransportDirection direction);\n  ~HTTP2Codec() override;\n\n  // HTTPCodec API\n  CodecProtocol getProtocol() const override {\n    return CodecProtocol::HTTP_2;\n  }\n\n  const std::string& getUserAgent() const override {\n    return userAgent_;\n  }\n\n  size_t onIngress(const folly::IOBuf& buf) noexcept override;\n  bool onIngressUpgradeMessage(const HTTPMessage& msg) override;\n  size_t generateConnectionPreface(folly::IOBufQueue& writeBuf) override;\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override;\n  void generateContinuation(folly::IOBufQueue& writeBuf,\n                            folly::IOBufQueue& queue,\n                            StreamID stream,\n                            size_t maxFrameSize);\n  void generatePushPromise(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           const HTTPMessage& msg,\n                           StreamID assocStream,\n                           bool eom = false,\n                           HTTPHeaderSize* size = nullptr) override;\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n  size_t generateChunkHeader(folly::IOBufQueue& writeBuf,\n                             StreamID stream,\n                             size_t length) override;\n  size_t generateChunkTerminator(folly::IOBufQueue& writeBuf,\n                                 StreamID stream) override;\n  size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                          StreamID stream,\n                          const HTTPHeaders& trailers) override;\n  size_t generatePadding(folly::IOBufQueue& writeBuf,\n                         StreamID stream,\n                         uint16_t padding) override;\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) override;\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           ErrorCode statusCode) override;\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream = HTTPCodec::MaxStreamID,\n      ErrorCode statusCode = ErrorCode::NO_ERROR,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n  size_t generatePingRequest(\n      folly::IOBufQueue& writeBuf,\n      folly::Optional<uint64_t> data = folly::none) override;\n  size_t generatePingReply(folly::IOBufQueue& writeBuf, uint64_t data) override;\n  size_t generateSettings(folly::IOBufQueue& writeBuf) override;\n  size_t generateSettingsAck(folly::IOBufQueue& writeBuf) override;\n  size_t generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                              StreamID stream,\n                              uint32_t delta) override;\n  size_t generatePriority(folly::IOBufQueue& /* writeBuf */,\n                          StreamID /* stream */,\n                          HTTPPriority /* priority */) override;\n\n  size_t generateCertificateRequest(\n      folly::IOBufQueue& writeBuf,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> certificateRequestData) override;\n  size_t generateCertificate(folly::IOBufQueue& writeBuf,\n                             uint16_t certId,\n                             std::unique_ptr<folly::IOBuf> certData) override;\n  const HTTPSettings* getIngressSettings() const override {\n    return &ingressSettings_;\n  }\n  HTTPSettings* getEgressSettings() override {\n    return &egressSettings_;\n  }\n  uint32_t getDefaultWindowSize() const override {\n    return http2::kInitialWindow;\n  }\n  bool supportsPushTransactions() const override {\n    return (transportDirection_ == TransportDirection::DOWNSTREAM &&\n            ingressSettings_.getSetting(SettingsId::ENABLE_PUSH, 1)) ||\n           (transportDirection_ == TransportDirection::UPSTREAM &&\n            egressSettings_.getSetting(SettingsId::ENABLE_PUSH, 1));\n  }\n  bool peerHasWebsockets() const {\n    return ingressSettings_.getSetting(SettingsId::ENABLE_CONNECT_PROTOCOL);\n  }\n  bool supportsExTransactions() const override {\n    return false;\n  }\n  void setHeaderCodecStats(HeaderCodec::Stats* hcStats) override {\n    headerCodec_.setStats(hcStats);\n  }\n\n  bool isRequest(StreamID id) const {\n    return ((transportDirection_ == TransportDirection::DOWNSTREAM &&\n             (id & 0x1) == 1) ||\n            (transportDirection_ == TransportDirection::UPSTREAM &&\n             (id & 0x1) == 0));\n  }\n\n  CompressionInfo getCompressionInfo() const override {\n    return headerCodec_.getCompressionInfo();\n  }\n\n  // HTTP2Codec specific API\n\n  static size_t generateDefaultSettings(folly::IOBufQueue& writeBuf);\n\n#ifndef NDEBUG\n  uint64_t getReceivedFrameCount() const {\n    return receivedFrameCount_;\n  }\n#endif\n\n  // Whether turn on the optimization to reuse IOBuf headroom when write DATA\n  // frame. For other frames, it's always ON.\n  void setReuseIOBufHeadroomForData(bool enabled) {\n    reuseIOBufHeadroomForData_ = enabled;\n  }\n\n  void setHeaderIndexingStrategy(const HeaderIndexingStrategy* indexingStrat) {\n    headerCodec_.setHeaderIndexingStrategy(indexingStrat);\n  }\n  const HeaderIndexingStrategy* getHeaderIndexingStrategy() const {\n    return headerCodec_.getHeaderIndexingStrategy();\n  }\n\n  void setAddDateHeaderToResponse(bool addDateHeader) {\n    addDateToResponse_ = addDateHeader;\n  }\n\n  void setValidateHeaders(bool validate) {\n    validateHeaders_ = validate;\n  }\n\n  void setStrictValidation(bool strict) {\n    strictValidation_ = strict;\n  }\n\n  void setDebugLevel(uint64_t debugLevel) {\n    debugLevel_ = debugLevel;\n  }\n\n private:\n  size_t splitCompressed(size_t compressed,\n                         uint32_t remainingFrameSize,\n                         folly::IOBufQueue& writeBuf,\n                         folly::IOBufQueue& queue);\n\n  void generateHeaderImpl(folly::IOBufQueue& writeBuf,\n                          StreamID stream,\n                          const HTTPMessage& msg,\n                          const folly::Optional<StreamID>& assocStream,\n                          bool eom,\n                          HTTPHeaderSize* size,\n                          const folly::Optional<HTTPHeaders>& extraHeaders);\n  void encodeHeaders(folly::IOBufQueue& writeBuf,\n                     const HTTPHeaders& headers,\n                     std::vector<compress::Header>& allHeaders,\n                     HTTPHeaderSize* size);\n\n  size_t generateHeaderCallbackWrapper(StreamID stream,\n                                       http2::FrameType type,\n                                       size_t length);\n\n  ErrorCode parseFrame(folly::io::Cursor& cursor);\n  ErrorCode parseAllData(folly::io::Cursor& cursor);\n  ErrorCode parseDataFrameData(folly::io::Cursor& cursor,\n                               size_t bufLen,\n                               size_t& parsed);\n  ErrorCode parseHeaders(folly::io::Cursor& cursor);\n  ErrorCode parseExHeaders(folly::io::Cursor& cursor);\n  ErrorCode parseRFC9218Priority(folly::io::Cursor& cursor);\n  ErrorCode parseRstStream(folly::io::Cursor& cursor);\n  ErrorCode parseSettings(folly::io::Cursor& cursor);\n  ErrorCode parsePushPromise(folly::io::Cursor& cursor);\n  ErrorCode parsePing(folly::io::Cursor& cursor);\n  ErrorCode parseGoaway(folly::io::Cursor& cursor);\n  ErrorCode parseContinuation(folly::io::Cursor& cursor);\n  ErrorCode parseWindowUpdate(folly::io::Cursor& cursor);\n  ErrorCode parseCertificateRequest(folly::io::Cursor& cursor);\n  ErrorCode parseCertificate(folly::io::Cursor& cursor);\n  ErrorCode parseHeadersImpl(folly::io::Cursor& cursor,\n                             std::unique_ptr<folly::IOBuf> headerBuf,\n                             const folly::Optional<uint32_t>& promisedStream);\n\n  struct DeferredParseError {\n    ErrorCode errorCode{ErrorCode::NO_ERROR};\n    bool connectionError{false};\n    std::string errorMessage;\n    mutable std::unique_ptr<HTTPMessage> partialMessage;\n\n    DeferredParseError(ErrorCode ec,\n                       bool conn,\n                       std::string msg,\n                       std::unique_ptr<HTTPMessage> partialMsg = nullptr)\n        : errorCode(ec),\n          connectionError(conn),\n          errorMessage(std::move(msg)),\n          partialMessage(std::move(partialMsg)) {\n    }\n\n    DeferredParseError() = default;\n    DeferredParseError(DeferredParseError&& goner) = default;\n    DeferredParseError& operator=(DeferredParseError&& goner) = default;\n    ~DeferredParseError() = default;\n    DeferredParseError& operator=(const DeferredParseError&) = delete;\n    DeferredParseError(const DeferredParseError& other)\n        : errorCode(other.errorCode),\n          connectionError(other.connectionError),\n\n          errorMessage(other.errorMessage),\n          partialMessage(other.partialMessage ? std::make_unique<HTTPMessage>(\n                                                    *other.partialMessage)\n                                              : nullptr) {\n    }\n  };\n\n  folly::Expected<std::unique_ptr<HTTPMessage>, DeferredParseError>\n  parseHeadersDecodeFrames();\n  void deliverDeferredParseError(const DeferredParseError& parseError);\n\n  folly::Optional<ErrorCode> parseHeadersCheckConcurrentStreams();\n\n  ErrorCode handleEndStream();\n  ErrorCode checkNewStream(uint32_t stream, bool trailersAllowed);\n  bool checkConnectionError(ErrorCode, const folly::IOBuf* buf);\n  ErrorCode handleSettings(const std::deque<SettingPair>& settings);\n  void handleSettingsAck();\n  size_t maxSendFrameSize() const {\n    return (uint32_t)ingressSettings_.getSetting(\n        SettingsId::MAX_FRAME_SIZE, http2::kMaxFramePayloadLengthMin);\n  }\n  uint32_t maxRecvFrameSize() const {\n    return (uint32_t)egressSettings_.getSetting(\n        SettingsId::MAX_FRAME_SIZE, http2::kMaxFramePayloadLengthMin);\n  }\n  void streamError(const std::string& msg,\n                   ErrorCode error,\n                   bool newTxn = false,\n                   folly::Optional<HTTPCodec::StreamID> streamId = folly::none,\n                   std::unique_ptr<HTTPMessage> partialMsg = nullptr);\n  bool parsingHeaders() const;\n  bool parsingTrailers() const;\n\n  HPACKCodec headerCodec_;\n\n  // Current frame state\n  http2::FrameHeader curHeader_;\n  StreamID expectedContinuationStream_{0};\n  static constexpr uint32_t kMaxContinuationFramesPerHeaderBlock{100};\n  uint32_t continuationFramesLeftInHeaderBlock_{\n      kMaxContinuationFramesPerHeaderBlock};\n  // Used for parsing PUSH_PROMISE+CONTINUATION\n  folly::Optional<StreamID> promisedStream_;\n  bool parsingReq_{false};\n  bool pendingEndStreamHandling_{false};\n  bool ingressWebsocketUpgrade_{false};\n\n  std::unordered_set<StreamID> upgradedStreams_;\n\n  uint16_t curCertId_{0};\n  folly::IOBufQueue curAuthenticatorBlock_{\n      folly::IOBufQueue::cacheChainLength()};\n\n  folly::IOBufQueue curHeaderBlock_{folly::IOBufQueue::cacheChainLength()};\n  HTTPSettings ingressSettings_{\n      {SettingsId::HEADER_TABLE_SIZE, 4096},\n      {SettingsId::ENABLE_PUSH, 1},\n      {SettingsId::MAX_FRAME_SIZE, 16384},\n  };\n  HTTPSettings egressSettings_{\n      {SettingsId::HEADER_TABLE_SIZE, 4096},\n      {SettingsId::ENABLE_PUSH, 0},\n      {SettingsId::MAX_FRAME_SIZE, 16384},\n      {SettingsId::MAX_HEADER_LIST_SIZE, 1 << 17},\n  };\n#ifndef NDEBUG\n  uint64_t receivedFrameCount_{0};\n#endif\n  enum class FrameState : uint8_t {\n    UPSTREAM_CONNECTION_PREFACE = 0,\n    EXPECT_FIRST_SETTINGS = 1,\n    FRAME_HEADER = 2,\n    FRAME_DATA = 3,\n    DATA_FRAME_DATA = 4,\n  };\n  FrameState frameState_ : 3;\n  std::string userAgent_;\n\n  size_t pendingDataFrameBytes_{0};\n  size_t pendingDataFramePaddingBytes_{0};\n\n  HeaderDecodeInfo decodeInfo_;\n  folly::Optional<uint32_t> pendingTableMaxSize_;\n  bool reuseIOBufHeadroomForData_{true};\n\n  // True if last parsed HEADERS frame was trailers.\n  // Reset only when HEADERS frame is parsed, thus\n  // remains unchanged and used during CONTINUATION frame\n  // parsing as well.\n  // Applies only to DOWNSTREAM, for UPSTREAM we use\n  // diffrent heuristic - lack of status code.\n  bool parsingDownstreamTrailers_{false};\n  bool addDateToResponse_{true};\n  bool validateHeaders_{true};\n  // Default false for now to match existing behavior\n  bool strictValidation_{false};\n  uint8_t debugLevel_{0};\n\n  // CONTINUATION frame can follow either HEADERS or PUSH_PROMISE frames.\n  // Keeps frame type of initiating frame of header block.\n  http2::FrameType headerBlockFrameType_{http2::FrameType::DATA};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Constants.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n\nnamespace proxygen::http2 {\n\nconst uint32_t kFrameHeaderSize = 9;\n\nconst uint32_t kFrameHeadersBaseMaxSize = kFramePrioritySize + 1;\nconst uint32_t kFramePrioritySize = 5;\nconst uint32_t kFrameStreamIDSize = 4;\nconst uint32_t kFrameRstStreamSize = 4;\nconst uint32_t kFramePushPromiseSize = 4;\nconst uint32_t kFramePingSize = 8;\nconst uint32_t kFrameGoawaySize = 8;\nconst uint32_t kFrameWindowUpdateSize = 4;\nconst uint32_t kFrameCertificateRequestSizeBase = 2;\nconst uint32_t kFrameCertificateSizeBase = 2;\n\nconst uint32_t kFrameAltSvcSizeBase = 8;\n\nconst uint32_t kMaxFramePayloadLengthMin = (1u << 14);\nconst uint32_t kMaxFramePayloadLength = (1u << 24) - 1;\nconst uint32_t kMaxStreamID = (1u << 31) - 1;\nconst uint32_t kInitialWindow = (1u << 16) - 1;\nconst uint32_t kMaxWindowUpdateSize = (1u << 31) - 1;\nconst uint32_t kMaxHeaderTableSize = (1u << 16);\n\nconst uint32_t kMaxAuthenticatorBufSize = 0x20000; // 128k\n\nconst std::string kConnectionPreface(\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\");\n\nconst std::string kProtocolString(\"h2\");\nconst std::string kProtocolDraftString(\"h2-14\");\nconst std::string kProtocolExperimentalString(\"h2-fb\");\nconst std::string kProtocolCleartextString(\"h2c\");\nconst std::string kProtocolSettingsHeader(\"HTTP2-Settings\");\n\n} // namespace proxygen::http2\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Constants.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <string>\n\nnamespace proxygen::http2 {\n\nextern const uint32_t kFrameHeaderSize;\nextern const uint32_t kFrameHeadersBaseMaxSize;\nextern const uint32_t kFramePrioritySize;\nextern const uint32_t kFrameStreamIDSize;\nextern const uint32_t kFrameRstStreamSize;\nextern const uint32_t kFramePushPromiseSize;\nextern const uint32_t kFramePingSize;\nextern const uint32_t kFrameGoawaySize;\nextern const uint32_t kFrameWindowUpdateSize;\nextern const uint32_t kFrameCertificateRequestSizeBase;\nextern const uint32_t kFrameCertificateSizeBase;\n\n// These constants indicate the size of the required fields in the frame\nextern const uint32_t kFrameAltSvcSizeBase;\n\nextern const uint32_t kMaxFramePayloadLengthMin;\nextern const uint32_t kMaxFramePayloadLength;\nextern const uint32_t kMaxStreamID;\nextern const uint32_t kInitialWindow;\nextern const uint32_t kMaxWindowUpdateSize;\nextern const uint32_t kMaxHeaderTableSize;\n\n// The maximum size of the data buffer caching an authenticator.\n// For secondary authentication in HTTP/2.\nextern const uint32_t kMaxAuthenticatorBufSize;\n\nextern const uint32_t kMaxHeaderTableSize;\n\nextern const std::string kConnectionPreface;\n\nextern const std::string kProtocolString;\nextern const std::string kProtocolDraftString;\nextern const std::string kProtocolExperimentalString;\nextern const std::string kProtocolCleartextString;\nextern const std::string kProtocolSettingsHeader;\n\n} // namespace proxygen::http2\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Framer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n\n#include <folly/tracing/ScopedTraceSection.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\nusing namespace folly::io;\nusing namespace folly;\n\nnamespace proxygen::http2 {\n\nconst uint8_t kMinExperimentalFrameType = 0xf0;\nconst Padding kNoPadding = folly::none;\nconst PriorityUpdate DefaultPriority{\n    .streamDependency = 0, .exclusive = false, .weight = 15};\n\nnamespace {\n\nconst uint32_t kLengthMask = 0x00ffffff;\nconst uint32_t kUint31Mask = 0x7fffffff;\n\nstatic const uint64_t kZeroPad[32] = {0};\n\nstatic const bool kStrictPadding = true;\n\nstatic_assert(sizeof(kZeroPad) == 256, \"bad zero padding\");\n\nvoid writePadding(IOBufQueue& queue, folly::Optional<uint8_t> size) {\n  if (size && *size > 0) {\n    auto out = queue.preallocate(*size, *size);\n    memset(out.first, 0, *size);\n    queue.postallocate(*size);\n  }\n}\n\n/**\n * Generate just the common frame header. This includes the padding length\n * bits that sometimes come right after the frame header. Returns the\n * length field written to the common frame header.\n */\n\nsize_t computeLengthAndType(uint32_t length,\n                            FrameType type,\n                            uint8_t& flags,\n                            uint32_t stream,\n                            folly::Optional<uint8_t> padding,\n                            size_t& headerSize) {\n  // the acceptable length is now conditional based on state :(\n  DCHECK_EQ(0, ~kLengthMask & length);\n  DCHECK_EQ(0, ~kUint31Mask & stream);\n\n  // Add or remove padding flags\n  if (padding) {\n    flags |= PADDED;\n    DCHECK(FrameType::HEADERS == type || FrameType::DATA == type ||\n           FrameType::PUSH_PROMISE == type);\n    length += *padding + 1;\n    headerSize += 1;\n  } else {\n    flags &= ~PADDED;\n  }\n\n  DCHECK_EQ(0, ~kLengthMask & length);\n  DCHECK_EQ(true, isValidFrameType(type));\n  return ((kLengthMask & length) << 8) | static_cast<uint8_t>(type);\n}\n\n// There are two versions of writeFrameHeader.  One takes an IOBufQueue and\n// uses a QueueAppender.  The other takes a raw buffer.\n\nsize_t writeFrameHeader(IOBufQueue& queue,\n                        uint32_t length,\n                        FrameType type,\n                        uint8_t flags,\n                        uint32_t stream,\n                        folly::Optional<uint8_t> padding,\n                        std::unique_ptr<IOBuf> payload,\n                        bool reuseIOBufHeadroom = true) noexcept {\n  size_t headerSize = kFrameHeaderSize;\n  uint32_t lengthAndType =\n      computeLengthAndType(length, type, flags, stream, padding, headerSize);\n\n  uint64_t payloadLength = 0;\n  if (reuseIOBufHeadroom && payload && !payload->isSharedOne() &&\n      payload->headroom() >= headerSize && queue.tailroom() < headerSize) {\n    // Use the headroom in payload for the frame header.\n    // Make it appear that the payload IOBuf is empty and retreat so\n    // appender can access the headroom\n    payloadLength = payload->length();\n    payload->trimEnd(payloadLength);\n    payload->retreat(headerSize);\n    auto tail = payload->pop();\n    queue.append(std::move(payload));\n    payload = std::move(tail);\n  }\n  QueueAppender appender(&queue, headerSize);\n  appender.writeBE<uint32_t>(lengthAndType);\n  appender.writeBE<uint8_t>(flags);\n  appender.writeBE<uint32_t>(kUint31Mask & stream);\n\n  if (padding) {\n    appender.writeBE<uint8_t>(*padding);\n  }\n  if (payloadLength) {\n    queue.postallocate(payloadLength);\n  }\n  queue.append(std::move(payload));\n\n  return length;\n}\n\nsize_t writeFrameHeader(uint8_t* buf,\n                        size_t bufLen,\n                        uint32_t length,\n                        FrameType type,\n                        uint8_t flags,\n                        uint32_t stream,\n                        folly::Optional<uint8_t> padding) noexcept {\n  size_t headerSize = kFrameHeaderSize;\n  uint32_t lengthAndType =\n      computeLengthAndType(length, type, flags, stream, padding, headerSize);\n\n  CHECK_GE(bufLen, headerSize);\n  lengthAndType = htonl(lengthAndType);\n  memcpy(buf, &lengthAndType, sizeof(lengthAndType));\n  buf += sizeof(lengthAndType);\n  *buf = flags;\n  buf++;\n  stream &= kUint31Mask;\n  stream = htonl(stream);\n  memcpy(buf, &stream, sizeof(stream));\n  buf += sizeof(stream);\n  bufLen -= kFrameHeaderSize;\n\n  if (padding) {\n    CHECK_GE(bufLen, 1);\n    *buf = *padding;\n    buf++;\n    bufLen--;\n  }\n  return length;\n}\n\nuint32_t parseUint31(Cursor& cursor) {\n  // MUST ignore the 1 bit before the stream-id\n  return kUint31Mask & cursor.readBE<uint32_t>();\n}\n\nErrorCode parseErrorCode(Cursor& cursor, ErrorCode& outCode) {\n  auto code = cursor.readBE<uint32_t>();\n  if (code >= static_cast<uint32_t>(ErrorCode::MAX)) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  outCode = ErrorCode(code);\n  return ErrorCode::NO_ERROR;\n}\n\nvoid skipPriority(Cursor& cursor) {\n  cursor.skip(kFramePrioritySize);\n}\n\n/**\n * Given the flags for a frame and the cursor pointing at the top of the\n * frame-specific section (after the common header), return the number of\n * bytes to skip at the end of the frame. Caller must ensure there is at\n * least 1 bytes in the cursor.\n *\n * @param cursor The cursor to pull data from\n * @param header The frame header for the frame being parsed.\n * @param padding The out parameter that will return the number of padding\n *                bytes at the end of the frame.\n * @return Nothing if success. The connection error code if failure.\n */\nErrorCode parsePadding(Cursor& cursor,\n                       const FrameHeader& header,\n                       uint8_t& padding,\n                       uint32_t& lefttoparse) noexcept {\n  DCHECK(header.type == FrameType::DATA || header.type == FrameType::HEADERS ||\n         header.type == FrameType::PUSH_PROMISE);\n  lefttoparse = header.length;\n  if (frameHasPadding(header)) {\n    if (lefttoparse < 1) {\n      return ErrorCode::FRAME_SIZE_ERROR;\n    }\n    lefttoparse -= 1;\n    padding = cursor.readBE<uint8_t>();\n  } else {\n    padding = 0;\n  }\n\n  if (lefttoparse < padding) {\n    return ErrorCode::PROTOCOL_ERROR;\n  } else {\n    lefttoparse -= padding;\n    return ErrorCode::NO_ERROR;\n  }\n}\n\nErrorCode skipPadding(Cursor& cursor, uint8_t length, bool verify) {\n  if (verify) {\n    while (length > 0) {\n      auto cur = cursor.peek();\n      uint8_t toCmp = std::min<size_t>(cur.size(), length);\n      if (memcmp(cur.data(), kZeroPad, toCmp) != 0) {\n        return ErrorCode::PROTOCOL_ERROR;\n      }\n      cursor.skip(toCmp);\n      length -= toCmp;\n    }\n  } else {\n    cursor.skip(length);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\n} // anonymous namespace\n\nbool isValidFrameType(FrameType type) {\n  auto val = static_cast<uint8_t>(type);\n  if (val < kMinExperimentalFrameType) {\n    return val <= static_cast<uint8_t>(FrameType::ALTSVC) ||\n           type == FrameType::PADDING || type == FrameType::RFC9218_PRIORITY;\n  } else {\n    switch (type) {\n      case FrameType::CERTIFICATE_REQUEST:\n      case FrameType::CERTIFICATE:\n        return true;\n      // The following enums fall through to default (return false):\n      case FrameType::CERTIFICATE_NEEDED:\n      case FrameType::USE_CERTIFICATE:\n      case FrameType::DATA:\n      case FrameType::HEADERS:\n      case FrameType::PRIORITY:\n      case FrameType::RST_STREAM:\n      case FrameType::SETTINGS:\n      case FrameType::PUSH_PROMISE:\n      case FrameType::PING:\n      case FrameType::GOAWAY:\n      case FrameType::WINDOW_UPDATE:\n      case FrameType::CONTINUATION:\n      case FrameType::ALTSVC:\n      case FrameType::RFC9218_PRIORITY:\n      case FrameType::PADDING:\n      default:\n        return false;\n    }\n  }\n}\n\nbool frameAffectsCompression(FrameType t) {\n  return t == FrameType::HEADERS || t == FrameType::PUSH_PROMISE ||\n         t == FrameType::CONTINUATION;\n}\n\nbool frameHasPadding(const FrameHeader& header) {\n  return header.flags & PADDED;\n}\n\n//// Parsing ////\n\nErrorCode parseFrameHeader(Cursor& cursor, FrameHeader& header) noexcept {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTP2Framer - parseFrameHeader\");\n  DCHECK_LE(kFrameHeaderSize, cursor.totalLength());\n\n  // MUST ignore the 2 bits before the length\n  auto lengthAndType = cursor.readBE<uint32_t>();\n  header.length = kLengthMask & (lengthAndType >> 8);\n  uint8_t type = lengthAndType & 0xff;\n  header.type = FrameType(type);\n  header.flags = cursor.readBE<uint8_t>();\n  header.stream = parseUint31(cursor);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseData(Cursor& cursor,\n                    const FrameHeader& header,\n                    std::unique_ptr<IOBuf>& outBuf,\n                    uint16_t& outPadding) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream == 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n\n  uint8_t padding;\n  uint32_t lefttoparse;\n  const auto err = parsePadding(cursor, header, padding, lefttoparse);\n  RETURN_IF_ERROR(err);\n  // outPadding is the total number of flow-controlled pad bytes, which\n  // includes the length byte, if present.\n  outPadding = padding + ((frameHasPadding(header)) ? 1 : 0);\n  cursor.clone(outBuf, lefttoparse);\n  return skipPadding(cursor, padding, kStrictPadding);\n}\n\nErrorCode parseDataBegin(Cursor& cursor,\n                         const FrameHeader& header,\n                         size_t& /*parsed*/,\n                         uint16_t& outPadding) noexcept {\n  uint8_t padding;\n  uint32_t lefttoparse;\n  const auto err = http2::parsePadding(cursor, header, padding, lefttoparse);\n  RETURN_IF_ERROR(err);\n  // outPadding is the total number of flow-controlled pad bytes, which\n  // includes the length byte, if present.\n  outPadding = padding + ((frameHasPadding(header)) ? 1 : 0);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseDataEnd(Cursor& cursor,\n                       const size_t bufLen,\n                       const size_t pendingDataFramePaddingBytes,\n                       size_t& toSkip) noexcept {\n  toSkip = std::min(pendingDataFramePaddingBytes, bufLen);\n  return skipPadding(cursor, toSkip, kStrictPadding);\n}\n\nErrorCode parseHeaders(Cursor& cursor,\n                       const FrameHeader& header,\n                       std::unique_ptr<IOBuf>& outBuf) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream == 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  uint8_t padding;\n  uint32_t lefttoparse;\n  auto err = parsePadding(cursor, header, padding, lefttoparse);\n  RETURN_IF_ERROR(err);\n  if (header.flags & PRIORITY) {\n    if (lefttoparse < kFramePrioritySize) {\n      return ErrorCode::FRAME_SIZE_ERROR;\n    }\n    skipPriority(cursor);\n    lefttoparse -= kFramePrioritySize;\n  }\n  cursor.clone(outBuf, lefttoparse);\n  return skipPadding(cursor, padding, kStrictPadding);\n}\n\nErrorCode parseRFC9218Priority(Cursor& cursor,\n                               const FrameHeader& header,\n                               uint32_t& priStream,\n                               std::string& outPriority) {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (header.length <= sizeof(priStream)) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  priStream = parseUint31(cursor);\n  auto priLen = header.length - sizeof(priStream);\n  outPriority.resize(priLen);\n  cursor.pull((void*)outPriority.data(), priLen);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseRstStream(Cursor& cursor,\n                         const FrameHeader& header,\n                         ErrorCode& outCode) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length != kFrameRstStreamSize) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  if (header.stream == 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  return parseErrorCode(cursor, outCode);\n}\n\nErrorCode parseSettings(Cursor& cursor,\n                        const FrameHeader& header,\n                        std::deque<SettingPair>& settings) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (header.flags & ACK) {\n    if (header.length != 0) {\n      return ErrorCode::FRAME_SIZE_ERROR;\n    }\n    return ErrorCode::NO_ERROR;\n  }\n\n  if (header.length % 6 != 0) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  for (auto length = header.length; length > 0; length -= 6) {\n    auto id = cursor.readBE<uint16_t>();\n    auto val = cursor.readBE<uint32_t>();\n    settings.emplace_back(SettingsId(id), val);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parsePushPromise(Cursor& cursor,\n                           const FrameHeader& header,\n                           uint32_t& outPromisedStream,\n                           std::unique_ptr<IOBuf>& outBuf) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream == 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n\n  uint8_t padding;\n  uint32_t lefttoparse;\n  auto err = parsePadding(cursor, header, padding, lefttoparse);\n  RETURN_IF_ERROR(err);\n  if (lefttoparse < kFramePushPromiseSize) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  lefttoparse -= kFramePushPromiseSize;\n  outPromisedStream = parseUint31(cursor);\n  if (outPromisedStream == 0 || outPromisedStream & 0x1) {\n    // client MUST reserve an even stream id greater than 0\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  if (lefttoparse < padding) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  cursor.clone(outBuf, lefttoparse);\n  return skipPadding(cursor, padding, kStrictPadding);\n}\n\nErrorCode parsePing(Cursor& cursor,\n                    const FrameHeader& header,\n                    uint64_t& outOpaqueData) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n\n  if (header.length != kFramePingSize) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n\n  cursor.pull(&outOpaqueData, sizeof(outOpaqueData));\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseGoaway(Cursor& cursor,\n                      const FrameHeader& header,\n                      uint32_t& outLastStreamID,\n                      ErrorCode& outCode,\n                      std::unique_ptr<IOBuf>& outDebugData) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length < kFrameGoawaySize) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  outLastStreamID = parseUint31(cursor);\n  auto err = parseErrorCode(cursor, outCode);\n  RETURN_IF_ERROR(err);\n  auto length = header.length;\n  length -= kFrameGoawaySize;\n  if (length > 0) {\n    cursor.clone(outDebugData, length);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseWindowUpdate(Cursor& cursor,\n                            const FrameHeader& header,\n                            uint32_t& outAmount) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length != kFrameWindowUpdateSize) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  outAmount = parseUint31(cursor);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseContinuation(Cursor& cursor,\n                            const FrameHeader& header,\n                            std::unique_ptr<IOBuf>& outBuf) noexcept {\n  DCHECK(header.type == FrameType::CONTINUATION);\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.stream == 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  cursor.clone(outBuf, header.length);\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseAltSvc(Cursor& cursor,\n                      const FrameHeader& header,\n                      uint32_t& outMaxAge,\n                      uint32_t& outPort,\n                      std::string& outProtocol,\n                      std::string& outHost,\n                      std::string& outOrigin) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length < kFrameAltSvcSizeBase) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  std::unique_ptr<IOBuf> tmpBuf;\n\n  outMaxAge = cursor.readBE<uint32_t>();\n  outPort = cursor.readBE<uint16_t>();\n  const auto protoLen = cursor.readBE<uint8_t>();\n  if (header.length < kFrameAltSvcSizeBase + protoLen) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  outProtocol = cursor.readFixedString(protoLen);\n  const auto hostLen = cursor.readBE<uint8_t>();\n  if (header.length < kFrameAltSvcSizeBase + protoLen + hostLen) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  outHost = cursor.readFixedString(hostLen);\n  const auto originLen =\n      (header.length - kFrameAltSvcSizeBase - protoLen - hostLen);\n  outOrigin = cursor.readFixedString(originLen);\n\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseCertificateRequest(\n    folly::io::Cursor& cursor,\n    const FrameHeader& header,\n    uint16_t& outRequestId,\n    std::unique_ptr<folly::IOBuf>& outAuthRequest) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length < kFrameCertificateRequestSizeBase) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  outRequestId = cursor.readBE<uint16_t>();\n  auto length = header.length;\n  length -= kFrameCertificateRequestSizeBase;\n  if (length > 0) {\n    cursor.clone(outAuthRequest, length);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\nErrorCode parseCertificate(\n    folly::io::Cursor& cursor,\n    const FrameHeader& header,\n    uint16_t& outCertId,\n    std::unique_ptr<folly::IOBuf>& outAuthenticator) noexcept {\n  DCHECK_LE(header.length, cursor.totalLength());\n  if (header.length < kFrameCertificateSizeBase) {\n    return ErrorCode::FRAME_SIZE_ERROR;\n  }\n  if (header.stream != 0) {\n    return ErrorCode::PROTOCOL_ERROR;\n  }\n  outCertId = cursor.readBE<uint16_t>();\n  auto length = header.length;\n  length -= kFrameCertificateSizeBase;\n  if (length > 0) {\n    cursor.clone(outAuthenticator, length);\n  }\n  return ErrorCode::NO_ERROR;\n}\n\n//// Egress ////\n\nsize_t writeData(IOBufQueue& queue,\n                 std::unique_ptr<IOBuf> data,\n                 uint32_t stream,\n                 folly::Optional<uint8_t> padding,\n                 bool endStream,\n                 bool reuseIOBufHeadroom) noexcept {\n  DCHECK_NE(0, stream);\n  uint8_t flags = 0;\n  if (endStream) {\n    flags |= END_STREAM;\n  }\n  const uint64_t dataLen = data ? data->computeChainDataLength() : 0;\n  // Caller must not exceed peer setting for MAX_FRAME_SIZE\n  // TODO: look into using headroom from data to hold the frame header\n  const auto frameLen = writeFrameHeader(queue,\n                                         dataLen,\n                                         FrameType::DATA,\n                                         flags,\n                                         stream,\n                                         padding,\n                                         std::move(data),\n                                         reuseIOBufHeadroom);\n  writePadding(queue, padding);\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writePadding(IOBufQueue& queue,\n                    uint32_t stream,\n                    uint16_t padding) noexcept {\n  uint16_t padBodyLen =\n      padding > kFrameHeaderSize ? padding - kFrameHeaderSize : 0;\n\n  // Set up the padding body\n  auto body = CodecUtil::zeroedBuffer(padBodyLen);\n\n  // Write the frame\n  const auto frameLen = writeFrameHeader(queue,\n                                         padBodyLen,\n                                         FrameType::PADDING,\n                                         0,\n                                         stream,\n                                         folly::none,\n                                         std::move(body));\n  return kFrameHeaderSize + frameLen;\n}\n\nuint8_t calculatePreHeaderBlockSize(bool hasAssocStream,\n                                    bool hasPriority,\n                                    bool hasPadding) {\n  uint8_t headerSize =\n      http2::kFrameHeaderSize + ((hasAssocStream) ? sizeof(uint32_t) : 0);\n  if (hasPriority && !hasAssocStream) {\n    headerSize += http2::kFramePrioritySize;\n  }\n  if (hasPadding) {\n    headerSize += 1;\n  }\n  return headerSize;\n}\n\nsize_t writeHeaders(uint8_t* header,\n                    size_t headerLen,\n                    IOBufQueue& queue,\n                    size_t headersLen,\n                    uint32_t stream,\n                    folly::Optional<uint8_t> padding,\n                    bool endStream,\n                    bool endHeaders) noexcept {\n  DCHECK_NE(0, stream);\n  uint32_t flags = 0;\n  if (endStream) {\n    flags |= END_STREAM;\n  }\n  if (endHeaders) {\n    flags |= END_HEADERS;\n  }\n  // padding flags handled directly inside writeFrameHeader()\n  const auto frameLen = writeFrameHeader(header,\n                                         headerLen,\n                                         headersLen,\n                                         FrameType::HEADERS,\n                                         flags,\n                                         stream,\n                                         padding);\n  writePadding(queue, padding);\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeRFC9218Priority(IOBufQueue& queue,\n                            uint32_t stream,\n                            std::string& priority) {\n  CHECK_NE(0, stream);\n  QueueAppender appender(&queue, sizeof(stream) + priority.size());\n  appender.writeBE<uint32_t>(stream);\n  appender.pushAtMost((const uint8_t*)(priority.data()), priority.size());\n  auto payloadLen = queue.chainLength();\n\n  const auto frameLen = writeFrameHeader(queue,\n                                         payloadLen,\n                                         FrameType::RFC9218_PRIORITY,\n                                         0,\n                                         0,\n                                         kNoPadding,\n                                         queue.move());\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeRstStream(IOBufQueue& queue,\n                      uint32_t stream,\n                      ErrorCode errorCode) noexcept {\n  DCHECK_NE(0, stream);\n  const auto frameLen = writeFrameHeader(queue,\n                                         kFrameRstStreamSize,\n                                         FrameType::RST_STREAM,\n                                         0,\n                                         stream,\n                                         kNoPadding,\n                                         nullptr);\n  QueueAppender appender(&queue, frameLen);\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(errorCode));\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeSettings(IOBufQueue& queue,\n                     const std::deque<SettingPair>& settings) {\n  const auto settingsSize = settings.size() * 6;\n  const auto frameLen = writeFrameHeader(\n      queue, settingsSize, FrameType::SETTINGS, 0, 0, kNoPadding, nullptr);\n  QueueAppender appender(&queue, settingsSize);\n  for (const auto& setting : settings) {\n    DCHECK_LE(static_cast<uint32_t>(setting.first),\n              std::numeric_limits<uint16_t>::max());\n    appender.writeBE<uint16_t>(static_cast<uint16_t>(setting.first));\n    appender.writeBE<uint32_t>(setting.second);\n  }\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeSettingsAck(IOBufQueue& queue) {\n  writeFrameHeader(queue, 0, FrameType::SETTINGS, ACK, 0, kNoPadding, nullptr);\n  return kFrameHeaderSize;\n}\n\nsize_t writePushPromise(uint8_t* header,\n                        size_t headerLen,\n                        IOBufQueue& queue,\n                        uint32_t associatedStream,\n                        uint32_t promisedStream,\n                        size_t headersLen,\n                        folly::Optional<uint8_t> padding,\n                        bool endHeaders) noexcept {\n  DCHECK_NE(0, promisedStream);\n  DCHECK_NE(0, associatedStream);\n  DCHECK_EQ(0, 0x1 & promisedStream);\n  DCHECK_EQ(1, 0x1 & associatedStream);\n  DCHECK_EQ(0, ~kUint31Mask & promisedStream);\n\n  const auto frameLen = writeFrameHeader(header,\n                                         headerLen,\n                                         headersLen + kFramePushPromiseSize,\n                                         FrameType::PUSH_PROMISE,\n                                         endHeaders ? END_HEADERS : 0,\n                                         associatedStream,\n                                         padding);\n  promisedStream = htonl(promisedStream);\n  uint8_t* psPtr = header + kFrameHeaderSize;\n  if (padding) {\n    psPtr++;\n  }\n  memcpy(psPtr, &promisedStream, sizeof(promisedStream));\n  writePadding(queue, padding);\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writePing(IOBufQueue& queue, uint64_t opaqueData, bool ack) noexcept {\n  const auto frameLen = writeFrameHeader(queue,\n                                         kFramePingSize,\n                                         FrameType::PING,\n                                         ack ? ACK : 0,\n                                         0,\n                                         kNoPadding,\n                                         nullptr);\n  queue.append(&opaqueData, sizeof(opaqueData));\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeGoaway(IOBufQueue& queue,\n                   uint32_t lastStreamID,\n                   ErrorCode errorCode,\n                   std::unique_ptr<IOBuf> debugData) noexcept {\n  uint32_t debugLen = debugData ? debugData->computeChainDataLength() : 0;\n  DCHECK_EQ(0, ~kLengthMask & debugLen);\n  const auto frameLen = writeFrameHeader(queue,\n                                         kFrameGoawaySize + debugLen,\n                                         FrameType::GOAWAY,\n                                         0,\n                                         0,\n                                         kNoPadding,\n                                         nullptr);\n  QueueAppender appender(&queue, frameLen);\n  appender.writeBE<uint32_t>(lastStreamID);\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(errorCode));\n  queue.append(std::move(debugData));\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeWindowUpdate(IOBufQueue& queue,\n                         uint32_t stream,\n                         uint32_t amount) noexcept {\n  const auto frameLen = writeFrameHeader(queue,\n                                         kFrameWindowUpdateSize,\n                                         FrameType::WINDOW_UPDATE,\n                                         0,\n                                         stream,\n                                         kNoPadding,\n                                         nullptr);\n  DCHECK_EQ(0, ~kUint31Mask & amount);\n  DCHECK_LT(0, amount);\n  QueueAppender appender(&queue, kFrameWindowUpdateSize);\n  appender.writeBE<uint32_t>(amount);\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeContinuation(IOBufQueue& queue,\n                         uint32_t stream,\n                         bool endHeaders,\n                         std::unique_ptr<IOBuf> headers) noexcept {\n  DCHECK_NE(0, stream);\n  const auto dataLen = headers->computeChainDataLength();\n  const auto frameLen = writeFrameHeader(queue,\n                                         dataLen,\n                                         FrameType::CONTINUATION,\n                                         endHeaders ? END_HEADERS : 0,\n                                         stream,\n                                         kNoPadding,\n                                         std::move(headers));\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeAltSvc(IOBufQueue& queue,\n                   uint32_t stream,\n                   uint32_t maxAge,\n                   uint16_t port,\n                   StringPiece protocol,\n                   StringPiece host,\n                   StringPiece origin) noexcept {\n  const auto protoLen = protocol.size();\n  const auto hostLen = host.size();\n  const auto originLen = origin.size();\n  const auto frameLen = protoLen + hostLen + originLen + kFrameAltSvcSizeBase;\n\n  writeFrameHeader(\n      queue, frameLen, FrameType::ALTSVC, 0, stream, kNoPadding, nullptr);\n  QueueAppender appender(&queue, frameLen);\n  appender.writeBE<uint32_t>(maxAge);\n  appender.writeBE<uint16_t>(port);\n  appender.writeBE<uint8_t>(static_cast<uint8_t>(protoLen));\n  appender.push(reinterpret_cast<const uint8_t*>(protocol.data()), protoLen);\n  appender.writeBE<uint8_t>(static_cast<uint8_t>(hostLen));\n  appender.push(reinterpret_cast<const uint8_t*>(host.data()), hostLen);\n  appender.push(reinterpret_cast<const uint8_t*>(origin.data()), originLen);\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeCertificateRequest(folly::IOBufQueue& writeBuf,\n                               uint16_t requestId,\n                               std::unique_ptr<folly::IOBuf> authRequest) {\n  const auto dataLen = authRequest ? kFrameCertificateRequestSizeBase +\n                                         authRequest->computeChainDataLength()\n                                   : kFrameCertificateRequestSizeBase;\n  // The CERTIFICATE_REQUEST frame must be sent on stream 0.\n  const auto frameLen = writeFrameHeader(writeBuf,\n                                         dataLen,\n                                         FrameType::CERTIFICATE_REQUEST,\n                                         0,\n                                         0,\n                                         kNoPadding,\n                                         nullptr);\n  QueueAppender appender(&writeBuf, frameLen);\n  appender.writeBE<uint16_t>(requestId);\n  writeBuf.append(std::move(authRequest));\n  return kFrameHeaderSize + frameLen;\n}\n\nsize_t writeCertificate(folly::IOBufQueue& writeBuf,\n                        uint16_t certId,\n                        std::unique_ptr<folly::IOBuf> authenticator,\n                        bool toBeContinued) {\n  uint8_t flags = 0;\n  if (toBeContinued) {\n    flags |= TO_BE_CONTINUED;\n  }\n  const auto dataLen =\n      authenticator\n          ? kFrameCertificateSizeBase + authenticator->computeChainDataLength()\n          : kFrameCertificateSizeBase;\n  // The CERTIFICATE_REQUEST frame must be sent on stream 0.\n  const auto frameLen = writeFrameHeader(\n      writeBuf, dataLen, FrameType::CERTIFICATE, flags, 0, kNoPadding, nullptr);\n  QueueAppender appender(&writeBuf, frameLen);\n  appender.writeBE<uint16_t>(certId);\n  writeBuf.append(std::move(authenticator));\n  return kFrameHeaderSize + frameLen;\n}\n\nconst char* getFrameTypeString(FrameType type) {\n  switch (type) {\n    case FrameType::DATA:\n      return \"DATA\";\n    case FrameType::HEADERS:\n      return \"HEADERS\";\n    case FrameType::PRIORITY:\n      return \"PRIORITY\";\n    case FrameType::RST_STREAM:\n      return \"RST_STREAM\";\n    case FrameType::SETTINGS:\n      return \"SETTINGS\";\n    case FrameType::PUSH_PROMISE:\n      return \"PUSH_PROMISE\";\n    case FrameType::PING:\n      return \"PING\";\n    case FrameType::GOAWAY:\n      return \"GOAWAY\";\n    case FrameType::WINDOW_UPDATE:\n      return \"WINDOW_UPDATE\";\n    case FrameType::CONTINUATION:\n      return \"CONTINUATION\";\n    case FrameType::ALTSVC:\n      return \"ALTSVC\";\n    case FrameType::RFC9218_PRIORITY:\n      return \"RFC9218_PRIORITY\";\n    case FrameType::PADDING:\n      return \"PADDING\";\n    case FrameType::CERTIFICATE_REQUEST:\n      return \"CERTIFICATE_REQUEST\";\n    case FrameType::CERTIFICATE:\n      return \"CERTIFICATE\";\n    case FrameType::CERTIFICATE_NEEDED:\n      return \"CERTIFICATE_NEEDED\";\n    case FrameType::USE_CERTIFICATE:\n      return \"USE_CERTIFICATE\";\n    default:\n      // can happen when type was cast from uint8_t\n      return \"Unknown\";\n  }\n  LOG(FATAL) << \"Unreachable\";\n}\n} // namespace proxygen::http2\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTP2Framer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <deque>\n#include <folly/Optional.h>\n#include <folly/Range.h>\n#include <folly/io/Cursor.h>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/SettingsId.h>\n#include <proxygen/lib/utils/Export.h>\n#include <string.h>\n\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n\nnamespace proxygen::http2 {\n\n//////// Constants ////////\n\nextern const uint8_t kMinExperimentalFrameType;\nusing Padding = folly::Optional<uint8_t>;\nextern const Padding kNoPadding;\n\n//////// Types ////////\n\nenum class FrameType : uint8_t {\n  DATA = 0,\n  HEADERS = 1,\n  PRIORITY = 2,\n  RST_STREAM = 3,\n  SETTINGS = 4,\n  PUSH_PROMISE = 5,\n  PING = 6,\n  GOAWAY = 7,\n  WINDOW_UPDATE = 8,\n  CONTINUATION = 9,\n  ALTSVC = 10, // not in current draft so frame type has not been assigned\n  RFC9218_PRIORITY = 16,\n  PADDING = 0xbb, // not in current draft, nor likely to ever be used, so will\n                  // be ignored\n\n  // For secondary certificate authentication in HTTP/2 as specified in the\n  // draft-ietf-httpbis-http2-secondary-certs-02.\n  CERTIFICATE_REQUEST = 0xf0,\n  CERTIFICATE = 0xf1,\n  CERTIFICATE_NEEDED = 0xf2,\n  USE_CERTIFICATE = 0xf3,\n};\n\nenum Flags {\n  ACK = 0x1,\n  END_STREAM = 0x1,\n  END_HEADERS = 0x4,\n  PADDED = 0x8,\n  PRIORITY = 0x20,\n  // experimental flag for EX stream only\n  UNIDIRECTIONAL = 0x40,\n\n  // for secondary certificate authentication frames\n  UNSOLICITED = 0x1,\n  TO_BE_CONTINUED = 0x1,\n};\n\nstruct FrameHeader {\n  uint32_t length; // only 24 valid bits\n  uint32_t stream;\n  FrameType type;\n  uint8_t flags;\n  uint16_t unused;\n};\n\nstatic_assert(sizeof(FrameHeader) == 12, \"The maths are not working\");\n\nstruct PriorityUpdate {\n  // StreamID is 64bit integer to accommodate both HTTP2 and HTTP3 streams so\n  // just validate the id for HTTP2 before encoding it on the wire\n  HTTPCodec::StreamID streamDependency;\n  bool exclusive;\n  uint8_t weight;\n};\n\n//////// Bonus Constant ////////\n\nFB_EXPORT extern const PriorityUpdate DefaultPriority;\n\n//////// Functions ////////\n\nbool isValidFrameType(FrameType t);\n\nbool frameAffectsCompression(FrameType t);\n\n/**\n * This function returns true if the padding bit is set in the header\n *\n * @param header The frame header.\n * @return true if the padding bit is set, false otherwise.\n */\nbool frameHasPadding(const FrameHeader& header);\n\n//// Parsing ////\n\n/**\n * This function parses the common HTTP/2 frame header. This function\n * pulls kFrameHeaderSize bytes from the cursor, so the caller must check\n * that that amount is available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header struct to populate.\n * @return Nothing if success. The connection error code if failure.\n */\nErrorCode parseFrameHeader(folly::io::Cursor& cursor,\n                           FrameHeader& header) noexcept;\n\n/**\n * This function parses the section of the DATA frame after the common\n * frame header. It discards any padding and returns the body data in\n * outBuf. It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor  The cursor to pull data from.\n * @param header  The frame header for the frame being parsed.\n * @param outBuf  The buf to fill with body data.\n * @param padding The number of padding bytes in this data frame\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseData(folly::io::Cursor& cursor,\n                    const FrameHeader& header,\n                    std::unique_ptr<folly::IOBuf>& outBuf,\n                    uint16_t& padding) noexcept;\n\nErrorCode parseDataBegin(folly::io::Cursor& cursor,\n                         const FrameHeader& header,\n                         size_t& parsed,\n                         uint16_t& outPadding) noexcept;\n\nErrorCode parseDataEnd(folly::io::Cursor& cursor,\n                       const size_t bufLen,\n                       const size_t pendingDataFramePaddingBytes,\n                       size_t& toSkip) noexcept;\n\n/**\n * This function parses the section of the HEADERS frame after the common\n * frame header. It discards any padding and returns the header data in\n * outBuf. It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outBuf The buf to fill with header data.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseHeaders(folly::io::Cursor& cursor,\n                       const FrameHeader& header,\n                       std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the PRIORITY frame after the common\n * frame header. It pulls header.length bytes from the cursor, so it is the\n * caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param priStream The stream to be prioritized.\n * @param outPriority On success, filled with the priority information\n *                    from this frame.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseRFC9218Priority(folly::io::Cursor& cursor,\n                               const FrameHeader& header,\n                               uint32_t& priStream,\n                               std::string& outPriority);\n\n/**\n * This function parses the section of the RST_STREAM frame after the\n * common frame header. It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outCode The error code received in the frame.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseRstStream(folly::io::Cursor& cursor,\n                         const FrameHeader& header,\n                         ErrorCode& outCode) noexcept;\n\n/**\n * This function parses the section of the SETTINGS frame after the\n * common frame header. It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param settings The settings received in this frame.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseSettings(folly::io::Cursor& cursor,\n                        const FrameHeader& header,\n                        std::deque<SettingPair>& settings) noexcept;\n\n/**\n * This function parses the section of the PUSH_PROMISE frame after the\n * common frame header. It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outPromisedStream The id of the stream promised by the remote.\n * @param outBuf The buffer to fill with header data.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parsePushPromise(folly::io::Cursor& cursor,\n                           const FrameHeader& header,\n                           uint32_t& outPromisedStream,\n                           std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the PING frame after the common\n * frame header. It pulls header.length bytes from the cursor, so it is\n * the caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outData The opaque data from the ping frame\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parsePing(folly::io::Cursor& cursor,\n                    const FrameHeader& header,\n                    uint64_t& outData) noexcept;\n\n/**\n * This function parses the section of the GOAWAY frame after the common\n * frame header.  It pulls header.length bytes from the cursor, so\n * it is the caller's responsibility to ensure there is enough data\n * available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outLastStreamID The last stream id accepted by the remote.\n * @param outCode The error code received in the frame.\n * @param outDebugData Additional debug-data in the frame, if any\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseGoaway(folly::io::Cursor& cursor,\n                      const FrameHeader& header,\n                      uint32_t& outLastStreamID,\n                      ErrorCode& outCode,\n                      std::unique_ptr<folly::IOBuf>& outDebugData) noexcept;\n\n/**\n * This function parses the section of the WINDOW_UPDATE frame after the\n * common frame header. The caller must ensure there is header.length\n * bytes available in the cursor.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outAmount The amount to increment the stream's window by.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseWindowUpdate(folly::io::Cursor& cursor,\n                            const FrameHeader& header,\n                            uint32_t& outAmount) noexcept;\n\n/**\n * This function parses the section of the CONTINUATION frame after the\n * common frame header. The caller must ensure there is header.length\n * bytes available in the cursor.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outBuf The buffer to fill with header data.\n * @param outAmount The amount to increment the stream's window by.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseContinuation(folly::io::Cursor& cursor,\n                            const FrameHeader& header,\n                            std::unique_ptr<folly::IOBuf>& outBuf) noexcept;\n\n/**\n * This function parses the section of the ALTSVC frame after the\n * common frame header. The caller must ensure there is header.length\n * bytes available in the cursor.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outMaxAge The max age field.\n * @param outPort The port the alternative service is on.\n * @param outProtocol The alternative service protocol string.\n * @param outHost The alternative service host name.\n * @param outOrigin The origin the alternative service is applicable to.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a GOAWAY frame if failure.\n */\nErrorCode parseAltSvc(folly::io::Cursor& cursor,\n                      const FrameHeader& header,\n                      uint32_t& outMaxAge,\n                      uint32_t& outPort,\n                      std::string& outProtocol,\n                      std::string& outHost,\n                      std::string& outOrigin) noexcept;\n\n/**\n * This function parses the section of the CERTIFICATE_REQUEST frame after the\n * common frame header.  It pulls header.length bytes from the cursor, so it is\n * the caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outRequestId The Request-ID identifying this certificate request.\n * @param outAuthRequest Authenticator request in the frame, if any.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a CERTIFICATE_REQUEST frame if failure.\n */\nErrorCode parseCertificateRequest(\n    folly::io::Cursor& cursor,\n    const FrameHeader& header,\n    uint16_t& outRequestId,\n    std::unique_ptr<folly::IOBuf>& outAuthRequest) noexcept;\n\n/**\n * This function parses the section of the CERTIFICATE frame after the\n * common frame header.  It pulls header.length bytes from the cursor, so it is\n * the caller's responsibility to ensure there is enough data available.\n *\n * @param cursor The cursor to pull data from.\n * @param header The frame header for the frame being parsed.\n * @param outCertId The Cert-ID identifying the frame.\n * @param outAuthenticator Authenticator fragment in the frame, if any.\n * @return NO_ERROR for successful parse. The connection error code to\n *         return in a CERTIFICATE frame if failure.\n */\nErrorCode parseCertificate(\n    folly::io::Cursor& cursor,\n    const FrameHeader& header,\n    uint16_t& outCertId,\n    std::unique_ptr<folly::IOBuf>& outAuthenticator) noexcept;\n\n//// Egress ////\n\n/**\n * Generate an entire DATA frame, including the common frame header.\n * The combined length of the data buffer, the padding, and the padding\n * length MUST NOT exceed 2^14 - 1, which is kMaxFramePayloadLength.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param data The body data to write out, can be nullptr for 0 length\n * @param stream The stream identifier of the DATA frame.\n * @param padding If not kNoPadding, adds 1 byte pad len and @padding pad bytes\n * @param endStream True iff this frame ends the stream.\n * @param reuseIOBufHeadroom If HTTP2Framer should reuse headroom in data if\n *                           headroom is enough for frame header\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeData(folly::IOBufQueue& writeBuf,\n                 std::unique_ptr<folly::IOBuf> data,\n                 uint32_t stream,\n                 folly::Optional<uint8_t> padding,\n                 bool endStream,\n                 bool reuseIOBufHeadroom) noexcept;\n\n/**\n * Generate an entire HEADERS frame, including the common frame header. The\n * combined length of the data buffer and the padding and priority fields MUST\n * NOT exceed 2^14 - 1, which is kMaxFramePayloadLength.\n *\n * @param headerBuf Buffer that will contain the frame header and other fields\n *                  before the header block.  Must be sized correctly and\n *                  in the queue.  Call calculatePreHeaderBlockSize/preallocate/\n *                  postallocate.\n * @param headeBufLen Length of headerBuf\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param headersLen The length of the encoded headers data (already in writBuf)\n * @param stream The stream identifier of the HEADERS frame.\n * @param padding If not kNoPadding, adds 1 byte pad len and @padding pad bytes\n * @param endStream True iff this frame ends the stream.\n * @param endHeaders True iff no CONTINUATION frames will follow this frame.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeHeaders(uint8_t* headerBuf,\n                    size_t headerBufLen,\n                    folly::IOBufQueue& queue,\n                    size_t headersLen,\n                    uint32_t stream,\n                    folly::Optional<uint8_t> padding,\n                    bool endStream,\n                    bool endHeaders) noexcept;\n\n/**\n * Generate an entire PRIORITY frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param stream The stream identifier of the DATA frame.\n * @param priority The priority depedency information to update the stream with.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeRFC9218Priority(folly::IOBufQueue& writeBuf,\n                            uint32_t stream,\n                            std::string& priority);\n\n/**\n * Generate an entire RST_STREAM frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param stream The identifier of the stream to reset.\n * @param errorCode The error code returned in the frame.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeRstStream(folly::IOBufQueue& writeBuf,\n                      uint32_t stream,\n                      ErrorCode errorCode) noexcept;\n\n/**\n * Generate an entire SETTINGS frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param settings The settings to send\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeSettings(folly::IOBufQueue& writeBuf,\n                     const std::deque<SettingPair>& settings);\n\n/**\n * Writes an entire empty SETTINGS frame, including the common frame\n * header. No settings can be transmitted with this frame.\n */\nsize_t writeSettingsAck(folly::IOBufQueue& writeBuf);\n\n/**\n * Writes an entire PUSH_PROMISE frame, including the common frame\n * header.\n *\n * @param headerBuf Buffer that will contain the frame header and other fields\n *                  before the header block.  Must be sized correctly and\n *                  in the queue.  Call calculatePreHeaderBlockSize/preallocate/\n *                  postallocate.\n * @param headeBufLen Length of headerBuf\n * @param queue The output queue to write to. It may grow or add\n *              underlying buffers inside this function.\n * @param associatedStream The identifier of the stream the promised\n *                         stream is associated with.\n * @param promisedStream The identifier of the promised stream.\n * @param headersLen The length of the encoded headers (already in queue).\n * @param padding If not kNoPadding, adds 1 byte pad len and @padding pad bytes\n * @param endHeaders True iff no CONTINUATION frames will follow this frame.\n * @return The number of bytes written to writeBuf/\n */\nsize_t writePushPromise(uint8_t* headerBuf,\n                        size_t headerBufLen,\n                        folly::IOBufQueue& queue,\n                        uint32_t associatedStream,\n                        uint32_t promisedStream,\n                        size_t headersLen,\n                        folly::Optional<uint8_t> padding,\n                        bool endHeaders) noexcept;\n\n/**\n * Generate an entire PING frame, including the common frame header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param data The opaque data to include.\n * @param ack True iff this is a ping response.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writePing(folly::IOBufQueue& writeBuf, uint64_t data, bool ack) noexcept;\n\n/**\n * Generate an entire GOAWAY frame, including the common frame\n * header. We do not implement the optional opaque data.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param lastStreamID The identifier of the last stream accepted.\n * @param errorCode The error code returned in the frame.\n * @param debugData Optional debug information to add to the frame\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeGoaway(folly::IOBufQueue& writeBuf,\n                   uint32_t lastStreamID,\n                   ErrorCode errorCode,\n                   std::unique_ptr<folly::IOBuf> debugData = nullptr) noexcept;\n\n/**\n * Generate an entire WINDOW_UPDATE frame, including the common frame\n * header. |amount| MUST be between 1 to 2^31 - 1 inclusive\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param stream The stream to send a WINDOW_UPDATE on\n * @param amount The number of bytes to AK\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeWindowUpdate(folly::IOBufQueue& writeBuf,\n                         uint32_t stream,\n                         uint32_t amount) noexcept;\n\n/**\n * Generate an entire CONTINUATION frame, including the common frame\n * header. The combined length of the data buffer and the padding MUST NOT\n * exceed 2^14 - 3, which is kMaxFramePayloadLength minus the two bytes to\n * encode the length of the padding.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param stream The stream identifier of the DATA frame.\n * @param endHeaders True iff more CONTINUATION frames will follow.\n * @param headers The encoded headers data to write out.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeContinuation(folly::IOBufQueue& queue,\n                         uint32_t stream,\n                         bool endHeaders,\n                         std::unique_ptr<folly::IOBuf> headers) noexcept;\n/**\n * Generate an entire ALTSVC frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param stream The stream to do Alt-Svc on. May be zero.\n * @param maxAge The max age field.\n * @param port The port the alternative service is on.\n * @param protocol The alternative service protocol string.\n * @param host The alternative service host name.\n * @param origin The origin the alternative service is applicable to.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeAltSvc(folly::IOBufQueue& writeBuf,\n                   uint32_t stream,\n                   uint32_t maxAge,\n                   uint16_t port,\n                   folly::StringPiece protocol,\n                   folly::StringPiece host,\n                   folly::StringPiece origin) noexcept;\n\n/**\n * Generate an entire CERTIFICATE_REQUEST frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param requestId The opaque Request-ID of this used to correlate subsequent\n *                  certificate-related frames with this request.\n * @param authRequest The encoded authenticator request.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeCertificateRequest(folly::IOBufQueue& writeBuf,\n                               uint16_t requestId,\n                               std::unique_ptr<folly::IOBuf> authRequest);\n\n/**\n * Generate an entire CERTIFICATE frame, including the common frame\n * header.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param certId The opaque Cert-ID of this frame which is used to correlate\n * subsequent certificate-related frames with this certificate.\n * @param authenticator The encoded authenticator fragment.\n * @param toBeContinued Indicates whether there is additional authenticator\n * fragment.\n * @return The number of bytes written to writeBuf.\n */\nsize_t writeCertificate(folly::IOBufQueue& writeBuf,\n                        uint16_t certId,\n                        std::unique_ptr<folly::IOBuf> authenticator,\n                        bool toBeContinued);\n\n/**\n * Generate a padding frame, using an unused reserved frame type, which the\n * recipient MUST ignore.\n *\n * @param writeBuf The output queue to write to. It may grow or add\n *                 underlying buffers inside this function.\n * @param padding The requested number of bytes to write in total to writeBuf\n * @return The actual number of bytes written to writeBuf.\n */\nsize_t writePadding(folly::IOBufQueue& writeBuf,\n                    uint32_t stream,\n                    uint16_t padding) noexcept;\n\n/**\n * Get the string representation of the given FrameType\n *\n * @param type frame type\n *\n * @return string representation of the frame type\n */\nconst char* getFrameTypeString(FrameType type);\n\n/**\n * Calculate the amount of space needed for the frame header and any payload\n * components that come before the header block.\n *\n * @param hasAssociatedStream Set for PUSH_PROMISE\n * @param hasExAttributes Set for EX_HEADERS\n * @param hasPriority Set if there is priority\n * @param hasPadding Set if there is padding\n */\nuint8_t calculatePreHeaderBlockSize(bool hasAssocStream,\n                                    bool hasPriority,\n                                    bool hasPadding);\n\n} // namespace proxygen::http2\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPBinaryCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPBinaryCodec.h>\n\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\n#define HANDLE_ERROR_OR_WAITING_PARSE_RESULT(parseResult)           \\\n  if ((parseResult).parseResultState_ == ParseResultState::ERROR) { \\\n    parseError_ = (parseResult).error_;                             \\\n    break;                                                          \\\n  } else if ((parseResult).parseResultState_ ==                     \\\n             ParseResultState::WAITING_FOR_MORE_DATA) {             \\\n    parserWaitingForMoreData_ = true;                               \\\n    break;                                                          \\\n  }\n\nnamespace proxygen {\n\nnamespace {\nfolly::Expected<size_t, quic::TransportErrorCode> encodeInteger(\n    uint64_t i, folly::io::QueueAppender& appender) {\n  auto result = quic::encodeQuicInteger(\n      i, [&](auto val) { appender.writeBE(folly::tag<decltype(val)>, val); });\n  if (result.has_value()) {\n    return result.value();\n  } else {\n    return folly::makeUnexpected(result.error());\n  }\n}\n\nvoid encodeString(folly::StringPiece str, folly::io::QueueAppender& appender) {\n  encodeInteger(str.size(), appender);\n  appender.pushAtMost((const uint8_t*)str.data(), str.size());\n}\n} // namespace\n\nHTTPBinaryCodec::HTTPBinaryCodec(TransportDirection direction)\n    : HTTPBinaryCodec(direction, true) {\n}\nHTTPBinaryCodec::HTTPBinaryCodec(TransportDirection direction,\n                                 bool knownEgressLength)\n    : knownEgressLength_(knownEgressLength),\n      state_(ParseState::FRAMING_INDICATOR),\n      parserPaused_(false),\n      parseError_(folly::none),\n      transportDirection_(direction) {\n}\n\nHTTPBinaryCodec::~HTTPBinaryCodec() = default;\n\nParseResult HTTPBinaryCodec::parseFramingIndicator(folly::io::Cursor& cursor,\n                                                   bool& request,\n                                                   bool& knownLength) {\n  size_t parsed = 0;\n\n  // Parse the framingIndicator and advance the cursor\n  auto framingIndicator = quic::follyutils::decodeQuicInteger(cursor);\n  if (!framingIndicator) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Increase parsed by the number of bytes read\n  parsed += framingIndicator->second;\n  // Sanity check the value of the framingIndicator\n  if (framingIndicator->first >\n      static_cast<uint64_t>(\n          HTTPBinaryCodec::FramingIndicator::RESPONSE_INDETERMINATE_LENGTH)) {\n    return ParseResult(\n        fmt::format(\"Invalid Framing Indicator: {}\", framingIndicator->first));\n  }\n\n  // Set request to true if framingIndicator is even (0 and 2 correspond to\n  // requests)\n  request = ((framingIndicator->first & 0x01) == 0);\n  // Set knownLength to true if framingIndicator is 0 or 1\n  knownLength = ((framingIndicator->first & 0x02) == 0);\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseKnownLengthString(\n    folly::io::Cursor& cursor,\n    size_t remaining,\n    folly::StringPiece stringName,\n    std::string& stringValue) {\n  size_t parsed = 0;\n\n  // Parse the encodedStringLength and advance cursor\n  auto encodedStringLength = quic::follyutils::decodeQuicInteger(cursor);\n  if (!encodedStringLength) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Increase parsed by the number of bytes read\n  parsed += encodedStringLength->second;\n  // If this would cause us to go beyond \"remaining\", we need to wait for more\n  // data\n  if (encodedStringLength->first > remaining - parsed) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n\n  // Handle edge case where field is not present/has length 0\n  if (encodedStringLength->first == 0) {\n    stringValue.clear();\n    return ParseResult(parsed);\n  }\n\n  // Read the value of the encodedString\n  stringValue = cursor.readFixedString(encodedStringLength->first);\n\n  // Increase parsed by the number of bytes read\n  parsed += encodedStringLength->first;\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseRequestControlData(folly::io::Cursor& cursor,\n                                                     size_t remaining,\n                                                     HTTPMessage& msg) {\n  size_t parsed = 0;\n\n  // Parse method\n  std::string method;\n  auto methodRes = parseKnownLengthString(cursor, remaining, \"method\", method);\n  if (methodRes.parseResultState_ == ParseResultState::ERROR ||\n      methodRes.parseResultState_ == ParseResultState::WAITING_FOR_MORE_DATA) {\n    return methodRes;\n  }\n  parsed += methodRes.bytesParsed_;\n  remaining -= methodRes.bytesParsed_;\n  msg.setMethod(method);\n\n  // Parse scheme\n  std::string scheme;\n  auto schemeRes = parseKnownLengthString(cursor, remaining, \"scheme\", scheme);\n  if (schemeRes.parseResultState_ == ParseResultState::ERROR ||\n      schemeRes.parseResultState_ == ParseResultState::WAITING_FOR_MORE_DATA) {\n    return schemeRes;\n  }\n  parsed += schemeRes.bytesParsed_;\n  remaining -= schemeRes.bytesParsed_;\n  if (scheme == proxygen::headers::kHttp) {\n    msg.setSecure(false);\n  } else if (scheme == proxygen::headers::kHttps) {\n    msg.setSecure(true);\n  } else {\n    return ParseResult(\n        std::string(\"Failure to parse: scheme. Should be 'http' or 'https'\"));\n  }\n\n  // Parse authority\n  std::string authority;\n  auto authorityRes =\n      parseKnownLengthString(cursor, remaining, \"authority\", authority);\n  if (authorityRes.parseResultState_ == ParseResultState::ERROR ||\n      authorityRes.parseResultState_ ==\n          ParseResultState::WAITING_FOR_MORE_DATA) {\n    return authorityRes;\n  }\n  parsed += authorityRes.bytesParsed_;\n  remaining -= authorityRes.bytesParsed_;\n\n  // Parse path\n  std::string path;\n  auto pathRes = parseKnownLengthString(cursor, remaining, \"path\", path);\n  if (pathRes.parseResultState_ == ParseResultState::ERROR ||\n      pathRes.parseResultState_ == ParseResultState::WAITING_FOR_MORE_DATA) {\n    return pathRes;\n  }\n  // Set relative path to msg URL\n  auto parseUrl = msg.setURL(path);\n  if (!parseUrl.valid()) {\n    return ParseResult(\n        fmt::format(\"Failure to parse: invalid URL path '{}'\", path));\n  }\n  parsed += pathRes.bytesParsed_;\n  remaining -= pathRes.bytesParsed_;\n  CHECK(remaining >= 0);\n\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseResponseControlData(folly::io::Cursor& cursor,\n                                                      size_t remaining,\n                                                      HTTPMessage& msg) {\n  // Parse statusCode and advance cursor\n  auto statusCode = quic::follyutils::decodeQuicInteger(cursor);\n  if (!statusCode) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Sanity check status code\n  if (statusCode->first < 200 || statusCode->first > 599) {\n    return ParseResult(\n        fmt::format(\"Invalid response status code: {}\", statusCode->first));\n  }\n  msg.setStatusCode(statusCode->first);\n  return ParseResult(statusCode->second);\n}\n\nParseResult HTTPBinaryCodec::parseSingleHeaderHelper(\n    folly::io::Cursor& cursor,\n    HeaderDecodeInfo& decodeInfo,\n    size_t& parsed,\n    size_t& remaining,\n    size_t& numHeaders) {\n  std::string headerName;\n  auto headerNameRes =\n      parseKnownLengthString(cursor, remaining, \"headerName\", headerName);\n  if (headerNameRes.parseResultState_ == ParseResultState::ERROR ||\n      headerNameRes.parseResultState_ ==\n          ParseResultState::WAITING_FOR_MORE_DATA) {\n    return headerNameRes;\n  }\n  parsed += headerNameRes.bytesParsed_;\n  remaining -= headerNameRes.bytesParsed_;\n\n  std::string headerValue;\n  auto headerValueRes =\n      parseKnownLengthString(cursor, remaining, \"headerValue\", headerValue);\n  if (headerValueRes.parseResultState_ == ParseResultState::ERROR ||\n      headerValueRes.parseResultState_ ==\n          ParseResultState::WAITING_FOR_MORE_DATA) {\n    return headerValueRes;\n  }\n  parsed += headerValueRes.bytesParsed_;\n  remaining -= headerValueRes.bytesParsed_;\n\n  if (!decodeInfo.onHeader(proxygen::HPACKHeaderName(headerName),\n                           headerValue) ||\n      !decodeInfo.parsingError.empty()) {\n    return ParseResult(fmt::format(\"Error parsing field section (Error: {})\",\n                                   decodeInfo.parsingError));\n  }\n  numHeaders++;\n  return ParseResult(ParseResultState::DONE);\n}\n\nParseResult HTTPBinaryCodec::parseKnownLengthHeadersHelper(\n    folly::io::Cursor& cursor,\n    size_t remaining,\n    HeaderDecodeInfo& decodeInfo,\n    bool isTrailers) {\n  size_t parsed = 0;\n\n  // Parse length of headers and advance cursor\n  auto lengthOfHeaders = quic::follyutils::decodeQuicInteger(cursor);\n  if (!lengthOfHeaders) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Check that we had enough bytes to parse lengthOfHeaders\n  if (remaining < lengthOfHeaders->second) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Increase parsed and decrease remaining by the number of bytes read\n  parsed += lengthOfHeaders->second;\n  remaining -= lengthOfHeaders->second;\n  if (remaining < lengthOfHeaders->first) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n\n  size_t numHeaders = 0;\n  while (parsed < lengthOfHeaders->first) {\n    auto result = parseSingleHeaderHelper(\n        cursor, decodeInfo, parsed, remaining, numHeaders);\n    if (result.parseResultState_ == ParseResultState::ERROR ||\n        result.parseResultState_ == ParseResultState::WAITING_FOR_MORE_DATA) {\n      return result;\n    }\n  }\n\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseIndeterminateLengthHeadersHelper(\n    folly::io::Cursor& cursor,\n    size_t remaining,\n    HeaderDecodeInfo& decodeInfo,\n    bool isTrailers) {\n  size_t parsed = 0;\n\n  auto currentByte = cursor.peek().data();\n  size_t numHeaders = 0;\n  // Continue parsing headers until we reach the Content Terminator field (0)\n  while (currentByte != nullptr && *currentByte != 0x00) {\n    auto result = parseSingleHeaderHelper(\n        cursor, decodeInfo, parsed, remaining, numHeaders);\n    if (result.parseResultState_ == ParseResultState::ERROR ||\n        result.parseResultState_ == ParseResultState::WAITING_FOR_MORE_DATA) {\n      return result;\n    }\n    // If we have reached the end of the cursor at this point, we must be\n    // waiting for more data since we haven't seen a Content Terminator field\n    // yet\n    if (cursor.isAtEnd()) {\n      return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n    }\n    currentByte = cursor.peek().data();\n  }\n  // Skip over the Content Terminator field\n  parsed++;\n  remaining--;\n  cursor.skip(1);\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseHeaders(folly::io::Cursor& cursor,\n                                          size_t remaining,\n                                          HeaderDecodeInfo& decodeInfo,\n                                          bool knownLength) {\n  return knownLength ? parseKnownLengthHeadersHelper(\n                           cursor, remaining, decodeInfo, false)\n                     : parseIndeterminateLengthHeadersHelper(\n                           cursor, remaining, decodeInfo, false);\n}\n\nParseResult HTTPBinaryCodec::parseContent(folly::io::Cursor& cursor,\n                                          size_t remaining) {\n  return knownIngressLength_\n             ? parseKnownLengthContentHelper(cursor, remaining)\n             : parseIndeterminateLengthContentHelper(cursor, remaining);\n}\n\nParseResult HTTPBinaryCodec::parseSingleContentHelper(folly::io::Cursor& cursor,\n                                                      size_t remaining) {\n  size_t parsed = 0;\n\n  // Parse the contentLength and advance cursor\n  auto contentLength = quic::follyutils::decodeQuicInteger(cursor);\n  if (!contentLength) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n  // Increase parsed by the number of bytes read\n  parsed += contentLength->second;\n  if (contentLength->first == 0) {\n    return ParseResult(parsed);\n  }\n  // Check that we have not gone beyond \"remaining\"\n  if (contentLength->first > remaining - parsed) {\n    return ParseResult(ParseResultState::WAITING_FOR_MORE_DATA);\n  }\n\n  // Write the data to msgBody_ and then advance the cursor\n  msgBody_ = std::make_unique<folly::IOBuf>();\n  if (contentLength->first > 0 && msgBody_) {\n    cursor.cloneAtMost(*msgBody_.get(), contentLength->first);\n  }\n\n  // Increase parsed by the number of bytes read\n  parsed += contentLength->first;\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseKnownLengthContentHelper(\n    folly::io::Cursor& cursor, size_t remaining) {\n  auto parseResult = parseSingleContentHelper(cursor, remaining);\n  if (parseResult.parseResultState_ == ParseResultState::DONE && msgBody_ &&\n      callback_) {\n    callback_->onBody(ingressTxnID_, std::move(msgBody_), 0);\n  }\n  return parseResult;\n}\n\nParseResult HTTPBinaryCodec::parseIndeterminateLengthContentHelper(\n    folly::io::Cursor& cursor, size_t remaining) {\n  // If the content length is indeterminate, then we need to parse the\n  // body piece by piece, calling onBody each time when we parse a piece\n  const unsigned char* currentByte;\n  ParseResult parseResult(ParseResultState::INITIALIZED);\n  size_t parsed = 0;\n  do {\n    // Parse the next body chunk\n    parseResult = parseSingleContentHelper(cursor, remaining);\n    if (parseResult.parseResultState_ == ParseResultState::ERROR ||\n        parseResult.parseResultState_ ==\n            ParseResultState::WAITING_FOR_MORE_DATA) {\n      return parseResult;\n    }\n    // After successfully processing a body chunk, we call onBody\n    parsed += parseResult.bytesParsed_;\n    if (msgBody_ && callback_) {\n      callback_->onBody(ingressTxnID_, std::move(msgBody_), 0);\n    }\n    // If we have reached the end of the cursor at this point, we must\n    // be waiting for more data since we haven't seen a Content\n    // Terminator field yet\n    if (cursor.isAtEnd()) {\n      return ParseResult(parsed, ParseResultState::WAITING_FOR_MORE_DATA);\n    }\n    // Update the currentByte to the next byte in the cursor to check\n    // if we have reached the end of all the bodies\n    currentByte = cursor.peek().data();\n  } while (currentByte != nullptr && *currentByte != 0x00);\n  // If we finished parsing all the content, then we can skip over the Content\n  // Terminator field and then return\n  parsed++;\n  cursor.skip(1);\n  return ParseResult(parsed);\n}\n\nParseResult HTTPBinaryCodec::parseTrailers(folly::io::Cursor& cursor,\n                                           size_t remaining,\n                                           HeaderDecodeInfo& decodeInfo) {\n  return knownIngressLength_ ? parseKnownLengthHeadersHelper(\n                                   cursor, remaining, decodeInfo, true)\n                             : parseIndeterminateLengthHeadersHelper(\n                                   cursor, remaining, decodeInfo, true);\n}\n\nsize_t HTTPBinaryCodec::onIngress(const folly::IOBuf& buf) {\n  parserWaitingForMoreData_ = false;\n\n  bufferedIngress_.append(buf.clone());\n  const auto len = bufferedIngress_.chainLength();\n\n  size_t parsedTot = 0;\n  folly::io::Cursor cursor(bufferedIngress_.front());\n  auto bufLen = bufferedIngress_.chainLength();\n\n  while (!parseError_ && !parserPaused_ && !parserWaitingForMoreData_ &&\n         parsedTot < bufLen) {\n    size_t parsed = 0;\n    ParseResult parseResult(ParseResultState::INITIALIZED);\n    switch (state_) {\n      case ParseState::FRAMING_INDICATOR: {\n        // FRAMING_INDICATOR should be the first item that is parsed\n        parseResult =\n            parseFramingIndicator(cursor, isRequest_, knownIngressLength_);\n        HANDLE_ERROR_OR_WAITING_PARSE_RESULT(parseResult);\n        parsed += parseResult.bytesParsed_;\n        // If the framing indicator is for a request, then the\n        // TransportDirection should be downstream\n        const bool ok = isDownstream(transportDirection_) == isRequest_;\n        if (!ok) {\n          parseError_ =\n              fmt::format(\"Invalid Framing Indicator '{}' for {} codec\",\n                          isRequest_ ? \"request\" : \"response\",\n                          folly::to_underlying(transportDirection_));\n          break;\n        }\n        if (!isRequest_) {\n          // If it's a response, then the next item to parse is the\n          // INFORMATIONAL_RESPONSE\n          state_ = ParseState::INFORMATIONAL_RESPONSE;\n        } else {\n          // else we parse the control data\n          state_ = ParseState::CONTROL_DATA;\n        }\n        break;\n      }\n\n      case ParseState::INFORMATIONAL_RESPONSE:\n        // TODO(T118289674) - Currently, the OHAI protocol doesn't support\n        // informational responses\n        // (https://ietf-wg-ohai.github.io/oblivious-http/draft-ietf-ohai-ohttp.html#name-informational-responses).\n        // Since we are primarily building this codec for an MVP 3rd Party\n        // OHAI proxy, we will skip parsing the INFORMATIONAL_RESPONSE for now\n        // and we can implement this later for complete functionality.\n        state_ = ParseState::CONTROL_DATA;\n        break;\n\n      case ParseState::CONTROL_DATA:\n        if (!decodeInfo_.msg) {\n          decodeInfo_.init(isRequest_,\n                           false /* isRequestTrailers */,\n                           true /* validate */,\n                           true /* strictValidation */,\n                           false /* allowEmptyPath */);\n        }\n        // The control data has a different format based on request/response\n        if (isRequest_) {\n          parseResult = parseRequestControlData(\n              cursor, bufLen - parsedTot, *decodeInfo_.msg);\n        } else {\n          parseResult = parseResponseControlData(\n              cursor, bufLen - parsedTot, *decodeInfo_.msg);\n        }\n        HANDLE_ERROR_OR_WAITING_PARSE_RESULT(parseResult);\n        parsed += parseResult.bytesParsed_;\n        state_ = ParseState::HEADERS_SECTION;\n        break;\n\n      case ParseState::HEADERS_SECTION:\n        CHECK(decodeInfo_.msg);\n        parseResult = parseHeaders(\n            cursor, bufLen - parsedTot, decodeInfo_, knownIngressLength_);\n        HANDLE_ERROR_OR_WAITING_PARSE_RESULT(parseResult);\n        parsed += parseResult.bytesParsed_;\n        state_ = ParseState::CONTENT;\n        msg_ = std::move(decodeInfo_.msg);\n        callback_->onHeadersComplete(ingressTxnID_, std::move(msg_));\n        break;\n\n      case ParseState::CONTENT:\n        parseResult = parseContent(cursor, bufLen - parsedTot);\n        if (parseResult.parseResultState_ == ParseResultState::ERROR) {\n          parseError_ = parseResult.error_;\n          break;\n        } else if (parseResult.parseResultState_ ==\n                   ParseResultState::WAITING_FOR_MORE_DATA) {\n          parsed += parseResult.bytesParsed_;\n          parserWaitingForMoreData_ = true;\n          break;\n        }\n        parsed += parseResult.bytesParsed_;\n        state_ = ParseState::TRAILERS_SECTION;\n        break;\n\n      case ParseState::TRAILERS_SECTION:\n        if (!decodeInfo_.msg) {\n          decodeInfo_.init(isRequest_,\n                           true /* isRequestTrailers */,\n                           true /* validate */,\n                           true /* strictValidation */,\n                           false /* allowEmptyPath */);\n        }\n        parseResult = parseTrailers(cursor, bufLen - parsedTot, decodeInfo_);\n        HANDLE_ERROR_OR_WAITING_PARSE_RESULT(parseResult);\n        trailers_ =\n            std::make_unique<HTTPHeaders>(decodeInfo_.msg->getHeaders());\n        parsed += parseResult.bytesParsed_;\n        state_ = ParseState::PADDING;\n        if (trailers_) {\n          callback_->onTrailersComplete(ingressTxnID_, std::move(trailers_));\n        }\n        break;\n\n      case ParseState::PADDING:\n        // This needs to be the last section\n        parsed = bufLen - parsedTot;\n        cursor.advanceToEnd();\n        break;\n\n      default:\n        CHECK(false);\n    }\n    parsedTot += parsed;\n  }\n\n  if (parseError_) {\n    callback_->onError(\n        ingressTxnID_,\n        HTTPException(HTTPException::Direction::INGRESS,\n                      fmt::format(\"Invalid Message: {}\", *parseError_)));\n  }\n\n  // We can trim the amount of bufferedIngress_ that we were successfully able\n  // to parse\n  bufferedIngress_.trimStartAtMost(parsedTot);\n\n  return len;\n}\n\nvoid HTTPBinaryCodec::onIngressEOF() {\n  if (!parseError_ && !bufferedIngress_.empty()) {\n    // Case where the ingress EOF is received before the entire message is\n    // parsed\n    callback_->onError(ingressTxnID_,\n                       HTTPException(HTTPException::Direction::INGRESS,\n                                     \"Incomplete message received\"));\n    return;\n  }\n  if (!parseError_ && (state_ == ParseState::FRAMING_INDICATOR ||\n                       state_ == ParseState::CONTROL_DATA)) {\n    // Case where sent message is either empty or only contains framing\n    // indicator\n    callback_->onError(\n        ingressTxnID_,\n        HTTPException(HTTPException::Direction::INGRESS,\n                      \"Incomplete message received, either empty or only \"\n                      \"contains framing indicator\"));\n    return;\n  }\n  if (state_ == ParseState::HEADERS_SECTION) {\n    // Case where the sent message only contains control data and no headers\n    // nor body\n    callback_->onHeadersComplete(ingressTxnID_, std::move(decodeInfo_.msg));\n  }\n  if (!parseError_ && !parserPaused_) {\n    callback_->onMessageComplete(ingressTxnID_, false);\n  }\n  return;\n}\n\nsize_t HTTPBinaryCodec::generateHeaderHelper(folly::io::QueueAppender& appender,\n                                             const HTTPHeaders& headers) {\n\n  size_t headersLength = 0;\n  if (knownEgressLength_) {\n    // Calculate the number of bytes it will take to encode all the headers\n    headers.forEach([&](folly::StringPiece name, folly::StringPiece value) {\n      auto nameSize = name.size();\n      auto valueSize = value.size();\n      headersLength += quic::getQuicIntegerSize(nameSize).value() + nameSize +\n                       quic::getQuicIntegerSize(valueSize).value() + valueSize;\n    });\n\n    // Encode all the headers\n    auto lengthOfAllHeaders = encodeInteger(headersLength, appender);\n    headersLength += lengthOfAllHeaders.value();\n  }\n\n  headers.forEach([&](folly::StringPiece name, folly::StringPiece value) {\n    encodeString(name, appender);\n    encodeString(value, appender);\n  });\n\n  if (!knownEgressLength_) {\n    encodeInteger(0, appender);\n  }\n\n  return headersLength;\n}\n\nvoid HTTPBinaryCodec::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID txn,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  folly::io::QueueAppender appender(&writeBuf, queueAppenderMaxGrowth);\n  if (isUpstream(transportDirection_)) {\n    // Encode Framing Indicator for Request\n    encodeInteger(\n        folly::to<uint64_t>(\n            knownEgressLength_\n                ? HTTPBinaryCodec::FramingIndicator::REQUEST_KNOWN_LENGTH\n                : HTTPBinaryCodec::FramingIndicator::\n                      REQUEST_INDETERMINATE_LENGTH),\n        appender);\n\n    // Encode Request Control Data\n    encodeString(msg.getMethodString(), appender);\n    encodeString(msg.isSecure() ? \"https\" : \"http\", appender);\n    encodeString(msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST), appender);\n\n    std::string pathWithQueryString = msg.getPath();\n    if (!msg.getQueryString().empty()) {\n      pathWithQueryString.append(\"?\");\n      pathWithQueryString.append(msg.getQueryString());\n    }\n    encodeString(pathWithQueryString, appender);\n  } else {\n    encodeInteger(\n        folly::to<uint64_t>(\n            knownEgressLength_\n                ? HTTPBinaryCodec::FramingIndicator::RESPONSE_KNOWN_LENGTH\n                : HTTPBinaryCodec::FramingIndicator::\n                      RESPONSE_INDETERMINATE_LENGTH),\n        appender);\n    // Response Control Data\n    encodeInteger(msg.getStatusCode(), appender);\n  }\n  generateHeaderHelper(appender, msg.getHeaders());\n}\n\nsize_t HTTPBinaryCodec::generateBody(folly::IOBufQueue& writeBuf,\n                                     StreamID txn,\n                                     std::unique_ptr<folly::IOBuf> chain,\n                                     folly::Optional<uint8_t> padding,\n                                     bool eom) {\n  folly::io::QueueAppender appender(&writeBuf, queueAppenderMaxGrowth);\n  size_t lengthWritten = 0;\n  if (chain) {\n    lengthWritten = chain->computeChainDataLength();\n    encodeInteger(lengthWritten, appender);\n    appender.insert(std::move(chain));\n  } else {\n    encodeInteger(0, appender);\n  }\n  if (eom) {\n    lengthWritten += generateEOM(writeBuf, txn);\n  }\n\n  return lengthWritten;\n}\n\nsize_t HTTPBinaryCodec::generateTrailers(folly::IOBufQueue& writeBuf,\n                                         StreamID txn,\n                                         const HTTPHeaders& trailers) {\n  folly::io::QueueAppender appender(&writeBuf, queueAppenderMaxGrowth);\n  auto trailersLengthWritten = generateHeaderHelper(appender, trailers);\n  encodeInteger(0, appender);\n  trailersLengthWritten++;\n\n  return trailersLengthWritten;\n}\n\nsize_t HTTPBinaryCodec::generatePadding(folly::IOBufQueue& writeBuf,\n                                        StreamID stream,\n                                        uint16_t bytes) {\n  return 0;\n}\n\nsize_t HTTPBinaryCodec::generateEOM(folly::IOBufQueue& writeBuf, StreamID txn) {\n  if (!knownEgressLength_) {\n    folly::io::QueueAppender appender(&writeBuf, queueAppenderMaxGrowth);\n    encodeInteger(0, appender);\n    return 1;\n  }\n  return 0;\n}\n\nsize_t HTTPBinaryCodec::generateChunkHeader(folly::IOBufQueue& writeBuf,\n                                            HTTPBinaryCodec::StreamID stream,\n                                            size_t length) {\n  // TODO(T118289674) - Implement HTTPBinaryCodec\n  return 0;\n}\n\nsize_t HTTPBinaryCodec::generateChunkTerminator(\n    folly::IOBufQueue& writeBuf, HTTPBinaryCodec::StreamID stream) {\n  // TODO(T118289674) - Implement HTTPBinaryCodec\n  return 0;\n}\n\nsize_t HTTPBinaryCodec::generateRstStream(folly::IOBufQueue& writeBuf,\n                                          HTTPBinaryCodec::StreamID stream,\n                                          ErrorCode statusCode) {\n  // TODO(T118289674) - Implement HTTPBinaryCodec\n  return 0;\n}\n\nsize_t HTTPBinaryCodec::generateGoaway(\n    folly::IOBufQueue& writeBuf,\n    HTTPBinaryCodec::StreamID lastStream,\n    ErrorCode statusCode,\n    std::unique_ptr<folly::IOBuf> debugData) {\n  // TODO(T118289674) - Implement HTTPBinaryCodec\n  return 0;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPBinaryCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HeaderDecodeInfo.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <string>\n\nnamespace proxygen {\n\nenum class ParseResultState {\n  INITIALIZED = 0,\n  WAITING_FOR_MORE_DATA = 1,\n  DONE = 2,\n  ERROR = 3,\n};\n\nclass ParseResult {\n public:\n  explicit ParseResult(size_t bytesParsed)\n      : parseResultState_(ParseResultState::DONE), bytesParsed_(bytesParsed) {\n  }\n  explicit ParseResult(size_t bytesParsed,\n                       const ParseResultState& parseResultState)\n      : parseResultState_(parseResultState), bytesParsed_(bytesParsed) {\n  }\n  explicit ParseResult(const ParseResultState& parseResultState)\n      : parseResultState_(parseResultState) {\n  }\n  explicit ParseResult(std::string error)\n      : parseResultState_(ParseResultState::ERROR), error_(std::move(error)) {\n  }\n\n  ParseResultState parseResultState_{ParseResultState::INITIALIZED};\n  size_t bytesParsed_{};\n  std::string error_;\n};\n\n/* The HTTPBinaryCodec class is an implementation of the \"Binary Representation\n * of HTTP Messages\" RFC -\n * (https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-binary-message-01).\n * We support both the \"Known Length Messages\" and \"Indeterminate-Length\n * Messages\" modes of the RFC.\n */\nclass HTTPBinaryCodec : public HTTPCodec {\n public:\n  // Default strictValidation to false for now to match existing behavior\n  explicit HTTPBinaryCodec(TransportDirection direction);\n  HTTPBinaryCodec(TransportDirection direction, bool knownEgressLength);\n  ~HTTPBinaryCodec() override;\n\n  HTTPBinaryCodec(HTTPBinaryCodec&&) = default;\n\n  // HTTPCodec API\n  CodecProtocol getProtocol() const override {\n    return CodecProtocol::HTTP_BINARY;\n  }\n\n  const std::string& getUserAgent() const override {\n    return userAgent_;\n  }\n\n  TransportDirection getTransportDirection() const override {\n    return transportDirection_;\n  }\n  StreamID createStream() override {\n    return 0;\n  }\n  void setCallback(Callback* callback) override {\n    callback_ = callback;\n  }\n  bool isBusy() const override {\n    return false;\n  }\n  void setParserPaused(bool paused) override {\n    parserPaused_ = paused;\n  }\n  bool isParserPaused() const override {\n    return parserPaused_;\n  }\n  size_t onIngress(const folly::IOBuf& buf) override;\n  void onIngressEOF() override;\n  bool isReusable() const override {\n    return true;\n  }\n  bool isWaitingToDrain() const override {\n    return false;\n  }\n  bool isEgressBusy() const {\n    return false;\n  }\n  // True if the session requires an EOF (or RST) to terminate the message\n  bool closeOnEgressComplete() const override {\n    return !isEgressBusy() && !isReusable();\n  }\n  bool supportsParallelRequests() const override {\n    return false;\n  }\n  bool supportsPushTransactions() const override {\n    return false;\n  }\n  size_t generateChunkHeader(folly::IOBufQueue& writeBuf,\n                             StreamID stream,\n                             size_t length) override;\n  size_t generateChunkTerminator(folly::IOBufQueue& writeBuf,\n                                 StreamID stream) override;\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           ErrorCode statusCode) override;\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream = HTTPCodec::MaxStreamID,\n      ErrorCode statusCode = ErrorCode::NO_ERROR,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID txn,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override;\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID txn,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding = folly::none,\n                      bool eom = false) override;\n  size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                          StreamID txn,\n                          const HTTPHeaders& trailers) override;\n  size_t generatePadding(folly::IOBufQueue& writeBuf,\n                         StreamID stream,\n                         uint16_t bytes) override;\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID txn) override;\n\n protected:\n  /* The format of Binary HTTP Messages is the following:\n   *\n   * Message with Known-Length {\n   *    Framing Indicator (i) = 0..1,\n   *    Known-Length Informational Response (..),\n   *    Control Data (..),\n   *    Known-Length Field Section (..),\n   *    Known-Length Content (..),\n   *    Known-Length Field Section (..),\n   *    Padding (..),\n   *  }\n   *\n   * Message with Indeterminate-Length {\n   *    Framing Indicator (i) = 2,\n   *    Indeterminate-Length Informational Response (..),\n   *    Control Data (..),\n   *    Indeterminate-Length Field Section (..),\n   *    Indeterminate-Length Content (..),\n   *    Indeterminate-Length Field Section (..),\n   *    Padding (..),\n   *  }\n   */\n  ParseResult parseFramingIndicator(folly::io::Cursor& cursor,\n                                    bool& request,\n                                    bool& knownLength);\n  ParseResult parseKnownLengthString(folly::io::Cursor& cursor,\n                                     size_t remaining,\n                                     folly::StringPiece stringName,\n                                     std::string& stringValue);\n  ParseResult parseRequestControlData(folly::io::Cursor& cursor,\n                                      size_t remaining,\n                                      HTTPMessage& msg);\n  ParseResult parseResponseControlData(folly::io::Cursor& cursor,\n                                       size_t remaining,\n                                       HTTPMessage& msg);\n  ParseResult parseHeaders(folly::io::Cursor& cursor,\n                           size_t remaining,\n                           HeaderDecodeInfo& decodeInfo,\n                           bool knownLength);\n  ParseResult parseContent(folly::io::Cursor& cursor, size_t remaining);\n  ParseResult parseSingleContentHelper(folly::io::Cursor& cursor,\n                                       size_t remaining);\n  ParseResult parseKnownLengthContentHelper(folly::io::Cursor& cursor,\n                                            size_t remaining);\n  ParseResult parseIndeterminateLengthContentHelper(folly::io::Cursor& cursor,\n                                                    size_t remaining);\n  ParseResult parseTrailers(folly::io::Cursor& cursor,\n                            size_t remaining,\n                            HeaderDecodeInfo& decodeInfo);\n  ParseResult parseSingleHeaderHelper(folly::io::Cursor& cursor,\n                                      HeaderDecodeInfo& decodeInfo,\n                                      size_t& parsed,\n                                      size_t& remaining,\n                                      size_t& numHeaders);\n  ParseResult parseKnownLengthHeadersHelper(folly::io::Cursor& cursor,\n                                            size_t remaining,\n                                            HeaderDecodeInfo& decodeInfo,\n                                            bool isTrailers);\n  ParseResult parseIndeterminateLengthHeadersHelper(\n      folly::io::Cursor& cursor,\n      size_t remaining,\n      HeaderDecodeInfo& decodeInfo,\n      bool isTrailers);\n  size_t generateHeaderHelper(folly::io::QueueAppender& appender,\n                              const HTTPHeaders& headers);\n\n  bool isRequest_{true};\n  bool knownEgressLength_{true};\n  bool knownIngressLength_{false};\n  enum class ParseState : uint8_t {\n    FRAMING_INDICATOR = 0,\n    INFORMATIONAL_RESPONSE = 1,\n    CONTROL_DATA = 2,\n    HEADERS_SECTION = 3,\n    CONTENT = 4,\n    TRAILERS_SECTION = 5,\n    PADDING = 6,\n  };\n  ParseState state_;\n  bool parserWaitingForMoreData_{false};\n  bool parserPaused_;\n  folly::Optional<std::string> parseError_{folly::none};\n\n  enum class FramingIndicator : uint8_t {\n    REQUEST_KNOWN_LENGTH = 0,\n    RESPONSE_KNOWN_LENGTH = 1,\n    REQUEST_INDETERMINATE_LENGTH = 2,\n    RESPONSE_INDETERMINATE_LENGTH = 3,\n  };\n\n  const size_t queueAppenderMaxGrowth = 256;\n\n  // This callback_ will be how we return decoded responses to the caller\n  HTTPCodec::Callback* callback_ = nullptr;\n\n  StreamID ingressTxnID_;\n\n  folly::IOBufQueue bufferedIngress_{folly::IOBufQueue::cacheChainLength()};\n  // We don't need to use an IOBufQueue for msgBody_ since we are writing a\n  // complete message to it and don't need to rely on the efficient size\n  // computation provided by IOBufQueue\n  std::unique_ptr<folly::IOBuf> msgBody_;\n\n  HeaderDecodeInfo decodeInfo_;\n  std::unique_ptr<HTTPMessage> msg_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n  std::string userAgent_;\n  TransportDirection transportDirection_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPChecks.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n\n#include <proxygen/lib/http/RFC2616.h>\n\nnamespace proxygen {\n\nvoid HTTPChecks::onHeadersComplete(StreamID stream,\n                                   std::unique_ptr<HTTPMessage> msg) {\n\n  if (msg->isRequest() &&\n      (RFC2616::isRequestBodyAllowed(msg->getMethod()) ==\n       RFC2616::BodyAllowed::NOT_ALLOWED) &&\n      RFC2616::bodyImplied(msg->getHeaders())) {\n    HTTPException ex(HTTPException::Direction::INGRESS,\n                     \"RFC2616: Request Body Not Allowed\");\n    ex.setProxygenError(kErrorParseHeader);\n    // setting the status code means that the error is at the HTTP layer and\n    // that parsing succeeded.\n    ex.setHttpStatusCode(400);\n    callback_->onError(stream, ex, true);\n    return;\n  }\n\n  callback_->onHeadersComplete(stream, std::move(msg));\n}\n\nvoid HTTPChecks::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* sizeOut,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  if (msg.isRequest() && RFC2616::bodyImplied(msg.getHeaders())) {\n    CHECK(RFC2616::isRequestBodyAllowed(msg.getMethod()) !=\n          RFC2616::BodyAllowed::NOT_ALLOWED);\n    // We could also add a \"strict\" mode that disallows sending body on GET\n    // requests here too.\n  }\n\n  call_->generateHeader(writeBuf, stream, msg, eom, sizeOut, extraHeaders);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPChecks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n\nnamespace proxygen {\n\n/**\n * This class enforces certain higher-level HTTP semantics. It does not enforce\n * conditions that require state to decide. That is, this class is stateless and\n * only examines the calls and callbacks that go through it.\n */\n\nclass HTTPChecks : public PassThroughHTTPCodecFilter {\n public:\n  // HTTPCodec::Callback methods\n\n  void onHeadersComplete(StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override;\n\n  // HTTPCodec methods\n\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom,\n      HTTPHeaderSize* sizeOut,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Portability.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/lang/Assume.h>\n#include <proxygen/lib/http/HTTPException.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen {\n\nclass HTTPHeaders;\nclass HTTPMessage;\nclass HTTPTransactionHandler;\nclass HTTPErrorPage;\n\n/**\n * Interface for a parser&generator that can translate between an internal\n * representation of an HTTP request and a wire format.  The details of the\n * wire format (e.g., HTTP/1.x encoding vs. SPDY encoding) are left for\n * subclasses to implement.\n */\nclass HTTPCodec {\n public:\n  /**\n   * Key that uniquely identifies a request/response pair within\n   * (and only within) the scope of the codec.  Code outside the\n   * codec should regard the StreamID as an opaque data\n   * structure; different subclasses of HTTPCodec are likely to\n   * use different conventions for generating StreamID values.\n   *\n   * A value of zero indicates an uninitialized/unknown/unspecified\n   * StreamID.\n   */\n  using StreamID = uint64_t;\n\n  static const folly::Optional<StreamID> NoStream;\n\n  static const folly::Optional<uint8_t> NoPadding;\n\n  static constexpr StreamID MaxStreamID = std::numeric_limits<StreamID>::max();\n\n  class PriorityQueue {\n   public:\n    virtual ~PriorityQueue() = default;\n\n    virtual void addPriorityNode(StreamID id, StreamID parent) = 0;\n  };\n\n  /**\n   * Callback interface that users of HTTPCodec must implement\n   */\n  class Callback {\n   public:\n    /**\n     * Called when a new message is seen while parsing the ingress\n     * @param stream   The stream ID\n     * @param msg      A newly allocated HTTPMessage\n     */\n    virtual void onMessageBegin(StreamID stream, HTTPMessage* msg) = 0;\n\n    /**\n     * Called when a new push message is seen while parsing the ingress.\n     *\n     * @param stream   The stream ID\n     * @param assocStream The stream ID of the associated stream,\n     *                 which can never be 0\n     * @param msg      A newly allocated HTTPMessage\n     */\n    virtual void onPushMessageBegin(StreamID /* stream */,\n                                    StreamID /* assocStream */,\n                                    HTTPMessage* /* msg */) {\n    }\n\n    /**\n     * Called when all the headers of an ingress message have been parsed\n     * @param stream   The stream ID\n     * @param msg      The message\n     * @param size     Size of the ingress header\n     */\n    virtual void onHeadersComplete(StreamID stream,\n                                   std::unique_ptr<HTTPMessage> msg) = 0;\n\n    /**\n     * Called for each block of message body data\n     * @param stream  The stream ID\n     * @param chain   One or more buffers of body data. The codec will\n     *                remove any protocol framing, such as HTTP/1.1 chunk\n     *                headers, from the buffers before calling this function.\n     * @param padding Number of pad bytes that came with the data segment\n     */\n    virtual void onBody(StreamID stream,\n                        std::unique_ptr<folly::IOBuf> chain,\n                        uint16_t padding) = 0;\n\n    /**\n     * Called for each HTTP chunk header.\n     *\n     * onChunkHeader() will be called when the chunk header is received.  As\n     * the chunk data arrives, it will be passed to the callback normally with\n     * onBody() calls.  Note that the chunk data may arrive in multiple\n     * onBody() calls: it is not guaranteed to arrive in a single onBody()\n     * call.\n     *\n     * After the chunk data has been received and the terminating CRLF has been\n     * received, onChunkComplete() will be called.\n     *\n     * @param stream    The stream ID\n     * @param length    The chunk length.\n     */\n    virtual void onChunkHeader(StreamID /* stream */, size_t /* length */) {\n    }\n\n    /**\n     * Called when the terminating CRLF is received to end a chunk of HTTP body\n     * data.\n     *\n     * @param stream    The stream ID\n     */\n    virtual void onChunkComplete(StreamID /* stream */) {\n    }\n\n    /**\n     * Called when all the trailers of an ingress message have been\n     * parsed, but only if the number of trailers is nonzero.\n     * @param stream   The stream ID\n     * @param trailers  The message trailers\n     */\n    virtual void onTrailersComplete(StreamID stream,\n                                    std::unique_ptr<HTTPHeaders> trailers) = 0;\n\n    /**\n     * Called at end of a message (including body and trailers, if applicable)\n     * @param stream   The stream ID\n     * @param upgrade  Whether the connection has been upgraded to another\n     *                 protocol.\n     */\n    virtual void onMessageComplete(StreamID stream, bool upgrade) = 0;\n\n    /**\n     * Called when a parsing or protocol error has occurred\n     * @param stream   The stream ID\n     * @param error    Description of the error\n     * @param newTxn   true if onMessageBegin has not been called for txn\n     */\n    virtual void onError(StreamID stream,\n                         const HTTPException& error,\n                         bool newTxn = false) = 0;\n\n    /**\n     * Called when the peer has asked to shut down a stream\n     * immediately.\n     * @param stream   The stream ID\n     * @param code     The code the stream was aborted with\n     * @note  Not applicable to all protocols.\n     */\n    virtual void onAbort(StreamID /* stream */, ErrorCode /* code */) {\n    }\n\n    /**\n     * Called upon receipt of a frame header.\n     * @param stream_id The stream ID\n     * @param flags     The flags field of frame header\n     * @param length    The length field of frame header\n     * @param type      The type field of frame header\n     * @param version   The version of frame (SPDY only)\n     * @note Not all protocols have frames. SPDY and HTTP/2 do,\n     *       but HTTP/1.1 doesn't.\n     */\n    virtual void onFrameHeader(StreamID /* stream_id */,\n                               uint8_t /* flags */,\n                               uint64_t /* length */,\n                               uint64_t /* type */,\n                               uint16_t /* version */ = 0) {\n    }\n\n    /**\n     * Called upon receipt of a goaway.\n     * @param lastGoodStreamID  Last successful stream created by the receiver\n     * @param code              The code the connection was aborted with\n     * @param debugData         The additional debug data for diagnostic purpose\n     * @note Not all protocols have goaways. SPDY does, but HTTP/1.1 doesn't.\n     */\n    virtual void onGoaway(\n        uint64_t /* lastGoodStreamID */,\n        ErrorCode /* code */,\n        std::unique_ptr<folly::IOBuf> /* debugData */ = nullptr) {\n    }\n\n    /**\n     * Called upon receipt of an unknown frame.\n     * @param streamID          stream ID\n     * @param frameType         frame type\n     * @note only used for quic testing\n     */\n    virtual void onUnknownFrame(uint64_t /* streamID */,\n                                uint64_t /* frameType */) {\n    }\n\n    /**\n     * Called upon receipt of a ping request\n     * @param data attached to the ping request\n     * @note Not all protocols have pings.HTTP/2 does, but HTTP/1.1 doesn't.\n     */\n    virtual void onPingRequest(uint64_t /* data */) {\n    }\n\n    /**\n     * Called upon receipt of a ping reply\n     * @param data data attached to the ping reply\n     * @note Not all protocols have pings. HTTP/2 does, but HTTP/1.1 doesn't.\n     */\n    virtual void onPingReply(uint64_t /* data */) {\n    }\n\n    /**\n     * Called upon receipt of a window update, for protocols that support\n     * flow control. For instance spdy/3 and higher.\n     */\n    virtual void onWindowUpdate(StreamID /* stream */, uint32_t /* amount */) {\n    }\n\n    /**\n     * Called upon receipt of a settings frame, for protocols that support\n     * settings.\n     *\n     * @param settings a list of settings that were sent in the settings frame\n     */\n    virtual void onSettings(const SettingsList& /* settings */) {\n    }\n\n    /**\n     * Called upon receipt of a settings frame with ACK set, for\n     * protocols that support settings ack.\n     */\n    virtual void onSettingsAck() {\n    }\n\n    /**\n     * Experimental: this is the new HTTP Priority draft format of priority\n     * update. This is called when a PRIORITY_UPDATE frame is received.\n     */\n    virtual void onPriority(StreamID, const HTTPPriority& /* pri */) {\n    }\n\n    /**\n     * Experimental: this is the new HTTP Priority draft format of priority\n     * update. This is called when a PUSH_PRIORITY_UPDATE frame is received.\n     */\n    virtual void onPushPriority(StreamID, const HTTPPriority& /* pri */) {\n    }\n\n    /**\n     * Called after a header frame is generated.\n     * This only applies to framed codecs.\n     */\n    virtual void onGenerateFrameHeader(StreamID /* stream_id */,\n                                       uint8_t /* type */,\n                                       uint64_t /* length */,\n                                       uint16_t /* version */ = 0) {\n    }\n\n    /**\n     * Called upon receipt of a certificate request frame, for protocols that\n     * support secondary certificate authentication.\n     * @param requestId The Request-ID identifying the certificate request\n     * @param authRequest The authenticator request\n     * @note Not all protocols support secondary certificate authentication.\n     * HTTP/2 does, but HTTP/1.1 doesn't.\n     */\n    virtual void onCertificateRequest(\n        uint16_t /* requestId */,\n        std::unique_ptr<folly::IOBuf> /* authRequest */) {\n    }\n\n    /**\n     * Called upon receipt of an authenticator, for protocols that\n     * support secondary certificate authentication.\n     * @param certId The Cert-ID identifying this authenticator\n     * @param authenticator The authenticator request\n     * @note Not all protocols support secondary certificate authentication.\n     * HTTP/2 does, but HTTP/1.1 doesn't.\n     */\n    virtual void onCertificate(\n        uint16_t /* certId */,\n        std::unique_ptr<folly::IOBuf> /* authenticator */) {\n    }\n\n    /**\n     * Return the number of open streams started by this codec callback.\n     * Parallel codecs with a maximum number of streams will invoke this\n     * to determine if a new stream exceeds the limit.\n     */\n    [[nodiscard]] virtual uint32_t numOutgoingStreams() const {\n      return 0;\n    }\n\n    /**\n     * Return the number of open streams started by the remote side.\n     * Parallel codecs with a maximum number of streams will invoke this\n     * to determine if a new stream exceeds the limit.\n     */\n    [[nodiscard]] virtual uint32_t numIncomingStreams() const {\n      return 0;\n    }\n\n    virtual ~Callback() = default;\n  };\n\n  virtual ~HTTPCodec() = default;\n\n  /**\n   * Maps a stream id to its sequence number using the underlying protocol as\n   * context.\n   */\n  static size_t streamIDToSeqNo(CodecProtocol protocol,\n                                HTTPCodec::StreamID id) {\n    switch (protocol) {\n      case CodecProtocol::HTTP_1_1:\n        DCHECK_NE(id, 0);\n        return id - 1;\n      case CodecProtocol::HTTP_2:\n        return id / 2;\n      case CodecProtocol::HQ:\n      case CodecProtocol::HTTP_3:\n        // This doesn't factor out of order stream arrival...\n        return id / 4;\n      case CodecProtocol::HTTP_BINARY:\n        [[fallthrough]];\n      case CodecProtocol::TUNNEL_LITE:\n        [[fallthrough]];\n      default:\n        LOG(FATAL) << \"Unreachable\";\n    }\n  }\n\n  /**\n   * Gets both the egress and ingress header table size, bytes stored in header\n   * table, and the number of headers stored in the header table\n   **/\n  [[nodiscard]] virtual CompressionInfo getCompressionInfo() const {\n    static CompressionInfo defaultCompressionInfo;\n    return defaultCompressionInfo;\n  }\n\n  /**\n   * Gets the session protocol currently used by the codec. This can be\n   * mapped to a string for logging and diagnostic use.\n   */\n  [[nodiscard]] virtual CodecProtocol getProtocol() const = 0;\n\n  /**\n   * Gets the user agent string of the client. Thus, it is only meaningful for a\n   * DOWNSTREAM session. Note that the value is available after\n   * onHeadersComplete().  It can help in diagnosing the interactions between\n   * different codec implementation.\n   */\n  [[nodiscard]] virtual const std::string& getUserAgent() const = 0;\n\n  /**\n   * Get the transport direction of this codec:\n   * DOWNSTREAM if the codec receives requests from clients or\n   * UPSTREAM if the codec sends requests to servers.\n   */\n  [[nodiscard]] virtual TransportDirection getTransportDirection() const = 0;\n\n  /**\n   * Returns true iff this codec supports per stream flow control\n   */\n  [[nodiscard]] virtual bool supportsStreamFlowControl() const {\n    return false;\n  }\n\n  /**\n   * Returns true iff this codec supports session level flow control\n   */\n  [[nodiscard]] virtual bool supportsSessionFlowControl() const {\n    return false;\n  }\n\n  /**\n   * Reserve a stream ID.\n   * @return           A stream ID on success, or zero on error.\n   */\n  virtual StreamID createStream() = 0;\n\n  /**\n   * Set the callback to notify on ingress events\n   * @param callback  The callback object\n   */\n  virtual void setCallback(Callback* callback) = 0;\n\n  /**\n   * Check whether the codec still has at least one HTTP\n   * stream to parse.\n   */\n  [[nodiscard]] virtual bool isBusy() const = 0;\n\n  /**\n   * Pause or resume the ingress parser\n   * @param paused  Whether the caller wants the parser to be paused\n   */\n  virtual void setParserPaused(bool paused) = 0;\n\n  /**\n   * Return true if the parser is paused\n   */\n  [[nodiscard]] virtual bool isParserPaused() const = 0;\n\n  /**\n   * Parse ingress data.\n   * @param  buf   A single IOBuf of data to parse\n   * @return Number of bytes consumed.\n   */\n  virtual size_t onIngress(const folly::IOBuf& buf) = 0;\n\n  /**\n   * Finish parsing when the ingress stream has ended.\n   */\n  virtual void onIngressEOF() = 0;\n\n  /**\n   * Invoked on a codec that has been upgraded to via an HTTPMessage on\n   * a different codec.  The codec may return false to halt the upgrade.\n   */\n  virtual bool onIngressUpgradeMessage(const HTTPMessage& /* msg */) {\n    return true;\n  }\n\n  /**\n   * Check whether the codec can process new streams. Typically,\n   * an implementing subclass will return true when a new codec is\n   * created and false once it encounters a situation that would\n   * prevent reuse of the underlying transport (e.g., a \"Connection: close\"\n   * in HTTP/1.x).\n   * @note A return value of true means that the codec can process new\n   *       connections at some reasonable point in the future; that may\n   *       mean \"immediately,\" for codecs that support pipelined or\n   *       interleaved requests, or \"upon completion of the current\n   *       stream\" for codecs that do not.\n   */\n  [[nodiscard]] virtual bool isReusable() const = 0;\n\n  /**\n   * Returns true if this codec is in a state where it accepting new\n   * requests but will soon begin to reject new requests. For SPDY and\n   * HTTP/2, this is true when the first GOAWAY NO_ERROR is sent during\n   * graceful shutdown.\n   */\n  [[nodiscard]] virtual bool isWaitingToDrain() const = 0;\n\n  /**\n   * Checks whether the socket needs to be closed when EOM is sent. This is used\n   * during CONNECT when EOF needs to be sent after upgrade to notify the server\n   */\n  [[nodiscard]] virtual bool closeOnEgressComplete() const = 0;\n\n  /**\n   * Check whether the codec supports the processing of multiple\n   * requests in parallel.\n   */\n  [[nodiscard]] virtual bool supportsParallelRequests() const = 0;\n\n  /**\n   * Check whether the codec supports pushing resources from server to\n   * client.\n   */\n  [[nodiscard]] virtual bool supportsPushTransactions() const = 0;\n\n  /**\n   * Check whether the codec supports bidirectional communications between\n   * server and client.\n   */\n  [[nodiscard]] virtual bool supportsExTransactions() const {\n    return false;\n  }\n\n  /**\n   * Generate a connection preface, if there is any for this protocol.\n   *\n   * @return size of the generated message\n   */\n  virtual size_t generateConnectionPreface(folly::IOBufQueue& /* writeBuf */) {\n    return 0;\n  }\n\n  /**\n   * Write an egress message header.  For pushed streams, you must specify\n   * the assocStream.\n   * @param extraHeaders Optional extra headers to be generated togetger with\n   *                     the msg.\n   * @retval size the size of the generated message, both the actual size\n   *              and the size of the uncompressed data.\n   * @return None\n   */\n  virtual void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom = false,\n      HTTPHeaderSize* size = nullptr,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) = 0;\n\n  virtual void generatePushPromise(folly::IOBufQueue& /* writeBuf */,\n                                   StreamID /* stream */,\n                                   const HTTPMessage& /* msg */,\n                                   StreamID /* assocStream */,\n                                   bool /* eom = false */,\n                                   HTTPHeaderSize* /* size = nullptr */) {\n  }\n\n  /**\n   * Write part of an egress message body.\n   *\n   * This will automatically generate a chunk header and footer around the data\n   * if necessary (e.g. you haven't manually sent a chunk header and the\n   * message should be chunked).\n   *\n   * @param padding Optionally add padding bytes to the body if possible\n   * @param eom implicitly generate the EOM marker with this body frame\n   *\n   * @return number of bytes written\n   */\n  virtual size_t generateBody(folly::IOBufQueue& writeBuf,\n                              StreamID stream,\n                              std::unique_ptr<folly::IOBuf> chain,\n                              folly::Optional<uint8_t> padding,\n                              bool eom) = 0;\n\n  /**\n   * Write egress message body with fixed length. This is used in DSR where the\n   * actual body bytes are held elsewhere and take an express path.\n   *\n   * @param padding Optionally add padding bytes to the body if possible\n   * @param eom implicitly generate the EOM marker with this body frame\n   *\n   * @return number of bytes written\n   */\n  virtual size_t generateBodyDSR(StreamID,\n                                 size_t,\n                                 folly::Optional<uint8_t>,\n                                 bool) {\n    LOG(FATAL) << __func__ << \" not supported on this codec\";\n    folly::assume_unreachable();\n  }\n\n  /**\n   * Write a body chunk header, if relevant.\n   */\n  virtual size_t generateChunkHeader(folly::IOBufQueue& writeBuf,\n                                     StreamID stream,\n                                     size_t length) = 0;\n\n  /**\n   * Write a body chunk terminator, if relevant.\n   */\n  virtual size_t generateChunkTerminator(folly::IOBufQueue& writeBuf,\n                                         StreamID stream) = 0;\n\n  /**\n   * Write the message trailers\n   * @return number of bytes written\n   */\n  virtual size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                                  StreamID stream,\n                                  const HTTPHeaders& trailers) = 0;\n\n  /**\n   * Write padding bytes, if the protocol supports it. This is separate from\n   * generateBody() because its padding is limited to 2^8 bytes, which would\n   * lead to inefficiencies with large padding.\n   *\n   * @return number of bytes written\n   */\n  virtual size_t generatePadding(folly::IOBufQueue& /* writeBuf */,\n                                 StreamID /* stream */,\n                                 uint16_t /* bytes */) = 0;\n\n  /**\n   * Generate any protocol framing needed to finalize an egress\n   * message. This method must be called to complete a stream.\n   *\n   * @return number of bytes written\n   */\n  virtual size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) = 0;\n\n  /**\n   * Generate any protocol framing needed to abort a stream.\n   * @return number of bytes written\n   */\n  virtual size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                                   StreamID stream,\n                                   ErrorCode code) = 0;\n\n  /**\n   * Generate any protocol framing needed to gracefully drain or abort a\n   * connection.\n   *\n   * Calling with lastStream = MaxStreamID and code = NO_ERROR will leave it to\n   * the codec to properly fill in the last stream ID.\n   *\n   * @return number of bytes written\n   */\n  virtual size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream = MaxStreamID,\n      ErrorCode code = ErrorCode::NO_ERROR,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) = 0;\n\n  // Generate an immediate goaway\n  virtual size_t generateImmediateGoaway(\n      folly::IOBufQueue& writeBuf,\n      ErrorCode code = ErrorCode::NO_ERROR,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) {\n    return generateGoaway(\n        writeBuf, getLastIncomingStreamID(), code, std::move(debugData));\n  }\n\n  /**\n   * If the protocol supports it, generate a ping message that the other\n   * side should respond to.\n   */\n  virtual size_t generatePingRequest(\n      folly::IOBufQueue& /* writeBuf */,\n      folly::Optional<uint64_t> /* data */ = folly::none) {\n    return 0;\n  }\n\n  /**\n   * Generate a reply to a ping message, if supported in the\n   * protocol implemented by the codec.\n   */\n  virtual size_t generatePingReply(folly::IOBufQueue& /* writeBuf */,\n                                   uint64_t /* data */) {\n    return 0;\n  }\n\n  /**\n   * Generate a settings message, if supported in the\n   * protocol implemented by the codec.\n   */\n  virtual size_t generateSettings(folly::IOBufQueue& /* writeBuf */) {\n    return 0;\n  }\n\n  /**\n   * Generate a settings ack message, if supported in the\n   * protocol implemented by the codec.\n   */\n  virtual size_t generateSettingsAck(folly::IOBufQueue& /* writeBuf */) {\n    return 0;\n  }\n\n  /*\n   * Generate a WINDOW_UPDATE message, if supported. The delta is the amount\n   * of ingress bytes we processed and freed from the current receive window.\n   * Returns the number of bytes written on the wire as a result of invoking\n   * this function.\n   */\n  virtual size_t generateWindowUpdate(folly::IOBufQueue& /* writeBuf */,\n                                      StreamID /* stream */,\n                                      uint32_t /* delta */) {\n    return 0;\n  }\n\n  /**\n   * Generate a PRIORITY_UPDATE frame, according to the new HTTP priority\n   * draft, if supported.\n   */\n  virtual size_t generatePriority(folly::IOBufQueue& /* writeBuf */,\n                                  StreamID /* stream */,\n                                  HTTPPriority /* priority */) {\n    return 0;\n  }\n\n  /**\n   * Generate a PUSH_PRIORITY_UPDATE frame for non push stream, according to\n   * the new HTTP priority draft, if supported.\n   */\n  virtual size_t generatePushPriority(folly::IOBufQueue& /* writeBuf */,\n                                      StreamID /* stream */,\n                                      HTTPPriority /* priority */) {\n    return 0;\n  }\n\n  /*\n   * Generate a CERTIFICATE_REQUEST message, if supported in the protocol\n   * implemented by the codec.\n   */\n  virtual size_t generateCertificateRequest(\n      folly::IOBufQueue& /* writeBuf */,\n      uint16_t /* requestId */,\n      std::unique_ptr<folly::IOBuf> /* chain */) {\n    return 0;\n  }\n\n  /*\n   * Generate a CERTIFICATE message, if supported in the protocol\n   * implemented by the codec.\n   */\n  virtual size_t generateCertificate(\n      folly::IOBufQueue& /* writeBuf */,\n      uint16_t /* certId */,\n      std::unique_ptr<folly::IOBuf> /* certData */) {\n    return 0;\n  }\n\n  /*\n   * The below interfaces need only be implemented if the codec supports\n   * settings\n   */\n  virtual HTTPSettings* getEgressSettings() {\n    return nullptr;\n  }\n\n  [[nodiscard]] virtual const HTTPSettings* getIngressSettings() const {\n    return nullptr;\n  }\n\n  /**\n   * This enables HTTP/2 style behavior during graceful shutdown that allows\n   * 2 GOAWAYs to be sent during shutdown.\n   */\n  virtual void enableDoubleGoawayDrain() {\n  }\n\n  virtual void disableDoubleGoawayDrain() {\n  }\n\n  /**\n   * set stats for the header codec, if the protocol supports header compression\n   */\n  virtual void setHeaderCodecStats(HeaderCodec::Stats* /* stats */) {\n  }\n\n  /**\n   * Get the identifier of the last stream started by the remote.\n   */\n  [[nodiscard]] virtual StreamID getLastIncomingStreamID() const {\n    return 0;\n  }\n\n  /**\n   * Get the default size of flow control windows for this protocol\n   */\n  [[nodiscard]] virtual uint32_t getDefaultWindowSize() const {\n    return 0;\n  }\n\n  /**\n   * Map the parent back to the priority, -1 if this doesn't make sense.\n   */\n  [[nodiscard]] virtual int8_t mapDependencyToPriority(\n      StreamID /* parent */) const {\n    return -1;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n\nnamespace proxygen {\n\nstd::unique_ptr<HTTPCodec> HTTPCodecFactory::getCodec(\n    CodecProtocol protocol,\n    TransportDirection direction,\n    bool strictValidation) {\n  // Static m\n  switch (protocol) {\n    case CodecProtocol::HTTP_1_1:\n      return std::make_unique<HTTP1xCodec>(\n          direction, /*force_1_1=*/false, strictValidation);\n    case CodecProtocol::HTTP_2: {\n      auto codec = std::make_unique<HTTP2Codec>(direction);\n      codec->setStrictValidation(strictValidation);\n      return codec;\n    }\n    case CodecProtocol::HQ:\n    case CodecProtocol::HTTP_3:\n    case CodecProtocol::HTTP_BINARY:\n    case CodecProtocol::TUNNEL_LITE:\n    default:\n      LOG(FATAL) << \"Unreachable\";\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <list>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n\nnamespace proxygen {\n\n/**\n * Factory for produces HTTPCodec objects.\n */\nclass HTTPCodecFactory {\n public:\n  struct HTTP1xCodecConfig {\n    bool forceHTTP1xCodecTo1_1{false};\n  };\n\n  struct HTTP2CodecConfig {\n    const HeaderIndexingStrategy* headerIndexingStrategy{nullptr};\n  };\n\n  struct CodecConfig {\n    // The debug setting controls header logging verbosity.\n    // 0 - do not log any header names or values to stderr\n    // 1 - log header names to stderr on certain errors\n    // 2 - log header names and values to stderr on certain errors\n    uint8_t debugLevel{0};\n    // Default to false for now to match existing behavior\n    bool strictValidation{false};\n    HTTP1xCodecConfig h1;\n    HTTP2CodecConfig h2;\n  };\n\n  HTTPCodecFactory() = default;\n  explicit HTTPCodecFactory(CodecConfig config) : defaultConfig_(config) {\n  }\n  virtual ~HTTPCodecFactory() = default;\n\n  /**\n   * Get a codec instance\n   */\n  virtual std::unique_ptr<HTTPCodec> getCodec(const std::string& protocolHint,\n                                              TransportDirection direction,\n                                              bool isTLS) = 0;\n\n  static std::unique_ptr<HTTPCodec> getCodec(CodecProtocol protocol,\n                                             TransportDirection direction,\n                                             bool strictValidation = false);\n\n  CodecConfig& getDefaultConfig() {\n    return defaultConfig_;\n  }\n\n  void setConfigFn(std::function<CodecConfig()> configFn) {\n    configFn_ = configFn;\n  }\n\n  bool useStrictValidation() {\n    return configFn_().strictValidation;\n  }\n\n protected:\n  CodecConfig defaultConfig_;\n  std::function<CodecConfig()> configFn_{[this] { return defaultConfig_; }};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n\nnamespace proxygen {\n\n// HTTPCodec::Callback methods\nvoid PassThroughHTTPCodecFilter::onMessageBegin(StreamID stream,\n                                                HTTPMessage* msg) {\n  callback_->onMessageBegin(stream, msg);\n}\n\nvoid PassThroughHTTPCodecFilter::onPushMessageBegin(StreamID stream,\n                                                    StreamID assocStream,\n                                                    HTTPMessage* msg) {\n  callback_->onPushMessageBegin(stream, assocStream, msg);\n}\n\nvoid PassThroughHTTPCodecFilter::onHeadersComplete(\n    StreamID stream, std::unique_ptr<HTTPMessage> msg) {\n  callback_->onHeadersComplete(stream, std::move(msg));\n}\n\nvoid PassThroughHTTPCodecFilter::onBody(StreamID stream,\n                                        std::unique_ptr<folly::IOBuf> chain,\n                                        uint16_t padding) {\n  callback_->onBody(stream, std::move(chain), padding);\n}\n\nvoid PassThroughHTTPCodecFilter::onChunkHeader(StreamID stream, size_t length) {\n  callback_->onChunkHeader(stream, length);\n}\n\nvoid PassThroughHTTPCodecFilter::onChunkComplete(StreamID stream) {\n  callback_->onChunkComplete(stream);\n}\n\nvoid PassThroughHTTPCodecFilter::onTrailersComplete(\n    StreamID stream, std::unique_ptr<HTTPHeaders> trailers) {\n  callback_->onTrailersComplete(stream, std::move(trailers));\n}\n\nvoid PassThroughHTTPCodecFilter::onMessageComplete(StreamID stream,\n                                                   bool upgrade) {\n  callback_->onMessageComplete(stream, upgrade);\n}\n\nvoid PassThroughHTTPCodecFilter::onFrameHeader(StreamID stream_id,\n                                               uint8_t flags,\n                                               uint64_t length,\n                                               uint64_t type,\n                                               uint16_t version) {\n  callback_->onFrameHeader(stream_id, flags, length, type, version);\n}\n\nvoid PassThroughHTTPCodecFilter::onError(StreamID stream,\n                                         const HTTPException& error,\n                                         bool newStream) {\n  callback_->onError(stream, error, newStream);\n}\n\nvoid PassThroughHTTPCodecFilter::onAbort(StreamID stream, ErrorCode code) {\n  callback_->onAbort(stream, code);\n}\n\nvoid PassThroughHTTPCodecFilter::onGoaway(\n    uint64_t lastGoodStreamID,\n    ErrorCode code,\n    std::unique_ptr<folly::IOBuf> debugData) {\n  callback_->onGoaway(lastGoodStreamID, code, std::move(debugData));\n}\n\nvoid PassThroughHTTPCodecFilter::onPingRequest(uint64_t data) {\n  callback_->onPingRequest(data);\n}\n\nvoid PassThroughHTTPCodecFilter::onPingReply(uint64_t data) {\n  callback_->onPingReply(data);\n}\n\nvoid PassThroughHTTPCodecFilter::onWindowUpdate(StreamID stream,\n                                                uint32_t amount) {\n  callback_->onWindowUpdate(stream, amount);\n}\n\nvoid PassThroughHTTPCodecFilter::onSettings(const SettingsList& settings) {\n  callback_->onSettings(settings);\n}\n\nvoid PassThroughHTTPCodecFilter::onSettingsAck() {\n  callback_->onSettingsAck();\n}\n\nvoid PassThroughHTTPCodecFilter::onPriority(StreamID stream,\n                                            const HTTPPriority& pri) {\n  callback_->onPriority(stream, pri);\n}\n\nvoid PassThroughHTTPCodecFilter::onPushPriority(StreamID stream,\n                                                const HTTPPriority& pri) {\n  callback_->onPushPriority(stream, pri);\n}\n\nvoid PassThroughHTTPCodecFilter::onGenerateFrameHeader(StreamID streamID,\n                                                       uint8_t type,\n                                                       uint64_t length,\n                                                       uint16_t version) {\n  callback_->onGenerateFrameHeader(streamID, length, type, version);\n}\n\nvoid PassThroughHTTPCodecFilter::onCertificateRequest(\n    uint16_t requestId, std::unique_ptr<folly::IOBuf> authRequest) {\n  callback_->onCertificateRequest(requestId, std::move(authRequest));\n}\n\nvoid PassThroughHTTPCodecFilter::onCertificate(\n    uint16_t certId, std::unique_ptr<folly::IOBuf> authenticator) {\n  callback_->onCertificate(certId, std::move(authenticator));\n}\n\nuint32_t PassThroughHTTPCodecFilter::numOutgoingStreams() const {\n  return callback_->numOutgoingStreams();\n}\n\nuint32_t PassThroughHTTPCodecFilter::numIncomingStreams() const {\n  return callback_->numIncomingStreams();\n}\n\n// PassThroughHTTPCodec methods\nCompressionInfo PassThroughHTTPCodecFilter::getCompressionInfo() const {\n  return call_->getCompressionInfo();\n}\n\nCodecProtocol PassThroughHTTPCodecFilter::getProtocol() const {\n  return call_->getProtocol();\n}\n\nconst std::string& PassThroughHTTPCodecFilter::getUserAgent() const {\n  return call_->getUserAgent();\n}\n\nTransportDirection PassThroughHTTPCodecFilter::getTransportDirection() const {\n  return call_->getTransportDirection();\n}\n\nbool PassThroughHTTPCodecFilter::supportsStreamFlowControl() const {\n  return call_->supportsStreamFlowControl();\n}\n\nbool PassThroughHTTPCodecFilter::supportsSessionFlowControl() const {\n  return call_->supportsSessionFlowControl();\n}\n\nHTTPCodec::StreamID PassThroughHTTPCodecFilter::createStream() {\n  return call_->createStream();\n}\n\nvoid PassThroughHTTPCodecFilter::setCallback(HTTPCodec::Callback* callback) {\n  setCallbackInternal(callback);\n}\n\nbool PassThroughHTTPCodecFilter::isBusy() const {\n  return call_->isBusy();\n}\n\nvoid PassThroughHTTPCodecFilter::setParserPaused(bool paused) {\n  call_->setParserPaused(paused);\n}\n\nbool PassThroughHTTPCodecFilter::isParserPaused() const {\n  return call_->isParserPaused();\n}\n\nsize_t PassThroughHTTPCodecFilter::onIngress(const folly::IOBuf& buf) {\n  return call_->onIngress(buf);\n}\n\nvoid PassThroughHTTPCodecFilter::onIngressEOF() {\n  call_->onIngressEOF();\n}\n\nbool PassThroughHTTPCodecFilter::onIngressUpgradeMessage(\n    const HTTPMessage& msg) {\n  return call_->onIngressUpgradeMessage(msg);\n}\n\nbool PassThroughHTTPCodecFilter::isReusable() const {\n  return call_->isReusable();\n}\n\nbool PassThroughHTTPCodecFilter::isWaitingToDrain() const {\n  return call_->isWaitingToDrain();\n}\n\nbool PassThroughHTTPCodecFilter::closeOnEgressComplete() const {\n  return call_->closeOnEgressComplete();\n}\n\nbool PassThroughHTTPCodecFilter::supportsParallelRequests() const {\n  return call_->supportsParallelRequests();\n}\n\nbool PassThroughHTTPCodecFilter::supportsPushTransactions() const {\n  return call_->supportsPushTransactions();\n}\n\nsize_t PassThroughHTTPCodecFilter::generateConnectionPreface(\n    folly::IOBufQueue& writeBuf) {\n  return call_->generateConnectionPreface(writeBuf);\n}\n\nvoid PassThroughHTTPCodecFilter::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  return call_->generateHeader(writeBuf, stream, msg, eom, size, extraHeaders);\n}\n\nvoid PassThroughHTTPCodecFilter::generatePushPromise(folly::IOBufQueue& buf,\n                                                     StreamID stream,\n                                                     const HTTPMessage& msg,\n                                                     StreamID assocStream,\n                                                     bool eom,\n                                                     HTTPHeaderSize* size) {\n  return call_->generatePushPromise(buf, stream, msg, assocStream, eom, size);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateBody(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    std::unique_ptr<folly::IOBuf> chain,\n    folly::Optional<uint8_t> padding,\n    bool eom) {\n  return call_->generateBody(writeBuf, stream, std::move(chain), padding, eom);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateBodyDSR(\n    StreamID stream,\n    size_t length,\n    folly::Optional<uint8_t> padding,\n    bool eom) {\n  return call_->generateBodyDSR(stream, length, padding, eom);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateChunkHeader(\n    folly::IOBufQueue& writeBuf, StreamID stream, size_t length) {\n  return call_->generateChunkHeader(writeBuf, stream, length);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateChunkTerminator(\n    folly::IOBufQueue& writeBuf, StreamID stream) {\n  return call_->generateChunkTerminator(writeBuf, stream);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateTrailers(\n    folly::IOBufQueue& writeBuf, StreamID stream, const HTTPHeaders& trailers) {\n  return call_->generateTrailers(writeBuf, stream, trailers);\n}\n\nsize_t PassThroughHTTPCodecFilter::generatePadding(folly::IOBufQueue& writeBuf,\n                                                   StreamID stream,\n                                                   uint16_t bytes) {\n  return call_->generatePadding(writeBuf, stream, bytes);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateEOM(folly::IOBufQueue& writeBuf,\n                                               StreamID stream) {\n  return call_->generateEOM(writeBuf, stream);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateRstStream(\n    folly::IOBufQueue& writeBuf, StreamID stream, ErrorCode code) {\n  return call_->generateRstStream(writeBuf, stream, code);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateGoaway(\n    folly::IOBufQueue& writeBuf,\n    StreamID lastStream,\n    ErrorCode statusCode,\n    std::unique_ptr<folly::IOBuf> debugData) {\n  return call_->generateGoaway(\n      writeBuf, lastStream, statusCode, std::move(debugData));\n}\n\nsize_t PassThroughHTTPCodecFilter::generatePingRequest(\n    folly::IOBufQueue& writeBuf, folly::Optional<uint64_t> data) {\n  return call_->generatePingRequest(writeBuf, data);\n}\n\nsize_t PassThroughHTTPCodecFilter::generatePingReply(\n    folly::IOBufQueue& writeBuf, uint64_t data) {\n  return call_->generatePingReply(writeBuf, data);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateSettings(folly::IOBufQueue& buf) {\n  return call_->generateSettings(buf);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateSettingsAck(folly::IOBufQueue& buf) {\n  return call_->generateSettingsAck(buf);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateWindowUpdate(folly::IOBufQueue& buf,\n                                                        StreamID stream,\n                                                        uint32_t delta) {\n  return call_->generateWindowUpdate(buf, stream, delta);\n}\n\nsize_t PassThroughHTTPCodecFilter::generatePriority(folly::IOBufQueue& writeBuf,\n                                                    StreamID streamId,\n                                                    HTTPPriority priority) {\n  return call_->generatePriority(writeBuf, streamId, priority);\n}\n\nsize_t PassThroughHTTPCodecFilter::generatePushPriority(\n    folly::IOBufQueue& writeBuf, StreamID pushId, HTTPPriority priority) {\n  return call_->generatePriority(writeBuf, pushId, priority);\n}\n\nsize_t PassThroughHTTPCodecFilter::generateCertificateRequest(\n    folly::IOBufQueue& writeBuf,\n    uint16_t requestId,\n    std::unique_ptr<folly::IOBuf> chain) {\n  return call_->generateCertificateRequest(\n      writeBuf, requestId, std::move(chain));\n}\n\nsize_t PassThroughHTTPCodecFilter::generateCertificate(\n    folly::IOBufQueue& writeBuf,\n    uint16_t certId,\n    std::unique_ptr<folly::IOBuf> certData) {\n  return call_->generateCertificate(writeBuf, certId, std::move(certData));\n}\n\nHTTPSettings* PassThroughHTTPCodecFilter::getEgressSettings() {\n  return call_->getEgressSettings();\n}\n\nconst HTTPSettings* PassThroughHTTPCodecFilter::getIngressSettings() const {\n  return call_->getIngressSettings();\n}\n\nvoid PassThroughHTTPCodecFilter::enableDoubleGoawayDrain() {\n  return call_->enableDoubleGoawayDrain();\n}\n\nvoid PassThroughHTTPCodecFilter::disableDoubleGoawayDrain() {\n  return call_->disableDoubleGoawayDrain();\n}\n\nvoid PassThroughHTTPCodecFilter::setHeaderCodecStats(\n    HeaderCodec::Stats* stats) {\n  call_->setHeaderCodecStats(stats);\n}\n\nHTTPCodec::StreamID PassThroughHTTPCodecFilter::getLastIncomingStreamID()\n    const {\n  return call_->getLastIncomingStreamID();\n}\n\nuint32_t PassThroughHTTPCodecFilter::getDefaultWindowSize() const {\n  return call_->getDefaultWindowSize();\n}\n\nint8_t PassThroughHTTPCodecFilter::mapDependencyToPriority(\n    StreamID parent) const {\n  return call_->mapDependencyToPriority(parent);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/utils/FilterChain.h>\n\nnamespace proxygen {\n\nusing HTTPCodecFilter = GenericFilter<HTTPCodec,\n                                      HTTPCodec::Callback,\n                                      &HTTPCodec::setCallback,\n                                      true>;\n\n/**\n * An implementation of HTTPCodecFilter that passes through all calls. This is\n * useful to subclass if you aren't interested in intercepting every function.\n * See HTTPCodec.h for documentation on these methods.\n */\nclass PassThroughHTTPCodecFilter : public HTTPCodecFilter {\n public:\n  /**\n   * By default, the filter gets both calls and callbacks\n   */\n  explicit PassThroughHTTPCodecFilter(bool calls = true, bool callbacks = true)\n      : HTTPCodecFilter(calls, callbacks) {\n  }\n\n  // HTTPCodec::Callback methods\n  void onMessageBegin(StreamID stream, HTTPMessage* msg) override;\n\n  void onPushMessageBegin(StreamID stream,\n                          StreamID assocStream,\n                          HTTPMessage* msg) override;\n\n  void onHeadersComplete(StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override;\n\n  void onBody(StreamID stream,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override;\n\n  void onChunkHeader(StreamID stream, size_t length) override;\n\n  void onChunkComplete(StreamID stream) override;\n\n  void onTrailersComplete(StreamID stream,\n                          std::unique_ptr<HTTPHeaders> trailers) override;\n\n  void onMessageComplete(StreamID stream, bool upgrade) override;\n\n  void onFrameHeader(StreamID stream_id,\n                     uint8_t flags,\n                     uint64_t length,\n                     uint64_t type,\n                     uint16_t version = 0) override;\n\n  void onError(StreamID stream,\n               const HTTPException& error,\n               bool newStream = false) override;\n\n  void onAbort(StreamID stream, ErrorCode code) override;\n\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  void onPingRequest(uint64_t data) override;\n\n  void onPingReply(uint64_t data) override;\n\n  void onWindowUpdate(StreamID stream, uint32_t amount) override;\n\n  void onSettings(const SettingsList& settings) override;\n\n  void onSettingsAck() override;\n\n  void onPriority(StreamID stream, const HTTPPriority& pri) override;\n\n  void onPushPriority(StreamID stream, const HTTPPriority& pri) override;\n\n  void onGenerateFrameHeader(StreamID streamID,\n                             uint8_t type,\n                             uint64_t length,\n                             uint16_t version) override;\n\n  void onCertificateRequest(uint16_t requestId,\n                            std::unique_ptr<folly::IOBuf> authRequest) override;\n\n  void onCertificate(uint16_t certId,\n                     std::unique_ptr<folly::IOBuf> authenticator) override;\n\n  [[nodiscard]] uint32_t numOutgoingStreams() const override;\n\n  [[nodiscard]] uint32_t numIncomingStreams() const override;\n\n  // HTTPCodec methods\n  [[nodiscard]] CompressionInfo getCompressionInfo() const override;\n\n  [[nodiscard]] CodecProtocol getProtocol() const override;\n\n  [[nodiscard]] const std::string& getUserAgent() const override;\n\n  [[nodiscard]] TransportDirection getTransportDirection() const override;\n\n  [[nodiscard]] bool supportsStreamFlowControl() const override;\n\n  [[nodiscard]] bool supportsSessionFlowControl() const override;\n\n  StreamID createStream() override;\n\n  void setCallback(HTTPCodec::Callback* callback) override;\n\n  [[nodiscard]] bool isBusy() const override;\n\n  void setParserPaused(bool paused) override;\n\n  [[nodiscard]] bool isParserPaused() const override;\n\n  size_t onIngress(const folly::IOBuf& buf) override;\n\n  void onIngressEOF() override;\n\n  bool onIngressUpgradeMessage(const HTTPMessage& msg) override;\n\n  [[nodiscard]] bool isReusable() const override;\n\n  [[nodiscard]] bool isWaitingToDrain() const override;\n\n  [[nodiscard]] bool closeOnEgressComplete() const override;\n\n  [[nodiscard]] bool supportsParallelRequests() const override;\n\n  [[nodiscard]] bool supportsPushTransactions() const override;\n\n  size_t generateConnectionPreface(folly::IOBufQueue& writeBuf) override;\n\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom,\n      HTTPHeaderSize* size,\n      const folly::Optional<HTTPHeaders>& extraHeaders) override;\n\n  void generatePushPromise(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           const HTTPMessage& msg,\n                           StreamID assocStream,\n                           bool eom,\n                           HTTPHeaderSize* size) override;\n\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n\n  size_t generateBodyDSR(StreamID stream,\n                         size_t length,\n                         folly::Optional<uint8_t> padding,\n                         bool eom) override;\n\n  size_t generateChunkHeader(folly::IOBufQueue& writeBuf,\n                             StreamID stream,\n                             size_t length) override;\n\n  size_t generateChunkTerminator(folly::IOBufQueue& writeBuf,\n                                 StreamID stream) override;\n\n  size_t generateTrailers(folly::IOBufQueue& writeBuf,\n                          StreamID stream,\n                          const HTTPHeaders& trailers) override;\n\n  size_t generatePadding(folly::IOBufQueue& writeBuf,\n                         StreamID stream,\n                         uint16_t bytes) override;\n\n  size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) override;\n\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           ErrorCode statusCode) override;\n\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream,\n      ErrorCode statusCode,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  size_t generatePingRequest(\n      folly::IOBufQueue& writeBuf,\n      folly::Optional<uint64_t> data = folly::none) override;\n\n  size_t generatePingReply(folly::IOBufQueue& writeBuf, uint64_t data) override;\n\n  size_t generateSettings(folly::IOBufQueue& writeBuf) override;\n\n  size_t generateSettingsAck(folly::IOBufQueue& writeBuf) override;\n\n  size_t generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                              StreamID stream,\n                              uint32_t delta) override;\n\n  size_t generatePriority(folly::IOBufQueue& writeBuf,\n                          StreamID streamId,\n                          HTTPPriority priority) override;\n\n  size_t generatePushPriority(folly::IOBufQueue& writeBuf,\n                              StreamID pushId,\n                              HTTPPriority priority) override;\n\n  size_t generateCertificateRequest(\n      folly::IOBufQueue& writeBuf,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> chain) override;\n\n  size_t generateCertificate(folly::IOBufQueue& writeBuf,\n                             uint16_t certId,\n                             std::unique_ptr<folly::IOBuf> certData) override;\n\n  HTTPSettings* getEgressSettings() override;\n\n  [[nodiscard]] const HTTPSettings* getIngressSettings() const override;\n\n  void setHeaderCodecStats(HeaderCodec::Stats* stats) override;\n\n  void enableDoubleGoawayDrain() override;\n\n  void disableDoubleGoawayDrain() override;\n\n  [[nodiscard]] HTTPCodec::StreamID getLastIncomingStreamID() const override;\n\n  [[nodiscard]] uint32_t getDefaultWindowSize() const override;\n\n  [[nodiscard]] int8_t mapDependencyToPriority(StreamID parent) const override;\n};\n\nusing HTTPCodecFilterChain = FilterChain<HTTPCodec,\n                                         HTTPCodec::Callback,\n                                         PassThroughHTTPCodecFilter,\n                                         &HTTPCodec::setCallback,\n                                         true>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecPrinter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPCodecPrinter.h>\n\n#include <iostream>\n\nnamespace proxygen {\n\nvoid HTTPCodecPrinter::onFrameHeader(StreamID stream_id,\n                                     uint8_t flags,\n                                     uint64_t length,\n                                     uint64_t type,\n                                     uint16_t version) {\n  switch (call_->getProtocol()) {\n    case CodecProtocol::HTTP_2:\n      std::cout << \"[FRAME] stream_id=\" << stream_id << \", flags=\" << std::hex\n                << folly::to<unsigned int>(flags) << std::dec\n                << \", length=\" << length << \", type=\" << type << std::endl;\n      break;\n    case CodecProtocol::HTTP_1_1:\n    default:\n      break;\n  }\n  callback_->onFrameHeader(stream_id, flags, length, type, version);\n}\n\nvoid HTTPCodecPrinter::onError(StreamID stream,\n                               const HTTPException& error,\n                               bool newStream) {\n  std::cout << \"[Exception] \" << error.what() << std::endl;\n  callback_->onError(stream, error, newStream);\n}\n\nvoid HTTPCodecPrinter::onBody(StreamID stream,\n                              std::unique_ptr<folly::IOBuf> chain,\n                              uint16_t padding) {\n  std::cout << \"DataChunk: stream_id=\" << stream\n            << \", length=\" << chain->length() << \", padding=\" << padding\n            << std::endl;\n  callback_->onBody(stream, std::move(chain), padding);\n}\n\nvoid HTTPCodecPrinter::onMessageComplete(StreamID stream, bool upgrade) {\n  std::cout << \"DataComplete: stream_id=\" << stream << std::endl;\n  callback_->onMessageComplete(stream, upgrade);\n}\n\nvoid HTTPCodecPrinter::onHeadersComplete(StreamID stream,\n                                         std::unique_ptr<HTTPMessage> msg) {\n  std::cout << \"HEADERS: stream_id=\" << stream\n            << \", numHeaders=\" << msg->getHeaders().size() << std::endl;\n  if (msg->isRequest()) {\n    std::cout << \"Method= \" << msg->getMethodString() << std::endl;\n    std::cout << \"Path= \" << msg->getPathAsStringPiece() << std::endl;\n    std::cout << \"URL=\" << msg->getURL() << std::endl;\n  } else {\n    std::cout << \"Status=\" << msg->getStatusCode() << std::endl;\n  }\n  msg->getHeaders().forEach(\n      [&](const std::string& header, const std::string& val) {\n        std::cout << \"\\t\" << header << \": \" << val << std::endl;\n      });\n  callback_->onHeadersComplete(stream, std::move(msg));\n}\n\nvoid HTTPCodecPrinter::onAbort(StreamID stream, ErrorCode code) {\n  std::cout << \"RST_STREAM: stream_id=\" << stream\n            << \", error=\" << getErrorCodeString(code) << std::endl;\n  callback_->onAbort(stream, code);\n}\n\nvoid HTTPCodecPrinter::onGoaway(uint64_t lastGoodStream,\n                                ErrorCode code,\n                                std::unique_ptr<folly::IOBuf> debugData) {\n  std::string debugInfo =\n      (debugData) ? \", debug info=\" + debugData->to<std::string>() : \"\";\n  std::cout << \"GOAWAY: lastGoodStream=\" << lastGoodStream\n            << \", error=\" << getErrorCodeString(code) << debugInfo << std::endl;\n  callback_->onGoaway(lastGoodStream, code, std::move(debugData));\n}\n\nvoid HTTPCodecPrinter::onWindowUpdate(StreamID stream, uint32_t amount) {\n  std::cout << \"WINDOW_UPDATE: stream_id=\" << stream\n            << \", delta_window_size=\" << amount << std::endl;\n  callback_->onWindowUpdate(stream, amount);\n}\n\nvoid HTTPCodecPrinter::onSettings(const SettingsList& settings) {\n  std::cout << \"SETTINGS: num=\" << settings.size() << std::endl;\n  for (const auto& setting : settings) {\n    std::cout << \"\\tid=\" << folly::to<uint16_t>(setting.id)\n              << \", value=\" << setting.value << std::endl;\n  }\n  callback_->onSettings(settings);\n}\n\nvoid HTTPCodecPrinter::onSettingsAck() {\n  std::cout << \"SETTINGS_ACK\" << std::endl;\n  callback_->onSettingsAck();\n}\n\nvoid HTTPCodecPrinter::onPingRequest(uint64_t unique_id) {\n  printPing(unique_id);\n  callback_->onPingRequest(unique_id);\n}\n\nvoid HTTPCodecPrinter::onPingReply(uint64_t unique_id) {\n  printPing(unique_id);\n  callback_->onPingReply(unique_id);\n}\n\nvoid HTTPCodecPrinter::printPing(uint64_t unique_id) {\n  std::cout << \"PING: unique_id=\" << unique_id << std::endl;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPCodecPrinter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n\nnamespace proxygen {\n\n/**\n * This class enforces certain higher-level HTTP semantics. It does not enforce\n * conditions that require state to decide. That is, this class is stateless and\n * only examines the calls and callbacks that go through it.\n */\n\nclass HTTPCodecPrinter : public PassThroughHTTPCodecFilter {\n public:\n  /*\n   * Called from HTTP2Codec::parseIngress()\n   *             HTTP2Codec::onIngress()\n   * when HTTP frame headers are parsed\n   */\n  void onFrameHeader(StreamID stream_id,\n                     uint8_t flags,\n                     uint64_t length,\n                     uint64_t type,\n                     uint16_t version = 0) override;\n\n  /*\n   * Called from HTTP2Codec::failSession()\n   *             HTTP2Codec::checkConnectionError()\n   */\n  void onError(StreamID stream,\n               const HTTPException& error,\n               bool newStream = false) override;\n\n  /*\n   * Called from HTTP2Codec::parseIngress()\n   *             HTTP2Codec::parseData()\n   */\n  void onBody(StreamID stream,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override;\n\n  /*\n   * Called from HTTP2Codec::parseIngress()\n   *             HTTP2Codec::handleEndStream()\n   */\n  void onMessageComplete(StreamID stream, bool upgrade) override;\n\n  /*\n   * Called from HTTP2Codec::onSynCommon()\n   *             HTTP2Codec::HTTP2Codec::parseHeadersImpl()\n   */\n  void onHeadersComplete(StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override;\n\n  /*\n   * Called from HTTP2Codec::onRstStream()\n   *             HTTP2Codec::parseRstStream()\n   */\n  void onAbort(StreamID stream, ErrorCode code) override;\n\n  /*\n   * Called from HTTP2Codec::onWindowUpdate() with different arguments\n   *             HTTP2Codec::parseWindowUpdate()\n   */\n  void onWindowUpdate(StreamID stream, uint32_t amount) override;\n\n  /*\n   * Called from HTTP2Codec::onSettings()\n   *             HTTP2Codec::parseSettings()\n   */\n  void onSettings(const SettingsList& settings) override;\n\n  /*\n   * Called from HTTP2Codec::parseSettings()\n   */\n  void onSettingsAck() override;\n\n  /*\n   * Called from HTTP2Codec::onGoaway() with different arguments\n   *             HTTP2Codec::parseGoaway()\n   */\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  /*\n   * Called from HTTP2Codec::onPing()\n   *             HTTP2Codec::parsePing()\n   */\n  void onPingRequest(uint64_t data) override;\n\n  /*\n   * Called from HTTP2Codec::onPing()\n   *             HTTP2Codec::parsePing()\n   */\n  void onPingReply(uint64_t data) override;\n\n protected:\n  void printPing(uint64_t data);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPParallelCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPParallelCodec.h>\n\n#include <glog/logging.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n\nnamespace proxygen {\n\nHTTPParallelCodec::HTTPParallelCodec(TransportDirection direction)\n    : transportDirection_(direction), sessionClosing_(ClosingState::OPEN) {\n  switch (transportDirection_) {\n    case TransportDirection::DOWNSTREAM:\n      nextEgressStreamID_ = 2;\n      break;\n    case TransportDirection::UPSTREAM:\n      nextEgressStreamID_ = 1;\n      break;\n    default:\n      LOG(FATAL) << \"Unknown transport direction.\";\n  }\n}\n\nHTTPCodec::StreamID HTTPParallelCodec::createStream() {\n  auto ret = nextEgressStreamID_;\n  nextEgressStreamID_ += 2;\n  return ret;\n}\n\nbool HTTPParallelCodec::isWaitingToDrain() const {\n  return sessionClosing_ == ClosingState::OPEN ||\n         sessionClosing_ == ClosingState::FIRST_GOAWAY_SENT;\n}\n\nbool HTTPParallelCodec::isReusable() const {\n  return (sessionClosing_ == ClosingState::OPEN ||\n          sessionClosing_ == ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED ||\n          (transportDirection_ == TransportDirection::DOWNSTREAM &&\n           isWaitingToDrain())) &&\n         (ingressGoawayAck_ == std::numeric_limits<uint32_t>::max()) &&\n         (nextEgressStreamID_ <= std::numeric_limits<int32_t>::max() - 2);\n}\n\nvoid HTTPParallelCodec::enableDoubleGoawayDrain() {\n  if (sessionClosing_ == ClosingState::OPEN) {\n    sessionClosing_ = ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED;\n  } else {\n    VLOG(3) << \"Cannot enable double goaway because the session is already \"\n               \"draining or closed\";\n  }\n}\n\nvoid HTTPParallelCodec::disableDoubleGoawayDrain() {\n  if (sessionClosing_ == ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED) {\n    sessionClosing_ = ClosingState::OPEN;\n  } else {\n    VLOG(3) << \"Cannot enable double goaway because the session is already \"\n               \"draining or closed, or never enabled GRACEFUL_DRAIN\";\n  }\n}\n\nbool HTTPParallelCodec::onIngressUpgradeMessage(const HTTPMessage& /*msg*/) {\n  if (transportDirection_ == TransportDirection::DOWNSTREAM) {\n    lastStreamID_ = 1;\n  }\n  return true;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPParallelCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <bitset>\n#include <deque>\n#include <folly/Optional.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace folly::io {\nclass Cursor;\n} // namespace folly::io\n\nnamespace proxygen {\n\n/**\n * An implementation of common codec functionality used for multiple\n * parallel stream downloads. Currently shared by SPDY and HTTP/2\n */\nclass HTTPParallelCodec : public HTTPCodec {\n public:\n  explicit HTTPParallelCodec(TransportDirection direction);\n\n  [[nodiscard]] TransportDirection getTransportDirection() const override {\n    return transportDirection_;\n  }\n\n  StreamID createStream() override;\n  [[nodiscard]] bool isBusy() const override {\n    return false;\n  }\n  [[nodiscard]] bool supportsStreamFlowControl() const override {\n    return true;\n  }\n  [[nodiscard]] bool supportsSessionFlowControl() const override {\n    return true;\n  }\n  [[nodiscard]] bool supportsParallelRequests() const override {\n    return true;\n  }\n  [[nodiscard]] bool closeOnEgressComplete() const override {\n    return false;\n  }\n  void setCallback(Callback* callback) override {\n    callback_ = callback;\n  }\n  void setParserPaused(bool /* paused */) override {\n  }\n  [[nodiscard]] bool isParserPaused() const override {\n    return false;\n  }\n  void onIngressEOF() override {\n  }\n  [[nodiscard]] bool isReusable() const override;\n  [[nodiscard]] bool isWaitingToDrain() const override;\n  [[nodiscard]] StreamID getLastIncomingStreamID() const override {\n    return lastStreamID_;\n  }\n  void enableDoubleGoawayDrain() override;\n  void disableDoubleGoawayDrain() override;\n\n  bool onIngressUpgradeMessage(const HTTPMessage& msg) override;\n\n  void setNextEgressStreamId(StreamID nextEgressStreamID) {\n    if (nextEgressStreamID > nextEgressStreamID_ &&\n        (nextEgressStreamID & 0x1) == (nextEgressStreamID_ & 0x1) &&\n        nextEgressStreamID_ < std::numeric_limits<int32_t>::max()) {\n      nextEgressStreamID_ = nextEgressStreamID;\n    }\n  }\n\n  [[nodiscard]] bool isInitiatedStream(StreamID stream) const {\n    return isInitiatedStream(transportDirection_, stream);\n  }\n\n  static bool isInitiatedStream(TransportDirection direction, StreamID stream) {\n    bool odd = stream & 0x01;\n    bool upstream = (direction == TransportDirection::UPSTREAM);\n    return (odd && upstream) || (!odd && !upstream);\n  }\n\n  [[nodiscard]] bool isStreamIngressEgressAllowed(StreamID stream) const {\n    bool isInitiated = isInitiatedStream(stream);\n    return (isInitiated && stream <= ingressGoawayAck_) ||\n           (!isInitiated && stream <= egressGoawayAck_);\n  }\n\n protected:\n  TransportDirection transportDirection_;\n  StreamID nextEgressStreamID_;\n  StreamID lastStreamID_{0};\n  HTTPCodec::Callback* callback_{nullptr};\n  StreamID ingressGoawayAck_{std::numeric_limits<uint32_t>::max()};\n  StreamID egressGoawayAck_{std::numeric_limits<uint32_t>::max()};\n  std::string goawayErrorMessage_;\n\n  enum ClosingState {\n    OPEN = 0,\n    OPEN_WITH_GRACEFUL_DRAIN_ENABLED = 1,\n    FIRST_GOAWAY_SENT = 2,\n    CLOSED = 4 // HTTP2 only\n  } sessionClosing_;\n\n  template <typename T, typename... Args>\n  bool deliverCallbackIfAllowed(T callbackFn,\n                                char const* cbName,\n                                StreamID stream,\n                                Args&&... args) {\n    if (isStreamIngressEgressAllowed(stream)) {\n      if (callback_) {\n        (*callback_.*callbackFn)(stream, std::forward<Args>(args)...);\n      }\n      return true;\n    } else {\n      VLOG(3) << \"Suppressing \" << cbName << \" for stream=\" << stream\n              << \" egressGoawayAck_=\" << egressGoawayAck_;\n    }\n    return false;\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPRequestVerifier.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/HeaderConstants.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\nnamespace proxygen {\n\nclass HTTPRequestVerifier {\n public:\n  explicit HTTPRequestVerifier() = default;\n\n  void reset(HTTPMessage* msg) {\n    msg_ = msg;\n    error = \"\";\n    hasMethod_ = false;\n    hasPath_ = false;\n    hasScheme_ = false;\n    hasAuthority_ = false;\n    hasUpgradeProtocol_ = false;\n    hasValidationError_ = false;\n  }\n\n  bool setMethod(folly::StringPiece method) {\n    if (hasMethod_) {\n      error = \"Duplicate method\";\n      return false;\n    }\n    if (!CodecUtil::validateMethod(method)) {\n      hasValidationError_ = true;\n      error = folly::to<std::string>(\"Invalid method: \", method);\n      return false;\n    }\n    hasMethod_ = true;\n    assert(msg_ != nullptr);\n    msg_->setMethod(method);\n    return true;\n  }\n\n  bool setPath(folly::StringPiece path,\n               bool strictValidation,\n               bool allowEmptyPath) {\n    if (hasPath_) {\n      error = \"Duplicate path\";\n      return false;\n    }\n    if (!CodecUtil::validateURL(path,\n                                strictValidation\n                                    ? URLValidateMode::STRICT\n                                    : URLValidateMode::STRICT_COMPAT)) {\n      hasValidationError_ = true;\n      error = folly::to<std::string>(\"Invalid url: \", path);\n      return false;\n    }\n    hasPath_ = true;\n    assert(msg_ != nullptr);\n    // Relax strictValidation here if empty paths are allowed and it's empty\n    strictValidation &= !(allowEmptyPath && path.empty());\n    auto parseUrl = msg_->setURL(path.str(), strictValidation);\n    if (strictValidation && !parseUrl.valid()) {\n      error = folly::to<std::string>(\"Invalid url: \", path);\n      return false;\n    }\n    return !strictValidation || parseUrl.valid();\n  }\n\n  bool setScheme(folly::StringPiece scheme) {\n    if (hasScheme_) {\n      error = \"Duplicate scheme\";\n      return false;\n    }\n    // This just checks for alpha chars\n    if (!CodecUtil::validateScheme(scheme)) {\n      hasValidationError_ = true;\n      error = folly::to<std::string>(\"Invalid scheme: \", scheme);\n      return false;\n    }\n    hasScheme_ = true;\n    // TODO support non http/https schemes\n    if (scheme == headers::kHttps) {\n      assert(msg_ != nullptr);\n      msg_->setSecure(true);\n    } else if (scheme == headers::kMasque) {\n      assert(msg_ != nullptr);\n      msg_->setMasque();\n    }\n    return true;\n  }\n\n  bool setAuthority(folly::StringPiece authority,\n                    bool validate,\n                    bool strictValidation) {\n    if (hasAuthority_) {\n      error = \"Duplicate authority\";\n      return false;\n    }\n    if (validate &&\n        !CodecUtil::validateHeaderValue(\n            authority,\n            strictValidation ? CodecUtil::CtlEscapeMode::STRICT\n                             : CodecUtil::CtlEscapeMode::STRICT_COMPAT)) {\n      hasValidationError_ = true;\n      error = folly::to<std::string>(\"Invalid authority: \", authority);\n      return false;\n    }\n    hasAuthority_ = true;\n    assert(msg_ != nullptr);\n    msg_->getHeaders().add(HTTP_HEADER_HOST, authority);\n    return true;\n  }\n\n  bool setUpgradeProtocol(folly::StringPiece protocol, bool strictValidation) {\n    if (hasUpgradeProtocol_) {\n      error = \"Duplicate protocol\";\n      return false;\n    }\n    if (strictValidation && !CodecUtil::validateHeaderValue(\n                                protocol, CodecUtil::CtlEscapeMode::STRICT)) {\n      hasValidationError_ = true;\n      error = folly::to<std::string>(\"Invalid protocol: \", protocol);\n      return false;\n    }\n    setHasUpgradeProtocol(true);\n    msg_->setUpgradeProtocol(folly::to<std::string>(protocol));\n    return true;\n  }\n\n  bool validate() {\n    if (error.size()) {\n      return false;\n    }\n    if (msg_->getMethod() == HTTPMethod::CONNECT) {\n      if ((!hasUpgradeProtocol_ &&\n           (!hasMethod_ || !hasAuthority_ || hasScheme_ || hasPath_)) ||\n          (hasUpgradeProtocol_ && (!hasScheme_ || !hasPath_))) {\n        error = folly::to<std::string>(\"Malformed CONNECT request m/a/s/pa/pr=\",\n                                       hasMethod_,\n                                       hasAuthority_,\n                                       hasScheme_,\n                                       hasPath_,\n                                       hasUpgradeProtocol_);\n      }\n    } else if (hasUpgradeProtocol_ || !hasMethod_ || !hasScheme_ || !hasPath_) {\n      error = folly::to<std::string>(\"Malformed request m/a/s/pa/pr=\",\n                                     hasMethod_,\n                                     hasAuthority_,\n                                     hasScheme_,\n                                     hasPath_,\n                                     hasUpgradeProtocol_);\n    }\n    return error.empty();\n  }\n\n  void setMessage(HTTPMessage* msg) {\n    msg_ = msg;\n  }\n\n  void setHasMethod(bool hasMethod) {\n    hasMethod_ = hasMethod;\n  }\n\n  void setHasPath(bool hasPath) {\n    hasPath_ = hasPath;\n  }\n\n  void setHasScheme(bool hasScheme) {\n    hasScheme_ = hasScheme;\n  }\n\n  void setHasAuthority(bool hasAuthority) {\n    hasAuthority_ = hasAuthority;\n  }\n\n  [[nodiscard]] bool hasAuthority() const {\n    return hasAuthority_;\n  }\n\n  void setHasUpgradeProtocol(bool val) {\n    hasUpgradeProtocol_ = val;\n  }\n\n  bool hasUpgradeProtocol() {\n    return hasUpgradeProtocol_;\n  }\n\n  bool hasValidationError() {\n    return hasValidationError_;\n  }\n\n  std::string error;\n\n private:\n  HTTPMessage* msg_{nullptr};\n  bool hasMethod_{false};\n  bool hasPath_{false};\n  bool hasScheme_{false};\n  bool hasAuthority_{false};\n  bool hasUpgradeProtocol_{false};\n  bool hasValidationError_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPSettings.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n\n#include <algorithm>\n\nnamespace proxygen {\n\nvoid HTTPSettings::setSetting(SettingsId id, SettingsValue val) {\n  auto iter = getSettingIter(id);\n  if (iter != settings_.end()) {\n    (*iter).value = val;\n  } else {\n    settings_.emplace_back(id, val);\n  }\n}\n\nvoid HTTPSettings::unsetSetting(SettingsId id) {\n  auto iter = getSettingIter(id);\n  if (iter != settings_.end()) {\n    *iter = settings_.back();\n    settings_.pop_back();\n  }\n}\n\nconst HTTPSetting* HTTPSettings::getSetting(SettingsId id) const {\n  auto iter = getSettingConstIter(id);\n  if (iter != settings_.end()) {\n    return &(*iter);\n  } else {\n    return nullptr;\n  }\n}\n\nSettingsValue HTTPSettings::getSetting(SettingsId id,\n                                       SettingsValue defaultValue) const {\n  auto iter = getSettingConstIter(id);\n  if (iter != settings_.end()) {\n    return (*iter).value;\n  } else {\n    return defaultValue;\n  }\n}\n\nbool HTTPSettings::setIfNotPresent(SettingsId id, SettingsValue val) noexcept {\n  if (auto* setting = getSetting(id); !setting) {\n    setSetting(id, val);\n    return true;\n  }\n  return false;\n}\n\nstd::vector<HTTPSetting>::iterator HTTPSettings::getSettingIter(SettingsId id) {\n  return std::find_if(settings_.begin(),\n                      settings_.end(),\n                      [&](HTTPSetting const& s) { return s.id == id; });\n}\n\nstd::vector<HTTPSetting>::const_iterator HTTPSettings::getSettingConstIter(\n    SettingsId id) const {\n  return std::find_if(settings_.begin(),\n                      settings_.end(),\n                      [&](HTTPSetting const& s) { return s.id == id; });\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HTTPSettings.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <initializer_list>\n#include <proxygen/lib/http/codec/SettingsId.h>\n#include <vector>\n\n/*\n * HTTPSettings are stored in an underlying vector, presumably to limit the\n * amount of memory required.  Access various settings thus generally involves\n * iterating over available entries.  Removing entries does not require any\n * shifts.\n */\n\nnamespace proxygen {\n\nusing SettingsValue = uint64_t;\n\nstruct HTTPSetting {\n  HTTPSetting(SettingsId i, SettingsValue v) : id(i), value(v) {\n  }\n\n  SettingsId id;\n  SettingsValue value;\n};\n\nclass HTTPSettings {\n public:\n  // HTTP/2 Defaults\n  HTTPSettings()\n      : settings_({{SettingsId::HEADER_TABLE_SIZE, 4096},\n                   {SettingsId::ENABLE_PUSH, 1},\n                   {SettingsId::MAX_FRAME_SIZE, 16384}}) {\n  }\n  explicit HTTPSettings(\n      const std::initializer_list<SettingPair>& initialSettings) {\n    settings_.reserve(initialSettings.size());\n    // TODO: setSetting involves looping over all settings so the below actually\n    // models as O(n^2) which is pretty bad.  Can't change it outright without\n    // making sure we handle duplicates (same setting id)\n    for (auto& setting : initialSettings) {\n      setSetting(setting.first, setting.second);\n    }\n  }\n  void setSetting(SettingsId id, SettingsValue val);\n  // sets id if id is not currently present; returns true if was not initially\n  // present\n  bool setIfNotPresent(SettingsId id, SettingsValue val) noexcept;\n  void unsetSetting(SettingsId id);\n  [[nodiscard]] const HTTPSetting* getSetting(SettingsId id) const;\n  [[nodiscard]] SettingsValue getSetting(SettingsId id,\n                                         SettingsValue defaultVal) const;\n  // Note: this does not count disabled settings\n  [[nodiscard]] std::size_t getNumSettings() const {\n    return settings_.size();\n  }\n  const std::vector<HTTPSetting>& getAllSettings() {\n    return settings_;\n  }\n  void clearSettings() {\n    settings_.clear();\n  }\n\n private:\n  // Returns the specified type of iterator to the setting associated with the\n  // specified id\n  std::vector<HTTPSetting>::iterator getSettingIter(SettingsId id);\n  [[nodiscard]] std::vector<HTTPSetting>::const_iterator getSettingConstIter(\n      SettingsId id) const;\n\n  // TODO: evaluate whether using a list or default initializing vector with\n  // all settings so that size == capacity (else on push_backs capacity is\n  // likely to be > size)\n  std::vector<HTTPSetting> settings_;\n};\n\nusing SettingsList = std::vector<HTTPSetting>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HeaderDecodeInfo.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HeaderDecodeInfo.h>\n\n#include <folly/Conv.h>\n#include <folly/String.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\nusing std::string;\n\nnamespace proxygen {\n\nbool HeaderDecodeInfo::onHeader(const HPACKHeaderName& name,\n                                const folly::fbstring& value) {\n  // Refuse decoding other headers if an error is already found\n  if (decodeError != HPACK::DecodeError::NONE || !parsingError.empty()) {\n    VLOG(4) << \"Ignoring header=\" << name << \" value=\" << value\n            << \" due to parser error=\" << parsingError;\n    return true;\n  }\n  DVLOG(5) << \"Processing header=\" << name << \" value=\" << value;\n  auto headerCode = name.getHeaderCode();\n  folly::StringPiece nameSp(name.get());\n  folly::StringPiece valueSp(value);\n  auto& headers = msg->getHeaders();\n\n  if (nameSp.startsWith(':')) {\n    pseudoHeaderSeen_ = true;\n    if (regularHeaderSeen_) {\n      parsingError = folly::to<string>(\"Illegal pseudo header name=\", nameSp);\n      return false;\n    }\n    if (isRequest_) {\n      bool ok = false;\n      switch (headerCode) {\n        case HTTP_HEADER_COLON_METHOD:\n          ok = verifier.setMethod(valueSp);\n          if (verifier.hasValidationError()) {\n            proxygenError = kErrorHeaderContentValidation;\n          }\n          break;\n        case HTTP_HEADER_COLON_SCHEME:\n          ok = verifier.setScheme(valueSp);\n          if (verifier.hasValidationError()) {\n            proxygenError = kErrorHeaderContentValidation;\n          }\n          break;\n        case HTTP_HEADER_COLON_AUTHORITY:\n          ok = verifier.setAuthority(valueSp, validate_, strictValidation_);\n          if (verifier.hasValidationError()) {\n            proxygenError = kErrorHeaderContentValidation;\n          }\n          break;\n        case HTTP_HEADER_COLON_PATH:\n          ok = verifier.setPath(valueSp, strictValidation_, allowEmptyPath_);\n          if (verifier.hasValidationError()) {\n            proxygenError = kErrorHeaderContentValidation;\n          }\n          break;\n        case HTTP_HEADER_COLON_PROTOCOL:\n          ok = verifier.setUpgradeProtocol(valueSp, strictValidation_);\n          if (verifier.hasValidationError()) {\n            proxygenError = kErrorHeaderContentValidation;\n          }\n          break;\n        default:\n          parsingError = folly::to<string>(\"Invalid req header name=\", nameSp);\n          return false;\n      }\n      if (!ok) {\n        return false;\n      }\n    } else {\n      if (headerCode == HTTP_HEADER_COLON_STATUS) {\n        if (hasStatus_) {\n          parsingError = string(\"Duplicate status\");\n          return false;\n        }\n        hasStatus_ = true;\n        int32_t code = -1;\n        folly::tryTo<int32_t>(valueSp).then(\n            [&code](int32_t num) { code = num; });\n        if (code >= 100 && code <= 999) {\n          msg->setStatusCode(code);\n          msg->setStatusMessage(HTTPMessage::getDefaultReason(code));\n        } else {\n          parsingError = folly::to<string>(\"Malformed status code=\", valueSp);\n          return false;\n        }\n      } else {\n        parsingError = folly::to<string>(\"Invalid resp header name=\", nameSp);\n        return false;\n      }\n    }\n  } else {\n    regularHeaderSeen_ = true;\n    switch (headerCode) {\n      case HTTP_HEADER_CONNECTION:\n        parsingError = string(\"HTTP/2 Message with Connection header\");\n        return false;\n      case HTTP_HEADER_CONTENT_LENGTH: {\n        const auto cl = headers.getSingleOrNullptr(HTTP_HEADER_CONTENT_LENGTH);\n        if (cl) {\n          bool ok = *cl == valueSp;\n          if (!ok) {\n            parsingError = string(\"Multiple content-length headers\");\n          }\n          return ok; // skips adding if already present and equal\n        }\n        break;\n      }\n      case HTTP_HEADER_HOST: {\n        if (verifier.hasAuthority()) { // HTTP_HEADER_HOST already present\n          bool ok = headers.getSingleOrEmpty(HTTP_HEADER_HOST) == valueSp;\n          if (!ok) {\n            parsingError = \":authority/Host header mismatch\";\n          }\n          return ok; // skips adding if already present and equal\n        }\n        break;\n      }\n      default:\n        // no op\n        break;\n    }\n    bool nameOk = !validate_ || headerCode != HTTP_HEADER_OTHER ||\n                  CodecUtil::validateHeaderName(\n                      nameSp,\n                      strictValidation_ ? CodecUtil::HEADER_NAME_STRICT\n                                        : CodecUtil::HEADER_NAME_STRICT_COMPAT);\n    bool valueOk =\n        !validate_ ||\n        CodecUtil::validateHeaderValue(\n            valueSp,\n            strictValidation_ ? CodecUtil::CtlEscapeMode::STRICT\n                              : CodecUtil::CtlEscapeMode::STRICT_COMPAT);\n    if (!nameOk || !valueOk) {\n      proxygenError =\n          nameSp.empty() ? kErrorParseHeader : kErrorHeaderContentValidation;\n      parsingError = folly::to<string>(\"Invalid header name=\", nameSp);\n      headerErrorValue = valueSp;\n      return false;\n    }\n\n    // Add the (name, value) pair to headers\n    headerCode == HTTP_HEADER_OTHER ? headers.add(nameSp, valueSp)\n                                    : headers.add(headerCode, valueSp);\n  }\n  return true;\n}\n\nvoid HeaderDecodeInfo::onHeadersComplete(HTTPHeaderSize decodedSize) {\n  HTTPHeaders& headers = msg->getHeaders();\n\n  if (isRequest_ && !isRequestTrailers_) {\n    auto combinedCookie = headers.combine(HTTP_HEADER_COOKIE, \"; \");\n    if (!combinedCookie.empty()) {\n      headers.set(HTTP_HEADER_COOKIE, combinedCookie);\n    }\n    if (!verifier.validate()) {\n      parsingError = verifier.error;\n      return;\n    }\n  }\n\n  bool isResponseTrailers = (!isRequest_ && !hasStatus_);\n  if ((isRequestTrailers_ || isResponseTrailers) && pseudoHeaderSeen_) {\n    parsingError = \"Pseudo headers forbidden in trailers.\";\n    return;\n  }\n\n  msg->setHTTPVersion(1, 1);\n  msg->setIngressHeaderSize(decodedSize);\n}\n\nbool HeaderDecodeInfo::hasStatus() const {\n  return hasStatus_;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HeaderDecodeInfo.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <proxygen/lib/http/codec/HTTPRequestVerifier.h>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeaderName.h>\n\n#include <memory>\n\nnamespace proxygen {\n\nclass HTTPMessage;\n\nclass HeaderDecodeInfo {\n public:\n  void init(bool isRequestIn,\n            bool isRequestTrailers,\n            bool validate,\n            bool strictValidation,\n            bool allowEmptyPath) {\n    CHECK(!msg);\n    msg = std::make_unique<HTTPMessage>();\n    isRequest_ = isRequestIn;\n    isRequestTrailers_ = isRequestTrailers;\n    validate_ = validate;\n    hasStatus_ = false;\n    regularHeaderSeen_ = false;\n    pseudoHeaderSeen_ = false;\n    parsingError.clear();\n    headerErrorValue.clear();\n    decodeError = HPACK::DecodeError::NONE;\n    strictValidation_ = strictValidation;\n    allowEmptyPath_ = allowEmptyPath;\n    verifier.reset(msg.get());\n  }\n\n  bool onHeader(const HPACKHeaderName& name, const folly::fbstring& value);\n\n  void onHeadersComplete(HTTPHeaderSize decodedSize);\n\n  [[nodiscard]] bool hasStatus() const;\n\n  // Change this to a map of decoded header blocks when we decide\n  // to concurrently decode partial header blocks\n  std::unique_ptr<HTTPMessage> msg;\n  HTTPRequestVerifier verifier;\n  std::string parsingError;\n  std::string headerErrorValue;\n  folly::Optional<ProxygenError> proxygenError;\n  HPACK::DecodeError decodeError{HPACK::DecodeError::NONE};\n\n private:\n  bool isRequest_{false};\n  bool isRequestTrailers_{false};\n  bool validate_{true};\n  bool hasStatus_{false};\n  bool regularHeaderSeen_{false};\n  bool pseudoHeaderSeen_{false};\n  // Default to false for now to match existing behavior\n  bool strictValidation_{false};\n  bool allowEmptyPath_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/HeadersRateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n\nnamespace proxygen {\n\nclass HeadersRateLimiter : public RateLimiter {\n public:\n  static const uint32_t kDefaultMaxEventsPerInterval = 50000;\n  static const uint32_t kMaxEventsPerIntervalLowerBound = 100;\n  static constexpr std::chrono::milliseconds kDefaultTimeoutDuration{100};\n\n  explicit HeadersRateLimiter(folly::HHWheelTimer* timer,\n                              HTTPSessionStats* httpSessionStats)\n      : RateLimiter(timer, httpSessionStats) {\n    maxEventsInInterval_ = kDefaultMaxEventsPerInterval;\n    timeoutDuration_ = kDefaultTimeoutDuration;\n  }\n\n  void recordNumEventsInCurrentInterval(uint32_t numEvents) override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordHeadersInInterval(numEvents);\n    }\n  }\n\n  void recordRateLimitBreached() override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordHeadersRateLimited();\n    }\n  }\n\n  [[nodiscard]] uint32_t getMaxEventsPerInvervalLowerBound() const override {\n    return kMaxEventsPerIntervalLowerBound;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/QPACKDecoderCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\nnamespace proxygen::hq {\n\nclass QPACKDecoderCodec : public HQUnidirectionalCodec {\n\n public:\n  QPACKDecoderCodec(QPACKCodec& qpackCodec, Callback& cb)\n      : HQUnidirectionalCodec(UnidirectionalStreamType::QPACK_DECODER,\n                              StreamDirection::INGRESS),\n        qpackCodec_(qpackCodec),\n        callback_(cb) {\n  }\n\n  // HQUnidirectionalCodec API\n  std::unique_ptr<folly::IOBuf> onUnidirectionalIngress(\n      std::unique_ptr<folly::IOBuf> buf) override {\n    auto err = qpackCodec_.decodeDecoderStream(std::move(buf));\n    if (err != HPACK::DecodeError::NONE) {\n      LOG(ERROR) << \"QPACK decoder stream decode error err=\" << err;\n      HTTPException ex(\n          HTTPException::Direction::INGRESS_AND_EGRESS,\n          folly::to<std::string>(\"Compression error on decoder stream err=\",\n                                 uint32_t(err)));\n      ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR);\n      callback_.onError(kSessionStreamId, ex, false);\n    }\n    return nullptr;\n  }\n\n  void onUnidirectionalIngressEOF() override {\n    LOG(ERROR) << \"Unexpected QPACK encoder stream EOF\";\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                     \"Encoder stream EOF\");\n    ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n    callback_.onError(kSessionStreamId, ex, false);\n  }\n\n private:\n  QPACKCodec& qpackCodec_;\n  Callback& callback_;\n};\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/QPACKEncoderCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\nnamespace proxygen::hq {\n\nclass QPACKEncoderCodec : public HQUnidirectionalCodec {\n\n public:\n  QPACKEncoderCodec(QPACKCodec& qpackCodec, Callback& cb)\n      : HQUnidirectionalCodec(UnidirectionalStreamType::QPACK_ENCODER,\n                              StreamDirection::INGRESS),\n        qpackCodec_(qpackCodec),\n        callback_(cb) {\n  }\n\n  // HQUnidirectionalCodec API\n  std::unique_ptr<folly::IOBuf> onUnidirectionalIngress(\n      std::unique_ptr<folly::IOBuf> buf) override {\n    auto err = qpackCodec_.decodeEncoderStream(std::move(buf));\n    if (err != HPACK::DecodeError::NONE) {\n      LOG(ERROR) << \"QPACK encoder stream decode error err=\" << err;\n      HTTPException ex(\n          HTTPException::Direction::INGRESS_AND_EGRESS,\n          folly::to<std::string>(\"Compression error on encoder stream err=\",\n                                 uint32_t(err)));\n      ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_QPACK_ENCODER_STREAM_ERROR);\n      callback_.onError(kSessionStreamId, ex, false);\n    }\n    return nullptr;\n  }\n  void onUnidirectionalIngressEOF() override {\n    LOG(ERROR) << \"Unexpected QPACK encoder stream EOF\";\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                     \"Encoder stream EOF\");\n    ex.setHttp3ErrorCode(HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n    callback_.onError(kSessionStreamId, ex, false);\n  }\n\n private:\n  QPACKCodec& qpackCodec_;\n  Callback& callback_;\n};\n\n} // namespace proxygen::hq\n"
  },
  {
    "path": "proxygen/lib/http/codec/RateLimitFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/ControlMessageRateLimitFilter.h>\n#include <proxygen/lib/http/codec/DirectErrorsRateLimitFilter.h>\n#include <proxygen/lib/http/codec/HeadersRateLimitFilter.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n#include <proxygen/lib/http/codec/ResetsRateLimitFilter.h>\n\nnamespace proxygen {\n\nstd::string_view RateLimiter::toStr(Type type) {\n  switch (type) {\n    case Type::HEADERS:\n      return \"headers\";\n    case Type::MISC_CONTROL_MSGS:\n      return \"misc_control_msgs\";\n    case Type::RSTS:\n      return \"rsts\";\n    case Type::DIRECT_ERROR_HANDLING:\n      return \"direct_error_handling\";\n    default:\n      return \"unknown\";\n  }\n}\n\nstd::unique_ptr<RateLimiter> RateLimiter::createRateLimiter(\n    Type type, folly::HHWheelTimer* timer, HTTPSessionStats* httpSessionStats) {\n  switch (type) {\n    case Type::HEADERS:\n      return std::make_unique<HeadersRateLimiter>(timer, httpSessionStats);\n    case Type::MISC_CONTROL_MSGS:\n      return std::make_unique<ControlMessageRateLimiter>(timer,\n                                                         httpSessionStats);\n    case Type::RSTS:\n      return std::make_unique<ResetsRateLimiter>(timer, httpSessionStats);\n    case Type::DIRECT_ERROR_HANDLING:\n      return std::make_unique<DirectErrorsRateLimiter>(timer, httpSessionStats);\n    default:\n      return nullptr;\n  }\n}\n\nbool RateLimiter::incrementNumEventsInCurrentInterval() {\n  if (numEventsInCurrentInterval_ == 0) {\n    // The first control message (or first after a reset) schedules the next\n    // reset timer\n    CHECK(timer_);\n    timer_->scheduleTimeout(this, timeoutDuration_);\n  }\n\n  numEventsInCurrentInterval_++;\n  bool rateLimitExceeded = (numEventsInCurrentInterval_ > maxEventsInInterval_);\n  if (rateLimitExceeded) {\n    recordRateLimitBreached();\n  }\n  return rateLimitExceeded;\n}\n\nvoid RateLimiter::setSessionStats(HTTPSessionStats* httpSessionStats) {\n  httpSessionStats_ = httpSessionStats;\n}\n\nvoid RateLimiter::setParams(uint32_t maxEventsInInterval,\n                            std::chrono::milliseconds timeoutDuration) {\n  maxEventsInInterval_ = maxEventsInInterval;\n  timeoutDuration_ = timeoutDuration;\n}\n\nvoid RateLimiter::callbackCanceled() noexcept {\n}\n\nvoid RateLimiter::timeoutExpired() noexcept {\n  recordNumEventsInCurrentInterval(numEventsInCurrentInterval_);\n  numEventsInCurrentInterval_ = 0;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/RateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/HHWheelTimer.h>\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n\nnamespace proxygen {\n\nclass RateLimiter : public folly::HHWheelTimer::Callback {\n public:\n  enum class Type : uint8_t {\n    HEADERS = 0,\n    MISC_CONTROL_MSGS = 1,\n    RSTS = 2,\n    DIRECT_ERROR_HANDLING = 3,\n    // Upper bound\n    MAX = 4,\n  };\n\n  static_assert(folly::to_underlying(Type::DIRECT_ERROR_HANDLING) + 1 ==\n                folly::to_underlying(Type::MAX));\n\n  static std::string_view toStr(Type type);\n\n  static std::unique_ptr<RateLimiter> createRateLimiter(\n      Type type,\n      folly::HHWheelTimer* timer,\n      HTTPSessionStats* httpSessionStats);\n\n  RateLimiter(folly::HHWheelTimer* timer, HTTPSessionStats* httpSessionStats)\n      : timer_(timer), httpSessionStats_(httpSessionStats) {\n  }\n\n  virtual bool incrementNumEventsInCurrentInterval();\n\n  virtual void recordNumEventsInCurrentInterval(uint32_t) = 0;\n\n  virtual void recordRateLimitBreached() = 0;\n\n  [[nodiscard]] virtual uint32_t getMaxEventsPerInvervalLowerBound() const = 0;\n\n  void setSessionStats(HTTPSessionStats* httpSessionStats);\n\n  void setParams(uint32_t maxEventsInInterval,\n                 std::chrono::milliseconds timeoutDuration);\n\n  void attachThreadLocals(folly::HHWheelTimer* timer) {\n    timer_ = timer;\n  }\n\n  void detachThreadLocals() {\n    cancelTimeout();\n    timer_ = nullptr;\n    // Free pass when switching threads\n    numEventsInCurrentInterval_ = 0;\n  }\n\n  [[nodiscard]] uint32_t numEventsInCurrentInterval() const {\n    return numEventsInCurrentInterval_;\n  }\n\n protected:\n  void callbackCanceled() noexcept override;\n\n  void timeoutExpired() noexcept override;\n\n  uint32_t numEventsInCurrentInterval_{0};\n  uint32_t maxEventsInInterval_{0};\n  std::chrono::milliseconds timeoutDuration_;\n\n  folly::HHWheelTimer* timer_;\n  HTTPSessionStats* httpSessionStats_;\n};\n\nclass RateLimitFilter : public PassThroughHTTPCodecFilter {\n public:\n  RateLimitFilter(folly::HHWheelTimer* timer,\n                  HTTPSessionStats* httpSessionStats)\n      : timer_(timer), httpSessionStats_(httpSessionStats) {\n  }\n\n  void addRateLimiter(RateLimiter::Type type) {\n    CHECK_LT(folly::to_underlying(type),\n             folly::to_underlying(RateLimiter::Type::MAX))\n        << \"Received a rate limit type that exceeded the specified maximum\";\n    auto index = folly::to_underlying(type);\n    if (!rateLimiters_[index]) {\n      rateLimiters_[index] =\n          RateLimiter::createRateLimiter(type, timer_, httpSessionStats_);\n      CHECK(rateLimiters_[index])\n          << \"Unable to construct a rate limit filter of type \"\n          << RateLimiter::toStr(type);\n    }\n  }\n\n  void setRateLimitParams(RateLimiter::Type type,\n                          uint32_t maxEventsPerInterval,\n                          std::chrono::milliseconds intervalDuration) {\n    uint32_t typeIndex = folly::to_underlying(type);\n    CHECK_LT(typeIndex, folly::to_underlying(RateLimiter::Type::MAX))\n        << \"Out of bounds access to rate limit filter array\";\n    auto& rateLimiter = rateLimiters_.at(typeIndex);\n    if (rateLimiter) {\n      uint32_t maxEventsPerIntervalLowerBound =\n          rateLimiter->getMaxEventsPerInvervalLowerBound();\n      if (maxEventsPerInterval < maxEventsPerIntervalLowerBound) {\n        LOG(WARNING) << \"Invalid maxEventsPerInterval for event \"\n                     << RateLimiter::toStr(type) << \": \"\n                     << maxEventsPerInterval;\n        maxEventsPerInterval = maxEventsPerIntervalLowerBound;\n      }\n      rateLimiter->setParams(maxEventsPerInterval, intervalDuration);\n    }\n  }\n\n  void onHeadersComplete(StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override {\n    auto& rateLimiter =\n        rateLimiters_[folly::to_underlying(RateLimiter::Type::HEADERS)];\n    if (!rateLimiter || !rateLimiter->incrementNumEventsInCurrentInterval()) {\n      callback_->onHeadersComplete(stream, std::move(msg));\n    } else {\n      call_->disableDoubleGoawayDrain();\n      callback_->onGoaway(http2::kMaxStreamID, ErrorCode::NO_ERROR);\n    }\n  }\n  void onAbort(HTTPCodec::StreamID streamID, ErrorCode code) override {\n    auto& rateLimiter =\n        rateLimiters_[folly::to_underlying(RateLimiter::Type::RSTS)];\n    if (!rateLimiter || !rateLimiter->incrementNumEventsInCurrentInterval()) {\n      callback_->onAbort(streamID, code);\n    } else {\n      sendErrorCallback(http2::FrameType::RST_STREAM,\n                        rateLimiter->numEventsInCurrentInterval());\n    }\n  }\n  void onPingRequest(uint64_t data) override {\n    auto& rateLimiter = rateLimiters_[folly::to_underlying(\n        RateLimiter::Type::MISC_CONTROL_MSGS)];\n    if (!rateLimiter || !rateLimiter->incrementNumEventsInCurrentInterval()) {\n      callback_->onPingRequest(data);\n    } else {\n      sendErrorCallback(http2::FrameType::PING,\n                        rateLimiter->numEventsInCurrentInterval());\n    }\n  }\n  void onSettings(const SettingsList& settings) override {\n    auto& rateLimiter = rateLimiters_[folly::to_underlying(\n        RateLimiter::Type::MISC_CONTROL_MSGS)];\n    if (!rateLimiter || !rateLimiter->incrementNumEventsInCurrentInterval()) {\n      callback_->onSettings(settings);\n    } else {\n      sendErrorCallback(http2::FrameType::SETTINGS,\n                        rateLimiter->numEventsInCurrentInterval());\n    }\n  }\n\n  void onPriority(StreamID streamID, const HTTPPriority& pri) override {\n    auto& rateLimiter = rateLimiters_[folly::to_underlying(\n        RateLimiter::Type::MISC_CONTROL_MSGS)];\n    if (!rateLimiter || !rateLimiter->incrementNumEventsInCurrentInterval()) {\n      callback_->onPriority(streamID, pri);\n    } else {\n      sendErrorCallback(http2::FrameType::PRIORITY,\n                        rateLimiter->numEventsInCurrentInterval());\n    }\n  }\n  void onError(HTTPCodec::StreamID streamID,\n               const HTTPException& error,\n               bool newTxn) override {\n    auto& rateLimiter = rateLimiters_[folly::to_underlying(\n        RateLimiter::Type::DIRECT_ERROR_HANDLING)];\n    // We only rate limit stream errors with no codec status code.\n    // These may trigger a direct HTTP response.\n    if (!rateLimiter || streamID == 0 || error.hasCodecStatusCode()) {\n      callback_->onError(streamID, error, newTxn);\n    } else if (rateLimiter->incrementNumEventsInCurrentInterval()) {\n      sendErrorCallback(http2::FrameType::RST_STREAM,\n                        rateLimiter->numEventsInCurrentInterval());\n    } else {\n      callback_->onError(streamID, error, newTxn);\n    }\n  }\n\n  void setSessionStats(HTTPSessionStats* httpSessionStats) {\n    httpSessionStats_ = httpSessionStats;\n    for (auto& rateLimiter : rateLimiters_) {\n      if (rateLimiter) {\n        rateLimiter->setSessionStats(httpSessionStats_);\n      }\n    }\n  }\n\n  void attachThreadLocals(folly::HHWheelTimer* timer) {\n    timer_ = timer;\n    for (auto& rateLimiter : rateLimiters_) {\n      if (rateLimiter) {\n        rateLimiter->attachThreadLocals(timer);\n      }\n    }\n  }\n\n  void detachThreadLocals() {\n    for (auto& rateLimiter : rateLimiters_) {\n      if (rateLimiter) {\n        rateLimiter->detachThreadLocals();\n      }\n    }\n    timer_ = nullptr;\n  }\n\n private:\n  folly::HHWheelTimer* timer_;\n  HTTPSessionStats* httpSessionStats_;\n\n  void sendErrorCallback(http2::FrameType frameType,\n                         uint64_t numEventsInCurrentInterval) {\n    HTTPException ex(\n        HTTPException::Direction::INGRESS_AND_EGRESS,\n        folly::to<std::string>(\n            \"dropping connection due to too many control messages, num \"\n            \"control messages = \",\n            numEventsInCurrentInterval,\n            \", most recent frame type = \",\n            getFrameTypeString(frameType)));\n    ex.setCodecStatusCode(ErrorCode::CANCEL);\n    ex.setProxygenError(kErrorDropped);\n    callback_->onError(0, ex, true);\n  }\n\n  std::array<std::unique_ptr<RateLimiter>,\n             folly::to_underlying(RateLimiter::Type::MAX)>\n      rateLimiters_{};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/ResetsRateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n\nnamespace proxygen {\n\nclass ResetsRateLimiter : public RateLimiter {\n public:\n  static const uint32_t kDefaultMaxEventsPerInterval = 10000;\n  static const uint32_t kMaxEventsPerIntervalLowerBound = 100;\n  static constexpr std::chrono::milliseconds kDefaultTimeoutDuration{1000};\n\n  explicit ResetsRateLimiter(folly::HHWheelTimer* timer,\n                             HTTPSessionStats* httpSessionStats)\n      : RateLimiter(timer, httpSessionStats) {\n    maxEventsInInterval_ = kDefaultMaxEventsPerInterval;\n    timeoutDuration_ = kDefaultTimeoutDuration;\n  }\n\n  void recordNumEventsInCurrentInterval(uint32_t numEvents) override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordResetsInInterval(numEvents);\n    }\n  }\n\n  void recordRateLimitBreached() override {\n    if (httpSessionStats_) {\n      httpSessionStats_->recordResetsRateLimited();\n    }\n  }\n\n  [[nodiscard]] uint32_t getMaxEventsPerInvervalLowerBound() const override {\n    return kMaxEventsPerIntervalLowerBound;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/SettingsId.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <utility>\n\nnamespace proxygen {\n\n#define HQ_SETTINGS_MASK (1ull << 32)\n\nenum class SettingsId : uint64_t {\n  // From HTTP/2\n  HEADER_TABLE_SIZE = 1,\n  ENABLE_PUSH = 2,\n  MAX_CONCURRENT_STREAMS = 3,\n  INITIAL_WINDOW_SIZE = 4,\n  MAX_FRAME_SIZE = 5,\n  MAX_HEADER_LIST_SIZE = 6,\n\n  ENABLE_CONNECT_PROTOCOL = 8,\n\n  THRIFT_CHANNEL_ID_DEPRECATED = 100,\n\n  // 0xf000 and 0xffff being reserved for Experimental Use\n  THRIFT_CHANNEL_ID = 0xf100,\n\n  // For secondary authentication in HTTP/2\n  SETTINGS_HTTP_CERT_AUTH = 0xff00,\n\n  WT_MAX_SESSIONS = 0x2b60,\n  WT_INITIAL_MAX_DATA = 0x2b61,\n  WT_INITIAL_MAX_STREAM_DATA_UNI = 0x2b62,\n  WT_INITIAL_MAX_STREAM_DATA_BIDI = 0x2b63,\n  WT_INITIAL_MAX_STREAMS_UNI = 0x2b64,\n  WT_INITIAL_MAX_STREAMS_BIDI = 0x2b65,\n\n  // From HQ\n  //_HQ_HEADER_TABLE_SIZE = HQ_SETTINGS_MASK | 1, -- use HEADER_TABLE_SIZE\n  //_HQ_MAX_HEADER_LIST_SIZE = HQ_SETTINGS_MASK | 6, -- use\n  // MAX_HEADER_LIST_SIZE\n  _HQ_QPACK_BLOCKED_STREAMS = HQ_SETTINGS_MASK | 7,\n  _HQ_DATAGRAM = HQ_SETTINGS_MASK | 0x0276,\n  _HQ_DATAGRAM_DRAFT_8 = HQ_SETTINGS_MASK | 0xffd277,\n  _HQ_DATAGRAM_RFC = HQ_SETTINGS_MASK | 0x33,\n  ENABLE_WEBTRANSPORT = 0x2b603742,\n  H3_WT_MAX_SESSIONS = 0x14e9cd29,\n};\n\nusing SettingPair = std::pair<SettingsId, uint32_t>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/TransportDirection.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/TransportDirection.h>\n\n#include <ostream>\n\nnamespace proxygen {\n\nstd::string_view getTransportDirectionString(TransportDirection dir) {\n  switch (dir) {\n    case TransportDirection::UPSTREAM:\n      return \"upstream\";\n    case TransportDirection::DOWNSTREAM:\n      return \"downstream\";\n  }\n  // unreachable\n  return \"\";\n}\n\nTransportDirection operator!(TransportDirection dir) {\n  return isDownstream(dir) ? TransportDirection::UPSTREAM\n                           : TransportDirection::DOWNSTREAM;\n}\n\nstd::ostream& operator<<(std::ostream& os, const TransportDirection dir) {\n  os << getTransportDirectionString(dir);\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/TransportDirection.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <iosfwd>\n#include <stdint.h>\n#include <string_view>\n\nnamespace proxygen {\n\nenum class TransportDirection : uint8_t {\n  DOWNSTREAM = 0, // toward the client\n  UPSTREAM = 1    // toward the origin application or data\n};\n\ninline bool isUpstream(TransportDirection dir) noexcept {\n  return dir == TransportDirection::UPSTREAM;\n}\n\ninline bool isDownstream(TransportDirection dir) noexcept {\n  return dir == TransportDirection::DOWNSTREAM;\n}\n\nstd::string_view getTransportDirectionString(TransportDirection dir);\n\nTransportDirection operator!(TransportDirection dir);\n\nstd::ostream& operator<<(std::ostream& os, const TransportDirection dir);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_codec_compress_fast_header_name\n  EXPORTED_DEPS\n    proxygen_http_http_headers\n    Folly::folly_range\n    Folly::folly_string\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_codec_compress_hpack\n  SRCS\n    HPACKCodec.cpp\n    HPACKContext.cpp\n    HPACKDecodeBuffer.cpp\n    HPACKDecoder.cpp\n    HPACKDecoderBase.cpp\n    HPACKEncodeBuffer.cpp\n    HPACKEncoder.cpp\n    HPACKEncoderBase.cpp\n    HPACKHeader.cpp\n    HeaderIndexingStrategy.cpp\n    HeaderTable.cpp\n    Huffman.cpp\n    Logging.cpp\n    NoPathIndexingStrategy.cpp\n    StaticHeaderTable.cpp\n  DEPS\n    proxygen_http_codec_util\n    proxygen_http_header_constants\n    proxygen_http_message\n    proxygen_utils_logging\n    Folly::folly_container_reserve\n    Folly::folly_indestructible\n    Folly::folly_portability_sockets\n    Folly::folly_thread_local\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_compression_info\n    proxygen_http_codec_compress_fast_header_name\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_codec_direction\n    proxygen_http_http_headers\n    Folly::folly_container_f14_hash\n    Folly::folly_conv\n    Folly::folly_fbstring\n    Folly::folly_io_iobuf\n    Folly::folly_small_vector\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_codec_compress_qpack\n  SRCS\n    QPACKCodec.cpp\n    QPACKContext.cpp\n    QPACKDecoder.cpp\n    QPACKEncoder.cpp\n    QPACKHeaderTable.cpp\n    QPACKStaticHeaderTable.cpp\n  DEPS\n    proxygen_http_codec_util\n    proxygen_http_header_constants\n    proxygen_http_message\n    Folly::folly_indestructible\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_codec_compress_hpack\n    proxygen_http_codec_direction\n    proxygen_http_http_headers\n    Folly::folly_io_async_destructor_check\n    Folly::folly_io_iobuf\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_codec_compress_compression_info)\n\nproxygen_add_library(proxygen_http_codec_compress_header_codec\n  EXPORTED_DEPS\n    proxygen_http_http_header_size\n    proxygen_http_http_headers\n    Folly::folly_expected\n    Folly::folly_fbstring\n    Folly::folly_range\n    glog::glog\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/CompressionInfo.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <stdint.h>\n\nnamespace proxygen {\n\n/*\n * Struct to hold the encoder and decoder information\n */\nstruct CompressionInfoPart {\n  uint32_t headerTableSize_{0};\n  uint32_t bytesStored_{0};\n  uint32_t headersStored_{0};\n  uint32_t inserts_{0};\n  uint32_t blockedInserts_{0};\n  uint32_t duplications_{0};\n  uint32_t staticRefs_{0};\n\n  CompressionInfoPart(uint32_t headerTableSize,\n                      uint32_t bytesStored,\n                      uint32_t headersStored,\n                      uint32_t inserts,\n                      uint32_t blockedInserts,\n                      uint32_t duplications,\n                      uint32_t staticRefs)\n      : headerTableSize_(headerTableSize),\n        bytesStored_(bytesStored),\n        headersStored_(headersStored),\n        inserts_(inserts),\n        blockedInserts_(blockedInserts),\n        duplications_(duplications),\n        staticRefs_(staticRefs) {\n  }\n\n  CompressionInfoPart() = default;\n\n  // copy constructor\n  CompressionInfoPart(const CompressionInfoPart&) = default;\n  // copy assignment operator\n  CompressionInfoPart& operator=(const CompressionInfoPart& other) = default;\n  // move constructor\n  CompressionInfoPart(CompressionInfoPart&&) = default;\n  // move assignment operator\n  CompressionInfoPart& operator=(CompressionInfoPart&&) = default;\n  // destructor\n  ~CompressionInfoPart() = default;\n};\n\nstruct CompressionInfo {\n  // Egress table info (encoder)\n  CompressionInfoPart egress;\n\n  // Ingress table info (decoder)\n  CompressionInfoPart ingress;\n\n  CompressionInfo(uint32_t egressHeaderTableSize,\n                  uint32_t egressBytesStored,\n                  uint32_t egressHeadersStored,\n                  uint32_t egressInserts,\n                  uint32_t egressBlockedInserts,\n                  uint32_t egressDuplications,\n                  uint32_t egressStaticRefs,\n                  uint32_t ingressHeaderTableSize,\n                  uint32_t ingressBytesStored,\n                  uint32_t ingressHeadersStored,\n                  uint32_t ingressInserts,\n                  uint32_t ingressBlockedInserts,\n                  uint32_t ingressDuplications,\n                  uint32_t ingressStaticRefs)\n      : egress(egressHeaderTableSize,\n               egressBytesStored,\n               egressHeadersStored,\n               egressInserts,\n               egressBlockedInserts,\n               egressDuplications,\n               egressStaticRefs),\n        ingress(ingressHeaderTableSize,\n                ingressBytesStored,\n                ingressHeadersStored,\n                ingressInserts,\n                ingressBlockedInserts,\n                ingressDuplications,\n                ingressStaticRefs) {\n  }\n\n  CompressionInfo() = default;\n\n  bool operator==(const CompressionInfo& tableInfo) const {\n    return egress.headerTableSize_ == tableInfo.egress.headerTableSize_ &&\n           egress.bytesStored_ == tableInfo.egress.bytesStored_ &&\n           egress.headersStored_ == tableInfo.egress.headersStored_ &&\n           ingress.headerTableSize_ == tableInfo.ingress.headerTableSize_ &&\n           ingress.bytesStored_ == tableInfo.ingress.bytesStored_ &&\n           ingress.headersStored_ == tableInfo.ingress.headersStored_;\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n\n#include <algorithm>\n#include <folly/ThreadLocal.h>\n#include <folly/io/Cursor.h>\n#include <iosfwd>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/HeaderConstants.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\nusing folly::IOBuf;\nusing folly::io::Cursor;\nusing proxygen::compress::Header;\nusing std::unique_ptr;\nusing std::vector;\n\nnamespace proxygen {\n\nnamespace compress {\nuint32_t prepareHeaders(const vector<Header>& headers,\n                        vector<HPACKHeader>& converted) {\n  // convert to HPACK API format\n  uint32_t uncompressed = 0;\n  converted.clear();\n  converted.reserve(headers.size());\n  for (const auto& h : headers) {\n    // HPACKHeader automatically lowercases\n    converted.emplace_back(*h.name, *h.value);\n    auto& header = converted.back();\n    uncompressed += header.name.size() + header.value.size() + 2;\n  }\n  return uncompressed;\n}\n} // namespace compress\n\nHPACKCodec::HPACKCodec(TransportDirection /*direction*/)\n    : encoder_(true, HPACK::kTableSize),\n      decoder_(HPACK::kTableSize, maxUncompressed_) {\n}\n\nunique_ptr<IOBuf> HPACKCodec::encode(vector<Header>& headers) noexcept {\n  folly::ThreadLocal<std::vector<HPACKHeader>> preparedTL;\n  auto& prepared = *preparedTL.get();\n  encodedSize_.uncompressed = compress::prepareHeaders(headers, prepared);\n  auto buf = encoder_.encode(prepared, encodeHeadroom_);\n  recordCompressedSize(buf.get());\n  return buf;\n}\n\nvoid HPACKCodec::encode(vector<Header>& headers,\n                        folly::IOBufQueue& writeBuf) noexcept {\n  folly::ThreadLocal<vector<HPACKHeader>> preparedTL;\n  auto& prepared = *preparedTL.get();\n  encodedSize_.uncompressed = compress::prepareHeaders(headers, prepared);\n  auto prevSize = writeBuf.chainLength();\n  encoder_.encode(prepared, writeBuf);\n  recordCompressedSize(writeBuf.chainLength() - prevSize);\n}\n\nvoid HPACKCodec::encodeHTTP(\n    const HTTPMessage& msg,\n    folly::IOBufQueue& writeBuf,\n    bool includeDate,\n    const folly::Optional<HTTPHeaders>& extraHeaders) noexcept {\n  auto prevSize = writeBuf.chainLength();\n  encoder_.startEncode(writeBuf);\n\n  auto uncompressed = 0;\n  if (msg.isRequest()) {\n    if (msg.isEgressWebsocketUpgrade()) {\n      uncompressed += encoder_.encodeHeader(\n          HTTP_HEADER_COLON_METHOD, methodToString(HTTPMethod::CONNECT));\n      uncompressed += encoder_.encodeHeader(HTTP_HEADER_COLON_PROTOCOL,\n                                            headers::kWebsocketString);\n    } else if (msg.getUpgradeProtocol()) {\n      uncompressed += encoder_.encodeHeader(\n          HTTP_HEADER_COLON_METHOD, methodToString(HTTPMethod::CONNECT));\n      uncompressed += encoder_.encodeHeader(HTTP_HEADER_COLON_PROTOCOL,\n                                            *msg.getUpgradeProtocol());\n    } else {\n      uncompressed += encoder_.encodeHeader(HTTP_HEADER_COLON_METHOD,\n                                            msg.getMethodString());\n    }\n\n    if (msg.getMethod() != HTTPMethod::CONNECT ||\n        msg.isEgressWebsocketUpgrade() || msg.getUpgradeProtocol()) {\n      uncompressed +=\n          encoder_.encodeHeader(HTTP_HEADER_COLON_SCHEME, msg.getScheme());\n      uncompressed +=\n          encoder_.encodeHeader(HTTP_HEADER_COLON_PATH, msg.getURL());\n    }\n    const HTTPHeaders& headers = msg.getHeaders();\n    const std::string& host = headers.getSingleOrEmpty(HTTP_HEADER_HOST);\n    uncompressed += encoder_.encodeHeader(HTTP_HEADER_COLON_AUTHORITY, host);\n  } else {\n    if (msg.isEgressWebsocketUpgrade()) {\n      uncompressed +=\n          encoder_.encodeHeader(HTTP_HEADER_COLON_STATUS, headers::kStatus200);\n    } else {\n      uncompressed += encoder_.encodeHeader(\n          HTTP_HEADER_COLON_STATUS,\n          folly::to<folly::fbstring>(msg.getStatusCode()));\n    }\n    // HEADERS frames do not include a version or reason string.\n  }\n\n  bool hasDateHeader = false;\n  // Add the HTTP headers supplied by the caller, but skip\n  // any per-hop headers that aren't supported in HTTP/2.\n  auto headerEncodeHelper = [&](HTTPHeaderCode code,\n                                const std::string& name,\n                                const std::string& value) {\n    if (CodecUtil::disallowedModernHTTPFields()[code] || name.empty() ||\n        name[0] == ':') {\n      DCHECK(!name.empty()) << \"Empty header\";\n      DCHECK_NE(name[0], ':') << \"Invalid header=\" << name;\n      return;\n    }\n    // Note this code will not drop headers named by Connection.  That's the\n    // caller's job\n\n    // see HTTP/2 spec, 8.1.2\n    DCHECK(name != \"TE\" || value == \"trailers\");\n    if ((!name.empty() && name[0] != ':') && code != HTTP_HEADER_HOST) {\n      if (code == HTTP_HEADER_OTHER) {\n        uncompressed += encoder_.encodeHeader(name, value);\n      } else {\n        uncompressed += encoder_.encodeHeader(code, value);\n      }\n    }\n    hasDateHeader |= ((code == HTTP_HEADER_DATE) ? 1 : 0);\n  };\n\n  msg.getHeaders().forEachWithCode(headerEncodeHelper);\n  if (extraHeaders) {\n    extraHeaders->forEachWithCode(headerEncodeHelper);\n  }\n  if (includeDate && msg.isResponse() && !hasDateHeader) {\n    uncompressed += encoder_.encodeHeader(HTTP_HEADER_DATE,\n                                          HTTPMessage::formatDateHeader());\n  }\n\n  encoder_.completeEncode();\n  encodedSize_.uncompressed = uncompressed;\n  recordCompressedSize(writeBuf.chainLength() - prevSize);\n}\n\nvoid HPACKCodec::recordCompressedSize(const IOBuf* stream) {\n  encodedSize_.compressed = 0;\n  if (stream) {\n    auto streamDataLength = stream->computeChainDataLength();\n    encodedSize_.compressed += streamDataLength;\n    encodedSize_.compressedBlock += streamDataLength;\n  }\n  if (stats_) {\n    stats_->recordEncode(Type::HPACK, encodedSize_);\n  }\n}\n\nvoid HPACKCodec::recordCompressedSize(size_t size) {\n  encodedSize_.compressed = size;\n  encodedSize_.compressedBlock += size;\n  if (stats_) {\n    stats_->recordEncode(Type::HPACK, encodedSize_);\n  }\n}\n\nvoid HPACKCodec::decodeStreaming(\n    Cursor& cursor,\n    uint32_t length,\n    HPACK::StreamingCallback* streamingCb) noexcept {\n  streamingCb->stats = stats_;\n  decoder_.decodeStreaming(cursor, length, streamingCb);\n}\n\nvoid HPACKCodec::describe(std::ostream& stream) const {\n  stream << \"DecoderTable:\\n\" << decoder_;\n  stream << \"EncoderTable:\\n\" << encoder_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HPACKCodec& codec) {\n  codec.describe(os);\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <proxygen/lib/http/codec/compress/CompressionInfo.h>\n#include <proxygen/lib/http/codec/compress/HPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n#include <vector>\n\nnamespace folly::io {\nclass Cursor;\n} // namespace folly::io\n\nnamespace proxygen {\n\nclass HPACKHeader;\nclass HTTPMessage;\n\nnamespace compress {\nuint32_t prepareHeaders(const std::vector<Header>& headers,\n                        std::vector<HPACKHeader>& prepared);\n}\n\n/*\n * Current version of the wire protocol. When we're making changes to the wire\n * protocol we need to change this version and the NPN string so that old\n * clients will not be able to negotiate it anymore.\n */\n\nclass HPACKCodec : public HeaderCodec {\n public:\n  explicit HPACKCodec(TransportDirection direction);\n  ~HPACKCodec() override = default;\n\n  std::unique_ptr<folly::IOBuf> encode(\n      std::vector<compress::Header>& headers) noexcept;\n\n  void encode(std::vector<compress::Header>& headers,\n              folly::IOBufQueue& writeBuf) noexcept;\n\n  void encodeHTTP(\n      const HTTPMessage& msg,\n      folly::IOBufQueue& writeBuf,\n      bool includeDate,\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) noexcept;\n\n  void decodeStreaming(folly::io::Cursor& cursor,\n                       uint32_t length,\n                       HPACK::StreamingCallback* streamingCb) noexcept;\n\n  void setEncoderHeaderTableSize(uint32_t size) {\n    encoder_.setHeaderTableSize(size);\n  }\n\n  void setDecoderHeaderTableMaxSize(uint32_t size) {\n    decoder_.setHeaderTableMaxSize(size);\n  }\n\n  void describe(std::ostream& os) const;\n\n  void setMaxUncompressed(uint64_t maxUncompressed) override {\n    HeaderCodec::setMaxUncompressed(maxUncompressed);\n    decoder_.setMaxUncompressed(maxUncompressed);\n  }\n\n  CompressionInfo getCompressionInfo() const {\n    return {encoder_.getTableSize(),\n            encoder_.getBytesStored(),\n            encoder_.getHeadersStored(),\n            encoder_.getInsertCount(),\n            0,\n            0,\n            encoder_.getStaticRefs(),\n            decoder_.getTableSize(),\n            decoder_.getBytesStored(),\n            decoder_.getHeadersStored(),\n            decoder_.getInsertCount(),\n            0,\n            0,\n            decoder_.getStaticRefs()};\n  }\n\n  void setHeaderIndexingStrategy(const HeaderIndexingStrategy* indexingStrat) {\n    encoder_.setHeaderIndexingStrategy(indexingStrat);\n  }\n  const HeaderIndexingStrategy* getHeaderIndexingStrategy() const {\n    return encoder_.getHeaderIndexingStrategy();\n  }\n\n protected:\n  HPACKEncoder encoder_;\n  HPACKDecoder decoder_;\n\n private:\n  void recordCompressedSize(const folly::IOBuf* buf);\n  void recordCompressedSize(size_t size);\n\n  std::vector<HPACKHeader> decodedHeaders_;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HPACKCodec& codec);\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKConstants.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <iosfwd>\n#include <stdint.h>\n\nnamespace proxygen::HPACK {\n\nstruct Instruction {\n  uint8_t code;\n  uint8_t prefixLength;\n};\n\nconst Instruction INDEX_REF{.code = 0x80, .prefixLength = 7};\nconst Instruction LITERAL_INC_INDEX{.code = 0x40, .prefixLength = 6};\nconst Instruction LITERAL{.code = 0x00, .prefixLength = 4};\nconst Instruction LITERAL_NEV_INDEX{.code = 0x10, .prefixLength = 4};\nconst Instruction TABLE_SIZE_UPDATE{.code = 0x20, .prefixLength = 5};\n\n// Encoder Stream\nconst Instruction Q_INSERT_NAME_REF{.code = 0x80, .prefixLength = 6};\nconst Instruction Q_INSERT_NO_NAME_REF{.code = 0x40, .prefixLength = 5};\nconst Instruction Q_TABLE_SIZE_UPDATE{.code = 0x20, .prefixLength = 5};\nconst Instruction Q_DUPLICATE{.code = 0x00, .prefixLength = 5};\n\n// Decoder Stream\nconst Instruction Q_HEADER_ACK{.code = 0x80, .prefixLength = 7};\nconst Instruction Q_CANCEL_STREAM{.code = 0x40, .prefixLength = 6};\nconst Instruction Q_INSERT_COUNT_INC{.code = 0x00, .prefixLength = 6};\n\n// Request/Push Streams\n\n// Prefix\nconst uint8_t Q_DELTA_BASE_NEG = 0x80;\nconst uint8_t Q_DELTA_BASE_POS = 0x00;\n\nconst Instruction Q_DELTA_BASE{.code = 0x00, .prefixLength = 7};\n\n// Instructions\nconst Instruction Q_INDEXED{.code = 0x80, .prefixLength = 6};\nconst Instruction Q_INDEXED_POST{.code = 0x10, .prefixLength = 4};\nconst Instruction Q_LITERAL_NAME_REF{.code = 0x40, .prefixLength = 4};\nconst Instruction Q_LITERAL_NAME_REF_POST{.code = 0x00, .prefixLength = 3};\nconst Instruction Q_LITERAL{.code = 0x20, .prefixLength = 3};\n\nconst uint8_t Q_INDEXED_STATIC = 0x40;\nconst uint8_t Q_INSERT_NAME_REF_STATIC = 0x40;\nconst uint8_t Q_LITERAL_STATIC = 0x10;\n\nconst uint32_t kDefaultBlocking = 100;\n\nconst uint32_t kTableSize = 4096;\n\nconst uint8_t NBIT_MASKS[9] = {\n    0x00, // 00000000, unused\n    0x01, // 00000001\n    0x03, // 00000011\n    0x07, // 00000111\n    0x0F, // 00001111\n    0x1F, // 00011111\n    0x3F, // 00111111\n    0x7F, // 01111111\n    0xFF, // 11111111\n};\n\nenum LiteralEncoding : uint8_t { PLAIN = 0x00, HUFFMAN = 0x80 };\n\nenum class DecodeError : uint8_t {\n  NONE = 0,\n  INVALID_INDEX = 1,\n  INVALID_HUFFMAN_CODE = 2,\n  INVALID_ENCODING = 3,\n  INTEGER_OVERFLOW = 4,\n  INVALID_TABLE_SIZE = 5,\n  HEADERS_TOO_LARGE = 6,\n  BUFFER_UNDERFLOW = 7,\n  LITERAL_TOO_LARGE = 8,\n  TIMEOUT = 9,\n  ENCODER_STREAM_CLOSED = 10,\n  BAD_SEQUENCE_NUMBER = 11,\n  INVALID_ACK = 12,\n  TOO_MANY_BLOCKING = 13,\n  INSERT_TOO_LARGE = 14\n};\n\nstd::ostream& operator<<(std::ostream& os, DecodeError err);\n} // namespace proxygen::HPACK\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKContext.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKContext.h>\n\nnamespace proxygen {\n\nHPACKContext::HPACKContext(uint32_t tableSize) : table_(tableSize) {\n}\n\nstd::pair<uint32_t, uint32_t> HPACKContext::getIndex(\n    const HPACKHeader& header) const {\n  return getIndex(header.name, header.value);\n}\n\nstd::pair<uint32_t, uint32_t> HPACKContext::getIndex(\n    const HPACKHeaderName& name,\n    folly::StringPiece value,\n    bool checkDynamicTable) const {\n  // First consult the static header table if applicable\n  // Applicability is determined by the following guided optimizations:\n  // 1) The set of CommonHeaders includes all StaticTable headers and so we can\n  // quickly conclude that we need not check the StaticTable\n  // for non-CommonHeaders\n  // 2) The StaticTable only contains non empty values for a very small subset\n  // of header names.  As getIndex is only meaingful if both name and value\n  // match, we know that if our header has a value and is not part of the very\n  // small subset of header names, there is no point consulting the StaticTable\n  bool getIndexOnStaticTable = false;\n  if (value.empty()) {\n    // For uncommon static names and empty values, we will send them as a\n    // literal with static name reference.  See uncommon list in\n    // HPACKContextTests - StaticTableHeaderNamesAreCommon\n    getIndexOnStaticTable = name.isCommonHeader();\n  } else {\n    getIndexOnStaticTable =\n        StaticHeaderTable::isHeaderCodeInTableWithNonEmptyValue(\n            name.getHeaderCode());\n  }\n  std::pair<uint32_t, uint32_t> staticIndex{0, 0};\n  if (getIndexOnStaticTable) {\n    staticIndex = getStaticTable().getIndex(name, value);\n    if (staticIndex.first) {\n      staticRefs_++;\n      return {staticToGlobalIndex(staticIndex.first), 0};\n    }\n  }\n\n  std::pair<uint32_t, uint32_t> dynamicIndex{0, 0};\n  if (checkDynamicTable && table_.capacity() > 0) {\n    dynamicIndex = table_.getIndex(name, value);\n  }\n  if (dynamicIndex.first) {\n    return {dynamicToGlobalIndex(dynamicIndex.first), 0};\n  } else if (staticIndex.second) {\n    staticRefs_++;\n    return {0, staticToGlobalIndex(staticIndex.second)};\n  } else if (name.getHeaderCode() == HTTP_HEADER_DATE) {\n    // The name *may* be in the static table and doing a lookup could save a\n    // byte.  But the CPU isn't worth it.  Just hack DATE to make\n    // RFCExamplesTest pass\n    static uint32_t dateIndex =\n        getStaticTable().nameIndex(HPACKHeaderName(HTTP_HEADER_DATE));\n    staticRefs_++;\n    return {0, staticToGlobalIndex(dateIndex)};\n  } else if (dynamicIndex.second) {\n    return {0, dynamicToGlobalIndex(dynamicIndex.second)};\n  } else if (!getIndexOnStaticTable && name.isCommonHeader()) {\n    // Maybe the name is in the static table but we didn't look before\n    staticIndex.second = getStaticTable().nameIndex(name);\n    if (staticIndex.second) {\n      staticRefs_++;\n      return {0, staticToGlobalIndex(staticIndex.second)};\n    }\n  }\n  return {0, 0};\n}\n\nuint32_t HPACKContext::nameIndex(const HPACKHeaderName& headerName) const {\n  uint32_t index = getStaticTable().nameIndex(headerName);\n  if (index) {\n    staticRefs_++;\n    return staticToGlobalIndex(index);\n  }\n  index = table_.nameIndex(headerName);\n  if (index) {\n    return dynamicToGlobalIndex(index);\n  }\n  return 0;\n}\n\nbool HPACKContext::isStatic(uint32_t index) const {\n  return index <= getStaticTable().size();\n}\n\nconst HPACKHeader& HPACKContext::getHeader(uint32_t index) {\n  if (isStatic(index)) {\n    staticRefs_++;\n    return getStaticTable().getHeader(globalToStaticIndex(index));\n  }\n  return table_.getHeader(globalToDynamicIndex(index));\n}\n\nvoid HPACKContext::seedHeaderTable(std::vector<HPACKHeader>& headers) {\n  for (auto& header : headers) {\n    CHECK(table_.add(std::move(header)));\n  }\n}\n\nvoid HPACKContext::describe(std::ostream& os) const {\n  os << table_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HPACKContext& context) {\n  context.describe(os);\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKContext.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n#include <proxygen/lib/http/codec/compress/StaticHeaderTable.h>\n\nnamespace proxygen {\n\nclass HPACKContext {\n public:\n  explicit HPACKContext(uint32_t tableSize);\n  ~HPACKContext() = default;\n\n  /**\n   * get the index of the given header by looking into both dynamic and static\n   * header table\n   *\n   * @return 0 if cannot be found\n   */\n  std::pair<uint32_t, uint32_t> getIndex(const HPACKHeader& header) const;\n\n  std::pair<uint32_t, uint32_t> getIndex(const HPACKHeaderName& name,\n                                         folly::StringPiece value,\n                                         bool checkDynamicTable = true) const;\n\n  /**\n   * index of a header entry with the given name from dynamic or static table\n   *\n   * @return 0 if name not found\n   */\n  uint32_t nameIndex(const HPACKHeaderName& headerName) const;\n\n  /**\n   * @return true if the given index points to a static header entry\n   */\n  bool isStatic(uint32_t index) const;\n\n  /**\n   * @return header at the given index by composing dynamic and static tables\n   */\n  const HPACKHeader& getHeader(uint32_t index);\n\n  const HeaderTable& getTable() const {\n    return table_;\n  }\n\n  uint32_t getTableSize() const {\n    return table_.capacity();\n  }\n\n  uint32_t getBytesStored() const {\n    return table_.bytes();\n  }\n\n  uint32_t getHeadersStored() const {\n    return table_.size();\n  }\n\n  void seedHeaderTable(std::vector<HPACKHeader>& headers);\n\n  void describe(std::ostream& os) const;\n\n  uint32_t getStaticRefs() const {\n    return staticRefs_;\n  }\n\n  uint32_t getInsertCount() const {\n    return table_.getInsertCount();\n  }\n\n protected:\n  const StaticHeaderTable& getStaticTable() const {\n    return StaticHeaderTable::get();\n  }\n\n  uint32_t globalToDynamicIndex(uint32_t index) const {\n    return index - getStaticTable().size();\n  }\n  uint32_t globalToStaticIndex(uint32_t index) const {\n    return index;\n  }\n  uint32_t dynamicToGlobalIndex(uint32_t index) const {\n    return index + getStaticTable().size();\n  }\n  uint32_t staticToGlobalIndex(uint32_t index) const {\n    return index;\n  }\n\n  HeaderTable table_;\n  mutable uint32_t staticRefs_{0};\n};\n\nstd::ostream& operator<<(std::ostream& os, const HPACKContext& context);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecodeBuffer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n\n#include <limits>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/Huffman.h>\n\nusing folly::IOBuf;\nusing proxygen::HPACK::DecodeError;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nvoid HPACKDecodeBuffer::EOB_LOG(std::string msg, DecodeError code) const {\n  if (endOfBufferIsError_ || code != DecodeError::BUFFER_UNDERFLOW) {\n    LOG(ERROR) << msg;\n  } else {\n    VLOG(4) << msg;\n  }\n}\n\nbool HPACKDecodeBuffer::empty() {\n  return remainingBytes_ == 0;\n}\n\nuint8_t HPACKDecodeBuffer::next() {\n  CHECK_GT(remainingBytes_, 0);\n  // in case we are the end of an IOBuf, peek() will move to the next one\n  uint8_t byte = peek();\n  cursor_.skip(1);\n  remainingBytes_--;\n\n  return byte;\n}\n\nuint8_t HPACKDecodeBuffer::peek() {\n  CHECK_GT(remainingBytes_, 0);\n  if (cursor_.length() == 0) {\n    cursor_.peek();\n  }\n  return *cursor_.data();\n}\n\nDecodeError HPACKDecodeBuffer::decodeLiteral(folly::fbstring& literal) {\n  return decodeLiteral(7, literal);\n}\n\nDecodeError HPACKDecodeBuffer::decodeLiteral(uint8_t nbit,\n                                             folly::fbstring& literal) {\n  literal.clear();\n  if (remainingBytes_ == 0) {\n    EOB_LOG(\"remainingBytes_ == 0\");\n    return DecodeError::BUFFER_UNDERFLOW;\n  }\n  auto byte = peek();\n  auto huffmanCheck = uint8_t(1 << nbit);\n  bool huffman = byte & huffmanCheck;\n  // extract the size\n  uint64_t size;\n  DecodeError result = decodeInteger(nbit, size);\n  if (result != DecodeError::NONE) {\n    EOB_LOG(\"Could not decode literal size\", result);\n    return result;\n  }\n  if (size > remainingBytes_) {\n    EOB_LOG(folly::to<std::string>(\n        \"size(\", size, \") > remainingBytes_(\", remainingBytes_, \")\"));\n    return DecodeError::BUFFER_UNDERFLOW;\n  }\n  if (size > maxLiteralSize_) {\n    LOG(ERROR) << \"Literal too large, size=\" << size;\n    return DecodeError::LITERAL_TOO_LARGE;\n  }\n  const uint8_t* data;\n  unique_ptr<IOBuf> tmpbuf;\n  // handle the case where the buffer spans multiple buffers\n  if (cursor_.length() >= size) {\n    data = cursor_.data();\n    cursor_.skip(size);\n  } else {\n    // temporary buffer to pull the chunks together\n    tmpbuf = IOBuf::create(size);\n    // pull() will move the cursor\n    cursor_.pull(tmpbuf->writableData(), size);\n    data = tmpbuf->data();\n  }\n  if (huffman) {\n    static auto& huffmanTree = huffman::huffTree();\n    huffmanTree.decode(data, size, literal);\n  } else {\n    literal.append((const char*)data, size);\n  }\n  remainingBytes_ -= size;\n  return DecodeError::NONE;\n}\n\nDecodeError HPACKDecodeBuffer::decodeInteger(uint64_t& integer) {\n  return decodeInteger(8, integer);\n}\n\nDecodeError HPACKDecodeBuffer::decodeInteger(uint8_t nbit, uint64_t& integer) {\n  if (remainingBytes_ == 0) {\n    EOB_LOG(\"remainingBytes_ == 0\");\n    return DecodeError::BUFFER_UNDERFLOW;\n  }\n  uint8_t byte = next();\n  uint8_t mask = HPACK::NBIT_MASKS[nbit];\n  // remove the first (8 - nbit) bits\n  byte = byte & mask;\n  integer = byte;\n  if (byte != mask) {\n    // the value fit in one byte\n    return DecodeError::NONE;\n  }\n  uint64_t f = 1;\n  uint32_t fexp = 0;\n  do {\n    if (remainingBytes_ == 0) {\n      EOB_LOG(\"remainingBytes_ == 0\");\n      return DecodeError::BUFFER_UNDERFLOW;\n    }\n    byte = next();\n    if (fexp > 64) {\n      // overflow in factorizer, f > 2^64\n      LOG(ERROR) << \"overflow fexp=\" << fexp;\n      return DecodeError::INTEGER_OVERFLOW;\n    }\n    uint64_t add = (byte & 127) * f;\n    if (std::numeric_limits<uint64_t>::max() - integer <= add) {\n      // overflow detected - we disallow uint64_t max.\n      LOG(ERROR) << \"overflow integer=\" << integer << \" add=\" << add;\n      return DecodeError::INTEGER_OVERFLOW;\n    }\n    integer += add;\n    f = f << 7;\n    fexp += 7;\n  } while (byte & 128);\n  return DecodeError::NONE;\n}\nnamespace HPACK {\nstd::ostream& operator<<(std::ostream& os, DecodeError err) {\n  return os << static_cast<uint32_t>(err);\n}\n} // namespace HPACK\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <folly/FBString.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/Huffman.h>\n\nnamespace proxygen {\n\nclass HPACKDecodeBuffer {\n public:\n  explicit HPACKDecodeBuffer(folly::io::Cursor& cursorVal,\n                             uint32_t totalBytes,\n                             uint32_t maxLiteralSize,\n                             bool endOfBufferIsError = true)\n      : cursor_(cursorVal),\n        totalBytes_(totalBytes),\n        remainingBytes_(totalBytes),\n        maxLiteralSize_(maxLiteralSize),\n        endOfBufferIsError_(endOfBufferIsError) {\n  }\n\n  ~HPACKDecodeBuffer() = default;\n\n  void reset(folly::io::Cursor& cursorVal) {\n    reset(cursorVal, folly::to<uint32_t>(cursorVal.totalLength()));\n  }\n\n  void reset(folly::io::Cursor& cursorVal, uint32_t totalBytes) {\n    cursor_ = cursorVal;\n    totalBytes_ = totalBytes;\n    remainingBytes_ = totalBytes;\n  }\n\n  [[nodiscard]] uint32_t consumedBytes() const {\n    return totalBytes_ - remainingBytes_;\n  }\n\n  [[nodiscard]] const folly::io::Cursor& cursor() const {\n    return cursor_;\n  }\n\n  /**\n   * @returns true if there are no more bytes to decode. Calling this method\n   * might move the cursor from the current IOBuf to the next one\n   */\n  bool empty();\n\n  /**\n   * extracts one byte from the buffer and advances the cursor\n   */\n  uint8_t next();\n\n  /**\n   * just peeks at the next available byte without moving the cursor\n   */\n  uint8_t peek();\n\n  /**\n   * decode an integer from the current position, given a nbit prefix.\n   * Ignores 8 - nbit bits in the first byte of the buffer.\n   */\n  HPACK::DecodeError decodeInteger(uint8_t nbit, uint64_t& integer);\n\n  /**\n   * As above but with no prefix\n   */\n  HPACK::DecodeError decodeInteger(uint64_t& integer);\n\n  /**\n   * decode a literal starting from the current position\n   */\n  HPACK::DecodeError decodeLiteral(folly::fbstring& literal);\n\n  HPACK::DecodeError decodeLiteral(uint8_t nbit, folly::fbstring& literal);\n\n private:\n  void EOB_LOG(\n      std::string msg,\n      HPACK::DecodeError code = HPACK::DecodeError::BUFFER_UNDERFLOW) const;\n\n  folly::io::Cursor& cursor_;\n  uint32_t totalBytes_;\n  uint32_t remainingBytes_;\n  uint32_t maxLiteralSize_{std::numeric_limits<uint32_t>::max()};\n  bool endOfBufferIsError_{true};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKDecoder.h>\n\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nusing folly::io::Cursor;\n\nnamespace proxygen {\n\nvoid HPACKDecoder::decodeStreaming(Cursor& cursor,\n                                   uint32_t totalBytes,\n                                   HPACK::StreamingCallback* streamingCb) {\n  HPACKDecodeBuffer dbuf(cursor, totalBytes, maxUncompressed_);\n  uint32_t emittedSize = 0;\n\n  while (!hasError() && !dbuf.empty()) {\n    emittedSize += decodeHeader(dbuf, streamingCb, nullptr);\n\n    if (emittedSize > maxUncompressed_) {\n      LOG(ERROR) << \"exceeded uncompressed size limit of \" << maxUncompressed_\n                 << \" bytes\";\n      err_ = HPACK::DecodeError::HEADERS_TOO_LARGE;\n      break;\n    }\n    emittedSize += 2;\n  }\n  auto compressedSize = dbuf.consumedBytes();\n  completeDecode(HeaderCodec::Type::HPACK,\n                 streamingCb,\n                 compressedSize,\n                 compressedSize,\n                 emittedSize);\n}\n\nuint32_t HPACKDecoder::decodeLiteralHeader(\n    HPACKDecodeBuffer& dbuf,\n    HPACK::StreamingCallback* streamingCb,\n    headers_t* emitted) {\n  uint8_t byte = dbuf.peek();\n  bool indexing = byte & HPACK::LITERAL_INC_INDEX.code;\n  HPACKHeader header;\n  uint8_t indexMask = 0x3F; // 0011 1111\n  uint8_t length = HPACK::LITERAL_INC_INDEX.prefixLength;\n  if (!indexing) {\n    // bool neverIndex = byte & HPACK::LITERAL_NEV_INDEX.code;\n    // TODO: we need to emit this flag with the headers\n    indexMask = 0x0F; // 0000 1111\n    length = HPACK::LITERAL.prefixLength;\n  }\n  if (byte & indexMask) {\n    uint64_t index;\n    err_ = dbuf.decodeInteger(length, index);\n    if (err_ != HPACK::DecodeError::NONE) {\n      LOG(ERROR) << \"Decode error decoding index err_=\" << err_;\n      return 0;\n    }\n    // validate the index\n    if (!isValid(index)) {\n      LOG(ERROR) << \"received invalid index: \" << index;\n      err_ = HPACK::DecodeError::INVALID_INDEX;\n      return 0;\n    }\n    header.name = getHeader(index).name;\n  } else {\n    // skip current byte\n    dbuf.next();\n    folly::fbstring headerName;\n    err_ = dbuf.decodeLiteral(headerName);\n    header.name = HPACKHeaderName{headerName};\n    if (err_ != HPACK::DecodeError::NONE) {\n      LOG(ERROR) << \"Error decoding header name err_=\" << err_;\n      return 0;\n    }\n  }\n  // value\n  err_ = dbuf.decodeLiteral(header.value);\n  if (err_ != HPACK::DecodeError::NONE) {\n    LOG(ERROR) << \"Error decoding header value name=\" << header.name\n               << \" err_=\" << err_;\n    return 0;\n  }\n\n  uint32_t emittedSize = emit(header, streamingCb, emitted);\n\n  if (indexing) {\n    auto headerBytes = header.bytes();\n    if (!table_.add(std::move(header))) {\n      // The only way add can return false is clearing the table with a large\n      // entry.  Any other failure would result in compression contexts out of\n      // sync.\n      CHECK_GT(headerBytes, table_.capacity());\n    }\n  }\n\n  return emittedSize;\n}\n\nuint32_t HPACKDecoder::decodeIndexedHeader(\n    HPACKDecodeBuffer& dbuf,\n    HPACK::StreamingCallback* streamingCb,\n    headers_t* emitted) {\n  uint64_t index;\n  err_ = dbuf.decodeInteger(HPACK::INDEX_REF.prefixLength, index);\n  if (err_ != HPACK::DecodeError::NONE) {\n    LOG(ERROR) << \"Decode error decoding index err_=\" << err_;\n    return 0;\n  }\n  // validate the index\n  if (index == 0 || !isValid(index)) {\n    LOG(ERROR) << \"received invalid index: \" << index;\n    err_ = HPACK::DecodeError::INVALID_INDEX;\n    return 0;\n  }\n\n  const auto& header = getHeader(index);\n  return emit(header, streamingCb, emitted);\n}\n\nbool HPACKDecoder::isValid(uint32_t index) {\n  if (isStatic(index)) {\n    return getStaticTable().isValid(globalToStaticIndex(index));\n  } else {\n    return table_.isValid(globalToDynamicIndex(index));\n  }\n}\n\nuint32_t HPACKDecoder::decodeHeader(HPACKDecodeBuffer& dbuf,\n                                    HPACK::StreamingCallback* streamingCb,\n                                    headers_t* emitted) {\n  uint8_t byte = dbuf.peek();\n  if (byte & HPACK::INDEX_REF.code) {\n    return decodeIndexedHeader(dbuf, streamingCb, emitted);\n  } else if (byte & HPACK::LITERAL_INC_INDEX.code) {\n    // else it's fine, fall through to decodeLiteralHeader\n  } else if (byte & HPACK::TABLE_SIZE_UPDATE.code) {\n    handleTableSizeUpdate(dbuf, table_);\n    return 0;\n  } // else LITERAL\n  // LITERAL_NO_INDEXING or LITERAL_INCR_INDEXING\n  return decodeLiteralHeader(dbuf, streamingCb, emitted);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <proxygen/lib/http/codec/compress/HPACKContext.h>\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKDecoderBase.h>\n#include <proxygen/lib/http/codec/compress/HPACKStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen {\n\nclass HPACKDecoder\n    : public HPACKDecoderBase\n    , public HPACKContext {\n public:\n  explicit HPACKDecoder(\n      uint32_t tableSize = HPACK::kTableSize,\n      uint32_t maxUncompressed = HeaderCodec::kMaxUncompressed)\n      : HPACKDecoderBase(tableSize, maxUncompressed), HPACKContext(tableSize) {\n    table_.disableNamesIndex();\n  }\n\n  /**\n   * given a Cursor and a total amount of bytes we can consume from it,\n   * decode headers and invoke a callback.\n   */\n  void decodeStreaming(folly::io::Cursor& cursor,\n                       uint32_t totalBytes,\n                       HPACK::StreamingCallback* streamingCb);\n\n  void setHeaderTableMaxSize(uint32_t maxSize) {\n    HPACKDecoderBase::setHeaderTableMaxSize(table_, maxSize);\n  }\n\n private:\n  bool isValid(uint32_t index);\n\n  uint32_t decodeIndexedHeader(HPACKDecodeBuffer& dbuf,\n                               HPACK::StreamingCallback* streamingCb,\n                               headers_t* emitted);\n\n  uint32_t decodeLiteralHeader(HPACKDecodeBuffer& dbuf,\n                               HPACK::StreamingCallback* streamingCb,\n                               headers_t* emitted);\n\n  uint32_t decodeHeader(HPACKDecodeBuffer& dbuf,\n                        HPACK::StreamingCallback* streamingCb,\n                        headers_t* emitted);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecoderBase.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKDecoderBase.h>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n\nnamespace proxygen {\n\nuint32_t HPACKDecoderBase::emit(const HPACKHeader& header,\n                                HPACK::StreamingCallback* streamingCb,\n                                headers_t* emitted) {\n  if (streamingCb) {\n    streamingCb->onHeader(header.name, header.value);\n  } else if (emitted) {\n    // copying HPACKHeader\n    emitted->emplace_back(header.name.get(), header.value);\n  }\n  return header.realBytes();\n}\n\nvoid HPACKDecoderBase::completeDecode(HeaderCodec::Type type,\n                                      HPACK::StreamingCallback* streamingCb,\n                                      uint32_t compressedSize,\n                                      uint32_t compressedBlockSize,\n                                      uint32_t emittedSize,\n                                      bool acknowledge) {\n  if (!streamingCb) {\n    return;\n  }\n  if (err_ != HPACK::DecodeError::NONE) {\n    if (streamingCb->stats) {\n      if (err_ == HPACK::DecodeError::HEADERS_TOO_LARGE ||\n          err_ == HPACK::DecodeError::LITERAL_TOO_LARGE) {\n        streamingCb->stats->recordDecodeTooLarge(type);\n      } else {\n        streamingCb->stats->recordDecodeError(type);\n      }\n    }\n    streamingCb->onDecodeError(err_);\n  } else {\n    HTTPHeaderSize decodedSize;\n    decodedSize.compressed = compressedSize;\n    decodedSize.compressedBlock = compressedBlockSize,\n    decodedSize.uncompressed = emittedSize;\n    if (streamingCb->stats) {\n      streamingCb->stats->recordDecode(type, decodedSize);\n    }\n    streamingCb->onHeadersComplete(decodedSize, acknowledge);\n  }\n}\n\nvoid HPACKDecoderBase::setHeaderTableMaxSize(HeaderTable& table,\n                                             uint32_t maxSize) {\n  maxTableSize_ = maxSize;\n  if (maxTableSize_ < table.capacity()) {\n    CHECK(table.setCapacity(maxTableSize_));\n  }\n}\n\nvoid HPACKDecoderBase::handleTableSizeUpdate(HPACKDecodeBuffer& dbuf,\n                                             HeaderTable& table,\n                                             bool isQpack) {\n  uint64_t arg = 0;\n  err_ = dbuf.decodeInteger(HPACK::TABLE_SIZE_UPDATE.prefixLength, arg);\n  if (err_ != HPACK::DecodeError::NONE) {\n    if (!isQpack || err_ != HPACK::DecodeError::BUFFER_UNDERFLOW) {\n      LOG(ERROR) << \"Decode error decoding maxSize err_=\" << err_;\n    }\n    return;\n  }\n\n  if (arg > maxTableSize_) {\n    LOG(ERROR) << \"Tried to increase size of the header table to \" << arg\n               << \" maxTableSize_=\" << maxTableSize_;\n    err_ = HPACK::DecodeError::INVALID_TABLE_SIZE;\n    return;\n  }\n  VLOG(5) << \"Received table size update, new size=\" << arg;\n  table.setCapacity(arg);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKDecoderBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n#include <proxygen/lib/http/codec/compress/HPACKStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen {\n\nclass HeaderTable;\n\n/**\n * Common decoder functionality between HPACK and QPACK\n */\nclass HPACKDecoderBase {\n public:\n  HPACKDecoderBase(uint32_t tableSize, uint32_t maxUncompressed)\n      : maxTableSize_(tableSize), maxUncompressed_(maxUncompressed) {\n  }\n\n  using headers_t = std::vector<HPACKHeader>;\n\n  [[nodiscard]] HPACK::DecodeError getError() const {\n    return err_;\n  }\n\n  [[nodiscard]] bool hasError() const {\n    return err_ != HPACK::DecodeError::NONE;\n  }\n\n  void setHeaderTableMaxSize(HeaderTable& table, uint32_t maxSize);\n\n  void setMaxUncompressed(uint64_t maxUncompressed) {\n    maxUncompressed_ = maxUncompressed;\n  }\n\n protected:\n  uint32_t emit(const HPACKHeader& header,\n                HPACK::StreamingCallback* streamingCb,\n                headers_t* emitted);\n\n  void completeDecode(HeaderCodec::Type type,\n                      HPACK::StreamingCallback* streamingCb,\n                      uint32_t compressedSize,\n                      uint32_t compressedBlockSize,\n                      uint32_t emittedSize,\n                      bool acknowledge = false);\n\n  void handleTableSizeUpdate(HPACKDecodeBuffer& dbuf,\n                             HeaderTable& table,\n                             /* used to determine whether or not we log\n                                certain events */\n                             bool isQpack = false);\n\n  HPACK::DecodeError err_{HPACK::DecodeError::NONE};\n  uint32_t maxTableSize_;\n  uint64_t maxUncompressed_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncodeBuffer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/utils/Logging.h>\n\nusing folly::IOBuf;\nusing std::string;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nHPACKEncodeBuffer::HPACKEncodeBuffer(uint32_t growthSize, bool huffmanEnabled)\n    : buf_(&bufQueue_, growthSize),\n      growthSize_(growthSize),\n      huffMin_(huffmanEnabled ? 0 : std::numeric_limits<uint32_t>::max()),\n      huffMax_(huffmanEnabled ? std::numeric_limits<uint32_t>::max() : 0) {\n}\n\nHPACKEncodeBuffer::HPACKEncodeBuffer(uint32_t growthSize)\n    : buf_(&bufQueue_, growthSize), growthSize_(growthSize), huffMax_(0) {\n}\n\nvoid HPACKEncodeBuffer::addHeadroom(uint32_t headroom) {\n  // we expect that this function is called before any encoding happens\n  CHECK(bufQueuePtr_->front() == nullptr);\n  // create a custom IOBuf and add it to the queue\n  unique_ptr<IOBuf> buf = IOBuf::create(std::max(headroom, growthSize_));\n  buf->advance(headroom);\n  bufQueuePtr_->append(std::move(buf));\n}\n\nvoid HPACKEncodeBuffer::append(uint8_t byte) {\n  buf_.push(&byte, 1);\n}\n\nuint32_t HPACKEncodeBuffer::encodeInteger(uint64_t value) {\n  return encodeInteger(value, 0, 8);\n}\n\nuint32_t HPACKEncodeBuffer::encodeInteger(\n    uint64_t value, const HPACK::Instruction& instruction) {\n  return encodeInteger(value, instruction.code, instruction.prefixLength);\n}\n\nuint32_t HPACKEncodeBuffer::encodeInteger(uint64_t value,\n                                          uint8_t instruction,\n                                          uint8_t nbit) {\n  CHECK(nbit > 0 && nbit <= 8);\n  uint32_t count = 0;\n  uint8_t mask = HPACK::NBIT_MASKS[nbit];\n  // The instruction should not extend into mask\n  DCHECK_EQ(instruction & mask, 0);\n\n  // write the first byte\n  uint8_t byte = instruction;\n  if (value < mask) {\n    // fits in the first byte\n    byte |= value;\n    append(byte);\n    return 1;\n  }\n\n  byte |= mask;\n  value -= mask;\n  ++count;\n  append(byte);\n  // variable length encoding\n  while (value >= 128) {\n    byte = 128 | (127 & value);\n    append(byte);\n    value = value >> 7;\n    ++count;\n  }\n  // last byte, which should always fit on 1 byte\n  append(value);\n  ++count;\n  return count;\n}\n\nuint32_t HPACKEncodeBuffer::encodeHuffman(folly::StringPiece literal) {\n  return encodeHuffman(0, 7, literal);\n}\n\n/*\n * Huffman encode the literal and serialize, with an optional leading\n * instruction.  The instruction can be at most 8 - 1 - nbit bits long.  nbit\n * bits of the first byte will contain the prefix of the encoded literal's\n * length.  For HPACK instruction/nbit should always be 0/7.\n *\n * The encoded output looks like this\n *\n * | instruction | 1 | Length... | Huffman Coded Literal |\n */\nuint32_t HPACKEncodeBuffer::encodeHuffman(uint8_t instruction,\n                                          uint8_t nbit,\n                                          folly::StringPiece literal) {\n  static const auto& huffmanTree = huffman::huffTree();\n  uint32_t size = huffmanTree.getEncodeSize(literal);\n  // add the length\n  DCHECK_LE(nbit, 7);\n  auto huffmanOn = uint8_t(1 << nbit);\n  DCHECK_EQ(instruction & huffmanOn, 0);\n  uint32_t count = encodeInteger(size, instruction | huffmanOn, nbit);\n  // ensure we have enough bytes before performing the encoding\n  count += huffmanTree.encode(literal, buf_);\n  return count;\n}\n\nuint32_t HPACKEncodeBuffer::encodeLiteral(folly::StringPiece literal) {\n  return encodeLiteral(0, 7, literal);\n}\n\nuint32_t HPACKEncodeBuffer::encodeLiteral(uint8_t instruction,\n                                          uint8_t nbit,\n                                          folly::StringPiece literal) {\n  if (literal.size() >= huffMin_ && literal.size() <= huffMax_) {\n    return encodeHuffman(instruction, nbit, literal);\n  }\n  // otherwise use simple layout\n  uint32_t count = encodeInteger(literal.size(), instruction, nbit);\n  // copy the entire string\n  buf_.push((uint8_t*)literal.data(), literal.size());\n  count += literal.size();\n  return count;\n}\n\nstring HPACKEncodeBuffer::toBin() {\n  return IOBufPrinter::printBin(bufQueuePtr_->front());\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/FBString.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBufQueue.h>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/Huffman.h>\n\nnamespace proxygen {\n\nclass HPACKEncodeBuffer {\n\n public:\n  HPACKEncodeBuffer(uint32_t growthSize, bool huffmanEnabled);\n\n  explicit HPACKEncodeBuffer(uint32_t growthSize);\n\n  ~HPACKEncodeBuffer() = default;\n\n  void setHuffmanLimits(std::pair<uint32_t, uint32_t> limits) {\n    if (huffMax_ >= huffMin_) {\n      huffMin_ = limits.first;\n      huffMax_ = limits.second;\n    }\n  }\n\n  /**\n   * transfer ownership of the underlying IOBuf's\n   */\n  std::unique_ptr<folly::IOBuf> release() {\n    return bufQueuePtr_->move();\n  }\n\n  /**\n   * Add headroom at the beginning of the IOBufQueue\n   * Meant to be called before encoding anything.\n   */\n  void addHeadroom(uint32_t bytes);\n\n  /**\n   * Encode the integer value using variable-length layout and the given\n   * instruction using an nbit prefix.  Per the spec, prefix is the portion\n   * of value that fits in one byte.\n   * The instruction is given as 1-byte value (not need for shifting) used only\n   * for the first byte. It starts from MSB.\n   *\n   * For example for integer=3, instruction=0x80, nbit=6:\n   *\n   * MSB           LSB\n   * X X 0 0 0 0 1 1 (value)\n   * 1 0 X X X X X X (instruction)\n   * 1 0 0 0 0 0 1 1 (encoded value)\n   *\n   * @return how many bytes were used to encode the value\n   */\n  uint32_t encodeInteger(uint64_t value, uint8_t instruction, uint8_t nbit);\n\n  uint32_t encodeInteger(uint64_t value, const HPACK::Instruction& instruction);\n\n  uint32_t encodeInteger(uint64_t value);\n\n  /**\n   * encodes a string, either header name or header value\n   *\n   * @return bytes used for encoding\n   */\n  uint32_t encodeLiteral(folly::StringPiece literal);\n\n  /**\n   * encodes a string, either header name or header value QPACK style, where\n   * literal length has an nbit prefix.\n   *\n   * @return bytes used for encoding\n   */\n  uint32_t encodeLiteral(uint8_t instruction,\n                         uint8_t nbit,\n                         folly::StringPiece literal);\n\n  /**\n   * encodes a string using huffman encoding\n   */\n  uint32_t encodeHuffman(folly::StringPiece literal);\n\n  /**\n   * encodes a string using huffman encoding QPACK style, where\n   * literal length has an nbit prefix.\n   */\n  uint32_t encodeHuffman(uint8_t instruction,\n                         uint8_t nbit,\n                         folly::StringPiece literal);\n\n  /**\n   * prints the content of an IOBuf in binary format. Useful for debugging.\n   */\n  std::string toBin();\n\n  void setWriteBuf(folly::IOBufQueue* writeBuf) {\n    if (writeBuf) {\n      bufQueuePtr_ = writeBuf;\n    } else {\n      bufQueuePtr_ = &bufQueue_;\n    }\n    buf_.reset(bufQueuePtr_, growthSize_);\n  }\n\n private:\n  /**\n   * append one byte at the end of buffer ensuring we always have enough space\n   */\n  void append(uint8_t byte);\n\n  folly::IOBufQueue bufQueue_;\n  folly::IOBufQueue* bufQueuePtr_{&bufQueue_};\n  folly::io::QueueAppender buf_;\n  uint32_t growthSize_;\n  uint32_t huffMin_{0};\n  uint32_t huffMax_{std::numeric_limits<uint32_t>::max()};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n\nusing std::vector;\n\nnamespace proxygen {\n\nstd::unique_ptr<folly::IOBuf> HPACKEncoder::encode(\n    const vector<HPACKHeader>& headers, uint32_t headroom) {\n  if (headroom) {\n    streamBuffer_.addHeadroom(headroom);\n  }\n  handlePendingContextUpdate(streamBuffer_, table_.capacity());\n  for (const auto& header : headers) {\n    encodeHeader(header.name, header.value); // const, string piece\n  }\n  return streamBuffer_.release();\n}\n\nvoid HPACKEncoder::encode(const vector<HPACKHeader>& headers,\n                          folly::IOBufQueue& writeBuf) {\n  streamBuffer_.setWriteBuf(&writeBuf);\n  handlePendingContextUpdate(streamBuffer_, table_.capacity());\n  for (const auto& header : headers) {\n    encodeHeader(header.name, header.value); // const, string piece\n  }\n  streamBuffer_.setWriteBuf(nullptr);\n}\n\nvoid HPACKEncoder::startEncode(folly::IOBufQueue& writeBuf) {\n  streamBuffer_.setWriteBuf(&writeBuf);\n  handlePendingContextUpdate(streamBuffer_, table_.capacity());\n}\n\nvoid HPACKEncoder::completeEncode() {\n  streamBuffer_.setWriteBuf(nullptr);\n}\n\nsize_t HPACKEncoder::encodeHeader(HTTPHeaderCode code,\n                                  const std::string& value) {\n  DCHECK_NE(code, HTTP_HEADER_OTHER);\n  HPACKHeaderName name(code);\n  size_t uncompressed = name.size() + value.size() + 2;\n  encodeHeader(name, value); // const, string piece\n  return uncompressed;\n}\n\nsize_t HPACKEncoder::encodeHeader(HTTPHeaderCode code,\n                                  folly::fbstring&& value) {\n  DCHECK_NE(code, HTTP_HEADER_OTHER);\n  HPACKHeaderName name(code);\n  size_t uncompressed = name.size() + value.size() + 2;\n  encodeHeader(std::move(name), std::move(value));\n  return uncompressed;\n}\n\nsize_t HPACKEncoder::encodeHeader(const std::string& nameStr,\n                                  const std::string& value) {\n  HPACKHeaderName name(nameStr);\n  size_t uncompressed = name.size() + value.size() + 2;\n  encodeHeader(std::move(name), folly::StringPiece(value)); // &&, StringPiece\n  return uncompressed;\n}\n\nvoid HPACKEncoder::encodeAsLiteralImpl(const HPACKHeaderName& name,\n                                       uint32_t nameIndex,\n                                       folly::StringPiece value,\n                                       bool& indexing) {\n  if (HPACKHeader::bytes(name.size(), value.size()) > table_.capacity()) {\n    // May want to investigate further whether or not this is wanted.\n    // Flushing the table on a large header frees up some memory,\n    // however, there will be no compression due to an empty table, and\n    // the table will fill up again fairly quickly\n    indexing = false;\n  }\n\n  HPACK::Instruction instruction =\n      (indexing) ? HPACK::LITERAL_INC_INDEX : HPACK::LITERAL;\n\n  encodeLiteral(name, value, nameIndex, instruction);\n}\n\nbool HPACKEncoder::encodeAsLiteral(const HPACKHeaderName& name,\n                                   uint32_t nameIndex,\n                                   folly::StringPiece value,\n                                   bool indexing) {\n  encodeAsLiteralImpl(name, nameIndex, value, indexing);\n  // indexed ones need to get added to the header table\n  if (indexing) {\n    CHECK(table_.add(HPACKHeader(name, value)));\n  }\n  return true;\n}\n\nbool HPACKEncoder::encodeAsLiteral(HPACKHeaderName&& name,\n                                   uint32_t nameIndex,\n                                   folly::fbstring&& value,\n                                   bool indexing) {\n  encodeAsLiteralImpl(name, nameIndex, value, indexing);\n  // indexed ones need to get added to the header table\n  if (indexing) {\n    CHECK(table_.add(HPACKHeader(std::move(name), std::move(value))));\n  }\n  return true;\n}\n\nbool HPACKEncoder::encodeAsLiteral(HPACKHeaderName&& name,\n                                   uint32_t nameIndex,\n                                   folly::StringPiece value,\n                                   bool indexing) {\n  encodeAsLiteralImpl(name, nameIndex, value, indexing);\n  // indexed ones need to get added to the header table\n  if (indexing) {\n    CHECK(table_.add(HPACKHeader(std::move(name), value)));\n  }\n  return true;\n}\n\nvoid HPACKEncoder::encodeLiteral(const HPACKHeaderName& name,\n                                 folly::StringPiece value,\n                                 uint32_t nameIndex,\n                                 const HPACK::Instruction& instruction) {\n  // name\n  if (nameIndex) {\n    VLOG(10) << \"encoding name index=\" << nameIndex;\n    streamBuffer_.encodeInteger(nameIndex, instruction);\n  } else {\n    streamBuffer_.encodeInteger(0, instruction);\n    streamBuffer_.encodeLiteral(name.get());\n  }\n  // value\n  streamBuffer_.encodeLiteral(value);\n}\n\nvoid HPACKEncoder::encodeAsIndex(uint32_t index) {\n  VLOG(10) << \"encoding index=\" << index;\n  streamBuffer_.encodeInteger(index, HPACK::INDEX_REF);\n}\n\nfolly::Optional<uint32_t> HPACKEncoder::encodeHeaderImpl(\n    const HPACKHeaderName& name, folly::StringPiece value, bool& indexable) {\n  uint32_t index = 0;\n  uint32_t nameIndex = 0;\n  // Check to see if the header is the static or dynamic table\n  std::tie(index, nameIndex) = getIndex(name, value);\n\n  // Finally encode the header as determined above\n  if (index) {\n    encodeAsIndex(index);\n    return folly::none;\n  } else {\n    indexable =\n        HPACKHeader::bytes(name.size(), value.size()) <= table_.capacity() &&\n        (!indexingStrat_ ||\n         indexingStrat_->indexHeader(\n             name, value, nameIndex > 0 && !isStatic(nameIndex)));\n\n    // caller must encodeAsLiteral\n    return nameIndex;\n  }\n}\n\nvoid HPACKEncoder::encodeHeader(const HPACKHeaderName& name,\n                                folly::StringPiece value) {\n  bool indexable = false;\n  auto nameIndex = encodeHeaderImpl(name, value, indexable);\n  if (nameIndex) {\n    encodeAsLiteral(name, *nameIndex, value, indexable);\n  }\n}\n\nvoid HPACKEncoder::encodeHeader(HPACKHeaderName&& name,\n                                folly::fbstring&& value) {\n  bool indexable = false;\n  auto nameIndex = encodeHeaderImpl(name, value, indexable);\n  if (nameIndex) {\n    encodeAsLiteral(std::move(name), *nameIndex, std::move(value), indexable);\n  }\n}\n\nvoid HPACKEncoder::encodeHeader(HPACKHeaderName&& name,\n                                folly::StringPiece value) {\n  bool indexable = false;\n  auto nameIndex = encodeHeaderImpl(name, value, indexable);\n  if (nameIndex) {\n    encodeAsLiteral(std::move(name), *nameIndex, value, indexable);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBuf.h>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoderBase.h>\n#include <vector>\n\nnamespace proxygen {\n\nclass HPACKEncoder\n    : public HPACKEncoderBase\n    , public HPACKContext {\n\n public:\n  explicit HPACKEncoder(bool huffman, uint32_t tableSize = HPACK::kTableSize)\n      : HPACKEncoderBase(huffman), HPACKContext(tableSize) {\n  }\n\n  /**\n   * Encode the given headers.\n   */\n\n  std::unique_ptr<folly::IOBuf> encode(const std::vector<HPACKHeader>& headers,\n                                       uint32_t headroom = 0);\n\n  void encode(const std::vector<HPACKHeader>& headers,\n              folly::IOBufQueue& writeBuf);\n\n  void startEncode(folly::IOBufQueue& writeBuf);\n\n  size_t encodeHeader(HTTPHeaderCode code, const std::string& value);\n\n  size_t encodeHeader(HTTPHeaderCode code, folly::fbstring&& value);\n\n  size_t encodeHeader(const std::string& name, const std::string& value);\n\n  void completeEncode();\n\n  void setHeaderTableSize(uint32_t size) {\n    HPACKEncoderBase::setHeaderTableSize(table_, size);\n  }\n\n private:\n  void encodeAsIndex(uint32_t index);\n\n  // movable name and value\n  void encodeHeader(HPACKHeaderName&& name, folly::fbstring&& value);\n\n  // movable name\n  void encodeHeader(HPACKHeaderName&& name, folly::StringPiece value);\n\n  // neither movable\n  void encodeHeader(const HPACKHeaderName& name, folly::StringPiece value);\n\n  // Returns folly::none if the header was encoded, or a nameIndex to use\n  // when encoding the literal if not (may be 0)\n  folly::Optional<uint32_t> encodeHeaderImpl(const HPACKHeaderName& name,\n                                             folly::StringPiece value,\n                                             bool& indexable);\n\n  bool encodeAsLiteral(HPACKHeaderName&& name,\n                       uint32_t nameIndex,\n                       folly::fbstring&& value,\n                       bool indexing);\n\n  bool encodeAsLiteral(HPACKHeaderName&& name,\n                       uint32_t nameIndex,\n                       folly::StringPiece value,\n                       bool indexing);\n\n  bool encodeAsLiteral(const HPACKHeaderName& name,\n                       uint32_t nameIndex,\n                       folly::StringPiece value,\n                       bool indexing);\n\n  void encodeAsLiteralImpl(const HPACKHeaderName& name,\n                           uint32_t nameIndex,\n                           folly::StringPiece value,\n                           bool& indexing);\n\n  void encodeLiteral(const HPACKHeaderName& name,\n                     folly::StringPiece value,\n                     uint32_t nameIndex,\n                     const HPACK::Instruction& instruction);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncoderBase.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKEncoderBase.h>\n\nnamespace proxygen {\n\nuint32_t HPACKEncoderBase::handlePendingContextUpdate(HPACKEncodeBuffer& buf,\n                                                      uint32_t tableCapacity) {\n  CHECK_EQ(HPACK::TABLE_SIZE_UPDATE.code, HPACK::Q_TABLE_SIZE_UPDATE.code)\n      << \"Code assumes these are equal\";\n  uint32_t encoded = 0;\n  if (pendingContextUpdate_) {\n    VLOG(5) << \"Encoding table size update size=\" << tableCapacity;\n    encoded = buf.encodeInteger(tableCapacity, HPACK::TABLE_SIZE_UPDATE);\n    pendingContextUpdate_ = false;\n  }\n\n  return encoded;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKEncoderBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKContext.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n\nnamespace proxygen {\n\n/**\n * Common encoder functionality between HPACK and QPACK\n */\nclass HPACKEncoderBase {\n public:\n  explicit HPACKEncoderBase(bool huffman)\n      : streamBuffer_(kBufferGrowth, huffman) {\n    indexingStrat_ = HeaderIndexingStrategy::getDefaultInstance();\n    // if huffman is false, the huffman limits don't matter\n    streamBuffer_.setHuffmanLimits(indexingStrat_->getHuffmanLimits());\n  }\n\n  /**\n   * Size of a new IOBuf which is added to the chain\n   *\n   * jemalloc will round up to 4k - overhead\n   */\n  static const uint32_t kBufferGrowth = 4000;\n\n  void setHeaderTableSize(HeaderTable& table, uint32_t size) {\n    if (size != table.capacity()) {\n      CHECK(table.setCapacity(size));\n      pendingContextUpdate_ = true;\n    }\n  }\n\n  void setHeaderIndexingStrategy(const HeaderIndexingStrategy* indexingStrat) {\n    indexingStrat_ = indexingStrat;\n    if (indexingStrat_) {\n      streamBuffer_.setHuffmanLimits(indexingStrat_->getHuffmanLimits());\n    }\n  }\n  const HeaderIndexingStrategy* getHeaderIndexingStrategy() const {\n    return indexingStrat_;\n  }\n\n protected:\n  uint32_t handlePendingContextUpdate(HPACKEncodeBuffer& buf,\n                                      uint32_t tableCapacity);\n\n  const HeaderIndexingStrategy* indexingStrat_;\n  HPACKEncodeBuffer streamBuffer_;\n  bool pendingContextUpdate_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKHeader.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\nnamespace proxygen {\n\nstd::ostream& operator<<(std::ostream& os, const HPACKHeader& h) {\n  os << h.name << \": \" << h.value;\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKHeader.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <folly/FBString.h>\n#include <glog/logging.h>\n#include <ostream>\n#include <proxygen/lib/http/codec/compress/HPACKHeaderName.h>\n#include <string_view>\n\nnamespace proxygen {\n\nclass HPACKHeader {\n public:\n  static const uint32_t kMinLength = 32;\n\n  HPACKHeader() = default;\n\n  HPACKHeader(const HPACKHeaderName& name_, std::string_view value_)\n      : name(name_), value(value_.data(), value_.size()) {\n  }\n\n  // this one is usually with a code\n  HPACKHeader(const HPACKHeaderName& name_, folly::fbstring&& value_)\n      : name(name_), value(std::move(value_)) {\n  }\n\n  HPACKHeader(HPACKHeaderName&& name_, folly::fbstring&& value_)\n      : name(std::move(name_)), value(std::move(value_)) {\n  }\n\n  HPACKHeader(HPACKHeaderName&& name_, std::string_view value_)\n      : name(std::move(name_)), value(value_.data(), value_.size()) {\n  }\n\n  HPACKHeader(std::string_view name_, std::string_view value_)\n      : name(name_), value(value_.data(), value_.size()) {\n  }\n\n  HPACKHeader(HPACKHeader&& goner) noexcept\n      : name(std::move(goner.name)), value(std::move(goner.value)) {\n  }\n\n  HPACKHeader& operator=(HPACKHeader&& goner) noexcept {\n    std::swap(name, goner.name);\n    std::swap(value, goner.value);\n    return *this;\n  }\n\n  ~HPACKHeader() = default;\n\n  /**\n   * size of usable bytes of the header entry, does not include overhead\n   */\n  [[nodiscard]] uint32_t realBytes() const {\n    return realBytes(name.size(), value.size());\n  }\n\n  static uint32_t realBytes(uint64_t nameSize, uint64_t valueSize) {\n    DCHECK_LE(nameSize + valueSize, std::numeric_limits<uint32_t>::max());\n    return folly::tryTo<uint32_t>(nameSize + valueSize)\n        .value_or(std::numeric_limits<uint32_t>::max());\n  }\n\n  /**\n   * size in bytes of the header entry, as defined in the HPACK spec\n   */\n  [[nodiscard]] uint32_t bytes() const {\n    return kMinLength + realBytes();\n  }\n\n  static uint32_t bytes(uint64_t nameSize, uint64_t valueSize) {\n    DCHECK_LE(kMinLength + nameSize + valueSize,\n              std::numeric_limits<uint32_t>::max());\n    return folly::tryTo<uint32_t>(kMinLength + realBytes(nameSize, valueSize))\n        .value_or(std::numeric_limits<uint32_t>::max());\n  }\n\n  bool operator==(const HPACKHeader& other) const {\n    return name == other.name && value == other.value;\n  }\n  bool operator<(const HPACKHeader& other) const {\n    bool eqname = (name == other.name);\n    if (!eqname) {\n      return name < other.name;\n    }\n    return value < other.value;\n  }\n  bool operator>(const HPACKHeader& other) const {\n    bool eqname = (name == other.name);\n    if (!eqname) {\n      return name > other.name;\n    }\n    return value > other.value;\n  }\n\n  [[nodiscard]] HPACKHeader copy() const {\n    return HPACKHeader(name, value);\n  }\n\n  /**\n   * Some header entries don't have a value, see StaticHeaderTable\n   */\n  [[nodiscard]] bool hasValue() const {\n    return !value.empty();\n  }\n\n  HPACKHeaderName name;\n  folly::fbstring value;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HPACKHeader& h);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKHeaderName.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <folly/String.h>\n#include <functional>\n#include <glog/logging.h>\n#include <iostream>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <string>\n#include <string_view>\n\nnamespace proxygen {\n\n/*\n * HPACKHeaderName stores the header name of HPACKHeaders. If\n * the header name is in HTTPCommonHeader, it will store a\n * pointer to the common header, otherwise, it will store a\n * pointer to a the dynamically allocated std::string\n */\nclass HPACKHeaderName {\n public:\n  HPACKHeaderName() = default;\n  explicit HPACKHeaderName(std::string_view name) {\n    storeAddress(name);\n  }\n  explicit HPACKHeaderName(HTTPHeaderCode headerCode) {\n    CHECK_NE(headerCode, HTTPHeaderCode::HTTP_HEADER_NONE);\n    CHECK_NE(headerCode, HTTPHeaderCode::HTTP_HEADER_OTHER);\n    address_ = HTTPCommonHeaders::getPointerToName(\n        headerCode, HTTPCommonHeaderTableType::TABLE_LOWERCASE);\n  }\n  HPACKHeaderName(const HPACKHeaderName& headerName) {\n    copyAddress(headerName);\n  }\n  HPACKHeaderName(HPACKHeaderName&& goner) noexcept {\n    moveAddress(std::move(goner));\n  }\n  HPACKHeaderName& operator=(const HPACKHeaderName& headerName) {\n    if (this != &headerName) {\n      resetAddress();\n      copyAddress(headerName);\n    }\n    return *this;\n  }\n  HPACKHeaderName& operator=(HPACKHeaderName&& goner) noexcept {\n    if (this != &goner) {\n      resetAddress();\n      moveAddress(std::move(goner));\n    }\n    return *this;\n  }\n\n  ~HPACKHeaderName() {\n    resetAddress();\n  }\n\n  /*\n   * Compare the strings stored in HPACKHeaderName\n   */\n  bool operator==(const HPACKHeaderName& headerName) const {\n    return address_ == headerName.address_ ||\n           *address_ == *(headerName.address_);\n  }\n  bool operator!=(const HPACKHeaderName& headerName) const {\n    // Utilize the == overloaded operator\n    return !(*this == headerName);\n  }\n  bool operator>(const HPACKHeaderName& headerName) const {\n    if (!isAllocated() && !headerName.isAllocated()) {\n      // Common header tables are aligned alphabetically (unit tested as well\n      // to ensure it isn't accidentally changed)\n      return address_ > headerName.address_;\n    }\n    return *address_ > *(headerName.address_);\n  }\n  bool operator<(const HPACKHeaderName& headerName) const {\n    if (!isAllocated() && !headerName.isAllocated()) {\n      // Common header tables are aligned alphabetically (unit tested as well\n      // to ensure it isn't accidentally changed)\n      return address_ < headerName.address_;\n    }\n    return *address_ < *(headerName.address_);\n  }\n  bool operator>=(const HPACKHeaderName& headerName) const {\n    // Utilize existing < overloaded operator\n    return !(*this < headerName);\n  }\n  bool operator<=(const HPACKHeaderName& headerName) const {\n    // Utilize existing > overload operator\n    return !(*this > headerName);\n  }\n\n  /*\n   * Return std::string stored in HPACKHeaderName\n   */\n  [[nodiscard]] const std::string& get() const {\n    return *address_;\n  }\n\n  /*\n   * Returns the HTTPHeaderCode associated with the wrapped address_\n   */\n  [[nodiscard]] HTTPHeaderCode getHeaderCode() const {\n    return HTTPCommonHeaders::getCodeFromTableName(\n        address_, HTTPCommonHeaderTableType::TABLE_LOWERCASE);\n  }\n\n  /*\n   * Returns whether the name pointed to by this instance is a common header\n   */\n  [[nodiscard]] bool isCommonHeader() const {\n    return HTTPCommonHeaders::isNameFromTable(\n        address_, HTTPCommonHeaderTableType::TABLE_LOWERCASE);\n  }\n\n  /*\n   * Exposing wrapped std::string member properties\n   */\n  [[nodiscard]] uint32_t size() const {\n    return (uint32_t)(address_->size());\n  }\n  [[nodiscard]] const char* c_str() const {\n    return address_->c_str();\n  }\n\n private:\n  /*\n   * Store a reference to either a common header or newly allocated string\n   */\n  void storeAddress(std::string_view name) {\n    HTTPHeaderCode headerCode =\n        HTTPCommonHeaders::hash(name.data(), name.size());\n    if (headerCode == HTTPHeaderCode::HTTP_HEADER_NONE ||\n        headerCode == HTTPHeaderCode::HTTP_HEADER_OTHER) {\n      auto newAddress = new std::string(name);\n      folly::toLowerAscii(*newAddress);\n      address_ = newAddress;\n    } else {\n      address_ = HTTPCommonHeaders::getPointerToName(\n          headerCode, HTTPCommonHeaderTableType::TABLE_LOWERCASE);\n    }\n  }\n\n  /*\n   * Copy the address_ from another HPACKHeaderName\n   */\n  void copyAddress(const HPACKHeaderName& headerName) {\n    if (headerName.isAllocated()) {\n      address_ = new std::string(headerName.get());\n    } else {\n      address_ = headerName.address_;\n    }\n  }\n\n  /*\n   * Move the address_ from another HPACKHeaderName\n   */\n  void moveAddress(HPACKHeaderName&& goner) {\n    address_ = std::exchange(goner.address_, nullptr);\n  }\n\n  /*\n   * Delete any allocated memory and reset address_ to nullptr\n   */\n  void resetAddress() {\n    if (isAllocated()) {\n      delete address_;\n    }\n    address_ = nullptr;\n  }\n\n  /*\n   * Returns whether the underlying address_ points to a string that was\n   * allocated (memory) by this instance\n   */\n  [[nodiscard]] bool isAllocated() const {\n    if (address_ == nullptr) {\n      return false;\n    }\n    return !HTTPCommonHeaders::isNameFromTable(\n        address_, HTTPCommonHeaderTableType::TABLE_LOWERCASE);\n  }\n\n  /*\n   * Address either stores a pointer to a header name in HTTPCommonHeaders,\n   * or stores a pointer to a dynamically allocated std::string\n   */\n  const std::string* address_ = nullptr;\n};\n\ninline std::ostream& operator<<(std::ostream& os, const HPACKHeaderName& name) {\n  os << name.get();\n  return os;\n}\n\n} // namespace proxygen\n\nnamespace std {\n\ntemplate <>\nstruct hash<proxygen::HPACKHeaderName> {\n  size_t operator()(const proxygen::HPACKHeaderName& headerName) const {\n    auto code = headerName.getHeaderCode();\n    if (code == proxygen::HTTPHeaderCode::HTTP_HEADER_OTHER) {\n      return std::hash<std::string>()(headerName.get());\n    }\n    return std::hash<uint8_t>()(code);\n  }\n};\n\n} // namespace std\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HPACKStreamingCallback.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeaderName.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen::HPACK {\nclass StreamingCallback {\n public:\n  virtual ~StreamingCallback() = default;\n\n  virtual void onHeader(const HPACKHeaderName& name,\n                        const folly::fbstring& value) = 0;\n  virtual void onHeadersComplete(HTTPHeaderSize decodedSize,\n                                 bool acknowledge) = 0;\n  virtual void onDecodeError(HPACK::DecodeError decodeError) = 0;\n  HeaderCodec::Stats* stats{nullptr};\n};\n\n} // namespace proxygen::HPACK\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/Header.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <string>\n\nnamespace proxygen::compress {\n\n/**\n * Helper structure used when serializing the uncompressed\n * representation of a header name/value list.\n */\nstruct Header {\n  HTTPHeaderCode code;\n  const std::string* name;\n  const std::string* value;\n\n  Header(HTTPHeaderCode c, const std::string& v)\n      : code(c), name(HTTPCommonHeaders::getPointerToName(c)), value(&v) {\n  }\n\n  Header(HTTPHeaderCode c, const std::string& n, const std::string& v)\n      : code(c), name(&n), value(&v) {\n  }\n\n  bool operator<(const Header& h) const {\n    return (code < h.code) || ((code == h.code) && (*name < *h.name));\n  }\n\n  // For use by tests\n  static Header makeHeaderForTest(const std::string& n, const std::string& v) {\n    return Header(n, v);\n  }\n\n private:\n  // This constructor ideally should not be used in production code\n  // This is because in prod the common header code is likely already known and\n  // an above constructor could be used; this exists for test purposes\n  Header(const std::string& n, const std::string& v)\n      : code(HTTPCommonHeaders::hash(n)), name(&n), value(&v) {\n  }\n};\n\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <folly/FBString.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/compress/Header.h>\n#include <proxygen/lib/http/codec/compress/HeaderPiece.h>\n\nnamespace folly {\nclass IOBuf;\nclass IOBufQueue;\n} // namespace folly\n\nnamespace folly::io {\nclass Cursor;\n} // namespace folly::io\n\nnamespace proxygen {\n\nstruct HeaderDecodeResult {\n  compress::HeaderPieceList& headers;\n  uint32_t bytesConsumed;\n};\n\nclass HeaderCodec {\n public:\n  const static uint32_t kMaxUncompressed = 128 * 1024;\n\n  enum class Type : uint8_t { GZIP = 0, HPACK = 1, QPACK = 2 };\n\n  class Stats {\n   public:\n    Stats() = default;\n    virtual ~Stats() = default;\n\n    virtual void recordEncode(Type type, HTTPHeaderSize& size) = 0;\n    virtual void recordDecode(Type type, HTTPHeaderSize& size) = 0;\n    virtual void recordDecodeError(Type type) = 0;\n    virtual void recordDecodeTooLarge(Type type) = 0;\n  };\n\n  HeaderCodec() = default;\n  virtual ~HeaderCodec() = default;\n\n  /**\n   * compressed and uncompressed size of the last encode\n   */\n  [[nodiscard]] const HTTPHeaderSize& getEncodedSize() const {\n    return encodedSize_;\n  }\n\n  /**\n   * amount of space to reserve as a headroom in the encode buffer\n   */\n  void setEncodeHeadroom(uint32_t headroom) {\n    encodeHeadroom_ = headroom;\n  }\n\n  virtual void setMaxUncompressed(uint64_t maxUncompressed) {\n    maxUncompressed_ = maxUncompressed;\n  }\n\n  [[nodiscard]] uint64_t getMaxUncompressed() const {\n    return maxUncompressed_;\n  }\n\n  /**\n   * set the stats object\n   */\n  void setStats(Stats* stats) {\n    stats_ = stats;\n  }\n\n protected:\n  HTTPHeaderSize encodedSize_;\n  uint32_t encodeHeadroom_{0};\n  uint64_t maxUncompressed_{kMaxUncompressed};\n  Stats* stats_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderIndexingStrategy.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n\nnamespace proxygen {\n\nconst HeaderIndexingStrategy* HeaderIndexingStrategy::getDefaultInstance() {\n  static const HeaderIndexingStrategy* instance = new HeaderIndexingStrategy();\n  return instance;\n}\n\nbool HeaderIndexingStrategy::indexHeader(const HPACKHeaderName& name,\n                                         folly::StringPiece value,\n                                         bool) const {\n  // Handle all the cases where we want to return false in the switch statement\n  // below; else let the code fall through and return true\n  switch (name.getHeaderCode()) {\n    case HTTP_HEADER_COLON_PATH:\n      if (value.find('=') != std::string::npos) {\n        return false;\n      }\n      if (value.find(\"jpg\") != std::string::npos) {\n        return false;\n      }\n      break;\n\n    // The wrapped header should never be HTTP_HEADER_NONE but for completeness\n    // the condition is included below\n    case HTTP_HEADER_NONE:\n    case HTTP_HEADER_CONTENT_LENGTH:\n    case HTTP_HEADER_IF_MODIFIED_SINCE:\n    case HTTP_HEADER_LAST_MODIFIED:\n      return false;\n\n    default:\n      break;\n  }\n\n  return true;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\nnamespace proxygen {\n\nclass HeaderIndexingStrategy {\n public:\n  static const HeaderIndexingStrategy* getDefaultInstance();\n\n  // Explicitly defined constructor/destructor\n  // Destructor is virtual so that a subclass can provide an implementation\n  // and that it will be correctly called even when aliased by a\n  // HPACKEnoderStrat* var\n  HeaderIndexingStrategy() = default;\n  virtual ~HeaderIndexingStrategy() = default;\n\n  // Virtual method for subclasses to implement as they see fit\n  // Returns a bool that indicates whether the specified header should be\n  // indexed\n  [[nodiscard]] virtual bool indexHeader(const HPACKHeaderName& name,\n                                         folly::StringPiece value,\n                                         bool nameExists = false) const;\n\n  // Only apply huffman to literal strings in the range [first, second]\n  // Huffman encoding small strings doesn't save that many bytes\n  // Huffman encoding very large strings is expensive\n  [[nodiscard]] virtual std::pair<uint32_t, uint32_t> getHuffmanLimits() const {\n    return {0, std::numeric_limits<uint32_t>::max()};\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderPiece.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <deque>\n\n#include <glog/logging.h>\n\n#include <folly/Range.h>\n\nnamespace proxygen::compress {\n\n/**\n * in-place representation of a header name or value\n */\nclass HeaderPiece {\n public:\n  /**\n   * Construct a view around the data\n   */\n  HeaderPiece(const char* inData,\n              uint32_t inLen,\n              bool inOwner,\n              bool inMultiValued)\n      : str(inData, inLen), owner(inOwner), multiValued(inMultiValued) {\n  }\n\n  HeaderPiece(HeaderPiece&& goner) noexcept\n      : str(goner.str), owner(goner.owner), multiValued(goner.multiValued) {\n    goner.owner = false;\n  }\n\n  ~HeaderPiece() {\n    if (owner) {\n      CHECK_NOTNULL(str.data());\n      delete[] str.data();\n    }\n  }\n\n  [[nodiscard]] bool isMultiValued() const {\n    return multiValued;\n  }\n\n  // should be const, but for one use in GzipHeaderCodec\n  folly::StringPiece str;\n\n private:\n  bool owner;\n  bool multiValued;\n};\n\nusing HeaderPieceList = std::deque<HeaderPiece>;\n\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderTable.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\nuint32_t HeaderTable::initialTableLength(uint32_t capacity) {\n  auto maxTableLength = getMaxTableLength(capacity);\n  return (maxTableLength == 1) ? 1 : (maxTableLength / 2);\n}\n\nvoid HeaderTable::init(uint32_t capacityVal) {\n  bytes_ = 0;\n  size_ = 0;\n  head_ = 0;\n  capacity_ = capacityVal;\n  uint32_t initLength = initialTableLength(capacity_);\n  table_.reserve(initLength);\n  for (uint32_t i = 0; i < initLength; i++) {\n    table_.emplace_back();\n  }\n  names_.clear();\n}\n\nbool HeaderTable::add(HPACKHeader header) {\n  if (header.bytes() > capacity_) {\n    // Per the RFC spec https://tools.ietf.org/html/rfc7541#page-11, we must\n    // flush the underlying table if a request is made for a header that is\n    // larger than the current table capacity\n    reset();\n    return false;\n  }\n\n  // Make the necessary room in the table if appropriate per RFC spec\n  if ((bytes_ + header.bytes()) > capacity_) {\n    if (evict(header.bytes(), capacity_) == 0) {\n      return false;\n    }\n  }\n\n  if (size_ == length()) {\n    increaseTableLengthTo(\n        std::min((uint32_t)ceil(size_ * 1.5), getMaxTableLength(capacity_)));\n  }\n  head_ = next(head_);\n  // index name\n  if (indexNames_) {\n    names_[header.name].push_back(head_);\n  }\n  bytes_ += header.bytes();\n  table_[head_] = std::move(header);\n\n  ++size_;\n  ++insertCount_;\n  return true;\n}\n\nstd::pair<uint32_t, uint32_t> HeaderTable::getIndex(\n    const HPACKHeader& header) const {\n  return getIndexImpl(header.name, header.value, false);\n}\n\nstd::pair<uint32_t, uint32_t> HeaderTable::getIndex(\n    const HPACKHeaderName& name, folly::StringPiece value) const {\n  return getIndexImpl(name, value, false);\n}\n\nstd::pair<uint32_t, uint32_t> HeaderTable::getIndexImpl(\n    const HPACKHeaderName& headerName,\n    folly::StringPiece value,\n    bool nameOnly) const {\n  CHECK(indexNames_);\n  auto it = names_.find(headerName);\n  if (it == names_.end()) {\n    return {0, 0};\n  }\n  for (auto indexIt = it->second.rbegin(); indexIt != it->second.rend();\n       ++indexIt) {\n    auto i = *indexIt;\n    if (nameOnly || table_[i].value == value) {\n      return {toExternal(i), 0};\n    }\n  }\n  return {0, toExternal(*it->second.rbegin())};\n}\n\nbool HeaderTable::hasName(const HPACKHeaderName& headerName) {\n  CHECK(indexNames_);\n  return names_.find(headerName) != names_.end();\n}\n\nuint32_t HeaderTable::nameIndex(const HPACKHeaderName& headerName) const {\n  folly::StringPiece value;\n  return getIndexImpl(headerName, value, true /* name only */).first;\n}\n\nconst HPACKHeader& HeaderTable::getHeader(uint32_t index) const {\n  CHECK(isValid(index));\n  return table_[toInternal(index)];\n}\n\nuint32_t HeaderTable::getMaxTableLength(uint32_t capacityVal) const {\n  // At a minimum an entry will take 32 bytes\n  // No need to add an extra slot; i.e. a capacity of 32 to 63 bytes can hold\n  // at most one entry.\n  return (capacityVal >> 5);\n}\n\nuint32_t HeaderTable::removeLast() {\n  auto t = tail();\n  // remove the first element from the names index\n  if (indexNames_) {\n    auto names_it = names_.find(table_[t].name);\n    DCHECK(names_it != names_.end());\n    auto& ilist = names_it->second;\n    DCHECK_EQ(ilist.front(), t);\n    ilist.erase(ilist.begin());\n\n    // remove the name if there are no indices associated with it\n    if (ilist.empty()) {\n      names_.erase(names_it);\n    }\n  }\n  const auto& header = table_[t];\n  uint32_t headerBytes = header.bytes();\n  bytes_ -= headerBytes;\n  DVLOG(10) << \"Removing local idx=\" << t << \" name=\" << header.name\n            << \" value=\" << header.value;\n  --size_;\n  return headerBytes;\n}\n\nvoid HeaderTable::reset() {\n  names_.clear();\n\n  bytes_ = 0;\n  size_ = 0;\n\n  // Capacity remains unchanged and for now we leave head_ index the same\n}\n\nbool HeaderTable::setCapacity(uint32_t newCapacity) {\n  if (newCapacity == capacity_) {\n    return true;\n  } else if (newCapacity < capacity_) {\n    // NOTE: currently no actual resizing is performed...\n    evict(0, newCapacity);\n    if (bytes_ > newCapacity) {\n      // eviction failed!\n      return false;\n    }\n  } else {\n    // NOTE: due to the above lack of resizing, we must determine whether a\n    // resize is actually appropriate (to handle cases where the underlying\n    // vector is still >= to the size related to the new capacity requested)\n    uint32_t newLength = initialTableLength(newCapacity);\n    if (newLength > length()) {\n      increaseTableLengthTo(newLength);\n    }\n  }\n  capacity_ = newCapacity;\n  return true;\n}\n\nvoid HeaderTable::increaseTableLengthTo(uint32_t newLength) {\n  DCHECK_GE(newLength, length());\n  uint32_t oldTail = (size_ > 0) ? tail() : 0;\n  auto oldLength = length();\n  resizeTable(newLength);\n\n  // TODO: referenence to head here is incompatible with baseIndex\n  if (size_ > 0 && oldTail > head_) {\n    // the list wrapped around, need to move oldTail..oldLength to the end\n    // of the now-larger table_\n    updateResizedTable(oldTail, oldLength, newLength);\n    // Update the names indecies that pointed to the old range\n    if (indexNames_) {\n      for (auto& names_it : names_) {\n        for (auto& idx : names_it.second) {\n          if (idx >= oldTail) {\n            DCHECK_LT(idx + (length() - oldLength), length());\n            idx += (length() - oldLength);\n          } else {\n            // remaining indecies in the list were smaller than oldTail, so\n            // should be indexed from 0\n            break;\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid HeaderTable::resizeTable(uint32_t newLength) {\n  table_.resize(newLength);\n}\n\nvoid HeaderTable::updateResizedTable(uint32_t oldTail,\n                                     uint32_t oldLength,\n                                     uint32_t newLength) {\n  std::move_backward(table_.begin() + oldTail,\n                     table_.begin() + oldLength,\n                     table_.begin() + newLength);\n}\n\nuint32_t HeaderTable::evict(uint32_t needed, uint32_t desiredCapacity) {\n  uint32_t previousSize = size_;\n  while (size_ > 0 && (bytes_ + needed > desiredCapacity)) {\n    removeLast();\n  }\n  return previousSize - size_;\n}\n\nbool HeaderTable::isValid(uint32_t index) const {\n  bool result = false;\n  result = 0 < index && index <= size_;\n  if (!result) {\n    LOG(ERROR) << \"Invalid index=\" << index << \" size_=\" << size_;\n  }\n  return result;\n}\n\nuint32_t HeaderTable::next(uint32_t i) const {\n  return (i + 1) % length();\n}\n\nuint32_t HeaderTable::tail() const {\n  // tail is private, and only called in the encoder, where head_ is always\n  // valid\n  DCHECK_GT(size_, 0) << \"tail() undefined\";\n  return (head_ + length() - size_ + 1) % length();\n}\n\nuint32_t HeaderTable::toExternal(uint32_t internalIndex) const {\n  return toExternal(head_, length(), internalIndex);\n}\n\nuint32_t HeaderTable::toExternal(uint32_t head,\n                                 uint32_t length,\n                                 uint32_t internalIndex) {\n  return ((head + length - internalIndex) % length) + 1;\n}\n\nuint32_t HeaderTable::toInternal(uint32_t externalIndex) const {\n  return toInternal(head_, length(), externalIndex);\n}\n\nuint32_t HeaderTable::toInternal(uint32_t head,\n                                 uint32_t length,\n                                 uint32_t externalIndex) {\n  // remove the offset\n  --externalIndex;\n  return (head + length - externalIndex) % length;\n}\n\nbool HeaderTable::operator==(const HeaderTable& other) const {\n  if (size() != other.size()) {\n    return false;\n  }\n  if (bytes() != other.bytes()) {\n    return false;\n  }\n  return true;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HeaderTable& table) {\n  os << std::endl;\n  for (size_t i = 1; i <= table.size(); i++) {\n    const HPACKHeader& h = table.getHeader(i);\n    os << '[' << i << \"] (s=\" << h.bytes() << \") \" << h.name << \": \" << h.value\n       << std::endl;\n  }\n  os << \"total size: \" << table.bytes() << std::endl;\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/HeaderTable.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <string>\n#include <vector>\n\n#include <folly/container/F14Map.h>\n#include <folly/small_vector.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\nnamespace proxygen {\n\n/**\n * Data structure for maintaining indexed headers, based on a fixed-length ring\n * with FIFO semantics. Externally it acts as an array.\n */\n\nclass HeaderTable {\n public:\n  // 7 is chosen as an empirically good value that's not too big.\n  // small_vector spills to the heap beyond that.\n  using names_map =\n      folly::F14ValueMap<HPACKHeaderName, folly::small_vector<uint32_t, 7>>;\n\n  explicit HeaderTable(uint32_t capacityVal) {\n    init(capacityVal);\n  }\n\n  virtual ~HeaderTable() = default;\n  HeaderTable(const HeaderTable&) = delete;\n  HeaderTable& operator=(const HeaderTable&) = delete;\n\n  /**\n   * Return Insert Count - the total number of headers inserted to this table,\n   * including evictions\n   */\n  [[nodiscard]] uint32_t getInsertCount() const {\n    return insertCount_;\n  }\n\n  void disableNamesIndex() {\n    indexNames_ = false;\n  }\n\n  /**\n   * Add the header entry at the beginning of the table (index=1)\n   *\n   * @return true if it was able to add the entry\n   */\n  virtual bool add(HPACKHeader header);\n\n  /**\n   * Get the index of the given header, if found, and the index of a header\n   * with the same name if not.\n   *\n   * @return a pair containing <index, 0>, <0, nameIndex>, or <0, 0>\n   */\n  [[nodiscard]] std::pair<uint32_t, uint32_t> getIndex(\n      const HPACKHeader& header) const;\n\n  /**\n   * Get the index of the given header, if found, and the index of a header\n   * with the same name if not.\n   *\n   * @return a pair containing <index, 0>, <0, nameIndex>, or <0, 0>\n   */\n  [[nodiscard]] std::pair<uint32_t, uint32_t> getIndex(\n      const HPACKHeaderName& name, folly::StringPiece value) const;\n\n  /**\n   * Get the table entry at the given external index.\n   *\n   * @return the header entry\n   */\n  [[nodiscard]] const HPACKHeader& getHeader(uint32_t index) const;\n\n  /**\n   * Checks if an external index is valid.\n   */\n  [[nodiscard]] bool isValid(uint32_t index) const;\n\n  /**\n   * @return true if there is at least one header with the given name\n   */\n  bool hasName(const HPACKHeaderName& headerName);\n\n  /**\n   * @return the map holding the indexed names\n   */\n  [[nodiscard]] const names_map& names() const {\n    return names_;\n  }\n\n  /**\n   * Get any index of a header that has the given name. From all the\n   * headers with the given name we pick the last one added to the header\n   * table, but the way we pick the header can be arbitrary.\n   */\n  [[nodiscard]] uint32_t nameIndex(const HPACKHeaderName& headerName) const;\n\n  /**\n   * Table capacity, or maximum number of bytes we can hold.\n   */\n  [[nodiscard]] uint32_t capacity() const {\n    return capacity_;\n  }\n\n  /**\n   * Returns the maximum table length required to support HPACK headers given\n   * the specified capacity bytes\n   */\n  [[nodiscard]] uint32_t getMaxTableLength(uint32_t capacityVal) const;\n\n  /**\n   * Sets the current capacity of the header table, and evicts entries\n   * if needed.  Returns false if eviction failed.\n   */\n  virtual bool setCapacity(uint32_t capacity);\n\n  /**\n   * @return number of valid entries\n   */\n  [[nodiscard]] uint32_t size() const {\n    return size_;\n  }\n\n  /**\n   * @return size in bytes, the sum of the size of all entries\n   */\n  [[nodiscard]] uint32_t bytes() const {\n    return bytes_;\n  }\n\n  /**\n   * @return how many slots we have in the table\n   */\n  [[nodiscard]] size_t length() const {\n    return table_.size();\n  }\n\n  bool operator==(const HeaderTable& other) const;\n\n  /**\n   * Static versions of the methods that translate indices.\n   */\n  static uint32_t toExternal(uint32_t head,\n                             uint32_t length,\n                             uint32_t internalIndex);\n\n  static uint32_t toInternal(uint32_t head,\n                             uint32_t length,\n                             uint32_t externalIndex);\n\n protected:\n  /**\n   * Initialize with a given capacity.\n   */\n  void init(uint32_t capacityVal);\n\n  /*\n   * Increase table length to newLength\n   */\n  virtual void increaseTableLengthTo(uint32_t newLength);\n\n  virtual void resizeTable(uint32_t newLength);\n\n  virtual void updateResizedTable(uint32_t oldTail,\n                                  uint32_t oldLength,\n                                  uint32_t newLength);\n\n  /**\n   * Removes one header entry from the beginning of the header table.\n   *\n   * Returns the size of the removed header\n   */\n  virtual uint32_t removeLast();\n\n  /**\n   * Empties the underlying header table\n   */\n  void reset();\n\n  /**\n   * Evict entries to make space for the needed amount of bytes.\n   */\n  virtual uint32_t evict(uint32_t needed, uint32_t desiredCapacity);\n\n  /**\n   * Move the index to the right.\n   */\n  [[nodiscard]] uint32_t next(uint32_t i) const;\n\n  /**\n   * Get the index of the tail element of the table.\n   */\n  [[nodiscard]] uint32_t tail() const;\n\n  /**\n   * Translate internal index to external one, including a static version.\n   */\n  [[nodiscard]] uint32_t toExternal(uint32_t internalIndex) const;\n\n  /**\n   * Translate external index to internal one.\n   */\n  [[nodiscard]] uint32_t toInternal(uint32_t externalIndex) const;\n\n  uint32_t capacity_{0};\n  uint32_t bytes_{0}; // size in bytes of the current entries\n  std::vector<HPACKHeader> table_;\n\n  uint32_t size_{0}; // how many entries we have in the table\n  uint32_t head_{0}; // points to the first element of the ring\n  uint32_t insertCount_{0};\n  bool indexNames_{true};\n\n  names_map names_;\n\n private:\n  /*\n   * Shared implementation for getIndex and nameIndex\n   */\n  [[nodiscard]] std::pair<uint32_t, uint32_t> getIndexImpl(\n      const HPACKHeaderName& header,\n      folly::StringPiece value,\n      bool nameOnly) const;\n\n  uint32_t initialTableLength(uint32_t capacity);\n};\n\nstd::ostream& operator<<(std::ostream& os, const HeaderTable& table);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/Huffman.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/Huffman.h>\n\n#include <array>\n#include <folly/Indestructible.h>\n#include <folly/container/Reserve.h>\n#include <folly/portability/Sockets.h>\n\nusing std::pair;\n\nnamespace proxygen::huffman {\n\n// These constants were decided upon empirically\nconstexpr static uint32_t kHuffmanDecodeSpaceNumerator = 3;\nconstexpr static uint32_t kHuffmanDecodeSpaceDenominator = 2;\n\nHuffTree::HuffTree(const uint32_t* codes, const uint8_t* bits)\n    : codes_(codes), bits_(bits) {\n  buildTree();\n}\n\nHuffTree::HuffTree(const HuffTree& tree)\n    : codes_(tree.codes_), bits_(tree.bits_) {\n  buildTree();\n}\n\nbool HuffTree::decode(const uint8_t* buf,\n                      uint32_t size,\n                      folly::fbstring& literal) const {\n  folly::grow_capacity_by(\n      literal,\n      (size * kHuffmanDecodeSpaceNumerator) / kHuffmanDecodeSpaceDenominator);\n  const SuperHuffNode* snode = &table_[0];\n  uint32_t w = 0;\n  uint32_t wbits = 0;\n  uint32_t i = 0;\n  while (i < size || wbits > 0) {\n    // decide if we need to load more bits using an 8-bit chunk\n    if (i < size && wbits < 8) {\n      w = (w << 8) | buf[i];\n      wbits += 8;\n      i++;\n    }\n    // key is used for performing the indexed lookup\n    uint32_t key;\n    if (wbits >= 8) {\n      key = w >> (wbits - 8);\n    } else {\n      // this the case we're at the end of the buffer\n      uint8_t xbits = 8 - wbits;\n      w = (w << xbits) | ((1 << xbits) - 1);\n      key = w;\n      wbits = 8;\n    }\n    // perform the indexed lookup\n    const HuffNode& node = snode->index[key];\n    if (node.isLeaf()) {\n      // final node, we can emit the character\n      literal.push_back(node.data.ch);\n      wbits -= node.metadata.bits;\n      snode = &table_[0];\n    } else {\n      // this is a branch, so we just need to move one level\n      wbits -= 8;\n      snode = &table_[node.data.superNodeIndex];\n    }\n    // remove what we've just used\n    w = w & ((1 << wbits) - 1);\n  }\n  return true;\n}\n\n/**\n * insert a new character into the tree, identified by an unique code,\n * a number of bits to represent it. The code is aligned at LSB.\n */\nvoid HuffTree::insert(uint32_t code, uint8_t bits, uint8_t ch) {\n  SuperHuffNode* snode = &table_[0];\n  while (bits > 8) {\n    uint32_t mask = 0xFF << (bits - 8);\n    uint32_t x = (code & mask) >> (bits - 8);\n    // mark this node as branch\n    if (snode->index[x].isLeaf()) {\n      nodes_++;\n      HuffNode& node = snode->index[x];\n      node.metadata.isSuperNode = true;\n      node.data.superNodeIndex = nodes_;\n    }\n    snode = &table_[snode->index[x].data.superNodeIndex];\n    bits -= 8;\n    code = code & ~mask;\n  }\n  // fill the node with all the suffixes\n  fillIndex(*snode, code, bits, ch, bits);\n}\n\nconst uint32_t* HuffTree::codesTable() const {\n  return codes_;\n}\n\nconst uint8_t* HuffTree::bitsTable() const {\n  return bits_;\n}\n\n/**\n * recursive function for generating subtrees\n */\nvoid HuffTree::fillIndex(SuperHuffNode& snode,\n                         uint32_t code,\n                         uint8_t bits,\n                         uint8_t ch,\n                         uint8_t level) {\n  if (level == 8) {\n    snode.index[code].data.ch = ch;\n    snode.index[code].metadata.bits = bits;\n    return;\n  }\n  // generate the bit at the current level\n  code = code << 1;\n  for (uint8_t bit = 0; bit <= 1; bit++) {\n    fillIndex(snode, code | bit, bits, ch, level + 1);\n  }\n}\n\n/**\n * initializes and builds the huffman tree\n */\nvoid HuffTree::buildTree() {\n  // create the indexed table\n  for (uint32_t i = 0; i < kTableSize; i++) {\n    insert(codes_[i], bits_[i], i);\n  }\n}\n\nuint32_t HuffTree::encode(folly::StringPiece literal,\n                          folly::io::QueueAppender& buf) const {\n  uint32_t code;     // the huffman code of a given character\n  uint8_t bits;      // on how many bits code is represented\n  uint32_t w = 0;    // 4-byte word used for packing bits and write it to memory\n  uint8_t wbits = 0; // how many bits we have in 'w'\n  uint32_t totalBytes = 0;\n  for (size_t i = 0; i < literal.size(); i++) {\n    uint8_t ch = literal[i];\n    code = codes_[ch];\n    bits = bits_[ch];\n\n    if (wbits + bits < 32) {\n      w = (w << bits) | code;\n      wbits += bits;\n    } else {\n      uint8_t xbits = wbits + bits - 32;\n      w = (w << (bits - xbits)) | (code >> xbits);\n      // write the word into the buffer by converting to network order, which\n      // takes care of the endianness problems\n      buf.writeBE<uint32_t>(w);\n      totalBytes += 4;\n      // carry for next batch\n      wbits = xbits;\n      w = code & ((1 << xbits) - 1);\n    }\n  }\n  // we might have some padding at the byte level\n  if (wbits & 0x7) {\n    // padding bits\n    uint8_t padbits = 8 - (wbits & 0x7);\n    w = (w << padbits) | ((1 << padbits) - 1);\n\n    wbits += padbits;\n  }\n  // we need to write the leftover bytes, from 1 to 4 bytes\n  if (wbits > 0) {\n    uint8_t bytes = wbits >> 3;\n    // align the bits to the MSB\n    w = w << (32 - wbits);\n    // set the bytes in the network order and copy w[0], w[1]...\n    w = htonl(w);\n    // we need to use memcpy because we might write less than 4 bytes\n    buf.push((uint8_t*)&w, bytes);\n    totalBytes += bytes;\n  }\n  return totalBytes;\n}\n\nuint32_t HuffTree::getEncodeSize(folly::StringPiece literal) const {\n  uint32_t totalBits = 0;\n  for (size_t i = 0; i < literal.size(); i++) {\n    // we just need the number of bits\n    uint8_t ch = literal[i];\n    totalBits += bits_[ch];\n  }\n  uint32_t size = totalBits >> 3;\n  if (totalBits & 0x07) {\n    ++size;\n  }\n  return size;\n}\n\npair<uint32_t, uint8_t> HuffTree::getCode(uint8_t ch) const {\n  return std::make_pair(codes_[ch], bits_[ch]);\n}\n\n// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#appendix-C\nconstexpr std::array<uint32_t, kTableSize> s_codesTable = {\n    0x1ff8,    0x7fffd8,   0xfffffe2, 0xfffffe3, 0xfffffe4,  0xfffffe5,\n    0xfffffe6, 0xfffffe7,  0xfffffe8, 0xffffea,  0x3ffffffc, 0xfffffe9,\n    0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed,  0xfffffee,\n    0xfffffef, 0xffffff0,  0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3,\n    0xffffff4, 0xffffff5,  0xffffff6, 0xffffff7, 0xffffff8,  0xffffff9,\n    0xffffffa, 0xffffffb,  0x14,      0x3f8,     0x3f9,      0xffa,\n    0x1ff9,    0x15,       0xf8,      0x7fa,     0x3fa,      0x3fb,\n    0xf9,      0x7fb,      0xfa,      0x16,      0x17,       0x18,\n    0x0,       0x1,        0x2,       0x19,      0x1a,       0x1b,\n    0x1c,      0x1d,       0x1e,      0x1f,      0x5c,       0xfb,\n    0x7ffc,    0x20,       0xffb,     0x3fc,     0x1ffa,     0x21,\n    0x5d,      0x5e,       0x5f,      0x60,      0x61,       0x62,\n    0x63,      0x64,       0x65,      0x66,      0x67,       0x68,\n    0x69,      0x6a,       0x6b,      0x6c,      0x6d,       0x6e,\n    0x6f,      0x70,       0x71,      0x72,      0xfc,       0x73,\n    0xfd,      0x1ffb,     0x7fff0,   0x1ffc,    0x3ffc,     0x22,\n    0x7ffd,    0x3,        0x23,      0x4,       0x24,       0x5,\n    0x25,      0x26,       0x27,      0x6,       0x74,       0x75,\n    0x28,      0x29,       0x2a,      0x7,       0x2b,       0x76,\n    0x2c,      0x8,        0x9,       0x2d,      0x77,       0x78,\n    0x79,      0x7a,       0x7b,      0x7ffe,    0x7fc,      0x3ffd,\n    0x1ffd,    0xffffffc,  0xfffe6,   0x3fffd2,  0xfffe7,    0xfffe8,\n    0x3fffd3,  0x3fffd4,   0x3fffd5,  0x7fffd9,  0x3fffd6,   0x7fffda,\n    0x7fffdb,  0x7fffdc,   0x7fffdd,  0x7fffde,  0xffffeb,   0x7fffdf,\n    0xffffec,  0xffffed,   0x3fffd7,  0x7fffe0,  0xffffee,   0x7fffe1,\n    0x7fffe2,  0x7fffe3,   0x7fffe4,  0x1fffdc,  0x3fffd8,   0x7fffe5,\n    0x3fffd9,  0x7fffe6,   0x7fffe7,  0xffffef,  0x3fffda,   0x1fffdd,\n    0xfffe9,   0x3fffdb,   0x3fffdc,  0x7fffe8,  0x7fffe9,   0x1fffde,\n    0x7fffea,  0x3fffdd,   0x3fffde,  0xfffff0,  0x1fffdf,   0x3fffdf,\n    0x7fffeb,  0x7fffec,   0x1fffe0,  0x1fffe1,  0x3fffe0,   0x1fffe2,\n    0x7fffed,  0x3fffe1,   0x7fffee,  0x7fffef,  0xfffea,    0x3fffe2,\n    0x3fffe3,  0x3fffe4,   0x7ffff0,  0x3fffe5,  0x3fffe6,   0x7ffff1,\n    0x3ffffe0, 0x3ffffe1,  0xfffeb,   0x7fff1,   0x3fffe7,   0x7ffff2,\n    0x3fffe8,  0x1ffffec,  0x3ffffe2, 0x3ffffe3, 0x3ffffe4,  0x7ffffde,\n    0x7ffffdf, 0x3ffffe5,  0xfffff1,  0x1ffffed, 0x7fff2,    0x1fffe3,\n    0x3ffffe6, 0x7ffffe0,  0x7ffffe1, 0x3ffffe7, 0x7ffffe2,  0xfffff2,\n    0x1fffe4,  0x1fffe5,   0x3ffffe8, 0x3ffffe9, 0xffffffd,  0x7ffffe3,\n    0x7ffffe4, 0x7ffffe5,  0xfffec,   0xfffff3,  0xfffed,    0x1fffe6,\n    0x3fffe9,  0x1fffe7,   0x1fffe8,  0x7ffff3,  0x3fffea,   0x3fffeb,\n    0x1ffffee, 0x1ffffef,  0xfffff4,  0xfffff5,  0x3ffffea,  0x7ffff4,\n    0x3ffffeb, 0x7ffffe6,  0x3ffffec, 0x3ffffed, 0x7ffffe7,  0x7ffffe8,\n    0x7ffffe9, 0x7ffffea,  0x7ffffeb, 0xffffffe, 0x7ffffec,  0x7ffffed,\n    0x7ffffee, 0x7ffffef,  0x7fffff0, 0x3ffffee};\n\nconstexpr std::array<uint8_t, kTableSize> s_bitsTable = {\n    13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28,\n    28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, 6,  10, 10, 12, 13, 6,\n    8,  11, 10, 10, 8,  11, 8,  6,  6,  6,  5,  5,  5,  6,  6,  6,  6,  6,  6,\n    6,  7,  8,  15, 6,  12, 10, 13, 6,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,\n    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  8,  7,  8,  13, 19, 13, 14,\n    6,  15, 5,  6,  5,  6,  5,  6,  6,  6,  5,  7,  7,  6,  6,  6,  5,  6,  7,\n    6,  5,  5,  6,  7,  7,  7,  7,  7,  15, 11, 14, 13, 28, 20, 22, 20, 20, 22,\n    22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23,\n    23, 21, 22, 23, 22, 23, 23, 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22,\n    24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22,\n    22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, 19,\n    21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21,\n    22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27,\n    27, 27, 28, 27, 27, 27, 27, 27, 26};\n\nconst HuffTree& huffTree() {\n  static const folly::Indestructible<HuffTree> huffTree{\n      HuffTree{s_codesTable.data(), s_bitsTable.data()}};\n  return *huffTree;\n}\n\n} // namespace proxygen::huffman\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/Huffman.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/FBString.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <string>\n\nnamespace proxygen::huffman {\n\n// size of the huffman tables (codes and bits)\nconst uint32_t kTableSize = 256;\n\n// not used explicitly, since the prefixes are all 1's and they are\n// used only for padding of up to 7 bits\nconst uint32_t kEOSHpack = 0x3fffffff;\n\n/**\n * node from the huffman tree\n *\n * A leaf has no index table, or index == nullptr\n */\nstruct HuffNode {\n  union {\n    uint8_t ch; // leafs hold characters\n    uint8_t superNodeIndex;\n  } data{0};\n  struct {\n    uint8_t bits : 4; // how many bits are used for representing ch, range is\n                      // 0-8\n    bool isSuperNode : 1;\n  } metadata{.bits = 0, .isSuperNode = false};\n\n  [[nodiscard]] bool isLeaf() const {\n    return !metadata.isSuperNode;\n  }\n};\n\n/**\n * a super node from the condensed Huffman Tree representation with 8-bit level\n */\nstruct SuperHuffNode {\n  HuffNode index[256];\n};\n\n/**\n * Immutable Huffman tree used in the process of decoding. Traditionally the\n * huffman tree is binary, but using that approach leads to major inefficiencies\n * since it's using per-bit level processing and needs to perform several memory\n * accesses and bit operations for every single bit.\n * This implementation is using 8-bit level indexing and uses aggregated nodes\n * that link up to 256 other nodes. The complexity for lookup is reduced from\n * O(bits) to O(Bytes) which is 1 or 2 for most of the printable characters.\n * The tradeoff of using this approach is using more memory and generating the\n * tree is more laborious since we need to fill all the subtrees denoted by a\n * character code, which is an unique prefix.\n *\n * Example\n *\n * bit stream:\n * 00001111 1111010\n * 1. our lookup key is 00001111 which will point to character '/', since the\n * entire subtree with prefix 0000 points to it. We know the subtree has just\n * 4 bits, we remove just those from the current key.\n * bit stream:\n * 11111111 010\n *\n * 2. key is 11111111 which points to a branch, so we go down one level\n * bit stream:\n * 010\n *\n * 3. we don't have enough bits, so we use paddding and we get a key of\n * 01011111, which points to '(' character, like any other node under the\n * subtree '010'.\n */\nclass HuffTree {\n public:\n  /**\n   * the constructor assumes the codes and bits tables will not be freed,\n   * ideally they are static\n   */\n  explicit HuffTree(const uint32_t* codes, const uint8_t* bits);\n  explicit HuffTree(HuffTree&& tree) = default;\n  ~HuffTree() = default;\n\n  /**\n   * decode bitstream into a string literal\n   *\n   * @param buf start of a huffman-encoded bit stream\n   * @param size size of the buffer\n   * @param literal where to append decoded characters\n   *\n   * @return true if the decode process was successful\n   */\n  bool decode(const uint8_t* buf,\n              uint32_t size,\n              folly::fbstring& literal) const;\n\n  /**\n   * encode string literal into huffman encoded bit stream\n   *\n   * @param literal string to encode\n   * @param buf where to append the encoded binary data\n   */\n  uint32_t encode(folly::StringPiece literal,\n                  folly::io::QueueAppender& buf) const;\n\n  /**\n   * get the encode size for a string literal, works as a dry-run for the encode\n   * useful to allocate enough buffer space before doing the actual encode\n   *\n   * @param literal string literal\n   * @return size how many bytes it will take to encode the given string\n   */\n  [[nodiscard]] uint32_t getEncodeSize(folly::StringPiece literal) const;\n\n  /**\n   * get the binary representation for a given character, as a 32-bit word and\n   * a number of bits is represented on (<32). The code is aligned to LSB.\n   *\n   * @param ch ASCII character\n   * @return pair<word, bits>\n   *\n   * Example:\n   * 'e' will be encoded as 1 using 4 bits: 0001\n   */\n  [[nodiscard]] std::pair<uint32_t, uint8_t> getCode(uint8_t ch) const;\n\n  // get internal tables for codes and bit lengths, useful for testing\n  [[nodiscard]] const uint32_t* codesTable() const;\n  [[nodiscard]] const uint8_t* bitsTable() const;\n\n private:\n  void fillIndex(SuperHuffNode& snode,\n                 uint32_t code,\n                 uint8_t bits,\n                 uint8_t ch,\n                 uint8_t level);\n  void buildTree();\n  void insert(uint32_t code, uint8_t bits, uint8_t ch);\n\n  uint32_t nodes_{0};\n  const uint32_t* codes_;\n  const uint8_t* bits_;\n\n protected:\n  explicit HuffTree(const HuffTree& tree);\n  SuperHuffNode table_[46];\n};\n\nconst HuffTree& huffTree();\n\n} // namespace proxygen::huffman\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/Logging.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/Logging.h>\n\n#include <sstream>\n\nusing std::ostream;\nusing std::string;\nusing std::stringstream;\nusing std::vector;\n\nnamespace proxygen {\n\nostream& operator<<(ostream& os, const std::list<uint32_t>* refset) {\n  os << std::endl << '[';\n  for (auto& ref : *refset) {\n    os << ref << ' ';\n  }\n  os << ']' << std::endl;\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const std::vector<HPACKHeader>& v) {\n  for (const auto& h : v) {\n    os << h.name << \": \" << h.value << std::endl;\n  }\n  os << std::endl;\n  return os;\n}\n\nstring printDelta(const vector<HPACKHeader>& v1,\n                  const vector<HPACKHeader>& v2) {\n  stringstream out;\n  // similar with merge operation\n  size_t i = 0;\n  size_t j = 0;\n  out << std::endl;\n  while (i < v1.size() && j < v2.size()) {\n    if (v1[i] < v2[j]) {\n      if (i > 0 && v1[i - 1] == v1[i]) {\n        out << \" duplicate \" << v1[i] << std::endl;\n      } else {\n        out << \" + \" << v1[i] << std::endl;\n      }\n      i++;\n    } else if (v1[i] > v2[j]) {\n      out << \" - \" << v2[j] << std::endl;\n      j++;\n    } else {\n      i++;\n      j++;\n    }\n  }\n  while (i < v1.size()) {\n    out << \" + \" << v1[i];\n    if (i > 0 && v1[i - 1] == v1[i]) {\n      out << \" (duplicate)\";\n    }\n    out << std::endl;\n    i++;\n  }\n  while (j < v2.size()) {\n    out << \" - \" << v2[j] << std::endl;\n    j++;\n  }\n  return out.str();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/Logging.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <list>\n#include <ostream>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n#include <vector>\n\nnamespace proxygen {\n\nstd::ostream& operator<<(std::ostream& os, const std::list<uint32_t>* refset);\n\nstd::ostream& operator<<(std::ostream& os, const std::vector<HPACKHeader>& v);\n\n/**\n * print the difference between 2 sorted list of headers\n */\nstd::string printDelta(const std::vector<HPACKHeader>& v1,\n                       const std::vector<HPACKHeader>& v2);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/NoPathIndexingStrategy.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/NoPathIndexingStrategy.h>\n\nnamespace proxygen {\n\nconst NoPathIndexingStrategy* NoPathIndexingStrategy::getInstance() {\n  static const NoPathIndexingStrategy* instance = new NoPathIndexingStrategy();\n  return instance;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/NoPathIndexingStrategy.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n\nnamespace proxygen {\n\nclass NoPathIndexingStrategy : public HeaderIndexingStrategy {\n public:\n  static const NoPathIndexingStrategy* getInstance();\n\n  NoPathIndexingStrategy() : HeaderIndexingStrategy() {\n  }\n\n  // For compression simulations we do not want to index :path headers\n  [[nodiscard]] bool indexHeader(const HPACKHeaderName& name,\n                                 folly::StringPiece value,\n                                 bool) const override {\n    if (name.getHeaderCode() == HTTP_HEADER_COLON_PATH) {\n      return false;\n    } else {\n      return HeaderIndexingStrategy::indexHeader(name, value);\n    }\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n\n#include <algorithm>\n#include <folly/io/Cursor.h>\n#include <iosfwd>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/HeaderConstants.h>\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h> // for prepareHeaders\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\nusing proxygen::compress::Header;\nusing std::vector;\nnamespace proxygen {\n\nQPACKCodec::QPACKCodec()\n    // by default dynamic tables are 0 size\n    : encoder_(true, 0), decoder_(0, maxUncompressed_) {\n}\n\nvoid QPACKCodec::recordCompressedSize(const folly::IOBuf* stream,\n                                      size_t controlSize) {\n  encodedSize_.compressed = 0;\n  encodedSize_.compressedBlock = 0;\n  encodedSize_.compressed += controlSize;\n  if (stream) {\n    encodedSize_.compressedBlock = stream->computeChainDataLength();\n    encodedSize_.compressed += encodedSize_.compressedBlock;\n  }\n  if (stats_) {\n    stats_->recordEncode(Type::QPACK, encodedSize_);\n  }\n}\n\nQPACKEncoder::EncodeResult QPACKCodec::encode(\n    vector<Header>& headers,\n    uint64_t streamId,\n    uint32_t maxEncoderStreamBytes) noexcept {\n  std::vector<HPACKHeader> prepared;\n  encodedSize_.uncompressed = compress::prepareHeaders(headers, prepared);\n  auto res = encoder_.encode(\n      prepared, encodeHeadroom_, streamId, maxEncoderStreamBytes);\n  size_t controlSize = res.control ? res.control->computeChainDataLength() : 0;\n  recordCompressedSize(res.stream.get(), controlSize);\n  return res;\n}\n\nstd::unique_ptr<folly::IOBuf> QPACKCodec::encodeHTTP(\n    folly::IOBufQueue& controlQueue,\n    const HTTPMessage& msg,\n    bool includeDate,\n    uint64_t streamId,\n    uint32_t maxEncoderStreamBytes,\n    const folly::Optional<HTTPHeaders>& extraHeaders) noexcept {\n  auto baseIndex = encoder_.startEncode(controlQueue, 0, maxEncoderStreamBytes);\n  uint32_t requiredInsertCount = 0;\n  auto prevSize = controlQueue.chainLength();\n\n  auto uncompressed = 0;\n  if (msg.isRequest()) {\n    if (msg.isEgressWebsocketUpgrade()) {\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_METHOD),\n                                 methodToString(HTTPMethod::CONNECT),\n                                 baseIndex,\n                                 requiredInsertCount);\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_PROTOCOL),\n                                 headers::kWebsocketString,\n                                 baseIndex,\n                                 requiredInsertCount);\n    } else if (msg.getUpgradeProtocol()) {\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_METHOD),\n                                 methodToString(HTTPMethod::CONNECT),\n                                 baseIndex,\n                                 requiredInsertCount);\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_PROTOCOL),\n                                 *msg.getUpgradeProtocol(),\n                                 baseIndex,\n                                 requiredInsertCount);\n    } else {\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_METHOD),\n                                 msg.getMethodString(),\n                                 baseIndex,\n                                 requiredInsertCount);\n    }\n\n    if (msg.getMethod() != HTTPMethod::CONNECT ||\n        msg.isEgressWebsocketUpgrade() || msg.getUpgradeProtocol()) {\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_SCHEME),\n                                 msg.getScheme(),\n                                 baseIndex,\n                                 requiredInsertCount);\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_PATH),\n                                 msg.getURL(),\n                                 baseIndex,\n                                 requiredInsertCount);\n    }\n    const HTTPHeaders& headers = msg.getHeaders();\n    const std::string& host = headers.getSingleOrEmpty(HTTP_HEADER_HOST);\n    uncompressed +=\n        encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_AUTHORITY),\n                               host,\n                               baseIndex,\n                               requiredInsertCount);\n  } else {\n    if (msg.isEgressWebsocketUpgrade()) {\n      uncompressed +=\n          encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_COLON_STATUS),\n                                 headers::kStatus200,\n                                 baseIndex,\n                                 requiredInsertCount);\n    } else {\n      uncompressed += encoder_.encodeHeaderQ(\n          HPACKHeaderName(HTTP_HEADER_COLON_STATUS),\n          folly::to<folly::fbstring>(msg.getStatusCode()),\n          baseIndex,\n          requiredInsertCount);\n    }\n    // HEADERS frames do not include a version or reason string.\n  }\n\n  bool hasDateHeader = false;\n  // Add the HTTP headers supplied by the caller, but skip\n  // any per-hop headers that aren't supported in HTTP/2.\n  auto headerEncodeHelper = [&](HTTPHeaderCode code,\n                                folly::StringPiece name,\n                                folly::StringPiece value) {\n    if (CodecUtil::disallowedModernHTTPFields()[code] || name.empty() ||\n        name[0] == ':') {\n      DCHECK(!name.empty()) << \"Empty header\";\n      DCHECK_NE(name[0], ':') << \"Invalid header=\" << name;\n      return;\n    }\n    // Note this code will not drop headers named by Connection.  That's the\n    // caller's job\n\n    // see HTTP/2 spec, 8.1.2\n    DCHECK(name != \"TE\" || value == \"trailers\");\n    if ((!name.empty() && name[0] != ':') && code != HTTP_HEADER_HOST) {\n      if (code == HTTP_HEADER_OTHER) {\n        uncompressed += encoder_.encodeHeaderQ(\n            HPACKHeaderName(name), value, baseIndex, requiredInsertCount);\n      } else {\n        uncompressed += encoder_.encodeHeaderQ(\n            HPACKHeaderName(code), value, baseIndex, requiredInsertCount);\n      }\n    }\n    hasDateHeader |= ((code == HTTP_HEADER_DATE) ? 1 : 0);\n  };\n  msg.getHeaders().forEachWithCode(headerEncodeHelper);\n  if (extraHeaders) {\n    extraHeaders->forEachWithCode(headerEncodeHelper);\n  }\n\n  if (includeDate && msg.isResponse() && !hasDateHeader) {\n    uncompressed += encoder_.encodeHeaderQ(HPACKHeaderName(HTTP_HEADER_DATE),\n                                           HTTPMessage::formatDateHeader(),\n                                           baseIndex,\n                                           requiredInsertCount);\n  }\n\n  auto result =\n      encoder_.completeEncode(streamId, baseIndex, requiredInsertCount);\n  encodedSize_.uncompressed = uncompressed;\n  recordCompressedSize(result.get(), controlQueue.chainLength() - prevSize);\n  return result;\n}\n\nvoid QPACKCodec::decodeStreaming(\n    uint64_t streamID,\n    std::unique_ptr<folly::IOBuf> block,\n    uint32_t length,\n    HPACK::StreamingCallback* streamingCb) noexcept {\n  if (streamingCb) {\n    streamingCb->stats = stats_;\n  }\n  decoder_.decodeStreaming(streamID, std::move(block), length, streamingCb);\n}\n\nvoid QPACKCodec::describe(std::ostream& stream) const {\n  stream << \"DecoderTable:\\n\" << decoder_;\n  stream << \"EncoderTable:\\n\" << encoder_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const QPACKCodec& codec) {\n  codec.describe(os);\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h> // table info\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n#include <proxygen/lib/http/codec/compress/QPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/QPACKEncoder.h>\n#include <vector>\n\nnamespace folly::io {\nclass Cursor;\n} // namespace folly::io\n\nnamespace proxygen {\n\nclass HPACKHeader;\n\n/*\n * Current version of the wire protocol. When we're making changes to the wire\n * protocol we need to change this version and the ALPN string so that old\n * clients will not be able to negotiate it anymore.\n */\n\nclass QPACKCodec : public HeaderCodec {\n public:\n  QPACKCodec();\n  ~QPACKCodec() override = default;\n\n  // QPACK encode: id is used for internal tracking of references\n  QPACKEncoder::EncodeResult encode(\n      std::vector<compress::Header>& headers,\n      uint64_t id,\n      uint32_t maxEncoderStreamBytes =\n          std::numeric_limits<uint32_t>::max()) noexcept;\n\n  std::unique_ptr<folly::IOBuf> encodeHTTP(\n      folly::IOBufQueue& controlQueue,\n      const HTTPMessage& msg,\n      bool includeDate,\n      uint64_t id,\n      uint32_t maxEncoderStreamBytes = std::numeric_limits<uint32_t>::max(),\n      const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) noexcept;\n\n  HPACK::DecodeError decodeEncoderStream(std::unique_ptr<folly::IOBuf> buf) {\n    // stats?\n    return decoder_.decodeEncoderStream(std::move(buf));\n  }\n\n  HPACK::DecodeError encoderStreamEnd() {\n    return decoder_.encoderStreamEnd();\n  }\n\n  // QPACK blocking decode.  The decoder may queue the block if there are\n  // unsatisfied dependencies\n  void decodeStreaming(uint64_t streamId,\n                       std::unique_ptr<folly::IOBuf> block,\n                       uint32_t length,\n                       HPACK::StreamingCallback* streamingCb) noexcept;\n\n  // This function sets both the decoder's advertised max and the size the\n  // encoder will use.  The encoder has a limit of 64k.  This function can\n  // only be called once with a unique non-zero value.\n  //\n  // Returns false if it was previously called with a different non-zero value.\n  bool setEncoderHeaderTableSize(uint32_t size, bool updateMax = true) {\n    VLOG(4) << __func__ << \" size=\" << size;\n    return encoder_.setHeaderTableSize(size, updateMax);\n  }\n\n  void setDecoderHeaderTableMaxSize(uint32_t size) {\n    decoder_.setHeaderTableMaxSize(size);\n  }\n\n  // Process bytes on the decoder stream\n  HPACK::DecodeError decodeDecoderStream(std::unique_ptr<folly::IOBuf> buf) {\n    return encoder_.decodeDecoderStream(std::move(buf));\n  }\n\n  HPACK::DecodeError decoderStreamEnd() {\n    return encoder_.decoderStreamEnd();\n  }\n\n  // QPACK when a stream is reset.  Clears all reference counts for outstanding\n  // blocks\n  void onStreamReset(uint64_t streamId) {\n    encoder_.onHeaderAck(streamId, true);\n  }\n\n  std::unique_ptr<folly::IOBuf> encodeInsertCountInc() {\n    return decoder_.encodeInsertCountInc();\n  }\n\n  std::unique_ptr<folly::IOBuf> encodeHeaderAck(uint64_t streamId) {\n    return decoder_.encodeHeaderAck(streamId);\n  }\n\n  std::unique_ptr<folly::IOBuf> encodeCancelStream(uint64_t streamId) {\n    return decoder_.encodeCancelStream(streamId);\n  }\n\n  void describe(std::ostream& os) const;\n\n  void setMaxUncompressed(uint64_t maxUncompressed) override {\n    HeaderCodec::setMaxUncompressed(maxUncompressed);\n    decoder_.setMaxUncompressed(maxUncompressed);\n  }\n\n  CompressionInfo getCompressionInfo() const {\n    return {encoder_.getTableSize(),\n            encoder_.getBytesStored(),\n            encoder_.getHeadersStored(),\n            encoder_.getInsertCount(),\n            encoder_.getBlockedInserts(),\n            encoder_.getDuplications(),\n            encoder_.getStaticRefs(),\n            decoder_.getTableSize(),\n            decoder_.getBytesStored(),\n            decoder_.getHeadersStored(),\n            decoder_.getInsertCount(),\n            0, // decoder can't track blocked inserts\n            decoder_.getDuplications(),\n            decoder_.getStaticRefs()};\n  }\n\n  void setHeaderIndexingStrategy(const HeaderIndexingStrategy* indexingStrat) {\n    encoder_.setHeaderIndexingStrategy(indexingStrat);\n  }\n  const HeaderIndexingStrategy* getHeaderIndexingStrategy() const {\n    return encoder_.getHeaderIndexingStrategy();\n  }\n\n  uint64_t getHolBlockCount() const {\n    return decoder_.getHolBlockCount();\n  }\n\n  uint64_t getQueuedBytes() const {\n    return decoder_.getQueuedBytes();\n  }\n\n  void setMaxVulnerable(uint32_t maxVulnerable) {\n    encoder_.setMaxVulnerable(maxVulnerable);\n  }\n\n  void setMaxBlocking(uint32_t maxBlocking) {\n    decoder_.setMaxBlocking(maxBlocking);\n  }\n\n  void setMaxNumOutstandingBlocks(uint32_t value) {\n    encoder_.setMaxNumOutstandingBlocks(value);\n  }\n\n protected:\n  QPACKEncoder encoder_;\n  QPACKDecoder decoder_;\n\n private:\n  void recordCompressedSize(const folly::IOBuf* stream, size_t controlSize);\n\n  std::vector<HPACKHeader> decodedHeaders_;\n};\n\nstd::ostream& operator<<(std::ostream& os, const QPACKCodec& codec);\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKContext.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKContext.h>\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\nQPACKContext::QPACKContext(uint32_t tableSize, bool trackReferences)\n    : table_(tableSize, trackReferences) {\n}\n\nconst HPACKHeader& QPACKContext::getHeader(bool isStatic,\n                                           uint32_t index,\n                                           uint32_t base,\n                                           bool aboveBase) {\n  if (isStatic) {\n    staticRefs_++;\n    return getStaticTable().getHeader(index);\n  }\n  if (aboveBase) {\n    CHECK_LE(base, std::numeric_limits<uint32_t>::max() - index);\n    base += index;\n    index = 1;\n  }\n  return table_.getHeader(index, base);\n}\n\nvoid QPACKContext::seedHeaderTable(std::vector<HPACKHeader>& headers) {\n  for (auto& header : headers) {\n    CHECK(table_.add(std::move(header)));\n  }\n}\n\nvoid QPACKContext::describe(std::ostream& os) const {\n  os << table_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const QPACKContext& context) {\n  context.describe(os);\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKContext.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/QPACKHeaderTable.h>\n#include <proxygen/lib/http/codec/compress/QPACKStaticHeaderTable.h>\n\nnamespace proxygen {\n\nclass QPACKContext {\n public:\n  QPACKContext(uint32_t tableSize, bool trackReferences);\n  ~QPACKContext() = default;\n\n  /**\n   * @return header at the given index by composing dynamic and static tables\n   */\n  const HPACKHeader& getHeader(bool isStatic,\n                               uint32_t index,\n                               uint32_t base,\n                               bool aboveBase);\n\n  [[nodiscard]] const QPACKHeaderTable& getTable() const {\n    return table_;\n  }\n\n  [[nodiscard]] uint32_t getTableSize() const {\n    return table_.capacity();\n  }\n\n  [[nodiscard]] uint32_t getBytesStored() const {\n    return table_.bytes();\n  }\n\n  [[nodiscard]] uint32_t getHeadersStored() const {\n    return table_.size();\n  }\n\n  [[nodiscard]] uint32_t getInsertCount() const {\n    return table_.getInsertCount();\n  }\n\n  [[nodiscard]] uint32_t getBlockedInserts() const {\n    return blockedInsertions_;\n  }\n\n  [[nodiscard]] uint32_t getDuplications() const {\n    return duplications_;\n  }\n\n  [[nodiscard]] uint32_t getStaticRefs() const {\n    return staticRefs_;\n  }\n\n  void seedHeaderTable(std::vector<HPACKHeader>& headers);\n\n  void describe(std::ostream& os) const;\n\n protected:\n  static uint32_t getMaxEntries(uint32_t tableSize) {\n    return tableSize / HPACKHeader::kMinLength;\n  }\n\n  [[nodiscard]] const StaticHeaderTable& getStaticTable() const {\n    return QPACKStaticHeaderTable::get();\n  }\n\n  QPACKHeaderTable table_;\n  uint32_t blockedInsertions_{0};\n  uint32_t duplications_{0};\n  uint32_t staticRefs_{0};\n};\n\nstd::ostream& operator<<(std::ostream& os, const QPACKContext& context);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKDecoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKDecoder.h>\n\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n\nusing folly::io::Cursor;\n\nnamespace {\nconst uint32_t kGrowth = 100;\n}\n\nnamespace proxygen {\n\n// Blocking implementation - may queue\nvoid QPACKDecoder::decodeStreaming(uint64_t streamID,\n                                   std::unique_ptr<folly::IOBuf> block,\n                                   uint32_t totalBytes,\n                                   HPACK::StreamingCallback* streamingCb) {\n  Cursor cursor(block.get());\n  HPACKDecodeBuffer dbuf(cursor, totalBytes, maxUncompressed_);\n  err_ = HPACK::DecodeError::NONE;\n  uint32_t requiredInsertCount = decodePrefix(dbuf);\n  if (requiredInsertCount > table_.getInsertCount()) {\n    VLOG(5) << \"requiredInsertCount=\" << requiredInsertCount\n            << \" > insertCount=\" << table_.getInsertCount() << \", queuing\";\n    if (queue_.size() >= maxBlocking_) {\n      VLOG(2) << \"QPACK queue full size=\" << queue_.size()\n              << \" maxBlocking_=\" << maxBlocking_;\n      err_ = HPACK::DecodeError::TOO_MANY_BLOCKING;\n      completeDecode(HeaderCodec::Type::QPACK, streamingCb, 0, 0, 0, false);\n    } else {\n      folly::IOBufQueue q;\n      q.append(std::move(block));\n      q.trimStart(dbuf.consumedBytes());\n      enqueueHeaderBlock(streamID,\n                         requiredInsertCount,\n                         baseIndex_,\n                         dbuf.consumedBytes(),\n                         q.move(),\n                         totalBytes - dbuf.consumedBytes(),\n                         streamingCb);\n    }\n  } else {\n    decodeStreamingImpl(requiredInsertCount, 0, dbuf, streamingCb);\n  }\n}\n\nuint32_t QPACKDecoder::decodePrefix(HPACKDecodeBuffer& dbuf) {\n  uint64_t requiredInsertCount;\n  uint64_t wireRIC;\n  uint64_t maxEntries = getMaxEntries(maxTableSize_);\n  uint64_t fullRange = 2 * maxEntries;\n\n  err_ = dbuf.decodeInteger(wireRIC);\n  if (err_ != HPACK::DecodeError::NONE) {\n    LOG(ERROR) << \"Decode error decoding requiredInsertCount err_=\" << err_;\n    return 0;\n  }\n  if (wireRIC == 0) {\n    requiredInsertCount = 0;\n  } else if (maxEntries == 0) {\n    LOG(ERROR) << \"Encoder used dynamic table when not permitted, wireRIC=\"\n               << wireRIC;\n    err_ = HPACK::DecodeError::INVALID_INDEX;\n    return 0;\n  } else {\n    uint64_t maxValue = table_.getInsertCount() + maxEntries;\n    uint64_t maxWrapped = (maxValue / fullRange) * fullRange;\n    requiredInsertCount = maxWrapped + wireRIC - 1;\n    // If requiredInsertCount exceeds maxValue, the Encoder's value must have\n    // wrapped one fewer time\n    if (requiredInsertCount > maxValue) {\n      if (wireRIC > fullRange || requiredInsertCount < fullRange) {\n        LOG(ERROR) << \"Decode error RIC out of range=\" << wireRIC;\n        err_ = HPACK::DecodeError::INVALID_INDEX;\n        return 0;\n      }\n      requiredInsertCount -= fullRange;\n    }\n  }\n  VLOG(5) << \"Decoded requiredInsertCount=\" << requiredInsertCount;\n  uint64_t delta = 0;\n  if (dbuf.empty()) {\n    LOG(ERROR) << \"Invalid prefix, no delta-base\";\n    err_ = HPACK::DecodeError::BUFFER_UNDERFLOW;\n    return 0;\n  }\n  bool neg = dbuf.peek() & HPACK::Q_DELTA_BASE_NEG;\n  err_ = dbuf.decodeInteger(HPACK::Q_DELTA_BASE.prefixLength, delta);\n  if (err_ != HPACK::DecodeError::NONE) {\n    LOG(ERROR) << \"Decode error decoding delta base=\" << err_;\n    return 0;\n  }\n  if (neg) {\n    // delta must be smaller than RIC\n    if (delta >= requiredInsertCount) {\n      LOG(ERROR) << \"Received invalid delta=\" << delta\n                 << \" requiredInsertCount=\" << requiredInsertCount;\n      err_ = HPACK::DecodeError::INVALID_INDEX;\n      return 0;\n    }\n    // The largest table we support is 2^32 - 1 / 32 entries, so\n    // requiredInsertCount (less any delta, etc) must be < 2^32.\n    CHECK_LE(requiredInsertCount - delta - 1,\n             std::numeric_limits<uint32_t>::max());\n    baseIndex_ = requiredInsertCount - delta - 1;\n  } else {\n    // base must be < 2^32\n    if (delta > std::numeric_limits<uint32_t>::max() ||\n        requiredInsertCount >=\n            uint64_t(std::numeric_limits<uint32_t>::max()) - delta) {\n      LOG(ERROR) << \"Invalid delta=\" << delta\n                 << \" requiredInsertCount=\" << requiredInsertCount;\n      err_ = HPACK::DecodeError::INVALID_INDEX;\n      return 0;\n    }\n    baseIndex_ = requiredInsertCount + delta;\n  }\n  VLOG(5) << \"Decoded baseIndex_=\" << baseIndex_;\n  return requiredInsertCount;\n}\n\nvoid QPACKDecoder::decodeStreamingImpl(uint32_t requiredInsertCount,\n                                       uint32_t consumed,\n                                       HPACKDecodeBuffer& dbuf,\n                                       HPACK::StreamingCallback* streamingCb) {\n  uint32_t emittedSize = 0;\n\n  while (!hasError() && !dbuf.empty()) {\n    emittedSize += decodeHeaderQ(dbuf, streamingCb);\n    if (emittedSize > maxUncompressed_) {\n      LOG(ERROR) << \"Exceeded uncompressed size limit of \" << maxUncompressed_\n                 << \" bytes\";\n      err_ = HPACK::DecodeError::HEADERS_TOO_LARGE;\n      break;\n    }\n    emittedSize += 2;\n  }\n\n  bool acknowledge = requiredInsertCount != 0;\n  if (!hasError()) {\n    // lastAcked_ is only read in encodeInsertCountInc, so all completed header\n    // blocks must be call encodeHeaderAck BEFORE calling encodeInsertCountInc.\n    lastAcked_ = std::max(lastAcked_, requiredInsertCount);\n  }\n  auto blockSize = consumed + dbuf.consumedBytes();\n  auto compressedSize = pendingEncoderBytes_ + blockSize;\n  pendingEncoderBytes_ = 0;\n  completeDecode(HeaderCodec::Type::QPACK,\n                 streamingCb,\n                 compressedSize,\n                 blockSize,\n                 emittedSize,\n                 acknowledge);\n}\n\nuint32_t QPACKDecoder::decodeHeaderQ(HPACKDecodeBuffer& dbuf,\n                                     HPACK::StreamingCallback* streamingCb) {\n  uint8_t byte = dbuf.peek();\n  if (byte & HPACK::Q_INDEXED.code) {\n    return decodeIndexedHeaderQ(\n        dbuf, HPACK::Q_INDEXED.prefixLength, false, streamingCb, nullptr);\n  } else if (byte & HPACK::Q_LITERAL_NAME_REF.code) {\n    return decodeLiteralHeaderQ(dbuf,\n                                false,\n                                true,\n                                HPACK::Q_LITERAL_NAME_REF.prefixLength,\n                                false,\n                                streamingCb);\n  } else if (byte & HPACK::Q_LITERAL.code) {\n    return decodeLiteralHeaderQ(\n        dbuf, false, false, HPACK::Q_LITERAL.prefixLength, false, streamingCb);\n  } else if (byte & HPACK::Q_INDEXED_POST.code) {\n    return decodeIndexedHeaderQ(\n        dbuf, HPACK::Q_INDEXED_POST.prefixLength, true, streamingCb, nullptr);\n  } else { // Q_LITERAL_NAME_REF_POST\n    return decodeLiteralHeaderQ(dbuf,\n                                false,\n                                true,\n                                HPACK::Q_LITERAL_NAME_REF_POST.prefixLength,\n                                true,\n                                streamingCb);\n  }\n}\n\nHPACK::DecodeError QPACKDecoder::decodeEncoderStream(\n    std::unique_ptr<folly::IOBuf> buf) {\n  ingress_.append(std::move(buf));\n  Cursor cursor(ingress_.front());\n  HPACKDecodeBuffer dbuf(cursor,\n                         ingress_.chainLength(),\n                         maxUncompressed_,\n                         /* endOfBufferIsError=*/false);\n\n  VLOG(6) << \"Decoding control block\";\n  baseIndex_ = 0;\n  err_ = HPACK::DecodeError::NONE;\n  while (!hasError() && !dbuf.empty()) {\n    decodeEncoderStreamInstruction(dbuf);\n    if (err_ == HPACK::DecodeError::BUFFER_UNDERFLOW) {\n      ingress_.trimStart(partial_.consumed);\n      drainQueue();\n      return HPACK::DecodeError::NONE;\n    }\n  }\n  pendingEncoderBytes_ += dbuf.consumedBytes();\n  ingress_.trimStart(dbuf.consumedBytes());\n  if (hasError()) {\n    return err_;\n  } else {\n    drainQueue();\n    return HPACK::DecodeError::NONE;\n  }\n}\n\nHPACK::DecodeError QPACKDecoder::encoderStreamEnd() {\n  if (!ingress_.empty()) {\n    err_ = HPACK::DecodeError::BUFFER_UNDERFLOW;\n  }\n  if (!queue_.empty()) {\n    if (err_ != HPACK::DecodeError::NONE) {\n      err_ = HPACK::DecodeError::ENCODER_STREAM_CLOSED;\n    }\n    errorQueue();\n  }\n  return err_;\n}\n\nvoid QPACKDecoder::decodeEncoderStreamInstruction(HPACKDecodeBuffer& dbuf) {\n  uint8_t byte = dbuf.peek();\n  partial_.consumed = dbuf.consumedBytes();\n  if (partial_.state == Partial::VALUE ||\n      byte & HPACK::Q_INSERT_NAME_REF.code) {\n    // If partial state is VALUE, it might have been a NO_NAME_REF instruction,\n    // but we've already parsed the name, so it doesn't matter\n    decodeLiteralHeaderQ(dbuf,\n                         true,\n                         true,\n                         HPACK::Q_INSERT_NAME_REF.prefixLength,\n                         false,\n                         nullptr);\n  } else if (byte & HPACK::Q_INSERT_NO_NAME_REF.code) {\n    decodeLiteralHeaderQ(dbuf,\n                         true,\n                         false,\n                         HPACK::Q_INSERT_NO_NAME_REF.prefixLength,\n                         false,\n                         nullptr);\n  } else if (byte & HPACK::Q_TABLE_SIZE_UPDATE.code) {\n    handleTableSizeUpdate(dbuf, table_, true);\n  } else { // must be Q_DUPLICATE=000\n    headers_t emitted;\n    decodeIndexedHeaderQ(\n        dbuf, HPACK::Q_DUPLICATE.prefixLength, false, nullptr, &emitted);\n    if (!hasError()) {\n      CHECK(!emitted.empty());\n      if (!table_.add(std::move(emitted[0]))) {\n        // the only case is the header was > table capacity.  But how can we\n        // duplicate such a header?\n        LOG(DFATAL) << \"Encoder duplicated a header larger than capacity\";\n        err_ = HPACK::DecodeError::INSERT_TOO_LARGE;\n      } else {\n        duplications_++;\n      }\n    }\n  }\n}\n\nuint32_t QPACKDecoder::decodeLiteralHeaderQ(\n    HPACKDecodeBuffer& dbuf,\n    bool indexing,\n    bool nameIndexed,\n    uint8_t prefixLength,\n    bool aboveBase,\n    HPACK::StreamingCallback* streamingCb) {\n  bool allowPartial = (streamingCb == nullptr);\n  Partial localPartial;\n  Partial* partial = (allowPartial) ? &partial_ : &localPartial;\n  if (partial->state == Partial::NAME) {\n    if (nameIndexed) {\n      uint64_t nameIndex = 0;\n      bool isStaticName = !aboveBase && (dbuf.peek() & (1 << prefixLength));\n      err_ = dbuf.decodeInteger(prefixLength, nameIndex);\n      if (allowPartial && err_ == HPACK::DecodeError::BUFFER_UNDERFLOW) {\n        return 0;\n      }\n      if (err_ != HPACK::DecodeError::NONE) {\n        LOG(ERROR) << \"Decode error decoding index err_=\" << err_;\n        return 0;\n      }\n      nameIndex++;\n      // validate the index\n      if (!isValid(isStaticName, nameIndex, aboveBase)) {\n        LOG(ERROR) << \"Received invalid index=\" << nameIndex;\n        err_ = HPACK::DecodeError::INVALID_INDEX;\n        return 0;\n      }\n      partial->header.name =\n          getHeader(isStaticName, nameIndex, baseIndex_, aboveBase).name;\n    } else {\n      folly::fbstring headerName;\n      err_ = dbuf.decodeLiteral(prefixLength, headerName);\n      if (allowPartial && err_ == HPACK::DecodeError::BUFFER_UNDERFLOW) {\n        return 0;\n      }\n      if (err_ != HPACK::DecodeError::NONE) {\n        LOG(ERROR) << \"Error decoding header name err_=\" << err_;\n        return 0;\n      }\n      partial->header.name = HPACKHeaderName{headerName};\n    }\n    partial->state = Partial::VALUE;\n    partial->consumed = dbuf.consumedBytes();\n  }\n  // value\n  err_ = dbuf.decodeLiteral(partial->header.value);\n  if (allowPartial && err_ == HPACK::DecodeError::BUFFER_UNDERFLOW) {\n    return 0;\n  }\n  if (err_ != HPACK::DecodeError::NONE) {\n    LOG(ERROR) << \"Error decoding header value name=\" << partial->header.name\n               << \" err_=\" << err_;\n    return 0;\n  }\n  partial->state = Partial::NAME;\n\n  uint32_t emittedSize = emit(partial->header, streamingCb, nullptr);\n\n  if (indexing) {\n    if (!table_.add(std::move(partial->header))) {\n      // the only case is the header was > table capacity\n      LOG(ERROR) << \"Encoder inserted a header larger than capacity\";\n      err_ = HPACK::DecodeError::INSERT_TOO_LARGE;\n    }\n  }\n\n  return emittedSize;\n}\n\nuint32_t QPACKDecoder::decodeIndexedHeaderQ(\n    HPACKDecodeBuffer& dbuf,\n    uint32_t prefixLength,\n    bool aboveBase,\n    HPACK::StreamingCallback* streamingCb,\n    headers_t* emitted) {\n  uint64_t index;\n  bool isStatic = !aboveBase && (dbuf.peek() & (1 << prefixLength));\n  err_ = dbuf.decodeInteger(prefixLength, index);\n  if (err_ != HPACK::DecodeError::NONE) {\n    if (streamingCb || err_ != HPACK::DecodeError::BUFFER_UNDERFLOW) {\n      LOG(ERROR) << \"Decode error decoding index err_=\" << err_;\n    }\n    return 0;\n  }\n  CHECK_LT(index, std::numeric_limits<uint64_t>::max());\n  index++;\n  // validate the index\n  if (index == 0 || !isValid(isStatic, index, aboveBase)) {\n    LOG(ERROR) << \"received invalid index: \" << index;\n    err_ = HPACK::DecodeError::INVALID_INDEX;\n    return 0;\n  }\n\n  auto& header = getHeader(isStatic, index, baseIndex_, aboveBase);\n  return emit(header, streamingCb, emitted);\n}\n\nbool QPACKDecoder::isValid(bool isStatic, uint64_t index, bool aboveBase) {\n  if (index > std::numeric_limits<uint32_t>::max()) {\n    return false;\n  }\n  if (isStatic) {\n    return getStaticTable().isValid(index);\n  } else {\n    uint64_t baseIndex = baseIndex_;\n    if (aboveBase) {\n      baseIndex = baseIndex + index;\n      if (baseIndex > std::numeric_limits<uint32_t>::max()) {\n        return false;\n      }\n      index = 1;\n    }\n    return table_.isValid(index, baseIndex);\n  }\n}\n\nstd::unique_ptr<folly::IOBuf> QPACKDecoder::encodeInsertCountInc() {\n  uint32_t toAck = table_.getInsertCount() - lastAcked_;\n  if (toAck > 0) {\n    VLOG(6) << \"encodeInsertCountInc toAck=\" << toAck;\n    HPACKEncodeBuffer ackEncoder(kGrowth, false);\n    ackEncoder.encodeInteger(toAck, HPACK::Q_INSERT_COUNT_INC);\n    lastAcked_ = table_.getInsertCount();\n    return ackEncoder.release();\n  } else {\n    return nullptr;\n  }\n}\n\nstd::unique_ptr<folly::IOBuf> QPACKDecoder::encodeHeaderAck(\n    uint64_t streamId) const {\n  HPACKEncodeBuffer ackEncoder(kGrowth, false);\n  VLOG(6) << \"encodeHeaderAck id=\" << streamId;\n  ackEncoder.encodeInteger(streamId, HPACK::Q_HEADER_ACK);\n  return ackEncoder.release();\n}\n\nstd::unique_ptr<folly::IOBuf> QPACKDecoder::encodeCancelStream(\n    uint64_t streamId) {\n  // Remove this stream from the queue\n  VLOG(6) << \"encodeCancelStream id=\" << streamId;\n  auto it = queue_.begin();\n  while (it != queue_.end()) {\n    if (it->second.streamID == streamId) {\n      it = queue_.erase(it);\n    } else {\n      it++;\n    }\n  }\n  HPACKEncodeBuffer ackEncoder(kGrowth, false);\n  ackEncoder.encodeInteger(streamId, HPACK::Q_CANCEL_STREAM);\n  return ackEncoder.release();\n}\n\nvoid QPACKDecoder::enqueueHeaderBlock(uint64_t streamID,\n                                      uint32_t requiredInsertCount,\n                                      uint32_t baseIndex,\n                                      uint32_t consumed,\n                                      std::unique_ptr<folly::IOBuf> block,\n                                      size_t length,\n                                      HPACK::StreamingCallback* streamingCb) {\n  // TDOO: this queue is currently unbounded and has no timeouts\n  CHECK_GT(requiredInsertCount, table_.getInsertCount());\n  queue_.emplace(std::piecewise_construct,\n                 std::forward_as_tuple(requiredInsertCount),\n                 std::forward_as_tuple(streamID,\n                                       baseIndex,\n                                       length,\n                                       consumed,\n                                       std::move(block),\n                                       streamingCb));\n  holBlockCount_++;\n  VLOG(5) << \"queued block=\" << requiredInsertCount << \" len=\" << length;\n  queuedBytes_ += length;\n}\n\nbool QPACKDecoder::decodeBlock(uint32_t requiredInsertCount,\n                               const PendingBlock& pending) {\n  if (pending.length > 0) {\n    VLOG(5) << \"decodeBlock len=\" << pending.length;\n    folly::io::Cursor cursor(pending.block.get());\n    HPACKDecodeBuffer dbuf(cursor, pending.length, maxUncompressed_);\n    DCHECK_LE(pending.length, queuedBytes_);\n    queuedBytes_ -= pending.length;\n    baseIndex_ = pending.baseIndex;\n    folly::DestructorCheck::Safety safety(*this);\n    decodeStreamingImpl(\n        requiredInsertCount, pending.consumed, dbuf, pending.cb);\n    // The callback may destroy this, if so stop queue processing\n    if (safety.destroyed()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid QPACKDecoder::drainQueue() {\n  auto it = queue_.begin();\n  while (!queue_.empty() && it->first <= table_.getInsertCount() &&\n         !hasError()) {\n    auto id = it->first;\n    PendingBlock block = std::move(it->second);\n    queue_.erase(it);\n    if (decodeBlock(id, block)) {\n      return;\n    }\n    it = queue_.begin();\n  }\n}\n\nvoid QPACKDecoder::errorQueue() {\n  // The callback may destroy this, if so stop queue processing\n  folly::DestructorCheck::Safety safety(*this);\n  while (!safety.destroyed() && !queue_.empty()) {\n    auto it = queue_.begin();\n    PendingBlock block = std::move(it->second);\n    queue_.erase(it);\n    block.cb->onDecodeError(HPACK::DecodeError::ENCODER_STREAM_CLOSED);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKDecoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/io/async/DestructorCheck.h>\n#include <map>\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKDecoderBase.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/codec/compress/QPACKContext.h>\n\nnamespace proxygen {\n\nclass QPACKDecoder\n    : public HPACKDecoderBase\n    , public QPACKContext\n    , public folly::DestructorCheck {\n public:\n  explicit QPACKDecoder(\n      uint32_t tableSize = HPACK::kTableSize,\n      uint32_t maxUncompressed = HeaderCodec::kMaxUncompressed)\n      : HPACKDecoderBase(tableSize, maxUncompressed),\n        QPACKContext(tableSize, false /* don't track references */) {\n  }\n\n  void decodeStreaming(uint64_t streamId,\n                       std::unique_ptr<folly::IOBuf> block,\n                       uint32_t totalBytes,\n                       HPACK::StreamingCallback* streamingCb);\n\n  HPACK::DecodeError decodeEncoderStream(std::unique_ptr<folly::IOBuf> buf);\n\n  HPACK::DecodeError encoderStreamEnd();\n\n  std::unique_ptr<folly::IOBuf> encodeInsertCountInc();\n\n  std::unique_ptr<folly::IOBuf> encodeHeaderAck(uint64_t streamId) const;\n\n  std::unique_ptr<folly::IOBuf> encodeCancelStream(uint64_t streamId);\n\n  uint64_t getHolBlockCount() const {\n    return holBlockCount_;\n  }\n\n  uint64_t getQueuedBytes() const {\n    return queuedBytes_;\n  }\n\n  void setMaxBlocking(uint32_t maxBlocking) {\n    maxBlocking_ = maxBlocking;\n  }\n\n  void setHeaderTableMaxSize(uint32_t maxSize) {\n    CHECK(maxTableSize_ == 0 || maxTableSize_ == maxSize)\n        << \"Cannot change non-zero max header table size, \"\n           \"maxTableSize_=\"\n        << maxTableSize_ << \" maxSize=\" << maxSize;\n    HPACKDecoderBase::setHeaderTableMaxSize(table_, maxSize);\n  }\n\n private:\n  bool isValid(bool isStatic, uint64_t index, bool aboveBase);\n\n  uint32_t decodePrefix(HPACKDecodeBuffer& dbuf);\n\n  void decodeStreamingImpl(uint32_t requiredInsertCount,\n                           uint32_t consumed,\n                           HPACKDecodeBuffer& dbuf,\n                           HPACK::StreamingCallback* streamingCb);\n\n  uint32_t decodeHeaderQ(HPACKDecodeBuffer& dbuf,\n                         HPACK::StreamingCallback* streamingCb);\n\n  uint32_t decodeIndexedHeaderQ(HPACKDecodeBuffer& dbuf,\n                                uint32_t prefixLength,\n                                bool aboveBase,\n                                HPACK::StreamingCallback* streamingCb,\n                                headers_t* emitted);\n\n  uint32_t decodeLiteralHeaderQ(HPACKDecodeBuffer& dbuf,\n                                bool indexing,\n                                bool nameIndexed,\n                                uint8_t prefixLength,\n                                bool aboveBase,\n                                HPACK::StreamingCallback* streamingCb);\n\n  void decodeEncoderStreamInstruction(HPACKDecodeBuffer& dbuf);\n\n  void enqueueHeaderBlock(uint64_t streamId,\n                          uint32_t requiredInsertCount,\n                          uint32_t baseIndex,\n                          uint32_t consumed,\n                          std::unique_ptr<folly::IOBuf> block,\n                          size_t length,\n                          HPACK::StreamingCallback* streamingCb);\n\n  struct PendingBlock {\n    PendingBlock(uint64_t sid,\n                 uint32_t bi,\n                 uint32_t l,\n                 uint32_t cons,\n                 std::unique_ptr<folly::IOBuf> b,\n                 HPACK::StreamingCallback* c)\n        : streamID(sid),\n          baseIndex(bi),\n          length(l),\n          consumed(cons),\n          block(std::move(b)),\n          cb(c) {\n    }\n    uint64_t streamID;\n    uint32_t baseIndex;\n    uint32_t length;\n    uint32_t consumed;\n    std::unique_ptr<folly::IOBuf> block;\n    HPACK::StreamingCallback* cb;\n  };\n\n  // Returns true if this object was destroyed by its callback.  Callers\n  // should check the result and immediately return.\n  bool decodeBlock(uint32_t requiredInsertCount, const PendingBlock& pending);\n\n  void drainQueue();\n  void errorQueue();\n\n  uint32_t maxBlocking_{HPACK::kDefaultBlocking};\n  uint32_t baseIndex_{0};\n  uint32_t lastAcked_{0};\n  uint32_t holBlockCount_{0};\n  uint32_t pendingEncoderBytes_{0};\n  uint64_t queuedBytes_{0};\n  std::multimap<uint32_t, PendingBlock> queue_;\n\n  // This holds the state of a partially decoded literal insert on the control\n  // stream\n  struct Partial {\n    enum { NAME, VALUE } state{NAME};\n    uint32_t consumed;\n    HPACKHeader header;\n  };\n  Partial partial_;\n  folly::IOBufQueue ingress_{folly::IOBufQueue::cacheChainLength()};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKEncoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKEncoder.h>\n\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n\nusing std::vector;\n\nnamespace proxygen {\n\nQPACKEncoder::QPACKEncoder(bool huffman, uint32_t tableSize)\n    : HPACKEncoderBase(huffman),\n      QPACKContext(tableSize, true),\n      controlBuffer_(kBufferGrowth, huffman),\n      maxTableSize_(tableSize) {\n}\n\nQPACKEncoder::EncodeResult QPACKEncoder::encode(\n    const vector<HPACKHeader>& headers,\n    uint32_t headroom,\n    uint64_t streamId,\n    uint32_t maxEncoderStreamBytes) {\n  // This routine is now used only for testing\n  folly::IOBufQueue controlQueue{folly::IOBufQueue::cacheChainLength()};\n  startEncode(controlQueue, headroom, maxEncoderStreamBytes);\n  auto baseIndex = table_.getInsertCount();\n\n  uint32_t requiredInsertCount = 0;\n  for (const auto& header : headers) {\n    encodeHeaderQ(HPACKHeaderName(header.name),\n                  header.value,\n                  baseIndex,\n                  /*ref*/ requiredInsertCount);\n  }\n\n  return {controlQueue.move(),\n          completeEncode(streamId, baseIndex, requiredInsertCount)};\n}\n\nuint32_t QPACKEncoder::startEncode(folly::IOBufQueue& controlQueue,\n                                   uint32_t headroom,\n                                   uint32_t maxEncoderStreamBytes) {\n  controlBuffer_.setWriteBuf(&controlQueue);\n  if (headroom) {\n    streamBuffer_.addHeadroom(headroom);\n  }\n  maxEncoderStreamBytes_ = maxEncoderStreamBytes;\n  maxEncoderStreamBytes_ -=\n      handlePendingContextUpdate(controlBuffer_, table_.capacity());\n\n  return table_.getInsertCount();\n}\n\nstd::unique_ptr<folly::IOBuf> QPACKEncoder::completeEncode(\n    uint64_t streamId, uint32_t baseIndex, uint32_t requiredInsertCount) {\n  auto streamBlock = streamBuffer_.release();\n\n  // encode the prefix\n  if (requiredInsertCount == 0) {\n    streamBuffer_.encodeInteger(0); // required insert count\n    streamBuffer_.encodeInteger(0); // delta base\n  } else {\n    auto wireRIC =\n        (requiredInsertCount % (2 * getMaxEntries(maxTableSize_))) + 1;\n    streamBuffer_.encodeInteger(wireRIC);\n    if (requiredInsertCount > baseIndex) {\n      streamBuffer_.encodeInteger(requiredInsertCount - baseIndex - 1,\n                                  HPACK::Q_DELTA_BASE_NEG,\n                                  HPACK::Q_DELTA_BASE.prefixLength);\n    } else {\n      streamBuffer_.encodeInteger(baseIndex - requiredInsertCount,\n                                  HPACK::Q_DELTA_BASE_POS,\n                                  HPACK::Q_DELTA_BASE.prefixLength);\n    }\n  }\n  auto streamBuffer = streamBuffer_.release();\n  if (streamBlock) {\n    streamBuffer->prependChain(std::move(streamBlock));\n  }\n\n  // curOutstanding_.minInUseIndex could be max, if the block encodes only\n  // static headers and/or literals.  If so we don't track anything.\n  if (curOutstanding_.minInUseIndex != std::numeric_limits<uint32_t>::max()) {\n    outstandingMins_.push_back(curOutstanding_.minInUseIndex);\n    if (curOutstanding_.vulnerable) {\n      DCHECK(allowVulnerable());\n      numVulnerable_++;\n    }\n    numOutstandingBlocks_++;\n    outstanding_[streamId].emplace_back(std::move(curOutstanding_));\n    curOutstanding_.vulnerable = false;\n    curOutstanding_.minInUseIndex = std::numeric_limits<uint32_t>::max();\n    curOutstanding_.maxInUseIndex = 0;\n  }\n\n  controlBuffer_.setWriteBuf(nullptr);\n  return streamBuffer;\n}\n\nsize_t QPACKEncoder::encodeHeaderQ(HPACKHeaderName name,\n                                   folly::StringPiece value,\n                                   uint32_t baseIndex,\n                                   uint32_t& requiredInsertCount) {\n  size_t uncompressed = HPACKHeader::realBytes(name.size(), value.size()) + 2;\n  uint32_t index = getStaticTable().getIndex(name, value).first;\n  if (index > 0) {\n    // static reference\n    staticRefs_++;\n    streamBuffer_.encodeInteger(index - 1,\n                                HPACK::Q_INDEXED.code | HPACK::Q_INDEXED_STATIC,\n                                HPACK::Q_INDEXED.prefixLength);\n    return uncompressed;\n  }\n\n  bool indexable = shouldIndex(name, value);\n  if (indexable) {\n    index = table_.getIndex(name, value, allowVulnerable());\n    if (index == QPACKHeaderTable::UNACKED) {\n      index = 0;\n      indexable = false;\n    }\n  }\n  if (index != 0) {\n    // dynamic reference\n    bool duplicated = false;\n    std::tie(duplicated, index) = maybeDuplicate(index);\n    // index is now 0 or absolute\n    indexable &= (duplicated && index == 0);\n  }\n  if (index == 0) {\n    // No valid entry matching header, see if there's a matching name\n    uint32_t nameIndex = 0;\n    uint32_t absoluteNameIndex = 0;\n    bool isStaticName = false;\n    std::tie(isStaticName, nameIndex, absoluteNameIndex) = getNameIndexQ(name);\n\n    // Now check if we should emit an insertion on the control stream\n    // Don't try to index if we're out of encoder flow control\n    indexable &= maxEncoderStreamBytes_ > 0;\n    if (indexable) {\n      if (table_.canIndex(name, value)) {\n        encodeInsertQ(name, value, isStaticName, nameIndex);\n        CHECK(table_.add(HPACKHeader(std::move(name), value)));\n        if (allowVulnerable() && lastEntryAvailable()) {\n          index = table_.getInsertCount();\n          // name is invalid on this branch, but index must be non-zero since\n          // we inserted just above.\n        } else {\n          // We still need name.  Get it from the table\n          name = getHeader(false, 1, table_.getInsertCount(), false).name;\n          index = 0;\n          if (absoluteNameIndex > 0 &&\n              !table_.isValid(table_.absoluteToRelative(absoluteNameIndex))) {\n            // The insert may have invalidated the name index.\n            isStaticName = true;\n            nameIndex = 0;\n            absoluteNameIndex = 0;\n          }\n        }\n      } else {\n        blockedInsertions_++;\n      }\n    }\n    if (index == 0) {\n      // Couldn't insert it: table full, not indexable, or table contains\n      // vulnerable reference.  Encode a literal on the request stream.\n      encodeStreamLiteralQ(name,\n                           value,\n                           isStaticName,\n                           nameIndex,\n                           absoluteNameIndex,\n                           baseIndex,\n                           requiredInsertCount);\n      return uncompressed;\n    }\n  }\n\n  // Encoding a dynamic index reference\n  DCHECK_NE(index, 0);\n  trackReference(index, requiredInsertCount);\n  if (index > baseIndex) {\n    streamBuffer_.encodeInteger(index - baseIndex - 1, HPACK::Q_INDEXED_POST);\n  } else {\n    streamBuffer_.encodeInteger(baseIndex - index, HPACK::Q_INDEXED);\n  }\n  return uncompressed;\n}\n\nbool QPACKEncoder::shouldIndex(const HPACKHeaderName& name,\n                               folly::StringPiece value) const {\n  return (HPACKHeader::bytes(name.size(), value.size()) <= table_.capacity()) &&\n         (!indexingStrat_ || indexingStrat_->indexHeader(name, value)) &&\n         dynamicReferenceAllowed();\n}\n\nbool QPACKEncoder::dynamicReferenceAllowed() const {\n  return numOutstandingBlocks_ < maxNumOutstandingBlocks_;\n}\n\nstd::pair<bool, uint32_t> QPACKEncoder::maybeDuplicate(uint32_t relativeIndex) {\n  auto res = table_.maybeDuplicate(relativeIndex, allowVulnerable());\n  if (res.first) {\n    VLOG(4) << \"Encoded duplicate index=\" << relativeIndex;\n    duplications_++;\n    encodeDuplicate(relativeIndex);\n    // Note we will emit duplications even when we are out of flow control,\n    // but we won't reference them (eg: like we were at vulnerable max).\n    if (!lastEntryAvailable()) {\n      VLOG(4) << \"Duplicate is not usable because it overran encoder flow \"\n                 \"control\";\n      return {true, 0};\n    }\n  }\n  return res;\n}\n\nstd::tuple<bool, uint32_t, uint32_t> QPACKEncoder::getNameIndexQ(\n    const HPACKHeaderName& headerName) {\n  uint32_t absoluteNameIndex = 0;\n  uint32_t nameIndex = getStaticTable().nameIndex(headerName);\n  bool isStatic = true;\n  if (nameIndex == 0 && dynamicReferenceAllowed()) {\n    // check dynamic table\n    nameIndex = table_.nameIndex(headerName, allowVulnerable());\n    if (nameIndex != 0) {\n      absoluteNameIndex = maybeDuplicate(nameIndex).second;\n      if (absoluteNameIndex) {\n        isStatic = false;\n        nameIndex = table_.absoluteToRelative(absoluteNameIndex);\n      } else {\n        nameIndex = 0;\n        absoluteNameIndex = 0;\n      }\n    }\n  }\n  return std::tuple<bool, uint32_t, uint32_t>(\n      isStatic, nameIndex, absoluteNameIndex);\n}\n\nsize_t QPACKEncoder::encodeStreamLiteralQ(const HPACKHeaderName& name,\n                                          folly::StringPiece value,\n                                          bool isStaticName,\n                                          uint32_t nameIndex,\n                                          uint32_t absoluteNameIndex,\n                                          uint32_t baseIndex,\n                                          uint32_t& requiredInsertCount) {\n  if (absoluteNameIndex > 0) {\n    // Dynamic name reference, vulnerability checks already done\n    CHECK(absoluteNameIndex <= baseIndex || allowVulnerable());\n    trackReference(absoluteNameIndex, requiredInsertCount);\n  }\n  if (absoluteNameIndex > baseIndex) {\n    return encodeLiteralQ(name,\n                          value,\n                          false, /* not static */\n                          true,  /* post base */\n                          absoluteNameIndex - baseIndex,\n                          HPACK::Q_LITERAL_NAME_REF_POST);\n  } else {\n    return encodeLiteralQ(name,\n                          value,\n                          isStaticName,\n                          false, /* not post base */\n                          isStaticName ? nameIndex\n                                       : baseIndex - absoluteNameIndex + 1,\n                          HPACK::Q_LITERAL_NAME_REF);\n  }\n}\n\nvoid QPACKEncoder::trackReference(uint32_t absoluteIndex,\n                                  uint32_t& requiredInsertCount) {\n  CHECK_NE(absoluteIndex, 0);\n  if (absoluteIndex > requiredInsertCount) {\n    requiredInsertCount = absoluteIndex;\n    curOutstanding_.maxInUseIndex = requiredInsertCount;\n    if (table_.isVulnerable(absoluteIndex)) {\n      curOutstanding_.vulnerable = true;\n    }\n  }\n  if (absoluteIndex < curOutstanding_.minInUseIndex) {\n    curOutstanding_.minInUseIndex = absoluteIndex;\n    minOutstandingMin_ =\n        std::min(minOutstandingMin_, curOutstanding_.minInUseIndex);\n    // Update table min here to prevent new encodes from evicting reference\n    table_.setMinInUseIndex(minOutstandingMin_);\n  }\n}\n\nvoid QPACKEncoder::encodeDuplicate(uint32_t index) {\n  DCHECK_GT(index, 0);\n  maxEncoderStreamBytes_ -=\n      controlBuffer_.encodeInteger(index - 1, HPACK::Q_DUPLICATE);\n}\n\nvoid QPACKEncoder::encodeInsertQ(const HPACKHeaderName& name,\n                                 folly::StringPiece value,\n                                 bool isStaticName,\n                                 uint32_t nameIndex) {\n  auto encoded = encodeLiteralQHelper(controlBuffer_,\n                                      name,\n                                      value,\n                                      isStaticName,\n                                      nameIndex,\n                                      HPACK::Q_INSERT_NAME_REF_STATIC,\n                                      HPACK::Q_INSERT_NAME_REF,\n                                      HPACK::Q_INSERT_NO_NAME_REF);\n  maxEncoderStreamBytes_ -= encoded;\n}\n\nsize_t QPACKEncoder::encodeLiteralQ(const HPACKHeaderName& name,\n                                    folly::StringPiece value,\n                                    bool isStaticName,\n                                    bool postBase,\n                                    uint32_t nameIndex,\n                                    const HPACK::Instruction& idxInstr) {\n  DCHECK(!isStaticName || !postBase);\n  return encodeLiteralQHelper(streamBuffer_,\n                              name,\n                              value,\n                              isStaticName,\n                              nameIndex,\n                              HPACK::Q_LITERAL_STATIC,\n                              idxInstr,\n                              HPACK::Q_LITERAL);\n}\n\nuint32_t QPACKEncoder::encodeLiteralQHelper(\n    HPACKEncodeBuffer& buffer,\n    const HPACKHeaderName& name,\n    folly::StringPiece value,\n    bool isStaticName,\n    uint32_t nameIndex,\n    uint8_t staticFlag,\n    const HPACK::Instruction& idxInstr,\n    const HPACK::Instruction& litInstr) {\n  uint32_t encoded = 0;\n  // name\n  if (nameIndex) {\n    VLOG(10) << \"encoding name index=\" << nameIndex;\n    DCHECK_NE(nameIndex, QPACKHeaderTable::UNACKED);\n    nameIndex -= 1; // we already know it's not 0\n    uint8_t byte = idxInstr.code;\n    if (isStaticName) {\n      // This counts static refs on the encoder stream\n      staticRefs_++;\n      byte |= staticFlag;\n    }\n    encoded += buffer.encodeInteger(nameIndex, byte, idxInstr.prefixLength);\n  } else {\n    encoded +=\n        buffer.encodeLiteral(litInstr.code, litInstr.prefixLength, name.get());\n  }\n  // value\n  encoded += buffer.encodeLiteral(value);\n  return encoded;\n}\n\nHPACK::DecodeError QPACKEncoder::decodeDecoderStream(\n    std::unique_ptr<folly::IOBuf> buf) {\n  decoderIngress_.append(std::move(buf));\n  folly::io::Cursor cursor(decoderIngress_.front());\n  HPACKDecodeBuffer dbuf(cursor,\n                         decoderIngress_.chainLength(),\n                         0,\n                         /* endOfBufferIsError= */ false);\n  HPACK::DecodeError err = HPACK::DecodeError::NONE;\n  uint32_t consumed = 0;\n  while (err == HPACK::DecodeError::NONE && !dbuf.empty()) {\n    consumed = dbuf.consumedBytes();\n    auto byte = dbuf.peek();\n    if (byte & HPACK::Q_HEADER_ACK.code) {\n      err = decodeHeaderAck(dbuf, HPACK::Q_HEADER_ACK.prefixLength, false);\n    } else if (byte & HPACK::Q_CANCEL_STREAM.code) {\n      err = decodeHeaderAck(dbuf, HPACK::Q_CANCEL_STREAM.prefixLength, true);\n    } else { // INSERT_COUNT_INC\n      uint64_t numInserts = 0;\n      err = dbuf.decodeInteger(HPACK::Q_INSERT_COUNT_INC.prefixLength,\n                               numInserts);\n      if (err == HPACK::DecodeError::NONE) {\n        err = onInsertCountIncrement(numInserts);\n      } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {\n        LOG(ERROR) << \"Failed to decode numInserts, err=\" << err;\n      }\n    }\n  } // while\n  if (err == HPACK::DecodeError::BUFFER_UNDERFLOW) {\n    err = HPACK::DecodeError::NONE;\n    decoderIngress_.trimStart(consumed);\n  } else {\n    decoderIngress_.trimStart(dbuf.consumedBytes());\n  }\n  return err;\n}\n\nHPACK::DecodeError QPACKEncoder::decoderStreamEnd() {\n  if (!decoderIngress_.empty()) {\n    return HPACK::DecodeError::BUFFER_UNDERFLOW;\n  }\n  return HPACK::DecodeError::NONE;\n}\n\nHPACK::DecodeError QPACKEncoder::decodeHeaderAck(HPACKDecodeBuffer& dbuf,\n                                                 uint8_t prefixLength,\n                                                 bool all) {\n  uint64_t streamId = 0;\n  auto err = dbuf.decodeInteger(prefixLength, streamId);\n  if (err == HPACK::DecodeError::NONE) {\n    err = onHeaderAck(streamId, all);\n  } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {\n    LOG(ERROR) << \"Failed to decode streamId, err=\" << err;\n  }\n  return err;\n}\n\nHPACK::DecodeError QPACKEncoder::onInsertCountIncrement(uint32_t inserts) {\n  if (inserts == 0 || !table_.onInsertCountIncrement(inserts)) {\n    return HPACK::DecodeError::INVALID_ACK;\n  }\n  return HPACK::DecodeError::NONE;\n}\n\nHPACK::DecodeError QPACKEncoder::onHeaderAck(uint64_t streamId, bool all) {\n  auto it = outstanding_.find(streamId);\n  if (it == outstanding_.end()) {\n    if (!all) {\n      LOG(ERROR) << \"Received an ack with no outstanding header blocks stream=\"\n                 << streamId;\n      return HPACK::DecodeError::INVALID_ACK;\n    } else {\n      // all implies a reset, meaning it's not an error if there are no\n      // outstanding blocks\n      return HPACK::DecodeError::NONE;\n    }\n  }\n  DCHECK(!it->second.empty()) << \"Invariant violation: no blocks in stream \"\n                                 \"record\";\n  VLOG(5) << ((all) ? \"onCancelStream\" : \"onHeaderAck\")\n          << \" streamId=\" << streamId;\n  if (all) {\n    // Happens when a stream is reset (should be rare)\n    for (auto& block : it->second) {\n      if (block.vulnerable) {\n        numVulnerable_--;\n      }\n      removeFromMinOutstanding(block.minInUseIndex);\n    }\n    numOutstandingBlocks_ -= it->second.size();\n    it->second.clear();\n  } else {\n    auto block = std::move(it->second.front());\n    numOutstandingBlocks_--;\n    it->second.pop_front();\n    if (block.vulnerable) {\n      numVulnerable_--;\n    }\n    CHECK_NE(block.minInUseIndex, std::numeric_limits<uint32_t>::max());\n    removeFromMinOutstanding(block.minInUseIndex);\n    // Up through maxInUseIndex is implicitly acknowledged\n    VLOG(5) << \"Implicitly acknowledging requiredInsertCount=\"\n            << block.maxInUseIndex;\n    table_.setAcknowledgedInsertCount(block.maxInUseIndex);\n  }\n  if (it->second.empty()) {\n    outstanding_.erase(it);\n  }\n  VLOG(6) << \"New min use index=\" << minOutstandingMin_;\n  table_.setMinInUseIndex(minOutstandingMin_);\n  return HPACK::DecodeError::NONE;\n}\n\nvoid QPACKEncoder::removeFromMinOutstanding(uint32_t valToRemove) {\n  CHECK(!outstandingMins_.empty());\n  VLOG(10) << \"mins remove val=\" << valToRemove;\n  bool recomputeMin = (valToRemove == minOutstandingMin_);\n  uint32_t newMin = std::numeric_limits<uint32_t>::max();\n  size_t i = 0;\n  for (; i < outstandingMins_.size(); i++) {\n    auto value = outstandingMins_.at(i);\n    if (value == valToRemove) {\n      outstandingMins_.at(i) = outstandingMins_.back();\n      outstandingMins_.pop_back();\n      break;\n    }\n    newMin = std::min(newMin, value);\n  }\n  if (recomputeMin) {\n    for (; i < outstandingMins_.size(); i++) {\n      newMin = std::min(newMin, outstandingMins_.at(i));\n    }\n    minOutstandingMin_ = newMin;\n  }\n}\n\nvoid QPACKEncoder::setMaxNumOutstandingBlocks(uint32_t value) {\n  maxNumOutstandingBlocks_ = value;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKEncoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBuf.h>\n#include <list>\n#include <proxygen/lib/http/codec/compress/HPACKConstants.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoderBase.h>\n#include <proxygen/lib/http/codec/compress/QPACKContext.h>\n#include <unordered_map>\n#include <vector>\n\nnamespace proxygen {\n\nclass HPACKDecodeBuffer;\n\nclass QPACKEncoder\n    : public HPACKEncoderBase\n    , public QPACKContext {\n\n public:\n  static constexpr uint32_t kMaxHeaderTableSize = (1u << 16);\n  static constexpr uint32_t kDefaultMaxOutstandingListSize = (1u << 8);\n\n  explicit QPACKEncoder(bool huffman, uint32_t tableSize = HPACK::kTableSize);\n\n  /**\n   * Encode the given headers.\n   */\n\n  using Buf = std::unique_ptr<folly::IOBuf>;\n  struct EncodeResult {\n    EncodeResult(Buf c, Buf s) : control(std::move(c)), stream(std::move(s)) {\n    }\n    Buf control;\n    Buf stream;\n  };\n\n  // Returns a pair of buffers.  One for the control stream and one for the\n  // request stream\n  EncodeResult encode(\n      const std::vector<HPACKHeader>& headers,\n      uint32_t headroom,\n      uint64_t streamId,\n      uint32_t maxEncoderStreamBytes = std::numeric_limits<uint32_t>::max());\n\n  HPACK::DecodeError decodeDecoderStream(std::unique_ptr<folly::IOBuf> buf);\n\n  HPACK::DecodeError decoderStreamEnd();\n\n  HPACK::DecodeError onInsertCountIncrement(uint32_t inserts);\n\n  HPACK::DecodeError onHeaderAck(uint64_t streamId, bool all);\n\n  bool setHeaderTableSize(uint32_t tableSize, bool updateMax = true) {\n    if (updateMax) {\n      // The peer's max is used whenn encoding RequiredInsertCouunt\n      if (maxTableSize_ != 0 && maxTableSize_ != tableSize) {\n        LOG(ERROR) << \"Cannot change non-zero max header table size, \"\n                      \"maxTableSize_=\"\n                   << maxTableSize_ << \" tableSize=\" << tableSize;\n        return false;\n      }\n      maxTableSize_ = tableSize;\n    }\n    if (tableSize > kMaxHeaderTableSize) {\n      VLOG(2) << \"Limiting table size from \" << tableSize << \" to \"\n              << kMaxHeaderTableSize;\n      tableSize = kMaxHeaderTableSize;\n    }\n    HPACKEncoderBase::setHeaderTableSize(table_, tableSize);\n    return true;\n  }\n\n  uint32_t getMaxHeaderTableSize() const {\n    return maxTableSize_;\n  }\n\n  void setMaxVulnerable(uint32_t maxVulnerable) {\n    maxVulnerable_ = maxVulnerable;\n  }\n\n  // This API is only for tests, and doesn't work correctly if the table is\n  // already populated.\n  void setMinFreeForTesting(uint32_t minFree) {\n    table_.setMinFreeForTesting(minFree);\n  }\n\n  void setMaxNumOutstandingBlocks(uint32_t value);\n\n  uint32_t startEncode(folly::IOBufQueue& controlQueue,\n                       uint32_t headroom,\n                       uint32_t maxEncoderStreamBytes);\n\n  size_t encodeHeaderQ(HPACKHeaderName name,\n                       folly::StringPiece value,\n                       uint32_t baseIndex,\n                       uint32_t& requiredInsertCount);\n\n  std::unique_ptr<folly::IOBuf> completeEncode(uint64_t streamId,\n                                               uint32_t baseIndex,\n                                               uint32_t requiredInsertCount);\n\n private:\n  bool allowVulnerable() const {\n    return numVulnerable_ < maxVulnerable_;\n  }\n\n  bool shouldIndex(const HPACKHeaderName& name, folly::StringPiece value) const;\n\n  bool dynamicReferenceAllowed() const;\n\n  void encodeControl(const HPACKHeader& header);\n\n  std::pair<bool, uint32_t> maybeDuplicate(uint32_t relativeIndex);\n\n  std::tuple<bool, uint32_t, uint32_t> getNameIndexQ(\n      const HPACKHeaderName& headerName);\n\n  size_t encodeStreamLiteralQ(const HPACKHeaderName& name,\n                              folly::StringPiece value,\n                              bool isStaticName,\n                              uint32_t nameIndex,\n                              uint32_t absoluteNameIndex,\n                              uint32_t baseIndex,\n                              uint32_t& requiredInsertCount);\n\n  void encodeInsertQ(const HPACKHeaderName& name,\n                     folly::StringPiece value,\n                     bool isStaticName,\n                     uint32_t nameIndex);\n\n  size_t encodeLiteralQ(const HPACKHeaderName& name,\n                        folly::StringPiece value,\n                        bool isStaticName,\n                        bool postBase,\n                        uint32_t nameIndex,\n                        const HPACK::Instruction& idxInstr);\n\n  uint32_t encodeLiteralQHelper(HPACKEncodeBuffer& buffer,\n                                const HPACKHeaderName& name,\n                                folly::StringPiece value,\n                                bool isStaticName,\n                                uint32_t nameIndex,\n                                uint8_t staticFlag,\n                                const HPACK::Instruction& idxInstr,\n                                const HPACK::Instruction& litInstr);\n\n  void trackReference(uint32_t index, uint32_t& requiredInsertCount);\n\n  void encodeDuplicate(uint32_t index);\n\n  HPACK::DecodeError decodeHeaderAck(HPACKDecodeBuffer& dbuf,\n                                     uint8_t prefixLength,\n                                     bool all);\n\n  // Returns true if the most recently encoded value (duplicate, insert)\n  // fit in the encoder stream's flow control window.  The encoder will only\n  // make references to dynamic table entries that fit.  This prevents a nasty\n  // deadlock.\n  bool lastEntryAvailable() const {\n    return maxEncoderStreamBytes_ >= 0;\n  }\n\n  void removeFromMinOutstanding(uint32_t val);\n\n  HPACKEncodeBuffer controlBuffer_;\n  struct OutstandingBlock {\n    uint32_t minInUseIndex{std::numeric_limits<uint32_t>::max()};\n    uint32_t maxInUseIndex{0};\n    bool vulnerable{false};\n  };\n  // Map streamID -> list of table index references for each outstanding block;\n  std::unordered_map<uint64_t, std::list<OutstandingBlock>> outstanding_;\n  std::vector<uint32_t> outstandingMins_;\n  uint32_t minOutstandingMin_{std::numeric_limits<uint32_t>::max()};\n  OutstandingBlock curOutstanding_;\n  uint32_t maxDepends_{0};\n  uint32_t maxVulnerable_{HPACK::kDefaultBlocking};\n  uint32_t numVulnerable_{0};\n  uint32_t maxTableSize_{0};\n  int64_t maxEncoderStreamBytes_{0};\n  folly::IOBufQueue decoderIngress_{folly::IOBufQueue::cacheChainLength()};\n  uint32_t numOutstandingBlocks_{0};\n  uint32_t maxNumOutstandingBlocks_{kDefaultMaxOutstandingListSize};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKHeaderTable.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKHeaderTable.h>\n\n#include <glog/logging.h>\n\nnamespace {\n// For tables 0..384     minFree = 48\n// For tables 385..4096  minFree = tableSize/8\n// For tables 4096+      minFree = 512\n\n// Determines how much space in the table should be reserved for new inserts.\n// Min Free is set to tableSize / kMinFreeSlice\nconst uint32_t kMinFreeSlice = 8;\n\nconst uint32_t kMinMinFree = 48;\nconst uint32_t kMaxMinFree = 512;\n\nuint32_t getMinFree(uint32_t tableSize) {\n  return std::min(std::max(tableSize / kMinFreeSlice, kMinMinFree),\n                  kMaxMinFree);\n}\n\n} // namespace\n\nnamespace proxygen {\n\nQPACKHeaderTable::QPACKHeaderTable(uint32_t capacityVal, bool trackReferences)\n    : HeaderTable(capacityVal) {\n  if (trackReferences) {\n    minFree_ = getMinFree(capacityVal);\n    trackReferences_ = true;\n  } else {\n    minFree_ = 0;\n    disableNamesIndex();\n  }\n}\n\nbool QPACKHeaderTable::add(HPACKHeader header) {\n  if (insertCount_ == std::numeric_limits<uint32_t>::max()) {\n    LOG(ERROR) << \"Cowardly refusing to add more entries since insertCount_ \"\n                  \" would wrap\";\n    return false;\n  }\n\n  DVLOG(6) << \"Adding header=\" << header << \" absIndex=\" << insertCount_ + 1;\n  if (!HeaderTable::add(std::move(header))) {\n    return false;\n  }\n  DCHECK_EQ(internalToAbsolute(head_), insertCount_);\n  // Increase minUsable_ until the free space + drainedBytes is >= minFree.\n  // For HPACK, minFree is 0 and this is a no-op.\n  while (capacity_ - bytes_ + drainedBytes_ < minFree_ &&\n         minUsable_ <= insertCount_) {\n    auto bytes = table_[absoluteToInternal(minUsable_)].bytes();\n    VLOG(5) << \"Draining absolute index \" << minUsable_ << \" bytes=\" << bytes\n            << \" drainedBytes_= \" << (drainedBytes_ + bytes);\n    drainedBytes_ += bytes;\n    minUsable_++;\n  }\n  return true;\n}\n\nbool QPACKHeaderTable::setCapacity(uint32_t capacity) {\n  if (!HeaderTable::setCapacity(capacity)) {\n    return false;\n  }\n  if (trackReferences_) {\n    minFree_ = getMinFree(capacity);\n  } // else minFree is always 0\n  return true;\n}\n\nuint32_t QPACKHeaderTable::getIndex(const HPACKHeader& header,\n                                    bool allowVulnerable) const {\n  return getIndexImpl(header.name, header.value, false, allowVulnerable);\n}\n\nuint32_t QPACKHeaderTable::getIndex(const HPACKHeaderName& name,\n                                    folly::StringPiece value,\n                                    bool allowVulnerable) const {\n  return getIndexImpl(name, value, false, allowVulnerable);\n}\n\nuint32_t QPACKHeaderTable::getIndexImpl(const HPACKHeaderName& headerName,\n                                        folly::StringPiece value,\n                                        bool nameOnly,\n                                        bool allowVulnerable) const {\n  auto it = names_.find(headerName);\n  if (it == names_.end()) {\n    return 0;\n  }\n  bool encoderHasUnackedEntry = false;\n  // Searching backwards gives smallest index, but more likely vulnerable\n  // Searching forwards least likely vulnerable but could prevent eviction\n  for (auto indexIt = it->second.rbegin(); indexIt != it->second.rend();\n       ++indexIt) {\n    auto i = *indexIt;\n    if (nameOnly || table_[i].value == value) {\n      // allow vulnerable or not vulnerable\n      if (allowVulnerable || internalToAbsolute(i) <= ackedInsertCount_) {\n        // index *may* be draining, caller has to check\n        return toExternal(i);\n      } else {\n        encoderHasUnackedEntry = true;\n      }\n    }\n  }\n  if (encoderHasUnackedEntry) {\n    return UNACKED;\n  }\n  return 0;\n}\n\nuint32_t QPACKHeaderTable::nameIndex(const HPACKHeaderName& headerName,\n                                     bool allowVulnerable) const {\n  folly::fbstring value;\n  return getIndexImpl(headerName, value, true /* name only */, allowVulnerable);\n}\n\nconst HPACKHeader& QPACKHeaderTable::getHeader(uint32_t index,\n                                               uint32_t base) const {\n  CHECK(isValid(index, base));\n  return table_[toInternal(index, base)];\n}\n\nuint32_t QPACKHeaderTable::removeLast() {\n  auto idx = tail();\n  if (trackReferences_) {\n    CHECK_LT(internalToAbsolute(idx), minInUseIndex_)\n        << \"Removed in use header\";\n  }\n  auto removedBytes = HeaderTable::removeLast();\n  // Only non-zero when minUsable_ > insertCount_ - size_.\n  if (drainedBytes_ > 0) {\n    VLOG(5) << \"Removing draining entry=\" << idx << \" size=\" << removedBytes\n            << \" drainedBytes_=\" << drainedBytes_\n            << \" new drainedBytes_=\" << (int32_t(drainedBytes_) - removedBytes);\n    CHECK_GE(drainedBytes_, removedBytes);\n    drainedBytes_ -= removedBytes;\n  } else {\n    // Keep minUsable_ as a valid index when evicting an undrained header\n    if (size() > 0) {\n      minUsable_ = internalToAbsolute(tail());\n    } else {\n      minUsable_ = insertCount_ + 1;\n    }\n  }\n  return removedBytes;\n}\n\nvoid QPACKHeaderTable::increaseTableLengthTo(uint32_t newLength) {\n  HeaderTable::increaseTableLengthTo(newLength);\n  if (size_ > 0) {\n    DCHECK_EQ(internalToAbsolute(head_), insertCount_);\n    DCHECK_EQ(internalToAbsolute(tail()), insertCount_ - size_ + 1);\n  }\n}\n\nuint32_t QPACKHeaderTable::evict(uint32_t needed, uint32_t desiredCapacity) {\n  if (bytes_ + needed < desiredCapacity ||\n      !canEvict(bytes_ + needed - desiredCapacity)) {\n    return 0;\n  }\n  return HeaderTable::evict(needed, desiredCapacity);\n}\n\nbool QPACKHeaderTable::canEvict(uint32_t needed) {\n  if (size_ == 0 || !trackReferences_) {\n    return needed <= capacity_;\n  }\n  uint32_t freeable = 0;\n  uint32_t i = tail();\n  uint32_t nChecked = 0;\n  while (nChecked++ < size() && freeable < needed &&\n         internalToAbsolute(i) < minInUseIndex_ && // don't evict referenced or\n                                                   // unacked headers\n         internalToAbsolute(i) <= ackedInsertCount_) {\n    freeable += table_[i].bytes();\n    i = next(i);\n  }\n  if (freeable < needed) {\n    DVLOG(5) << \"header=\" << table_[i].name << \":\" << table_[i].value\n             << \" blocked eviction, minInUseIndex_=\" << minInUseIndex_;\n    return false;\n  }\n  return true;\n}\n\nbool QPACKHeaderTable::isValid(uint32_t index, uint32_t base) const {\n  int64_t testIndex = index;\n  if (base > 0) {\n    auto baseOffset = ((int64_t)base - (int64_t)insertCount_);\n    // recompute relative to current insertCount_.  testIndex may go negative\n    // if this is a reference to an entry that hasn't arrived yet\n    testIndex -= baseOffset;\n  }\n  return HeaderTable::isValid(testIndex);\n}\n\n// Checks if relativeIndex is draining.  If not, returns the corresponding\n// absolute index.  Otherwise, attempt to duplicate.  If duplication is\n// successful, and vulnerable references are allowed, return absolute index of\n// the duplicate.  If duplication is unsuccessful, or vulnerable references are\n// not allowed, return 0.\nstd::pair<bool, uint32_t> QPACKHeaderTable::maybeDuplicate(\n    uint32_t relativeIndex, bool allowVulnerable) {\n  if (relativeIndex == UNACKED) {\n    return {false, 0};\n  }\n  DCHECK(isValid(relativeIndex));\n  uint32_t absIndex = relativeToAbsolute(relativeIndex);\n  DCHECK(!isVulnerable(absIndex) || allowVulnerable);\n  if (absIndex < minUsable_) {\n    // draining\n    const HPACKHeader& header = getHeader(relativeIndex);\n    if (canIndex(header.name, header.value)) {\n      CHECK(add(header.copy()));\n      if (allowVulnerable) {\n        return {true, insertCount_};\n      } else {\n        return {true, 0};\n      }\n    } else {\n      return {false, 0};\n    }\n  }\n  return {false, absIndex};\n}\n\n// Converts an array index in [0..table_.size() - 1] to an absolute\n// external index\nuint32_t QPACKHeaderTable::internalToAbsolute(uint32_t internalIndex) const {\n  return relativeToAbsolute(toExternal(internalIndex));\n}\n\n// Converts an absolute index to an array index in [0..table_.size() - 1]\nuint32_t QPACKHeaderTable::absoluteToInternal(uint32_t absoluteIndex) const {\n  return toInternal(absoluteToRelative(absoluteIndex), 0);\n}\n\nuint32_t QPACKHeaderTable::toInternal(uint32_t externalIndex,\n                                      uint32_t base) const {\n  if (base > 0) {\n    uint32_t absIndex = base - externalIndex + 1;\n    externalIndex = absoluteToRelative(absIndex);\n  }\n  return HeaderTable::toInternal(externalIndex);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKHeaderTable.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <limits>\n\n#include <glog/logging.h>\n\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n\nnamespace proxygen {\n\n/**\n * Data structure for maintaining indexed headers, based on a fixed-length ring\n * with FIFO semantics. Externally it acts as an array.\n */\n\nclass QPACKHeaderTable : public HeaderTable {\n public:\n  static constexpr uint32_t UNACKED = std::numeric_limits<uint32_t>::max();\n\n  QPACKHeaderTable(uint32_t capacityVal, bool trackReferences);\n\n  ~QPACKHeaderTable() override = default;\n  QPACKHeaderTable(const QPACKHeaderTable&) = delete;\n  QPACKHeaderTable& operator=(const QPACKHeaderTable&) = delete;\n\n  /**\n   * Returns true if the absolute index has not been ack'ed yet.\n   */\n  [[nodiscard]] bool isVulnerable(uint32_t absIndex) const {\n    return (absIndex > ackedInsertCount_);\n  }\n\n  /**\n   * Returns true if the header can be added to the table.  May be linear\n   * in the number of entries\n   */\n  bool canIndex(const HPACKHeaderName& name, folly::StringPiece value) {\n    auto headerBytes = HPACKHeader::bytes(name.size(), value.size());\n    auto totalBytes = bytes_ + headerBytes;\n    // Don't index headers that would immediately be drained\n    return ((headerBytes <= (capacity_ - minFree_)) &&\n            (totalBytes <= capacity_ || canEvict(totalBytes - capacity_)));\n  }\n\n  /**\n   * Returns true if the index should not be used so table space can be freed\n   */\n  bool isDraining(uint32_t relativeIndex) {\n    return relativeToAbsolute(relativeIndex) < minUsable_;\n  }\n\n  /**\n   * Returns the absolute index for a reference to the header at relativeIndex,\n   * along with a boolean indicating if the returned index is a duplicate.  It\n   * may return 0 if the entry at relativeIndex was draining and could not be\n   * duplicated, or vulnerable references are not allowed.\n   */\n  std::pair<bool, uint32_t> maybeDuplicate(uint32_t relativeIndex,\n                                           bool allowVulnerable);\n\n  /**\n   * Add the header entry at the beginning of the table (index=1)\n   *\n   * @return true if it was able to add the entry\n   */\n  bool add(HPACKHeader header) override;\n\n  bool setCapacity(uint32_t capacity) override;\n\n  // This API is only for tests, and doesn't work correctly if the table is\n  // already populated.\n  void setMinFreeForTesting(uint32_t minFree) {\n    minFree_ = minFree;\n  }\n\n  /**\n   * Get the index of the given header, if found.  The index is relative to\n   * head/insertCount.  If allowVulnerable is true, the index returned may not\n   * have been acknowledged by the decoder.\n   *\n   * @return 0 in case the header is not found\n   */\n  [[nodiscard]] uint32_t getIndex(const HPACKHeader& header,\n                                  bool allowVulnerable = true) const;\n\n  [[nodiscard]] uint32_t getIndex(const HPACKHeaderName& name,\n                                  folly::StringPiece value,\n                                  bool allowVulnerable = true) const;\n\n  /**\n   * Get the table entry at the given external index.  If base is 0,\n   * index is relative to head/insertCount.  If base is non-zero, index is\n   * relative to base.\n   *\n   * @return the header entry\n   */\n  [[nodiscard]] const HPACKHeader& getHeader(uint32_t index,\n                                             uint32_t base = 0) const;\n\n  /**\n   * Checks if an external index is valid.  If base is 0,\n   * index is relative to head/insertCount.  If base is non-zero, index is\n   * relative to base.\n   */\n  [[nodiscard]] bool isValid(uint32_t index, uint32_t base = 0) const;\n\n  /**\n   * Get any index of a header that has the given name. From all the\n   * headers with the given name we pick the last one added to the header\n   * table, but the way we pick the header can be arbitrary.\n   *\n   * See getIndex for a description of base/allowVulnerable\n   */\n  [[nodiscard]] uint32_t nameIndex(const HPACKHeaderName& headerName,\n                                   bool allowVulnerable = true) const;\n\n  bool onInsertCountIncrement(uint32_t numInserts) {\n    // compare this way to avoid overflow\n    if (numInserts > insertCount_ ||\n        ackedInsertCount_ > insertCount_ - numInserts) {\n      LOG(ERROR)\n          << \"Decoder ack'd too much: ackedInsertCount_=\" << ackedInsertCount_\n          << \" insertCount_=\" << insertCount_ << \" numInserts=\" << numInserts;\n      return false;\n    }\n    ackedInsertCount_ += numInserts;\n    CHECK_LE(ackedInsertCount_, insertCount_);\n    return true;\n  }\n\n  void setAcknowledgedInsertCount(uint32_t ackInsertCount) {\n    if (ackInsertCount < ackedInsertCount_) {\n      return;\n    }\n    CHECK_LE(ackInsertCount, insertCount_);\n    ackedInsertCount_ = ackInsertCount;\n  }\n\n  /**\n   * Convert a relative index to an absolute index\n   */\n  [[nodiscard]] uint32_t relativeToAbsolute(uint32_t relativeIndex) const {\n    DCHECK(isValid(relativeIndex, 0));\n    return insertCount_ - relativeIndex + 1;\n  }\n\n  /**\n   * Convert an absolute index to a relative index\n   */\n  [[nodiscard]] uint32_t absoluteToRelative(uint32_t absIndex) const {\n    CHECK_LE(absIndex, insertCount_);\n    return insertCount_ - absIndex + 1;\n  }\n\n  void setMinInUseIndex(\n      uint32_t minInUseIndex = std::numeric_limits<uint32_t>::max()) {\n    minInUseIndex_ = minInUseIndex;\n  }\n\n private:\n  /*\n   * Shared implementation for getIndex and nameIndex\n   */\n  [[nodiscard]] uint32_t getIndexImpl(const HPACKHeaderName& header,\n                                      folly::StringPiece value,\n                                      bool nameOnly,\n                                      bool allowVulnerable = true) const;\n\n  /*\n   * Increase table length to newLength\n   */\n  void increaseTableLengthTo(uint32_t newLength) override;\n\n  /**\n   * Removes one header entry from the beginning of the header table.\n   */\n  uint32_t removeLast() override;\n\n  /**\n   * Return true if the table can evict needed bytes\n   */\n  bool canEvict(uint32_t needed);\n\n  /**\n   * Evict entries to make space for the needed amount of bytes.\n   */\n  uint32_t evict(uint32_t needed, uint32_t desiredCapacity) override;\n\n  /**\n   * Translate external index to internal one.\n   */\n  [[nodiscard]] uint32_t toInternal(uint32_t externalIndex,\n                                    uint32_t base) const;\n\n  [[nodiscard]] uint32_t internalToAbsolute(uint32_t internalIndex) const;\n  [[nodiscard]] uint32_t absoluteToInternal(uint32_t absoluteIndex) const;\n\n  uint32_t drainedBytes_{0};\n  uint32_t minUsable_{1};\n  uint32_t ackedInsertCount_{0};\n  uint32_t minFree_{0};\n  bool trackReferences_{false};\n  uint32_t minInUseIndex_{std::numeric_limits<uint32_t>::max()};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKStaticHeaderTable.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/QPACKStaticHeaderTable.h>\n\n#include <folly/Indestructible.h>\n\n#include <glog/logging.h>\n\nnamespace {\n\n// Array of static header table entires pair\n// Note: if updating this table (should never have to but whatever), update\n// isHeaderNameInTableWithNonEmptyValue as well\n//\n// From https://github.com/quicwg/base-drafts/wiki/QPACK-Static-Table\nconst char* s_tableEntries[][2] = {\n    {\":authority\", \"\"},\n    {\":path\", \"/\"},\n    {\"age\", \"0\"},\n    {\"content-disposition\", \"\"},\n    {\"content-length\", \"0\"},\n    {\"cookie\", \"\"},\n    {\"date\", \"\"},\n    {\"etag\", \"\"},\n    {\"if-modified-since\", \"\"},\n    {\"if-none-match\", \"\"},\n    {\"last-modified\", \"\"},\n    {\"link\", \"\"},\n    {\"location\", \"\"},\n    {\"referer\", \"\"},\n    {\"set-cookie\", \"\"},\n    {\":method\", \"CONNECT\"},\n    {\":method\", \"DELETE\"},\n    {\":method\", \"GET\"},\n    {\":method\", \"HEAD\"},\n    {\":method\", \"OPTIONS\"},\n    {\":method\", \"POST\"},\n    {\":method\", \"PUT\"},\n    {\":scheme\", \"http\"},\n    {\":scheme\", \"https\"},\n    {\":status\", \"103\"},\n    {\":status\", \"200\"},\n    {\":status\", \"304\"},\n    {\":status\", \"404\"},\n    {\":status\", \"503\"},\n    {\"accept\", \"*/*\"},\n    {\"accept\", \"application/dns-message\"},\n    {\"accept-encoding\", \"gzip, deflate, br\"},\n    {\"accept-ranges\", \"bytes\"},\n    {\"access-control-allow-headers\", \"cache-control\"},\n    {\"access-control-allow-headers\", \"content-type\"},\n    {\"access-control-allow-origin\", \"*\"},\n    {\"cache-control\", \"max-age=0\"},\n    {\"cache-control\", \"max-age=2592000\"},\n    {\"cache-control\", \"max-age=604800\"},\n    {\"cache-control\", \"no-cache\"},\n    {\"cache-control\", \"no-store\"},\n    {\"cache-control\", \"public, max-age=31536000\"},\n    {\"content-encoding\", \"br\"},\n    {\"content-encoding\", \"gzip\"},\n    {\"content-type\", \"application/dns-message\"},\n    {\"content-type\", \"application/javascript\"},\n    {\"content-type\", \"application/json\"},\n    {\"content-type\", \"application/x-www-form-urlencoded\"},\n    {\"content-type\", \"image/gif\"},\n    {\"content-type\", \"image/jpeg\"},\n    {\"content-type\", \"image/png\"},\n    {\"content-type\", \"text/css\"},\n    {\"content-type\", \"text/html; charset=utf-8\"},\n    {\"content-type\", \"text/plain\"},\n    {\"content-type\", \"text/plain;charset=utf-8\"},\n    {\"range\", \"bytes=0-\"},\n    {\"strict-transport-security\", \"max-age=31536000\"},\n    {\"strict-transport-security\", \"max-age=31536000; includesubdomains\"},\n    {\"strict-transport-security\",\n     \"max-age=31536000; includesubdomains; preload\"},\n    {\"vary\", \"accept-encoding\"},\n    {\"vary\", \"origin\"},\n    {\"x-content-type-options\", \"nosniff\"},\n    {\"x-xss-protection\", \"1; mode=block\"},\n    {\":status\", \"100\"},\n    {\":status\", \"204\"},\n    {\":status\", \"206\"},\n    {\":status\", \"302\"},\n    {\":status\", \"400\"},\n    {\":status\", \"403\"},\n    {\":status\", \"421\"},\n    {\":status\", \"425\"},\n    {\":status\", \"500\"},\n    {\"accept-language\", \"\"},\n    {\"access-control-allow-credentials\", \"FALSE\"},\n    {\"access-control-allow-credentials\", \"TRUE\"},\n    {\"access-control-allow-headers\", \"*\"},\n    {\"access-control-allow-methods\", \"get\"},\n    {\"access-control-allow-methods\", \"get, post, options\"},\n    {\"access-control-allow-methods\", \"options\"},\n    {\"access-control-expose-headers\", \"content-length\"},\n    {\"access-control-request-headers\", \"content-type\"},\n    {\"access-control-request-method\", \"get\"},\n    {\"access-control-request-method\", \"post\"},\n    {\"alt-svc\", \"clear\"},\n    {\"authorization\", \"\"},\n    {\"content-security-policy\",\n     \"script-src 'none'; object-src 'none'; base-uri 'none'\"},\n    {\"early-data\", \"1\"},\n    {\"expect-ct\", \"\"},\n    {\"forwarded\", \"\"},\n    {\"if-range\", \"\"},\n    {\"origin\", \"\"},\n    {\"purpose\", \"prefetch\"},\n    {\"server\", \"\"},\n    {\"timing-allow-origin\", \"*\"},\n    {\"upgrade-insecure-requests\", \"1\"},\n    {\"user-agent\", \"\"},\n    {\"x-forwarded-for\", \"\"},\n    {\"x-frame-options\", \"deny\"},\n    {\"x-frame-options\", \"sameorigin\"}};\n\nconst int kEntriesSize = sizeof(s_tableEntries) / (2 * sizeof(const char*));\n} // namespace\n\nnamespace proxygen {\n\n/**\n * Not currently used for QPACK, because the table contains 28 common headers\n * with non-empty value.  To get the list run:\n *\n * cat HTTPCommonHeaders.txt  | tr '[:upper:]' '[:lower:]' | \\\n * xargs -n 1 --replace=STR \\\n *    grep -w STR codec/compress/QPACKStaticHeaderTable.cpp | \\\n *  grep -v \"\\\"\\\"\" | cut -d\\\" -f 2 | sort | uniq | \\\n *  tr '[:lower:]' '[:upper:]' | \\\n * sed -e's/^/HTTP_HEADER_/g' -e's/-/_/g' -e's/:/COLON_/g'\n */\nbool QPACKStaticHeaderTable::isHeaderCodeInTableWithNonEmptyValue(\n    HTTPHeaderCode /*headerCode*/) {\n  LOG(FATAL) << __func__ << \" not supported for QPACK\";\n}\n\nconst StaticHeaderTable& QPACKStaticHeaderTable::get() {\n  static const folly::Indestructible<StaticHeaderTable> table(s_tableEntries,\n                                                              kEntriesSize);\n  return *table;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/QPACKStaticHeaderTable.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <proxygen/lib/http/codec/compress/StaticHeaderTable.h>\n\nnamespace proxygen {\n\nclass QPACKStaticHeaderTable {\n\n public:\n  static const StaticHeaderTable& get();\n\n  // Not currently used\n  static bool isHeaderCodeInTableWithNonEmptyValue(HTTPHeaderCode headerCode);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/StaticHeaderTable.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/StaticHeaderTable.h>\n\n#include <folly/Indestructible.h>\n\n#include <glog/logging.h>\n#include <list>\n\nusing std::list;\n\nnamespace {\n\n// Array of static header table entires pair\n// Note: if updating this table (should never have to but whatever), update\n// isHeaderNameInTableWithNonEmptyValue as well\nconst char* s_tableEntries[][2] = {{\":authority\", \"\"},\n                                   {\":method\", \"GET\"},\n                                   {\":method\", \"POST\"},\n                                   {\":path\", \"/\"},\n                                   {\":path\", \"/index.html\"},\n                                   {\":scheme\", \"http\"},\n                                   {\":scheme\", \"https\"},\n                                   {\":status\", \"200\"},\n                                   {\":status\", \"204\"},\n                                   {\":status\", \"206\"},\n                                   {\":status\", \"304\"},\n                                   {\":status\", \"400\"},\n                                   {\":status\", \"404\"},\n                                   {\":status\", \"500\"},\n                                   {\"accept-charset\", \"\"},\n                                   {\"accept-encoding\", \"gzip, deflate\"},\n                                   {\"accept-language\", \"\"},\n                                   {\"accept-ranges\", \"\"},\n                                   {\"accept\", \"\"},\n                                   {\"access-control-allow-origin\", \"\"},\n                                   {\"age\", \"\"},\n                                   {\"allow\", \"\"},\n                                   {\"authorization\", \"\"},\n                                   {\"cache-control\", \"\"},\n                                   {\"content-disposition\", \"\"},\n                                   {\"content-encoding\", \"\"},\n                                   {\"content-language\", \"\"},\n                                   {\"content-length\", \"\"},\n                                   {\"content-location\", \"\"},\n                                   {\"content-range\", \"\"},\n                                   {\"content-type\", \"\"},\n                                   {\"cookie\", \"\"},\n                                   {\"date\", \"\"},\n                                   {\"etag\", \"\"},\n                                   {\"expect\", \"\"},\n                                   {\"expires\", \"\"},\n                                   {\"from\", \"\"},\n                                   {\"host\", \"\"},\n                                   {\"if-match\", \"\"},\n                                   {\"if-modified-since\", \"\"},\n                                   {\"if-none-match\", \"\"},\n                                   {\"if-range\", \"\"},\n                                   {\"if-unmodified-since\", \"\"},\n                                   {\"last-modified\", \"\"},\n                                   {\"link\", \"\"},\n                                   {\"location\", \"\"},\n                                   {\"max-forwards\", \"\"},\n                                   {\"proxy-authenticate\", \"\"},\n                                   {\"proxy-authorization\", \"\"},\n                                   {\"range\", \"\"},\n                                   {\"referer\", \"\"},\n                                   {\"refresh\", \"\"},\n                                   {\"retry-after\", \"\"},\n                                   {\"server\", \"\"},\n                                   {\"set-cookie\", \"\"},\n                                   {\"strict-transport-security\", \"\"},\n                                   {\"transfer-encoding\", \"\"},\n                                   {\"user-agent\", \"\"},\n                                   {\"vary\", \"\"},\n                                   {\"via\", \"\"},\n                                   {\"www-authenticate\", \"\"}};\n\nconst int kEntriesSize = sizeof(s_tableEntries) / (2 * sizeof(const char*));\n} // namespace\n\nnamespace proxygen {\n\nbool StaticHeaderTable::isHeaderCodeInTableWithNonEmptyValue(\n    HTTPHeaderCode headerCode) {\n  switch (headerCode) {\n    case HTTP_HEADER_COLON_METHOD:\n    case HTTP_HEADER_COLON_PATH:\n    case HTTP_HEADER_COLON_SCHEME:\n    case HTTP_HEADER_COLON_STATUS:\n    case HTTP_HEADER_ACCEPT_ENCODING:\n      return true;\n\n    default:\n      return false;\n  }\n}\n\nStaticHeaderTable::StaticHeaderTable(const char* entries[][2], int size)\n    : HeaderTable(0) {\n  // calculate the size\n  list<HPACKHeader> hlist;\n  uint32_t byteCount = 0;\n  for (int i = 0; i < size; ++i) {\n    hlist.emplace_back(entries[i][0], entries[i][1]);\n    byteCount += hlist.back().bytes();\n  }\n  // initialize with a capacity that will exactly fit the static headers\n  init(byteCount);\n  hlist.reverse();\n  for (auto& header : hlist) {\n    CHECK(add(std::move(header)));\n  }\n}\n\nconst StaticHeaderTable& StaticHeaderTable::get() {\n  static const folly::Indestructible<StaticHeaderTable> table(s_tableEntries,\n                                                              kEntriesSize);\n  return *table;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/StaticHeaderTable.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n\nnamespace proxygen {\n\nclass StaticHeaderTable : public HeaderTable {\n\n public:\n  explicit StaticHeaderTable(const char* entries[][2], int size);\n\n  static const StaticHeaderTable& get();\n\n  static bool isHeaderCodeInTableWithNonEmptyValue(HTTPHeaderCode headerCode);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/interop/QPACKInterop.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/File.h>\n#include <folly/FileUtil.h>\n#include <folly/init/Init.h>\n#include <folly/io/Cursor.h>\n#include <folly/portability/GFlags.h>\n#include <fstream>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionUtils.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/SimStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/HTTPArchive.h>\n\nusing namespace proxygen;\nusing namespace proxygen::compress;\nusing namespace folly;\nusing namespace folly::io;\n\nDEFINE_string(output, \"compress.out\", \"Output file for encoding\");\nDEFINE_string(input, \"compress.in\", \"Input file for decoding\");\nDEFINE_string(har, \"\", \"HAR file to compress or compare\");\nDEFINE_string(mode, \"encode\", \"<encode|decode>\");\nDEFINE_bool(ack, true, \"Encoder assumes immediate ack of all frames\");\nDEFINE_int32(table_size, 4096, \"Dynamic table size\");\nDEFINE_int32(max_blocking, 100, \"Max blocking streams\");\nDEFINE_bool(public, false, \"Public HAR file\");\n\nnamespace {\n\nvoid writeFrame(folly::io::QueueAppender& appender,\n                uint64_t streamId,\n                std::unique_ptr<folly::IOBuf> buf) {\n  appender.writeBE<uint64_t>(streamId);\n  appender.writeBE<uint32_t>(\n      static_cast<uint32_t>(buf->computeChainDataLength()));\n  appender.insert(std::move(buf));\n}\n\nvoid encodeBlocks(QPACKCodec& decoder,\n                  std::vector<std::vector<compress::Header>>& blocks) {\n  uint64_t streamId = 1;\n  QPACKCodec encoder;\n  encoder.setMaxVulnerable(FLAGS_max_blocking);\n  encoder.setEncoderHeaderTableSize(FLAGS_table_size);\n  folly::File outputF(FLAGS_output, O_CREAT | O_RDWR | O_TRUNC);\n  IOBufQueue outbuf;\n  QueueAppender appender(&outbuf, 1000);\n  uint64_t bytesIn = 0;\n  uint64_t bytesOut = 0;\n  for (auto& block : blocks) {\n    auto result = encoder.encode(block, streamId);\n    // always write stream before control to test decoder blocking\n    SimStreamingCallback cb(streamId, nullptr);\n    if (result.stream) {\n      decoder.decodeStreaming(streamId,\n                              result.stream->clone(),\n                              result.stream->computeChainDataLength(),\n                              &cb);\n      writeFrame(appender, streamId, std::move(result.stream));\n    }\n    if (result.control) {\n      decoder.decodeEncoderStream(result.control->clone());\n      writeFrame(appender, 0, std::move(result.control));\n      if (FLAGS_ack) {\n        // There can be ICI when the decoder is non-blocking\n        auto res = decoder.encodeInsertCountInc();\n        if (res) {\n          encoder.decodeDecoderStream(std::move(res));\n        }\n      }\n    }\n    if (FLAGS_ack) {\n      if (cb.acknowledge) {\n        encoder.decodeDecoderStream(decoder.encodeHeaderAck(streamId));\n      } else {\n        auto res = decoder.encodeInsertCountInc();\n        if (res) {\n          encoder.decodeDecoderStream(std::move(res));\n        }\n      }\n    }\n    bytesIn += encoder.getEncodedSize().uncompressed;\n    auto out = outbuf.move();\n    auto iov = out->getIov();\n    bytesOut += writevFull(outputF.fd(), iov.data(), iov.size());\n    streamId++;\n  }\n  LOG(INFO) << \"Encoded \" << (streamId - 1) << \" streams.  Bytes in=\" << bytesIn\n            << \" Bytes out=\" << bytesOut\n            << \" Ratio=\" << int32_t(100 * (1 - (bytesOut / double(bytesIn))));\n}\n\nvoid encodeHar(QPACKCodec& decoder, const proxygen::HTTPArchive& har) {\n  std::vector<std::vector<compress::Header>> blocks;\n  std::vector<std::vector<std::string>> cookies{har.requests.size()};\n  uint32_t i = 0;\n  for (auto& req : har.requests) {\n    blocks.emplace_back(prepareMessageForCompression(req, cookies[i++]));\n  }\n  encodeBlocks(decoder, blocks);\n}\n\nclass Reader {\n  std::string filename;\n\n public:\n  explicit Reader(const std::string& fname) : filename(fname) {\n  }\n  virtual ~Reader() = default;\n\n  virtual ssize_t read() {\n    ssize_t rc = -1;\n    folly::IOBufQueue inbuf{folly::IOBufQueue::cacheChainLength()};\n    folly::File inputF(filename, O_RDONLY);\n    do {\n      auto pre = inbuf.preallocate(4096, 4096);\n      rc = readNoInt(inputF.fd(), pre.first, pre.second);\n      if (rc < 0) {\n        LOG(ERROR) << \"Read failed on \" << FLAGS_input;\n        return 1;\n      }\n      inbuf.postallocate(rc);\n      onIngress(inbuf);\n    } while (rc != 0);\n    if (!inbuf.empty()) {\n      LOG(ERROR) << \"Premature end of file\";\n      return 1;\n    }\n\n    return rc;\n  }\n\n  virtual void onIngress(folly::IOBufQueue& inbuf) = 0;\n};\n\nclass CompressedReader : public Reader {\n  enum { HEADER, DATA } state{HEADER};\n  uint64_t streamId{0};\n  uint32_t length{0};\n  std::function<void(uint64_t, uint32_t, std::unique_ptr<folly::IOBuf>)>\n      callback;\n\n public:\n  explicit CompressedReader(\n      std::function<void(uint64_t, uint32_t, std::unique_ptr<folly::IOBuf>)> cb)\n      : Reader(FLAGS_input), callback(cb) {\n  }\n\n  void onIngress(folly::IOBufQueue& inbuf) override {\n    while (true) {\n      if (state == HEADER) {\n        if (inbuf.chainLength() < (sizeof(uint64_t) + sizeof(uint32_t))) {\n          return;\n        }\n        Cursor c(inbuf.front());\n        streamId = c.readBE<uint64_t>();\n        length = c.readBE<uint32_t>();\n        inbuf.trimStart(sizeof(uint64_t) + sizeof(uint32_t));\n        state = DATA;\n      }\n      if (state == DATA) {\n        if (inbuf.chainLength() < length) {\n          return;\n        }\n        auto buf = inbuf.split(length);\n        callback(streamId, length, std::move(buf));\n        state = HEADER;\n      }\n    }\n  }\n};\n\nint decodeAndVerify(QPACKCodec& decoder, const proxygen::HTTPArchive& har) {\n  std::map<uint64_t, SimStreamingCallback> streams;\n  CompressedReader creader([&](uint64_t streamId,\n                               uint32_t length,\n                               std::unique_ptr<folly::IOBuf> buf) {\n    if (streamId == 0) {\n      CHECK_EQ(decoder.decodeEncoderStream(std::move(buf)),\n               HPACK::DecodeError::NONE);\n    } else {\n      auto res = streams.emplace(\n          std::piecewise_construct,\n          std::forward_as_tuple(streamId),\n          std::forward_as_tuple(streamId, nullptr, FLAGS_public));\n      decoder.decodeStreaming(\n          streamId, std::move(buf), length, &res.first->second);\n    }\n  });\n  if (creader.read()) {\n    return 1;\n  }\n  size_t i = 0;\n  for (const auto& req : streams) {\n    if (req.second.error != HPACK::DecodeError::NONE) {\n      LOG(ERROR) << \"request=\" << req.first\n                 << \" failed to decode error=\" << req.second.error;\n      return 1;\n    }\n    if (!(req.second.msg == har.requests[i])) {\n      LOG(ERROR) << \"requests are not equal, got=\" << req.second.msg\n                 << \" expected=\" << har.requests[i];\n    }\n    i++;\n  }\n  LOG(INFO) << \"Verified \" << i << \" streams.\";\n  return 0;\n}\n\nclass QIFCallback : public HPACK::StreamingCallback {\n public:\n  QIFCallback(uint64_t id_, std::ofstream& of_) : id(id_), of(of_) {\n  }\n\n  void onHeader(const HPACKHeaderName& name,\n                const folly::fbstring& value) override {\n    if (first) {\n      of << \"# stream \" << id << std::endl;\n      first = false;\n    }\n    of << name.get() << \"\\t\" << value << std::endl;\n  }\n  void onHeadersComplete(HTTPHeaderSize /*decodedSize*/,\n                         bool /*acknowledge*/) override {\n    of << std::endl;\n    complete = true;\n  }\n  void onDecodeError(HPACK::DecodeError decodeError) override {\n    LOG(FATAL) << \"Decode error with stream=\" << id << \" err=\" << decodeError;\n  }\n\n  uint64_t id{0};\n  std::ofstream& of;\n  bool first{true};\n  bool complete{false};\n};\n\nint decodeToQIF(QPACKCodec& decoder) {\n  std::ofstream of(FLAGS_output, std::ofstream::trunc);\n  std::map<uint64_t, QIFCallback> streams;\n  uint64_t encoderStreamBytes = 0;\n  CompressedReader creader([&](uint64_t streamId,\n                               uint32_t length,\n                               std::unique_ptr<folly::IOBuf> buf) {\n    if (streamId == 0) {\n      CHECK_EQ(decoder.decodeEncoderStream(std::move(buf)),\n               HPACK::DecodeError::NONE);\n      encoderStreamBytes += length;\n    } else {\n      auto res = streams.emplace(std::piecewise_construct,\n                                 std::forward_as_tuple(streamId),\n                                 std::forward_as_tuple(streamId, of));\n      decoder.decodeStreaming(\n          streamId, std::move(buf), length, &res.first->second);\n    }\n  });\n  if (creader.read()) {\n    return 1;\n  }\n\n  for (const auto& stream : streams) {\n    CHECK(stream.second.complete)\n        << \"Stream \" << stream.first << \" didn't complete\";\n  }\n  LOG(INFO) << \"encoderStreamBytes=\" << encoderStreamBytes;\n  return 0;\n}\n\nint interopHAR(QPACKCodec& decoder) {\n  std::unique_ptr<HTTPArchive> har =\n      (FLAGS_public) ? HTTPArchive::fromPublicFile(FLAGS_har)\n                     : HTTPArchive::fromFile(FLAGS_har);\n  if (!har) {\n    LOG(ERROR) << \"Failed to read har file='\" << FLAGS_har << \"'\";\n    return 1;\n  }\n  if (FLAGS_mode == \"encode\") {\n    encodeHar(decoder, *har);\n  } else if (FLAGS_mode == \"decode\") {\n    return decodeAndVerify(decoder, *har);\n  } else {\n    LOG(ERROR) << \"Usage\" << std::endl;\n    return 1;\n  }\n  return 0;\n}\n\nstruct QIFReader : public Reader {\n\n  std::vector<std::string> strings;\n  std::vector<std::vector<Header>> blocks{1};\n  enum { LINESTART, COMMENT, NAME, VALUE, EOL } state_{LINESTART};\n  bool seenR{false};\n\n  QIFReader() : Reader(FLAGS_input) {\n    strings.reserve(32768);\n  }\n\n  ssize_t read() override {\n    ssize_t rc = Reader::read();\n    if (rc != 0) {\n      return rc;\n    }\n    CHECK(blocks.back().empty());\n    blocks.pop_back();\n    return 0;\n  }\n\n  static bool iseol(uint8_t ch) {\n    return ch == '\\r' || ch == '\\n';\n  }\n\n  void onIngress(folly::IOBufQueue& input) override {\n    Cursor c(input.front());\n    while (!c.isAtEnd()) {\n      switch (state_) {\n        case LINESTART: {\n          seenR = false;\n          auto p = c.peek();\n          switch (p[0]) {\n            case '#':\n              state_ = COMMENT;\n              break;\n            case '\\r':\n            case '\\n':\n              if (!blocks.back().empty()) {\n                blocks.emplace_back();\n              }\n              state_ = EOL;\n              break;\n            default:\n              state_ = NAME;\n              strings.emplace_back();\n          }\n          break;\n        }\n        case COMMENT:\n          c.skipWhile([](uint8_t ch) { return !iseol(ch); });\n          if (!c.isAtEnd()) {\n            state_ = EOL;\n          }\n          break;\n        case EOL: {\n          auto p = c.peek();\n          if (p[0] == '\\n') {\n            c.skip(1);\n            state_ = LINESTART;\n          } else if (seenR) { // \\r followed by anything but \\n -> mac newline\n            state_ = LINESTART;\n          } else if (p[0] == '\\r') {\n            c.skip(1);\n            seenR = true;\n          }\n          break;\n        }\n        case NAME:\n          strings.back() += c.readWhile([](uint8_t ch) { return ch != '\\t'; });\n          if (!c.isAtEnd()) {\n            c.skip(1);\n            state_ = VALUE;\n            strings.emplace_back();\n          }\n          break;\n        case VALUE:\n          strings.back() += c.readWhile([](uint8_t ch) { return !iseol(ch); });\n          if (!c.isAtEnd()) {\n            CHECK_GE(strings.size(), 2);\n            blocks.back().emplace_back(compress::Header::makeHeaderForTest(\n                *(strings.rbegin() + 1), *strings.rbegin()));\n            state_ = EOL;\n          }\n          break;\n      }\n    }\n    input.move();\n  }\n};\n\nint interopQIF(QPACKCodec& decoder) {\n  if (FLAGS_mode == \"encode\") {\n    QIFReader reader;\n    if (reader.read() != 0) {\n      LOG(ERROR) << \"Failed to read QIF file='\" << FLAGS_input << \"'\";\n      return 1;\n    }\n    encodeBlocks(decoder, reader.blocks);\n  } else if (FLAGS_mode == \"decode\") {\n    decodeToQIF(decoder);\n  } else {\n    LOG(ERROR) << \"Usage\" << std::endl;\n    return 1;\n  }\n\n  return 0;\n}\n\n} // namespace\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv, true);\n  QPACKCodec decoder;\n  decoder.setEncoderHeaderTableSize(FLAGS_table_size);\n  std::vector<compress::Header> empty;\n  auto res = decoder.encode(empty, 0);\n  decoder.setMaxBlocking(FLAGS_max_blocking);\n  decoder.setDecoderHeaderTableMaxSize(FLAGS_table_size);\n  decoder.decodeEncoderStream(std::move(res.control));\n  if (!FLAGS_har.empty()) {\n    return interopHAR(decoder);\n  } else {\n    return interopQIF(decoder);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_codec_compress_experimental_simulator_utils\n  SRCS\n    CompressionUtils.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_codec_compress_hpack\n    proxygen_http_header_constants\n    proxygen_http_message\n    Folly::folly_container_reserve\n    Folly::folly_expected\n)\n\nproxygen_add_library(proxygen_http_codec_compress_experimental_simulator_hpack_queue\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_hpack\n    Folly::folly_io_async_destructor_check\n)\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionScheme.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionTypes.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/SimStreamingCallback.h>\n\nnamespace proxygen::compress {\n\nclass CompressionSimulator;\n\nclass CompressionScheme : public folly::EventBase::LoopCallback {\n public:\n  explicit CompressionScheme(CompressionSimulator* sim) : simulator_(sim) {\n  }\n  ~CompressionScheme() override = default;\n\n  /* Parent class for acks */\n  struct Ack {\n    virtual ~Ack() = default;\n  };\n\n  /* Generate an ack for the given sequence number */\n  virtual std::unique_ptr<Ack> getAck(uint16_t seqn) = 0;\n\n  /* Deliver an ack to the client/encoder */\n  virtual void recvAck(std::unique_ptr<Ack>) = 0;\n\n  /* Encode the header list.\n   * The simulator sets newPacket if this block should be considered\n   * to start a new packet because of a time gap since the previous.\n   * Returns a pair { must-process-in-order, header block }\n   */\n  virtual std::pair<FrameFlags, std::unique_ptr<folly::IOBuf>> encode(\n      bool newPacket,\n      std::vector<compress::Header> allHeaders,\n      SimStats& stats) = 0;\n\n  /* Decode the supplied buffer.  allowOOO indicates if the server can process\n   * out of order.\n   */\n  virtual void decode(FrameFlags flags,\n                      std::unique_ptr<folly::IOBuf> encodedReq,\n                      SimStats& stats,\n                      SimStreamingCallback& cb) = 0;\n\n  /* Return the number of times the decoder was head-of-line blocked */\n  [[nodiscard]] virtual uint32_t getHolBlockCount() const = 0;\n\n  /* Loop callback simulates packet flushing once per loop*/\n  void runLoopCallback() noexcept override;\n\n  /* List of blocks encoded in the current event loop */\n  using BlockInfo = std::tuple<FrameFlags,\n                               bool /*newPacket*/,\n                               std::unique_ptr<folly::IOBuf>,\n                               SimStreamingCallback*>;\n  std::list<BlockInfo> encodedBlocks;\n\n  std::list<BlockInfo> packetBlocks;\n\n  // Running index of how many requests have been compressed with this scheme\n  size_t index{0};\n\n  // Used for starting new packets.\n  std::chrono::milliseconds prev;\n\n  size_t packetBytes{0};\n  std::chrono::milliseconds decodeDelay;\n\n  std::list<uint16_t> packetIndices;\n\n private:\n  CompressionSimulator* simulator_;\n};\n\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionSimulator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionSimulator.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionUtils.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/HPACKScheme.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/QMINScheme.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/QPACKScheme.h>\n\n#include <proxygen/lib/http/codec/compress/test/HTTPArchive.h>\n#include <proxygen/lib/utils/TestUtils.h>\n#include <proxygen/lib/utils/Time.h>\n\nusing namespace std;\nusing namespace folly;\n\nnamespace {\n\n// This needs to be synchronized with HPACKEncoder::kAutoFlushThreshold.\nconst size_t kMTU = 1400;\n\nconst std::string kTestDir = getContainingDirectory(XLOG_FILENAME).str();\n\n} // namespace\n\nnamespace proxygen::compress {\n\nbool CompressionSimulator::readInputFromFileAndSchedule(\n    const string& filename) {\n  unique_ptr<HTTPArchive> har;\n  try {\n    har = HTTPArchive::fromFile(kTestDir + filename);\n  } catch (const std::exception& ex) {\n    LOG(ERROR) << folly::exceptionStr(ex);\n  }\n  if (!har || har->requests.size() == 0) {\n    return false;\n  }\n  // Sort by start time (har ordered by finish time?)\n  std::sort(har->requests.begin(),\n            har->requests.end(),\n            [](const HTTPMessage& a, const HTTPMessage& b) {\n              return a.getStartTime() < b.getStartTime();\n            });\n  TimePoint last = har->requests[0].getStartTime();\n  std::chrono::milliseconds cumulativeDelay(0);\n  uint16_t index = 0;\n  for (HTTPMessage& msg : har->requests) {\n    auto delayFromPrevious = millisecondsBetween(msg.getStartTime(), last);\n    // If there was a quiescent gap in the HAR of at least some value, shrink\n    // it so the test doesn't last forever\n    if (delayFromPrevious > std::chrono::milliseconds(1000)) {\n      delayFromPrevious = std::chrono::milliseconds(1000);\n    }\n    last = msg.getStartTime();\n    cumulativeDelay += delayFromPrevious;\n    setupRequest(index++, std::move(msg), cumulativeDelay);\n  }\n  for (auto& kv : domains_) {\n    flushRequests(kv.second.get());\n  }\n  return true;\n}\n\nvoid CompressionSimulator::run() {\n#ifndef HAVE_REAL_QMIN\n  if (params_.type == SchemeType::QMIN) {\n    LOG(INFO) << \"QMIN not available\";\n    return;\n  }\n#endif\n\n  LOG(INFO) << \"Starting run\";\n  eventBase_.loop();\n  uint32_t holBlockCount = 0;\n  for (auto& scheme : domains_) {\n    holBlockCount += scheme.second->getHolBlockCount();\n  }\n  LOG(INFO) << \"Complete\"\n            << \"\\nStats:\"\n               \"\\nSeed: \"\n            << params_.seed << \"\\nBlocks sent: \" << requests_.size()\n            << \"\\nAllowed OOO: \" << stats_.allowedOOO\n            << \"\\nPackets: \" << stats_.packets\n            << \"\\nPacket Losses: \" << stats_.packetLosses\n            << \"\\nHOL Block Count: \" << holBlockCount\n            << \"\\nHOL Delay (ms): \" << stats_.holDelay.count()\n            << \"\\nMax Queue Buffer Bytes: \" << stats_.maxQueueBufferBytes\n            << \"\\nUncompressed Bytes: \" << stats_.uncompressed\n            << \"\\nCompressed Bytes: \" << stats_.compressed\n            << \"\\nCompression Ratio: \"\n            << int(100 - double(100 * stats_.compressed) / stats_.uncompressed);\n}\n\nvoid CompressionSimulator::flushRequests(CompressionScheme* scheme) {\n  VLOG(5) << \"schedule encode for \" << scheme->packetIndices.size()\n          << \" blocks at \" << scheme->prev.count();\n  // Flush previous train\n  scheduleEvent(\n      [this, scheme, indices = std::move(scheme->packetIndices)]() mutable {\n        bool newPacket = true;\n        while (!indices.empty()) {\n          int16_t index = indices.front();\n          indices.pop_front();\n          auto schemeIndex = scheme->index;\n          auto encodeRes = encode(scheme, newPacket, index);\n          FrameFlags flags = encodeRes.first;\n          bool allowOOO = flags.allowOOO;\n          if (schemeIndex < minOOOThresh()) {\n            allowOOO = false;\n            auto ack = scheme->getAck(schemeIndex);\n            if (ack) {\n              scheme->recvAck(std::move(ack));\n            }\n          }\n          stats_.allowedOOO += (allowOOO) ? 1 : 0;\n          flags.allowOOO = allowOOO;\n          scheme->encodedBlocks.emplace_back(flags,\n                                             newPacket,\n                                             std::move(encodeRes.second),\n                                             &callbacks_[index]);\n          newPacket = false;\n        }\n        eventBase_.runInLoop(scheme, true);\n      },\n      scheme->prev);\n}\n\nvoid CompressionSimulator::setupRequest(uint16_t index,\n                                        HTTPMessage&& msg,\n                                        std::chrono::milliseconds encodeDelay) {\n  // Normalize to relative paths\n  auto query = msg.getQueryStringAsStringPiece();\n  if (query.empty()) {\n    msg.setURL(msg.getPathAsStringPiece());\n  } else {\n    msg.setURL(folly::to<string>(msg.getPathAsStringPiece(), \"?\", query));\n  }\n\n  auto scheme = getScheme(msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST));\n  requests_.emplace_back(msg);\n  auto decodeCompleteCB =\n      [index, this, scheme](std::chrono::milliseconds holDelay) {\n        // record processed timestamp\n        CHECK(!callbacks_[index].getResult().hasError());\n        DCHECK_EQ(requests_[index], *callbacks_[index].getResult().value());\n        stats_.holDelay += holDelay;\n        VLOG(1) << \"Finished decoding request=\" << index\n                << \" with holDelay=\" << holDelay.count()\n                << \" cumulative HoL delay=\" << stats_.holDelay.count();\n        if (callbacks_[index].acknowledge) {\n          sendAck(scheme, scheme->getAck(callbacks_[index].seqn));\n        }\n      };\n  callbacks_.emplace_back(index, decodeCompleteCB);\n\n  // Assume that all packets with same encodeDelay will form a packet\n  // train.  Encode them as a group, so can we can emulate packet\n  // boundaries more realistically, telling the encoder which blocks\n  // start such trains.\n  if (scheme->packetIndices.size() > 0) {\n    auto delayFromPrevious = encodeDelay - scheme->prev;\n    VLOG(1) << \"request \" << index << \" delay \" << delayFromPrevious.count();\n    if (delayFromPrevious > std::chrono::milliseconds(1)) {\n      flushRequests(scheme);\n    }\n  }\n  scheme->prev = encodeDelay;\n  scheme->packetIndices.push_back(index);\n}\n\n// Once per loop, each connection flushes it's encode blocks and schedules\n// decodes based on how many packets the block occupies\nvoid CompressionScheme::runLoopCallback() noexcept {\n  simulator_->flushSchemePackets(this);\n}\n\nvoid CompressionSimulator::flushPacket(CompressionScheme* scheme) {\n  if (scheme->packetBlocks.empty()) {\n    return;\n  }\n\n  stats_.packets++;\n  VLOG(1) << \"schedule decode for \" << scheme->packetBlocks.size()\n          << \" blocks at \" << scheme->decodeDelay.count();\n  scheduleEvent(\n      {[this, scheme, blocks = std::move(scheme->packetBlocks)]() mutable {\n        decodePacket(scheme, blocks);\n      }},\n      scheme->decodeDelay);\n  scheme->packetBytes = 0;\n}\n\nvoid CompressionSimulator::flushSchemePackets(CompressionScheme* scheme) {\n  CHECK(!scheme->encodedBlocks.empty());\n  VLOG(2) << \"Flushing \" << scheme->encodedBlocks.size() << \" requests\";\n  // tracks the number of bytes in the current simulated packet\n  auto encodeRes = &scheme->encodedBlocks.front();\n  bool newPacket = std::get<1>(*encodeRes);\n  size_t headerBlockBytesRemaining =\n      std::get<2>(*encodeRes)->computeChainDataLength();\n  std::chrono::milliseconds packetDelay = deliveryDelay();\n  scheme->decodeDelay = packetDelay;\n  while (true) {\n    if (newPacket) {\n      flushPacket(scheme);\n    }\n    newPacket = false;\n    // precondition packetBytes < kMTU\n    if (scheme->packetBytes + headerBlockBytesRemaining >= kMTU) {\n      // Header block filled current packet, triggering a flush\n      VLOG(2) << \"Request(s) spanned multiple packets\";\n      newPacket = true;\n    } else {\n      scheme->packetBytes += headerBlockBytesRemaining;\n    }\n    headerBlockBytesRemaining -=\n        std::min(headerBlockBytesRemaining, kMTU - scheme->packetBytes);\n    if (headerBlockBytesRemaining == 0) {\n      // Move from the first element of encodedBlocks to the last\n      // element of packetBlocks.\n      scheme->packetBlocks.splice(scheme->packetBlocks.end(),\n                                  scheme->encodedBlocks,\n                                  scheme->encodedBlocks.begin());\n      if (scheme->encodedBlocks.empty()) {\n        // All done\n        break;\n      }\n      // Grab the next request\n      encodeRes = &scheme->encodedBlocks.front();\n      newPacket = std::get<1>(*encodeRes);\n      headerBlockBytesRemaining =\n          std::get<2>(*encodeRes)->computeChainDataLength();\n    }\n    if (newPacket) {\n      packetDelay = deliveryDelay();\n      scheme->decodeDelay = std::max(scheme->decodeDelay, packetDelay);\n    }\n  }\n  flushPacket(scheme);\n  CHECK(scheme->encodedBlocks.empty());\n}\n\nCompressionScheme* CompressionSimulator::getScheme(StringPiece domain) {\n  static string blended(\"\\\"Facebook\\\"\");\n  if (params_.blend &&\n      (domain.endsWith(\"facebook.com\") || domain.endsWith(\"fbcdn.net\"))) {\n    domain = blended;\n  }\n\n  auto it = domains_.find(domain.str());\n  CompressionScheme* scheme = nullptr;\n  if (it == domains_.end()) {\n    LOG(INFO) << \"Creating scheme for domain=\" << domain;\n    auto schemePtr = makeScheme();\n    scheme = schemePtr.get();\n    domains_.emplace(domain.str(), std::move(schemePtr));\n  } else {\n    scheme = it->second.get();\n  }\n  return scheme;\n}\n\nunique_ptr<CompressionScheme> CompressionSimulator::makeScheme() {\n  switch (params_.type) {\n    case SchemeType::QPACK:\n      return make_unique<QPACKScheme>(\n          this, params_.tableSize, params_.maxBlocking);\n    case SchemeType::QMIN:\n      return make_unique<QMINScheme>(this, params_.tableSize);\n    case SchemeType::HPACK:\n      return make_unique<HPACKScheme>(this, params_.tableSize);\n  }\n  LOG(FATAL) << \"Bad scheme\";\n}\n\nstd::pair<FrameFlags, unique_ptr<IOBuf>> CompressionSimulator::encode(\n    CompressionScheme* scheme, bool newPacket, uint16_t index) {\n  VLOG(1) << \"Start encoding request=\" << index;\n  // vector to hold cookie crumbs\n  vector<string> cookies;\n  vector<compress::Header> allHeaders =\n      prepareMessageForCompression(requests_[index], cookies);\n\n  auto before = stats_.uncompressed;\n  auto res = scheme->encode(newPacket, std::move(allHeaders), stats_);\n  VLOG(1) << \"Encoded request=\" << index << \" for host=\"\n          << requests_[index].getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST)\n          << \" orig size=\" << (stats_.uncompressed - before)\n          << \" block size=\" << res.second->computeChainDataLength()\n          << \" cumulative bytes=\" << stats_.compressed\n          << \" cumulative compression ratio=\"\n          << int(100 - double(100 * stats_.compressed) / stats_.uncompressed);\n  return res;\n}\n\nvoid CompressionSimulator::decode(CompressionScheme* scheme,\n                                  FrameFlags flags,\n                                  unique_ptr<IOBuf> encodedReq,\n                                  SimStreamingCallback& cb) {\n  scheme->decode(flags, std::move(encodedReq), stats_, cb);\n}\n\nvoid CompressionSimulator::decodePacket(\n    CompressionScheme* scheme,\n    std::list<CompressionScheme::BlockInfo>& blocks) {\n  VLOG(1) << \"decode packet with \" << blocks.size() << \" blocks\";\n  while (!blocks.empty()) {\n    auto encodeRes = &blocks.front();\n    // TODO(ckrasic) - to get packet coordination correct, could plumb\n    // through \"start of packet\" flag here.  Probably not worth it,\n    // since it seems to make only a very small difference (about a\n    // 0.1% compressiondifference on my facebook har).\n    decode(scheme,\n           std::get<0>(*encodeRes),\n           std::move(std::get<2>(*encodeRes)),\n           *std::get<3>(*encodeRes));\n    blocks.pop_front();\n  }\n}\n\nvoid CompressionSimulator::scheduleEvent(folly::Function<void()> f,\n                                         std::chrono::milliseconds ms) {\n  eventBase_.runAfterDelay(std::move(f), ms.count());\n}\n\nvoid CompressionSimulator::sendAck(CompressionScheme* scheme,\n                                   unique_ptr<CompressionScheme::Ack> ack) {\n  if (!ack) {\n    return;\n  }\n  // An ack is a packet\n  stats_.packets++;\n  scheduleEvent([a = std::move(ack),\n                 this,\n                 scheme]() mutable { recvAck(scheme, std::move(a)); },\n                deliveryDelay());\n}\n\nvoid CompressionSimulator::recvAck(CompressionScheme* scheme,\n                                   unique_ptr<CompressionScheme::Ack> ack) {\n  scheme->recvAck(std::move(ack));\n}\n\nstd::chrono::milliseconds CompressionSimulator::deliveryDelay() {\n  std::chrono::milliseconds delay = one_half_rtt();\n  while (loss()) {\n    stats_.packetLosses++;\n    scheduleEvent([] { VLOG(4) << \"Packet lost!\"; }, delay);\n    std::chrono::milliseconds rxmit = rxmitDelay();\n    delay += rxmit;\n    scheduleEvent(\n        [rxmit] {\n          VLOG(4) << \"Packet loss detected, retransmitting with additional \"\n                  << rxmit.count();\n        },\n        delay - one_half_rtt());\n  }\n  if (delayed()) {\n    scheduleEvent([] { VLOG(4) << \"Packet delayed in network\"; }, delay);\n    delay += extraDelay();\n  }\n  return delay;\n}\n\nstd::chrono::milliseconds CompressionSimulator::rtt() {\n  return params_.rtt;\n}\n\nstd::chrono::milliseconds CompressionSimulator::one_half_rtt() {\n  return params_.rtt / 2;\n}\n\nstd::chrono::milliseconds CompressionSimulator::rxmitDelay() {\n  uint32_t ms = rtt().count() * Random::randDouble(1.1, 2, rng_);\n  return std::chrono::milliseconds(ms);\n}\n\nbool CompressionSimulator::loss() {\n  return Random::randDouble01(rng_) < params_.lossProbability;\n}\n\nbool CompressionSimulator::delayed() {\n  return Random::randDouble01(rng_) < params_.delayProbability;\n}\n\nstd::chrono::milliseconds CompressionSimulator::extraDelay() {\n  uint32_t ms = params_.maxDelay.count() * Random::randDouble01(rng_);\n  return std::chrono::milliseconds(ms);\n}\n\nuint32_t CompressionSimulator::minOOOThresh() {\n  return params_.minOOOThresh;\n}\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionSimulator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionScheme.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionTypes.h>\n\n#include <chrono>\n#include <folly/Random.h>\n#include <folly/io/async/EventBase.h>\n#include <unordered_map>\n#include <vector>\n\nnamespace proxygen::compress {\n\nclass CompressionSimulator {\n public:\n  explicit CompressionSimulator(SimParams p) : params_(p) {\n  }\n\n  bool readInputFromFileAndSchedule(const std::string& filename);\n  void run();\n\n  // Called from CompressionScheme::runLoopCallback\n  void flushSchemePackets(CompressionScheme* scheme);\n  void flushPacket(CompressionScheme* scheme);\n\n private:\n  void flushRequests(CompressionScheme* scheme);\n  void setupRequest(uint16_t seqn,\n                    HTTPMessage&& msg,\n                    std::chrono::milliseconds encodeDelay);\n  CompressionScheme* getScheme(folly::StringPiece host);\n  std::unique_ptr<CompressionScheme> makeScheme();\n  std::pair<FrameFlags, std::unique_ptr<folly::IOBuf>> encode(\n      CompressionScheme* scheme, bool newPacket, uint16_t seqn);\n  void decodePacket(CompressionScheme* scheme,\n                    std::list<CompressionScheme::BlockInfo>& blocks);\n  void decode(CompressionScheme* scheme,\n              FrameFlags flags,\n              std::unique_ptr<folly::IOBuf> encodedReq,\n              SimStreamingCallback& cb);\n  void scheduleEvent(folly::Function<void()> f, std::chrono::milliseconds ms);\n  void sendAck(CompressionScheme* scheme,\n               std::unique_ptr<CompressionScheme::Ack> ack);\n  void recvAck(CompressionScheme* scheme,\n               std::unique_ptr<CompressionScheme::Ack> ack);\n\n  std::chrono::milliseconds deliveryDelay();\n  std::chrono::milliseconds rtt();\n  std::chrono::milliseconds one_half_rtt();\n  std::chrono::milliseconds rxmitDelay();\n  bool loss();\n  bool delayed();\n  std::chrono::milliseconds extraDelay();\n  uint32_t minOOOThresh();\n\n  SimParams params_;\n  std::vector<proxygen::HTTPMessage> requests_;\n  folly::EventBase eventBase_;\n  // Map of domain-name to compression scheme\n  std::unordered_map<std::string, std::unique_ptr<CompressionScheme>> domains_;\n  std::vector<SimStreamingCallback> callbacks_;\n  folly::Random::DefaultGenerator rng_{\n      static_cast<folly::Random::DefaultGenerator::result_type>(params_.seed)};\n  SimStats stats_;\n};\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionTypes.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n\nnamespace proxygen::compress {\nenum class SchemeType { QPACK, QMIN, HPACK };\n\n// Metadata about encoded blocks.  In a real stack, these might be\n// conveyed via HTTP frame (HEADERS or PUSH_PROMISE) flags.\nstruct FrameFlags {\n  FrameFlags(bool ooo = false, bool depends = false)\n      : allowOOO(ooo), QPACKPrefixHasDepends(depends) {\n  }\n\n  bool allowOOO{false};\n  bool QPACKPrefixHasDepends{false};\n};\n\nstruct SimParams {\n  SchemeType type;\n  int64_t seed;\n  std::chrono::milliseconds rtt;\n  double lossProbability;\n  double delayProbability;\n  std::chrono::milliseconds maxDelay;\n  uint16_t minOOOThresh;\n  bool blend;\n  bool samePacketCompression;\n  uint32_t tableSize;\n  uint32_t maxBlocking;\n};\n\nstruct SimStats {\n  uint64_t allowedOOO{0};\n  uint64_t packetLosses{0};\n  uint64_t maxQueueBufferBytes{0};\n  std::chrono::milliseconds holDelay{0};\n  uint64_t uncompressed{0};\n  uint64_t compressed{0};\n  uint64_t packets{0};\n};\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/container/Reserve.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionUtils.h>\n\n#include <proxygen/lib/http/HeaderConstants.h>\n\nusing std::string;\nusing std::vector;\n\nnamespace {\n\nusing namespace proxygen;\n\nstd::string combineCookieCrumbsSorted(std::vector<std::string> crumbs) {\n  std::string retval;\n  sort(crumbs.begin(), crumbs.end());\n  folly::join(\"; \", crumbs.begin(), crumbs.end(), retval);\n  return retval;\n}\n\nbool containsAllHeaders(const HTTPHeaders& h1, const HTTPHeaders& h2) {\n  bool allValuesPresent = true;\n  bool verifyCookies = false;\n  h1.forEachWithCode(\n      [&](HTTPHeaderCode code, const string& name, const string& value1) {\n        bool h2HasValue =\n            h2.forEachValueOfHeader(code, [&value1](const std::string& value2) {\n              return (value1 == value2);\n            });\n        if (!h2HasValue && code == HTTP_HEADER_COOKIE) {\n          verifyCookies = true;\n          return;\n        }\n        DCHECK(h2HasValue) << \"h2 does not contain name=\" << name\n                           << \" value=\" << value1;\n        allValuesPresent &= h2HasValue;\n      });\n\n  if (verifyCookies) {\n    const HTTPHeaders* headers[] = {\n        &h1,\n        &h2,\n    };\n    std::string cookies[2] = {\n        \"\",\n        \"\",\n    };\n    unsigned i;\n    for (i = 0; i < 2; ++i) {\n      std::vector<std::string> crumbs;\n      headers[i]->forEachValueOfHeader(HTTP_HEADER_COOKIE,\n                                       [&](const std::string& crumb) {\n                                         crumbs.push_back(crumb);\n                                         return false;\n                                       });\n      cookies[i] = combineCookieCrumbsSorted(crumbs);\n    }\n    if (cookies[0] == cookies[1]) {\n      LOG(INFO) << \"Cookie crumbs are reordered\";\n    } else {\n      LOG(INFO) << \"Cookies are not equal: `\" << cookies[0] << \"' vs. `\"\n                << cookies[1] << \"'\";\n      return false;\n    }\n  }\n\n  return allValuesPresent;\n}\n\n} // namespace\n\nnamespace proxygen {\n\nnamespace compress {\n\nstd::vector<compress::Header> prepareMessageForCompression(\n    const HTTPMessage& msg, std::vector<string>& cookies) {\n  std::vector<compress::Header> allHeaders;\n  // The encode API is pretty bad.  We should just let HPACK directly encode\n  // HTTP messages\n  const HTTPHeaders& headers = msg.getHeaders();\n  const string& host = headers.getSingleOrEmpty(HTTP_HEADER_HOST);\n  bool isPublic = msg.getMethodString().empty();\n  if (!isPublic) {\n    allHeaders.emplace_back(\n        HTTP_HEADER_COLON_METHOD, headers::kMethod, msg.getMethodString());\n    if (msg.getMethod() != HTTPMethod::CONNECT) {\n      allHeaders.emplace_back(\n          HTTP_HEADER_COLON_SCHEME,\n          headers::kScheme,\n          (msg.isSecure() ? headers::kHttps : headers::kHttp));\n      allHeaders.emplace_back(\n          HTTP_HEADER_COLON_PATH, headers::kPath, msg.getURL());\n    }\n\n    if (!host.empty()) {\n      allHeaders.emplace_back(\n          HTTP_HEADER_COLON_AUTHORITY, headers::kAuthority, host);\n    }\n  }\n  // Cookies are coalesced in the HAR file but need to be added as separate\n  // headers to optimize compression ratio\n  headers.forEachWithCode(\n      [&](HTTPHeaderCode code, const string& name, const string& value) {\n        if (code == HTTP_HEADER_COOKIE) {\n          vector<folly::StringPiece> cookiePieces;\n          folly::split(';', value, cookiePieces);\n          folly::grow_capacity_by(cookies, cookiePieces.size());\n          for (auto cookie : cookiePieces) {\n            cookies.push_back(ltrimWhitespace(cookie).str());\n            allHeaders.emplace_back(code, name, cookies.back());\n          }\n        } else if (code != HTTP_HEADER_HOST && (isPublic || name[0] != ':')) {\n          // HAR files contain actual serialized headers protocol headers like\n          // :authority, which we are re-adding above.  Strip them so our\n          // equality test works\n          allHeaders.emplace_back(code, name, value);\n        }\n      });\n  return allHeaders;\n}\n\n} // namespace compress\n\nbool operator==(const HTTPMessage& msg1, const HTTPMessage& msg2) {\n  return (msg1.getMethodString() == msg2.getMethodString() &&\n          msg1.getURL() == msg2.getURL() &&\n          msg1.isSecure() == msg2.isSecure() &&\n          containsAllHeaders(msg1.getHeaders(), msg2.getHeaders()) &&\n          containsAllHeaders(msg2.getHeaders(), msg1.getHeaders()));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/CompressionUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/compress/Header.h>\n\n#include <string>\n#include <vector>\n\nnamespace proxygen {\nnamespace compress {\n\n// Convert an HTTPMessage into a format that can be passed to a HeaderCodec\nstd::vector<Header> prepareMessageForCompression(\n    const HTTPMessage& msg, std::vector<std::string>& cookies);\n\n} // namespace compress\n\n// Compare two HTTPMessage's for equality\nbool operator==(const HTTPMessage& msg1, const HTTPMessage& msg2);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/HPACKQueue.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/DestructorCheck.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n\n#include <deque>\n#include <memory>\n#include <tuple>\n\nnamespace proxygen {\n\nclass HPACKQueue : public folly::DestructorCheck {\n public:\n  explicit HPACKQueue(HPACKCodec& codec) : codec_(codec) {\n  }\n\n  void enqueueHeaderBlock(uint32_t seqn,\n                          std::unique_ptr<folly::IOBuf> block,\n                          size_t length,\n                          HPACK::StreamingCallback* streamingCb,\n                          bool oooOk) {\n    if (seqn < nextSeqn_) {\n      streamingCb->onDecodeError(HPACK::DecodeError::BAD_SEQUENCE_NUMBER);\n      return;\n    }\n    if (nextSeqn_ == seqn) {\n      // common case, decode immediately\n      if (decodeBlock(seqn,\n                      std::move(block),\n                      length,\n                      streamingCb,\n                      false /* in order */)) {\n        return;\n      }\n      drainQueue();\n    } else {\n      // there's a gap, have to queue\n      auto it = queue_.begin();\n      while (it != queue_.end()) {\n        auto qSeqn = std::get<0>(*it);\n        if (seqn == qSeqn) {\n          streamingCb->onDecodeError(HPACK::DecodeError::BAD_SEQUENCE_NUMBER);\n          return;\n        } else if (seqn < qSeqn) {\n          break;\n        }\n        it++;\n      }\n      if (oooOk) {\n        // out-of-order allowed.  Decode the block, but make an empty entry in\n        // the queue\n        if (decodeBlock(\n                seqn, std::move(block), length, streamingCb, true /* ooo */)) {\n          return;\n        }\n        length = 0;\n        streamingCb = nullptr;\n      } else {\n        holBlockCount_++;\n      }\n      VLOG(5) << \"queued block=\" << seqn << \" len=\" << length\n              << \" placeholder=\" << int32_t(oooOk);\n      queuedBytes_ += length;\n      queue_.emplace(it, seqn, std::move(block), length, streamingCb);\n    }\n  }\n\n  [[nodiscard]] uint64_t getHolBlockCount() const {\n    return holBlockCount_;\n  }\n\n  [[nodiscard]] uint64_t getQueuedBytes() const {\n    return queuedBytes_;\n  }\n\n private:\n  // Returns true if this object was destroyed by its callback.  Callers\n  // should check the result and immediately return.\n  bool decodeBlock(int32_t seqn,\n                   std::unique_ptr<folly::IOBuf> block,\n                   size_t length,\n                   HPACK::StreamingCallback* cb,\n                   bool ooo) {\n    if (length > 0) {\n      VLOG(5) << \"decodeBlock for block=\" << seqn << \" len=\" << length;\n      folly::io::Cursor c(block.get());\n      folly::DestructorCheck::Safety safety(*this);\n      codec_.decodeStreaming(c, length, cb);\n      if (safety.destroyed()) {\n        return true;\n      }\n    }\n    if (!ooo) {\n      nextSeqn_++;\n    }\n    return false;\n  }\n\n  void drainQueue() {\n    while (!queue_.empty() && nextSeqn_ == std::get<0>(queue_.front())) {\n      auto& next = queue_.front();\n      auto length = std::get<2>(next);\n      if (decodeBlock(std::get<0>(next),\n                      std::move(std::get<1>(next)),\n                      length,\n                      std::get<3>(next),\n                      false /* in order */)) {\n        return;\n      }\n      DCHECK_LE(length, queuedBytes_);\n      queuedBytes_ -= length;\n      queue_.pop_front();\n    }\n  }\n\n  size_t nextSeqn_{0};\n  uint64_t holBlockCount_{0};\n  uint64_t queuedBytes_{0};\n  std::deque<std::tuple<uint32_t,\n                        std::unique_ptr<folly::IOBuf>,\n                        size_t,\n                        HPACK::StreamingCallback*>>\n      queue_;\n  HPACKCodec& codec_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/HPACKQueueTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/experimental/simulator/HPACKQueue.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace folly;\nusing namespace proxygen::compress;\nusing namespace proxygen;\nusing namespace proxygen::hpack;\n\nnamespace {\nuint64_t bufLen(const std::unique_ptr<IOBuf>& buf) {\n  if (buf) {\n    return buf->computeChainDataLength();\n  }\n  return 0;\n}\n\n} // namespace\n\nclass HPACKQueueTests : public testing::TestWithParam<int> {\n public:\n  HPACKQueueTests() : queue(std::make_unique<HPACKQueue>(server)) {\n  }\n\n protected:\n  HPACKCodec client{TransportDirection::UPSTREAM};\n  HPACKCodec server{TransportDirection::DOWNSTREAM};\n  std::unique_ptr<HPACKQueue> queue;\n};\n\nTEST_F(HPACKQueueTests, QueueInline) {\n  std::vector<Header> req = basicHeaders();\n  TestStreamingCallback cb;\n\n  for (int i = 0; i < 3; i++) {\n    std::unique_ptr<IOBuf> encodedReq = client.encode(req);\n    auto len = bufLen(encodedReq);\n    cb.reset();\n    queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &cb, false);\n    auto result = cb.getResult();\n    EXPECT_TRUE(!result.hasError());\n    EXPECT_EQ(result->headers.size(), 12);\n  }\n}\n\nTEST_F(HPACKQueueTests, QueueReorder) {\n  std::vector<Header> req = basicHeaders();\n  std::vector<std::pair<std::unique_ptr<IOBuf>, TestStreamingCallback>> data;\n\n  data.reserve(4);\n  for (int i = 0; i < 4; i++) {\n    data.emplace_back(client.encode(req), TestStreamingCallback());\n  }\n\n  std::vector<int> insertOrder{1, 3, 2, 0};\n  for (auto i : insertOrder) {\n    auto& encodedReq = data[i].first;\n    auto len = bufLen(encodedReq);\n    queue->enqueueHeaderBlock(\n        i, std::move(encodedReq), len, &data[i].second, false);\n  }\n  for (auto& d : data) {\n    auto result = d.second.getResult();\n    EXPECT_TRUE(!result.hasError());\n    EXPECT_EQ(result->headers.size(), 12);\n  }\n  EXPECT_EQ(queue->getHolBlockCount(), 3);\n}\n\nTEST_F(HPACKQueueTests, QueueReorderOoo) {\n  std::vector<Header> req = basicHeaders();\n  std::vector<std::pair<std::unique_ptr<IOBuf>, TestStreamingCallback>> data;\n\n  data.reserve(4);\n  for (int i = 0; i < 4; i++) {\n    data.emplace_back(client.encode(req), TestStreamingCallback());\n  }\n\n  std::vector<int> insertOrder{0, 3, 2, 1};\n  for (auto i : insertOrder) {\n    auto& encodedReq = data[i].first;\n    auto len = bufLen(encodedReq);\n    // Allow idx 3 to be decoded out of order\n    queue->enqueueHeaderBlock(\n        i, std::move(encodedReq), len, &data[i].second, i == 3);\n  }\n  for (auto& d : data) {\n    auto result = d.second.getResult();\n    EXPECT_TRUE(!result.hasError());\n    EXPECT_EQ(result->headers.size(), 12);\n  }\n  EXPECT_EQ(queue->getHolBlockCount(), 1);\n}\n\nTEST_F(HPACKQueueTests, QueueError) {\n  std::vector<Header> req = basicHeaders();\n  TestStreamingCallback cb;\n\n  bool expectOk = true;\n  // ok, dup, ok, lower\n  for (auto i : std::vector<int>({0, 0, 1, 0, 3, 3, 2})) {\n    std::unique_ptr<IOBuf> encodedReq = client.encode(req);\n    auto len = bufLen(encodedReq);\n    cb.reset();\n    queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &cb, true);\n    auto result = cb.getResult();\n    if (expectOk) {\n      EXPECT_TRUE(!result.hasError());\n      EXPECT_EQ(result->headers.size(), 12);\n    } else {\n      EXPECT_TRUE(result.hasError());\n      EXPECT_EQ(result.error(), HPACK::DecodeError::BAD_SEQUENCE_NUMBER);\n    }\n    expectOk = !expectOk;\n  }\n}\n\nTEST_P(HPACKQueueTests, QueueDeleted) {\n  std::vector<Header> req = basicHeaders();\n  std::vector<std::pair<std::unique_ptr<IOBuf>, TestStreamingCallback>> data;\n\n  for (int i = 0; i < 4; i++) {\n    data.emplace_back(client.encode(req), TestStreamingCallback());\n    if (i == GetParam()) {\n      data.back().second.headersCompleteCb = [&] { queue.reset(); };\n    }\n  }\n\n  std::vector<int> insertOrder{0, 3, 2, 1};\n  for (auto i : insertOrder) {\n    auto& encodedReq = data[i].first;\n    auto len = bufLen(encodedReq);\n\n    // Allow idx 3 to be decoded out of order\n    queue->enqueueHeaderBlock(\n        i, std::move(encodedReq), len, &data[i].second, i == 3);\n    if (!queue) {\n      break;\n    }\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(Queue, HPACKQueueTests, ::testing::Values(0, 1, 2, 3));\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/HPACKScheme.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/NoPathIndexingStrategy.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionScheme.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/HPACKQueue.h>\n\nnamespace proxygen::compress {\n\n/**\n * Compression scheme for HPACK with a prepended sequence number\n */\nclass HPACKScheme : public CompressionScheme {\n public:\n  explicit HPACKScheme(CompressionSimulator* sim, uint32_t tableSize)\n      : CompressionScheme(sim) {\n    client_.setEncodeHeadroom(2);\n    client_.setHeaderIndexingStrategy(NoPathIndexingStrategy::getInstance());\n    server_.setHeaderIndexingStrategy(NoPathIndexingStrategy::getInstance());\n    client_.setEncoderHeaderTableSize(tableSize);\n    server_.setDecoderHeaderTableMaxSize(tableSize);\n    allowOOO_ = (tableSize == 0);\n  }\n\n  ~HPACKScheme() override {\n    CHECK_EQ(serverQueue_.getQueuedBytes(), 0);\n  }\n\n  // HPACK has no ACKs\n  std::unique_ptr<Ack> getAck(uint16_t /*seqn*/) override {\n    return nullptr;\n  }\n  void recvAck(std::unique_ptr<Ack> /*ack*/) override {\n  }\n\n  std::pair<FrameFlags, std::unique_ptr<folly::IOBuf>> encode(\n      bool /*newPacket*/,\n      std::vector<compress::Header> allHeaders,\n      SimStats& stats) override {\n    auto block = client_.encode(allHeaders);\n    block->prepend(sizeof(uint16_t));\n    folly::io::RWPrivateCursor c(block.get());\n    c.writeBE<uint16_t>(static_cast<uint16_t>(index++));\n    stats.uncompressed += client_.getEncodedSize().uncompressed;\n    stats.compressed += client_.getEncodedSize().compressed;\n    // OOO is allowed with 0 table size\n    FrameFlags flags{allowOOO_};\n    return {flags, std::move(block)};\n  }\n\n  void decode(FrameFlags flags,\n              std::unique_ptr<folly::IOBuf> encodedReq,\n              SimStats& stats,\n              SimStreamingCallback& callback) override {\n    folly::io::Cursor cursor(encodedReq.get());\n    auto seqn = cursor.readBE<uint16_t>();\n    callback.seqn = seqn;\n    VLOG(1) << \"Decoding request=\" << callback.requestIndex\n            << \" header seqn=\" << seqn;\n    auto len = cursor.totalLength();\n    encodedReq->trimStart(sizeof(uint16_t));\n    serverQueue_.enqueueHeaderBlock(\n        seqn, std::move(encodedReq), len, &callback, flags.allowOOO);\n    callback.maybeMarkHolDelay();\n    if (serverQueue_.getQueuedBytes() > stats.maxQueueBufferBytes) {\n      stats.maxQueueBufferBytes = serverQueue_.getQueuedBytes();\n    }\n  }\n\n  uint32_t getHolBlockCount() const override {\n    return serverQueue_.getHolBlockCount();\n  }\n\n  HPACKCodec client_{TransportDirection::UPSTREAM};\n  HPACKCodec server_{TransportDirection::DOWNSTREAM};\n  HPACKQueue serverQueue_{server_};\n  bool allowOOO_{false};\n};\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/Main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/init/Init.h>\n#include <folly/portability/GFlags.h>\n\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionSimulator.h>\n\nDEFINE_string(input, \"\", \"File containing requests\");\nDEFINE_string(scheme, \"qpack\", \"Scheme: <qpack|qmin|hpack>\");\n\nDEFINE_int32(rtt, 100, \"Simulated RTT\");\nDEFINE_double(lossp, 0.0, \"Loss Probability\");\nDEFINE_double(delayp, 0.05, \"Delay Probability\");\nDEFINE_int32(delay, 100, \"Max extra delay\");\nDEFINE_int32(ooo_thresh, 0, \"First seqn to allow ooo\");\nDEFINE_int32(table_size, 4096, \"HPACK dynamic table size\");\nDEFINE_int64(seed, 0, \"RNG seed\");\nDEFINE_bool(blend, true, \"Blend all facebook.com and fbcdn.net domains\");\nDEFINE_int32(max_blocking,\n             100,\n             \"Maximum number of vulnerable/blocking header blocks\");\nDEFINE_bool(same_packet_compression,\n            true,\n            \"Allow QPACK to compress across \"\n            \"headers the same packet\");\n\nusing namespace proxygen::compress;\n\nint main(int argc, char* argv[]) {\n  const folly::Init init(&argc, &argv, true);\n  if (FLAGS_same_packet_compression) {\n    LOG(WARNING) << \"Same packet compression no longer supported\";\n  }\n\n  if (FLAGS_input.empty()) {\n    LOG(ERROR) << \"Must supply a filename\";\n    return 1;\n  }\n\n  SchemeType t = SchemeType::QPACK;\n  if (FLAGS_scheme == \"qpack\") {\n    LOG(INFO) << \"Using QPACK\";\n    t = SchemeType::QPACK;\n  } else if (FLAGS_scheme == \"qmin\") {\n    LOG(INFO) << \"Using QMIN\";\n    t = SchemeType::QMIN;\n  } else if (FLAGS_scheme == \"hpack\") {\n    LOG(INFO) << \"Using HPACK with table size=\" << FLAGS_table_size;\n    t = SchemeType::HPACK;\n  } else {\n    LOG(ERROR) << \"Unsupported scheme\";\n    return 1;\n  }\n\n  if (FLAGS_seed == 0) {\n    FLAGS_seed = folly::Random::rand64();\n    std::cout << \"Seed: \" << FLAGS_seed << std::endl;\n  }\n  SimParams p{.type = t,\n              .seed = FLAGS_seed,\n              .rtt = std::chrono::milliseconds(FLAGS_rtt),\n              .lossProbability = FLAGS_lossp,\n              .delayProbability = FLAGS_delayp,\n              .maxDelay = std::chrono::milliseconds(FLAGS_delay),\n              .minOOOThresh = uint16_t(FLAGS_ooo_thresh),\n              .blend = FLAGS_blend,\n              .samePacketCompression = FLAGS_same_packet_compression,\n              .tableSize = uint32_t(FLAGS_table_size),\n              .maxBlocking = uint32_t(FLAGS_max_blocking)};\n  CompressionSimulator sim(p);\n  if (sim.readInputFromFileAndSchedule(FLAGS_input)) {\n    sim.run();\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/QMINScheme.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <assert.h>\n#include <folly/String.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/NoPathIndexingStrategy.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionScheme.h>\n#include <sys/queue.h>\n\n#ifdef HAVE_REAL_QMIN\n#include \"qmin_common.h\" // @manual\n#include \"qmin_dec.h\"    // @manual\n#include \"qmin_enc.h\"    // @manual\n#else\n/* Stub implementation for when you don't have QMIN */\nextern \"C\" {\nenum qmin_index_type {\n  QIT_YES,\n  QIT_NO,\n  QIT_NEVER,\n};\nenum {\n  QSIDE_CLIENT,\n  QSIDE_SERVER,\n};\nenum qmin_encode_status {\n  QES_OK,\n  QES_NOBUFS,\n  QES_ERR,\n};\nstruct qmin_ctl_out {\n  void (*qco_write)(void *qco_ctx, const void *, size_t);\n  void *qco_ctx;\n};\nstruct qmin_enc;\nstatic struct qmin_enc *qmin_enc_new(int /*side*/,\n                                     unsigned /*max_capacity*/,\n                                     const struct qmin_ctl_out * /*ctl_out*/,\n                                     const char * /*idstr*/) {\n  return nullptr;\n}\nstatic ssize_t qmin_enc_cmds_in(struct qmin_enc * /*enc*/,\n                                const void * /*buf*/,\n                                size_t /*bufsz*/) {\n  return -1;\n}\nstatic enum qmin_encode_status qmin_enc_encode(struct qmin_enc * /*enc*/,\n                                               unsigned /*stream_id*/,\n                                               const char * /*name*/,\n                                               unsigned /*name_len*/,\n                                               const char * /*value*/,\n                                               unsigned /*value_len*/,\n                                               enum qmin_index_type /*ix_type*/,\n                                               unsigned char * /*dst*/,\n                                               size_t /*dst_sz*/,\n                                               size_t * /*n_written*/) {\n  return QES_ERR;\n}\nstatic int qmin_enc_end_stream_headers(struct qmin_enc * /*enc*/) {\n  return -1;\n}\nstatic char *qmin_enc_to_str(struct qmin_enc * /*enc*/, size_t * /*size*/) {\n  return strdup(\"\");\n}\nstatic void qmin_enc_destroy(struct qmin_enc * /*enc*/) {\n}\nstruct qmin_dec;\nstatic struct qmin_dec *qmin_dec_new(int /*side*/,\n                                     unsigned /*max_capacity*/,\n                                     const struct qmin_ctl_out * /*ctl_out*/,\n                                     const char * /*idstr*/) {\n  return nullptr;\n}\nstatic ssize_t qmin_dec_cmds_in(struct qmin_dec * /*dec*/,\n                                const void * /*buf*/,\n                                size_t /*bufsz*/) {\n  return -1;\n}\nstatic ssize_t qmin_dec_decode(struct qmin_dec * /*dec*/,\n                               const void * /*void_src*/,\n                               size_t /*src_sz*/,\n                               char * /*dst*/,\n                               size_t /*dst_sz*/,\n                               unsigned * /*name_len*/,\n                               unsigned * /*val_len*/) {\n  return -1;\n}\nstatic int qmin_dec_stream_done(struct qmin_dec * /*dec*/,\n                                unsigned /*stream_id*/) {\n  return -1;\n}\nstatic void qmin_dec_destroy(struct qmin_dec * /*dec*/) {\n}\n}\n#endif\n\nstatic unsigned s_seq;\n\nTAILQ_HEAD(stream_chunks_head, stream_chunk);\n\nstruct stream_chunk {\n  TAILQ_ENTRY(stream_chunk) sc_next;\n  size_t sc_off;\n  size_t sc_sz;\n  unsigned char sc_buf[0];\n};\n\nstruct stream {\n  struct stream_chunks_head sm_chunks;\n  size_t sm_read_off;\n};\n\nnamespace proxygen::compress {\nclass QMINScheme : public CompressionScheme {\n public:\n  static struct stream_chunk *stream_chunk_new(size_t off,\n                                               const void *buf,\n                                               size_t bufsz) {\n    struct stream_chunk *chunk =\n        (struct stream_chunk *)malloc(sizeof(*chunk) + bufsz);\n    assert(chunk);\n    chunk->sc_off = off;\n    chunk->sc_sz = bufsz;\n    memcpy(chunk->sc_buf, buf, bufsz);\n    return chunk;\n  }\n\n  static void insert_chunk(struct stream *stream,\n                           struct stream_chunk *new_chunk) {\n    struct stream_chunk *chunk;\n    TAILQ_FOREACH(chunk, &stream->sm_chunks, sc_next)\n    if (chunk->sc_off > new_chunk->sc_off) {\n      TAILQ_INSERT_BEFORE(chunk, new_chunk, sc_next);\n      return;\n    }\n    TAILQ_INSERT_TAIL(&stream->sm_chunks, new_chunk, sc_next);\n  }\n\n  static struct stream_chunk *maybe_pop_chunk(struct stream *stream) {\n    struct stream_chunk *chunk = TAILQ_FIRST(&stream->sm_chunks);\n    if (chunk && chunk->sc_off == stream->sm_read_off) {\n      TAILQ_REMOVE(&stream->sm_chunks, chunk, sc_next);\n      stream->sm_read_off += chunk->sc_sz;\n      return chunk;\n    } else {\n      return nullptr;\n    }\n  }\n\n  explicit QMINScheme(CompressionSimulator *sim, uint32_t /*tableSize*/)\n      : CompressionScheme(sim) {\n    // TODO: set table size?\n    qms_ctl[0].out.qco_write = write_enc2dec;\n    qms_ctl[0].write_off = 0;\n    qms_ctl[0].sz = 0;\n    qms_ctl[1].out.qco_write = write_dec2enc;\n    qms_ctl[1].write_off = 0;\n    qms_ctl[1].sz = 0;\n\n    qms_idstr = (char *)malloc(8);\n    sprintf(qms_idstr, \"%u\", s_seq++);\n\n    qms_enc = qmin_enc_new(QSIDE_CLIENT, 4 * 1024, &qms_ctl[0].out, qms_idstr);\n    qms_dec = qmin_dec_new(QSIDE_SERVER, 4 * 1024, &qms_ctl[1].out, qms_idstr);\n\n    qms_streams = (struct stream *)calloc(2, sizeof(qms_streams[0]));\n    TAILQ_INIT(&qms_streams[0].sm_chunks);\n    TAILQ_INIT(&qms_streams[1].sm_chunks);\n\n    qms_next_stream_id_to_encode = 1;\n  }\n\n  ~QMINScheme() override {\n    free(qms_streams);\n    qmin_enc_destroy(qms_enc);\n    qmin_dec_destroy(qms_dec);\n    free(qms_idstr);\n  }\n\n  /* QMIN Ack carries QMM_STREAM_DONE and QMM_ACK_FLUSH messages from decoder\n   * to the encoder.\n   */\n  struct QMINAck : public CompressionScheme::Ack {\n    explicit QMINAck(size_t off, const void *buf, size_t bufsz) {\n      qma_off = off;\n      qma_sz = bufsz;\n      memcpy(qma_buf, buf, bufsz);\n    }\n\n    size_t qma_off;\n    size_t qma_sz;\n    unsigned char qma_buf[0x1000];\n  };\n\n  std::unique_ptr<Ack> getAck(uint16_t /*seqn*/) override {\n    if (qms_ctl[1].sz) {\n      auto ack = std::make_unique<QMINAck>(\n          qms_ctl[1].write_off, qms_ctl[1].buf, qms_ctl[1].sz);\n      VLOG(4) << \"sent ACK for instance \" << qms_idstr\n              << \" off: \" << qms_ctl[1].write_off << \"; sz: \" << qms_ctl[1].sz;\n      qms_ctl[1].write_off += qms_ctl[1].sz;\n      qms_ctl[1].sz = 0;\n      return std::move(ack);\n    } else {\n      assert(0);\n      return nullptr;\n    }\n  }\n\n  void recvAck(std::unique_ptr<Ack> generic_ack) override {\n    struct stream_chunk *chunk;\n\n    CHECK(generic_ack);\n    auto ack = dynamic_cast<QMINAck *>(generic_ack.get());\n    CHECK_NOTNULL(ack);\n\n    VLOG(4) << \"received ACK for instance \" << qms_idstr\n            << \" off: \" << ack->qma_off << \"; sz: \" << ack->qma_sz;\n\n    chunk = stream_chunk_new(ack->qma_off, ack->qma_buf, ack->qma_sz);\n    insert_chunk(&qms_streams[0], chunk);\n\n    while ((chunk = maybe_pop_chunk(&qms_streams[0]))) {\n      ssize_t nread;\n      nread = qmin_enc_cmds_in(qms_enc, chunk->sc_buf, chunk->sc_sz);\n      if (nread < 0 || (size_t)nread != chunk->sc_sz) {\n        VLOG(1) << \"error: qmin_enc_cmds_in failed\";\n        assert(0);\n      }\n      free(chunk);\n    }\n  }\n\n  std::pair<FrameFlags, std::unique_ptr<folly::IOBuf>> encode(\n      bool /*newPacket*/,\n      std::vector<compress::Header> allHeaders,\n      SimStats &stats) override {\n    const size_t max_ctl = 0x1000;\n    const size_t max_comp = 0x1000;\n    unsigned char outbuf[max_ctl + max_comp];\n    unsigned char *const comp = outbuf + max_ctl;\n    size_t nw, comp_sz;\n    enum qmin_encode_status qes;\n    FrameFlags flags;\n\n    qms_ctl[0].out.qco_ctx = this;\n    comp_sz = 0;\n\n    for (const auto header : allHeaders) {\n      std::string name{header.name->c_str()};\n      folly::toLowerAscii(name);\n      qes = qmin_enc_encode(qms_enc,\n                            qms_next_stream_id_to_encode,\n                            name.c_str(),\n                            name.length(),\n                            header.value->c_str(),\n                            header.value->length(),\n                            QIT_YES,\n                            comp + comp_sz,\n                            max_comp - comp_sz,\n                            &nw);\n      switch (qes) {\n        case QES_OK:\n          /* 2 is a magic number added to the uncompressed size by the other\n           * encoder.  We follow suit to make the numbers match.\n           */\n          stats.uncompressed += name.length() + header.value->length() + 2;\n          stats.compressed += nw;\n          comp_sz += nw;\n          break;\n        case QES_NOBUFS:\n          VLOG(1) << \"compressed header does not fit into temporary \"\n                     \"output buffer\";\n          return {flags, nullptr};\n        case QES_ERR:\n          VLOG(1) << \"error: \" << strerror(errno);\n          assert(0);\n          return {flags, nullptr};\n      }\n    }\n\n    {\n      size_t sz;\n      char *state = qmin_enc_to_str(qms_enc, &sz);\n      VLOG(4) << \"encoder state: \" << state;\n      free(state);\n    }\n\n    if (0 != qmin_enc_end_stream_headers(qms_enc)) {\n      VLOG(1) << \"error: qmin_enc_end_stream_headers failed\";\n      assert(0);\n    }\n\n    /* Prepend control message and its size: */\n    size_t ctl_msg_sz = qms_ctl[0].sz;\n    qms_ctl[0].sz = 0;\n    size_t ctl_msg_sz_with_off;\n    if (ctl_msg_sz) {\n      memcpy(outbuf + max_ctl - ctl_msg_sz, qms_ctl[0].buf, ctl_msg_sz);\n      memcpy(outbuf + max_ctl - ctl_msg_sz - sizeof(qms_ctl[0].write_off),\n             &qms_ctl[0].write_off,\n             sizeof(qms_ctl[0].write_off));\n      qms_ctl[0].write_off += ctl_msg_sz;\n      ctl_msg_sz_with_off = ctl_msg_sz + sizeof(qms_ctl[0].write_off);\n    } else {\n      ctl_msg_sz_with_off = 0;\n    }\n    memcpy(outbuf + max_ctl - ctl_msg_sz_with_off - sizeof(ctl_msg_sz),\n           &ctl_msg_sz,\n           sizeof(ctl_msg_sz));\n\n    stats.compressed += ctl_msg_sz;\n\n    /* Prepend Stream ID: */\n    memcpy(outbuf + max_ctl - ctl_msg_sz_with_off - sizeof(ctl_msg_sz) -\n               sizeof(uint32_t),\n           &qms_next_stream_id_to_encode,\n           sizeof(qms_next_stream_id_to_encode));\n\n    qms_next_stream_id_to_encode += 2;\n    flags.allowOOO = true;\n    return {\n        flags,\n        folly::IOBuf::copyBuffer(outbuf + max_ctl - ctl_msg_sz_with_off -\n                                     sizeof(ctl_msg_sz) - sizeof(uint32_t),\n                                 comp_sz + ctl_msg_sz_with_off +\n                                     sizeof(ctl_msg_sz) + sizeof(uint32_t))};\n  }\n\n  void decode(FrameFlags,\n              std::unique_ptr<folly::IOBuf> encodedReq,\n              SimStats &,\n              SimStreamingCallback &callback) override {\n    folly::io::Cursor cursor(encodedReq.get());\n    const unsigned char *buf;\n    ssize_t nread;\n    size_t ctl_sz, stream_off;\n    char outbuf[0x1000];\n    unsigned name_len, val_len;\n    unsigned decoded_size = 0;\n    uint32_t stream_id;\n\n    qms_ctl[1].out.qco_ctx = this;\n\n    /* Read Stream ID: */\n    buf = cursor.data();\n    memcpy(&stream_id, buf, sizeof(uint32_t));\n    encodedReq->trimStart(sizeof(uint32_t));\n\n    /* Read size of control messages */\n    buf = cursor.data();\n    memcpy(&ctl_sz, buf, sizeof(ctl_sz));\n    encodedReq->trimStart(sizeof(ctl_sz));\n\n    /* Feed control messages to the decoder: */\n    if (ctl_sz) {\n      struct stream_chunk *chunk;\n\n      /* Read stream offset: */\n      buf = cursor.data();\n      memcpy(&stream_off, buf, sizeof(stream_off));\n      encodedReq->trimStart(sizeof(stream_off));\n\n      buf = cursor.data();\n      chunk = stream_chunk_new(stream_off, buf, ctl_sz);\n      encodedReq->trimStart(ctl_sz);\n\n      insert_chunk(&qms_streams[1], chunk);\n\n      while ((chunk = maybe_pop_chunk(&qms_streams[1]))) {\n        nread = qmin_dec_cmds_in(qms_dec, chunk->sc_buf, chunk->sc_sz);\n        if (nread < 0 || (size_t)nread != chunk->sc_sz) {\n          VLOG(1) << \"error: qmin_dec_cmds_in failed\";\n          assert(0);\n        }\n        free(chunk);\n      }\n    }\n\n    buf = cursor.data();\n    const unsigned char *const end = buf + cursor.length();\n\n    while (buf < end) {\n      nread = qmin_dec_decode(\n          qms_dec, buf, end - buf, outbuf, sizeof(outbuf), &name_len, &val_len);\n      if (nread < 0) {\n        VLOG(1) << \"error: decoder failed!\";\n        assert(0);\n        return;\n      }\n      assert(nread);\n      buf += nread;\n      decoded_size += name_len + val_len;\n      std::string name{outbuf, name_len};\n      std::string value{outbuf + name_len, val_len};\n      callback.onHeader(HPACKHeaderName(folly::StringPiece(name)), value);\n    }\n\n    if (0 != qmin_dec_stream_done(qms_dec, stream_id)) {\n      assert(0);\n      VLOG(1) << \"error: qmin_dec_stream_done failed\";\n    }\n\n    proxygen::HTTPHeaderSize sz;\n    sz.compressed = encodedReq->computeChainDataLength();\n    sz.uncompressed = decoded_size;\n    callback.onHeadersComplete(sz, true);\n  }\n\n  [[nodiscard]] uint32_t getHolBlockCount() const override {\n    return 0;\n  }\n\n  void runLoopCallback() noexcept override {\n    CompressionScheme::runLoopCallback();\n  }\n\n  void write_ctl_msg(const void *buf, size_t sz, unsigned idx) {\n    size_t avail = sizeof(qms_ctl[idx].buf) - qms_ctl[idx].sz;\n    assert(avail >= sz);\n    if (avail < sz) {\n      VLOG(1) << \"Truncating control message from \" << sz << \" to \" << avail\n              << \"bytes\";\n      sz = avail;\n    }\n    memcpy(qms_ctl[idx].buf + qms_ctl[idx].sz, buf, sz);\n    qms_ctl[idx].sz += sz;\n    VLOG(4) << \"Wrote \" << sz << \" bytes to control channel\";\n  }\n\n  static void write_enc2dec(void *ctx, const void *buf, size_t sz) {\n    QMINScheme *const qms = (QMINScheme *)ctx;\n    qms->write_ctl_msg(buf, sz, 0);\n  }\n\n  static void write_dec2enc(void *ctx, const void *buf, size_t sz) {\n    QMINScheme *const qms = (QMINScheme *)ctx;\n    qms->write_ctl_msg(buf, sz, 1);\n  }\n\n  char *qms_idstr;\n\n  struct qmin_enc *qms_enc;\n  struct qmin_dec *qms_dec;\n\n  /* Each call to `encode' is interpreted as a header block for a new\n   * stream.\n   */\n  unsigned qms_next_stream_id_to_encode;\n\n  /* 0: decoder-to-encoder; 1: encoder-to-decoder */\n  struct stream *qms_streams;\n\n  struct {\n    struct qmin_ctl_out out;\n    size_t write_off;\n    size_t sz;\n    unsigned char buf[0x1000];\n  } qms_ctl[2]; /* 0: enc-to-dec; 1: dec-to-enc */\n};\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/QPACKScheme.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/NoPathIndexingStrategy.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/experimental/simulator/CompressionScheme.h>\n\nnamespace proxygen::compress {\n\nclass QPACKScheme : public CompressionScheme {\n public:\n  explicit QPACKScheme(CompressionSimulator* sim,\n                       uint32_t tableSize,\n                       uint32_t maxBlocking)\n      : CompressionScheme(sim) {\n    client_.setHeaderIndexingStrategy(NoPathIndexingStrategy::getInstance());\n    server_.setHeaderIndexingStrategy(NoPathIndexingStrategy::getInstance());\n    client_.setEncoderHeaderTableSize(tableSize);\n    server_.setDecoderHeaderTableMaxSize(tableSize);\n    client_.setMaxVulnerable(maxBlocking);\n    server_.setMaxBlocking(maxBlocking);\n  }\n\n  ~QPACKScheme() override {\n    CHECK_EQ(server_.getQueuedBytes(), 0);\n  }\n\n  struct QPACKAck : public CompressionScheme::Ack {\n    explicit QPACKAck(uint16_t n,\n                      uint16_t an,\n                      std::unique_ptr<folly::IOBuf> hAck,\n                      std::unique_ptr<folly::IOBuf> cAck)\n        : seqn(n),\n          ackSeqn(an),\n          headerAck(std::move(hAck)),\n          controlAck(std::move(cAck)) {\n    }\n    uint16_t seqn;\n    uint16_t ackSeqn;\n    std::unique_ptr<folly::IOBuf> headerAck;\n    std::unique_ptr<folly::IOBuf> controlAck;\n  };\n\n  std::unique_ptr<Ack> getAck(uint16_t seqn) override {\n    VLOG(4) << \"Sending ack for seqn=\" << seqn;\n    auto res = std::make_unique<QPACKAck>(seqn,\n                                          sendAck_++,\n                                          server_.encodeHeaderAck(seqn),\n                                          server_.encodeInsertCountInc());\n    return std::move(res);\n  }\n  void recvAck(std::unique_ptr<Ack> ack) override {\n    CHECK(ack);\n    auto qpackAck = dynamic_cast<QPACKAck*>(ack.get());\n    CHECK_NOTNULL(qpackAck);\n    VLOG(4) << \"Received ack for seqn=\" << qpackAck->seqn;\n    CHECK(qpackAck->headerAck);\n    if (qpackAck->controlAck) {\n      qpackAck->headerAck->prependChain(std::move(qpackAck->controlAck));\n    }\n    // The decoder stream must be processed in order\n    acks_.emplace(qpackAck->ackSeqn, std::move(qpackAck->headerAck));\n    do {\n      auto it = acks_.begin();\n      if (it->first != recvAck_) {\n        break;\n      }\n      CHECK_EQ(client_.decodeDecoderStream(std::move(it->second)),\n               HPACK::DecodeError::NONE);\n      recvAck_++;\n      acks_.erase(it);\n    } while (!acks_.empty());\n  }\n\n  std::pair<FrameFlags, std::unique_ptr<folly::IOBuf>> encode(\n      bool /*newPacket*/,\n      std::vector<compress::Header> allHeaders,\n      SimStats& stats) override {\n    index++;\n    auto result = client_.encode(allHeaders, index);\n    uint16_t len = 0;\n    folly::IOBufQueue queue;\n    static const uint32_t growth = 1400; // chosen arbitrarily\n    folly::io::QueueAppender cursor(&queue, growth);\n    if (result.control) {\n      VLOG(5) << \"Writing encodeControlIndex_=\" << encodeControlIndex_;\n      len = result.control->computeChainDataLength();\n      cursor.writeBE<uint16_t>(len);\n      cursor.writeBE<uint16_t>(encodeControlIndex_++);\n      cursor.insert(std::move(result.control));\n      // Don't count the framing against the compression ratio, for now\n      // stats.compressed += 3 * sizeof(uint16_t);\n    } else {\n      cursor.writeBE<uint16_t>(static_cast<uint16_t>(0));\n    }\n    if (result.stream) {\n      len = result.stream->computeChainDataLength();\n    }\n    cursor.writeBE<uint16_t>(static_cast<uint16_t>(index));\n    cursor.writeBE<uint16_t>(len);\n    cursor.insert(std::move(result.stream));\n    stats.uncompressed += client_.getEncodedSize().uncompressed;\n    stats.compressed += client_.getEncodedSize().compressed;\n    // OOO is allowed if there has not been an eviction\n    FrameFlags flags(false, false);\n    return {flags, queue.move()};\n  }\n\n  void decode(FrameFlags flags,\n              std::unique_ptr<folly::IOBuf> encodedReq,\n              SimStats& stats,\n              SimStreamingCallback& callback) override {\n    folly::io::Cursor cursor(encodedReq.get());\n    auto toTrim = sizeof(uint16_t) * 3;\n    auto len = cursor.readBE<uint16_t>();\n    if (len > 0) {\n      // check decode result\n      auto controlIndex = cursor.readBE<uint16_t>();\n      toTrim += sizeof(uint16_t);\n      std::unique_ptr<folly::IOBuf> control;\n      cursor.clone(control, len);\n      if (controlIndex == decodeControlIndex_) {\n        // next expected control block, decode\n        VLOG(5) << \"decode controlIndex=\" << controlIndex;\n        server_.decodeEncoderStream(std::move(control));\n        decodeControlIndex_++;\n        while (!controlQueue_.empty() &&\n               controlQueue_.begin()->first == decodeControlIndex_) {\n          // drain the queue\n          VLOG(5) << \"decode controlIndex=\" << controlQueue_.begin()->first;\n          auto it = controlQueue_.begin();\n          server_.decodeEncoderStream(std::move(it->second));\n          decodeControlIndex_++;\n          controlQueue_.erase(it);\n        }\n      } else {\n        // out of order control block, queue it\n        controlQueue_.emplace(controlIndex, std::move(control));\n      }\n      toTrim += len;\n    }\n    auto seqn = cursor.readBE<uint16_t>();\n    callback.seqn = seqn;\n    VLOG(1) << \"Decoding request=\" << callback.requestIndex\n            << \" header seqn=\" << seqn\n            << \" allowOOO=\" << uint32_t(flags.allowOOO);\n    len = cursor.readBE<uint16_t>();\n    folly::IOBufQueue queue;\n    queue.append(std::move(encodedReq));\n    queue.trimStart(toTrim);\n    server_.decodeStreaming(seqn, queue.move(), len, &callback);\n    callback.maybeMarkHolDelay();\n    if (server_.getQueuedBytes() > stats.maxQueueBufferBytes) {\n      stats.maxQueueBufferBytes = server_.getQueuedBytes();\n    }\n  }\n\n  uint32_t getHolBlockCount() const override {\n    return server_.getHolBlockCount();\n  }\n\n  QPACKCodec client_;\n  QPACKCodec server_;\n  std::map<uint16_t, std::unique_ptr<folly::IOBuf>> controlQueue_;\n  uint16_t encodeControlIndex_{0};\n  uint16_t decodeControlIndex_{0};\n  std::map<uint16_t, std::unique_ptr<folly::IOBuf>> acks_;\n  uint16_t sendAck_{1};\n  uint16_t recvAck_{1};\n};\n\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/experimental/simulator/SimStreamingCallback.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/HeaderConstants.h>\n#include <proxygen/lib/http/codec/compress/HPACKStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen::compress {\nclass SimStreamingCallback : public HPACK::StreamingCallback {\n public:\n  SimStreamingCallback(uint16_t index,\n                       std::function<void(std::chrono::milliseconds)> cb,\n                       bool isP = false)\n      : requestIndex(index), headersCompleteCb(cb), isPublic(isP) {\n  }\n\n  SimStreamingCallback(SimStreamingCallback&& goner) noexcept {\n    std::swap(msg, goner.msg);\n    requestIndex = goner.requestIndex;\n    seqn = goner.seqn;\n    error = goner.error;\n    std::swap(headersCompleteCb, goner.headersCompleteCb);\n  }\n\n  void onHeader(const HPACKHeaderName& hname,\n                const folly::fbstring& value) override {\n    std::string name = hname.get();\n    if (name[0] == ':' && !isPublic) {\n      if (name == headers::kMethod) {\n        msg.setMethod(value);\n      } else if (name == headers::kScheme) {\n        if (value == headers::kHttps) {\n          msg.setSecure(true);\n        }\n      } else if (name == headers::kAuthority) {\n        msg.getHeaders().add(HTTP_HEADER_HOST, value.toStdString());\n      } else if (name == headers::kPath) {\n        msg.setURL(value.toStdString());\n      } else if (name == headers::kStatus) {\n        msg.setStatusCode(folly::to<uint16_t>(value.toStdString()));\n      } else {\n        DCHECK(false) << \"Bad header name=\" << name << \" value=\" << value;\n      }\n    } else {\n      msg.getHeaders().add(name, value.toStdString());\n    }\n  }\n\n  void onHeadersComplete(HTTPHeaderSize, bool ack) override {\n    auto combinedCookie = msg.getHeaders().combine(HTTP_HEADER_COOKIE, \"; \");\n    if (!combinedCookie.empty()) {\n      msg.getHeaders().set(HTTP_HEADER_COOKIE, combinedCookie);\n    }\n    std::chrono::milliseconds holDelay(0);\n    if (holStart != TimeUtil::getZeroTimePoint()) {\n      holDelay = millisecondsSince(holStart);\n    }\n    acknowledge = ack;\n    complete = true;\n    if (headersCompleteCb) {\n      headersCompleteCb(holDelay);\n    }\n  }\n\n  void onDecodeError(HPACK::DecodeError decodeError) override {\n    error = decodeError;\n    DCHECK(false) << \"Unexpected error in simulator\";\n  }\n\n  folly::Expected<proxygen::HTTPMessage*, HPACK::DecodeError> getResult() {\n    if (error == HPACK::DecodeError::NONE) {\n      return &msg;\n    } else {\n      return folly::makeUnexpected(error);\n    }\n  }\n\n  void maybeMarkHolDelay() {\n    if (!complete) {\n      holStart = getCurrentTime();\n    }\n  }\n\n  // Global index (across all domains)\n  uint16_t requestIndex{0};\n  // Per domain request sequence number\n  uint16_t seqn{0};\n  HPACK::DecodeError error{HPACK::DecodeError::NONE};\n  proxygen::HTTPMessage msg;\n  std::function<void(std::chrono::milliseconds)> headersCompleteCb;\n  TimePoint holStart{TimeUtil::getZeroTimePoint()};\n  bool complete{false};\n  bool isPublic{false};\n  bool acknowledge{false};\n};\n\n} // namespace proxygen::compress\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\nadd_library(hpacktestutils TestUtil.cpp)\ntarget_include_directories(\n    hpacktestutils PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIR}\n)\ntarget_compile_options(\n    hpacktestutils PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(hpacktestutils PUBLIC proxygen)\n\nproxygen_add_test(TARGET HPACKTests\n  SOURCES\n    HeaderPieceTests.cpp\n    HeaderTableTests.cpp\n    HTTPArchive.cpp\n    HPACKBufferTests.cpp\n    HPACKCodecTests.cpp\n    HPACKContextTests.cpp\n    HPACKHeaderTests.cpp\n    HuffmanTests.cpp\n    LoggingTests.cpp\n    QPACKCodecTests.cpp\n    QPACKContextTests.cpp\n    QPACKHeaderTableTests.cpp\n    RFCExamplesTests.cpp\n  DEPENDS\n    hpacktestutils\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/EncoderBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Benchmark.h>\n#include <folly/String.h>\n#include <iostream>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <string>\n\nusing namespace proxygen;\n\nconst HTTPHeaders& getHeaders() {\n  static bool initHeaders = true;\n  static HTTPHeaders headers;\n  if (initHeaders) {\n    initHeaders = false;\n    for (std::string line; std::getline(std::cin, line, '\\n');) {\n\n      std::vector<folly::StringPiece> pieces;\n      folly::split(' ', line, pieces);\n      CHECK_EQ(pieces[3][0], 'n');\n      std::string name(pieces[3].begin() + 2, pieces[3].size() - 2);\n      auto valueOff = pieces[3].end() + 1 - line.data();\n      folly::StringPiece valuePiece(line.data() + valueOff,\n                                    line.size() - valueOff);\n      CHECK_EQ(valuePiece[0], 'v');\n      std::string value(valuePiece.begin() + 2, valuePiece.size() - 2);\n      headers.add(name, value);\n    }\n  }\n  return headers;\n}\n\nvoid encoderBenchmark(uint32_t iters, bool huffman, uint32_t tableSize) {\n  const HTTPHeaders* headers = nullptr;\n  BENCHMARK_SUSPEND {\n    headers = &getHeaders();\n  }\n  uint64_t compressed = 0;\n  for (auto i = 0u; i < iters; i++) {\n    HPACKEncoder encoder(huffman, tableSize);\n    folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n    bool first = true;\n    headers->forEachWithCode([&](HTTPHeaderCode code,\n                                 const std::string& name,\n                                 const std::string& value) {\n      if (code == HTTP_HEADER_COLON_METHOD) {\n        if (first) {\n          first = false;\n        } else {\n          encoder.completeEncode();\n          compressed += writeBuf.chainLength();\n          writeBuf.move();\n        }\n        encoder.startEncode(writeBuf);\n      }\n      if (code == HTTP_HEADER_OTHER) {\n        encoder.encodeHeader(name, value);\n      } else {\n        encoder.encodeHeader(code, value);\n      }\n    });\n\n    compressed += writeBuf.chainLength();\n    encoder.completeEncode();\n  }\n  LOG(INFO) << \"compressed=\" << compressed / iters;\n}\n\nBENCHMARK(EncoderHuffman4096, iters) {\n  encoderBenchmark(iters, true, 4096);\n}\n\nBENCHMARK(EncoderNoHuffman4096, iters) {\n  encoderBenchmark(iters, false, 4096);\n}\n\nBENCHMARK(EncoderHuffman0, iters) {\n  encoderBenchmark(iters, true, 0);\n}\n\nBENCHMARK(EncoderNoHuffman0, iters) {\n  encoderBenchmark(iters, false, 0);\n}\n\nint main(int argc, char** argv) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HPACKBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Benchmark.h>\n#include <folly/Range.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n\n#include <algorithm>\n\nusing namespace std;\nusing namespace folly;\nusing namespace proxygen;\nusing proxygen::HPACKHeader;\n\nunique_ptr<IOBuf> encode(vector<HPACKHeader>& headers, HPACKEncoder& encoder) {\n  return encoder.encode(headers);\n}\n\nvoid encodeDecode(vector<HPACKHeader>& headers,\n                  HPACKEncoder& encoder,\n                  HPACKDecoder& decoder) {\n  unique_ptr<IOBuf> encoded = encode(headers, encoder);\n  CHECK(encoded);\n  TestStreamingCallback cb;\n  folly::io::Cursor c(encoded.get());\n  decoder.decodeStreaming(c, c.totalLength(), &cb);\n  CHECK(!cb.hasError());\n}\n\nvector<HPACKHeader> getHeaders() {\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\":authority\", \"www.facebook.com\");\n  headers.emplace_back(\":method\", \"GET\");\n  headers.emplace_back(\":path\", \"/graphql\");\n  headers.emplace_back(\":scheme\", \"https\");\n  headers.emplace_back(\n      \"user-agent\",\n      \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 \"\n      \"(KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36\");\n  headers.emplace_back(\"accept-encoding\", \"gzip, deflate, br\");\n  headers.emplace_back(\"accept-language\", \"en-US,en;q=0.8\");\n  headers.emplace_back(\n      \"accept\",\n      \"text/html,application/xhtml+xml,application/xml;q=0.9,image/\"\n      \"webp,image/apng,*/*;q=0.8\");\n  return headers;\n}\n\nnamespace {\nstatic vector<HPACKHeader> headers = getHeaders();\n}\n\nvoid encodeBench(int reencodes, int iters) {\n  for (int i = 0; i < iters; i++) {\n    HPACKEncoder encoder(true);\n    encode(headers, encoder);\n    for (int j = 0; j < reencodes; j++) {\n      encode(headers, encoder);\n    }\n  }\n}\n\nvoid encodeDecodeBench(int reencodes, int iters) {\n  for (int i = 0; i < iters; i++) {\n    HPACKEncoder encoder(true);\n    HPACKDecoder decoder;\n    encodeDecode(headers, encoder, decoder);\n    for (int j = 0; j < reencodes; j++) {\n      encodeDecode(headers, encoder, decoder);\n    }\n  }\n}\n\nBENCHMARK(Encode, iters) {\n  encodeBench(0, iters);\n}\n\nBENCHMARK(Encode1, iters) {\n  encodeBench(1, iters);\n}\n\nBENCHMARK(Encode2, iters) {\n  encodeBench(2, iters);\n}\n\nBENCHMARK(EncodeDecode, iters) {\n  encodeDecodeBench(0, iters);\n}\n\nBENCHMARK(EncodeDecode1, iters) {\n  encodeDecodeBench(1, iters);\n}\n\nBENCHMARK(EncodeDecode2, iters) {\n  encodeDecodeBench(2, iters);\n}\n\nint main(int argc, char** argv) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HPACKBufferTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n\nusing namespace folly::io;\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\nusing proxygen::HPACK::DecodeError;\n\nnamespace {\nconst uint32_t kMaxLiteralSize = 1 << 17;\n}\n\nclass HPACKBufferTests : public testing::Test {\n public:\n  /*\n   * most of the tests need a tiny slice of data, so we're setting up\n   * a queue with one IOBuf of 512 bytes in it\n   */\n  HPACKBufferTests() : encoder_(512), decoder_(cursor_, 0, kMaxLiteralSize) {\n  }\n\n protected:\n  // extracts the data from the encoder and setup pointers to it\n  void releaseData() {\n    releaseData(encoder_);\n  }\n\n  void releaseData(HPACKEncodeBuffer& encoder) {\n    buf_ = encoder.release();\n    if (buf_) {\n      data_ = buf_->data();\n    }\n  }\n\n  void resetDecoder() {\n    resetDecoder(buf_.get());\n  }\n\n  void resetDecoder(IOBuf* buf) {\n    cursor_.reset(buf);\n    decoder_.reset(cursor_);\n  }\n\n  Cursor cursor_{nullptr};\n  HPACKEncodeBuffer encoder_;\n  HPACKDecodeBuffer decoder_;\n  std::unique_ptr<IOBuf> buf_{nullptr};\n  const uint8_t* data_{nullptr};\n};\n\nTEST_F(HPACKBufferTests, EncodeInteger) {\n  // all these fit in one byte\n  EXPECT_EQ(encoder_.encodeInteger(7, 192, 6), 1);\n  // this one fits perfectly, but needs an additional 0 byte\n  EXPECT_EQ(encoder_.encodeInteger(7, 192, 3), 2);\n  EXPECT_EQ(encoder_.encodeInteger(255, 0, 8), 2);\n  releaseData();\n  EXPECT_EQ(buf_->length(), 5);\n  EXPECT_EQ(data_[0], 199); // 11000111\n  EXPECT_EQ(data_[1], 199); // 11000111\n  EXPECT_EQ(data_[2], 0);\n  EXPECT_EQ(data_[3], 255); // 11111111\n  EXPECT_EQ(data_[4], 0);\n\n  // multiple byte span\n  EXPECT_EQ(encoder_.encodeInteger(7, 192, 2), 2);\n  releaseData();\n  EXPECT_EQ(buf_->length(), 2);\n  EXPECT_EQ(data_[0], 195); // 11000011\n  EXPECT_EQ(data_[1], 4);   // 00000100\n\n  // the one from the spec - 1337\n  EXPECT_EQ(encoder_.encodeInteger(1337, 0, 5), 3);\n  releaseData();\n  EXPECT_EQ(buf_->length(), 3);\n  EXPECT_EQ(data_[0], 31);  // 00011111\n  EXPECT_EQ(data_[1], 154); // 10011010\n  EXPECT_EQ(data_[2], 10);  // 00001010\n}\n\nTEST_F(HPACKBufferTests, EncodePlainLiteral) {\n  string literal(\"accept-encoding\");\n  EXPECT_EQ(encoder_.encodeLiteral(literal), 16);\n  releaseData();\n  EXPECT_EQ(buf_->length(), 16);\n  EXPECT_EQ(data_[0], 15);\n  for (size_t i = 0; i < literal.size(); i++) {\n    EXPECT_EQ(data_[i + 1], literal[i]);\n  }\n}\n\nTEST_F(HPACKBufferTests, EncodePlainLiteralN) {\n  string literal(\"accept-encodin\"); // len=14\n  // length must fit in 4 bits, with room for 3 bit instruction\n  EXPECT_EQ(encoder_.encodeLiteral(0xE0, 4, literal), 15);\n  releaseData();\n  EXPECT_EQ(buf_->length(), 15);\n  EXPECT_EQ(data_[0], 0xE0 | 14);\n  for (size_t i = 0; i < literal.size(); i++) {\n    EXPECT_EQ(data_[i + 1], literal[i]);\n  }\n}\n\nTEST_F(HPACKBufferTests, EncodeHuffmanLiteral) {\n  string accept(\"accept-encoding\");\n  HPACKEncodeBuffer encoder(512, true);\n  uint32_t size = encoder.encodeLiteral(accept);\n  EXPECT_EQ(size, 12);\n  releaseData(encoder);\n  EXPECT_EQ(buf_->length(), 12);\n  EXPECT_EQ(data_[0], 0x80 | 11); // 128(huffman bit) | 11(length)\n  EXPECT_EQ(data_[11], 0x7f);\n}\n\nTEST_F(HPACKBufferTests, EncodeHuffmanLiteralN) {\n  string accept(\"accept-encoding\");\n  HPACKEncodeBuffer encoder(512, true);\n  uint32_t size = encoder.encodeLiteral(0xE0, 4, accept);\n  EXPECT_EQ(size, 12);\n  releaseData(encoder);\n  EXPECT_EQ(buf_->length(), 12);\n  EXPECT_EQ(data_[0], 0xE0 | 0x10 | 11); // instruction | huffman | length\n  EXPECT_EQ(data_[11], 0x7f);\n}\n\nTEST_F(HPACKBufferTests, DecodeSingleByte) {\n  buf_ = IOBuf::create(512);\n  uint8_t* wdata = buf_->writableData();\n  buf_->append(1);\n  // 6-bit prefix\n  *wdata = 67;\n  resetDecoder();\n  uint64_t integer;\n  CHECK_EQ(decoder_.decodeInteger(7, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 67);\n\n  resetDecoder();\n\n  CHECK_EQ(decoder_.decodeInteger(6, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 3);\n\n  // set a bit in the prefix - it should not affect the decoded value\n  *wdata = 195; // 195 = 128 + 67\n  resetDecoder();\n  CHECK_EQ(decoder_.decodeInteger(7, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 67);\n\n  // 8-bit prefix - the entire byte\n  resetDecoder();\n  CHECK_EQ(decoder_.decodeInteger(8, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 195);\n}\n\nTEST_F(HPACKBufferTests, DecodeMultiByte) {\n  buf_ = IOBuf::create(512);\n  uint8_t* wdata = buf_->writableData();\n  // edge case - max value in a 2-bit space\n  buf_->append(2);\n  wdata[0] = 67;\n  wdata[1] = 0;\n  resetDecoder();\n  uint64_t integer;\n  CHECK_EQ(decoder_.decodeInteger(2, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 3);\n  CHECK_EQ(decoder_.cursor().length(), 0);\n  // edge case - encode 130 = 127 + 3 on 2-bit prefix\n  wdata[0] = 3;\n  wdata[1] = 127;\n  resetDecoder();\n  CHECK_EQ(decoder_.decodeInteger(2, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 130);\n  CHECK_EQ(decoder_.cursor().length(), 0);\n  // edge case - encode 131 = 128 + 3\n  buf_->append(1);\n  wdata[0] = 3;\n  wdata[1] = 128;\n  wdata[2] = 1;\n  resetDecoder();\n  CHECK_EQ(decoder_.decodeInteger(2, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 131);\n  CHECK_EQ(decoder_.cursor().length(), 0);\n  // encode the value from the RFC example - 1337\n  wdata[0] = 31;\n  wdata[1] = 154;\n  wdata[2] = 10;\n  resetDecoder();\n  CHECK_EQ(decoder_.decodeInteger(5, integer), DecodeError::NONE);\n  CHECK_EQ(integer, 1337);\n  CHECK_EQ(decoder_.cursor().length(), 0);\n}\n\nTEST_F(HPACKBufferTests, DecodeIntegerError) {\n  buf_ = IOBuf::create(128);\n  resetDecoder();\n  // empty buffer\n  uint64_t integer;\n  CHECK_EQ(decoder_.decodeInteger(5, integer), DecodeError::BUFFER_UNDERFLOW);\n\n  // incomplete buffer\n  buf_->append(2);\n  uint8_t* wdata = buf_->writableData();\n  wdata[0] = 31;\n  wdata[1] = 154;\n  // wdata[2] = 10 missing\n  CHECK_EQ(decoder_.decodeInteger(5, integer), DecodeError::BUFFER_UNDERFLOW);\n}\n\nTEST_F(HPACKBufferTests, DecodeLiteralError) {\n  buf_ = IOBuf::create(128);\n\n  uint8_t* wdata = buf_->writableData();\n  buf_->append(3);\n  resetDecoder();\n  wdata[0] = 255; // size\n  wdata[1] = 'a';\n  wdata[2] = 'b';\n  folly::fbstring literal;\n  CHECK_EQ(decoder_.decodeLiteral(literal), DecodeError::BUFFER_UNDERFLOW);\n\n  resetDecoder();\n  // error decoding the size of the literal\n  wdata[0] = 0xFF;\n  wdata[1] = 0x80;\n  wdata[2] = 0x80;\n  EXPECT_EQ(decoder_.decodeLiteral(literal), DecodeError::BUFFER_UNDERFLOW);\n}\n\nTEST_F(HPACKBufferTests, DecodeLiteralMultiBuffer) {\n  auto buf1 = IOBuf::create(128);\n  auto buf2 = IOBuf::create(128);\n  // encode the size\n  // buf2 will not be entirely filled, to keep space for encoding the size\n  // without overflowing\n  uint32_t size = buf1->capacity() + buf2->capacity() - 10;\n  releaseData();\n  uint32_t sizeLen = encoder_.encodeInteger(size, 0, 7);\n  releaseData();\n  // copy the encoding of the size at the beginning\n  memcpy(buf1->writableData(), buf_->data(), sizeLen);\n  for (size_t i = sizeLen; i < buf1->capacity(); i++) {\n    buf1->writableData()[i] = 'x';\n  }\n  buf1->append(buf1->capacity());\n  for (size_t i = 0; i < buf2->capacity() - 10 + sizeLen; i++) {\n    buf2->writableData()[i] = 'y';\n  }\n  buf2->append(buf2->capacity() - 10 + sizeLen);\n  buf1->appendChain(std::move(buf2));\n  // decode\n  resetDecoder(buf1.get());\n  folly::fbstring literal;\n  EXPECT_EQ(decoder_.decodeLiteral(literal), DecodeError::NONE);\n  EXPECT_EQ(literal.size(), size);\n  EXPECT_EQ(literal[0], 'x');\n  EXPECT_EQ(literal[literal.size() - 1], 'y');\n}\n\nTEST_F(HPACKBufferTests, DecodeHuffmanLiteralMultiBuffer) {\n  // \"gzip\" fits perfectly in a 3 bytes block\n  std::array<uint8_t, 3> gzip{0x9b, 0xd9, 0xab};\n  auto buf1 = IOBuf::create(128);\n  auto buf2 = IOBuf::create(128);\n  // total size\n  uint32_t size = buf1->capacity() + buf2->capacity() - 10;\n  // it needs to fit a multiple of 3 blocks\n  size -= (size % 3);\n  // just in case we have some bytes left encoded\n  releaseData();\n  uint32_t sizeLen = encoder_.encodeInteger(size, 128, 7);\n  // extract the encoded size\n  releaseData();\n  memcpy(buf1->writableData(), buf_->data(), sizeLen);\n  // huffman index\n  uint32_t hi = 0;\n  for (size_t i = sizeLen; i < buf1->capacity(); i++) {\n    buf1->writableData()[i] = gzip[hi];\n    hi = (hi + 1) % 3;\n  }\n  buf1->append(buf1->capacity());\n  for (size_t i = 0; i < buf2->capacity() - 10 + sizeLen; i++) {\n    buf2->writableData()[i] = gzip[hi];\n    hi = (hi + 1) % 3;\n  }\n  buf2->append(buf2->capacity() - 10 + sizeLen);\n  buf1->appendChain(std::move(buf2));\n  // decode\n  resetDecoder(buf1.get());\n  folly::fbstring literal;\n  EXPECT_EQ(decoder_.decodeLiteral(literal), DecodeError::NONE);\n  EXPECT_EQ(literal.size(), 4 * (size / 3));\n  EXPECT_EQ(literal.find(\"gzip\"), 0);\n  EXPECT_EQ(literal.rfind(\"gzip\"), literal.size() - 4);\n}\n\nTEST_F(HPACKBufferTests, DecodeHuffmanLiteralN) {\n  // \"gzip\" fits perfectly in a 3 bytes block\n  std::array<uint8_t, 3> gzip{0x9b, 0xd9, 0xab};\n  uint32_t size = 3;\n  // just in case we have some bytes left encoded\n  releaseData();\n  encoder_.encodeInteger(size, 0x80 | 0x10, 4);\n  releaseData();\n  memcpy(buf_->writableData() + 1, gzip.data(), size);\n  buf_->append(size);\n  // decode\n  resetDecoder(buf_.get());\n  folly::fbstring literal;\n  EXPECT_EQ(decoder_.decodeLiteral(4, literal), DecodeError::NONE);\n  EXPECT_EQ(literal.size(), 4 * (size / 3));\n  EXPECT_EQ(literal.find(\"gzip\"), 0);\n  EXPECT_EQ(literal.rfind(\"gzip\"), literal.size() - 4);\n}\n\nTEST_F(HPACKBufferTests, DecodePlainLiteral) {\n  buf_ = IOBuf::create(512);\n  std::string gzip(\"gzip\");\n  folly::fbstring literal;\n  uint8_t* wdata = buf_->writableData();\n\n  buf_->append(1 + gzip.size());\n  wdata[0] = gzip.size();\n  memcpy(wdata + 1, gzip.c_str(), gzip.size());\n\n  resetDecoder();\n  decoder_.decodeLiteral(literal);\n  CHECK_EQ(literal, gzip);\n}\n\nTEST_F(HPACKBufferTests, DecodePlainLiteralN) {\n  buf_ = IOBuf::create(512);\n  std::string gzip(\"gzip\");\n  folly::fbstring literal;\n  uint8_t* wdata = buf_->writableData();\n\n  buf_->append(1 + gzip.size());\n  wdata[0] = 0xE0 | gzip.size(); // add a 3 bit instruction\n  memcpy(wdata + 1, gzip.c_str(), gzip.size());\n\n  resetDecoder();\n  decoder_.decodeLiteral(4, literal);\n  CHECK_EQ(literal, gzip);\n}\n\nTEST_F(HPACKBufferTests, IntegerEncodeDecode) {\n  HPACKEncodeBuffer encoder(512);\n  // first encode\n  uint32_t value = 145333;\n  encoder.encodeInteger(value, 128, 5);\n  releaseData(encoder);\n  resetDecoder();\n  EXPECT_EQ(decoder_.cursor().length(), 4);\n  // now decode\n  uint64_t integer;\n  EXPECT_EQ(decoder_.decodeInteger(5, integer), DecodeError::NONE);\n  EXPECT_EQ(integer, value);\n  EXPECT_EQ(decoder_.cursor().length(), 0);\n\n  // corner case\n  value = 63;\n  encoder.encodeInteger(value, 64, 6);\n  releaseData(encoder);\n  resetDecoder();\n  EXPECT_EQ(decoder_.decodeInteger(6, integer), DecodeError::NONE);\n  EXPECT_EQ(integer, value);\n}\n\n/**\n * really large integers\n */\nTEST_F(HPACKBufferTests, IntegerOverflow) {\n  uint64_t integer;\n  buf_ = IOBuf::create(128);\n  uint8_t* wdata = buf_->writableData();\n\n  // enough headroom for both cases\n  buf_->append(12);\n  // overflow the accumulated value\n  wdata[0] = 0xFF;\n  wdata[1] = 0xFF;\n  wdata[2] = 0xFF;\n  wdata[3] = 0xFF;\n  wdata[4] = 0xFF;\n  wdata[5] = 0xFF;\n  wdata[6] = 0xFF;\n  wdata[7] = 0xFF;\n  wdata[8] = 0xFF;\n  wdata[9] = 0xFF;\n  wdata[10] = 0x0F;\n  resetDecoder();\n  EXPECT_EQ(decoder_.decodeInteger(8, integer), DecodeError::INTEGER_OVERFLOW);\n\n  // overflow the factorizer\n  wdata[0] = 0xFF;\n  wdata[1] = 0x80;\n  wdata[2] = 0x80;\n  wdata[3] = 0x80;\n  wdata[4] = 0x80;\n  wdata[5] = 0x80;\n  wdata[6] = 0x80;\n  wdata[7] = 0x80;\n  wdata[8] = 0x80;\n  wdata[9] = 0x80;\n  wdata[10] = 0x80;\n  wdata[11] = 0x01;\n  resetDecoder();\n  EXPECT_EQ(decoder_.decodeInteger(8, integer), DecodeError::INTEGER_OVERFLOW);\n}\n\n/**\n * test that we're able to decode the max integer\n */\nTEST_F(HPACKBufferTests, IntegerMax) {\n  uint64_t hpackMax = std::numeric_limits<uint64_t>::max() - 1;\n  releaseData();\n  // encoding with all the bit prefixes\n  for (uint8_t bitprefix = 1; bitprefix <= 8; bitprefix++) {\n    encoder_.encodeInteger(hpackMax, 0, bitprefix);\n    // take the encoded data and shove it in the decoder\n    releaseData();\n    resetDecoder();\n    uint64_t integer = 0;\n    EXPECT_EQ(decoder_.decodeInteger(bitprefix, integer), DecodeError::NONE);\n    EXPECT_EQ(integer, hpackMax);\n  }\n}\n\n/**\n * making sure we're calling peek() before deferencing the first byte\n * to figure out if it's a huffman encoding or not\n */\nTEST_F(HPACKBufferTests, EmptyIobufLiteral) {\n  // construct an IOBuf chain made of 1 empty chain and a literal\n  unique_ptr<IOBuf> first = IOBuf::create(128);\n  // set a trap by setting first byte to 128, which signals Huffman encoding\n  first->writableData()[0] = 0x80;\n\n  HPACKEncodeBuffer encoder(128); // no huffman\n  folly::fbstring literal(\"randomheadervalue\");\n  encoder.encodeLiteral(literal);\n  first->appendChain(encoder.release());\n\n  uint32_t size = first->next()->length();\n  Cursor cursor(first.get());\n  HPACKDecodeBuffer decoder(cursor, size, kMaxLiteralSize);\n  folly::fbstring decoded;\n  decoder.decodeLiteral(decoded);\n\n  EXPECT_EQ(literal, decoded);\n}\n\n/**\n * the that we enforce a limit on the literal size\n */\nTEST_F(HPACKBufferTests, LargeLiteralError) {\n  uint32_t largeSize = 10 + kMaxLiteralSize;\n  // encode a large string\n  string largeLiteral;\n  largeLiteral.append(largeSize, 'x');\n  EXPECT_TRUE(encoder_.encodeLiteral(largeLiteral));\n  releaseData();\n  resetDecoder();\n  folly::fbstring decoded = \"\";\n  EXPECT_EQ(decoder_.decodeLiteral(decoded), DecodeError::LITERAL_TOO_LARGE);\n  EXPECT_EQ(decoded.size(), 0);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HPACKCodecTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Range.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/codec/compress/HPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/Header.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n#include <vector>\n\nusing namespace folly::io;\nusing namespace folly;\nusing namespace proxygen::compress;\nusing namespace proxygen::hpack;\nusing namespace proxygen;\nusing namespace std;\n\nbool isLowercase(StringPiece str) {\n  for (auto ch : str) {\n    if (isalpha(ch) && !islower(ch)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nnamespace {\n\nstruct DecodeResult {\n  compress::HeaderPieceList headers;\n  uint32_t bytesConsumed;\n};\n\nfolly::Expected<DecodeResult, HPACK::DecodeError> decode(\n    HPACKCodec& codec, Cursor& cursor, uint32_t length) noexcept {\n  TestStreamingCallback cb;\n  codec.decodeStreaming(cursor, length, &cb);\n  if (cb.hasError()) {\n    LOG(ERROR) << \"decoder state: \" << codec;\n    return folly::makeUnexpected(cb.error);\n  }\n  return DecodeResult{.headers = std::move(cb.getResult()->headers),\n                      .bytesConsumed = cb.getResult()->bytesConsumed};\n}\n\nfolly::Expected<DecodeResult, HPACK::DecodeError> encodeDecode(\n    HPACKCodec& encoder, HPACKCodec& decoder, vector<Header>&& headers) {\n  unique_ptr<IOBuf> encoded = encoder.encode(headers);\n  Cursor c(encoded.get());\n  return decode(decoder, c, c.totalLength());\n}\n\n} // namespace\n\nclass HPACKCodecTests : public testing::Test {\n protected:\n  HPACKCodec client{TransportDirection::UPSTREAM};\n  HPACKCodec server{TransportDirection::DOWNSTREAM};\n};\n\nTEST_F(HPACKCodecTests, Request) {\n  for (int i = 0; i < 3; i++) {\n    auto result = encodeDecode(client, server, basicHeaders());\n    EXPECT_TRUE(!result.hasError());\n    EXPECT_EQ(result->headers.size(), 12);\n  }\n}\n\nTEST_F(HPACKCodecTests, Response) {\n  vector<vector<string>> headers = {{\"content-length\", \"80\"},\n                                    {\"content-encoding\", \"gzip\"},\n                                    {\"x-fb-debug\", \"sdfgrwer\"}};\n  vector<Header> req = headersFromArray(headers);\n\n  for (int i = 0; i < 3; i++) {\n    auto result = encodeDecode(server, client, basicHeaders());\n    EXPECT_TRUE(!result.hasError());\n    EXPECT_EQ(result->headers.size(), 12);\n  }\n}\n\nTEST_F(HPACKCodecTests, Headroom) {\n  vector<Header> req = basicHeaders();\n\n  uint32_t headroom = 20;\n  client.setEncodeHeadroom(headroom);\n  unique_ptr<IOBuf> encodedReq = client.encode(req);\n  EXPECT_EQ(encodedReq->headroom(), headroom);\n  Cursor cursor(encodedReq.get());\n  auto result = decode(server, cursor, cursor.totalLength());\n  EXPECT_TRUE(!result.hasError());\n  EXPECT_EQ(result->headers.size(), 12);\n}\n\n/**\n * makes sure that the encoder will lowercase the header names\n */\nTEST_F(HPACKCodecTests, LowercasingHeaderNames) {\n  vector<vector<string>> headers = {{\"Content-Length\", \"80\"},\n                                    {\"Content-Encoding\", \"gzip\"},\n                                    {\"X-FB-Debug\", \"bleah\"}};\n  auto result = encodeDecode(server, client, headersFromArray(headers));\n  EXPECT_TRUE(!result.hasError());\n  auto& decoded = result->headers;\n  CHECK_EQ(decoded.size(), 6);\n  for (int i = 0; i < 6; i += 2) {\n    EXPECT_TRUE(isLowercase(decoded[i].str));\n  }\n}\n\n/**\n * make sure we mark multi-valued headers appropriately,\n * as expected by the SPDY codec.\n */\nTEST_F(HPACKCodecTests, MultivalueHeaders) {\n  vector<vector<string>> headers = {{\"Content-Length\", \"80\"},\n                                    {\"Content-Encoding\", \"gzip\"},\n                                    {\"X-FB-Dup\", \"bleah\"},\n                                    {\"X-FB-Dup\", \"hahaha\"}};\n  auto result = encodeDecode(server, client, headersFromArray(headers));\n  EXPECT_TRUE(!result.hasError());\n  auto& decoded = result->headers;\n  CHECK_EQ(decoded.size(), 8);\n  uint32_t count = 0;\n  for (int i = 0; i < 8; i += 2) {\n    if (decoded[i].str == \"x-fb-dup\") {\n      count++;\n    }\n  }\n  EXPECT_EQ(count, 2);\n}\n\n/**\n * test that we're propagating the error correctly in the decoder\n */\nTEST_F(HPACKCodecTests, DecodeError) {\n  vector<vector<string>> headers = {{\"Content-Length\", \"80\"}};\n  vector<Header> req = headersFromArray(headers);\n\n  unique_ptr<IOBuf> encodedReq = server.encode(req);\n  encodedReq->writableData()[0] = 0xFF;\n  Cursor cursor(encodedReq.get());\n\n  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);\n  client.setStats(&stats);\n  auto result = decode(client, cursor, cursor.totalLength());\n  // this means there was an error\n  EXPECT_TRUE(result.hasError());\n  EXPECT_EQ(result.error(), HPACK::DecodeError::INVALID_INDEX);\n  EXPECT_EQ(stats.errors, 1);\n  client.setStats(nullptr);\n}\n\n/**\n * testing that we're calling the stats callbacks appropriately\n */\nTEST_F(HPACKCodecTests, HeaderCodecStats) {\n  vector<vector<string>> headers = {\n      {\"Content-Length\", \"80\"},\n      {\"Content-Encoding\", \"gzip\"},\n      {\"X-FB-Debug\", \"eirtijvdgtccffkutnbttcgbfieghgev\"}};\n  vector<Header> resp = headersFromArray(headers);\n\n  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);\n  // encode\n  server.setStats(&stats);\n  unique_ptr<IOBuf> encodedResp = server.encode(resp);\n  EXPECT_EQ(stats.encodes, 1);\n  EXPECT_EQ(stats.decodes, 0);\n  EXPECT_EQ(stats.errors, 0);\n  EXPECT_GT(stats.encodedBytesCompr, 0);\n  EXPECT_GT(stats.encodedBytesComprBlock, 0);\n  EXPECT_GT(stats.encodedBytesUncompr, 0);\n  EXPECT_EQ(stats.decodedBytesCompr, 0);\n  EXPECT_EQ(stats.decodedBytesUncompr, 0);\n  server.setStats(nullptr);\n\n  // decode\n  Cursor cursor(encodedResp.get());\n  stats.reset();\n  client.setStats(&stats);\n  auto result = decode(client, cursor, cursor.totalLength());\n  EXPECT_TRUE(!result.hasError());\n  auto& decoded = result->headers;\n  CHECK_EQ(decoded.size(), 3 * 2);\n  EXPECT_EQ(stats.decodes, 1);\n  EXPECT_EQ(stats.encodes, 0);\n  EXPECT_GT(stats.decodedBytesCompr, 0);\n  EXPECT_GT(stats.decodedBytesUncompr, 0);\n  EXPECT_EQ(stats.encodedBytesCompr, 0);\n  EXPECT_EQ(stats.encodedBytesComprBlock, 0);\n  EXPECT_EQ(stats.encodedBytesUncompr, 0);\n  client.setStats(nullptr);\n}\n\n/**\n * check that we're enforcing the limit on total uncompressed size\n */\nTEST_F(HPACKCodecTests, UncompressedSizeLimit) {\n  vector<vector<string>> headers;\n  // generate lots of small headers\n  string contentLength = \"Content-Length\";\n  for (int i = 0; i < 10000; i++) {\n    auto value = folly::to<string>(i);\n    vector<string> header = {contentLength, value};\n    headers.push_back(header);\n  }\n  auto result = encodeDecode(server, client, headersFromArray(headers));\n  EXPECT_TRUE(result.hasError());\n  EXPECT_EQ(result.error(), HPACK::DecodeError::HEADERS_TOO_LARGE);\n}\n\n/**\n * Size limit stats\n */\nTEST_F(HPACKCodecTests, SizeLimitStats) {\n  vector<vector<string>> headers;\n  // generate lots of small headers\n  string contentLength = \"Content-Length\";\n  for (int i = 0; i < 10000; i++) {\n    auto value = folly::to<string>(i);\n    vector<string> header = {contentLength, value};\n    headers.push_back(header);\n  }\n  auto encHeaders = headersFromArray(headers);\n  unique_ptr<IOBuf> encoded = client.encode(encHeaders);\n  Cursor cursor(encoded.get());\n  TestStreamingCallback cb;\n  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);\n  server.setStats(&stats);\n  server.decodeStreaming(cursor, cursor.totalLength(), &cb);\n  auto result = cb.getResult();\n  EXPECT_TRUE(result.hasError());\n  EXPECT_EQ(result.error(), HPACK::DecodeError::HEADERS_TOO_LARGE);\n  EXPECT_EQ(stats.tooLarge, 1);\n}\n\nTEST_F(HPACKCodecTests, DefaultHeaderIndexingStrategy) {\n  vector<Header> headers = basicHeaders();\n  size_t headersIndexableSize = 4;\n\n  // Control equality check; all basic headers were indexed\n  client.encode(headers);\n  EXPECT_EQ(client.getCompressionInfo().egress.headersStored_,\n            headersIndexableSize);\n\n  // Verify HPACKCodec by default utilizes the default header indexing strategy\n  // by ensuring that it does not index any of the added headers below\n  // The below is quite verbose but that is because Header constructors use\n  // references and so we need the actual strings to not go out of scope\n  vector<vector<string>> noIndexHeadersStrings = {\n      {\"content-length\", \"80\"},\n      {\":path\", \"/some/random/file.jpg\"},\n      {\":path\", \"checks_for_=\"},\n      {\"if-modified-since\", \"some_value\"},\n      {\"last-modified\", \"some_value\"}};\n  vector<Header> noIndexHeaders = headersFromArray(noIndexHeadersStrings);\n  headers.insert(headers.end(), noIndexHeaders.begin(), noIndexHeaders.end());\n  HPACKCodec testCodec{TransportDirection::UPSTREAM};\n  testCodec.encode(headers);\n  EXPECT_EQ(testCodec.getCompressionInfo().egress.headersStored_,\n            headersIndexableSize);\n}\n\nnamespace {\nclass TestIndexingStrat : public HeaderIndexingStrategy {\n public:\n  [[nodiscard]] bool indexHeader(const HPACKHeaderName&,\n                                 folly::StringPiece,\n                                 bool) const override {\n    return false;\n  }\n\n  [[nodiscard]] std::pair<uint32_t, uint32_t> getHuffmanLimits()\n      const override {\n    // Too small for \"gzip\", too big for \"www.facebook.com\"\n    return {5, 10};\n  }\n};\n} // namespace\n\nTEST_F(HPACKCodecTests, CustomHeaderIndexingStrategy) {\n  vector<Header> headers = basicHeaders();\n  size_t headersIndexableSize = 0;\n\n  TestIndexingStrat strat;\n  client.setHeaderIndexingStrategy(&strat);\n  auto buf = client.encode(headers);\n  // Nothing is indexed\n  EXPECT_EQ(client.getCompressionInfo().egress.headersStored_,\n            headersIndexableSize);\n  EXPECT_NE(memmem(buf->data(), buf->length(), \"gzip\", 4), nullptr);\n  EXPECT_NE(memmem(buf->data(), buf->length(), \"www.facebook.com\", 16),\n            nullptr);\n  EXPECT_EQ(memmem(buf->data(), buf->length(), \"/index.php\", 10), nullptr);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HPACKContextTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Conv.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HPACKContext.h>\n#include <proxygen/lib/http/codec/compress/HPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/http/codec/compress/QPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/QPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nclass HPACKContextTests : public testing::TestWithParam<bool> {};\n\nclass TestContext : public HPACKContext {\n\n public:\n  explicit TestContext(uint32_t tableSize) : HPACKContext(tableSize) {\n  }\n\n  void add(const HPACKHeader& header) {\n    table_.add(header.copy());\n  }\n};\n\nTEST_F(HPACKContextTests, GetIndex) {\n  HPACKContext context(HPACK::kTableSize);\n  HPACKHeader method(\":method\", \"POST\");\n\n  // this will get it from the static table\n  CHECK_EQ(context.getIndex(method).first, 3);\n}\n\nTEST_F(HPACKContextTests, IsStatic) {\n  TestContext context(HPACK::kTableSize);\n  // add 10 headers to the table\n  for (int i = 1; i <= 10; i++) {\n    HPACKHeader header(\"name\" + folly::to<string>(i),\n                       \"value\" + folly::to<string>(i));\n    context.add(std::move(header));\n  }\n  EXPECT_EQ(context.getTable().size(), 10);\n\n  EXPECT_EQ(context.isStatic(1), true);\n  EXPECT_EQ(context.isStatic(10), true);\n  EXPECT_EQ(context.isStatic(40), true);\n  EXPECT_EQ(context.isStatic(60), true);\n  EXPECT_EQ(context.isStatic(69), false);\n}\n\nTEST_F(HPACKContextTests, StaticTable) {\n  auto& table = StaticHeaderTable::get();\n  const HPACKHeader& first = table.getHeader(1);\n  const HPACKHeader& methodPost = table.getHeader(3);\n  const HPACKHeader& last = table.getHeader(table.size());\n\n  // there are 61 entries in the spec\n  CHECK_EQ(table.size(), 61);\n  CHECK_EQ(methodPost, HPACKHeader(\":method\", \"POST\"));\n  CHECK_EQ(first.name.get(), \":authority\");\n  CHECK_EQ(last.name.get(), \"www-authenticate\");\n}\n\nTEST_F(HPACKContextTests, StaticTableHeaderNamesAreCommon) {\n  auto& table = StaticHeaderTable::get();\n  std::set<std::string> uncommonStaticEntries{\"allow\",\n                                              \"content-location\",\n                                              \"from\",\n                                              \"if-match\",\n                                              \"if-unmodified-since\",\n                                              \"max-forwards\",\n                                              \"if-range\",\n                                              \"refresh\"};\n  for (const auto& [header, _] : table.names()) {\n    EXPECT_TRUE(header.isCommonHeader() ||\n                uncommonStaticEntries.find(header.get()) !=\n                    uncommonStaticEntries.end())\n        << header.get();\n  }\n}\n\nTEST_F(HPACKContextTests,\n       static_table_is_header_code_in_table_with_non_empty_value) {\n  auto& table = StaticHeaderTable::get();\n  for (uint32_t i = 1; i <= table.size(); ++i) {\n    const HPACKHeader& staticTableHeader = table.getHeader(i);\n    EXPECT_TRUE(staticTableHeader.value.empty() !=\n                StaticHeaderTable::isHeaderCodeInTableWithNonEmptyValue(\n                    staticTableHeader.name.getHeaderCode()));\n  }\n}\n\nTEST_F(HPACKContextTests, StaticIndex) {\n  TestContext context(HPACK::kTableSize);\n  HPACKHeader authority(\":authority\", \"\");\n  EXPECT_EQ(context.getHeader(1), authority);\n\n  HPACKHeader post(\":method\", \"POST\");\n  EXPECT_EQ(context.getHeader(3), post);\n\n  HPACKHeader contentLength(\"content-length\", \"\");\n  EXPECT_EQ(context.getHeader(28), contentLength);\n}\n\nTEST_F(HPACKContextTests, EncoderMultipleValues) {\n  HPACKEncoder encoder(true);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"accept-encoding\", \"gzip\");\n  req.emplace_back(\"accept-encoding\", \"sdch,gzip\");\n  unique_ptr<IOBuf> encoded = encoder.encode(req);\n  EXPECT_TRUE(encoded->length() > 0);\n  EXPECT_EQ(encoder.getTable().size(), 2);\n  // sending the same request again should lead to a smaller but non\n  // empty buffer\n  unique_ptr<IOBuf> encoded2 = encoder.encode(req);\n  EXPECT_LT(encoded2->computeChainDataLength(),\n            encoded->computeChainDataLength());\n  EXPECT_GT(encoded2->computeChainDataLength(), 0);\n}\n\nTEST_F(HPACKContextTests, DecoderLargeHeader) {\n  // with this size basically the table will not be able to store any entry\n  uint32_t size = 32;\n  HPACKHeader header;\n  HPACKEncoder encoder(true, size);\n  HPACKDecoder decoder(size);\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\":path\", \"verylargeheader\");\n  // add a static entry\n  headers.emplace_back(\":method\", \"GET\");\n  auto buf = encoder.encode(headers);\n  auto decoded = proxygen::hpack::decode(decoder, buf.get());\n  EXPECT_EQ(encoder.getTable().size(), 0);\n  EXPECT_EQ(decoder.getTable().size(), 0);\n}\n\nTEST_F(HPACKContextTests, DecoderLargeHeaderClear) {\n  // Decode a header larger than the table, which will clear the decoder table\n  HPACKHeader header;\n  HPACKEncoder encoder(true, 4096);\n  HPACKDecoder decoder(40);\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\"foo\", \"bar\");\n  auto buf = encoder.encode(headers);\n  auto decoded = proxygen::hpack::decode(decoder, buf.get());\n  EXPECT_EQ(encoder.getTable().size(), 1);\n  EXPECT_EQ(decoder.getTable().size(), 1);\n  headers.clear();\n  headers.emplace_back(\"bar\", \"verylargeheader\");\n  buf = encoder.encode(headers);\n  decoded = proxygen::hpack::decode(decoder, buf.get());\n  EXPECT_EQ(encoder.getTable().size(), 2);\n  EXPECT_EQ(decoder.getTable().size(), 0);\n}\n\n/**\n * testing invalid memory access in the decoder; it has to always call peek()\n */\nTEST_F(HPACKContextTests, DecoderInvalidPeek) {\n  HPACKEncoder encoder(true);\n  HPACKDecoder decoder;\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\"x-fb-debug\", \"test\");\n\n  unique_ptr<IOBuf> encoded = encoder.encode(headers);\n  unique_ptr<IOBuf> first = IOBuf::create(128);\n  // set a trap for indexed header and don't call append\n  first->writableData()[0] = HPACK::INDEX_REF.code;\n\n  first->appendChain(std::move(encoded));\n  auto decoded = proxygen::hpack::decode(decoder, first.get());\n\n  EXPECT_FALSE(decoder.hasError());\n  EXPECT_EQ(*decoded, headers);\n}\n\n/**\n * similar with the one above, but slightly different code paths\n */\nTEST_F(HPACKContextTests, DecoderInvalidLiteralPeek) {\n  HPACKEncoder encoder(true);\n  HPACKDecoder decoder;\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\"x-fb-random\", \"bla\");\n  unique_ptr<IOBuf> encoded = encoder.encode(headers);\n\n  unique_ptr<IOBuf> first = IOBuf::create(128);\n  first->writableData()[0] = 0x3F;\n\n  first->appendChain(std::move(encoded));\n  auto decoded = proxygen::hpack::decode(decoder, first.get());\n\n  EXPECT_FALSE(decoder.hasError());\n  EXPECT_EQ(*decoded, headers);\n}\n\n/**\n * testing various error cases in HPACKDecoder::decodeLiterHeader()\n */\nvoid checkError(const IOBuf* buf, const HPACK::DecodeError err) {\n  HPACKDecoder decoder;\n  auto decoded = proxygen::hpack::decode(decoder, buf);\n  EXPECT_TRUE(decoder.hasError());\n  EXPECT_EQ(decoder.getError(), err);\n}\n\nTEST_F(HPACKContextTests, DecodeErrors) {\n  unique_ptr<IOBuf> buf = IOBuf::create(128);\n\n  // 1. simulate an error decoding the index for an indexed header name\n  // we try to encode index 65\n  buf->writableData()[0] = 0x3F;\n  buf->append(1); // intentionally omit the second byte\n  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  // 2. invalid index for indexed header name\n  buf->writableData()[0] = 0x7F;\n  buf->writableData()[1] = 0xFF;\n  buf->writableData()[2] = 0x7F;\n  buf->append(2);\n  checkError(buf.get(), HPACK::DecodeError::INVALID_INDEX);\n\n  // 2a. invalid integer for indexed header name\n  buf->writableData()[0] = 0x7F;\n  buf->writableData()[1] = 0xFF;\n  buf->writableData()[2] = 0xFF;\n  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  // 3. buffer overflow when decoding literal header name\n  buf->writableData()[0] = 0x00; // this will activate the non-indexed branch\n  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  // 4. buffer overflow when decoding a header value\n  // size for header name size and the actual header name\n  buf->writableData()[1] = 0x01;\n  buf->writableData()[2] = 'h';\n  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  // 5. buffer overflow decoding the index of an indexed header\n  buf->writableData()[0] = 0xFF; // first bit is 1 to mark indexed header\n  buf->writableData()[1] = 0x80; // first bit is 1 to continue the\n                                 // variable-length encoding\n  buf->writableData()[2] = 0x80;\n  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  // 6. Increase the table size\n  buf->writableData()[0] = 0x3F;\n  buf->writableData()[1] = 0xFF;\n  buf->writableData()[2] = 0x7F;\n  checkError(buf.get(), HPACK::DecodeError::INVALID_TABLE_SIZE);\n\n  // 7. integer overflow decoding the index of an indexed header\n  buf->writableData()[0] = 0xFF; // first bit is 1 to mark indexed header\n  buf->writableData()[1] = 0xFF;\n  buf->writableData()[2] = 0xFF;\n  buf->writableData()[3] = 0xFF;\n  buf->writableData()[4] = 0xFF;\n  buf->writableData()[5] = 0xFF;\n  buf->writableData()[6] = 0xFF;\n  buf->writableData()[7] = 0xFF;\n  buf->writableData()[8] = 0xFF;\n  buf->writableData()[9] = 0xFF;\n  buf->writableData()[10] = 0x7F;\n  buf->append(8);\n  checkError(buf.get(), HPACK::DecodeError::INTEGER_OVERFLOW);\n}\n\nTEST_F(HPACKContextTests, ExcludeHeadersLargerThanTable) {\n  HPACKEncoder encoder{true, 128};\n  std::string longer = std::string(150, '.');\n  HPACKHeader header1(longer, \"header\");\n  HPACKHeader header2(\"Short\", \"header\");\n\n  CHECK_GT(header1.bytes(), 128);\n  CHECK_LT(header2.bytes(), 128);\n\n  vector<HPACKHeader> headers;\n  headers.push_back(std::move(header2));\n  headers.push_back(std::move(header1));\n\n  encoder.encode(headers);\n\n  CHECK_EQ(encoder.getIndex(headers[1]).first, 0);\n  CHECK_EQ(encoder.getIndex(headers[0]).first, 62);\n}\n\nTEST_F(HPACKContextTests, EncodeToWriteBuf) {\n  HPACKEncoder encoder(true);\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\"x-fb-debug\", \"test\");\n\n  encoder.encode(headers, writeBuf);\n  EXPECT_GT(writeBuf.chainLength(), 0);\n}\n\nTEST_P(HPACKContextTests, ContextUpdate) {\n  HPACKEncoder encoder(true);\n  HPACKDecoder decoder;\n  vector<HPACKHeader> headers;\n  bool setDecoderSize = GetParam();\n  encoder.setHeaderTableSize(8192);\n  if (setDecoderSize) {\n    decoder.setHeaderTableMaxSize(8192);\n  }\n  headers.emplace_back(\"x-fb-random\", \"bla\");\n  unique_ptr<IOBuf> encoded = encoder.encode(headers);\n\n  unique_ptr<IOBuf> first = IOBuf::create(128);\n\n  first->appendChain(std::move(encoded));\n  auto decoded = proxygen::hpack::decode(decoder, first.get());\n\n  EXPECT_EQ(decoder.hasError(), !setDecoderSize);\n  if (setDecoderSize) {\n    EXPECT_EQ(*decoded, headers);\n  } else {\n    EXPECT_EQ(decoder.getError(), HPACK::DecodeError::INVALID_TABLE_SIZE);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(Context,\n                         HPACKContextTests,\n                         ::testing::Values(true, false));\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HPACKHeaderTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n\n#include <glog/logging.h>\n\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n#include <sstream>\n\nusing namespace proxygen;\nusing namespace std;\n\nclass HPACKHeaderTests : public testing::Test {};\n\nTEST_F(HPACKHeaderTests, Size) {\n  HPACKHeader h(\":path\", \"/\");\n  EXPECT_EQ(h.bytes(), 32 + 5 + 1);\n}\n\nTEST_F(HPACKHeaderTests, Operators) {\n  HPACKHeader h0(\":path\", \"/\");\n  HPACKHeader h1(\":path\", \"/\");\n  HPACKHeader h2(\":path\", \"/index.php\");\n  HPACKHeader h3(\"x-fb-debug\", \"test\");\n  // ==\n  EXPECT_TRUE(h0 == h1);\n  EXPECT_FALSE(h1 == h2);\n  // <\n  EXPECT_FALSE(h1 < h1);\n  EXPECT_TRUE(h1 < h2);\n  EXPECT_TRUE(h1 < h3);\n  // >\n  EXPECT_FALSE(h2 > h2);\n  EXPECT_TRUE(h3 > h2);\n  EXPECT_TRUE(h2 > h1);\n\n  stringstream out;\n  out << h1;\n  EXPECT_EQ(out.str(), \":path: /\");\n}\n\nTEST_F(HPACKHeaderTests, HasValue) {\n  HPACKHeader h1(\":path\", \"\");\n  HPACKHeader h2(\":path\", \"/\");\n  EXPECT_FALSE(h1.hasValue());\n  EXPECT_TRUE(h2.hasValue());\n}\n\nTEST_F(HPACKHeaderTests, HeaderIndexingStrategyBasic) {\n  HeaderIndexingStrategy indexingStrat;\n  HPACKHeader path(\":path\", \"index.php?q=42\");\n  EXPECT_FALSE(indexingStrat.indexHeader(path.name, path.value));\n  HPACKHeader cdn(\":path\", \"/hprofile-ak-prn1/49496_6024432_1026115112_n.jpg\");\n  EXPECT_FALSE(indexingStrat.indexHeader(cdn.name, cdn.value));\n  HPACKHeader clen(\"content-length\", \"512\");\n  EXPECT_FALSE(indexingStrat.indexHeader(clen.name, clen.value));\n  HPACKHeader data(\"data\", \"value\");\n  EXPECT_TRUE(indexingStrat.indexHeader(data.name, data.value));\n}\n\nclass HPACKHeaderNameTest : public testing::Test {};\n\nHPACKHeaderName destroyedHPACKHeaderName(std::string name) {\n  // return a HPACKHeaderName that goes destroyed\n  HPACKHeaderName headerName(name);\n  return headerName;\n}\n\nTEST_F(HPACKHeaderNameTest, TestConstructor) {\n  // Test constructor\n  HPACKHeaderName name1(\"accept-encoding\");\n  HPACKHeaderName name2(\"content-length\");\n  HPACKHeaderName name3(\"uncommon-name\");\n  HPACKHeaderName name4(\"uncommon-name-2\");\n  EXPECT_EQ(name1.get(), \"accept-encoding\");\n  EXPECT_EQ(name2.get(), \"content-length\");\n  EXPECT_EQ(name3.get(), \"uncommon-name\");\n  EXPECT_EQ(name4.get(), \"uncommon-name-2\");\n}\n\nTEST_F(HPACKHeaderNameTest, TestCopyConstructor) {\n  HPACKHeaderName name1(\"accept-encoding\");\n  HPACKHeaderName name2(\"uncommon-name\");\n\n  // Test copy constructor\n  HPACKHeaderName name3(name1);\n  HPACKHeaderName name4(name2);\n  HPACKHeaderName name5(destroyedHPACKHeaderName(\"x-fb-debug\"));\n  HPACKHeaderName name6(destroyedHPACKHeaderName(\"uncommon-name\"));\n  EXPECT_EQ(name3.get(), \"accept-encoding\");\n  EXPECT_EQ(name4.get(), \"uncommon-name\");\n  EXPECT_EQ(name5.get(), \"x-fb-debug\");\n  EXPECT_EQ(name6.get(), \"uncommon-name\");\n}\n\nTEST_F(HPACKHeaderNameTest, TestMoveConstructor) {\n  HPACKHeaderName name1(\"accept-encoding\");\n  HPACKHeaderName name2(\"uncommon-name\");\n\n  // Test move constructor\n  HPACKHeaderName name3(std::move(name1));\n  HPACKHeaderName name4(std::move(name2));\n  EXPECT_EQ(name3.get(), \"accept-encoding\");\n  EXPECT_EQ(name4.get(), \"uncommon-name\");\n}\n\nTEST_F(HPACKHeaderNameTest, TestAssignmentOperators) {\n  std::string testHeaderName = \"accept-encoding\";\n\n  HPACKHeaderName name1(testHeaderName);\n  EXPECT_EQ(name1.get(), testHeaderName);\n\n  // Explicitly test some self assignment overloads\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"\n#pragma clang diagnostic ignored \"-Wself-assign-overloaded\"\n#endif\n  name1 = name1;\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n  EXPECT_EQ(name1.get(), testHeaderName);\n  HPACKHeaderName* pName1 = &name1;\n  // Specifically require a temporary above to throw off the compiler/lint:\n  // explicitly moving variable of type 'proxygen::HPACKHeaderName' to itself\n  name1 = std::move(*pName1);\n  EXPECT_EQ(name1.get(), testHeaderName);\n\n  std::string otherHeaderName = \"uncommon-name\";\n  HPACKHeaderName name2(otherHeaderName);\n  name1 = name2;\n  EXPECT_EQ(name1.get(), otherHeaderName);\n  EXPECT_EQ(name2.get(), otherHeaderName);\n\n  HPACKHeaderName name3(testHeaderName);\n  name1 = std::move(name3);\n  EXPECT_EQ(name1.get(), testHeaderName);\n\n  name1 = HPACKHeaderName{otherHeaderName};\n  EXPECT_EQ(name1.get(), otherHeaderName);\n}\n\nTEST_F(HPACKHeaderNameTest, TestGetSize) {\n  // Test size()\n  HPACKHeaderName name1(\"accept-encoding\");\n  HPACKHeaderName name2(\"uncommon-header_now\");\n  EXPECT_EQ(name1.size(), 15);\n  EXPECT_EQ(name2.size(), 19);\n}\n\nTEST_F(HPACKHeaderNameTest, TestOperators) {\n  // Test operators\n  HPACKHeaderName name1(\"aaa\");\n  HPACKHeaderName name2(\"bbb\");\n  HPACKHeaderName name3(\"aaa\");\n  HPACKHeaderName name4(\"bbb\");\n  CHECK(name1 == name3);\n  CHECK(name1 != name2);\n  CHECK(name1 < name2);\n  CHECK(name2 > name1);\n  CHECK(name1 >= name3);\n  CHECK(name2 >= name1);\n  CHECK(name2 <= name4);\n  CHECK(name1 <= name2);\n}\n\nTEST_F(HPACKHeaderNameTest, TestIsCommonHeader) {\n  for (uint64_t j = 0; j < HTTPCommonHeaders::num_codes; ++j) {\n    auto code = static_cast<HTTPHeaderCode>(j);\n    HPACKHeader testHPACKHeader(*HTTPCommonHeaders::getPointerToName(code), \"\");\n\n    bool checkResult = j >= HTTPHeaderCodeCommonOffset;\n    EXPECT_EQ(testHPACKHeader.name.isCommonHeader(), checkResult);\n  }\n  std::string externalHeader = \"externalHeader\";\n  HPACKHeader testHPACKHeader(externalHeader, \"\");\n  EXPECT_FALSE(testHPACKHeader.name.isCommonHeader());\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HTTPArchive.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/test/HTTPArchive.h>\n\n#include <algorithm>\n#include <folly/io/IOBuf.h>\n#include <folly/json/json.h>\n#include <fstream>\n#include <glog/logging.h>\n#include <ios>\n#include <string>\n\nusing folly::IOBuf;\nusing std::ifstream;\nusing std::ios;\nusing std::string;\nusing std::unique_ptr;\nusing std::vector;\n\nnamespace {\n\nfolly::Optional<std::chrono::steady_clock::time_point> parseHTTPArchiveTime(\n    const std::string& s) {\n  struct tm tm = {};\n\n  if (s.empty()) {\n    return folly::none;\n  }\n\n  uint32_t ms = 0;\n  // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123\n  // Example: 2013-12-09T16:38:03.701Z\n  if (sscanf(s.c_str(),\n             \"%d-%d-%dT%d:%d:%d.%dZ\",\n             &tm.tm_year,\n             &tm.tm_mon,\n             &tm.tm_mday,\n             &tm.tm_hour,\n             &tm.tm_min,\n             &tm.tm_sec,\n             &ms) != 7) {\n    return folly::none;\n  }\n  // Per the spec, for some reason the API is inconsistent and requires\n  // years to be offset from 1900 and even more strange for month to be 0\n  // offset despite the fact it expects days to be 1 offset\n  // https://linux.die.net/man/3/mktime\n  tm.tm_year = tm.tm_year - 1900;\n  tm.tm_mon = tm.tm_mon - 1;\n\n  auto res = mktime(&tm);\n  return std::chrono::steady_clock::time_point(std::chrono::seconds(res) +\n                                               std::chrono::milliseconds(ms));\n}\n\nproxygen::HTTPMessage extractMessage(folly::dynamic& obj,\n                                     const std::string& timeStr,\n                                     bool request) {\n  proxygen::HTTPMessage msg;\n  auto& headersObj = obj[\"headers\"];\n  for (size_t i = 0; i < headersObj.size(); i++) {\n    string name = headersObj[i][\"name\"].asString();\n    std::transform(name.begin(), name.end(), name.begin(), ::tolower);\n    if (name[0] == ':') {\n      if (name == \":host\" || name == \":authority\") {\n        name = \"host\";\n      } else {\n        continue;\n      }\n    }\n    msg.getHeaders().add(name, headersObj[i][\"value\"].asString());\n  }\n  try {\n    if (request) {\n      msg.setURL(obj[\"url\"].asString());\n      msg.setMethod(obj[\"method\"].asString());\n      auto t = parseHTTPArchiveTime(timeStr);\n      if (t.has_value()) {\n        msg.setStartTime(t.value());\n      }\n    } else {\n      msg.setStatusCode(obj[\"status\"].asInt());\n      msg.setStatusMessage(obj[\"statusText\"].asString());\n    }\n\n    string proto;\n    string version;\n    folly::split('/', obj[\"httpVersion\"].asString(), proto, version);\n    msg.setVersionString(version);\n  } catch (...) {\n  }\n\n  return msg;\n}\n\nproxygen::HTTPMessage extractMessageFromPublic(folly::dynamic& obj) {\n  proxygen::HTTPMessage msg;\n  auto& headersObj = obj[\"headers\"];\n  for (size_t i = 0; i < headersObj.size(); i++) {\n    auto& headerObj = headersObj[i];\n    for (auto& k : headerObj.keys()) {\n      string name = k.asString();\n      string value = headerObj[name].asString();\n      std::transform(name.begin(), name.end(), name.begin(), ::tolower);\n      msg.getHeaders().add(name, value);\n    }\n  }\n  return msg;\n}\n} // namespace\n\nnamespace proxygen {\n\nstd::unique_ptr<IOBuf> readFileToIOBuf(const std::string& filename) {\n  // read the contents of the file\n  ifstream file(filename);\n  if (!file.is_open()) {\n    LOG(ERROR) << \"could not open file '\" << filename << \"'\";\n    return nullptr;\n  }\n  file.seekg(0, ios::end);\n  int64_t size = file.tellg();\n  if (size < 0) {\n    LOG(ERROR) << \"failed to fetch the position at the end of the file\";\n    return nullptr;\n  }\n  file.seekg(0, ios::beg);\n  unique_ptr<IOBuf> buffer = IOBuf::create(size + 1);\n  file.read((char*)buffer->writableData(), size);\n  buffer->writableData()[size] = 0;\n  buffer->append(size + 1);\n  if (!file) {\n    LOG(ERROR) << \"error occurred, was able to read only \" << file.gcount()\n               << \" bytes out of \" << size;\n    return nullptr;\n  }\n  return buffer;\n}\n\nunique_ptr<HTTPArchive> HTTPArchive::fromFile(const string& filename) {\n  unique_ptr<HTTPArchive> har = std::make_unique<HTTPArchive>();\n  auto buffer = readFileToIOBuf(filename);\n  if (!buffer) {\n    return nullptr;\n  }\n  folly::dynamic jsonObj = folly::parseJson((const char*)buffer->data());\n  auto entries = jsonObj[\"log\"][\"entries\"];\n  // go over all the transactions\n  for (size_t i = 0; i < entries.size(); i++) {\n    HTTPMessage msg = extractMessage(\n        entries[i][\"request\"], entries[i][\"startedDateTime\"].asString(), true);\n    if (msg.getHeaders().size() != 0) {\n      har->requests.emplace_back(std::move(msg));\n    }\n    msg = extractMessage(entries[i][\"response\"], \"\", false);\n    if (msg.getHeaders().size() != 0) {\n      har->responses.emplace_back(std::move(msg));\n    }\n  }\n\n  return har;\n}\n\nuint32_t HTTPArchive::getSize(const HTTPMessage& msg) {\n  uint32_t size = 0;\n\n  msg.getHeaders().forEach([&size](const string& name, const string& value) {\n    size += name.size() + value.size() + 2;\n  });\n  return size;\n}\n\nuint32_t HTTPArchive::getSize(const vector<HPACKHeader>& headers) {\n  uint32_t size = 0;\n\n  for (const auto& header : headers) {\n    size += header.name.size() + header.value.size() + 2;\n  }\n  return size;\n}\n\nunique_ptr<HTTPArchive> HTTPArchive::fromPublicFile(const string& filename) {\n  unique_ptr<HTTPArchive> har = std::make_unique<HTTPArchive>();\n  auto buffer = readFileToIOBuf(filename);\n  if (!buffer) {\n    return nullptr;\n  }\n  folly::dynamic jsonObj = folly::parseJson((const char*)buffer->data());\n  auto entries = jsonObj[\"cases\"];\n  // go over all the transactions\n  for (size_t i = 0; i < entries.size(); i++) {\n    HTTPMessage msg = extractMessageFromPublic(entries[i]);\n    if (msg.getHeaders().size() != 0) {\n      har->requests.emplace_back(msg);\n    }\n  }\n\n  return har;\n}\n\nstd::vector<std::vector<HPACKHeader>> HTTPArchive::convertToHPACK(\n    const std::vector<HTTPMessage>& msgs) {\n  std::vector<std::vector<HPACKHeader>> result;\n  for (const HTTPMessage& msg : msgs) {\n    std::vector<HPACKHeader> headers;\n    msg.getHeaders().forEach(\n        [&headers](const string& name, const string& value) {\n          headers.emplace_back(name, value);\n        });\n    result.emplace_back(std::move(headers));\n  }\n  return result;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HTTPArchive.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <folly/json/dynamic.h>\n#include <memory>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n\nclass HTTPArchive {\n public:\n  std::vector<HTTPMessage> requests;\n  std::vector<HTTPMessage> responses;\n\n  static std::vector<std::vector<HPACKHeader>> convertToHPACK(\n      const std::vector<HTTPMessage>& msgs);\n\n  static std::unique_ptr<HTTPArchive> fromFile(const std::string& filename);\n\n  static std::unique_ptr<HTTPArchive> fromPublicFile(const std::string& fname);\n\n  static uint32_t getSize(const HTTPMessage& msg);\n  static uint32_t getSize(const std::vector<HPACKHeader>& headers);\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HeaderPieceTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/codec/compress/HeaderPiece.h>\n\nusing namespace proxygen::compress;\n\nclass HeaderPieceTests : public testing::Test {};\n\nTEST_F(HeaderPieceTests, Basic) {\n  HeaderPiece *hp;\n\n  // creating non-owner piece with null pointer\n  hp = new HeaderPiece(nullptr, 0, false, true);\n  EXPECT_TRUE(hp->isMultiValued());\n  // destructing this should be fine, since will not try to release the memory\n  delete hp;\n\n  char *buf = new char[16];\n  hp = new HeaderPiece(buf, 16, true, true);\n  EXPECT_EQ(hp->str.data(), buf);\n  EXPECT_EQ(hp->str.size(), 16);\n  // this should release the mem\n  delete hp;\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HeaderTableTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HeaderTable.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/http/codec/compress/QPACKHeaderTable.h>\n#include <sstream>\n\nusing namespace std;\n\nnamespace proxygen {\n\nclass HeaderTableTests : public testing::Test {\n protected:\n  void xcheck(uint32_t internal, uint32_t external) {\n    EXPECT_EQ(HeaderTable::toExternal(head_, length_, internal), external);\n    EXPECT_EQ(HeaderTable::toInternal(head_, length_, external), internal);\n  }\n\n  void resizeTable(HeaderTable& table, uint32_t newCapacity, uint32_t newMax) {\n    table.setCapacity(newCapacity);\n    // On resizing the table size (count of headers) remains the same or sizes\n    // down; can not size up\n    EXPECT_LE(table.size(), newMax);\n  }\n\n  void resizeAndFillTable(HeaderTable& table,\n                          HPACKHeader& header,\n                          uint32_t newMax,\n                          uint32_t fillCount) {\n    uint32_t newCapacity = header.bytes() * newMax;\n    resizeTable(table, newCapacity, newMax);\n    // Fill the table (with one extra) and make sure we haven't violated our\n    // size (bytes) limits (expected one entry to be evicted)\n    for (size_t i = 0; i <= fillCount; ++i) {\n      EXPECT_EQ(table.add(header.copy()), true);\n    }\n    EXPECT_EQ(table.size(), newMax);\n    EXPECT_EQ(table.bytes(), newCapacity);\n  }\n\n  uint32_t head_{0};\n  uint32_t length_{0};\n};\n\nTEST_F(HeaderTableTests, IndexTranslation) {\n  // simple cases\n  length_ = 10;\n  head_ = 5;\n  xcheck(0, 6);\n  xcheck(3, 3);\n  xcheck(5, 1);\n\n  // wrap\n  head_ = 1;\n  xcheck(0, 2);\n  xcheck(8, 4);\n  xcheck(5, 7);\n}\n\nTEST_F(HeaderTableTests, Add) {\n  HeaderTable table(4096);\n  HPACKHeader header(\"accept-encoding\", \"gzip\");\n  table.add(header.copy());\n  table.add(header.copy());\n  table.add(header.copy());\n  EXPECT_EQ(table.names().size(), 1);\n  EXPECT_EQ(table.hasName(header.name), true);\n  auto it = table.names().find(header.name);\n  EXPECT_EQ(it->second.size(), 3);\n  EXPECT_EQ(table.nameIndex(header.name), 1);\n}\n\nTEST_F(HeaderTableTests, Evict) {\n  HPACKHeaderName name(\"accept-encoding\");\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  HPACKHeader accept2(\"accept-encoding\", \"----\"); // same size, different header\n  HPACKHeader accept3(\"accept-encoding\", \"third\"); // size is larger with 1 byte\n  uint32_t max = 10;\n  uint32_t capacity = accept.bytes() * max;\n  HeaderTable table(capacity);\n  // fill the table\n  for (size_t i = 0; i < max; i++) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n  EXPECT_EQ(table.add(accept2.copy()), true);\n  // evict the first one\n  EXPECT_EQ(table.getHeader(1), accept2);\n  auto ilist = table.names().find(name)->second;\n  EXPECT_EQ(ilist.size(), max);\n  // evict all the 'accept' headers\n  for (size_t i = 0; i < max - 1; i++) {\n    EXPECT_EQ(table.add(accept2.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n  EXPECT_EQ(table.getHeader(max), accept2);\n  EXPECT_EQ(table.names().size(), 1);\n  // add an entry that will cause 2 evictions\n  EXPECT_EQ(table.add(accept3.copy()), true);\n  EXPECT_EQ(table.getHeader(1), accept3);\n  EXPECT_EQ(table.size(), max - 1);\n\n  // add a super huge header\n  string bigvalue;\n  bigvalue.append(capacity, 'x');\n  HPACKHeader bigheader(\"user-agent\", bigvalue);\n  EXPECT_EQ(table.add(bigheader.copy()), false);\n  EXPECT_EQ(table.size(), 0);\n  EXPECT_EQ(table.names().size(), 0);\n}\n\nTEST_F(HeaderTableTests, ReduceCapacity) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  uint32_t max = 10;\n  uint32_t capacity = accept.bytes() * max;\n  HeaderTable table(capacity);\n  EXPECT_LE(table.length(), table.getMaxTableLength(capacity));\n\n  // fill the table\n  for (size_t i = 0; i < max; i++) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  // change capacity\n  table.setCapacity(capacity / 2);\n  EXPECT_EQ(table.size(), max / 2);\n  EXPECT_EQ(table.bytes(), capacity / 2);\n}\n\nTEST_F(HeaderTableTests, Comparison) {\n  uint32_t capacity = 128;\n  HeaderTable t1(capacity);\n  HeaderTable t2(capacity);\n\n  HPACKHeader h1(\"Content-Encoding\", \"gzip\");\n  HPACKHeader h2(\"Content-Encoding\", \"deflate\");\n  // different in number of elements\n  t1.add(h1.copy());\n  EXPECT_FALSE(t1 == t2);\n  // different in size (bytes)\n  t2.add(h2.copy());\n  EXPECT_FALSE(t1 == t2);\n\n  // make them the same\n  t1.add(h2.copy());\n  t2.add(h1.copy());\n  EXPECT_TRUE(t1 == t2);\n}\n\nTEST_F(HeaderTableTests, Print) {\n  stringstream out;\n  HeaderTable t(128);\n  t.add(HPACKHeader(\"Accept-Encoding\", \"gzip\"));\n  out << t;\n  EXPECT_EQ(out.str(), \"\\n[1] (s=51) accept-encoding: gzip\\ntotal size: 51\\n\");\n}\n\nTEST_F(HeaderTableTests, IncreaseCapacity) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  uint32_t max = 4;\n  uint32_t capacity = accept.bytes() * max;\n  HeaderTable table(capacity);\n  EXPECT_LE(table.length(), table.getMaxTableLength(capacity));\n\n  // fill the table\n  uint32_t length = table.length() + 1;\n  for (size_t i = 0; i < length; i++) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n  EXPECT_EQ(table.getIndex(accept).first, 1);\n  // head should be 0, tail should be 2\n  max = 8;\n  capacity = accept.bytes() * max;\n  table.setCapacity(capacity);\n\n  EXPECT_LE(table.length(), table.getMaxTableLength(capacity));\n  // external index didn't change\n  EXPECT_EQ(table.getIndex(accept).first, 1);\n}\n\nTEST_F(HeaderTableTests, VaryCapacity) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  uint32_t max = 6;\n  uint32_t capacity = accept.bytes() * max;\n  HeaderTable table(capacity);\n\n  // Fill the table (extra) and make sure we haven't violated our\n  // size (bytes) limits (expected one entry to be evicted)\n  for (size_t i = 0; i <= table.length(); ++i) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n\n  // Size down the table and verify we are still honoring our size (bytes)\n  // limits\n  resizeAndFillTable(table, accept, 4, 5);\n\n  // Size up the table (in between previous max and min within test) and verify\n  // we are still horing our size (bytes) limits\n  resizeAndFillTable(table, accept, 5, 6);\n\n  // Finally reize up one last timestamps\n  resizeAndFillTable(table, accept, 8, 9);\n}\n\nTEST_F(HeaderTableTests, VaryCapacityMalignHeadIndex) {\n  // Test checks for a previous bug/crash condition where due to resizing\n  // the underlying table to a size lower than a previous max but up from the\n  // current size and the position of the head_ index an out of bounds index\n  // would occur\n\n  // Initialize header table\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  uint32_t max = 6;\n  uint32_t capacity = accept.bytes() * max;\n  HeaderTable table(capacity);\n\n  // Push head_ to last index in underlying table before potential wrap\n  // This is our max table size for the duration of the test\n  for (size_t i = 0; i < table.getMaxTableLength(capacity); ++i) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n  EXPECT_EQ(table.bytes(), capacity);\n\n  // Flush underlying table (head_ remains the same at the previous max index)\n  // Header guranteed to cause a flush as header itself requires 32 bytes plus\n  // the sizes of the name and value anyways (which themselves would cause a\n  // flush)\n  string strLargerThanTableCapacity = string(capacity + 1, 'a');\n  HPACKHeader flush(\"flush\", strLargerThanTableCapacity);\n  EXPECT_EQ(table.add(flush.copy()), false);\n  EXPECT_EQ(table.size(), 0);\n\n  // Now reduce capacity of table (in functional terms table.size() is lowered\n  // but currently table.length() remains the same)\n  max = 3;\n  resizeTable(table, accept.bytes() * max, max);\n\n  // Increase capacity of table (but smaller than all time max; head_ still at\n  // previous max index).  Previously (now fixed) this size up resulted in\n  // incorrect resizing semantics\n  max = 4;\n  resizeTable(table, accept.bytes() * max, max);\n\n  // Now try and add headers; there should be no crash with current position of\n  // head_ in the underlying table.  Note this is merely one possible way we\n  // could force the test to crash as a result of the resize bug this test was\n  // added for\n  for (size_t i = 0; i <= table.length(); ++i) {\n    EXPECT_EQ(table.add(accept.copy()), true);\n  }\n  EXPECT_EQ(table.size(), max);\n}\n\nTEST_F(HeaderTableTests, AddLargerThanTable) {\n  // Construct a smallish table\n  uint32_t capacityBytes = 256;\n  HeaderTable table(capacityBytes);\n  HPACKHeaderName name(\"accept-encoding\");\n  table.add(HPACKHeader(\"accept-encoding\", \"gzip\")); // internal index = 0\n  table.add(HPACKHeader(\"accept-encoding\", \"gzip\")); // internal index = 1\n  table.add(HPACKHeader(\"test-encoding\", \"gzip\"));   // internal index = 2\n  EXPECT_EQ(table.names().size(), 2);\n\n  // Attempt to add a header that is larger than our specified table capacity\n  // bytes.  This should result in a table flush.\n  table.add(HPACKHeader(std::string(capacityBytes, 'a'), \"gzip\"));\n  EXPECT_EQ(table.names().size(), 0);\n\n  // Add the previous headers to the table again\n  table.add(HPACKHeader(\"accept-encoding\", \"gzip\")); // internal index = 3\n  table.add(HPACKHeader(\"accept-encoding\", \"gzip\")); // internal index = 4\n  table.add(HPACKHeader(\"test-encoding\", \"gzip\"));   // internal index = 5\n  EXPECT_EQ(table.names().size(), 2);\n\n  EXPECT_EQ(table.hasName(name), true);\n  auto it = table.names().find(name);\n  EXPECT_EQ(it->second.size(), 2);\n  // As nameIndex takes the last index added, we have head = 5, index = 4\n  // and so yields a difference of one and as external indexing is 1 based,\n  // we expect 2 here\n  EXPECT_EQ(table.nameIndex(name), 2);\n}\n\nTEST_F(HeaderTableTests, IncreaseLengthOfFullTable) {\n  HPACKHeader largeHeader(\"Access-Control-Allow-Credentials\", \"true\");\n  HPACKHeader smallHeader(\"Accept\", \"All-Content\");\n\n  HeaderTable table(448);\n  CHECK_EQ(table.length(), 7);\n\n  for (uint8_t count = 0; count < 3; count++) {\n    table.add(largeHeader.copy());\n    table.add(smallHeader.copy());\n  } // tail is at index 0\n  CHECK_EQ(table.length(), 7);\n\n  table.add(smallHeader.copy());\n  table.add(smallHeader.copy()); // tail is at index 1\n  table.add(smallHeader.copy()); // resize on this add\n  EXPECT_EQ(table.length(), 11);\n\n  // Check table is correct after resize\n  CHECK_EQ(table.getHeader(1), smallHeader);\n  CHECK_EQ(table.getHeader(2), smallHeader);\n  CHECK_EQ(table.getHeader(3), smallHeader);\n  CHECK_EQ(table.getHeader(4), smallHeader);\n  CHECK_EQ(table.getHeader(5), largeHeader);\n  CHECK_EQ(table.getHeader(6), smallHeader);\n  CHECK_EQ(table.getHeader(7), largeHeader);\n  CHECK_EQ(table.getHeader(8), smallHeader);\n}\n\nTEST_F(HeaderTableTests, SmallTable) {\n  HeaderTable table(80);\n  HPACKHeader foo(\"Foo\", \"bar\");\n  EXPECT_TRUE(table.add(foo.copy()));\n  EXPECT_TRUE(table.add(foo.copy()));\n  EXPECT_EQ(table.size(), 2);\n  EXPECT_EQ(table.length(), 2);\n}\n\nTEST_F(HeaderTableTests, TinyTable) {\n  // This table can only hold 1 header, but it better be able to hold it!\n  HeaderTable table(63);\n  HPACKHeader foo(\"Foo\", \"barbarbarbarbarbarbar1\");\n  EXPECT_TRUE(table.add(foo.copy()));\n  EXPECT_EQ(table.size(), 1);\n  EXPECT_EQ(table.length(), 1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/HuffmanTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <tuple>\n#include <unordered_set>\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/compress/Huffman.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n\nusing namespace folly::io;\nusing namespace folly;\nusing namespace proxygen::huffman;\nusing namespace std;\n\nclass HuffmanTests : public testing::Test {\n protected:\n  const HuffTree& tree_ = huffTree();\n};\n\nTEST_F(HuffmanTests, Codes) {\n  uint32_t code;\n  uint8_t bits;\n  // check 'e' for both requests and responses\n  tie(code, bits) = tree_.getCode('e');\n  EXPECT_EQ(code, 0x05);\n  EXPECT_EQ(bits, 5);\n  // some extreme cases\n  tie(code, bits) = tree_.getCode(0);\n  EXPECT_EQ(code, 0x1ff8);\n  EXPECT_EQ(bits, 13);\n  tie(code, bits) = tree_.getCode(255);\n  EXPECT_EQ(code, 0x3ffffee);\n  EXPECT_EQ(bits, 26);\n}\n\nTEST_F(HuffmanTests, Size) {\n  uint32_t size;\n  folly::fbstring onebyte(\"x\");\n  size = tree_.getEncodeSize(onebyte);\n  EXPECT_EQ(size, 1);\n\n  folly::fbstring accept(\"accept-encoding\");\n  size = tree_.getEncodeSize(accept);\n  EXPECT_EQ(size, 11);\n}\n\nTEST_F(HuffmanTests, Encode) {\n  uint32_t size;\n  // this is going to fit perfectly into 3 bytes\n  folly::fbstring gzip(\"gzip\");\n  IOBufQueue bufQueue;\n  QueueAppender appender(&bufQueue, 512);\n  // force the allocation\n  appender.ensure(512);\n\n  size = tree_.encode(gzip, appender);\n  EXPECT_EQ(size, 3);\n  const IOBuf* buf = bufQueue.front();\n  const uint8_t* data = buf->data();\n  EXPECT_EQ(data[0], 0x9b); // 10011011\n  EXPECT_EQ(data[1], 0xd9); // 11011001\n  EXPECT_EQ(data[2], 0xab); // 10101011\n\n  // size must equal with the actual encoding\n  folly::fbstring accept(\"accept-encoding\");\n  size = tree_.getEncodeSize(accept);\n  uint32_t encodedSize = tree_.encode(accept, appender);\n  EXPECT_EQ(size, encodedSize);\n}\n\nTEST_F(HuffmanTests, Decode) {\n  uint8_t buffer[3];\n  // simple test with one byte\n  buffer[0] = 0x60; //\n  buffer[1] = 0xbf; //\n  folly::fbstring literal;\n  tree_.decode(buffer, 2, literal);\n  EXPECT_EQ(literal, \"/e\");\n\n  // simple test with \"gzip\"\n  buffer[0] = 0x9b;\n  buffer[1] = 0xd9;\n  buffer[2] = 0xab;\n  literal.clear();\n  tree_.decode(buffer, 3, literal);\n  EXPECT_EQ(literal, \"gzip\");\n\n  // something with padding\n  buffer[0] = 0x98;\n  buffer[1] = 0xbf;\n  literal.clear();\n  tree_.decode(buffer, 2, literal);\n  EXPECT_EQ(literal, \"ge\");\n}\n\n/*\n * non-printable characters, that use 3 levels\n */\nTEST_F(HuffmanTests, NonPrintableDecode) {\n  // character code 9 and 38 (&) that have 24 + 8 = 32 bits\n  uint8_t buffer1[4] = {0xFF, 0xFF, 0xEA, 0xF8};\n  folly::fbstring literal;\n  tree_.decode(buffer1, 4, literal);\n  EXPECT_EQ(literal.size(), 2);\n  EXPECT_EQ((uint8_t)literal[0], 9);\n  EXPECT_EQ((uint8_t)literal[1], 38);\n\n  // two weird characters and padding\n  // 1 and 240 will have 26 + 23 = 49 bits + 7 bits padding\n  uint8_t buffer2[7] = {0xFF, 0xFF, 0xB1, 0xFF, 0xFF, 0xF5, 0xFF};\n  literal.clear();\n  tree_.decode(buffer2, 7, literal);\n  EXPECT_EQ(literal.size(), 2);\n  EXPECT_EQ((uint8_t)literal[0], 1);\n  EXPECT_EQ((uint8_t)literal[1], 240);\n}\n\nTEST_F(HuffmanTests, ExampleCom) {\n  // interesting case of one bit with value 0 in the last byte\n  IOBufQueue bufQueue;\n  QueueAppender appender(&bufQueue, 512);\n  appender.ensure(512);\n\n  folly::fbstring example(\"www.example.com\");\n  uint32_t size = tree_.getEncodeSize(example);\n  EXPECT_EQ(size, 12);\n  uint32_t encodedSize = tree_.encode(example, appender);\n  EXPECT_EQ(size, encodedSize);\n\n  folly::fbstring decoded;\n  tree_.decode(bufQueue.front()->data(), size, decoded);\n  CHECK_EQ(example, decoded);\n}\n\nTEST_F(HuffmanTests, UserAgent) {\n  folly::fbstring user_agent(\n      \"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_4 like Mac OS X) \"\n      \"AppleWebKit/537.51\"\n      \".1 (KHTML, like Gecko) Mobile/11B554a \"\n      \"[FBAN/FBIOS;FBAV/6.7;FBBV/566055;FBD\"\n      \"V/iPhone5,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; \"\n      \"FBCR/AT&T;FBID/p\"\n      \"hone;FBLC/en_US;FBOP/5]\");\n  IOBufQueue bufQueue;\n  QueueAppender appender(&bufQueue, 512);\n  appender.ensure(512);\n  const HuffTree& tree = huffTree();\n  uint32_t size = tree.getEncodeSize(user_agent);\n  uint32_t encodedSize = tree.encode(user_agent, appender);\n  EXPECT_EQ(size, encodedSize);\n\n  folly::fbstring decoded;\n  tree.decode(bufQueue.front()->data(), size, decoded);\n  CHECK_EQ(user_agent, decoded);\n}\n\n/*\n * this test is verifying the CHECK for length at the end of huffman::encode()\n */\nTEST_F(HuffmanTests, FitInBuffer) {\n  IOBufQueue bufQueue;\n  QueueAppender appender(&bufQueue, 128);\n\n  // call with an empty string\n  folly::fbstring literal(\"\");\n  tree_.encode(literal, appender);\n\n  // allow just 1 byte\n  appender.ensure(128);\n  appender.append(appender.length() - 1);\n  literal = \"g\";\n  tree_.encode(literal, appender);\n  CHECK_EQ(appender.length(), 0);\n}\n\n/*\n * sanity checks of each node in decode tree performed by a depth first search\n *\n * allSnodes is an array of up to 46 SuperHuffNode's, the 46 is hardcoded\n * in creation\n * nodeIndex is the current SuperHuffNode being visited\n * depth is the depth of the current SuperHuffNode being visited\n * fullCode remembers the code from parent HuffNodes\n * eosCode stores the code for End-Of-String characters which the tables\n *   do not store\n * eosCodeBits stores the number of bits for the End-Of-String character\n *   codes\n */\nuint32_t treeDfs(const SuperHuffNode* allSnodes,\n                 const uint32_t& snodeIndex,\n                 const uint32_t& depth,\n                 const uint32_t& fullCode,\n                 const uint32_t& eosCode,\n                 const uint32_t& eosCodeBits) {\n\n  EXPECT_TRUE(depth < 4);\n\n  unordered_set<uint32_t> leaves;\n  uint32_t subtreeLeafCount = 0;\n\n  for (uint32_t i = 0; i < 256; i++) {\n    const HuffNode& node = allSnodes[snodeIndex].index[i];\n\n    uint32_t newFullCode = fullCode ^ (i << (24 - 8 * depth));\n    uint32_t eosCodeDepth = (eosCodeBits - 1) / 8;\n\n    if (eosCodeDepth == depth &&\n        (newFullCode >> (32 - eosCodeBits)) == eosCode) {\n\n      // this condition corresponds to an EOS code\n      // this should be a leaf that doesn't have supernode or bits set\n\n      EXPECT_TRUE(node.isLeaf());\n      EXPECT_TRUE(node.metadata.bits == 0);\n    } else if (node.isLeaf()) {\n\n      // this condition is a normal leaf\n      // this should have bits set\n\n      EXPECT_TRUE(node.isLeaf());\n      EXPECT_TRUE(node.metadata.bits > 0);\n      EXPECT_TRUE(node.metadata.bits <= 8);\n\n      // used to count unique leaves at this node\n      leaves.insert(node.data.ch);\n    } else {\n\n      // this condition is a branching node\n      // this should have the superNodeIndex set but not bits should be set\n\n      EXPECT_TRUE(!node.isLeaf());\n      EXPECT_TRUE(node.data.superNodeIndex > 0);\n      EXPECT_TRUE(node.metadata.bits == 0);\n      EXPECT_TRUE(node.data.superNodeIndex < 46);\n\n      // keep track of leaf counts for this subtree\n      subtreeLeafCount += treeDfs(allSnodes,\n                                  node.data.superNodeIndex,\n                                  depth + 1,\n                                  newFullCode,\n                                  eosCode,\n                                  eosCodeBits);\n    }\n  }\n  return subtreeLeafCount + leaves.size();\n}\n\n/**\n * Class used in testing to expose the internal tables for requests\n * and responses\n */\nclass TestingHuffTree : public HuffTree {\n public:\n  explicit TestingHuffTree(const HuffTree& tree) : HuffTree(tree) {\n  }\n\n  const SuperHuffNode* getInternalTable() {\n    return table_;\n  }\n\n  static TestingHuffTree getHuffTree() {\n    TestingHuffTree reqTree(huffTree());\n    return reqTree;\n  }\n};\n\nTEST_F(HuffmanTests, SanityChecks) {\n  TestingHuffTree reqTree = TestingHuffTree::getHuffTree();\n  const SuperHuffNode* allSnodesReq = reqTree.getInternalTable();\n  uint32_t totalReqChars = treeDfs(allSnodesReq, 0, 0, 0, 0x3fffffff, 30);\n  EXPECT_EQ(totalReqChars, 256);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/LoggingTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <list>\n#include <proxygen/lib/http/codec/compress/HPACKEncodeBuffer.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <sstream>\n#include <vector>\n\nusing namespace proxygen;\nusing namespace std;\n\nclass LoggingTests : public testing::Test {};\n\nTEST_F(LoggingTests, Refset) {\n  list<uint32_t> refset;\n  refset.push_back(3);\n  refset.push_back(5);\n  stringstream out;\n  out << &refset;\n  EXPECT_EQ(out.str(), \"\\n[3 5 ]\\n\");\n}\n\nTEST_F(LoggingTests, DumpHeaderVector) {\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\":path\", \"index.html\");\n  headers.emplace_back(\"content-type\", \"gzip\");\n  stringstream out;\n  out << headers;\n  EXPECT_EQ(out.str(), \":path: index.html\\ncontent-type: gzip\\n\\n\");\n}\n\nTEST_F(LoggingTests, PrintDelta) {\n  vector<HPACKHeader> v1;\n  v1.emplace_back(\":path\", \"/\");\n  v1.emplace_back(\":host\", \"www.facebook.com\");\n  vector<HPACKHeader> v2;\n\n  // empty v1 or v2\n  EXPECT_EQ(printDelta(v1, v2), \"\\n + :path: /\\n + :host: www.facebook.com\\n\");\n  EXPECT_EQ(printDelta(v2, v1), \"\\n - :path: /\\n - :host: www.facebook.com\\n\");\n\n  // skip first header from v1\n  v2.emplace_back(\":path\", \"/\");\n  EXPECT_EQ(printDelta(v1, v2), \"\\n + :host: www.facebook.com\\n\");\n\n  v2.emplace_back(\":path\", \"/\");\n  EXPECT_EQ(printDelta(v2, v1),\n            \"\\n - :host: www.facebook.com\\n + :path: / (duplicate)\\n\");\n\n  v2.pop_back();\n  v1.clear();\n  v1.emplace_back(\":a\", \"b\");\n  v1.emplace_back(\":a\", \"b\");\n  v1.emplace_back(\":path\", \"/\");\n  EXPECT_EQ(printDelta(v1, v2), \"\\n + :a: b\\n duplicate :a: b\\n\");\n}\n\nTEST_F(LoggingTests, DumpBin) {\n  // test with an HPACKEncodeBuffer\n  HPACKEncodeBuffer buf(128);\n  buf.encodeLiteral(\"test\");\n  EXPECT_EQ(buf.toBin(),\n            \"00000100   01110100 t 01100101 e 01110011 s 01110100 t \\n\");\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/QPACKCodecTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Range.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/codec/compress/Header.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/codec/compress/QPACKCodec.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n#include <vector>\n\nusing namespace folly;\nusing namespace proxygen::compress;\nusing namespace proxygen::hpack;\nusing namespace proxygen;\nusing namespace std;\n\nnamespace {\nvoid headersEq(vector<Header>& headerVec, compress::HeaderPieceList& headers) {\n  size_t i = 0;\n  EXPECT_EQ(headerVec.size() * 2, headers.size());\n  for (auto& h : headerVec) {\n    string name = *h.name;\n    char* mutableName = (char*)name.data();\n    folly::toLowerAscii(mutableName, name.size());\n    EXPECT_EQ(name, headers[i++].str);\n    EXPECT_EQ(*h.value, headers[i++].str);\n  }\n}\n} // namespace\n\nclass QPACKTests : public testing::Test {\n public:\n  void SetUp() override {\n    server.setDecoderHeaderTableMaxSize(5120);\n    client.setDecoderHeaderTableMaxSize(5120);\n    EXPECT_TRUE(server.setEncoderHeaderTableSize(4096));\n  }\n\n protected:\n  void controlAck() {\n    auto ack = server.encodeInsertCountInc();\n    EXPECT_EQ(client.decodeDecoderStream(std::move(ack)),\n              HPACK::DecodeError::NONE);\n  }\n\n  void headerAck(uint64_t streamId) {\n    auto ack = server.encodeHeaderAck(streamId);\n    EXPECT_EQ(client.decodeDecoderStream(std::move(ack)),\n              HPACK::DecodeError::NONE);\n  }\n\n  QPACKCodec client;\n  QPACKCodec server;\n};\n\nTEST_F(QPACKTests, TestEncoderTableSize) {\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(0));\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(0));\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(4096));\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(4096));\n  EXPECT_FALSE(client.setEncoderHeaderTableSize(1024));\n  EXPECT_FALSE(client.setEncoderHeaderTableSize(5120));\n}\n\nTEST_F(QPACKTests, TestSimple) {\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(4096));\n  vector<Header> req = basicHeaders();\n  auto encodeResult = client.encode(req, 1);\n  ASSERT_NE(encodeResult.control.get(), nullptr);\n  EXPECT_EQ(server.decodeEncoderStream(std::move(encodeResult.control)),\n            HPACK::DecodeError::NONE);\n  TestStreamingCallback cb;\n  auto length = encodeResult.stream->computeChainDataLength();\n  server.decodeStreaming(1, std::move(encodeResult.stream), length, &cb);\n  headerAck(1);\n  auto result = cb.getResult();\n  EXPECT_TRUE(!result.hasError());\n  headersEq(req, result->headers);\n  EXPECT_GT(client.getCompressionInfo().egress.headersStored_, 0);\n  EXPECT_GT(server.getCompressionInfo().ingress.headersStored_, 0);\n}\n\nTEST_F(QPACKTests, TestAbsoluteIndex) {\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(4096));\n  int flights = 10;\n  for (int i = 0; i < flights; i++) {\n    vector<vector<string>> headers;\n    for (int j = 0; j < 32; j++) {\n      int value = (i >> 1) * 32 + j; // duplicate the last flight\n      headers.emplace_back(\n          vector<string>({string(\"foomonkey\"), folly::to<string>(value)}));\n    }\n    auto req = headersFromArray(headers);\n    auto encodeResult = client.encode(req, i + 1);\n    if (i % 2 == 1) {\n      EXPECT_EQ(encodeResult.control.get(), nullptr);\n    } else {\n      ASSERT_NE(encodeResult.control.get(), nullptr);\n      CHECK_EQ(server.decodeEncoderStream(std::move(encodeResult.control)),\n               HPACK::DecodeError::NONE);\n    }\n    TestStreamingCallback cb;\n    auto length = encodeResult.stream->computeChainDataLength();\n    server.decodeStreaming(i + 1, std::move(encodeResult.stream), length, &cb);\n    headerAck(i + 1);\n    auto result = cb.getResult();\n    EXPECT_TRUE(!result.hasError());\n    headersEq(req, result->headers);\n  }\n  EXPECT_GT(client.getCompressionInfo().egress.headersStored_, 0);\n  EXPECT_GT(server.getCompressionInfo().ingress.headersStored_, 0);\n}\n\nTEST_F(QPACKTests, TestWithQueue) {\n  // Sends 10 flights of 4 requests each\n  // Each request contains two 'connection' headers, one with the current\n  // index, and current index - 8.\n  // Each flight is processed in the order 0, 3, 2, 1, unless an eviction\n  // happens on 2 or 3, in which case we force an blocking event.\n  vector<Header> req = basicHeaders();\n  vector<string> values;\n  int flights = 10;\n  for (auto i = 0; i < flights * 4; i++) {\n    values.push_back(folly::to<string>(i));\n  }\n  EXPECT_TRUE(client.setEncoderHeaderTableSize(1024));\n  for (auto f = 0; f < flights; f++) {\n    vector<std::pair<unique_ptr<IOBuf>, TestStreamingCallback>> data;\n    list<unique_ptr<IOBuf>> controlFrames;\n    for (int i = 0; i < 4; i++) {\n      auto reqI = req;\n      for (int j = 0; j < 2; j++) {\n        reqI.emplace_back(HTTP_HEADER_CONNECTION,\n                          values[std::max(f * 4 + i - j * 8, 0)]);\n      }\n      VLOG(4) << \"Encoding req=\" << f * 4 + i;\n      auto res = client.encode(reqI, f * 4 + i);\n      if (res.control && res.control->computeChainDataLength() > 0) {\n        controlFrames.emplace_back(std::move(res.control));\n      }\n      data.emplace_back(std::move(res.stream), TestStreamingCallback());\n    }\n\n    std::vector<int> insertOrder{0, 3, 2, 1};\n    if (!controlFrames.empty()) {\n      auto control = std::move(controlFrames.front());\n      controlFrames.pop_front();\n      server.decodeEncoderStream(std::move(control));\n    }\n    for (auto i : insertOrder) {\n      auto& encodedReq = data[i].first;\n      auto len = encodedReq->computeChainDataLength();\n      server.decodeStreaming(i, std::move(encodedReq), len, &data[i].second);\n    }\n    while (!controlFrames.empty()) {\n      auto control = std::move(controlFrames.front());\n      controlFrames.pop_front();\n      server.decodeEncoderStream(std::move(control));\n    }\n    int i = 0;\n    for (auto& d : data) {\n      auto result = d.second.getResult();\n      EXPECT_TRUE(!result.hasError());\n      auto reqI = req;\n      for (int j = 0; j < 2; j++) {\n        reqI.emplace_back(HTTP_HEADER_CONNECTION,\n                          values[std::max(f * 4 + i - j * 8, 0)]);\n      }\n      headersEq(reqI, result->headers);\n      headerAck(f * 4 + i);\n      i++;\n    }\n    VLOG(4) << \"getHolBlockCount=\" << server.getHolBlockCount();\n  }\n  // Skipping redundant table adds reduces the HOL block count\n  EXPECT_EQ(server.getHolBlockCount(), 30);\n\n  EXPECT_GT(client.getCompressionInfo().egress.headersStored_, 0);\n  EXPECT_GT(server.getCompressionInfo().ingress.headersStored_, 0);\n}\n\nTEST_F(QPACKTests, HeaderCodecStats) {\n  vector<vector<string>> headers = {\n      {\"Content-Length\", \"80\"},\n      {\"Content-Encoding\", \"gzip\"},\n      {\"X-FB-Debug\", \"eirtijvdgtccffkutnbttcgbfieghgev\"}};\n  vector<Header> resp = headersFromArray(headers);\n\n  TestHeaderCodecStats stats(HeaderCodec::Type::QPACK);\n  // encode\n  server.setStats(&stats);\n  auto encResult = server.encode(resp, 1);\n  EXPECT_EQ(stats.encodes, 1);\n  EXPECT_EQ(stats.decodes, 0);\n  EXPECT_EQ(stats.errors, 0);\n  EXPECT_GT(stats.encodedBytesCompr, 0);\n  EXPECT_GT(stats.encodedBytesComprBlock, 0);\n  EXPECT_GT(stats.encodedBytesUncompr, 0);\n  EXPECT_EQ(stats.decodedBytesCompr, 0);\n  EXPECT_EQ(stats.decodedBytesUncompr, 0);\n  server.setStats(nullptr);\n\n  // decode\n  stats.reset();\n  client.setStats(&stats);\n  TestStreamingCallback cb;\n  auto len = encResult.stream->computeChainDataLength();\n  client.decodeEncoderStream(std::move(encResult.control));\n  client.decodeStreaming(1, std::move(encResult.stream), len, &cb);\n  auto result = cb.getResult();\n  EXPECT_TRUE(!result.hasError());\n  auto& decoded = result->headers;\n  CHECK_EQ(decoded.size(), 3 * 2);\n  EXPECT_EQ(stats.decodes, 1);\n  EXPECT_EQ(stats.encodes, 0);\n  EXPECT_GT(stats.decodedBytesCompr, 0);\n  EXPECT_GT(stats.decodedBytesUncompr, 0);\n  EXPECT_EQ(stats.encodedBytesCompr, 0);\n  EXPECT_EQ(stats.encodedBytesComprBlock, 0);\n  EXPECT_EQ(stats.encodedBytesUncompr, 0);\n  client.setStats(nullptr);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/QPACKContextTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Conv.h>\n#include <folly/Format.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/http/codec/compress/QPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/QPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nnamespace {\nstd::shared_ptr<bool> verifyDecode(\n    QPACKDecoder& decoder,\n    QPACKEncoder::EncodeResult result,\n    const std::vector<HPACKHeader>& expectedHeaders,\n    HPACK::DecodeError expectedError = HPACK::DecodeError::NONE) {\n  auto cb = std::make_shared<TestStreamingCallback>();\n  auto done = std::make_shared<bool>(false);\n  if (result.control) {\n    EXPECT_EQ(decoder.decodeEncoderStream(std::move(result.control)),\n              HPACK::DecodeError::NONE);\n  }\n  auto length = result.stream->computeChainDataLength();\n  if (expectedError == HPACK::DecodeError::NONE) {\n    cb->headersCompleteCb = [&expectedHeaders, cb, done]() mutable {\n      std::vector<HPACKHeader> test;\n      for (size_t i = 0; i < cb->headers.size(); i += 2) {\n        test.emplace_back(cb->headers[i].str, cb->headers[i + 1].str);\n      }\n      EXPECT_EQ(cb->error, HPACK::DecodeError::NONE);\n      EXPECT_EQ(test, expectedHeaders);\n      *done = true;\n      cb.reset();\n    };\n  }\n  // streamID only matters for cancellation\n  decoder.decodeStreaming(0, std::move(result.stream), length, cb.get());\n  EXPECT_EQ(cb->error, expectedError);\n  if (expectedError != HPACK::DecodeError::NONE) {\n    *done = true;\n  }\n  return done;\n}\n\nbool stringInOutput(IOBuf* stream, const std::string& expected) {\n  stream->coalesce();\n  return memmem(\n      stream->data(), stream->length(), expected.data(), expected.length());\n}\n\nHPACK::DecodeError headerAck(QPACKDecoder& decoder,\n                             QPACKEncoder& encoder,\n                             uint64_t streamId) {\n  return encoder.decodeDecoderStream(decoder.encodeHeaderAck(streamId));\n}\n\nHPACK::DecodeError cancelStream(QPACKDecoder& decoder,\n                                QPACKEncoder& encoder,\n                                uint64_t streamId) {\n  return encoder.decodeDecoderStream(decoder.encodeCancelStream(streamId));\n}\n\nstd::string toFixedLengthString(uint32_t i) {\n  CHECK_LT(i, 1000);\n  return fmt::format(\"{:3}\", i);\n}\n} // namespace\n\nTEST(QPACKContextTests, StaticOnly) {\n  QPACKEncoder encoder(true, 128);\n  QPACKDecoder decoder(128);\n  vector<HPACKHeader> req;\n  // testing static indexes on request streams (6 bits)\n  req.emplace_back(\":authority\", \"\");                    // qpack idx=0\n  req.emplace_back(\"x-xss-protection\", \"1; mode=block\"); // idx=62\n  req.emplace_back(\":status\", \"100\");                    // idx=63\n  req.emplace_back(\"x-frame-options\", \"sameorigin\");     // idx=last\n  auto result = encoder.encode(req, 10, 1);\n  EXPECT_EQ(result.control, nullptr);\n  // prefix(2) + instr(1) + instr(1) + instr(2) + instr(2)\n  EXPECT_EQ(result.stream->computeChainDataLength(), 8);\n  EXPECT_EQ(result.stream->data()[0], 0);\n  EXPECT_EQ(result.stream->data()[1], 0);\n  verifyDecode(decoder, std::move(result), req);\n  // nothing to ack\n  EXPECT_EQ(decoder.encodeInsertCountInc(), nullptr);\n}\n\nTEST(QPACKContextTests, StaticNameIndex) {\n  QPACKEncoder encoder(false, 210);\n  QPACKDecoder decoder(210);\n  vector<HPACKHeader> req;\n\n  // testing static name indexes on the control stream (6 bits)\n  req.emplace_back(\":authority\", \"foo.com\");       // qpack idx=0\n  req.emplace_back(\"x-xss-protection\", \"maximum\"); // idx=62\n  // :status at index 63 won't be used by our encoder, it will prefer idx=24\n  req.emplace_back(\"accept-language\", \"c++\"); // idx=72\n  req.emplace_back(\"x-frame-options\", \"zzz\"); // idx=last\n  auto result = encoder.encode(req, 10, 1);\n  // instr(1) + len(1) + foo.com(7) + instr(1) + len(1) + maximum(7) +\n  // instr(2) + len(1) + c++(3) + instr(2) + len(1) + zzz(3) = 30\n  EXPECT_EQ(result.control->computeChainDataLength(), 30);\n  EXPECT_EQ(result.stream->computeChainDataLength(), 6);\n  verifyDecode(decoder, std::move(result), req);\n\n  req.clear();\n  // testing static name indexes in literals (4 bits)\n  encoder.onHeaderAck(1, false);\n  encoder.setMaxVulnerable(0);\n  req.emplace_back(\"set-cookie\", \"abc\"); // idx=14\n  req.emplace_back(\":method\", \"DUDE\");   // idx=15\n  result = encoder.encode(req, 10, 1);\n  // prefix(2) + instr(1) + len(1) + abc(3) + instr(2) + len(1) + DUDE(4) = 14\n  EXPECT_EQ(result.stream->computeChainDataLength(), 14);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, Indexed) {\n  QPACKEncoder encoder(true, 128);\n  QPACKDecoder decoder(128);\n  vector<HPACKHeader> req;\n  // Encodes \"Post Base\"\n  req.emplace_back(\"Blarf\", \"Blah\");\n  auto result = encoder.encode(req, 10, 1);\n  verifyDecode(decoder, std::move(result), req);\n  // Encodes \"Normal\"\n  result = encoder.encode(req, 10, 2);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, NameIndexed) {\n  QPACKEncoder encoder(true, 64);\n  QPACKDecoder decoder(64);\n  vector<HPACKHeader> req;\n\n  // Encodes a \"Post Base\" name index since the table is full\n  req.emplace_back(\"Blarf\", \"Blah\");\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  auto result = encoder.encode(req, 10, 1);\n  verifyDecode(decoder, std::move(result), req);\n  // Encodes \"Normal\" name index\n  result = encoder.encode(req, 10, 2);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, NameIndexedInsert) {\n  QPACKEncoder encoder(false, 128);\n  QPACKDecoder decoder(128);\n  vector<HPACKHeader> req;\n\n  req.emplace_back(\"Blarf\", \"Blah\");\n  auto result = encoder.encode(req, 10, 1);\n  verifyDecode(decoder, std::move(result), req);\n\n  // Encodes an insert using a dynamic name reference\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  result = encoder.encode(req, 10, 2);\n  EXPECT_FALSE(stringInOutput(result.control.get(), \"blarf\"));\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, PostBaseNameIndexedLiteral) {\n  QPACKEncoder encoder(false, 360);\n  QPACKDecoder decoder(360);\n  vector<HPACKHeader> req;\n\n  encoder.setMaxVulnerable(1);\n  // Fills the table with exacty minFree (48) empty\n  for (auto i = 0; i < 8; i++) {\n    req.emplace_back(folly::to<std::string>(\"Blarf\", i), \"0\");\n  }\n  // Too big to put in the table without evicting, perfect\n  // for Post-Base Name-Indexed literal with idx=7\n  req.emplace_back(\"Blarf7\", \"blergblergblerg\");\n  auto result = encoder.encode(req, 10, 1);\n  EXPECT_EQ(result.stream->computeChainDataLength(),\n            2 /*prefix*/ + 8 /*pb indexed*/ + 2 /*name idx len*/ +\n                1 /*val len*/ + 15 /* value */);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, TestOutstandingListTooLong) {\n  QPACKEncoder encoder(false, 4096);\n  encoder.setMaxNumOutstandingBlocks(3);\n\n  for (int i = 0; i < 4; i++) {\n    vector<HPACKHeader> req;\n    req.emplace_back(string(\"monkey\" + folly::to<std::string>(i)),\n                     folly::to<string>(i));\n    auto result = encoder.encode(req, 10, 7);\n\n    if (i == 3) {\n      // The header has been encoded as a literal\n      EXPECT_TRUE(stringInOutput(result.stream.get(), \"monkey3\"));\n    } else {\n      // The header is indexed\n      EXPECT_EQ(result.stream->computeChainDataLength(),\n                2 /*prefix*/ + 1 /*pb indexed*/);\n    }\n  }\n\n  // See that static references still work\n  vector<HPACKHeader> reqStatic1;\n  reqStatic1.emplace_back(\":method\", \"GET\");\n  auto result = encoder.encode(reqStatic1, 10, 7);\n\n  // The header is indexed\n  EXPECT_EQ(result.stream->computeChainDataLength(),\n            2 /*prefix*/ + 1 /*pb indexed*/);\n\n  // See that dynamic name references don't work\n  vector<HPACKHeader> reqDynamicName;\n  reqDynamicName.emplace_back(\"monkey2\", \"banana2\");\n  result = encoder.encode(reqDynamicName, 10, 7);\n\n  // The header has been encoded as a literal\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"monkey2\"));\n\n  // See that static name references work\n  vector<HPACKHeader> reqStatic2;\n  reqStatic2.emplace_back(\":authority\", \"potato\");\n  result = encoder.encode(reqStatic2, 10, 7);\n\n  EXPECT_EQ(\n      result.stream->computeChainDataLength(),\n      2 /*prefix*/ + 1 /*name index*/ + 1 /*value length*/ + 6 /*value bytes*/);\n\n  // Ack the header, and see that new dynamic references can be made\n  encoder.onHeaderAck(7, false);\n  result = encoder.encode(reqDynamicName, 10, 7);\n  EXPECT_EQ(result.stream->computeChainDataLength(),\n            2 /*prefix*/ + 1 /*pb indexed*/);\n}\n\nTEST(QPACKContextTests, TestOutstandingListAckingAll) {\n  QPACKEncoder encoder(false, 4096);\n  encoder.setMaxNumOutstandingBlocks(5);\n\n  for (int i = 0; i < 8; i++) {\n    vector<HPACKHeader> req;\n    std::string headerName = \"monkey\" + folly::to<std::string>(i);\n    req.emplace_back(headerName, folly::to<string>(i));\n\n    if (i < 3) {\n      // make stream 1's outstanding list have a size of 3\n      // all of these should be indexed\n      auto result = encoder.encode(req, 10, 1);\n      EXPECT_FALSE(stringInOutput(result.stream.get(), \"monkey\"));\n    } else if (i < 5) {\n      // make stream 2's outstanding list have a size of 2\n      // all of these should be indexed\n      auto result = encoder.encode(req, 10, 2);\n      EXPECT_FALSE(stringInOutput(result.stream.get(), \"monkey\"));\n    }\n\n    if (i == 4) {\n      // ack all three of stream 1's headers. This should leave room for three\n      // more\n      encoder.onHeaderAck(1, true);\n    }\n\n    if (i >= 4 && i < 7) {\n      // put in three more blocks into the outstanding list. These should be\n      // indexed.\n      auto result = encoder.encode(req, 10, 1);\n      EXPECT_FALSE(stringInOutput(result.stream.get(), \"monkey\"));\n    }\n\n    if (i == 7) {\n      // since we're at the limit of the number of outstanding blocks, this\n      // should be encoded as a literal\n      auto result = encoder.encode(req, 10, 1);\n      EXPECT_TRUE(stringInOutput(result.stream.get(), \"monkey\"));\n    }\n  }\n}\n\nTEST(QPACKContextTests, Unacknowledged) {\n  QPACKEncoder encoder(true, 128);\n  QPACKDecoder decoder(128);\n  // Disallow unack'd headers\n  encoder.setMaxVulnerable(0);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  auto result = encoder.encode(req, 10, 1);\n\n  // Stream will encode a literal: prefix(2) + <more than 1>\n  EXPECT_GT(result.stream->computeChainDataLength(), 3);\n  verifyDecode(decoder, std::move(result), req);\n\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  result = encoder.encode(req, 10, 2);\n  EXPECT_GT(result.stream->computeChainDataLength(), 4);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, TestDraining) {\n  QPACKEncoder encoder(false, 128);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"accept-encoding\", \"gzip,deflate\");\n  auto result = encoder.encode(req, 0, 1);\n\n  // This will result in the first header being drained in the middle\n  // of encoding the new control channel, and force a literal.\n  req.clear();\n  req.emplace_back(\"accept-encoding\", \"sdch,gzip\");\n  req.emplace_back(\"accept-encoding\", \"gzip,deflate\");\n  result = encoder.encode(req, 0, 2);\n  EXPECT_GT(result.stream->computeChainDataLength(), 4);\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"gzip,deflate\"));\n}\n\nTEST(QPACKContextTests, TestDuplicate) {\n  QPACKEncoder encoder(false, 200);\n  QPACKDecoder decoder(200);\n  vector<HPACKHeader> req;\n  // 5 inserts and one literal\n  for (auto i = 0; i < 6; i++) {\n    req.emplace_back(folly::to<string>('a' + i), folly::to<string>(i));\n  }\n  // a=0 should now be draining\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(encoder.onInsertCountIncrement(5), HPACK::DecodeError::NONE);\n  EXPECT_EQ(headerAck(decoder, encoder, 1), HPACK::DecodeError::NONE);\n  req.erase(req.begin() + 1, req.end());\n  result = encoder.encode(req, 0, 2);\n  // Control contains one-byte duplicate instruction, stream prefix + 1\n  EXPECT_EQ(result.control->computeChainDataLength(), 1);\n  EXPECT_EQ(result.stream->computeChainDataLength(), 3);\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, TestTableSizeUpdate) {\n  QPACKEncoder encoder(false, 100);\n  QPACKDecoder decoder(200);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(encoder.onInsertCountIncrement(2), HPACK::DecodeError::NONE);\n  EXPECT_EQ(headerAck(decoder, encoder, 1), HPACK::DecodeError::NONE);\n  encoder.setHeaderTableSize(64, false); // This will evict the oldest header\n  EXPECT_EQ(encoder.getHeadersStored(), 1);\n  result = encoder.encode(req, 0, 2);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(decoder.getHeadersStored(), 1);\n  EXPECT_EQ(headerAck(decoder, encoder, 2), HPACK::DecodeError::NONE);\n\n  encoder.setHeaderTableSize(100, false);\n  result = encoder.encode(req, 0, 3);\n  EXPECT_EQ(encoder.getHeadersStored(), 2);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(decoder.getHeadersStored(), 2);\n}\n\nTEST(QPACKContextTests, TestTableSizeUpdateMax) {\n  // Encoder has table size 200 but decoder has 100.\n  // Encoder never sends a TSU, and overflows the table.\n  // Decoder fails\n  QPACKEncoder encoder(false, 200);\n  QPACKDecoder decoder(100);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  req.emplace_back(\"Blarf\", \"Blingo\");\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(\n      decoder, std::move(result), req, HPACK::DecodeError::INVALID_INDEX);\n  EXPECT_EQ(decoder.getHeadersStored(), 2);\n}\n\nTEST(QPACKContextTests, TestEncoderFlowControl) {\n  QPACKEncoder encoder(false, 170);\n  QPACKDecoder decoder(170);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  req.emplace_back(\"Blarf\", \"Blerg\");\n  req.emplace_back(\"Blarf\", \"Blingo\");\n  auto result = encoder.encode(req, 0, 1, 0);\n  EXPECT_EQ(result.control, nullptr);\n  verifyDecode(decoder, std::move(result), req, HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.getHeadersStored(), 0);\n\n  // There is enough room for the first header only\n  result = encoder.encode(req, 0, 1, 11);\n  EXPECT_EQ(result.control->computeChainDataLength(), 11);\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"Blah\"));\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"Blerg\"));\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"Blingo\"));\n  verifyDecode(decoder, std::move(result), req, HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.getHeadersStored(), 1);\n\n  // Blarf is name indexed, Blah is indexed, Blerg fits, Blingo is encoded but\n  // doesn't get used because it only half-fits\n  result = encoder.encode(req, 0, 1, 10);\n  EXPECT_EQ(result.control->computeChainDataLength(), 15);\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"Blah\"));\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"Blerg\"));\n  EXPECT_TRUE(stringInOutput(result.control.get(), \"Blingo\"));\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"Blingo\"));\n  auto controlTail = result.control->clone();\n  controlTail->trimStart(10);\n  result.control->trimEnd(5);\n  verifyDecode(decoder, std::move(result), req, HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.getHeadersStored(), 2);\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(controlTail)),\n            HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.getHeadersStored(), 3);\n\n  // Blah is now drained, so the next encode should produce a duplicate we\n  // can't use\n  req.erase(req.begin() + 1, req.end());\n  result = encoder.encode(req, 0, 1, 0);\n  EXPECT_EQ(result.control->computeChainDataLength(), 1);\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"Blah\"));\n  verifyDecode(decoder, std::move(result), req, HPACK::DecodeError::NONE);\n}\n\nTEST(QPACKContextTests, TestAcks) {\n  QPACKEncoder encoder(false, 100);\n  QPACKDecoder decoder(100);\n  encoder.setMaxVulnerable(1);\n  EXPECT_EQ(encoder.onInsertCountIncrement(1), HPACK::DecodeError::INVALID_ACK);\n  EXPECT_EQ(headerAck(decoder, encoder, 1), HPACK::DecodeError::INVALID_ACK);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"BlahBlahBlah\");\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  req.clear();\n  req.emplace_back(\"accept-encoding\", \"gzip, deflate\");\n  result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  req.clear();\n  req.emplace_back(\"Blarf\", \"BlahBlahBlah\");\n  result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n\n  // Blarf: Blah is unacknowledged and maxVulnerable is 1 -> literal\n  result = encoder.encode(req, 0, 2);\n  EXPECT_EQ(result.control, nullptr);\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"blarf\"));\n  verifyDecode(decoder, std::move(result), req);\n\n  // Table is full and Blarf: BlahBlahBlah cannot be evicted -> literal\n  req.clear();\n  req.emplace_back(\"Foo\", \"BlahBlahBlahBlah!\");\n  result = encoder.encode(req, 0, 3);\n  EXPECT_EQ(result.control, nullptr);\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"foo\"));\n  verifyDecode(decoder, std::move(result), req);\n  // ack is invalid because it's a pure literal\n  EXPECT_EQ(headerAck(decoder, encoder, 3), HPACK::DecodeError::INVALID_ACK);\n\n  // Should remove all encoder state.  Blarf: BlahBlahBlah can now be evicted\n  // and a new vulnerable reference can be made.\n  // stream 2 block was pure literals\n  EXPECT_EQ(headerAck(decoder, encoder, 2), HPACK::DecodeError::INVALID_ACK);\n  EXPECT_EQ(cancelStream(decoder, encoder, 1), HPACK::DecodeError::NONE);\n  EXPECT_EQ(encoder.onInsertCountIncrement(1), HPACK::DecodeError::NONE);\n\n  result = encoder.encode(req, 0, 2);\n  // Encodes an insert\n  EXPECT_GT(result.control->computeChainDataLength(), 1);\n  EXPECT_EQ(result.stream->computeChainDataLength(), 3);\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"foo\"));\n  verifyDecode(decoder, std::move(result), req);\n\n  EXPECT_EQ(encoder.onInsertCountIncrement(0), HPACK::DecodeError::INVALID_ACK);\n}\n\nTEST(QPACKContextTests, TestEncodeBlocksSelfEviction) {\n  // The references already made in an encode prevent eviction even before\n  // finishing the encode.\n  QPACKEncoder encoder(false, 192); // min free 48\n  QPACKDecoder decoder(192);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"aaaa\", \"xxxxxxxxxxxx\"); // 48\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(encoder.onHeaderAck(1, false), HPACK::DecodeError::NONE);\n\n  req.emplace_back(\"bbbb\", \"xxxxxxxxxxxx\");  // 48\n  req.emplace_back(\"cccc\", \"xxxxxxxxxxxxx\"); // 49, drains A\n  req.emplace_back(\"dddd\", \"xxxxxxxxxxxxx\"); // 48, not enough space\n  result = encoder.encode(req, 0, 1);\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"aaaa\"));\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"dddd\"));\n  verifyDecode(decoder, std::move(result), req);\n}\n\nTEST(QPACKContextTests, TestImplicitAcks) {\n  QPACKEncoder encoder(false, 1024);\n  QPACKDecoder decoder(1024);\n  encoder.setMaxVulnerable(2);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(decoder, std::move(result), req);\n  req.emplace_back(\"Foo\", \"Blah\");\n  result = encoder.encode(req, 0, 2);\n  verifyDecode(decoder, std::move(result), req);\n  EXPECT_EQ(encoder.onHeaderAck(2, false), HPACK::DecodeError::NONE);\n  // both headers are now acknowledged, 1 unacked header allowed\n  req.clear();\n  req.emplace_back(\"Bar\", \"Binky\");\n  result = encoder.encode(req, 0, 3);\n\n  // No unacked headers allowed\n  req.emplace_back(\"Blarf\", \"Blah\");\n  req.emplace_back(\"Foo\", \"Blah\");\n  result = encoder.encode(req, 0, 4);\n  EXPECT_FALSE(stringInOutput(result.stream.get(), \"Blah\"));\n  verifyDecode(decoder, std::move(result), req);\n\n  // cancel\n  EXPECT_EQ(encoder.onHeaderAck(2, true), HPACK::DecodeError::NONE);\n  EXPECT_EQ(encoder.onHeaderAck(4, true), HPACK::DecodeError::NONE);\n}\n\nTEST(QPACKContextTests, TestDecodeQueue) {\n  QPACKEncoder encoder(false, 64);\n  QPACKDecoder decoder(64);\n\n  vector<HPACKHeader> req1;\n  req1.emplace_back(\"Blarf\", \"Blah\");\n  auto result1 = encoder.encode(req1, 0, 1);\n\n  vector<HPACKHeader> req2;\n  req2.emplace_back(\"Blarf\", \"Blerg\");\n  auto result2 = encoder.encode(req2, 0, 2);\n  verifyDecode(decoder, std::move(result2), req2);\n  verifyDecode(decoder, std::move(result1), req1);\n}\n\nTEST(QPACKContextTests, TestDecodeQueueDelete) {\n  // This test deletes the decoder from a callback while there are items in\n  // the queue\n  QPACKEncoder encoder(true, 100);\n  auto decoder = std::make_unique<QPACKDecoder>(100);\n\n  vector<HPACKHeader> req1;\n  req1.emplace_back(\"Blarf\", \"Blah\");\n  auto result1 = encoder.encode(req1, 0, 1);\n\n  vector<HPACKHeader> req2;\n  req2.emplace_back(\"Blarf\", \"Blerg\");\n  auto result2 = encoder.encode(req2, 0, 2);\n\n  // Decode #1, no control stream, queued\n  auto cb1 = std::make_unique<TestStreamingCallback>();\n  auto rawCb1 = cb1.get();\n  auto rawDecoder = decoder.get();\n  cb1->headersCompleteCb = [decoder = std::move(decoder)]() mutable {\n    // Delete decoder from callback\n    decoder.reset();\n  };\n  auto length = result1.stream->computeChainDataLength();\n  rawDecoder->decodeStreaming(1, std::move(result1.stream), length, rawCb1);\n\n  // Decode #2, no control stream, queued\n  auto cb2 = std::make_unique<TestStreamingCallback>();\n  length = result2.stream->computeChainDataLength();\n  rawDecoder->decodeStreaming(2, std::move(result2.stream), length, cb2.get());\n\n  // Decode control stream #1, will unblock 1 and delete decoder\n  EXPECT_EQ(rawDecoder->decodeEncoderStream(std::move(result1.control)),\n            HPACK::DecodeError::NONE);\n\n  // cb2 doesn't execute because the decoder was destroyed from cb1\n  EXPECT_EQ(cb2->error, HPACK::DecodeError::NONE);\n  EXPECT_EQ(cb2->headers.size(), 0);\n}\n\nTEST(QPACKContextTests, TestDecodeQueueResetSelf) {\n  // This test calls cancelStream from inside the callback from drainQueue\n  QPACKEncoder encoder(true, 100);\n  QPACKDecoder decoder(100);\n\n  vector<HPACKHeader> req1;\n  req1.emplace_back(\"Blarf\", \"Blah\");\n  auto result1 = encoder.encode(req1, 0, 1);\n\n  // Decode #1, no control stream, queued\n  TestStreamingCallback cb1;\n  cb1.headersCompleteCb = [&] { decoder.encodeCancelStream(1); };\n  auto length = result1.stream->computeChainDataLength();\n  decoder.decodeStreaming(1, std::move(result1.stream), length, &cb1);\n\n  // Decode control stream #1, will unblock 1 and reset it\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(result1.control)),\n            HPACK::DecodeError::NONE);\n}\n\nTEST(QPACKContextTests, TestEncoderStreamEndBlocked) {\n  // This test queues a blocked stream, then ends the encoder stream\n  QPACKEncoder encoder(true, 100);\n  QPACKDecoder decoder(100);\n\n  vector<HPACKHeader> req1;\n  req1.emplace_back(\"Blarf\", \"Blah\");\n  auto result1 = encoder.encode(req1, 0, 1);\n\n  // Decode #1, no control stream, queued\n  TestStreamingCallback cb1;\n  auto length = result1.stream->computeChainDataLength();\n  decoder.decodeStreaming(1, std::move(result1.stream), length, &cb1);\n\n  EXPECT_EQ(decoder.encoderStreamEnd(), HPACK::DecodeError::NONE);\n  EXPECT_EQ(cb1.error, HPACK::DecodeError::ENCODER_STREAM_CLOSED);\n}\n\nTEST(QPACKContextTests, TestEncoderStreamEndUnderflow) {\n  // This test ends the encoder & decoder streams mid-instruction\n  QPACKEncoder encoder(true, 100);\n  QPACKDecoder decoder(100);\n\n  vector<HPACKHeader> req1;\n  req1.emplace_back(\"Blarf\", \"Blah\");\n  auto result1 = encoder.encode(req1, 0, 1);\n\n  folly::IOBufQueue q;\n  q.append(std::move(result1.control));\n  EXPECT_EQ(decoder.decodeEncoderStream(q.split(1)), HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.encoderStreamEnd(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n  // ? = 63 insert count with all 1's filled\n  EXPECT_EQ(encoder.decodeDecoderStream(folly::IOBuf::copyBuffer(\"?\", 1)),\n            HPACK::DecodeError::NONE);\n  EXPECT_EQ(encoder.decoderStreamEnd(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n}\n\nTEST(QPACKContextTests, TestDecodeMaxUncompressed) {\n  QPACKEncoder encoder(false, 64);\n  QPACKDecoder decoder(64);\n  decoder.setMaxUncompressed(5);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"Blarf\", \"Blah\");\n  auto result = encoder.encode(req, 0, 1);\n  verifyDecode(\n      decoder, std::move(result), req, HPACK::DecodeError::HEADERS_TOO_LARGE);\n}\n\nTEST(QPACKContextTests, TestDecoderStreamChunked) {\n  QPACKEncoder encoder(false, 5000);\n  QPACKDecoder decoder(5000);\n\n  vector<HPACKHeader> req;\n  for (auto i = 0; i < 128; i++) {\n    req.emplace_back(\"a\", folly::to<string>(i));\n  }\n  auto result = encoder.encode(req, 0, 1);\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(result.control)),\n            HPACK::DecodeError::NONE);\n  auto ack = decoder.encodeInsertCountInc();\n  EXPECT_EQ(ack->computeChainDataLength(), 2);\n  auto ackPart = ack->clone();\n  ackPart->trimEnd(1);\n  ack->trimStart(1);\n  EXPECT_EQ(encoder.decodeDecoderStream(std::move(ackPart)),\n            HPACK::DecodeError::NONE);\n  EXPECT_EQ(encoder.decodeDecoderStream(std::move(ack)),\n            HPACK::DecodeError::NONE);\n  EXPECT_FALSE(encoder.getTable().isVulnerable(128));\n  EXPECT_TRUE(encoder.getTable().isVulnerable(129));\n}\n\nTEST(QPACKContextTests, TestEncoderStreamReorder) {\n  QPACKEncoder encoder(false, 0);\n  QPACKDecoder decoder(0);\n\n  decoder.setHeaderTableMaxSize(4096);\n  encoder.setHeaderTableSize(4096);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"dynamic\", \"header\");\n  auto result = encoder.encode(req, 0, 1);\n  EXPECT_EQ(result.stream->computeChainDataLength() +\n                result.control->computeChainDataLength(),\n            21);\n  TestStreamingCallback cb1;\n  bool done = false;\n  cb1.headersCompleteCb = [&] { done = true; };\n  auto length = result.stream->computeChainDataLength();\n  decoder.decodeStreaming(1, std::move(result.stream), length, &cb1);\n  // Should be blocked on insert\n  EXPECT_FALSE(done);\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(result.control)),\n            HPACK::DecodeError::NONE);\n  EXPECT_TRUE(done);\n  EXPECT_EQ(*cb1.hpackHeaders(), req);\n  EXPECT_EQ(cb1.decodedSize_.uncompressed, 15);\n  EXPECT_EQ(cb1.decodedSize_.compressedBlock, 3);\n  EXPECT_EQ(cb1.decodedSize_.compressed, 21);\n}\n\nTEST(QPACKContextTests, TestEncoderTableLimit) {\n  QPACKEncoder encoder(false, 0);\n  encoder.setHeaderTableSize(std::numeric_limits<uint32_t>::max());\n  EXPECT_EQ(encoder.getTableSize(), 1u << 16);\n  EXPECT_EQ(encoder.getMaxHeaderTableSize(),\n            std::numeric_limits<uint32_t>::max());\n}\n\nTEST(QPACKContextTests, TestDecodePartialControl) {\n  QPACKEncoder encoder(false, 1000);\n  QPACKDecoder decoder(1000);\n\n  vector<HPACKHeader> req;\n  req.emplace_back(\"abcdeabcdeabcdeabcdeabcdeabcdeabcde\",\n                   \"vwxyzvwxyzvwxyzvwxyzvwxyzvwxyzvwxyz\");\n  auto result = encoder.encode(req, 0, 1);\n  folly::io::Cursor c(result.control.get());\n  while (!c.isAtEnd()) {\n    std::unique_ptr<folly::IOBuf> buf;\n    c.clone(buf, 1);\n    EXPECT_EQ(decoder.decodeEncoderStream(std::move(buf)),\n              HPACK::DecodeError::NONE);\n  }\n  EXPECT_EQ(decoder.getHeadersStored(), 1);\n  EXPECT_EQ(decoder.getHeader(false, 1, 1, false), req[0]);\n}\n\nTEST(QPACKContextTests, WrapRICBehind) {\n  // This tests how RIC wraps when the encoder and decoder have the same state\n  uint32_t tableSize = 1024;\n  uint32_t maxEntries = tableSize / 32;\n  uint32_t realMaxEntries = tableSize / (32 + sizeof(\"999\"));\n\n  QPACKEncoder encoder(true, tableSize);\n  QPACKDecoder decoder(tableSize);\n  encoder.setMinFreeForTesting(0);\n  for (uint32_t decoderIC = 0; decoderIC < maxEntries * 3; decoderIC++) {\n    if (decoderIC > 0) {\n      // add one more header to decoder\n      vector<HPACKHeader> req;\n      VLOG(5) << \"priming decoder with h=\" << decoderIC\n              << \" decoderIC=\" << decoderIC;\n      req.emplace_back(toFixedLengthString(decoderIC), \"\");\n      auto result = encoder.encode(req, 10, 1);\n      EXPECT_NE(result.control, nullptr)\n          << \"Every encode should produce an insert\";\n      EXPECT_TRUE(*verifyDecode(decoder, std::move(result), req));\n      EXPECT_EQ(encoder.decodeDecoderStream(decoder.encodeHeaderAck(1)),\n                HPACK::DecodeError::NONE);\n    }\n    for (auto requiredIC =\n             std::max<int64_t>(0, int64_t(decoderIC) - realMaxEntries + 1);\n         requiredIC <= decoderIC;\n         requiredIC++) {\n      VLOG(5) << \"WrapRIC test decoderIC=\" << decoderIC\n              << \" requiredIC=\" << requiredIC;\n\n      // Now send encode a request for the given RIC.\n      vector<HPACKHeader> req;\n      if (requiredIC > 0) {\n        req.emplace_back(toFixedLengthString(requiredIC), \"\");\n      } else {\n        req.emplace_back(\":scheme\", \"https\");\n      }\n      auto result = encoder.encode(req, 10, 2);\n      EXPECT_EQ(result.control, nullptr);                   // no inserts\n      CHECK_EQ(result.stream->computeChainDataLength(), 3); // prefix + 1\n      // the decoder should be able to immediately decode it\n      EXPECT_TRUE(*verifyDecode(decoder, std::move(result), req));\n      encoder.decodeDecoderStream(decoder.encodeHeaderAck(2));\n    }\n  }\n}\n\nTEST(QPACKContextTests, WrapRICAhead) {\n  // This tests how RIC wraps when the encoder is up to a full table ahead of\n  // the decoder.  tableSize is set such that realMaxEntries=64, which prevents\n  // RIC from being too far from base index as to expand the prefix.\n  uint32_t tableSize = 4064;\n  uint32_t maxEntries = tableSize / 32;\n  uint32_t realMaxEntries = tableSize / (32 + sizeof(\"999\"));\n\n  // With QPACK-02, this would have produced an encoded stream buffer of 4\n  // bytes.  Each loop of decoderIC is expensive, so start it at maxEntries,\n  // and only run it until it actually would have made a difference in\n  // the encoded size of required IC.\n  CHECK_LE(realMaxEntries, 256);\n  for (uint32_t decoderIC = maxEntries; decoderIC < (256 - realMaxEntries);\n       decoderIC++) {\n    QPACKEncoder encoder(true, tableSize);\n    QPACKDecoder decoder(tableSize);\n    encoder.setMaxVulnerable(realMaxEntries);\n    decoder.setMaxBlocking(realMaxEntries);\n    encoder.setMinFreeForTesting(0);\n    for (uint32_t i = 1; i <= decoderIC; i++) {\n      vector<HPACKHeader> req;\n      // populate the encoder and decode table to decoderIC.\n      VLOG(5) << \"priming decoder with h=\" << i << \" decoderIC=\" << decoderIC;\n      req.emplace_back(toFixedLengthString(i), \"\");\n      auto result = encoder.encode(req, 10, 1);\n      EXPECT_NE(result.control, nullptr)\n          << \"Every encode should produce an insert\";\n      EXPECT_TRUE(*verifyDecode(decoder, std::move(result), req));\n      EXPECT_EQ(encoder.decodeDecoderStream(decoder.encodeHeaderAck(1)),\n                HPACK::DecodeError::NONE);\n    }\n    folly::IOBufQueue controlQueue{folly::IOBufQueue::cacheChainLength()};\n    std::list<std::shared_ptr<bool>> allDone;\n    vector<vector<HPACKHeader>> reqs;\n    reqs.reserve(2 * realMaxEntries);\n    // encode realMaxEntries requests past decoderIC, and queue the decodes\n    // but don't process the inserts\n    for (auto requiredIC = decoderIC + 1;\n         requiredIC <= decoderIC + realMaxEntries;\n         requiredIC++) {\n      VLOG(5) << \"WrapRIC test decoderIC=\" << decoderIC\n              << \" requiredIC=\" << requiredIC;\n      reqs.emplace_back();\n      auto& req = reqs.back();\n      req.emplace_back(toFixedLengthString(requiredIC), \"\");\n      auto result = encoder.encode(req, 10, requiredIC);\n      EXPECT_NE(result.control, nullptr)\n          << \"Every encode should produce an insert\";\n      controlQueue.append(std::move(result.control));\n      CHECK_EQ(result.stream->computeChainDataLength(), 3); // prefix + 1\n      // the decoder has to block because the control stream is pending.\n      // This verifies the whole batch of encodes against the same decoderIC\n      allDone.emplace_back(verifyDecode(decoder, std::move(result), req));\n    }\n    // control block should unblock all requests\n    decoder.decodeEncoderStream(controlQueue.move());\n    for (const auto& done : allDone) {\n      EXPECT_TRUE(*done);\n    }\n  }\n}\n\nvoid checkQError(QPACKDecoder& decoder,\n                 std::unique_ptr<IOBuf> buf,\n                 const HPACK::DecodeError err) {\n  auto cb = std::make_unique<TestStreamingCallback>();\n  auto len = buf->computeChainDataLength();\n  // streamID only matters for cancellation\n  decoder.decodeStreaming(0, std::move(buf), len, cb.get());\n  EXPECT_EQ(cb->error, err);\n}\n\nTEST(QPACKContextTests, DecodeErrors) {\n  QPACKDecoder decoder(128);\n  unique_ptr<IOBuf> buf = IOBuf::create(128);\n\n  VLOG(10) << \"Required IC underflow\";\n  buf->writableData()[0] = 0xFF;\n  buf->append(1);\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Required IC > 2*ME\";\n  buf->writableData()[0] = 0x09;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Required IC invalid encoding\";\n  buf->writableData()[0] = 0x06;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Base delta missing\";\n  buf->writableData()[0] = 0x01;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Base delta invalid\";\n  buf->writableData()[1] = 0xFF;\n  buf->append(1);\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Base delta too negative\";\n  buf->writableData()[0] = 0x02;\n  buf->writableData()[1] = 0x83;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Base delta = LR\";\n  buf->writableData()[1] = 0x81;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"LR + deltaBase >= 2^32\";\n  HPACKEncodeBuffer encBuf(128, true);\n  encBuf.encodeInteger(2);\n  encBuf.encodeInteger((uint64_t(1) << 32) - 1, HPACK::Q_DELTA_BASE);\n  checkQError(decoder, encBuf.release(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Exceeds blocking max\";\n  decoder.setMaxBlocking(0);\n  buf->writableData()[0] = 0x02;\n  buf->writableData()[1] = 0x00;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::TOO_MANY_BLOCKING);\n\n  VLOG(10) << \"Non-zero insert count when decoder disabled dynamic table\";\n  QPACKDecoder zeroDecoder(0);\n  checkQError(zeroDecoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  // valid prefix\n  buf->writableData()[0] = 0x00;\n  buf->writableData()[1] = 0x00;\n\n  VLOG(10) << \"Literal bad name index\";\n  buf->writableData()[2] = 0x4F;\n  buf->append(1);\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Invalid literal name index\";\n  buf->writableData()[2] = 0x41;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Literal name index == 2^32 - 1\";\n  encBuf.encodeInteger(0);\n  encBuf.encodeInteger(0);\n  encBuf.encodeInteger(std::numeric_limits<uint32_t>::max(),\n                       HPACK::Q_LITERAL_NAME_REF);\n  checkQError(decoder, encBuf.release(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Post-base index > 2^32 - 1\";\n  encBuf.encodeInteger(std::numeric_limits<uint32_t>::max() - 5);\n  encBuf.encodeInteger(0);\n  encBuf.encodeInteger(5, HPACK::Q_INDEXED);\n  checkQError(decoder, encBuf.release(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"Literal bad name length\";\n  buf->writableData()[2] = 0x27;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Literal invalid value length\";\n  buf->writableData()[2] = 0x51;\n  buf->writableData()[3] = 0xFF;\n  buf->append(1);\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  buf->trimEnd(1);\n  VLOG(10) << \"Bad Index\";\n  buf->writableData()[2] = 0xBF;\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::BUFFER_UNDERFLOW);\n\n  VLOG(10) << \"Index static index\";\n  buf->writableData()[2] = 0xFF;\n  buf->writableData()[3] = 0x7E;\n  buf->append(1);\n  checkQError(decoder, buf->clone(), HPACK::DecodeError::INVALID_INDEX);\n\n  VLOG(10) << \"No error after previous error\";\n  buf->writableData()[0] = 0xC1;\n  buf->writableData()[1] = 0x01;\n  buf->writableData()[2] = 0x41;\n  buf->trimEnd(1);\n  EXPECT_EQ(decoder.decodeEncoderStream(buf->clone()),\n            HPACK::DecodeError::NONE);\n\n  VLOG(10) << \"Control decode error\";\n  QPACKDecoder decoder2(64);\n  buf->writableData()[0] = 0x01; // duplicate dynamic index 1\n  buf->trimEnd(2);\n  EXPECT_EQ(decoder2.decodeEncoderStream(buf->clone()),\n            HPACK::DecodeError::INVALID_INDEX);\n\n  QPACKEncoder encoder(true, 128);\n  buf->writableData()[0] = 0xFF;\n  buf->writableData()[1] = 0x80;\n  buf->writableData()[2] = 0xFF;\n  buf->writableData()[3] = 0xFF;\n  buf->writableData()[4] = 0xFF;\n  buf->writableData()[5] = 0xFF;\n  buf->writableData()[6] = 0xFF;\n  buf->writableData()[7] = 0xFF;\n  buf->writableData()[8] = 0xFF;\n  buf->writableData()[9] = 0xFF;\n  buf->writableData()[10] = 0x7F;\n  buf->append(10);\n\n  VLOG(10) << \"Bad header ack\";\n  EXPECT_EQ(encoder.decodeDecoderStream(buf->clone()),\n            HPACK::DecodeError::INTEGER_OVERFLOW);\n\n  VLOG(10) << \"Bad cancel\";\n  buf->writableData()[0] = 0x7F;\n  buf->writableData()[10] = 0xFF;\n  buf->writableData()[11] = 0x01;\n  buf->append(1);\n  EXPECT_EQ(encoder.decodeDecoderStream(buf->clone()),\n            HPACK::DecodeError::INTEGER_OVERFLOW);\n\n  VLOG(10) << \"Bad table state sync\";\n  buf->writableData()[0] = 0x3F;\n  EXPECT_EQ(encoder.decodeDecoderStream(buf->clone()),\n            HPACK::DecodeError::INTEGER_OVERFLOW);\n\n  VLOG(10) << \"Insert too large\";\n  vector<HPACKHeader> req;\n  req.emplace_back(\"X-Header-Too-Big\", \"aaaaaaaaaaaaaaaaa\");\n  auto result = encoder.encode(req, 10, 1);\n  EXPECT_EQ(decoder2.decodeEncoderStream(std::move(result.control)),\n            HPACK::DecodeError::INSERT_TOO_LARGE);\n}\n\nTEST(QPACKContextTests, TestEvictedNameReference) {\n  QPACKEncoder encoder(false, 109);\n  QPACKDecoder decoder(109);\n  encoder.setMaxVulnerable(0);\n  vector<HPACKHeader> req;\n  req.emplace_back(\"x-accept-encoding\", \"foobarfoobar\");\n  auto result = encoder.encode(req, 0, 1);\n  decoder.decodeEncoderStream(std::move(result.control));\n  decoder.decodeStreaming(1,\n                          result.stream->clone(),\n                          result.stream->computeChainDataLength(),\n                          nullptr);\n  encoder.onInsertCountIncrement(1);\n  req.clear();\n  req.emplace_back(\"x-accept-encoding\", \"barfoobarfoo\");\n  result = encoder.encode(req, 0, 2);\n  EXPECT_TRUE(stringInOutput(result.stream.get(), \"x-accept-encoding\"));\n  TestStreamingCallback cb;\n  decoder.decodeEncoderStream(std::move(result.control));\n  decoder.decodeStreaming(\n      2, result.stream->clone(), result.stream->computeChainDataLength(), &cb);\n  EXPECT_FALSE(cb.hasError());\n}\n\nTEST(QPACKContextTests, TestFragmentTableSizeUpdate) {\n  QPACKEncoder encoder(true, 0);\n  QPACKDecoder decoder(0, 10000);\n\n  decoder.setHeaderTableMaxSize(5120);\n\n  EXPECT_TRUE(encoder.setHeaderTableSize(4096));\n  vector<HPACKHeader> headers;\n  headers.emplace_back(\"x-accept-encoding\", \"foobarfoobar\");\n  auto encodeResult = encoder.encode(headers, 10, 1);\n\n  ASSERT_NE(encodeResult.control.get(), nullptr);\n\n  /*\n   * Split the control stream, so that the table size instruction is straddled\n   * between the two splits. The first instruction in the control stream is a\n   * table size instruction, with a value of 4096. This is due to the fact that\n   * we call client.setEncoderHeaderTableSize(4096).\n   */\n\n  // sanity check to confirm that the first instruction is indeed a table size\n  // update instruction.\n  uint8_t firstByte = *encodeResult.control->data();\n  EXPECT_GT(firstByte & HPACK::Q_TABLE_SIZE_UPDATE.code, 0);\n\n  auto controlLen = encodeResult.control->length();\n\n  // The first split has one byte\n  auto controlBegin = encodeResult.control->clone();\n  controlBegin->trimEnd(controlLen - 1);\n\n  // The second split has the remaining bytes\n  auto controlEnd = encodeResult.control->clone();\n  controlEnd->trimStart(1);\n\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(controlBegin)),\n            HPACK::DecodeError::NONE);\n  EXPECT_EQ(decoder.decodeEncoderStream(std::move(controlEnd)),\n            HPACK::DecodeError::NONE);\n\n  TestStreamingCallback cb;\n  auto length = encodeResult.stream->computeChainDataLength();\n  decoder.decodeStreaming(1, std::move(encodeResult.stream), length, &cb);\n  headerAck(decoder, encoder, 1);\n  auto result = cb.getResult();\n  EXPECT_TRUE(!result.hasError());\n\n  EXPECT_EQ(headers.size() * 2, result->headers.size());\n\n  size_t i = 0;\n  for (auto& h : headers) {\n    string name = h.name.get();\n    char* mutableName = (char*)name.data();\n    folly::toLowerAscii(mutableName, name.size());\n    EXPECT_EQ(name, result->headers[i++].str);\n    EXPECT_EQ(h.value, result->headers[i++].str);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/QPACKHeaderTableTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/http/codec/compress/QPACKHeaderTable.h>\n#include <sstream>\n\nnamespace proxygen {\n\nclass QPACKHeaderTableTests : public testing::Test {\n public:\n protected:\n  QPACKHeaderTable table_{320, true};\n};\n\nTEST_F(QPACKHeaderTableTests, Indexing) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  HPACKHeader agent(\"user-agent\", \"SeaMonkey\");\n\n  EXPECT_EQ(table_.getInsertCount(), 0);\n  table_.add(accept.copy());\n  EXPECT_EQ(table_.getInsertCount(), 1);\n  // Vulnerable - in the table\n  EXPECT_EQ(table_.getIndex(accept, false),\n            std::numeric_limits<uint32_t>::max());\n  // Allow vulnerable, get the index\n  EXPECT_EQ(table_.getIndex(accept, true), 1);\n  EXPECT_TRUE(table_.onInsertCountIncrement(1));\n  EXPECT_EQ(table_.getIndex(accept, false), 1);\n  table_.add(agent.copy());\n  // Indexes move\n  EXPECT_EQ(table_.getIndex(agent, true), 1);\n  EXPECT_EQ(table_.getIndex(accept, true), 2);\n}\n\nTEST_F(QPACKHeaderTableTests, Eviction) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n\n  int32_t max = 4;\n  uint32_t capacity = accept.bytes() * max;\n  table_.setCapacity(capacity);\n\n  for (auto i = 0; i < max; i++) {\n    EXPECT_TRUE(table_.add(accept.copy()));\n  }\n  table_.setMinInUseIndex(1);\n  table_.setAcknowledgedInsertCount(max);\n  EXPECT_FALSE(table_.canIndex(accept.name, accept.value));\n  EXPECT_FALSE(table_.add(accept.copy()));\n\n  table_.setMinInUseIndex(std::numeric_limits<uint32_t>::max());\n  EXPECT_TRUE(table_.canIndex(accept.name, accept.value));\n}\n\nTEST_F(QPACKHeaderTableTests, BadEviction) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n\n  int32_t max = 4;\n  uint32_t capacity = accept.bytes() * max;\n  table_.setCapacity(capacity);\n\n  for (auto i = 0; i < max; i++) {\n    EXPECT_TRUE(table_.add(accept.copy()));\n  }\n  EXPECT_EQ(table_.size(), max);\n  EXPECT_FALSE(table_.setCapacity(capacity / 2));\n  EXPECT_EQ(table_.size(), max);\n\n  // Ack all headers but mark the first as in use\n  table_.setAcknowledgedInsertCount(max);\n  table_.setMinInUseIndex(1);\n  EXPECT_FALSE(table_.setCapacity(capacity / 2));\n\n  // Clear all refs\n  table_.setMinInUseIndex(std::numeric_limits<uint32_t>::max());\n  EXPECT_TRUE(table_.setCapacity(capacity / 2));\n  EXPECT_EQ(table_.size(), max / 2);\n}\n\nTEST_F(QPACKHeaderTableTests, Wrapcount) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  HPACKHeader agent(\"user-agent\", \"SeaMonkey\");\n  HPACKHeader cookie(\"Cookie\", \"choco=chip\");\n\n  for (auto i = 0; i < 10; i++) {\n    EXPECT_TRUE(table_.add(accept.copy()));\n    table_.setAcknowledgedInsertCount(i + 1);\n  }\n  EXPECT_TRUE(table_.add(cookie.copy()));\n  EXPECT_TRUE(table_.add(agent.copy()));\n\n  EXPECT_EQ(table_.getInsertCount(), 12);\n  EXPECT_EQ(table_.getIndex(agent, true), 1);\n  EXPECT_EQ(table_.getIndex(cookie, true), 2);\n  EXPECT_EQ(table_.getIndex(accept, true), 3);\n  EXPECT_EQ(table_.getHeader(1, table_.getInsertCount()), agent);\n  EXPECT_EQ(table_.getHeader(2, table_.getInsertCount()), cookie);\n  EXPECT_EQ(table_.getHeader(table_.size(), table_.getInsertCount()), accept);\n}\n\nTEST_F(QPACKHeaderTableTests, NameIndex) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n  EXPECT_EQ(table_.nameIndex(accept.name), 0);\n  EXPECT_TRUE(table_.add(accept.copy()));\n  EXPECT_EQ(table_.nameIndex(accept.name), 1);\n}\n\nTEST_F(QPACKHeaderTableTests, GetIndex) {\n  HPACKHeader accept1(\"accept-encoding\", \"gzip\");\n  HPACKHeader accept2(\"accept-encoding\", \"blarf\");\n  EXPECT_EQ(table_.getIndex(accept1), 0);\n  EXPECT_TRUE(table_.add(accept1.copy()));\n  EXPECT_EQ(table_.getIndex(accept1), 1);\n  EXPECT_EQ(table_.getIndex(accept2), 0);\n}\n\nTEST_F(QPACKHeaderTableTests, Duplication) {\n  HPACKHeader accept(\"accept-encoding\", \"gzip\");\n\n  EXPECT_TRUE(table_.add(accept.copy()));\n\n  // Unnecessary duplicate\n  auto res = table_.maybeDuplicate(1, true);\n  EXPECT_FALSE(res.first);\n  EXPECT_EQ(res.second, 1);\n\n  for (auto i = 0; i < 6; i++) {\n    EXPECT_TRUE(table_.add(accept.copy()));\n    // Ack the first few entries so they can be evicted\n    table_.setAcknowledgedInsertCount(std::min(3u, table_.getInsertCount()));\n  }\n\n  // successful duplicate, vulnerable allowed\n  EXPECT_TRUE(table_.isDraining(table_.size()));\n  res = table_.maybeDuplicate(table_.size(), true);\n  EXPECT_TRUE(res.first);\n  EXPECT_EQ(res.second, 8);\n  EXPECT_EQ(table_.size(), 6); // evicted 1\n\n  // successful duplicate, vulnerable disallowed\n  EXPECT_TRUE(table_.onInsertCountIncrement(3));\n  res = table_.maybeDuplicate(table_.size(), false);\n  EXPECT_TRUE(res.first);\n  EXPECT_EQ(res.second, 0);\n  EXPECT_EQ(table_.size(), 6); // evicted 2\n\n  // Attempt to duplicate UNACKED\n  res = table_.maybeDuplicate(QPACKHeaderTable::UNACKED, true);\n  EXPECT_FALSE(res.first);\n  EXPECT_EQ(res.second, 0);\n  EXPECT_EQ(table_.size(), 6); // nothing changed\n  EXPECT_EQ(table_.getInsertCount(), 9);\n\n  // Hold a ref to oldest entry, prevents eviction\n  auto oldestAbsolute = table_.getInsertCount() - table_.size() + 1;\n  table_.setMinInUseIndex(oldestAbsolute);\n\n  // Table should be full\n  EXPECT_FALSE(table_.canIndex(accept.name, accept.value));\n\n  res = table_.maybeDuplicate(table_.size(), true);\n  EXPECT_FALSE(res.first);\n  EXPECT_EQ(res.second, 0);\n}\n\nTEST_F(QPACKHeaderTableTests, CanEvictWithRoom) {\n  HPACKHeader thirtyNineBytes(\"abcd\", \"efg\");\n  HPACKHeader fortySevenBytes(\"abcd\", \"efghijklmno\");\n  for (auto i = 0; i < 8; i++) {\n    EXPECT_TRUE(table_.add(thirtyNineBytes.copy()));\n  }\n  table_.setAcknowledgedInsertCount(table_.getInsertCount());\n  // abs index = 1 is evictable, but index = 2 is referenced, so we can\n  // insert up to (320 - 8 * 39) + 39 = 47\n  table_.setMinInUseIndex(2);\n  EXPECT_TRUE(table_.canIndex(fortySevenBytes.name, fortySevenBytes.value));\n  EXPECT_TRUE(table_.add(fortySevenBytes.copy()));\n}\n\nTEST_F(QPACKHeaderTableTests, EvictNonDrained) {\n  HPACKHeader small(\"ab\", \"cd\");                                 // 36 bytes\n  HPACKHeader small2(\"abcd\", std::string(14, 'b'));              // 50 bytes\n  HPACKHeader med(std::string(20, 'a'), std::string(20, 'b'));   // 72\n  HPACKHeader large(std::string(34, 'a'), std::string(34, 'b')); // 100\n\n  table_.setCapacity(220);\n  EXPECT_TRUE(table_.add(small.copy()));\n  EXPECT_TRUE(table_.add(med.copy()));\n  EXPECT_TRUE(table_.add(large.copy()));\n  EXPECT_TRUE(table_.isDraining(3));\n  EXPECT_FALSE(table_.isDraining(2));\n\n  table_.setAcknowledgedInsertCount(3);\n  // Evicts small and med\n  EXPECT_TRUE(table_.add(small2.copy()));\n  EXPECT_EQ(table_.size(), 2);\n  EXPECT_FALSE(table_.isDraining(1));\n  EXPECT_FALSE(table_.isDraining(2));\n\n  // Now lg should be draining\n  EXPECT_TRUE(table_.add(small.copy()));\n  EXPECT_TRUE(table_.isDraining(3));\n\n  // Evict large\n  EXPECT_TRUE(table_.add(small.copy()));\n}\n\nTEST_F(QPACKHeaderTableTests, BadSync) {\n  // Can't ack more than is in the table\n  EXPECT_FALSE(table_.onInsertCountIncrement(1));\n}\n\nTEST_F(QPACKHeaderTableTests, TinyTable) {\n  // This table will tell you it can't hold any headers, but it can!\n  QPACKHeaderTable table(0, true);\n  table.setCapacity(63);\n  HPACKHeader foo(\"F\", \"\");\n  EXPECT_FALSE(table.canIndex(foo.name, foo.value));\n  EXPECT_TRUE(table.add(foo.copy()));\n  EXPECT_EQ(table.size(), 1);\n  EXPECT_EQ(table.length(), 1);\n  EXPECT_TRUE(table.isDraining(1));\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/RFCExamplesTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <algorithm>\n#include <folly/String.h>\n#include <folly/portability/GTest.h>\n#include <list>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n#include <vector>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nusing RfcParam = std::pair<bool, vector<string>>;\n\nclass RFCRequestTest : public testing::TestWithParam<RfcParam> {\n public:\n  RFCRequestTest() {\n    req1.emplace_back(\":method\", \"GET\");\n    req1.emplace_back(\":scheme\", \"http\");\n    req1.emplace_back(\":path\", \"/\");\n    req1.emplace_back(\":authority\", \"www.example.com\");\n\n    req2.emplace_back(\":method\", \"GET\");\n    req2.emplace_back(\":scheme\", \"http\");\n    req2.emplace_back(\":path\", \"/\");\n    req2.emplace_back(\":authority\", \"www.example.com\");\n    req2.emplace_back(\"cache-control\", \"no-cache\");\n\n    req3.emplace_back(\":method\", \"GET\");\n    req3.emplace_back(\":scheme\", \"https\");\n    req3.emplace_back(\":path\", \"/index.html\");\n    req3.emplace_back(\":authority\", \"www.example.com\");\n    req3.emplace_back(\"custom-key\", \"custom-value\");\n  }\n\n protected:\n  vector<HPACKHeader> req1;\n  vector<HPACKHeader> req2;\n  vector<HPACKHeader> req3;\n};\n\nclass RFCResponseTest : public testing::TestWithParam<RfcParam> {\n public:\n  RFCResponseTest() {\n    resp1.emplace_back(\":status\", \"302\");\n    resp1.emplace_back(\"cache-control\", \"private\");\n    resp1.emplace_back(\"date\", \"Mon, 21 Oct 2013 20:13:21 GMT\");\n    resp1.emplace_back(\"location\", \"https://www.example.com\");\n\n    resp2.emplace_back(\":status\", \"307\");\n    resp2.emplace_back(\"cache-control\", \"private\");\n    resp2.emplace_back(\"date\", \"Mon, 21 Oct 2013 20:13:21 GMT\");\n    resp2.emplace_back(\"location\", \"https://www.example.com\");\n\n    resp3.emplace_back(\":status\", \"200\");\n    resp3.emplace_back(\"cache-control\", \"private\");\n    resp3.emplace_back(\"date\", \"Mon, 21 Oct 2013 20:13:22 GMT\");\n    resp3.emplace_back(\"location\", \"https://www.example.com\");\n    resp3.emplace_back(\"content-encoding\", \"gzip\");\n    resp3.emplace_back(\n        \"set-cookie\",\n        \"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\");\n  }\n\n protected:\n  vector<HPACKHeader> resp1;\n  vector<HPACKHeader> resp2;\n  vector<HPACKHeader> resp3;\n};\n\nvector<string> exampleHex1 = {\n    \"828684410f7777772e6578616d706c652e636f6d\",\n    \"828684be58086e6f2d6361636865\",\n    \"828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565\"};\n\nvector<string> exampleHex2 = {\n    \"828684418cf1e3c2e5f23a6ba0ab90f4ff\",\n    \"828684be5886a8eb10649cbf\",\n    \"828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf\"};\n\nRfcParam d3(false, exampleHex1);\nRfcParam d4(true, exampleHex2);\n\nvector<string> exampleHex3 = {\n    \"4803333032580770726976617465611d4d6f6e2c203231204f63742032303133\"\n    \"2032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d70\"\n    \"6c652e636f6d\",\n    \"4803333037c1c0bf\",\n    \"88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d\"\n    \"54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f5049\"\n    \"5541585157454f49553b206d61782d6167653d333630303b2076657273696f6e\"\n    \"3d31\"};\nvector<string> exampleHex4 = {\n    \"488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a6\"\n    \"2d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3\",\n    \"4883640effc1c0bf\",\n    \"88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab\"\n    \"77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f\"\n    \"9587316065c003ed4ee5b1063d5007\"};\n\nRfcParam d5(false, exampleHex3);\nRfcParam d6(true, exampleHex4);\n\nnamespace {\nstd::string unhexlify(const std::string& input) {\n  std::string result;\n  folly::unhexlify(input, result);\n  return result;\n}\n} // namespace\n\nTEST_P(RFCRequestTest, RfcExampleRequest) {\n  HPACKEncoder encoder(GetParam().first);\n  HPACKDecoder decoder;\n  // first request\n  unique_ptr<IOBuf> encoded = hpack::encodeDecode(req1, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[0]));\n  EXPECT_EQ(encoder.getTable().bytes(), 57);\n  EXPECT_EQ(encoder.getTable().size(), 1);\n\n  // second request\n  encoded = hpack::encodeDecode(req2, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[1]));\n  EXPECT_EQ(encoder.getTable().bytes(), 110);\n  EXPECT_EQ(encoder.getTable().size(), 2);\n\n  // third request\n  encoded = hpack::encodeDecode(req3, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[2]));\n  EXPECT_EQ(encoder.getTable().bytes(), 164);\n  EXPECT_EQ(encoder.getTable().size(), 3);\n}\n\nINSTANTIATE_TEST_SUITE_P(Huffman, RFCRequestTest, ::testing::Values(d3, d4));\n\nTEST_P(RFCResponseTest, RfcExampleResponse) {\n  // this test does some evictions\n  uint32_t tableSize = 256;\n  HPACKEncoder encoder(GetParam().first, tableSize);\n  HPACKDecoder decoder(tableSize);\n\n  // first\n  unique_ptr<IOBuf> encoded = hpack::encodeDecode(resp1, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[0]));\n  EXPECT_EQ(encoder.getTable().bytes(), 222);\n  EXPECT_EQ(encoder.getTable().size(), 4);\n  EXPECT_EQ(encoder.getHeader(64).name.get(), \"cache-control\");\n  EXPECT_EQ(encoder.getHeader(64).value, \"private\");\n\n  // second\n  encoded = hpack::encodeDecode(resp2, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[1]));\n  EXPECT_EQ(encoder.getTable().bytes(), 222);\n  EXPECT_EQ(encoder.getTable().size(), 4);\n\n  // third\n  encoded = hpack::encodeDecode(resp3, encoder, decoder);\n  EXPECT_EQ(encoded->moveToFbString(), unhexlify(GetParam().second[2]));\n\n  EXPECT_EQ(encoder.getTable().size(), 3);\n  EXPECT_EQ(encoder.getTable().bytes(), 215);\n}\n\nINSTANTIATE_TEST_SUITE_P(Huffman, RFCResponseTest, ::testing::Values(d5, d6));\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/TestStreamingCallback.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <folly/Function.h>\n#include <proxygen/lib/http/codec/compress/HPACKHeader.h>\n#include <proxygen/lib/http/codec/compress/HPACKStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n\nnamespace proxygen {\n\nclass TestStreamingCallback : public HPACK::StreamingCallback {\n public:\n  void onHeader(const HPACKHeaderName& hname,\n                const folly::fbstring& value) override {\n    auto name = hname.get();\n    headers.emplace_back(duplicate(name), name.size(), true, false);\n    headers.emplace_back(duplicate(value), value.size(), true, false);\n  }\n  void onHeadersComplete(HTTPHeaderSize decodedSize,\n                         bool /*acknowledge*/) override {\n    decodedSize_ = decodedSize;\n    if (headersCompleteCb) {\n      headersCompleteCb();\n    }\n  }\n  void onDecodeError(HPACK::DecodeError decodeError) override {\n    error = decodeError;\n  }\n\n  void reset() {\n    headers.clear();\n    error = HPACK::DecodeError::NONE;\n  }\n\n  folly::Expected<HeaderDecodeResult, HPACK::DecodeError> getResult() {\n    if (error == HPACK::DecodeError::NONE) {\n      return HeaderDecodeResult{.headers = headers, .bytesConsumed = 0};\n    } else {\n      return folly::makeUnexpected(error);\n    }\n  }\n\n  bool hasError() const {\n    return error != HPACK::DecodeError::NONE;\n  }\n\n  std::unique_ptr<std::vector<HPACKHeader>> hpackHeaders() const {\n    CHECK(!hasError());\n    auto result = std::make_unique<std::vector<HPACKHeader>>();\n    for (size_t i = 0; i < headers.size(); i += 2) {\n      result->emplace_back(headers[i].str, headers[i + 1].str);\n    }\n\n    return result;\n  }\n\n  compress::HeaderPieceList headers;\n  HPACK::DecodeError error{HPACK::DecodeError::NONE};\n  char* duplicate(const folly::fbstring& str) {\n    char* res = CHECK_NOTNULL(new char[str.length() + 1]);\n    memcpy(res, str.data(), str.length() + 1);\n    return res;\n  }\n\n  folly::Function<void()> headersCompleteCb;\n  HTTPHeaderSize decodedSize_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/TestUtil.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n\n#include <folly/io/Cursor.h>\n#include <folly/portability/GTest.h>\n#include <fstream>\n#include <glog/logging.h>\n#include <proxygen/lib/http/codec/compress/Logging.h>\n\nusing folly::IOBuf;\nusing std::ofstream;\nusing std::string;\nusing std::unique_ptr;\nusing std::vector;\n\nnamespace proxygen::hpack {\n\nvoid dumpToFile(const string& filename, const IOBuf* buf) {\n  ofstream outfile(filename, ofstream::binary);\n  if (buf) {\n    const IOBuf* p = buf;\n    do {\n      outfile.write((const char*)p->data(), p->length());\n      p = p->next();\n    } while (p->next() != buf);\n  }\n  outfile.close();\n}\n\nvoid verifyHeaders(vector<HPACKHeader>& headers,\n                   vector<HPACKHeader>& decodedHeaders) {\n  EXPECT_EQ(headers.size(), decodedHeaders.size());\n  std::sort(decodedHeaders.begin(), decodedHeaders.end());\n  std::sort(headers.begin(), headers.end());\n  if (headers.size() != decodedHeaders.size()) {\n    std::cerr << printDelta(decodedHeaders, headers);\n    CHECK(false) << \"Mismatched headers size\";\n  }\n  EXPECT_EQ(headers, decodedHeaders);\n  if (headers != decodedHeaders) {\n    std::cerr << printDelta(headers, decodedHeaders);\n    CHECK(false) << \"Mismatched headers\";\n  }\n}\n\nunique_ptr<IOBuf> encodeDecode(vector<HPACKHeader>& headers,\n                               HPACKEncoder& encoder,\n                               HPACKDecoder& decoder) {\n  unique_ptr<IOBuf> encoded = encoder.encode(headers);\n  auto decodedHeaders = hpack::decode(decoder, encoded.get());\n  CHECK(!decoder.hasError());\n\n  verifyHeaders(headers, *decodedHeaders);\n\n  // header tables should look the same\n  CHECK(encoder.getTable() == decoder.getTable());\n  EXPECT_EQ(encoder.getTable(), decoder.getTable());\n\n  return encoded;\n}\n\nvoid encodeDecode(vector<HPACKHeader>& headers,\n                  QPACKEncoder& encoder,\n                  QPACKDecoder& decoder) {\n  auto encoded = encoder.encode(headers, 0, 1);\n  TestStreamingCallback cb;\n  if (encoded.control) {\n    decoder.decodeEncoderStream(std::move(encoded.control));\n    encoder.decodeDecoderStream(decoder.encodeInsertCountInc());\n  }\n  CHECK(encoded.stream);\n  auto length = encoded.stream->computeChainDataLength();\n  decoder.decodeStreaming(1, std::move(encoded.stream), length, &cb);\n  CHECK(!cb.hasError());\n  auto decodedHeaders = cb.hpackHeaders();\n  verifyHeaders(headers, *decodedHeaders);\n  encoder.decodeDecoderStream(decoder.encodeHeaderAck(1));\n\n  // header tables should look the same\n  CHECK(encoder.getTable() == decoder.getTable());\n  EXPECT_EQ(encoder.getTable(), decoder.getTable());\n}\n\nunique_ptr<HPACKDecoder::headers_t> decode(HPACKDecoder& decoder,\n                                           const IOBuf* buffer) {\n  auto headers = std::make_unique<HPACKDecoder::headers_t>();\n  folly::io::Cursor cursor(buffer);\n  uint32_t totalBytes = buffer ? cursor.totalLength() : 0;\n  TestStreamingCallback cb;\n  decoder.decodeStreaming(cursor, totalBytes, &cb);\n  if (cb.hasError()) {\n    return headers;\n  }\n  return cb.hpackHeaders();\n}\n\nvector<compress::Header> headersFromArray(vector<vector<string>>& a) {\n  vector<compress::Header> headers;\n  for (auto& ha : a) {\n    headers.push_back(compress::Header::makeHeaderForTest(ha[0], ha[1]));\n  }\n  return headers;\n}\n\nvector<compress::Header> basicHeaders() {\n  static vector<vector<string>> headersStrings = {\n      {\":path\", \"/index.php\"},\n      {\":authority\", \"www.facebook.com\"},\n      {\":method\", \"GET\"},\n      {\":scheme\", \"https\"},\n      {\"Host\", \"www.facebook.com\"},\n      {\"accept-encoding\", \"gzip\"}};\n  static vector<compress::Header> headers = headersFromArray(headersStrings);\n  return headers;\n}\n\n} // namespace proxygen::hpack\n"
  },
  {
    "path": "proxygen/lib/http/codec/compress/test/TestUtil.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/codec/compress/HPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/HPACKEncoder.h>\n#include <proxygen/lib/http/codec/compress/QPACKDecoder.h>\n#include <proxygen/lib/http/codec/compress/QPACKEncoder.h>\n#include <string>\n\nnamespace proxygen::hpack {\n\nvoid dumpToFile(const std::string& filename, const folly::IOBuf* buf);\n\nstd::unique_ptr<folly::IOBuf> encodeDecode(std::vector<HPACKHeader>& headers,\n                                           HPACKEncoder& encoder,\n                                           HPACKDecoder& decoder);\n\nvoid encodeDecode(std::vector<HPACKHeader>& headers,\n                  QPACKEncoder& encoder,\n                  QPACKDecoder& decoder);\n\nstd::unique_ptr<HPACKDecoder::headers_t> decode(HPACKDecoder& decoder,\n                                                const folly::IOBuf* buffer);\n\nstd::vector<compress::Header> headersFromArray(\n    std::vector<std::vector<std::string>>& a);\n\nstd::vector<compress::Header> basicHeaders();\n\nclass TestHeaderCodecStats : public HeaderCodec::Stats {\n\n public:\n  explicit TestHeaderCodecStats(HeaderCodec::Type type) : type_(type) {\n  }\n\n  void recordEncode(HeaderCodec::Type type, HTTPHeaderSize& size) override {\n    EXPECT_EQ(type, type_);\n    encodes++;\n    encodedBytesCompr += size.compressed;\n    encodedBytesComprBlock += size.compressedBlock;\n    encodedBytesUncompr += size.uncompressed;\n  }\n\n  void recordDecode(HeaderCodec::Type type, HTTPHeaderSize& size) override {\n    EXPECT_EQ(type, type_);\n    decodes++;\n    decodedBytesCompr += size.compressed;\n    decodedBytesUncompr += size.uncompressed;\n  }\n\n  void recordDecodeError(HeaderCodec::Type type) override {\n    EXPECT_EQ(type, type_);\n    errors++;\n  }\n\n  void recordDecodeTooLarge(HeaderCodec::Type type) override {\n    EXPECT_EQ(type, type_);\n    tooLarge++;\n  }\n\n  void reset() {\n    encodes = 0;\n    decodes = 0;\n    encodedBytesCompr = 0;\n    encodedBytesComprBlock = 0;\n    encodedBytesUncompr = 0;\n    decodedBytesCompr = 0;\n    decodedBytesUncompr = 0;\n    errors = 0;\n    tooLarge = 0;\n  }\n\n  HeaderCodec::Type type_;\n  uint32_t encodes{0};\n  uint32_t encodedBytesCompr{0};\n  uint32_t encodedBytesComprBlock{0};\n  uint32_t encodedBytesUncompr{0};\n  uint32_t decodes{0};\n  uint32_t decodedBytesCompr{0};\n  uint32_t decodedBytesUncompr{0};\n  uint32_t errors{0};\n  uint32_t tooLarge{0};\n};\n\n} // namespace proxygen::hpack\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\nadd_library(codectestutils TestUtils.cpp)\ntarget_include_directories(\n    codectestutils PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIR}\n)\ntarget_compile_options(\n    codectestutils PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(codectestutils PUBLIC proxygen)\n\nproxygen_add_test(TARGET CodecTests\n  SOURCES\n    CrossCodecTest.cpp\n    CodecUtilTests.cpp\n    DefaultHTTPCodecFactoryTest.cpp\n    FilterTests.cpp\n    HTTP1xCodecTest.cpp\n    HTTP2CodecTest.cpp\n    HTTP2FramerTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET HQFramerTests\n  SOURCES\n    HQFramerTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n    testmain\n    mvfst::mvfst_codec_types\n)\n\nproxygen_add_test(TARGET HQCodecTests\n  SOURCES\n    HQCodecTest.cpp\n    HQMultiCodecTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n    testmain\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_state_machine\n)\n\nproxygen_add_test(TARGET BinaryCodecTests\n  SOURCES\n    HTTPBinaryCodecTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/CapsuleCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <gmock/gmock.h>\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/common/BufUtil.h>\n\nusing namespace proxygen;\nusing namespace folly;\nusing namespace testing;\n\nclass TestCapsuleCodecCallback : public CapsuleCodec::Callback {\n public:\n  MOCK_METHOD(void, onCapsule, (uint64_t, uint64_t), (noexcept)); // parses type\n                                                                  // + length\n  MOCK_METHOD(void, onConnectionError, (CapsuleCodec::ErrorCode), (noexcept));\n  MOCK_METHOD(void,\n              onStringCapsule,\n              (uint64_t, uint64_t, std::string)); // parses payload\n};\n\nclass TestCapsuleCodec : public CapsuleCodec {\n public:\n  explicit TestCapsuleCodec(TestCapsuleCodecCallback* callback = nullptr)\n      : CapsuleCodec(callback) {\n  }\n\n  bool canParseCapsule(uint64_t capsuleType) noexcept override {\n    return capsuleType == 0x01;\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> parseCapsule(\n      io::Cursor& cursor) override {\n    if (cursor.length() > curCapsuleLength_) {\n      VLOG(4) << \"remainingLength > curCapsuleLength_\";\n      return folly::makeUnexpected(ErrorCode::PARSE_UNDERFLOW);\n    }\n    auto payload = cursor.readFixedString(curCapsuleLength_);\n    static_cast<TestCapsuleCodecCallback*>(callback_)->onStringCapsule(\n        curCapsuleType_, curCapsuleLength_, payload);\n    return folly::unit;\n  }\n};\n\nclass CapsuleCodecTest : public Test {\n protected:\n  void SetUp() override {\n    callback_ = std::make_unique<TestCapsuleCodecCallback>();\n    codec_ = std::make_unique<TestCapsuleCodec>(callback_.get());\n  }\n  std::unique_ptr<TestCapsuleCodecCallback> callback_;\n  std::unique_ptr<TestCapsuleCodec> codec_;\n};\n\nquic::BufQueue generateStringCapsule(uint64_t type,\n                                     uint64_t length,\n                                     const std::string& value) {\n  auto buf = folly::IOBuf::create(1024);\n  quic::BufAppender appender(buf.get(), 1024);\n  auto typeResult =\n      quic::encodeQuicInteger(type, [&](auto val) { appender.writeBE(val); });\n  CHECK(typeResult.has_value());\n  auto lengthResult =\n      quic::encodeQuicInteger(length, [&](auto val) { appender.writeBE(val); });\n  CHECK(lengthResult.has_value());\n  quic::BufQueue queue(std::move(buf));\n  auto str = folly::IOBuf::copyBuffer(value);\n  queue.append(std::move(str));\n  return queue;\n}\n\nTEST_F(CapsuleCodecTest, ValidCapsuleParsing) {\n  // encode type=0x01, length=5, payload=\"AAAAA\"\n  auto capsule = generateStringCapsule(0x01, 5, \"AAAAA\");\n  EXPECT_CALL(*callback_, onCapsule(0x01, 5));\n  EXPECT_CALL(*callback_, onStringCapsule(0x1, 5, \"AAAAA\"));\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, InvalidCapsuleType) {\n  // partial QUIC integer for capsule type (1 byte of a 2-byte header)\n  auto partialType = folly::IOBuf::copyBuffer(R\"(@)\"); // 0x40 = 2-byte integer\n  quic::BufQueue capsule(std::move(partialType));\n  EXPECT_CALL(*callback_,\n              onConnectionError(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW));\n  EXPECT_CALL(*callback_, onCapsule(_, _)).Times(0);\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, TooLargePayloadAndEom) {\n  auto capsule = generateStringCapsule(0x01, 5, \"Invalid Payload\");\n  EXPECT_CALL(*callback_, onCapsule(0x01, 5));\n  EXPECT_CALL(*callback_,\n              onConnectionError(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW));\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, ParseUnderflowInvalidPayload) {\n  auto capsule = generateStringCapsule(0x01, 5, \"BAD\");\n  EXPECT_CALL(*callback_, onCapsule(0x01, 5));\n  EXPECT_CALL(*callback_,\n              onConnectionError(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW));\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, ParseUnderflowLength) {\n  // Encode type=0x01, missing length\n  auto buf = folly::IOBuf::create(1024);\n  quic::BufAppender appender(buf.get(), 1024);\n  auto typeResult =\n      quic::encodeQuicInteger(0x01, [&](auto val) { appender.writeBE(val); });\n  CHECK(typeResult.has_value());\n  quic::BufQueue capsule(std::move(buf));\n  EXPECT_CALL(*callback_, onCapsule(_, _)).Times(0);\n  EXPECT_CALL(*callback_,\n              onConnectionError(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW));\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, SkipCapsule) {\n  auto capsule = generateStringCapsule(0x02, 5, \"AAAAA\");\n  EXPECT_CALL(*callback_, onCapsule(0x02, 5));\n  EXPECT_CALL(*callback_, onStringCapsule(_, _, _)).Times(0); // skipping this\n  codec_->onIngress(capsule.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, SkipCapsuleMultiple) {\n  // encode type=0x02, length=5, payload=\"AAAAA\"\n  auto capsule1 = generateStringCapsule(0x02, 5, \"AAAAA\");\n  EXPECT_CALL(*callback_, onCapsule(0x02, 5));\n  EXPECT_CALL(*callback_, onStringCapsule(_, _, _)).Times(0); // skipping this\n  codec_->onIngress(capsule1.clone(), true);\n  // encode type=0x01, length=3, payload=\"BBB\"\n  auto capsule2 = generateStringCapsule(0x01, 3, \"BBB\");\n  EXPECT_CALL(*callback_, onCapsule(0x01, 3));\n  EXPECT_CALL(*callback_, onStringCapsule(0x01, 3, \"BBB\"));\n  codec_->onIngress(capsule2.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, ParseUnderflowAndNotEom) {\n  // encode type=0x01, length=5, payload=\"AAA\", false EOMs before true EOM\n  auto buf = folly::IOBuf::create(1024);\n  quic::BufAppender appender(buf.get(), 1024);\n  auto typeResult =\n      quic::encodeQuicInteger(0x01, [&](auto val) { appender.writeBE(val); });\n  CHECK(typeResult.has_value());\n  auto lengthResult =\n      quic::encodeQuicInteger(3, [&](auto val) { appender.writeBE(val); });\n  CHECK(lengthResult.has_value());\n  quic::BufQueue capsule1(std::move(buf));\n  EXPECT_CALL(*callback_, onCapsule(0x01, 3));\n  EXPECT_CALL(*callback_, onConnectionError(_)).Times(0);\n  codec_->onIngress(capsule1.clone(), false);\n\n  auto buf2 = folly::IOBuf::create(1024);\n  quic::BufQueue capsule2(std::move(buf2));\n  auto str = folly::IOBuf::copyBuffer(\"AA\");\n  capsule2.append(std::move(str));\n  codec_->onIngress(capsule2.clone(), false);\n\n  auto buf3 = folly::IOBuf::create(1024);\n  quic::BufQueue capsule3(std::move(buf3));\n  auto str3 = folly::IOBuf::copyBuffer(\"A\");\n  capsule3.append(std::move(str3));\n  EXPECT_CALL(*callback_, onStringCapsule(0x01, 3, \"AAA\"));\n  codec_->onIngress(capsule3.clone(), true);\n}\n\nTEST_F(CapsuleCodecTest, MultipleCapsules) {\n  // first capsule: type=0x01, length=5, payload=\"AAAAA\"\n  auto capsule1 = generateStringCapsule(0x01, 5, \"AAAAA\");\n  // second capsule: type=0x01, length=3, payload=\"BBB\"\n  auto capsule2 = generateStringCapsule(0x01, 3, \"BBB\");\n  capsule1.append(capsule2.move()); // combine queues\n\n  EXPECT_CALL(*callback_, onCapsule(0x01, 5));\n  EXPECT_CALL(*callback_, onStringCapsule(0x01, 5, \"AAAAA\"));\n  EXPECT_CALL(*callback_, onCapsule(0x01, 3));\n  EXPECT_CALL(*callback_, onStringCapsule(0x01, 3, \"BBB\"));\n  codec_->onIngress(capsule1.clone(), true);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/CodecUtilTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/CodecUtil.h>\n\n#include <folly/portability/GTest.h>\n\nusing std::string;\n\nnamespace proxygen::test {\n\nfolly::ByteRange input(const char *str) {\n  return {reinterpret_cast<const uint8_t *>(str), strlen(str)};\n}\n\nTEST(CodecUtil, validateURL) {\n  EXPECT_TRUE(CodecUtil::validateURL(\"/foo\", URLValidateMode::STRICT));\n  EXPECT_TRUE(\n      CodecUtil::validateURL(\"/foo\\xff\", URLValidateMode::STRICT_COMPAT));\n  EXPECT_FALSE(CodecUtil::validateURL(\"/foo\\xff\", URLValidateMode::STRICT));\n}\n\nTEST(CodecUtil, validateMethod) {\n  EXPECT_TRUE(CodecUtil::validateMethod(input(\"GET\")));\n  EXPECT_TRUE(CodecUtil::validateMethod(input(\"CONNECT-UDP\")));\n  // TODO:\n  // EXPECT_FALSE(CodecUtil::validateMethod(input(\"CONNECT-\")));\n  EXPECT_FALSE(CodecUtil::validateMethod(input(\"-UDP\")));\n  EXPECT_FALSE(CodecUtil::validateMethod(input(\"-\")));\n  EXPECT_TRUE(CodecUtil::validateMethod(input(\"lowercase\")));\n}\n\nTEST(CodecUtil, validateScheme) {\n  EXPECT_TRUE(CodecUtil::validateScheme(input(\"http\")));\n  EXPECT_TRUE(CodecUtil::validateScheme(input(\"foo\")));\n  EXPECT_FALSE(CodecUtil::validateScheme(input(\"h1th3r3\")));\n}\n\nTEST(CodecUtil, validateHeaderName) {\n  EXPECT_TRUE(CodecUtil::validateHeaderName(input(\"foo\"),\n                                            CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderName(input(\"foo_bar\"),\n                                            CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderName(input(\"foo-bar\"),\n                                            CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderName(input(\"\"), CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\":foo\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\"foo:bar\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\"foo\\xf0\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\"foo\\r\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\"foo\\n\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n  EXPECT_FALSE(CodecUtil::validateHeaderName(input(\"foo\\r\\nfoo\"),\n                                             CodecUtil::HEADER_NAME_STRICT));\n\n  std::array<char, 19> httpSeparators{'(',\n                                      ')',\n                                      '<',\n                                      '>',\n                                      '@',\n                                      ',',\n                                      ';',\n                                      ':',\n                                      '\\\\',\n                                      '\\\"',\n                                      '/',\n                                      '[',\n                                      ']',\n                                      '?',\n                                      '=',\n                                      '{',\n                                      '}',\n                                      ' ',\n                                      '\\t'};\n  for (auto sep : httpSeparators) {\n    auto testHeader = folly::to<std::string>(\"foo\", sep, \"bar\");\n    EXPECT_FALSE(CodecUtil::validateHeaderName(input(testHeader.c_str()),\n                                               CodecUtil::HEADER_NAME_STRICT));\n    if (sep == ' ' || sep == '/' || sep == '}' || sep == '\"') {\n      EXPECT_TRUE(CodecUtil::validateHeaderName(\n          input(testHeader.c_str()), CodecUtil::HEADER_NAME_STRICT_COMPAT));\n    }\n  }\n}\n\nTEST(CodecUtil, validateHeaderValue) {\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\"abc\"), CodecUtil::STRICT));\n  string allTheChars;\n  allTheChars.reserve(127 - 32);\n  for (uint8_t i = 32; i < 127; i++) {\n    allTheChars += folly::to<char>(i);\n  }\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(allTheChars.c_str()),\n                                             CodecUtil::STRICT));\n  // test without leading whitespace\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(allTheChars.c_str() + 1),\n                                             CodecUtil::STRICT));\n\n  // valid lws\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"abc\\r\\n\\tdef\"), CodecUtil::STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\"abc\\r\\n \\t \\t def\"),\n                                             CodecUtil::STRICT));\n  // Invalid lws\n  EXPECT_FALSE(CodecUtil::validateHeaderValue(input(\"abc\\r \\t \\t def\"),\n                                              CodecUtil::STRICT));\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"abc\\r\\ndef\"), CodecUtil::STRICT));\n  // terminating open quote\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"abc\\\"\"), CodecUtil::STRICT));\n  // open quote\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"abc\\\"def\"), CodecUtil::STRICT));\n  // quoted def\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"abc\\\"def\\\"\"), CodecUtil::STRICT));\n  // quoted, escaped CRLF\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\"abc\\\"\\\\\\r\\\\\\n\\\"\"),\n                                             CodecUtil::COMPLIANT));\n  EXPECT_FALSE(CodecUtil::validateHeaderValue(input(\"abc\\\"\\\\\\r\\\\\\n\\\"\"),\n                                              CodecUtil::STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\"abc\\xff\"),\n                                             CodecUtil::STRICT_COMPAT));\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"abc\\xff\"), CodecUtil::STRICT));\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"abc\\x7f\"), CodecUtil::STRICT));\n  // hard-tab OK\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"abc\\t\"), CodecUtil::STRICT));\n\n  // End on escape\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"\\\"\\\\\"), CodecUtil::COMPLIANT));\n  // End on partial LWS\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"foo\\r\"), CodecUtil::COMPLIANT));\n  EXPECT_FALSE(\n      CodecUtil::validateHeaderValue(input(\"foo\\r\\n\"), CodecUtil::COMPLIANT));\n\n  // leading white space stripped (copied EXPECT_TRUE cases from above and\n  // added ws to beginning)\n  EXPECT_TRUE(\n      CodecUtil::validateHeaderValue(input(\"\\tabc\\t\"), CodecUtil::STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\" abc\\r\\n\\tdef\"),\n                                             CodecUtil::STRICT));\n  EXPECT_TRUE(CodecUtil::validateHeaderValue(input(\"\\tabc\\\"\\\\\\r\\\\\\n\\\"\"),\n                                             CodecUtil::COMPLIANT));\n}\n\nTEST(CodecUtil, hasGzipAndDeflate) {\n  bool gzip = false;\n  bool deflate = false;\n  EXPECT_FALSE(CodecUtil::hasGzipAndDeflate(\"gzip\", gzip, deflate));\n  EXPECT_TRUE(gzip);\n  gzip = false;\n  deflate = false;\n  EXPECT_FALSE(CodecUtil::hasGzipAndDeflate(\"deflate\", gzip, deflate));\n  EXPECT_TRUE(deflate);\n  EXPECT_TRUE(CodecUtil::hasGzipAndDeflate(\"gzip, deflate\", gzip, deflate));\n  EXPECT_TRUE(CodecUtil::hasGzipAndDeflate(\"deflate, gzip\", gzip, deflate));\n  EXPECT_TRUE(\n      CodecUtil::hasGzipAndDeflate(\"foo, gzip, bar, deflate\", gzip, deflate));\n  EXPECT_FALSE(CodecUtil::hasGzipAndDeflate(\"zipg, default\", gzip, deflate));\n  EXPECT_FALSE(\n      CodecUtil::hasGzipAndDeflate(\"gzip; q=.00001, deflate\", gzip, deflate));\n}\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/CrossCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\n\nnamespace {\nvoid parseBufWithH1Codec(FakeHTTPCodecCallback& callback,\n                         const folly::IOBuf& buf) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  codec.setCallback(&callback);\n  auto bytesProcessed = codec.onIngress(buf);\n  EXPECT_EQ(bytesProcessed, buf.computeChainDataLength());\n}\n} // namespace\n\n// This test converts a HTTP CONNECT message from HTTP/1.1 to HTTP/2 then back\n// to HTTP/1.1 and expects (1) we're conforming to spec after every conversion,\n// and (2) we get the same message at the end (i.e., no loss of\n// information and no alteration). The reason is that in RFC7540 Section 8.3,\n// format of HTTP CONNECT messages is different in H2.\nTEST(CrossCodecTest, ConnectRequestConversion) {\n  constexpr std::string_view kOriginalRequest =\n      (\"CONNECT server.example.com:80 HTTP/1.1\\r\\n\"\n       \"Host: server.example.com:80\\r\\n\"\n       \"Proxy-Authorization: basic Zm9vOg==\\r\\n\"\n       \"\\r\\n\");\n\n  auto verifyOriginalRequest = [](const HTTPMessage& msg) {\n    EXPECT_EQ(msg.getMethod(), HTTPMethod::CONNECT);\n    EXPECT_EQ(msg.getHTTPVersion(), HTTPMessage::kHTTPVersion11);\n    EXPECT_EQ(msg.getURL(), \"server.example.com:80\");\n    EXPECT_EQ(msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n              \"server.example.com:80\");\n    EXPECT_EQ(\n        msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_PROXY_AUTHORIZATION),\n        \"basic Zm9vOg==\");\n  };\n\n  // Use H1 codec to parse the message.\n  FakeHTTPCodecCallback h1RecvCallback;\n  parseBufWithH1Codec(h1RecvCallback,\n                      *folly::IOBuf::copyBuffer(kOriginalRequest));\n  verifyOriginalRequest(*h1RecvCallback.msg);\n\n  // Then use H2 codec to dump the message.\n  folly::IOBufQueue h2OutBuf{folly::IOBufQueue::cacheChainLength()};\n  HTTP2Codec h2SendCodec(TransportDirection::UPSTREAM);\n  h2SendCodec.generateConnectionPreface(h2OutBuf);\n  h2SendCodec.generateSettings(h2OutBuf);\n  auto h2SendId = h2SendCodec.createStream();\n  h2SendCodec.generateHeader(\n      h2OutBuf, h2SendId, *h1RecvCallback.msg, false /* eom */);\n\n  // And use H2 codec to parse the dumped message.\n  FakeHTTPCodecCallback h2RecvCallback;\n  HTTP2Codec h2RecvCodec(TransportDirection::DOWNSTREAM);\n  h2RecvCodec.setCallback(&h2RecvCallback);\n  size_t h2BytesProcessed = h2RecvCodec.onIngress(*h2OutBuf.front());\n  EXPECT_EQ(h2BytesProcessed, h2OutBuf.chainLength());\n  EXPECT_EQ(h2RecvCallback.msg->getMethod(), HTTPMethod::CONNECT);\n  EXPECT_EQ(h2RecvCallback.msg->getHTTPVersion(), HTTPMessage::kHTTPVersion11);\n  // Per https://httpwg.org/specs/rfc7540.html#CONNECT :path pseudo-header\n  // must be omitted.\n  EXPECT_EQ(h2RecvCallback.msg->getURL(), \"\");\n  EXPECT_EQ(h2RecvCallback.msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n            \"server.example.com:80\");\n  EXPECT_EQ(h2RecvCallback.msg->getHeaders().getSingleOrEmpty(\n                HTTP_HEADER_PROXY_AUTHORIZATION),\n            \"basic Zm9vOg==\");\n\n  // Then dump the message with H1 codec.\n  folly::IOBufQueue h1OutBuf(folly::IOBufQueue::cacheChainLength());\n  HTTP1xCodec h1SendCodec(TransportDirection::UPSTREAM);\n  h1SendCodec.generateHeader(\n      h1OutBuf, h1SendCodec.createStream(), *h2RecvCallback.msg);\n\n  // Finally, load the buf with H1 codec again, and hopefully we get the same\n  // message as original.\n  FakeHTTPCodecCallback lastH1RecvCallback;\n  parseBufWithH1Codec(lastH1RecvCallback, *h1OutBuf.front());\n  verifyOriginalRequest(*lastH1RecvCallback.msg);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/DefaultHTTPCodecFactoryTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\n\nTEST(DefaultHTTPCodecFactoryTest, GetCodec) {\n  DefaultHTTPCodecFactory factory;\n\n  auto codec = factory.getCodec(\n      http2::kProtocolString, TransportDirection::UPSTREAM, true);\n  auto* http2Codec = dynamic_cast<HTTP2Codec*>(codec.get());\n  EXPECT_NE(http2Codec, nullptr);\n\n  codec = factory.getCodec(\"http/1.1\", TransportDirection::UPSTREAM, true);\n  auto* http1xCodec = dynamic_cast<HTTP1xCodec*>(codec.get());\n  EXPECT_NE(http1xCodec, nullptr);\n\n  codec = factory.getCodec(\"\", TransportDirection::UPSTREAM, true);\n  http1xCodec = dynamic_cast<HTTP1xCodec*>(codec.get());\n  EXPECT_NE(http1xCodec, nullptr);\n\n  codec = factory.getCodec(\"not/supported\", TransportDirection::UPSTREAM, true);\n  EXPECT_EQ(codec, nullptr);\n}\n\nclass DefaultHTTPCodecFactoryValidationTest\n    : public ::testing::TestWithParam<bool> {};\n\nTEST_P(DefaultHTTPCodecFactoryValidationTest, StrictValidation) {\n  DefaultHTTPCodecFactory factory;\n  bool strict = GetParam();\n  factory.setConfigFn([strict] {\n    HTTPCodecFactory::CodecConfig config;\n    config.strictValidation = strict;\n    return config;\n  });\n\n  auto codec = factory.getCodec(\n      http2::kProtocolString, TransportDirection::DOWNSTREAM, true);\n  HTTP2Codec upstream(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  folly::IOBufQueue output{folly::IOBufQueue::cacheChainLength()};\n  req.setURL(\"/foo\\xff\");\n  upstream.generateConnectionPreface(output);\n  upstream.generateSettings(output);\n  upstream.generateHeader(output, upstream.createStream(), req, true, nullptr);\n  FakeHTTPCodecCallback callbacks;\n  codec->setCallback(&callbacks);\n  codec->onIngress(*output.front());\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, strict ? 0 : 1);\n  EXPECT_EQ(callbacks.streamErrors, strict ? 1 : 0);\n  output.reset();\n\n  callbacks.reset();\n  codec = factory.getCodec(\"http/1.1\", TransportDirection::DOWNSTREAM, true);\n  codec->setCallback(&callbacks);\n  codec->onIngress(*folly::IOBuf::copyBuffer(\"GET /foo\\xff HTTP/1.1\\r\\n\\r\\n\"));\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, strict ? 0 : 1);\n  EXPECT_EQ(callbacks.streamErrors, strict ? 1 : 0);\n}\n\nINSTANTIATE_TEST_SUITE_P(DefaultHTTPCodecFactoryTest,\n                         DefaultHTTPCodecFactoryValidationTest,\n                         ::testing::Values(true, false));\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/FilterTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <limits>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/DebugFilter.h>\n#include <proxygen/lib/http/codec/FlowControlFilter.h>\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nnamespace {\nconst uint32_t kInitialCapacity = 12345;\n\nstd::unique_ptr<folly::IOBuf> makeIOBuf(const std::string& str) {\n  return folly::IOBuf::copyBuffer(str);\n}\n} // namespace\n\nclass MockFlowControlCallback : public FlowControlFilter::Callback {\n public:\n  MOCK_METHOD(void, onConnectionSendWindowOpen, ());\n  MOCK_METHOD(void, onConnectionSendWindowClosed, ());\n};\n\nclass FilterTest : public testing::Test {\n public:\n  FilterTest()\n      : codec_(new MockHTTPCodec()), chain_(unique_ptr<HTTPCodec>(codec_)) {\n    EXPECT_CALL(*codec_, setCallback(_))\n        .WillRepeatedly(SaveArg<0>(&callbackStart_));\n    chain_.setCallback(&callback_);\n  }\n\n protected:\n  MockHTTPCodec* codec_;\n  HTTPCodec::Callback* callbackStart_;\n  HTTPCodecFilterChain chain_;\n  MockHTTPCodecCallback callback_;\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n};\n\nclass HTTPChecksTest : public FilterTest {\n public:\n  void SetUp() override {\n    chain_.add<HTTPChecks>();\n  }\n};\n\nclass DebugFilterTest : public FilterTest {\n public:\n  void SetUp() override {\n    chain_.add<DebugFilter>(\n        \"trace-header\", 200, [this](std::unique_ptr<folly::IOBuf> ingress) {\n          dumpedIngress_.append(std::move(ingress));\n        });\n  }\n\n protected:\n  folly::IOBufQueue dumpedIngress_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n};\n\ntemplate <int initSize>\nclass FlowControlFilterTest : public FilterTest {\n public:\n  void SetUp() override {\n    EXPECT_CALL(*codec_, getDefaultWindowSize())\n        .WillRepeatedly(Return(kInitialCapacity));\n\n    if (initSize > kInitialCapacity) {\n      // If the initial size is bigger than the default, a window update\n      // will immediately be generated\n      EXPECT_CALL(*codec_,\n                  generateWindowUpdate(_, 0, initSize - kInitialCapacity))\n          .WillOnce(InvokeWithoutArgs([this]() {\n            writeBuf_.append(makeBuf(10));\n            return 10;\n          }));\n    }\n    EXPECT_CALL(*codec_, generateBody(_, _, _, _, _))\n        .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                  HTTPCodec::StreamID /*stream*/,\n                                  std::shared_ptr<folly::IOBuf> chain,\n                                  folly::Optional<uint8_t> /*padding*/,\n                                  bool /*eom*/) {\n          auto len = chain->computeChainDataLength() + 4;\n          writeBuf.append(makeBuf(len));\n          return len;\n        }));\n    EXPECT_CALL(*codec_, isReusable()).WillRepeatedly(Return(true));\n\n    // Construct flow control filter with capacity of 0, which will be\n    // overridden to the codec default, which is the minimum\n    filter_ = new FlowControlFilter(flowCallback_, writeBuf_, codec_, initSize);\n    chain_.addFilters(std::unique_ptr<FlowControlFilter>(filter_));\n  }\n  StrictMock<MockFlowControlCallback> flowCallback_;\n  FlowControlFilter* filter_;\n  int recvWindow_{initSize};\n};\n\nusing DefaultFlowControl = FlowControlFilterTest<0>;\nusing BigWindow = FlowControlFilterTest<1000000>;\n\nMATCHER(IsFlowException, \"\") {\n  return arg->hasCodecStatusCode() &&\n         arg->getCodecStatusCode() == ErrorCode::FLOW_CONTROL_ERROR &&\n         !arg->hasHttpStatusCode() && !arg->hasProxygenError();\n}\n\nTEST_F(DefaultFlowControl, FlowControlConstruct) {\n  // Constructing the filter with a low capacity defaults to kInitialCapacity\n  // initial capacity, so no window update should have been generated in\n  // the constructor\n  InSequence enforceSequence;\n  ASSERT_EQ(writeBuf_.chainLength(), 0);\n\n  // Our send window is limited to kInitialCapacity\n  chain_->generateBody(\n      writeBuf_, 1, makeBuf(kInitialCapacity - 1), HTTPCodec::NoPadding, false);\n\n  // the window isn't full yet, so getting a window update shouldn't give a\n  // callback informing us that it is open again\n  callbackStart_->onWindowUpdate(0, 1);\n\n  // Now fill the window (2 more bytes)\n  EXPECT_CALL(flowCallback_, onConnectionSendWindowClosed());\n  chain_->generateBody(writeBuf_, 1, makeBuf(2), HTTPCodec::NoPadding, false);\n  // get the callback informing the window is open once we get a window update\n  EXPECT_CALL(flowCallback_, onConnectionSendWindowOpen());\n  callbackStart_->onWindowUpdate(0, 1);\n\n  // Overflowing the window is fatal. Write 2 bytes (only 1 byte left in window)\n  EXPECT_DEATH_NO_CORE(\n      chain_->generateBody(\n          writeBuf_, 1, makeBuf(2), HTTPCodec::NoPadding, false),\n      \".*\");\n}\n\nTEST_F(DefaultFlowControl, SendUpdate) {\n  // Make sure we send a window update when the window decreases below half\n  InSequence enforceSequence;\n  EXPECT_CALL(callback_, onBody(_, _, _)).WillRepeatedly(Return());\n\n  // Have half the window outstanding\n  callbackStart_->onBody(1, makeBuf(kInitialCapacity / 2 + 1), 0);\n  filter_->ingressBytesProcessed(writeBuf_, kInitialCapacity / 2);\n\n  // It should wait until the \"+1\" is ack'd to generate the coallesced update\n  EXPECT_CALL(*codec_, generateWindowUpdate(_, 0, kInitialCapacity / 2 + 1));\n  filter_->ingressBytesProcessed(writeBuf_, 1);\n}\n\nTEST_F(BigWindow, RecvTooMuch) {\n  // Constructing the filter with a large capacity causes a WINDOW_UPDATE\n  // for stream zero to be generated\n  ASSERT_GT(writeBuf_.chainLength(), 0);\n\n  InSequence enforceSequence;\n  EXPECT_CALL(callback_, onBody(_, _, _));\n  EXPECT_CALL(callback_, onError(0, IsFlowException(), _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPException> exc,\n                          bool /*newTxn*/) {\n        ASSERT_EQ(\n            \"Failed to reserve receive window, window size=0, \"\n            \"amount=1\",\n            std::string(exc->what()));\n      }));\n\n  // Receive the max amount advertised\n  callbackStart_->onBody(1, makeBuf(recvWindow_), 0);\n  ASSERT_TRUE(chain_->isReusable());\n  // Receive 1 byte too much\n  callbackStart_->onBody(1, makeBuf(1), 0);\n  ASSERT_FALSE(chain_->isReusable());\n}\n\nTEST_F(BigWindow, RemoteIncrease) {\n  // The remote side sends us a window update for stream=0, increasing our\n  // available window\n  InSequence enforceSequence;\n\n  ASSERT_EQ(filter_->getAvailableSend(), kInitialCapacity);\n  callbackStart_->onWindowUpdate(0, 10);\n  ASSERT_EQ(filter_->getAvailableSend(), kInitialCapacity + 10);\n\n  EXPECT_CALL(flowCallback_, onConnectionSendWindowClosed());\n  chain_->generateBody(writeBuf_,\n                       1,\n                       makeBuf(kInitialCapacity + 10),\n                       HTTPCodec::NoPadding,\n                       false);\n  ASSERT_EQ(filter_->getAvailableSend(), 0);\n\n  // Now the remote side sends a HUGE update (just barely legal)\n  // Since the window was full, this generates a callback from the filter\n  // telling us the window is no longer full.\n  EXPECT_CALL(flowCallback_, onConnectionSendWindowOpen());\n  callbackStart_->onWindowUpdate(0, std::numeric_limits<int32_t>::max());\n  ASSERT_EQ(filter_->getAvailableSend(), std::numeric_limits<int32_t>::max());\n\n  // Now overflow it by 1\n  EXPECT_CALL(callback_, onError(0, IsFlowException(), _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPException> exc,\n                          bool /*newTxn*/) {\n        ASSERT_EQ(\n            \"Failed to update send window, outstanding=0, \"\n            \"amount=1\",\n            std::string(exc->what()));\n      }));\n  callbackStart_->onWindowUpdate(0, 1);\n  ASSERT_FALSE(chain_->isReusable());\n}\n\nTEST_F(HTTPChecksTest, SendTraceBodyDeath) {\n  // It is NOT allowed to send a TRACE with a body.\n\n  HTTPMessage msg = getPostRequest();\n  msg.setMethod(\"TRACE\");\n\n  EXPECT_DEATH_NO_CORE(chain_->generateHeader(writeBuf_, 0, msg), \".*\");\n}\n\nTEST_F(HTTPChecksTest, SendGetBody) {\n  // It is allowed to send a GET with a content-length. It is up to the\n  // server to ignore it.\n\n  EXPECT_CALL(*codec_, generateHeader(_, _, _, _, _, _));\n\n  HTTPMessage msg = getPostRequest();\n  msg.setMethod(\"GET\");\n\n  chain_->generateHeader(writeBuf_, 0, msg);\n}\n\nTEST_F(HTTPChecksTest, RecvTraceBody) {\n  // In proxygen, we deal with receiving a TRACE with a body by 400'ing it\n\n  EXPECT_CALL(callback_, onError(_, _, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPException> exc,\n                          bool newTxn) {\n        ASSERT_TRUE(newTxn);\n        ASSERT_EQ(exc->getHttpStatusCode(), 400);\n        ASSERT_EQ(0, strcmp(\"RFC2616: Request Body Not Allowed\", exc->what()));\n      }));\n\n  auto msg = makePostRequest();\n  msg->setMethod(\"TRACE\");\n\n  callbackStart_->onHeadersComplete(0, std::move(msg));\n}\n\nTEST_F(DebugFilterTest, NoError) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  chain_->onIngressEOF();\n  callbackStart_->onMessageBegin(1, nullptr);\n  callbackStart_->onHeadersComplete(1, makeGetRequest());\n  callbackStart_->onMessageComplete(1, false);\n  EXPECT_TRUE(dumpedIngress_.empty());\n}\n\nTEST_F(DebugFilterTest, NoErrorGoaway) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  callbackStart_->onGoaway(0, ErrorCode::NO_ERROR, makeIOBuf(\"bar\"));\n  EXPECT_TRUE(dumpedIngress_.empty());\n}\n\nTEST_F(DebugFilterTest, IngressGoaway) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  callbackStart_->onGoaway(0, ErrorCode::PROTOCOL_ERROR, makeIOBuf(\"bar\"));\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, EgressGoaway) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  chain_->generateGoaway(\n      writeBuf_, 0, ErrorCode::PROTOCOL_ERROR, makeIOBuf(\"bar\"));\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, IngressRstTrackedStream) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  callbackStart_->onMessageBegin(1, nullptr);\n  auto req = makeGetRequest();\n  req->getHeaders().add(\"trace-header\", \"true\");\n  callbackStart_->onHeadersComplete(1, std::move(req));\n  callbackStart_->onAbort(1, ErrorCode::INTERNAL_ERROR);\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, EgressRstTrackedStream) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  callbackStart_->onMessageBegin(1, nullptr);\n  auto req = makeGetRequest();\n  req->getHeaders().add(\"trace-header\", \"true\");\n  callbackStart_->onHeadersComplete(1, std::move(req));\n  chain_->generateRstStream(writeBuf_, 1, ErrorCode::INTERNAL_ERROR);\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, OnSessionError) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  HTTPException ex(HTTPException::Direction::INGRESS, \"error\");\n  callbackStart_->onError(0, ex, false);\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, OnStreamErrorTracked) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  callbackStart_->onMessageBegin(1, nullptr);\n  auto req = makeGetRequest();\n  req->getHeaders().add(\"trace-header\", \"true\");\n  callbackStart_->onHeadersComplete(1, std::move(req));\n  HTTPException ex(HTTPException::Direction::INGRESS, \"error\");\n  callbackStart_->onError(1, ex, false);\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n\nTEST_F(DebugFilterTest, OnStreamErrorPartialMsg) {\n  chain_->onIngress(*makeIOBuf(\"foo\"));\n  auto req = makeGetRequest();\n  req->getHeaders().add(\"trace-header\", \"true\");\n  HTTPException ex(HTTPException::Direction::INGRESS, \"error\");\n  ex.setPartialMsg(std::move(req));\n  callbackStart_->onError(1, ex, false);\n  EXPECT_EQ(dumpedIngress_.move()->moveToFbString(), std::string(\"foo\"));\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HQCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/QPACKDecoderCodec.h>\n#include <proxygen/lib/http/codec/QPACKEncoderCodec.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <proxygen/lib/http/codec/compress/test/TestStreamingCallback.h>\n#include <proxygen/lib/http/codec/compress/test/TestUtil.h>\n#include <proxygen/lib/http/codec/test/HQFramerTest.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace proxygen::hq;\nusing namespace testing;\n\n// a FakeHTTPCodecCallback that can\n// be used as a unidirectional codec as well\nclass FakeHQHTTPCodecCallback\n    : public FakeHTTPCodecCallback\n    , public HQUnidirectionalCodec::Callback {\n public:\n  // Delegate HQUnidirectionalCodec::Callback::onError\n  // to FakeHTTPCodecCallback::onError\n  void onError(HTTPCodec::StreamID streamId,\n               const HTTPException& ex,\n               bool newTxn) override {\n    FakeHTTPCodecCallback::onError(streamId, ex, newTxn);\n  }\n};\n\nenum class CodecType {\n  UPSTREAM,\n  DOWNSTREAM,\n  CONTROL_UPSTREAM,\n  CONTROL_DOWNSTREAM,\n  PUSH,\n};\n\n// This is a template since for regular tests the fixture has to be derived from\n// testing::Test and for parameterized tests it has to be derived from\n// testing::TestWithParam<>\ntemplate <class T>\nclass HQCodecTestFixture : public T {\n public:\n  void SetUp() override {\n    makeCodecs();\n    SetUpCodecs();\n  }\n\n  void SetUpCodecs() {\n    callbacks_.setSessionStreamId(kSessionStreamId);\n    downstreamCodec_->setCallback(&callbacks_);\n    upstreamCodec_->setCallback(&callbacks_);\n    upstreamControlCodec_.setCallback(&callbacks_);\n    downstreamControlCodec_.setCallback(&callbacks_);\n  }\n\n  void parse() {\n    auto consumed = downstreamCodec_->onIngress(*queue_.front());\n    queue_.trimStart(consumed);\n  }\n\n  void parseUpstream() {\n    auto consumed = upstreamCodec_->onIngress(*queue_.front());\n    queue_.trimStart(consumed);\n  }\n\n  void parseControl(CodecType type) {\n    HQControlCodec* codec = nullptr;\n    downstreamControlCodec_.setCallback(&callbacks_);\n\n    switch (type) {\n      case CodecType::CONTROL_UPSTREAM:\n        codec = &upstreamControlCodec_;\n        break;\n      case CodecType::CONTROL_DOWNSTREAM:\n        codec = &downstreamControlCodec_;\n        break;\n      default:\n        LOG(FATAL) << \"Unknown Control Codec type\";\n        break;\n    }\n    auto ret = codec->onUnidirectionalIngress(queueCtrl_.move());\n    queueCtrl_.append(std::move(ret));\n  }\n\n  void qpackTest(bool blocked);\n\n  size_t addAndCheckSimpleHeaders() {\n    std::array<uint8_t, 6> simpleReq{0x00, 0x00, 0xC0, 0xC1, 0xD1, 0xD7};\n    writeFrameHeaderManual(\n        queue_, static_cast<uint64_t>(FrameType::HEADERS), simpleReq.size());\n    queue_.append(simpleReq.data(), simpleReq.size());\n    size_t n = queue_.chainLength();\n    parse();\n    EXPECT_EQ(callbacks_.headerFrames, 1);\n    EXPECT_EQ(callbacks_.headersComplete, 1);\n    EXPECT_EQ(callbacks_.bodyCalls, 0);\n    EXPECT_EQ(callbacks_.bodyLength, 0);\n    return n;\n  }\n\n  void makeCodecs() {\n    upstreamCodec_ = std::make_unique<HQStreamCodec>(\n        streamId_,\n        TransportDirection::UPSTREAM,\n        qpackUpstream_,\n        qpackUpEncoderWriteBuf_,\n        qpackUpDecoderWriteBuf_,\n        [] { return std::numeric_limits<uint64_t>::max(); },\n        ingressSettings_);\n    downstreamCodec_ = std::make_unique<HQStreamCodec>(\n        streamId_,\n        TransportDirection::DOWNSTREAM,\n        qpackDownstream_,\n        qpackDownEncoderWriteBuf_,\n        qpackDownDecoderWriteBuf_,\n        [] { return std::numeric_limits<uint64_t>::max(); },\n        ingressSettings_);\n  }\n\n  void testGoaway(HQControlCodec& codec, uint64_t drainId);\n\n protected:\n  FakeHQHTTPCodecCallback callbacks_;\n  HTTPSettings egressSettings_;\n  HTTPSettings ingressSettings_;\n  HQControlCodec upstreamControlCodec_{0x1111,\n                                       TransportDirection::UPSTREAM,\n                                       StreamDirection::INGRESS,\n                                       ingressSettings_,\n                                       hq::UnidirectionalStreamType::CONTROL};\n  HQControlCodec downstreamControlCodec_{0x2222,\n                                         TransportDirection::DOWNSTREAM,\n                                         StreamDirection::INGRESS,\n                                         ingressSettings_,\n                                         hq::UnidirectionalStreamType::CONTROL};\n  QPACKCodec qpackUpstream_;\n  QPACKCodec qpackDownstream_;\n  QPACKEncoderCodec qpackEncoderCodec_{qpackDownstream_, callbacks_};\n  QPACKDecoderCodec qpackDecoderCodec_{qpackUpstream_, callbacks_};\n  HTTPCodec::StreamID streamId_{0x1234};\n  IOBufQueue qpackUpEncoderWriteBuf_{IOBufQueue::cacheChainLength()};\n  IOBufQueue qpackUpDecoderWriteBuf_{IOBufQueue::cacheChainLength()};\n  IOBufQueue qpackDownEncoderWriteBuf_{IOBufQueue::cacheChainLength()};\n  IOBufQueue qpackDownDecoderWriteBuf_{IOBufQueue::cacheChainLength()};\n  std::unique_ptr<HQStreamCodec> upstreamCodec_{nullptr};\n  std::unique_ptr<HQStreamCodec> downstreamCodec_{nullptr};\n  IOBufQueue queue_{IOBufQueue::cacheChainLength()};\n  IOBufQueue queueCtrl_{IOBufQueue::cacheChainLength()};\n};\n\nclass HQCodecTest : public HQCodecTestFixture<Test> {};\n\nTEST_F(HQCodecTest, DataFrame) {\n  auto data = makeBuf(500);\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::DATA), data->length());\n  queue_.append(data->clone());\n  parse();\n  EXPECT_EQ(callbacks_.headerFrames, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, data->length());\n}\n\nTEST_F(HQCodecTest, PriorityUpdate) {\n  // SETTINGS is a must have\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  EXPECT_GT(upstreamControlCodec_.generatePriority(\n                queueCtrl_, 123, HTTPPriority(5, true)),\n            0);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(5, callbacks_.urgency);\n  EXPECT_TRUE(callbacks_.incremental);\n}\n\nTEST_F(HQCodecTest, DataFrameStreaming) {\n  auto data1 = makeBuf(500);\n  auto data2 = makeBuf(500);\n  writeFrameHeaderManual(queue_,\n                         static_cast<uint64_t>(FrameType::DATA),\n                         data1->length() + data2->length());\n  queue_.append(data1->clone());\n  parse();\n  queue_.append(data2->clone());\n  parse();\n  EXPECT_EQ(callbacks_.headerFrames, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 2);\n  EXPECT_EQ(callbacks_.bodyLength, data1->length() + data2->length());\n  auto data3 = makeBuf(100);\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::DATA), data3->length());\n  queue_.append(data3->clone());\n  parse();\n  EXPECT_EQ(callbacks_.headerFrames, 2);\n  EXPECT_EQ(callbacks_.bodyCalls, 3);\n  EXPECT_EQ(callbacks_.bodyLength,\n            data1->length() + data2->length() + data3->length());\n}\n\nTEST_F(HQCodecTest, PushPromiseFrame) {\n  hq::PushId pushId = 1234;\n\n  HTTPMessage msg = getGetRequest();\n  msg.getHeaders().add(HTTP_HEADER_USER_AGENT, \"optimus-prime\");\n\n  // push promises can only be sent by the server.\n  downstreamCodec_->generatePushPromise(queue_, streamId_, msg, pushId, false);\n\n  // push promises should be parsed by the client\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.headerFrames, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.pushId, pushId);\n  EXPECT_EQ(callbacks_.assocStreamId, streamId_);\n}\n\nTEST_F(HQCodecTest, HeadersOverSize) {\n  // Server sends limited MAX_HEADER_LIST_SIZE, client will send anyways,\n  // server errors\n  HTTPSettings egressSettings{{SettingsId::MAX_HEADER_LIST_SIZE, 37}};\n  HQControlCodec downstreamControlEgressCodec{\n      0x2223,\n      TransportDirection::DOWNSTREAM,\n      StreamDirection::EGRESS,\n      egressSettings,\n      hq::UnidirectionalStreamType::CONTROL};\n  downstreamControlEgressCodec.generateSettings(queueCtrl_);\n  parseControl(CodecType::CONTROL_UPSTREAM);\n  // set so the stream codec sees it\n  // TODO: the downstream codec doesn't have access to the egress settings,\n  // it relies on QPACK to check this\n  qpackDownstream_.setMaxUncompressed(37);\n\n  HTTPMessage msg = getGetRequest();\n  msg.getHeaders().add(HTTP_HEADER_USER_AGENT, \"optimus-prime\");\n  upstreamCodec_->generateHeader(queue_, streamId_, msg, false, nullptr);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  // These trailers are generated, but never parsed because it pauses after\n  // the headers error\n  upstreamCodec_->generateTrailers(queue_, streamId_, trailers);\n  parse();\n  downstreamCodec_->onIngressEOF();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n}\n\nTEST_F(HQCodecTest, Trailers) {\n  for (auto body = 0; body <= 1; body++) {\n    HTTPMessage msg = getPostRequest(body * 5);\n    msg.getHeaders().add(HTTP_HEADER_USER_AGENT, \"optimus-prime\");\n    upstreamCodec_->generateHeader(queue_, streamId_, msg, false, nullptr);\n\n    if (body) {\n      std::string data(\"abcde\");\n      auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n      upstreamCodec_->generateBody(queue_,\n                                   streamId_,\n                                   std::move(buf),\n                                   HTTPCodec::NoPadding,\n                                   /*eom=*/false);\n    }\n    HTTPHeaders trailers;\n    trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n    // stripped on generate\n    trailers.add(HTTP_HEADER_CONNECTION, \"keep-alive\");\n    upstreamCodec_->generateTrailers(queue_, streamId_, trailers);\n    upstreamCodec_->generateEOM(queue_, streamId_);\n    parse();\n    downstreamCodec_->onIngressEOF();\n\n    EXPECT_EQ(callbacks_.messageBegin, 1);\n    EXPECT_EQ(callbacks_.headersComplete, 1);\n    EXPECT_EQ(callbacks_.bodyCalls, body);\n    EXPECT_EQ(callbacks_.bodyLength, body * 5);\n    EXPECT_EQ(callbacks_.trailers, 1);\n    EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n    EXPECT_EQ(\"pico-de-gallo\",\n              callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n    EXPECT_EQ(callbacks_.msg->getTrailers()->size(), 1);\n    EXPECT_EQ(callbacks_.messageComplete, 1);\n    EXPECT_EQ(callbacks_.streamErrors, 0);\n    EXPECT_EQ(callbacks_.sessionErrors, 0);\n    callbacks_.reset();\n    makeCodecs();\n    downstreamCodec_->setCallback(&callbacks_);\n  }\n}\n\nTEST_F(HQCodecTest, GenerateExtraHeaders) {\n  HTTPMessage resp = getResponse(200, 2000);\n  HTTPHeaders extraHeaders;\n  extraHeaders.add(HTTP_HEADER_PRIORITY, \"u=2\");\n  downstreamCodec_->generateHeader(queue_,\n                                   streamId_,\n                                   resp,\n                                   true /* eom */,\n                                   nullptr /* HTTPHeaderSize */,\n                                   std::move(extraHeaders));\n\n  parseUpstream();\n\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"2000\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH));\n  EXPECT_EQ(\"u=2\", headers.getSingleOrEmpty(HTTP_HEADER_PRIORITY));\n}\n\ntemplate <class T>\nvoid HQCodecTestFixture<T>::qpackTest(bool blocked) {\n  QPACKCodec& server = qpackDownstream_;\n  server.setDecoderHeaderTableMaxSize(1024);\n  qpackUpstream_.setEncoderHeaderTableSize(1024);\n  auto streamId = upstreamCodec_->createStream();\n  HTTPMessage msg = getGetRequest();\n  msg.getHeaders().add(HTTP_HEADER_USER_AGENT, \"optimus-prime\");\n  upstreamCodec_->generateHeader(queue_, streamId, msg, false, nullptr);\n  EXPECT_FALSE(qpackUpEncoderWriteBuf_.empty());\n  if (!blocked) {\n    qpackEncoderCodec_.onUnidirectionalIngress(qpackUpEncoderWriteBuf_.move());\n    EXPECT_EQ(callbacks_.sessionErrors, 0);\n  }\n  TestStreamingCallback cb;\n  CHECK(!queue_.empty());\n  upstreamCodec_->generateBody(\n      queue_, streamId, folly::IOBuf::copyBuffer(\"ohai\"), 0, false);\n  upstreamCodec_->generateEOM(queue_, streamId);\n  auto consumed = downstreamCodec_->onIngress(*queue_.front());\n  EXPECT_TRUE(blocked || consumed == queue_.chainLength());\n  queue_.trimStart(consumed);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  if (blocked) {\n    EXPECT_EQ(callbacks_.headersComplete, 0);\n    qpackEncoderCodec_.onUnidirectionalIngress(qpackUpEncoderWriteBuf_.move());\n    EXPECT_EQ(callbacks_.sessionErrors, 0);\n  }\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  CHECK(callbacks_.msg);\n  if (blocked) {\n    EXPECT_FALSE(queue_.empty());\n    EXPECT_EQ(callbacks_.bodyCalls, 0);\n    EXPECT_EQ(callbacks_.bodyLength, 0);\n    consumed = downstreamCodec_->onIngress(*queue_.front());\n    queue_.trimStart(consumed);\n    EXPECT_TRUE(queue_.empty());\n  }\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 4);\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  auto ack = server.encodeHeaderAck(streamId);\n  if (ack) {\n    qpackDecoderCodec_.onUnidirectionalIngress(std::move(ack));\n  }\n  auto ici = server.encodeInsertCountInc();\n  if (ici) {\n    qpackDecoderCodec_.onUnidirectionalIngress(std::move(ici));\n  }\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HQCodecTest, qpack) {\n  qpackTest(false);\n}\n\nTEST_F(HQCodecTest, qpackBlocked) {\n  qpackTest(true);\n}\n\nTEST_F(HQCodecTest, qpackError) {\n  qpackEncoderCodec_.onUnidirectionalIngress(folly::IOBuf::wrapBuffer(\"\", 1));\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_QPACK_ENCODER_STREAM_ERROR);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  qpackDecoderCodec_.onUnidirectionalIngressEOF();\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n  EXPECT_EQ(callbacks_.sessionErrors, 2);\n  qpackEncoderCodec_.onUnidirectionalIngressEOF();\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n  EXPECT_EQ(callbacks_.sessionErrors, 3);\n\n  // duplicate method in headers\n  std::vector<compress::Header> headers;\n  std::string meth(\"GET\");\n  headers.emplace_back(HTTP_HEADER_COLON_METHOD, meth);\n  headers.emplace_back(HTTP_HEADER_COLON_METHOD, meth);\n  QPACKCodec& client = qpackUpstream_;\n  auto result = client.encode(headers, 1);\n  hq::writeHeaders(queue_, std::move(result.stream));\n  downstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.lastParseError->getHttpStatusCode(), 400);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n}\n\nTEST_F(HQCodecTest, qpackErrorShort) {\n  std::array<uint8_t, 1> bad = {0x00}; // LR, no delta base\n  hq::writeHeaders(queue_, folly::IOBuf::wrapBuffer(bad.data(), bad.size()));\n  downstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HQCodecTest, extraHeaders) {\n  std::array<uint8_t, 6> simpleReq{0x00, 0x00, 0xC0, 0xC1, 0xD1, 0xD7};\n  std::array<uint8_t, 3> simpleResp{0x00, 0x00, 0xD9};\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::HEADERS), simpleReq.size());\n  queue_.append(simpleReq.data(), simpleReq.size());\n  downstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  downstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  // The headers fail to parse because the codec thinks they are trailers\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_MESSAGE_ERROR);\n  queue_.move();\n  callbacks_.reset();\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::HEADERS), simpleResp.size());\n  queue_.append(simpleResp.data(), simpleResp.size());\n  upstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  upstreamCodec_->onIngress(*queue_.front());\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  // The headers fail to parse because the codec thinks they are trailers\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_MESSAGE_ERROR);\n}\n\n/* A second HEADERS frame is unexpected, and stops the parser */\nTEST_F(HQCodecTest, MultipleHeaders) {\n  writeValidFrame(queue_, FrameType::HEADERS);\n\n  // Write empty trailers, no more HEADERS frames allowed\n  std::array<uint8_t, 2> emptyHeaderBlock{0x00, 0x00};\n  hq::writeHeaders(queue_,\n                   folly::IOBuf::copyBuffer(emptyHeaderBlock.data(),\n                                            emptyHeaderBlock.size()));\n\n  // Write invalid QPACK header.  It is never parsed because the frame is\n  // unexpected\n  std::array<uint8_t, 2> qpackError{0xC0, 0x00};\n  hq::writeHeaders(\n      queue_, folly::IOBuf::copyBuffer(qpackError.data(), qpackError.size()));\n  parse();\n  // never seen, parser is paused\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\n/* An invalid HEADERS frame stops the parser */\nTEST_F(HQCodecTest, InvalidHeaders) {\n  // Valid but incomplete QPACK\n  std::array<uint8_t, 5> headersMissingField = {0x00, 0x00, 0xC0, 0xC1, 0xD1};\n  hq::writeHeaders(queue_,\n                   folly::IOBuf::wrapBuffer(headersMissingField.data(),\n                                            headersMissingField.size()));\n  parse();\n  // never seen, parser is paused\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.lastParseError->getHttpStatusCode(), 400);\n}\n\nTEST_F(HQCodecTest, ParserStopsAfterPushPromiseError) {\n  std::array<uint8_t, 3> qpackError{0xC0, 0x00};\n  hq::writePushPromise(\n      queue_,\n      0,\n      folly::IOBuf::copyBuffer(qpackError.data(), qpackError.size()));\n\n  // push promises should be parsed by the client\n  parseUpstream();\n  // Never seen, error\n  upstreamCodec_->onIngressEOF();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n}\n\nTEST_F(HQCodecTest, ZeroLengthData) {\n  // 0, 1, 0, 1, 0\n  for (auto i = 0; i < 5; i++) {\n    auto data = folly::IOBuf::create(1);\n    if (i % 2 == 1) {\n      data->writableData()[0] = 'a';\n      data->append(1);\n    }\n    hq::writeData(queue_, std::move(data));\n    parse();\n    EXPECT_EQ(callbacks_.messageBegin, 0);\n    EXPECT_EQ(callbacks_.headerFrames, i + 1);\n    EXPECT_EQ(callbacks_.headersComplete, 0);\n    EXPECT_EQ(callbacks_.bodyCalls, (i + 1) / 2);\n    EXPECT_EQ(callbacks_.messageComplete, 0);\n    EXPECT_EQ(callbacks_.streamErrors, 0);\n    EXPECT_EQ(callbacks_.sessionErrors, 0);\n  }\n\n  // EOF after 0 length frame is fine\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HQCodecTest, ZeroLengthSettings) {\n  std::deque<hq::SettingPair> emptySettings;\n  hq::writeSettings(queueCtrl_, emptySettings);\n  parseControl(CodecType::CONTROL_UPSTREAM);\n  EXPECT_EQ(callbacks_.settings, 1);\n  // EOF is not mid-frame\n  upstreamControlCodec_.onIngressEOF();\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HQCodecTest, InvalidSettings) {\n  std::deque<hq::SettingPair> settings{\n      {hq::SettingId::ENABLE_WEBTRANSPORT, 37}};\n  hq::writeSettings(queueCtrl_, settings);\n  parseControl(CodecType::CONTROL_UPSTREAM);\n  EXPECT_EQ(callbacks_.settings, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_SETTINGS_ERROR);\n}\n\nTEST_F(HQCodecTest, ZeroLengthTrailers) {\n  writeValidFrame(queue_, FrameType::HEADERS);\n\n  // Write empty trailers, invalid, QPACK minimum is 2 bytes\n  hq::writeHeaders(queue_, folly::IOBuf::create(1));\n\n  parse();\n  // Never seen, parser paused on error\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HQCodecTest, TruncatedStream) {\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::HEADERS), 0x10);\n  parse();\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_ERROR);\n\n  // A second EOF goes nowhere, because the codec is in error\n  downstreamCodec_->onIngressEOF();\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_ERROR);\n}\n\nTEST_F(HQCodecTest, BasicConnect) {\n  std::string authority = \"myhost:1234\";\n  HTTPMessage request;\n  request.setMethod(HTTPMethod::CONNECT);\n  request.getHeaders().add(proxygen::HTTP_HEADER_HOST, authority);\n  auto streamId = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(queue_, streamId, request, false /* eom */);\n\n  parse();\n  callbacks_.expectMessage(false, 1, \"\");\n  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(authority, headers.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));\n}\n\nTEST_F(HQCodecTest, TemplateDrivenConnect) {\n  // See https://fburl.com/nql0na8x for definition\n  std::string authority = \"request-proxy.example\";\n  std::string path = \"/proxy?target_host=192.0.2.1,2001:db8::1&tcp_port=443\";\n  std::string protocol = \"connect-tcp\";\n\n  HTTPMessage request;\n  request.setMethod(HTTPMethod::CONNECT);\n  request.getHeaders().add(proxygen::HTTP_HEADER_HOST, authority);\n  request.setURL(path);\n  request.setUpgradeProtocol(protocol);\n\n  auto streamId = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(queue_, streamId, request, false /* eom */);\n\n  parse();\n  callbacks_.expectMessage(false, 1, path);\n  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(authority, headers.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));\n  EXPECT_EQ(protocol, *callbacks_.msg->getUpgradeProtocol());\n}\n\nTEST_F(HQCodecTest, TrimLwsInHeaderValue) {\n  HTTPMessage req = getGetRequest(\"/test\");\n  req.getHeaders().add(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE,\n                       \" application/x-fb\");\n  // adding header with lws will strip here\n  req.getHeaders().add(HTTPHeaderCode::HTTP_HEADER_USER_AGENT, \"\\n\\t \\r\");\n  auto streamId = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(queue_, streamId, req, true /* eom */);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  auto& parsedHeaders = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_USER_AGENT));\n  EXPECT_TRUE(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_USER_AGENT)\n          .empty());\n  EXPECT_EQ(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE),\n      \"application/x-fb\");\n}\n\nTEST_F(HQCodecTest, OnlyDataAfterConnect) {\n  std::string authority = \"myhost:1234\";\n  HTTPMessage request;\n  request.setMethod(HTTPMethod::CONNECT);\n  request.getHeaders().add(proxygen::HTTP_HEADER_HOST, authority);\n  auto streamId = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(queue_, streamId, request, false /* eom */);\n\n  parse();\n  callbacks_.expectMessage(false, 1, \"\");\n  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(authority, headers.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));\n\n  writeValidFrame(queue_, FrameType::HEADERS);\n  parse();\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\nTEST_F(HQCodecTest, MultipleSettingsUpstream) {\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  parseControl(CodecType::CONTROL_UPSTREAM);\n  EXPECT_EQ(callbacks_.headerFrames, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\nTEST_F(HQCodecTest, MultipleSettingsDownstream) {\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(callbacks_.headerFrames, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\nTEST_F(HQCodecTest, RfcPriorityCallback) {\n  // SETTINGS is a must have\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::PRIORITY_UPDATE);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(1, callbacks_.urgency);\n  EXPECT_TRUE(callbacks_.incremental);\n}\n\nTEST_F(HQCodecTest, RfcPushPriorityCallback) {\n  // SETTINGS is a must have\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::PUSH_PRIORITY_UPDATE);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(1, callbacks_.urgency);\n  EXPECT_TRUE(callbacks_.incremental);\n}\n\nTEST_F(HQCodecTest, PriorityCallback) {\n  // SETTINGS is a must have\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::FB_PRIORITY_UPDATE);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(1, callbacks_.urgency);\n  EXPECT_TRUE(callbacks_.incremental);\n}\n\nTEST_F(HQCodecTest, PushPriorityCallback) {\n  // SETTINGS is a must have\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  writeValidFrame(queueCtrl_, FrameType::FB_PUSH_PRIORITY_UPDATE);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(1, callbacks_.urgency);\n  EXPECT_TRUE(callbacks_.incremental);\n}\n\ntemplate <class T>\nvoid HQCodecTestFixture<T>::testGoaway(HQControlCodec& codec,\n                                       uint64_t drainId) {\n  writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n  // Send draining goaway\n  EXPECT_FALSE(codec.isWaitingToDrain());\n  auto size = codec.generateGoaway(\n      queueCtrl_, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR, nullptr);\n  EXPECT_GT(size, 0);\n  EXPECT_TRUE(codec.isWaitingToDrain());\n\n  // HQControlCodec doesn't track the id's on it's own.  Asking for a second\n  // goaway with MaxStreamID will send a final with MAX\n  size = codec.generateGoaway(\n      queueCtrl_, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR, nullptr);\n  EXPECT_GT(size, 0);\n\n  // Ask for another GOAWAY, no-op\n  size = codec.generateGoaway(\n      queueCtrl_, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR, nullptr);\n  EXPECT_EQ(size, 0);\n  parseControl(CodecType::CONTROL_DOWNSTREAM);\n  EXPECT_EQ(callbacks_.goaways, 2);\n  EXPECT_THAT(callbacks_.goawayStreamIds, ElementsAre(drainId, drainId));\n}\n\nTEST_F(HQCodecTest, ServerGoaway) {\n  testGoaway(downstreamControlCodec_, kMaxClientBidiStreamId);\n}\n\nTEST_F(HQCodecTest, ClientGoaway) {\n  testGoaway(upstreamControlCodec_, kMaxPushId + 1);\n}\n\nTEST_F(HQCodecTest, HighAscii) {\n  std::vector<HTTPMessage> reqs;\n  reqs.push_back(getGetRequest(\"/guacamole\\xff\"));\n\n  reqs.push_back(getGetRequest(\"/guacamole\"));\n  reqs.back().getHeaders().set(HTTP_HEADER_HOST, std::string(\"foo.com\\xff\"));\n\n  reqs.push_back(getGetRequest(\"/guacamole\"));\n  reqs.back().getHeaders().set(folly::StringPiece(\"Foo\\xff\"), \"bar\");\n\n  reqs.push_back(getGetRequest(\"/guacamole\"));\n  reqs.back().getHeaders().set(\"Foo\", std::string(\"bar\\xff\"));\n\n  for (auto& req : reqs) {\n    auto id = upstreamCodec_->createStream();\n    upstreamCodec_->generateHeader(\n        queue_, id, req, true, nullptr /* headerSize */);\n    HQStreamCodec downstreamCodec(\n        id,\n        TransportDirection::DOWNSTREAM,\n        qpackDownstream_,\n        qpackDownEncoderWriteBuf_,\n        qpackDownDecoderWriteBuf_,\n        [] { return std::numeric_limits<uint64_t>::max(); },\n        ingressSettings_);\n    downstreamCodec.setStrictValidation(true);\n    downstreamCodec.setCallback(&callbacks_);\n    qpackEncoderCodec_.onUnidirectionalIngress(qpackUpEncoderWriteBuf_.move());\n    auto consumed = downstreamCodec.onIngress(*queue_.front());\n    queue_.trimStart(consumed);\n  }\n\n  EXPECT_EQ(callbacks_.messageBegin, 4);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 4);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(),\n            kErrorHeaderContentValidation);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  callbacks_.reset();\n\n  auto id = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(\n      queue_, id, getGetRequest(\"/\"), false, nullptr /* headerSize */);\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\\xff\");\n  auto g = folly::makeGuard(\n      [this] { downstreamCodec_->setStrictValidation(false); });\n  downstreamCodec_->setStrictValidation(true);\n  upstreamCodec_->generateTrailers(queue_, id, trailers);\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(),\n            kErrorHeaderContentValidation);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HQCodecTest, WebTransportProtocol) {\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::CONNECT);\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/wt\");\n  req.setSecure(true);\n  req.setUpgradeProtocol(\"webtransport\");\n\n  auto id = upstreamCodec_->createStream();\n  upstreamCodec_->generateHeader(queue_, id, req, false);\n  parse();\n\n  EXPECT_FALSE(callbacks_.msg->isIngressWebsocketUpgrade());\n  EXPECT_NE(nullptr, callbacks_.msg->getUpgradeProtocol());\n  EXPECT_EQ(\"webtransport\", *callbacks_.msg->getUpgradeProtocol());\n}\n\nTEST_F(HQCodecTest, PaddingIgnored) {\n  std::string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  HTTPMessage req = getPostRequest(data.length());\n\n  upstreamCodec_->generateHeader(queue_, streamId_, req, false);\n  upstreamCodec_->generatePadding(queue_, streamId_, 123);\n  upstreamCodec_->generateBody(\n      queue_, streamId_, std::move(buf), HTTPCodec::NoPadding, false);\n  upstreamCodec_->generatePadding(\n      queue_, streamId_, std::numeric_limits<uint16_t>::max());\n  upstreamCodec_->generateEOM(queue_, streamId_);\n  parse();\n  downstreamCodec_->onIngressEOF();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, data.length());\n  EXPECT_EQ(callbacks_.trailers, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.lastParseError, nullptr);\n}\n\nstruct FrameAllowedParams {\n  CodecType codecType;\n  FrameType frameType;\n  bool allowed;\n};\n\nstd::string frameParamsToTestName(\n    const testing::TestParamInfo<FrameAllowedParams>& info) {\n  std::string testName;\n  switch (info.param.codecType) {\n    case CodecType::CONTROL_UPSTREAM:\n      testName = \"UpstreamControl\";\n      break;\n    case CodecType::CONTROL_DOWNSTREAM:\n      testName = \"DownstreamControl\";\n      break;\n    case CodecType::UPSTREAM:\n      testName = \"Upstream\";\n      break;\n    case CodecType::DOWNSTREAM:\n      testName = \"Downstream\";\n      break;\n    default:\n      LOG(FATAL) << \"Unknown Codec Type\";\n  }\n  switch (info.param.frameType) {\n    case FrameType::DATA:\n      testName += \"Data\";\n      break;\n    case FrameType::HEADERS:\n      testName += \"Headers\";\n      break;\n    case FrameType::CANCEL_PUSH:\n      testName += \"CancelPush\";\n      break;\n    case FrameType::SETTINGS:\n      testName += \"Settings\";\n      break;\n    case FrameType::PUSH_PROMISE:\n      testName += \"PushPromise\";\n      break;\n    case FrameType::GOAWAY:\n      testName += \"Goaway\";\n      break;\n    case FrameType::MAX_PUSH_ID:\n      testName += \"MaxPushID\";\n      break;\n    case FrameType::PRIORITY_UPDATE:\n      testName += \"RfcPriorityUpdate\";\n      break;\n    case FrameType::PUSH_PRIORITY_UPDATE:\n      testName += \"RfcPushPriorityUpdate\";\n      break;\n    case FrameType::FB_PRIORITY_UPDATE:\n      testName += \"PriorityUpdate\";\n      break;\n    case FrameType::FB_PUSH_PRIORITY_UPDATE:\n      testName += \"PushPriorityUpdate\";\n      break;\n    case FrameType::WEBTRANSPORT_BIDI:\n      testName += \"WebTransportBidiStreamType\";\n      break;\n    default:\n      testName +=\n          folly::to<std::string>(static_cast<uint64_t>(info.param.frameType));\n      break;\n  }\n  return testName;\n}\n\nclass HQCodecTestFrameAllowed\n    : public HQCodecTestFixture<TestWithParam<FrameAllowedParams>> {};\n\nTEST_P(HQCodecTestFrameAllowed, FrameAllowedOnCodec) {\n  int expectedFrames = 0;\n  switch (GetParam().codecType) {\n    case CodecType::CONTROL_UPSTREAM:\n    case CodecType::CONTROL_DOWNSTREAM:\n      // SETTINGS MUST be the first frame on the CONTROL stream\n      if (GetParam().frameType != FrameType::SETTINGS) {\n        writeValidFrame(queueCtrl_, FrameType::SETTINGS);\n        expectedFrames++;\n      }\n      writeValidFrame(queueCtrl_, GetParam().frameType);\n      // add some extra trailing junk to the input buffer\n      queueCtrl_.append(IOBuf::copyBuffer(\"j\"));\n      parseControl(GetParam().codecType);\n      break;\n    case CodecType::UPSTREAM:\n      writeValidFrame(queue_, GetParam().frameType);\n      queue_.append(IOBuf::copyBuffer(\"j\"));\n      parseUpstream();\n      break;\n    case CodecType::DOWNSTREAM:\n      writeValidFrame(queue_, GetParam().frameType);\n      queue_.append(IOBuf::copyBuffer(\"j\"));\n      parse();\n      break;\n    default:\n      CHECK(false);\n  }\n  expectedFrames += GetParam().allowed ? 1 : 0;\n  EXPECT_EQ(callbacks_.headerFrames, expectedFrames);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, GetParam().allowed ? 0 : 1);\n  // If an error was triggered, check that any additional parse call does not\n  // raise another error, and that no new bytes are parsed\n  if (!GetParam().allowed) {\n    CHECK(queueCtrl_.chainLength() != 0 || queue_.chainLength() != 0);\n    EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n              HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n    auto lenBefore = 0;\n    auto lenAfter = 0;\n    switch (GetParam().codecType) {\n      case CodecType::CONTROL_UPSTREAM:\n      case CodecType::CONTROL_DOWNSTREAM:\n        lenBefore = queueCtrl_.chainLength();\n        parseControl(GetParam().codecType);\n        lenAfter = queueCtrl_.chainLength();\n        break;\n      case CodecType::UPSTREAM:\n        lenBefore = queue_.chainLength();\n        parseUpstream();\n        lenAfter = queue_.chainLength();\n        break;\n      case CodecType::DOWNSTREAM:\n        lenBefore = queue_.chainLength();\n        parse();\n        lenAfter = queue_.chainLength();\n        break;\n      default:\n        CHECK(false);\n    }\n    EXPECT_EQ(lenBefore, lenAfter);\n    EXPECT_EQ(callbacks_.headerFrames, expectedFrames);\n    EXPECT_EQ(callbacks_.streamErrors, 0);\n    EXPECT_EQ(callbacks_.sessionErrors, 1);\n    EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n              HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    FrameAllowedTests,\n    HQCodecTestFrameAllowed,\n    Values(\n        (FrameAllowedParams){CodecType::DOWNSTREAM, FrameType::DATA, true},\n        (FrameAllowedParams){CodecType::DOWNSTREAM, FrameType::HEADERS, true},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::CANCEL_PUSH, false},\n        (FrameAllowedParams){CodecType::DOWNSTREAM, FrameType::SETTINGS, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::PUSH_PROMISE, false},\n        (FrameAllowedParams){\n            CodecType::UPSTREAM, FrameType::PUSH_PROMISE, true},\n        (FrameAllowedParams){CodecType::DOWNSTREAM, FrameType::GOAWAY, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::MAX_PUSH_ID, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType(*getGreaseId(0)), true},\n        (FrameAllowedParams){CodecType::DOWNSTREAM,\n                             FrameType(*getGreaseId(hq::kMaxGreaseIdIndex)),\n                             true},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::PUSH_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::FB_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::FB_PUSH_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::DOWNSTREAM, FrameType::WEBTRANSPORT_BIDI, false},\n        (FrameAllowedParams){\n            CodecType::UPSTREAM, FrameType::FB_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::UPSTREAM, FrameType::FB_PUSH_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){\n            CodecType::UPSTREAM, FrameType::WEBTRANSPORT_BIDI, false},\n        // HQ Upstream Ingress Control Codec\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::DATA, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::HEADERS, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::CANCEL_PUSH, true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::SETTINGS, true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::PUSH_PROMISE, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::GOAWAY, true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::MAX_PUSH_ID, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType(*getGreaseId(12345)), true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType(*getGreaseId(54321)), true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::PRIORITY_UPDATE, false},\n        (FrameAllowedParams){CodecType::CONTROL_UPSTREAM,\n                             FrameType::PUSH_PRIORITY_UPDATE,\n                             false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::FB_PRIORITY_UPDATE, false},\n        (FrameAllowedParams){CodecType::CONTROL_UPSTREAM,\n                             FrameType::FB_PUSH_PRIORITY_UPDATE,\n                             false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::WEBTRANSPORT_BIDI, false},\n        // HQ Downstream Ingress Control Codec\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::DATA, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::HEADERS, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::CANCEL_PUSH, true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::SETTINGS, true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::PUSH_PROMISE, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::MAX_PUSH_ID, true},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType(*getGreaseId(98765)),\n                             true},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType(*getGreaseId(567879)),\n                             true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::PRIORITY_UPDATE, true},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType::PUSH_PRIORITY_UPDATE,\n                             true},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::FB_PRIORITY_UPDATE, true},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType::FB_PUSH_PRIORITY_UPDATE,\n                             true},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType::WEBTRANSPORT_BIDI,\n                             false}),\n    frameParamsToTestName);\n\nclass HQCodecTestFrameBeforeSettings\n    : public HQCodecTestFixture<TestWithParam<FrameAllowedParams>> {};\n\nTEST_P(HQCodecTestFrameBeforeSettings, FrameAllowedOnControlCodec) {\n  writeValidFrame(queueCtrl_, GetParam().frameType);\n  parseControl(GetParam().codecType);\n  EXPECT_EQ(callbacks_.headerFrames, GetParam().allowed ? 1 : 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, GetParam().allowed ? 0 : 1);\n  if (!GetParam().allowed) {\n    if (GetParam().frameType == hq::FrameType::DATA ||\n        GetParam().frameType == hq::FrameType::HEADERS ||\n        GetParam().frameType == hq::FrameType::PUSH_PROMISE) {\n      EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n                HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n    } else {\n      EXPECT_EQ(callbacks_.lastParseError->getHttp3ErrorCode(),\n                HTTP3::ErrorCode::HTTP_MISSING_SETTINGS);\n    }\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    FrameBeforeSettingsTests,\n    HQCodecTestFrameBeforeSettings,\n    Values(\n        // HQ Upstream Ingress Control Codec\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::DATA, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::HEADERS, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::CANCEL_PUSH, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::PUSH_PROMISE, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::GOAWAY, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType::MAX_PUSH_ID, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType(*getGreaseId(24680)), false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_UPSTREAM, FrameType(*getGreaseId(8642)), false},\n        // HQ Downstream Ingress Control Codec\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::DATA, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::HEADERS, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::CANCEL_PUSH, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::PUSH_PROMISE, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::GOAWAY, false},\n        (FrameAllowedParams){\n            CodecType::CONTROL_DOWNSTREAM, FrameType::MAX_PUSH_ID, false},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType(*getGreaseId(12121212)),\n                             false},\n        (FrameAllowedParams){CodecType::CONTROL_DOWNSTREAM,\n                             FrameType(*getGreaseId(3434343434)),\n                             false}),\n    frameParamsToTestName);\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HQFramerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/HQFramerTest.h>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <quic/folly_utils/Utils.h>\n\nusing namespace folly;\nusing namespace folly::io;\nusing namespace proxygen::hq;\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nnamespace {\n// Return true if (lhs < rhs), false otherwise\nbool comparePushId(PushId lhs, PushId rhs) {\n  return (lhs < rhs) ? false : true;\n}\n\n// Validate the given push ID.\nbool isValidPushId(folly::Optional<PushId> maxAllowedPushId, PushId pushId) {\n  if (!maxAllowedPushId.has_value()) {\n    VLOG(3) << __func__ << \"maximum push ID value has not been set\";\n    return false;\n  } else if (!comparePushId(maxAllowedPushId.value(), pushId)) {\n    VLOG(3) << __func__ << \"given pushid=\" << pushId\n            << \"exceeds possible push ID value \"\n            << \"maxAllowedPushId_=\" << maxAllowedPushId.value();\n    return false;\n  }\n  return true;\n}\n} // namespace\n\ntemplate <class T>\nclass HQFramerTestFixture : public T {\n public:\n  HQFramerTestFixture() = default;\n\n  template <typename Func, typename... Args>\n  void parse(ParseResult parseError,\n             Func&& parseFn,\n             FrameHeader& outHeader,\n             Args&&... outArgs) {\n    parse(parseError,\n          queue_.front(),\n          parseFn,\n          outHeader,\n          std::forward<Args>(outArgs)...);\n  }\n\n  template <typename Func, typename... Args>\n  void parse(ParseResult parseError,\n             const IOBuf* data,\n             Func&& parseFn,\n             FrameHeader& outHeader,\n             Args&&... outArgs) {\n    Cursor cursor(data);\n    size_t prevLen = cursor.totalLength();\n\n    auto type = quic::follyutils::decodeQuicInteger(cursor);\n    ASSERT_TRUE(type.has_value());\n    outHeader.type = FrameType(type->first);\n    auto length = quic::follyutils::decodeQuicInteger(cursor);\n    ASSERT_TRUE(length.has_value());\n    outHeader.length = length->first;\n\n    auto readBytes = prevLen - cursor.totalLength();\n    ASSERT_LE(type->second + length->second, kMaxFrameHeaderSize);\n    ASSERT_EQ(type->second + length->second, readBytes);\n    auto ret2 = (*parseFn)(cursor, outHeader, std::forward<Args>(outArgs)...);\n    ASSERT_EQ(ret2, parseError);\n  }\n\n  IOBufQueue queue_{IOBufQueue::cacheChainLength()};\n};\n\nclass HQFramerTest : public HQFramerTestFixture<testing::Test> {};\n\nTEST_F(HQFramerTest, TestValidPushId) {\n  PushId maxValidPushId = 10;\n  PushId validPushId = 9;\n  PushId exceedingPushId = 11;\n\n  auto expectValid = isValidPushId(maxValidPushId, validPushId);\n  EXPECT_TRUE(expectValid);\n\n  auto expectTooLarge = isValidPushId(maxValidPushId, exceedingPushId);\n  EXPECT_FALSE(expectTooLarge);\n\n  auto expectMatching = isValidPushId(maxValidPushId, maxValidPushId);\n  EXPECT_TRUE(expectMatching);\n\n  auto expectEmpty = isValidPushId(folly::none, validPushId);\n  EXPECT_FALSE(expectEmpty);\n}\n\nTEST_F(HQFramerTest, TestWriteFrameHeaderManual) {\n  auto res = writeFrameHeaderManual(\n      queue_, 0, static_cast<uint8_t>(proxygen::hq::FrameType::DATA));\n  EXPECT_EQ(res, 2);\n}\n\nTEST_F(HQFramerTest, TestWriteUnframedBytes) {\n  auto data = IOBuf::copyBuffer(\"I just met you and this is crazy.\");\n  auto dataLen = data->length();\n  auto res = data->computeChainDataLength();\n  EXPECT_EQ(res, dataLen);\n  queue_.append(std::move(data));\n  EXPECT_EQ(\"I just met you and this is crazy.\",\n            queue_.front()->clone()->to<std::string>());\n}\n\nTEST_F(HQFramerTest, DataFrameZeroLength) {\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(proxygen::hq::FrameType::DATA), 0);\n  FrameHeader outHeader;\n  std::unique_ptr<IOBuf> outBuf;\n  Cursor cursor(queue_.front());\n  parse(folly::none, parseData, outHeader, outBuf);\n}\n\nTEST_F(HQFramerTest, TestWriteGreaseFrame) {\n  auto res = writeGreaseFrame(queue_);\n  EXPECT_FALSE(res.hasError());\n\n  Cursor cursor(queue_.front());\n  auto type = quic::follyutils::decodeQuicInteger(cursor);\n  EXPECT_TRUE(type.has_value());\n  EXPECT_TRUE(hq::isGreaseId(type->first));\n\n  auto length = quic::follyutils::decodeQuicInteger(cursor);\n  EXPECT_TRUE(length.has_value());\n  EXPECT_EQ(length->first, 0);\n}\n\nTEST_F(HQFramerTest, TestWriteWebTransportStreamPreface) {\n  auto streamType = hq::WebTransportStreamType::UNI;\n  auto wtSessionId = 1977;\n  auto res = writeWTStreamPreface(queue_, streamType, wtSessionId);\n  EXPECT_FALSE(res.hasError());\n\n  Cursor cursor(queue_.front());\n  auto type = quic::follyutils::decodeQuicInteger(cursor);\n  EXPECT_TRUE(type.has_value());\n  EXPECT_EQ(type->first,\n            folly::to_underlying(hq::UnidirectionalStreamType::WEBTRANSPORT));\n\n  auto sessionId = quic::follyutils::decodeQuicInteger(cursor);\n  EXPECT_TRUE(sessionId.has_value());\n  EXPECT_EQ(sessionId->first, wtSessionId);\n}\n\nstruct FrameHeaderLengthParams {\n  uint8_t headerLength;\n  ParseResult error;\n};\n\nstruct DataOnlyFrameParams {\n  proxygen::hq::FrameType type;\n  WriteResult (*writeFn)(folly::IOBufQueue&, std::unique_ptr<folly::IOBuf>);\n  ParseResult (*parseFn)(Cursor& cursor,\n                         const FrameHeader&,\n                         std::unique_ptr<folly::IOBuf>&);\n  HTTP3::ErrorCode error;\n};\n\nclass HQFramerTestDataOnlyFrames\n    : public HQFramerTestFixture<TestWithParam<DataOnlyFrameParams>> {};\n\nTEST_P(HQFramerTestDataOnlyFrames, TestDataOnlyFrame) {\n  // Test writing and parsing a valid frame\n  auto data = makeBuf(500);\n  auto res = GetParam().writeFn(queue_, data->clone());\n  EXPECT_FALSE(res.hasError());\n  FrameHeader header;\n  std::unique_ptr<IOBuf> outBuf;\n  parse(folly::none, GetParam().parseFn, header, outBuf);\n  EXPECT_EQ(outBuf->moveToFbString(), data->moveToFbString());\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    DataOnlyFrameWriteParseTests,\n    HQFramerTestDataOnlyFrames,\n    Values((DataOnlyFrameParams){proxygen::hq::FrameType::DATA,\n                                 writeData,\n                                 parseData,\n                                 HTTP3::ErrorCode::HTTP_FRAME_ERROR},\n           (DataOnlyFrameParams){proxygen::hq::FrameType::HEADERS,\n                                 writeHeaders,\n                                 parseHeaders,\n                                 HTTP3::ErrorCode::HTTP_FRAME_ERROR}));\n\nTEST_F(HQFramerTest, ParsePushPromiseFrameOK) {\n  auto data = makeBuf(1000);\n  PushId inPushId = 4563;\n  auto result = writePushPromise(queue_, inPushId, data->clone());\n  EXPECT_FALSE(result.hasError());\n\n  FrameHeader outHeader;\n  PushId outPushId;\n  std::unique_ptr<IOBuf> outBuf;\n  parse(folly::none, parsePushPromise, outHeader, outPushId, outBuf);\n  EXPECT_EQ(outPushId, inPushId);\n  EXPECT_EQ(outBuf->moveToFbString(), data->moveToFbString());\n}\n\nstruct IdOnlyFrameParams {\n  proxygen::hq::FrameType type;\n  WriteResult (*writeFn)(folly::IOBufQueue&, uint64_t);\n  ParseResult (*parseFn)(Cursor& cursor, const FrameHeader&, uint64_t&);\n  HTTP3::ErrorCode error;\n};\n\nclass HQFramerTestIdOnlyFrames\n    : public HQFramerTestFixture<TestWithParam<IdOnlyFrameParams>> {};\n\nTEST_P(HQFramerTestIdOnlyFrames, TestIdOnlyFrame) {\n  // test writing a valid ID\n  {\n    queue_.move();\n    uint64_t validVarLenInt = 123456;\n    auto result = GetParam().writeFn(queue_, validVarLenInt);\n    EXPECT_FALSE(result.hasError());\n\n    FrameHeader header;\n    uint64_t outId;\n    parse(folly::none, GetParam().parseFn, header, outId);\n    EXPECT_EQ(GetParam().type, header.type);\n    EXPECT_EQ(validVarLenInt, outId);\n  }\n\n  // test writing an invalid ID\n  {\n    queue_.move();\n    uint64_t invalidVarLenInt = std::numeric_limits<uint64_t>::max();\n    auto result = GetParam().writeFn(queue_, invalidVarLenInt);\n    EXPECT_TRUE(result.hasError());\n  }\n  // test writing a valid ID, then modifying to make the parser try to read\n  // too much data\n  {\n    queue_.move();\n    uint64_t validVarLenInt = 63; // requires just 1 byte\n    auto result = GetParam().writeFn(queue_, validVarLenInt);\n    EXPECT_FALSE(result.hasError());\n\n    // modify one byte in the buf\n    auto buf = queue_.move();\n    buf->coalesce();\n    RWPrivateCursor wcursor(buf.get());\n    // 2 bytes frame header (payload length is just 1)\n    wcursor.skip(2);\n    wcursor.writeBE<uint8_t>( // this varint requires two bytes\n        static_cast<uint8_t>(0x42));\n    queue_.append(std::move(buf));\n\n    FrameHeader header;\n    uint64_t outId;\n    parse(GetParam().error, GetParam().parseFn, header, outId);\n  }\n\n  {\n    queue_.move();\n    uint64_t id = 3; // requires just 1 byte\n    auto result = GetParam().writeFn(queue_, id);\n    EXPECT_FALSE(result.hasError());\n\n    // Trim the frame header off\n    queue_.trimStart(2);\n    auto buf = queue_.move();\n    // Put in a new frame header (too long)\n    auto badLength = buf->computeChainDataLength() - 1 + 4;\n    writeFrameHeaderManual(\n        queue_, static_cast<uint64_t>(GetParam().type), badLength);\n    // clip the bad frame length\n    queue_.trimEnd(1);\n    queue_.append(std::move(buf));\n    queue_.append(IOBuf::copyBuffer(\"junk\"));\n\n    FrameHeader header;\n    uint64_t outId;\n    parse(GetParam().error, GetParam().parseFn, header, outId);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    IdOnlyFrameWriteParseTests,\n    HQFramerTestIdOnlyFrames,\n    Values((IdOnlyFrameParams){proxygen::hq::FrameType::CANCEL_PUSH,\n                               writeCancelPush,\n                               parseCancelPush,\n                               HTTP3::ErrorCode::HTTP_FRAME_ERROR},\n           (IdOnlyFrameParams){proxygen::hq::FrameType::GOAWAY,\n                               writeGoaway,\n                               parseGoaway,\n                               HTTP3::ErrorCode::HTTP_FRAME_ERROR},\n           (IdOnlyFrameParams){proxygen::hq::FrameType::MAX_PUSH_ID,\n                               writeMaxPushId,\n                               parseMaxPushId,\n                               HTTP3::ErrorCode::HTTP_FRAME_ERROR}));\n\nTEST_F(HQFramerTest, SettingsFrameOK) {\n  deque<hq::SettingPair> settings = {\n      {hq::SettingId::MAX_HEADER_LIST_SIZE, (SettingValue)4},\n      // Unknown IDs get ignored, and identifiers of the format\n      // \"0x1f * N + 0x21\" are reserved exactly for this\n      {(hq::SettingId)*getGreaseId(kMaxGreaseIdIndex), (SettingValue)5}};\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<hq::SettingPair> outSettings;\n  parse(folly::none, &parseSettings, header, outSettings);\n\n  ASSERT_EQ(proxygen::hq::FrameType::SETTINGS, header.type);\n  // Remove the last setting before comparison,\n  // it must be ignored since it's a GREASE setting\n  settings.pop_back();\n  ASSERT_EQ(settings, outSettings);\n}\n\nTEST_F(HQFramerTest, MaxPushIdFrameOK) {\n  PushId maxPushId = 10;\n  writeMaxPushId(queue_, maxPushId);\n\n  FrameHeader header;\n  PushId resultingPushId;\n  parse(folly::none, &parseMaxPushId, header, resultingPushId);\n\n  // Ensure header of frame and Push ID value are equivalent\n  // when writing and parsing\n  ASSERT_EQ(proxygen::hq::FrameType::MAX_PUSH_ID, header.type);\n  ASSERT_EQ(maxPushId, resultingPushId);\n}\n\nTEST_F(HQFramerTest, MaxPushIdFrameLargePushId) {\n  // Test with largest possible number\n  PushId maxPushId = quic::kEightByteLimit;\n  writeMaxPushId(queue_, maxPushId);\n\n  FrameHeader header;\n  PushId resultingPushId;\n  parse(folly::none, &parseMaxPushId, header, resultingPushId);\n\n  // Ensure header of frame and Push ID value are equivalent\n  // when writing and parsing\n  ASSERT_EQ(proxygen::hq::FrameType::MAX_PUSH_ID, header.type);\n  ASSERT_EQ(maxPushId, resultingPushId);\n}\n\nTEST_F(HQFramerTest, MaxPushIdTooLarge) {\n  // Test kEightByteLimit + 1 as over the limit\n  PushId maxPushId = (quic::kEightByteLimit + 1);\n  auto res = writeMaxPushId(queue_, maxPushId);\n\n  ASSERT_TRUE(res.hasError());\n}\n\nstruct SettingsValuesParams {\n  hq::SettingId id;\n  hq::SettingValue value;\n  bool allowed;\n};\n\nclass HQFramerTestSettingsValues\n    : public HQFramerTestFixture<TestWithParam<SettingsValuesParams>> {};\n\nTEST_P(HQFramerTestSettingsValues, ValueAllowed) {\n  deque<hq::SettingPair> settings = {{GetParam().id, GetParam().value}};\n\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<hq::SettingPair> outSettings;\n  ParseResult expectedParseResult = folly::none;\n  if (!GetParam().allowed) {\n    expectedParseResult = HTTP3::ErrorCode::HTTP_FRAME_ERROR;\n  }\n  parse(expectedParseResult, &parseSettings, header, outSettings);\n\n  ASSERT_EQ(proxygen::hq::FrameType::SETTINGS, header.type);\n  if (GetParam().allowed) {\n    ASSERT_EQ(settings, outSettings);\n  } else {\n    ASSERT_EQ(outSettings.size(), 0);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    SettingsValuesAllowedTests,\n    HQFramerTestSettingsValues,\n    Values((SettingsValuesParams){hq::SettingId::MAX_HEADER_LIST_SIZE, 0, true},\n           (SettingsValuesParams){hq::SettingId::MAX_HEADER_LIST_SIZE,\n                                  std::numeric_limits<uint32_t>::max(),\n                                  true},\n           (SettingsValuesParams){\n               hq::SettingId::QPACK_BLOCKED_STREAMS, 0, true},\n           (SettingsValuesParams){hq::SettingId::QPACK_BLOCKED_STREAMS,\n                                  std::numeric_limits<uint32_t>::max(),\n                                  true},\n           (SettingsValuesParams){hq::SettingId::HEADER_TABLE_SIZE, 0, true},\n           (SettingsValuesParams){hq::SettingId::HEADER_TABLE_SIZE,\n                                  std::numeric_limits<uint32_t>::max(),\n                                  true}));\n\nTEST_F(HQFramerTest, SettingsFrameEmpty) {\n  const deque<hq::SettingPair> settings = {};\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<hq::SettingPair> outSettings;\n  parse(folly::none, &parseSettings, header, outSettings);\n\n  ASSERT_EQ(proxygen::hq::FrameType::SETTINGS, header.type);\n  ASSERT_EQ(settings, outSettings);\n}\n\nTEST_F(HQFramerTest, SettingsFrameTrailingJunk) {\n  deque<hq::SettingPair> settings = {\n      {hq::SettingId::MAX_HEADER_LIST_SIZE, (SettingValue)4},\n      // Unknown IDs get ignored, and identifiers of the format\n      // \"0x1f * N + 0x21\" are reserved exactly for this\n      {(hq::SettingId)*getGreaseId(1234), (SettingValue)5}};\n  writeSettings(queue_, settings);\n  // Trim the frame header off\n  queue_.trimStart(2);\n  auto buf = queue_.move();\n  // Put in a new frame header (too long)\n  auto badLength = buf->computeChainDataLength() + 1;\n  writeFrameHeaderManual(\n      queue_, static_cast<uint64_t>(FrameType::SETTINGS), badLength);\n  queue_.append(std::move(buf));\n  queue_.append(IOBuf::copyBuffer(\"j\"));\n\n  FrameHeader header;\n  std::deque<hq::SettingPair> outSettings;\n  parse(\n      HTTP3::ErrorCode::HTTP_FRAME_ERROR, &parseSettings, header, outSettings);\n}\n\nTEST_F(HQFramerTest, SettingsFrameWriteError) {\n  deque<hq::SettingPair> settings = {\n      {(hq::SettingId)*getGreaseId(54321),\n       SettingValue(std::numeric_limits<uint64_t>::max())}};\n  auto res = writeSettings(queue_, settings);\n  ASSERT_TRUE(res.hasError());\n}\n\nTEST_F(HQFramerTest, SettingsFrameUnknownId) {\n  deque<hq::SettingPair> settings = {\n      {(hq::SettingId)0x1234, SettingValue(100000)}};\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<hq::SettingPair> outSettings;\n  parse(folly::none, &parseSettings, header, outSettings);\n\n  ASSERT_EQ(proxygen::hq::FrameType::SETTINGS, header.type);\n  ASSERT_TRUE(outSettings.empty());\n}\n\nTEST_F(HQFramerTest, PriorityUpdate) {\n  writePriorityUpdate(queue_, 126, \"u=3, i\");\n  FrameHeader header;\n  quic::StreamId outId;\n  HTTPPriority outPriority;\n  parse(folly::none, &parsePriorityUpdate, header, outId, outPriority);\n\n  EXPECT_EQ(126, outId);\n  EXPECT_EQ(proxygen::hq::FrameType::FB_PRIORITY_UPDATE, header.type);\n  EXPECT_EQ(3, outPriority.urgency);\n  EXPECT_TRUE(outPriority.incremental);\n}\n\nTEST_F(HQFramerTest, PriorityUpdateWithoutIncremental) {\n  writePriorityUpdate(queue_, 357, \"u=7\");\n  FrameHeader header;\n  quic::StreamId outId;\n  HTTPPriority outPriority;\n  parse(folly::none, &parsePriorityUpdate, header, outId, outPriority);\n\n  EXPECT_EQ(357, outId);\n  EXPECT_EQ(proxygen::hq::FrameType::FB_PRIORITY_UPDATE, header.type);\n  EXPECT_EQ(7, outPriority.urgency);\n  EXPECT_FALSE(outPriority.incremental);\n}\n\nTEST_F(HQFramerTest, BadPriorityUpdate) {\n  writePriorityUpdate(queue_, 357, \"b=ad\");\n  FrameHeader header;\n  quic::StreamId outId;\n  HTTPPriority outPriority;\n  parse(HTTP3::ErrorCode::HTTP_FRAME_ERROR,\n        &parsePriorityUpdate,\n        header,\n        outId,\n        outPriority);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HQFramerTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBufQueue.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\n// Writes out the common frame header without checks\nsize_t writeFrameHeaderManual(folly::IOBufQueue& queue,\n                              uint64_t decodedType,\n                              uint64_t decodedLength) {\n  folly::io::QueueAppender appender(&queue, proxygen::hq::kMaxFrameHeaderSize);\n  auto appenderOp = [&](auto val) { appender.writeBE<decltype(val)>(val); };\n  auto typeRes = quic::encodeQuicInteger(decodedType, appenderOp);\n  CHECK(typeRes.has_value());\n  auto lengthRes = quic::encodeQuicInteger(decodedLength, appenderOp);\n  CHECK(lengthRes.has_value());\n  return *typeRes + *lengthRes;\n}\n\n// Write a valid frame for each frame type\nvoid writeValidFrame(folly::IOBufQueue& queue, proxygen::hq::FrameType type) {\n  switch (type) {\n    case proxygen::hq::FrameType::SETTINGS:\n      proxygen::hq::writeSettings(\n          queue,\n          {{proxygen::hq::SettingId::MAX_HEADER_LIST_SIZE,\n            proxygen::hq::SettingValue(4)},\n           {(proxygen::hq::SettingId)*proxygen::hq::getGreaseId(\n                folly::Random::rand32(16)),\n            proxygen::hq::SettingValue(0xFACEB00C)}});\n      break;\n    case proxygen::hq::FrameType::CANCEL_PUSH:\n    case proxygen::hq::FrameType::GOAWAY:\n    case proxygen::hq::FrameType::MAX_PUSH_ID:\n    case proxygen::hq::FrameType::PUSH_PROMISE: {\n      // Just a varlength integer ID\n      uint64_t id = 123456;\n      size_t maybeDataSize = 100;\n      auto idSize = quic::getQuicIntegerSize(id);\n      writeFrameHeaderManual(\n          queue,\n          static_cast<uint64_t>(type),\n          *idSize + (type == proxygen::hq::FrameType::PUSH_PROMISE\n                         ? maybeDataSize\n                         : 0));\n      folly::io::QueueAppender appender(&queue, *idSize);\n      auto appenderOp = [&](auto val) { appender.writeBE<decltype(val)>(val); };\n      auto idResult = quic::encodeQuicInteger(id, appenderOp);\n      CHECK(idResult.has_value());\n      if (type == proxygen::hq::FrameType::PUSH_PROMISE) {\n        // header data for push-promise\n        uint8_t simplePushPromise[] = {0x00, 0x00, 0xC0, 0xC1, 0xD1, 0xD7};\n        queue.append(folly::IOBuf::copyBuffer(simplePushPromise, 6));\n      }\n      break;\n    }\n    case proxygen::hq::FrameType::HEADERS: {\n      uint8_t simpleHeaders[] = {0x00, 0x00, 0xC0, 0xC1, 0xD1, 0xD7};\n      auto data = folly::IOBuf::copyBuffer(simpleHeaders, 6);\n      writeFrameHeaderManual(\n          queue, static_cast<uint64_t>(type), data->computeChainDataLength());\n      queue.append(std::move(data));\n      break;\n    }\n    case proxygen::hq::FrameType::PRIORITY_UPDATE:\n    case proxygen::hq::FrameType::PUSH_PRIORITY_UPDATE:\n    case proxygen::hq::FrameType::FB_PRIORITY_UPDATE:\n    case proxygen::hq::FrameType::FB_PUSH_PRIORITY_UPDATE: {\n      quic::StreamId prioritizedId = 123;\n      auto prioritizedIdSize = quic::getQuicIntegerSize(prioritizedId);\n      auto data = folly::IOBuf::copyBuffer(\"u=1, i\");\n      writeFrameHeaderManual(\n          queue,\n          static_cast<uint64_t>(type),\n          *prioritizedIdSize + data->computeChainDataLength());\n      folly::io::QueueAppender appender(&queue, *prioritizedIdSize);\n      auto appenderOp = [&](auto val) { appender.writeBE<decltype(val)>(val); };\n      auto prioritizedIdResult =\n          quic::encodeQuicInteger(prioritizedId, appenderOp);\n      CHECK(prioritizedIdResult.has_value());\n      queue.append(std::move(data));\n      break;\n    }\n    default: {\n      // all the other frames (DATA, GREASE_*, ...) just\n      // have a binary blob as payload\n      auto data = proxygen::makeBuf(500);\n      writeFrameHeaderManual(\n          queue, static_cast<uint64_t>(type), data->length());\n      queue.append(data->clone());\n      break;\n    }\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HQMultiCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HQMultiCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace proxygen::hq;\nusing namespace testing;\n\nclass HQMultiCodecTest : public Test {\n public:\n  void SetUp() override {\n    codec_.setControlStreamID(kSessionStreamId);\n  }\n\n protected:\n  HQMultiCodec codec_{TransportDirection::DOWNSTREAM};\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n};\n\nTEST_F(HQMultiCodecTest, Egress) {\n  codec_.getQPACKCodec().setEncoderHeaderTableSize(4096);\n  codec_.setQPACKEncoderMaxDataFn([] { return 100; });\n  EXPECT_EQ(codec_.generateConnectionPreface(writeBuf_), 0);\n  EXPECT_GT(codec_.generateSettings(writeBuf_), 0);\n  EXPECT_EQ(codec_.generateSettingsAck(writeBuf_), 0);\n\n  HTTPCodec::StreamID id{1};\n  codec_.addCodec(id);\n  HTTPHeaderSize size;\n  codec_.generateHeader(writeBuf_, id, getResponse(200), false, &size);\n  EXPECT_GT(size.uncompressed, 0);\n  EXPECT_GT(codec_.getQPACKEncoderWriteBuf().chainLength(), 0);\n  EXPECT_GT(\n      codec_.generateBody(writeBuf_, id, makeBuf(100), folly::none, false), 0);\n  codec_.generatePushPromise(\n      writeBuf_, id, getGetRequest(), codec_.nextPushID(), false, &size);\n  EXPECT_GT(size.uncompressed, 0);\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer1\", \"trailer1\");\n  EXPECT_GT(codec_.generateTrailers(writeBuf_, id, trailers), 0);\n  EXPECT_EQ(codec_.generateEOM(writeBuf_, id), 0);\n  codec_.removeCodec(id);\n  EXPECT_GT(codec_.generateGoaway(\n                writeBuf_, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR),\n            0);\n}\n\nTEST_F(HQMultiCodecTest, Ingress) {\n  FakeHTTPCodecCallback callbacks;\n  callbacks.setSessionStreamId(kSessionStreamId);\n  HQMultiCodec upstreamCodec{TransportDirection::UPSTREAM};\n  HTTPCodec::StreamID id{0};\n  upstreamCodec.addCodec(id);\n  upstreamCodec.generateHeader(writeBuf_, id, getPostRequest(100));\n  upstreamCodec.generateBody(writeBuf_, id, makeBuf(100), folly::none, false);\n  upstreamCodec.generateEOM(writeBuf_, id);\n\n  // Test setting callback after codec creation\n  codec_.addCodec(id);\n  codec_.setCallback(&callbacks);\n\n  HTTPCodec::StreamID id2{4};\n  EXPECT_FALSE(codec_.setCurrentStream(id2));\n  codec_.addCodec(id2);\n  EXPECT_TRUE(codec_.setCurrentStream(id2));\n  EXPECT_EQ(codec_.onIngress(*writeBuf_.front()), writeBuf_.chainLength());\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.headersCompleteId, id2);\n  EXPECT_EQ(callbacks.bodyCalls, 1);\n  EXPECT_EQ(callbacks.messageComplete, 0);\n  EXPECT_EQ(callbacks.streamErrors, 0);\n  EXPECT_EQ(callbacks.sessionErrors, 0);\n  EXPECT_GT(codec_.getCompressionInfo().ingress.staticRefs_, 0);\n\n  EXPECT_TRUE(codec_.setCurrentStream(id));\n  writeBuf_.reset();\n  std::array<uint8_t, 2> frameMissingPayload{0x01, 0x10};\n  writeBuf_.append(folly::IOBuf::wrapBuffer(frameMissingPayload.data(),\n                                            frameMissingPayload.size()));\n  EXPECT_EQ(codec_.onIngress(*writeBuf_.front()), writeBuf_.chainLength());\n  EXPECT_TRUE(codec_.setCurrentStream(id));\n  codec_.onIngressEOF();\n  EXPECT_EQ(callbacks.streamErrors, 0);\n  EXPECT_EQ(callbacks.sessionErrors, 1);\n  EXPECT_EQ(callbacks.lastParseError->getHttp3ErrorCode(),\n            HTTP3::ErrorCode::HTTP_FRAME_ERROR);\n\n  EXPECT_TRUE(codec_.setCurrentStream(id2));\n  codec_.onIngressEOF();\n  EXPECT_EQ(callbacks.messageComplete, 1);\n}\n\nTEST_F(HQMultiCodecTest, Generic) {\n  EXPECT_TRUE(codec_.supportsParallelRequests());\n  EXPECT_NE(codec_.getEgressSettings(), nullptr);\n  EXPECT_EQ(codec_.getDefaultWindowSize(),\n            std::numeric_limits<uint32_t>::max());\n  EXPECT_TRUE(codec_.isWaitingToDrain());\n  codec_.enableDoubleGoawayDrain();\n  EXPECT_FALSE(codec_.isWaitingToDrain());\n  EXPECT_TRUE(codec_.isReusable());\n  EXPECT_EQ(codec_.getUserAgent(), std::string());\n  codec_.onIngressPushId(0);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTP1xCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n\n#include <folly/String.h>\n#include <folly/base64.h>\n#include <folly/portability/GTest.h>\n#include <list>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nclass HTTP1xCodecCallback : public HTTPCodec::Callback {\n public:\n  HTTP1xCodecCallback() = default;\n\n  void onMessageBegin(HTTPCodec::StreamID /*stream*/,\n                      HTTPMessage* /*msg*/) override {\n  }\n  void onPushMessageBegin(HTTPCodec::StreamID /*stream*/,\n                          HTTPCodec::StreamID /*assocStream*/,\n                          HTTPMessage* /*msg*/) override {\n  }\n  void onHeadersComplete(HTTPCodec::StreamID /*stream*/,\n                         std::unique_ptr<HTTPMessage> msg) override {\n    headersComplete++;\n    headerSize = msg->getIngressHeaderSize();\n    msg_ = std::move(msg);\n  }\n  void onBody(HTTPCodec::StreamID /*stream*/,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t /*padding*/) override {\n    bodyLen += chain->computeChainDataLength();\n  }\n  void onChunkHeader(HTTPCodec::StreamID /*stream*/,\n                     size_t /*length*/) override {\n  }\n  void onChunkComplete(HTTPCodec::StreamID /*stream*/) override {\n  }\n  void onTrailersComplete(HTTPCodec::StreamID /*stream*/,\n                          std::unique_ptr<HTTPHeaders> trailers) override {\n    trailersComplete++;\n    trailers_ = std::move(trailers);\n  }\n  void onMessageComplete(HTTPCodec::StreamID /*stream*/,\n                         bool /*upgrade*/) override {\n    ++messageComplete;\n  }\n  void onError(HTTPCodec::StreamID /*stream*/,\n               const HTTPException& error,\n               bool /*newTxn*/) override {\n    ++errors;\n    LOG(ERROR) << \"parse error \" << error;\n  }\n\n  uint32_t headersComplete{0};\n  uint32_t trailersComplete{0};\n  uint32_t messageComplete{0};\n  uint32_t errors{0};\n  uint32_t bodyLen{0};\n  HTTPHeaderSize headerSize;\n  std::unique_ptr<HTTPMessage> msg_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n};\n\nunique_ptr<folly::IOBuf> getSimpleRequestData() {\n  string req(\"GET /yeah HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  return folly::IOBuf::copyBuffer(req);\n}\n\nunique_ptr<folly::IOBuf> getSimpleRequestDataQueue() {\n  string req1(\"GET /yeah HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  auto buf1 = folly::IOBuf::copyBuffer(req1);\n  string req2(\"GET /yeah2 HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  auto buf2 = folly::IOBuf::copyBuffer(req2);\n  buf1->appendToChain(std::move(buf2));\n  return buf1;\n}\n\nTEST(HTTP1xCodecTest, TestSimpleHeaders) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = getSimpleRequestData();\n  codec.onIngress(*buffer);\n  // Call it twice so we catch that we reset compressed size\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 2);\n  EXPECT_EQ(buffer->length(), callbacks.headerSize.uncompressed);\n  EXPECT_EQ(callbacks.headerSize.compressed, callbacks.headerSize.uncompressed);\n}\n\nTEST(HTTP1xCodecTest, TestSimpleHeadersQueue) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = getSimpleRequestDataQueue();\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 2);\n  EXPECT_EQ(callbacks.headerSize.compressed, callbacks.headerSize.uncompressed);\n}\n\nTEST(HTTP1xCodecTest, TestSimpleHeadersQueueWithPause) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = getSimpleRequestDataQueue();\n\n  EXPECT_CALL(callbacks, onMessageComplete(_, _))\n      .Times(1)\n      .WillOnce(InvokeWithoutArgs([&] { codec.setParserPaused(true); }));\n  size_t bytesParsed = codec.onIngress(*buffer);\n  codec.setParserPaused(false);\n  buffer->trimStart(bytesParsed);\n  EXPECT_CALL(callbacks, onMessageComplete(_, _)).Times(1);\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, Test09Req) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(string(\"GET /yeah\\r\\n\"));\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.messageComplete, 1);\n  EXPECT_EQ(buffer->length(), callbacks.headerSize.uncompressed);\n  EXPECT_EQ(callbacks.headerSize.compressed, callbacks.headerSize.uncompressed);\n  buffer = folly::IOBuf::copyBuffer(string(\"\\r\\n\"));\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.messageComplete, 1);\n  EXPECT_EQ(callbacks.msg_->getHTTPVersion(), HTTPMessage::kHTTPVersion09);\n}\n\nTEST(HTTP1xCodecTest, Test09ReqVers) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(string(\"GET /yeah HTTP/0.9\\r\\n\"));\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.messageComplete, 1);\n  EXPECT_EQ(buffer->length(), callbacks.headerSize.uncompressed);\n  EXPECT_EQ(callbacks.headerSize.compressed, callbacks.headerSize.uncompressed);\n}\n\nTEST(HTTP1xCodecTest, Test09Resp) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n  HTTP1xCodecCallback callbacks;\n  HTTPMessage req;\n  auto id = codec.createStream();\n  req.setHTTPVersion(0, 9);\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/\");\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue buf;\n  codec.generateHeader(buf, id, req, true);\n  auto buffer =\n      folly::IOBuf::copyBuffer(string(\"iamtheverymodelofamodernmajorgeneral\"));\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.bodyLen, buffer->computeChainDataLength());\n  EXPECT_EQ(callbacks.messageComplete, 0);\n  codec.onIngressEOF();\n  EXPECT_EQ(callbacks.messageComplete, 1);\n}\n\nTEST(HTTP1xCodecTest, TestResponseSplit) {\n  HTTP1xCodec downcodec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodec upcodec(TransportDirection::UPSTREAM);\n\n  HTTP1xCodecCallback callbacks;\n  downcodec.setCallback(&callbacks);\n  auto id = upcodec.createStream();\n\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setMethod(\"GET\");\n  req.setURL(\"/foo\");\n  req.getHeaders().set(\"InjectHeader\", \"Value\\r\\nx: new\");\n  folly::IOBufQueue buf;\n  upcodec.generateHeader(buf, id, req);\n  downcodec.onIngress(*buf.front());\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.messageComplete, 1);\n  // make sure that the header is not sent on the wire.\n  // Otherwise it would be parsed as two different headers with HTTP <= 1.1\n  EXPECT_EQ(callbacks.msg_->getHeaders().getSingleOrEmpty(\"InjectHeader\"), \"\");\n  EXPECT_EQ(callbacks.msg_->getHeaders().getSingleOrEmpty(\"x\"), \"\");\n}\n\nTEST(HTTP1xCodecTest, TestO9NoVersion) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  auto id = codec.createStream();\n  req.setHTTPVersion(0, 9);\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/yeah\");\n  folly::IOBufQueue buf;\n  codec.generateHeader(buf, id, req, true);\n  EXPECT_TRUE(folly::IOBufEqualTo()(\n      *buf.front(), *folly::IOBuf::copyBuffer(\"GET /yeah\\r\\n\")));\n}\n\nTEST(HTTP1xCodecTest, TestKeepalive09_10) {\n  HTTP1xCodec codec1(TransportDirection::DOWNSTREAM, true);\n  HTTP1xCodecCallback callbacks1;\n  codec1.setCallback(&callbacks1);\n  auto buffer = folly::IOBuf::copyBuffer(string(\"GET /yeah\\r\\n\"));\n  codec1.onIngress(*buffer);\n  EXPECT_EQ(callbacks1.headersComplete, 1);\n  EXPECT_EQ(callbacks1.messageComplete, 1);\n  EXPECT_EQ(callbacks1.msg_->getHTTPVersion(), HTTPMessage::kHTTPVersion09);\n  HTTPCodec::StreamID id = 1;\n  HTTPMessage resp;\n  resp.setHTTPVersion(0, 9);\n  resp.setStatusCode(200);\n  resp.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"0\");\n  resp.getHeaders().set(HTTP_HEADER_DATE, \"\");\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  codec1.generateHeader(buf, id, resp, true);\n  // Even if forced to HTTP/1.1, HTTP/0.9 has no headers\n  EXPECT_EQ(buf.chainLength(), 0);\n  EXPECT_FALSE(codec1.isReusable());\n\n  HTTP1xCodec codec2(TransportDirection::DOWNSTREAM, true);\n  HTTP1xCodecCallback callbacks2;\n  codec2.setCallback(&callbacks2);\n  buffer = folly::IOBuf::copyBuffer(string(\"GET /yeah HTTP/1.0\\r\\n\\r\\n\"));\n  codec2.onIngress(*buffer);\n  EXPECT_EQ(callbacks2.headersComplete, 1);\n  EXPECT_EQ(callbacks2.messageComplete, 1);\n  EXPECT_EQ(callbacks2.msg_->getHTTPVersion(), HTTPMessage::kHTTPVersion10);\n  resp.setHTTPVersion(1, 0);\n  codec2.generateHeader(buf, id, resp, true);\n\n  EXPECT_TRUE(folly::IOBufEqualTo()(\n      *buf.front(),\n      *folly::IOBuf::copyBuffer(\"HTTP/1.1 200 \\r\\n\"\n                                \"Date: \\r\\n\"\n                                \"Connection: close\\r\\n\"\n                                \"Content-Length: 0\\r\\n\\r\\n\")));\n  EXPECT_FALSE(codec2.isReusable());\n  buf.reset();\n\n  HTTP1xCodec codec3(TransportDirection::DOWNSTREAM, true);\n  HTTP1xCodecCallback callbacks3;\n  codec3.setCallback(&callbacks3);\n  buffer =\n      folly::IOBuf::copyBuffer(string(\"GET /yeah HTTP/1.0\\r\\n\"\n                                      \"Connection: keep-alive\\r\\n\\r\\n\"));\n  codec3.onIngress(*buffer);\n  EXPECT_EQ(callbacks3.headersComplete, 1);\n  EXPECT_EQ(callbacks3.messageComplete, 1);\n  EXPECT_EQ(callbacks3.msg_->getHTTPVersion(), HTTPMessage::kHTTPVersion10);\n  codec3.generateHeader(buf, id, resp, true);\n  EXPECT_TRUE(folly::IOBufEqualTo()(\n      *buf.front(),\n      *folly::IOBuf::copyBuffer(\"HTTP/1.1 200 \\r\\n\"\n                                \"Date: \\r\\n\"\n                                \"Connection: keep-alive\\r\\n\"\n                                \"Content-Length: 0\\r\\n\\r\\n\")));\n  EXPECT_TRUE(codec3.isReusable());\n  buf.reset();\n\n  HTTP1xCodec codec4(TransportDirection::DOWNSTREAM, true);\n  HTTP1xCodecCallback callbacks4;\n  codec4.setCallback(&callbacks4);\n  buffer = folly::IOBuf::copyBuffer(string(\"GET /yeah HTTP/1.0\\r\\n\\r\\n\"));\n  codec4.onIngress(*buffer);\n  EXPECT_EQ(callbacks4.headersComplete, 1);\n  EXPECT_EQ(callbacks4.messageComplete, 1);\n  EXPECT_EQ(callbacks4.msg_->getHTTPVersion(), HTTPMessage::kHTTPVersion10);\n  resp.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  resp.getHeaders().remove(HTTP_HEADER_CONTENT_LENGTH);\n  resp.setIsChunked(true);\n  codec4.generateHeader(buf, id, resp, true);\n  EXPECT_TRUE(folly::IOBufEqualTo()(\n      *buf.front(),\n      *folly::IOBuf::copyBuffer(\"HTTP/1.1 200 \\r\\n\"\n                                \"Date: \\r\\n\"\n                                \"Connection: close\\r\\n\\r\\n\")));\n  EXPECT_FALSE(codec4.isReusable());\n  buf.reset();\n}\n\nTEST(HTTP1xCodecTest, TestBadHeaders) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(\n      string(\"GET /yeah HTTP/1.1\\nUser-Agent: Mozilla/5.0 Version/4.0 \"\n             \"\\x10i\\xC7n tho\\xA1iSafari/534.30]\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n            EXPECT_EQ(error->getProxygenError(), kErrorParseHeader);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, TestHighAsciiUA) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(\n      string(\"GET /yeah HTTP/1.1\\r\\nUser-Agent: ꪶ𝛸ꫂ_𝐹𝛩𝑅𝐶𝛯_𝑉2\\r\\n\\r\\n\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n            EXPECT_EQ(error->getProxygenError(), kErrorHeaderContentValidation);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, TestBadURL) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(string(\"GET /Ñoño HTTP/1.1\\r\\n\\r\\n\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n            EXPECT_EQ(error->getProxygenError(), kErrorParseHeader);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, TestUnderscoreAllowedInHost) {\n  // The strict mode of http_parser used to couple strict URL parsing with\n  // disallowing _ in hostnames.  We decoupled those behaviors so this should\n  // pass even in strict mode.\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(\n      \"CONNECT face_book.facebook.com:443 HTTP/1.1\\r\\n\\r\\n\");\n\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks, onMessageComplete(1, _));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, TestHeadRequestChunkedResponse) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto txnID = codec.createStream();\n\n  // Generate a HEAD request\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"HEAD /www.facebook.com HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  codec.onIngress(*reqBuf);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n\n  // Generate chunked response with no body\n  HTTPMessage resp;\n  resp.setHTTPVersion(1, 1);\n  resp.setStatusCode(200);\n  resp.setIsChunked(true);\n  resp.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  folly::IOBufQueue respBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateHeader(respBuf, txnID, resp, true);\n  auto respStr = respBuf.move()->moveToFbString();\n  EXPECT_TRUE(respStr.find(\"0\\r\\n\") == string::npos);\n}\n\nTEST(HTTP1xCodecTest, TestGetRequestChunkedResponse) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto txnID = codec.createStream();\n\n  // Generate a GET request\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"GET /www.facebook.com HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  codec.onIngress(*reqBuf);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n\n  // Generate chunked response with body\n  HTTPMessage resp;\n  resp.setHTTPVersion(1, 1);\n  resp.setStatusCode(200);\n  resp.setIsChunked(true);\n  resp.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  folly::IOBufQueue respBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateHeader(respBuf, txnID, resp, false);\n\n  auto headerFromBuf = respBuf.split(respBuf.chainLength());\n\n  string resp1(\"Hello\");\n  auto body1 = folly::IOBuf::copyBuffer(resp1);\n\n  string resp2;\n  auto body2 = folly::IOBuf::copyBuffer(resp2);\n\n  codec.generateBody(\n      respBuf, txnID, std::move(body1), HTTPCodec::NoPadding, false);\n\n  auto bodyFromBuf = respBuf.split(respBuf.chainLength());\n  ASSERT_EQ(\"5\\r\\nHello\\r\\n\", bodyFromBuf->moveToFbString());\n\n  codec.generateBody(\n      respBuf, txnID, std::move(body2), HTTPCodec::NoPadding, true);\n\n  bodyFromBuf = respBuf.split(respBuf.chainLength());\n  ASSERT_EQ(\"0\\r\\n\\r\\n\", bodyFromBuf->moveToFbString());\n}\n\nunique_ptr<folly::IOBuf> getChunkedRequest1st() {\n  string req(\"GET /aha HTTP/1.1\\n\");\n  return folly::IOBuf::copyBuffer(req);\n}\n\nunique_ptr<folly::IOBuf> getChunkedRequest2nd() {\n  string req(\"Host: m.facebook.com\\nAccept-Encoding: meflate\\n\\n\");\n  return folly::IOBuf::copyBuffer(req);\n}\n\nTEST(HTTP1xCodecTest, TestChunkedHeaders) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  // test a sequence of requests to make sure we're resetting the size counter\n  for (int i = 0; i < 3; i++) {\n    callbacks.headersComplete = 0;\n    auto buffer1 = getChunkedRequest1st();\n    codec.onIngress(*buffer1);\n    EXPECT_EQ(callbacks.headersComplete, 0);\n\n    auto buffer2 = getChunkedRequest2nd();\n    codec.onIngress(*buffer2);\n    EXPECT_EQ(callbacks.headersComplete, 1);\n    EXPECT_EQ(callbacks.headerSize.uncompressed,\n              buffer1->length() + buffer2->length());\n    EXPECT_EQ(callbacks.headerSize.uncompressed,\n              callbacks.headerSize.compressed);\n  }\n}\n\nTEST(HTTP1xCodecTest, TestChunkedUpstream) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n\n  auto txnID = codec.createStream();\n\n  HTTPMessage msg;\n  msg.setHTTPVersion(1, 1);\n  msg.setURL(\"https://www.facebook.com/\");\n  msg.getHeaders().set(HTTP_HEADER_HOST, \"www.facebook.com\");\n  msg.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  msg.setIsChunked(true);\n\n  HTTPHeaderSize size;\n\n  folly::IOBufQueue buf(folly::IOBufQueue::cacheChainLength());\n  codec.generateHeader(buf, txnID, msg, false, &size);\n  auto headerFromBuf = buf.split(buf.chainLength());\n\n  string req1(\"Hello\");\n  auto body1 = folly::IOBuf::copyBuffer(req1);\n\n  string req2(\"World\");\n  auto body2 = folly::IOBuf::copyBuffer(req2);\n\n  codec.generateBody(buf, txnID, std::move(body1), HTTPCodec::NoPadding, false);\n\n  auto bodyFromBuf = buf.split(buf.chainLength());\n  ASSERT_EQ(\"5\\r\\nHello\\r\\n\", bodyFromBuf->moveToFbString());\n\n  codec.generateBody(buf, txnID, std::move(body2), HTTPCodec::NoPadding, true);\n  LOG(WARNING) << \"len chain\" << buf.chainLength();\n\n  auto eomFromBuf = buf.split(buf.chainLength());\n  ASSERT_EQ(\"5\\r\\nWorld\\r\\n0\\r\\n\\r\\n\", eomFromBuf->moveToFbString());\n}\n\nTEST(HTTP1xCodecTest, TestBadPost100) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  InSequence enforceOrder;\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _))\n      .WillOnce(InvokeWithoutArgs([&] {\n        HTTPMessage cont;\n        cont.setStatusCode(100);\n        cont.setStatusMessage(\"Continue\");\n        codec.generateHeader(writeBuf, 1, cont);\n      }));\n\n  EXPECT_CALL(callbacks, onBody(1, _, _));\n  EXPECT_CALL(callbacks, onMessageComplete(1, _));\n  EXPECT_CALL(callbacks, onMessageBegin(2, _)).WillOnce(InvokeWithoutArgs([&] {\n    // simulate HTTPSession's aversion to pipelining\n    codec.setParserPaused(true);\n\n    // Trigger the response to the POST\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    resp.setStatusMessage(\"OK\");\n    codec.generateHeader(writeBuf, 1, resp);\n    codec.generateEOM(writeBuf, 1);\n    codec.setParserPaused(false);\n  }));\n  EXPECT_CALL(callbacks, onError(2, _, _)).WillOnce(InvokeWithoutArgs([&] {\n    HTTPMessage resp;\n    resp.setStatusCode(400);\n    resp.setStatusMessage(\"Bad\");\n    codec.generateHeader(writeBuf, 2, resp);\n    codec.generateEOM(writeBuf, 2);\n  }));\n  // Generate a POST request with a bad content-length\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\nHost: www.facebook.com\\r\\n\"\n      \"Expect: 100-Continue\\r\\nContent-Length: 5\\r\\n\\r\\nabcdefghij\");\n  codec.onIngress(*reqBuf);\n}\n\nTEST(HTTP1xCodecTest, TestMultipleIdenticalContentLengthHeaders) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  // Generate a POST request with two identical Content-Length headers\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\nHost: www.facebook.com\\r\\n\"\n      \"Content-Length: 5\\r\\nContent-Length: 5\\r\\n\\r\\n\");\n  codec.onIngress(*reqBuf);\n\n  // Check that the request is accepted\n  EXPECT_EQ(callbacks.streamErrors, 0);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n}\n\nTEST(HTTP1xCodecTest, TestMultipleDistinctContentLengthHeaders) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  // Generate a POST request with two distinct Content-Length headers\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\nHost: www.facebook.com\\r\\n\"\n      \"Content-Length: 5\\r\\nContent-Length: 6\\r\\n\\r\\n\");\n  codec.onIngress(*reqBuf);\n\n  // Check that the request fails before the codec finishes parsing the headers\n  EXPECT_EQ(callbacks.streamErrors, 1);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 0);\n  EXPECT_EQ(callbacks.lastParseError->getHttpStatusCode(), 400);\n}\n\nTEST(HTTP1xCodecTest, TestCorrectTransferEncodingHeader) {\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  downstream.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  // Generate a POST request with folded\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\nHost: www.facebook.com\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\");\n  downstream.onIngress(*reqBuf);\n\n  // Check that the request fails before the codec finishes parsing the headers\n  EXPECT_EQ(callbacks.streamErrors, 0);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n}\n\nTEST(HTTP1xCodecTest, TestFoldedTransferEncodingHeader) {\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  downstream.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  // Generate a POST request with folded\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\nHost: www.facebook.com\\r\\n\"\n      \"Transfer-Encoding: \\r\\n chunked\\r\\nContent-Length: 8\\r\\n\\r\\n\");\n  downstream.onIngress(*reqBuf);\n\n  // Check that the request fails before the codec finishes parsing the headers\n  EXPECT_EQ(callbacks.streamErrors, 1);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 0);\n  EXPECT_EQ(callbacks.lastParseError->getHttpStatusCode(), 400);\n}\n\nTEST(HTTP1xCodecTest, TestBadTransferEncodingHeader) {\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  downstream.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"POST /www.facebook.com HTTP/1.1\\r\\n\"\n      \"Host: www.facebook.com\\r\\n\"\n      \"Transfer-Encoding: chunked, zorg\\r\\n\"\n      \"Transfer-Encoding: chunked, zorg\\r\\n\"\n      \"\\r\\n\");\n  downstream.onIngress(*reqBuf);\n\n  // Check that the request fails before the codec finishes parsing the headers\n  EXPECT_EQ(callbacks.streamErrors, 1);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 0);\n  EXPECT_EQ(callbacks.lastParseError->getHttpStatusCode(), 400);\n}\n\nTEST(HTTP1xCodecTest, TestMalformedChunkDelimiter) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n\n  auto buf = folly::IOBuf::copyBuffer(\n      \"GET /hello HTTP/1.1\\r\\n\"\n      \"Host: localhost:8080\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\"\n      \"\\r\\n\"\n      \"47;\"\n      \"\\rX\" // after a carriage return, \"X\" isn't a line feed character\n      \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"\n      \"AA\\r\\n\"\n      \"0\\r\\n\"\n      \"\\r\\n\"\n      \"GET /admin HTTP/1.1\\r\\n\"\n      \"Host: localhost\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\"\n      \"\\r\\n\"\n      \"0\\r\\n\"\n      \"\\r\\n\");\n\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n            EXPECT_EQ(error->getProxygenError(), kErrorParseBody);\n          }));\n\n  codec.onIngress(*buf);\n}\n\nTEST(HTTP1xCodecTest, Test1xxConnectionHeader) {\n  HTTP1xCodec upstream(TransportDirection::UPSTREAM);\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  upstream.setCallback(&callbacks);\n  HTTPMessage resp;\n  resp.setStatusCode(100);\n  resp.setHTTPVersion(1, 1);\n  resp.getHeaders().add(HTTP_HEADER_CONNECTION, \"Upgrade\");\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  auto streamID = downstream.createStream();\n  downstream.generateHeader(writeBuf, streamID, resp);\n  upstream.onIngress(*writeBuf.front());\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(\n      callbacks.msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n      \"Upgrade\");\n  resp.setStatusCode(200);\n  resp.getHeaders().remove(HTTP_HEADER_CONNECTION);\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"0\");\n  writeBuf.reset();\n  downstream.generateHeader(writeBuf, streamID, resp);\n  upstream.onIngress(*writeBuf.front());\n  EXPECT_EQ(callbacks.headersComplete, 2);\n  EXPECT_EQ(\n      callbacks.msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n      \"keep-alive\");\n}\n\nTEST(HTTP1xCodecTest, TestChainedBody) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n\n  folly::IOBufQueue bodyQueue;\n  ON_CALL(callbacks, onBody(1, _, _))\n      .WillByDefault(\n          Invoke([&bodyQueue](HTTPCodec::StreamID,\n                              std::shared_ptr<folly::IOBuf> buf,\n                              uint16_t) { bodyQueue.append(buf->clone()); }));\n\n  folly::IOBufQueue reqQueue;\n  reqQueue.append(folly::IOBuf::copyBuffer(\n      \"POST /test.php HTTP/1.1\\r\\nHost: www.test.com\\r\\n\"\n      \"Content-Length: 10\\r\\n\\r\\nabcde\"));\n  reqQueue.append(folly::IOBuf::copyBuffer(\"fghij\"));\n  codec.onIngress(*reqQueue.front());\n  EXPECT_TRUE(folly::IOBufEqualTo()(*bodyQueue.front(),\n                                    *folly::IOBuf::copyBuffer(\"abcdefghij\")));\n}\n\nTEST(HTTP1xCodecTest, TestIgnoreUpstreamUpgrade) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n  FakeHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"Upgrade: h2,h2c\\r\\n\"\n      \"\\r\\n\"\n      \"<!DOCTYPE html>\");\n  codec.onIngress(*reqBuf);\n\n  EXPECT_EQ(callbacks.streamErrors, 0);\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.bodyLength, 15);\n}\n\nTEST(HTTP1xCodecTest, WebsocketUpgrade) {\n  HTTP1xCodec upstreamCodec(TransportDirection::UPSTREAM);\n  HTTP1xCodec downstreamCodec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback downStreamCallbacks;\n  HTTP1xCodecCallback upstreamCallbacks;\n  downstreamCodec.setCallback(&downStreamCallbacks);\n  upstreamCodec.setCallback(&upstreamCallbacks);\n\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/websocket\");\n  req.setEgressWebsocketUpgrade();\n  folly::IOBufQueue buf;\n  auto streamID = upstreamCodec.createStream();\n  upstreamCodec.generateHeader(buf, streamID, req);\n\n  downstreamCodec.onIngress(*buf.front());\n  EXPECT_EQ(downStreamCallbacks.headersComplete, 1);\n  EXPECT_TRUE(downStreamCallbacks.msg_->isIngressWebsocketUpgrade());\n  auto& headers = downStreamCallbacks.msg_->getHeaders();\n  auto ws_key_header = headers.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY);\n  EXPECT_NE(ws_key_header, empty_string);\n\n  HTTPMessage resp;\n  resp.setHTTPVersion(1, 1);\n  resp.setStatusCode(101);\n  resp.setEgressWebsocketUpgrade();\n  buf.reset();\n  downstreamCodec.generateHeader(buf, streamID, resp);\n  upstreamCodec.onIngress(*buf.front());\n  EXPECT_EQ(upstreamCallbacks.headersComplete, 1);\n  headers = upstreamCallbacks.msg_->getHeaders();\n  auto ws_accept_header =\n      headers.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT);\n  EXPECT_NE(ws_accept_header, empty_string);\n}\n\nTEST(HTTP1xCodecTest, WebsocketUpgradeKeyError) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/websocket\");\n  req.setEgressWebsocketUpgrade();\n  folly::IOBufQueue buf;\n  auto streamID = codec.createStream();\n  codec.generateHeader(buf, streamID, req);\n\n  auto resp = folly::IOBuf::copyBuffer(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Connection: upgrade\\r\\n\"\n      \"Upgrade: websocket\\r\\n\"\n      \"\\r\\n\");\n  codec.onIngress(*resp);\n  EXPECT_EQ(callbacks.headersComplete, 0);\n  EXPECT_EQ(callbacks.errors, 1);\n}\n\nTEST(HTTP1xCodecTest, WebsocketUpgradeHeaderSet) {\n  HTTP1xCodec upstreamCodec(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/websocket\");\n  req.setEgressWebsocketUpgrade();\n  req.getHeaders().add(proxygen::HTTP_HEADER_UPGRADE, \"Websocket\");\n\n  folly::IOBufQueue buf;\n  upstreamCodec.generateHeader(buf, upstreamCodec.createStream(), req);\n\n  HTTP1xCodec downstreamCodec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  downstreamCodec.setCallback(&callbacks);\n  downstreamCodec.onIngress(*buf.front());\n  auto headers = callbacks.msg_->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY),\n            empty_string);\n}\n\nTEST(HTTP1xCodecTest, WebsocketConnectionHeader) {\n  HTTP1xCodec upstreamCodec(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/websocket\");\n  req.setEgressWebsocketUpgrade();\n  req.getHeaders().add(proxygen::HTTP_HEADER_CONNECTION, \"upgrade, keep-alive\");\n  req.getHeaders().add(proxygen::HTTP_HEADER_SEC_WEBSOCKET_KEY,\n                       \"key should change\");\n  req.getHeaders().add(proxygen::HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,\n                       \"this should not be found\");\n\n  folly::IOBufQueue buf;\n  upstreamCodec.generateHeader(buf, upstreamCodec.createStream(), req);\n  HTTP1xCodec downstreamCodec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  downstreamCodec.setCallback(&callbacks);\n  downstreamCodec.onIngress(*buf.front());\n  auto headers = callbacks.msg_->getHeaders();\n  auto ws_sec_key = headers.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY);\n  EXPECT_NE(ws_sec_key, empty_string);\n  EXPECT_NE(ws_sec_key, \"key should change\");\n\n  EXPECT_NO_THROW(folly::base64Decode(ws_sec_key));\n  EXPECT_EQ(16, folly::base64Decode(ws_sec_key).length());\n\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT),\n            empty_string);\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n            \"upgrade, keep-alive\");\n}\n\nTEST(HTTP1xCodecTest, WebsocketUpgradeSecondTxn) {\n  /**\n   * RFC9110:\n   *  For example, a CONNECT request or a request with the Upgrade header field\n   *  can occur at any time, not just in the first message on a connection.\n   */\n  HTTP1xCodec upstream{TransportDirection::UPSTREAM};\n  HTTP1xCodec downstream{TransportDirection::DOWNSTREAM};\n  HTTP1xCodecCallback upstreamCbs;\n  HTTP1xCodecCallback downstreamCbs;\n  folly::IOBufQueue buf;\n\n  downstream.setCallback(&downstreamCbs);\n  upstream.setCallback(&upstreamCbs);\n\n  // generate req/res for simple `GET /` request\n  {\n    HTTPMessage req;\n    req.setHTTPVersion(1, 1);\n    req.setMethod(HTTPMethod::GET);\n    req.setURL(\"/\");\n    auto streamId = upstream.createStream();\n\n    // parse downstream req\n    upstream.generateHeader(buf, streamId, req, /*eom=*/true);\n    downstream.onIngress(*buf.front());\n    buf.reset();\n\n    // send downstream resp\n    HTTPMessage resp;\n    resp.setHTTPVersion(1, 1);\n    resp.setStatusCode(200);\n    buf.reset();\n    downstream.generateHeader(buf, streamId, resp, /*eom=*/true);\n    upstream.onIngress(*buf.front());\n    buf.reset();\n\n    // both codecs should be reusable\n    EXPECT_TRUE(upstream.isReusable() && downstream.isReusable());\n  }\n\n  // reusing the same codec, generate req/res for ws request\n  {\n    HTTPMessage req;\n    req.setHTTPVersion(1, 1);\n    req.setURL(\"/websocket\");\n    req.setEgressWebsocketUpgrade();\n    auto streamID = upstream.createStream();\n    upstream.generateHeader(buf, streamID, req);\n\n    downstream.onIngress(*buf.front());\n    EXPECT_EQ(downstreamCbs.headersComplete, 2);\n    auto& msg = downstreamCbs.msg_;\n    EXPECT_TRUE(msg->isIngressWebsocketUpgrade());\n    auto ws_key_header =\n        msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY);\n    EXPECT_NE(ws_key_header, empty_string);\n\n    HTTPMessage resp;\n    resp.setHTTPVersion(1, 1);\n    resp.setStatusCode(101);\n    resp.setEgressWebsocketUpgrade();\n    buf.reset();\n    downstream.generateHeader(buf, streamID, resp);\n    upstream.onIngress(*buf.front());\n    auto ws_accept_header = upstreamCbs.msg_->getHeaders().getSingleOrEmpty(\n        HTTP_HEADER_SEC_WEBSOCKET_ACCEPT);\n    EXPECT_NE(ws_accept_header, empty_string);\n\n    // both codecs should no longer be reusable\n    EXPECT_TRUE(!upstream.isReusable() && !downstream.isReusable());\n  }\n}\n\nTEST(HTTP1xCodecTest, TrailersAndEomAreNotGeneratedWhenNonChunked) {\n  // Verify that generateTrailes and generateEom result in 0 bytes\n  // generated when message is not chunked.\n  // HTTP2 codec handles all BODY as regular non-chunked body, thus\n  // HTTPTransation SM transitions allow trailers after regular body. Which\n  // is not allowed in HTTP1.\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n\n  auto txnID = codec.createStream();\n\n  HTTPMessage msg;\n  msg.setHTTPVersion(1, 1);\n  msg.setURL(\"https://www.facebook.com/\");\n  msg.getHeaders().set(HTTP_HEADER_HOST, \"www.facebook.com\");\n  msg.setIsChunked(false);\n\n  folly::IOBufQueue buf;\n  codec.generateHeader(buf, txnID, msg);\n\n  HTTPHeaders trailers;\n  trailers.add(\"X-Test-Trailer\", \"test\");\n  EXPECT_EQ(0, codec.generateTrailers(buf, txnID, trailers));\n  EXPECT_EQ(0, codec.generateEOM(buf, txnID));\n}\n\nTEST(HTTP1xCodecTest, TestChunkResponseSerialization) {\n  // When codec is used for response serialization, it never gets\n  // to process request. Verify we can still serialize chunked response\n  // when mayChunkEgress=true.\n  folly::IOBufQueue blob;\n\n  HTTPMessage resp;\n  resp.setHTTPVersion(1, 1);\n  resp.setStatusCode(200);\n  resp.setIsChunked(true);\n  resp.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  resp.getHeaders().set(\"X-Custom-Header\", \"mac&cheese\");\n\n  string bodyStr(\"pizza\");\n  auto body = folly::IOBuf::copyBuffer(bodyStr);\n\n  HTTPHeaders trailers;\n  trailers.add(\"X-Test-Trailer\", \"chicken kyiv\");\n\n  // serialize\n  HTTP1xCodec downCodec = HTTP1xCodec::makeResponseCodec(\n      /*mayChunkEgress=*/true);\n  HTTP1xCodecCallback downCallbacks;\n  downCodec.setCallback(&downCallbacks);\n  auto downStream = downCodec.createStream();\n\n  downCodec.generateHeader(blob, downStream, resp);\n  downCodec.generateBody(\n      blob, downStream, body->clone(), HTTPCodec::NoPadding, false);\n  downCodec.generateTrailers(blob, downStream, trailers);\n\n  std::string tmp;\n  blob.appendToString(tmp);\n  VLOG(2) << \"serializeMessage blob: \" << tmp;\n\n  // deserialize\n  HTTP1xCodec upCodec(TransportDirection::UPSTREAM);\n  HTTP1xCodecCallback upCallbacks;\n  upCodec.setCallback(&upCallbacks);\n\n  auto tmpBuf = blob.front()->clone();\n  while (tmpBuf) {\n    auto next = tmpBuf->pop();\n    upCodec.onIngress(*tmpBuf);\n    tmpBuf = std::move(next);\n  }\n  upCodec.onIngressEOF();\n\n  EXPECT_EQ(upCallbacks.headersComplete, 1);\n  EXPECT_EQ(upCallbacks.trailersComplete, 1);\n  EXPECT_EQ(upCallbacks.messageComplete, 1);\n  EXPECT_EQ(upCallbacks.errors, 0);\n\n  EXPECT_EQ(resp.getStatusCode(), upCallbacks.msg_->getStatusCode());\n  EXPECT_TRUE(\n      upCallbacks.msg_->getHeaders().exists(HTTP_HEADER_TRANSFER_ENCODING));\n  EXPECT_TRUE(upCallbacks.msg_->getHeaders().exists(\"X-Custom-Header\"));\n  EXPECT_TRUE(upCallbacks.trailers_->exists(\"X-Test-Trailer\"));\n}\n\nTEST(HTTP1xCodecTest, TestGenerateEmptyBodyWithEOM) {\n  HTTP1xCodec upstreamCodec(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::POST);\n  req.setURL(\"/\");\n  req.setHTTPVersion(1, 1);\n  req.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  req.setIsChunked(true);\n  folly::IOBufQueue buf;\n  auto stream = upstreamCodec.createStream();\n  upstreamCodec.generateHeader(buf, stream, req);\n  EXPECT_GT(upstreamCodec.generateBody(buf, stream, nullptr, folly::none, true),\n            0);\n}\n\nTEST(HTTP1xCodecTest, TestHeaderValueWhiteSpaces) {\n  HTTP1xCodecCallback callbacks;\n  auto buf = folly::IOBuf::copyBuffer(\n      \"GET /status.php HTTP/1.1\\r\\nHost: www.facebook.com  \\r\\n\"\n      \"X-FB-HEADER: yay \\r\\n\\r\\n\");\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  codec.setCallback(&callbacks);\n\n  codec.onIngress(*buf);\n  const auto& headers = callbacks.msg_->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_HOST), \"www.facebook.com\");\n  EXPECT_EQ(headers.getSingleOrEmpty(\"X-FB-HEADER\"), \"yay\");\n}\n\nTEST(HTTP1xCodecTest, DownstreamGoaway) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  EXPECT_TRUE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateGoaway(writeBuf, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR);\n  EXPECT_TRUE(codec.isReusable());\n  EXPECT_TRUE(codec.isWaitingToDrain());\n  auto buf = folly::IOBuf::copyBuffer(\"GET / HTTP/1.1\\r\\n\\r\\n\");\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*buf);\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  codec.generateHeader(writeBuf, 1, resp, true);\n  EXPECT_FALSE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n}\n\nTEST(HTTP1xCodecTest, DownstreamImmediateGoaway) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateImmediateGoaway(writeBuf, ErrorCode::NO_ERROR, nullptr);\n  EXPECT_FALSE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n}\n\nTEST(HTTP1xCodecTest, DownstreamErrorGoaway) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateGoaway(\n      writeBuf, HTTPCodec::MaxStreamID, ErrorCode::PROTOCOL_ERROR);\n  EXPECT_FALSE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n}\n\nTEST(HTTP1xCodecTest, GenerateRst) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateRstStream(writeBuf, 1, ErrorCode::INTERNAL_ERROR);\n  EXPECT_FALSE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n}\n\nTEST(HTTP1xCodecTest, UpstreamGoaway) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  codec.generateGoaway(writeBuf, 0, ErrorCode::NO_ERROR);\n  EXPECT_FALSE(codec.isReusable());\n  EXPECT_FALSE(codec.isWaitingToDrain());\n}\n\nTEST(HTTP1xCodecTest, CloseOnEgressCompleteUpstreamConnect) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::CONNECT);\n  req.setURL(\"/\");\n  auto id = codec.createStream();\n  codec.generateHeader(writeBuf, id, req, false);\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n  codec.generateEOM(writeBuf, id);\n  EXPECT_TRUE(codec.closeOnEgressComplete());\n  auto ingress = folly::IOBuf::copyBuffer(std::string(\"HTTP/1.1 200 OK\\r\\r\\n\"));\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*ingress);\n  EXPECT_TRUE(codec.closeOnEgressComplete());\n}\n\nTEST(HTTP1xCodecTest, CloseOnEgressCompleteDownstreamResponse) {\n  // 1.0 response with no content-length/no chunking\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n  auto ingress =\n      folly::IOBuf::copyBuffer(std::string(\"GET / HTTP/1.0\\r\\n\\r\\n\"));\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*ingress);\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  auto body = makeBuf(100);\n  auto id = 1;\n  codec.generateHeader(writeBuf, id, resp, false);\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n  codec.generateBody(writeBuf, id, makeBuf(100), folly::none, false);\n  EXPECT_FALSE(codec.closeOnEgressComplete());\n  codec.generateEOM(writeBuf, id);\n  EXPECT_TRUE(codec.closeOnEgressComplete());\n}\n\nTEST(HTTP1xCodecTest, GenerateExtraHeaders) {\n  HTTP1xCodec upstream(TransportDirection::UPSTREAM);\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  upstream.setCallback(&callbacks);\n\n  HTTPMessage resp;\n  resp.setHTTPVersion(1, 1);\n  resp.setStatusCode(200);\n  resp.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"1000\");\n\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  auto id = downstream.createStream();\n  HTTPHeaders extraHeaders;\n  extraHeaders.add(HTTP_HEADER_PRIORITY, \"u=1\");\n  downstream.generateHeader(writeBuf,\n                            id,\n                            resp,\n                            false,\n                            nullptr /* HTTPHeaderSize */,\n                            std::move(extraHeaders));\n\n  upstream.onIngress(*writeBuf.front());\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(\n      \"u=1\",\n      callbacks.msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY));\n  EXPECT_EQ(\"1000\",\n            callbacks.msg_->getHeaders().getSingleOrEmpty(\n                HTTP_HEADER_CONTENT_LENGTH));\n}\n\nTEST(HTTP1xCodecTest, WebsocketUpgradeDuplicate) {\n  HTTP1xCodec downstreamCodec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback downStreamCallbacks;\n  downstreamCodec.setCallback(&downStreamCallbacks);\n\n  auto reqBuf = folly::IOBuf::copyBuffer(\n      \"GET / HTTP/1.1\\r\\nConnection:\\r\\nUpgrade:websocket\\r\\n\\r\\n\");\n  EXPECT_CALL(downStreamCallbacks, onError(_, _, _)).Times(1);\n\n  EXPECT_NO_THROW(downstreamCodec.onIngress(*reqBuf));\n  EXPECT_NO_THROW(downstreamCodec.onIngress(*reqBuf));\n}\n\nTEST(HTTP1xCodecTest, UpgradeAccepted) {\n  EXPECT_TRUE(serverAcceptedUpgrade(\"  Websocket,,,\", \",,,websocket  \"));\n  EXPECT_FALSE(serverAcceptedUpgrade(\",test1,test2,\", \",,Test3,Test4\"));\n  EXPECT_FALSE(serverAcceptedUpgrade(\"websocket\", \"\"));\n  EXPECT_FALSE(serverAcceptedUpgrade(\"\", \"websocket\"));\n}\n\nTEST(HTTP1xCodecTest, HugeURL) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto request = folly::to<std::string>(\"GET /echo?q=\",\n                                        std::string(84 * 1024, 'a'),\n                                        \" HTTP/1.1\\r\\n\"\n                                        \"Host: foo\\r\\n\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  folly::IOBufQueue input{folly::IOBufQueue::cacheChainLength()};\n  input.append(folly::IOBuf::wrapBuffer(request.data(), request.size()));\n  // TODO: seems we only check size when we're unfinished at the end of an IOBuf\n  while (input.chainLength() > 0) {\n    auto head = input.splitAtMost(4096);\n    codec.onIngress(*head);\n  }\n}\n\nTEST(HTTP1xCodecTest, UTF8Chars) {\n  // Non-ascii characters should be pct-encoded according to RFC3986. S220852\n  // was caused partly due to UTF-8 characters found in the URL.\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto utf8_string = std::string(\"Ñoño\");\n  auto badRequest =\n      folly::to<std::string>(\"GET /echo?dl=\", utf8_string, \" HTTP/1.1\\r\\n\\r\\n\");\n  std::string uriEscapedStr;\n  folly::uriEscape(utf8_string, uriEscapedStr);\n  auto goodRequest = folly::to<std::string>(\n      \"GET /echo?dl=\", uriEscapedStr, \" HTTP/1.1\\r\\n\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks, onMessageComplete(1, _));\n  codec.onIngress(\n      *folly::IOBuf::wrapBuffer(goodRequest.data(), goodRequest.size()));\n\n  EXPECT_CALL(callbacks, onMessageBegin(2, _));\n  EXPECT_CALL(callbacks, onError(2, _, _));\n\n  codec.onIngress(\n      *folly::IOBuf::wrapBuffer(badRequest.data(), badRequest.size()));\n}\n\nTEST(HTTP1xCodecTest, ExtraCRLF) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto requests = folly::to<std::string>(\n      \"\\r\"\n      \"GET /echo HTTP/1.1\\r\\n\\r\\n\",\n      \"\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\\r\\n\",\n      \"\\r\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\\r\\n\",\n      \"\\r\\n\\r\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\\r\\n\",\n      \"\\r\\n\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(_, _)).Times(4);\n  EXPECT_CALL(callbacks, onHeadersComplete(_, _)).Times(4);\n  EXPECT_CALL(callbacks, onMessageComplete(_, _)).Times(4);\n  codec.onIngress(*folly::IOBuf::wrapBuffer(requests.data(), requests.size()));\n}\n\nTEST(HTTP1xCodecTest, ContentLengthLast) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  std::string request = \"GET /echo HTTP/1.1\\r\\n\\r\\n\";\n  codec.onIngress(*folly::IOBuf::wrapBuffer(request.data(), request.size()));\n  HTTPCodec::StreamID id = 1;\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  resp.getHeaders().set(\"a\", \"b\");\n  resp.getHeaders().set(\"z\", \"x\");\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  codec.generateHeader(buf, id, resp, true);\n  auto length = buf.chainLength();\n  std::string clHeader = \"Content-Length: 100\";\n  auto expectedPos = length - clHeader.size() - 4 /* \\r\\n\\r\\n */;\n  EXPECT_EQ(buf.move()->moveToFbString().find(clHeader), expectedPos);\n}\n\nTEST(HTTP1xCodecTest, HugeChunkLength) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto request = folly::to<std::string>(\n      \"POST /echo HTTP/1.1\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\"\n      \"\\r\\n\"\n      \"ffffffffffffffff\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            // TODO: It would probably be nicer to return a 413 code.\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  codec.onIngress(*folly::IOBuf::wrapBuffer(request.data(), request.size()));\n}\n\nTEST(HTTP1xCodecTest, HugeContentLength) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto request = folly::to<std::string>(\n      \"POST /echo HTTP/1.1\\r\\n\"\n      \"Content-Length: ffffffffffffffff\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            // TODO: It would probably be nicer to return a 413 code.\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  codec.onIngress(*folly::IOBuf::wrapBuffer(request.data(), request.size()));\n}\n\nTEST(HTTP1xCodecTest, Dechunk) {\n  // Send a 1.0 request and a 1.1 chunked response.  The codec should dechunk\n  HTTP1xCodec upCodec(TransportDirection::UPSTREAM, false);\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  HTTPMessage req;\n  req.setHTTPVersion(1, 0);\n  req.setURL(\"/\");\n  req.setMethod(HTTPMethod::GET);\n  HTTPCodec::StreamID id = upCodec.createStream();\n  upCodec.generateHeader(buf, id, req, false);\n\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*buf.front());\n  buf.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setHTTPVersion(1, 1);\n  resp.setIsChunked(true);\n  resp.getHeaders().add(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  std::string body(10, 'a');\n  codec.generateHeader(buf, id, resp, false);\n  codec.generateChunkHeader(buf, id, 1);\n  codec.generateBody(buf,\n                     id,\n                     folly::IOBuf::wrapBuffer(body.data(), body.size()),\n                     folly::none,\n                     false);\n  codec.generateChunkTerminator(buf, id);\n  codec.generateEOM(buf, id);\n\n  StrictMock<MockHTTPCodecCallback> upCallbacks;\n  upCodec.setCallback(&upCallbacks);\n  // No chunk header callbacks\n  EXPECT_CALL(upCallbacks, onMessageBegin(1, _));\n  EXPECT_CALL(upCallbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> resp) {\n        EXPECT_FALSE(resp->getHeaders().exists(HTTP_HEADER_TRANSFER_ENCODING));\n        EXPECT_EQ(resp->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"close\");\n      }));\n  EXPECT_CALL(upCallbacks, onBody(1, _, _));\n  upCodec.onIngress(*buf.front());\n  EXPECT_CALL(upCallbacks, onMessageComplete(1, _));\n  upCodec.onIngressEOF();\n}\n\nTEST(HTTP1xCodecTest, Chunkify) {\n  // Send a 1.1 request and a 1.0 non-chunked response.  The codec should chunk\n  HTTP1xCodec upCodec(TransportDirection::UPSTREAM, false);\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/\");\n  req.setMethod(HTTPMethod::GET);\n  HTTPCodec::StreamID id = upCodec.createStream();\n  upCodec.generateHeader(buf, id, req, false);\n\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM, /*force11*/ true);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*buf.front());\n  buf.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setHTTPVersion(1, 0);\n  resp.setIsChunked(false);\n  std::string body(10, 'a');\n  codec.generateHeader(buf, id, resp, false);\n  codec.generateBody(buf,\n                     id,\n                     folly::IOBuf::wrapBuffer(body.data(), body.size()),\n                     folly::none,\n                     false);\n  codec.generateEOM(buf, id);\n\n  StrictMock<MockHTTPCodecCallback> upCallbacks;\n  upCodec.setCallback(&upCallbacks);\n  // No chunk header callbacks\n  EXPECT_CALL(upCallbacks, onMessageBegin(1, _));\n  EXPECT_CALL(upCallbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> resp) {\n        EXPECT_TRUE(resp->getHeaders().exists(HTTP_HEADER_TRANSFER_ENCODING));\n        EXPECT_EQ(resp->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"keep-alive\");\n      }));\n  EXPECT_CALL(upCallbacks, onChunkHeader(1, _));\n  EXPECT_CALL(upCallbacks, onBody(1, _, _));\n  EXPECT_CALL(upCallbacks, onChunkComplete(1));\n  EXPECT_CALL(upCallbacks, onMessageComplete(1, _));\n  upCodec.onIngress(*buf.front());\n}\n\nTEST(HTTP1xCodecTest, ChunkifyConnClose) {\n  // Send a 1.1 request with Connection: close and a 1.1 non-chunked response.\n  // The codec should chunk.\n  HTTP1xCodec upCodec(TransportDirection::UPSTREAM, false);\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/\");\n  req.setMethod(HTTPMethod::GET);\n  req.getHeaders().add(HTTP_HEADER_CONNECTION, \"close\");\n  HTTPCodec::StreamID id = upCodec.createStream();\n  upCodec.generateHeader(buf, id, req, false);\n\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM, false);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*buf.front());\n  buf.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setHTTPVersion(1, 1);\n  resp.setIsChunked(false);\n  std::string body(10, 'a');\n  codec.generateHeader(buf, id, resp, false);\n  codec.generateBody(buf,\n                     id,\n                     folly::IOBuf::wrapBuffer(body.data(), body.size()),\n                     folly::none,\n                     false);\n  codec.generateEOM(buf, id);\n\n  StrictMock<MockHTTPCodecCallback> upCallbacks;\n  upCodec.setCallback(&upCallbacks);\n  // No chunk header callbacks\n  EXPECT_CALL(upCallbacks, onMessageBegin(1, _));\n  EXPECT_CALL(upCallbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> resp) {\n        EXPECT_EQ(\n            resp->getHeaders().getSingleOrEmpty(HTTP_HEADER_TRANSFER_ENCODING),\n            \"chunked\");\n        EXPECT_EQ(resp->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"close\");\n      }));\n  EXPECT_CALL(upCallbacks, onChunkHeader(1, _));\n  EXPECT_CALL(upCallbacks, onBody(1, _, _));\n  EXPECT_CALL(upCallbacks, onChunkComplete(1));\n  EXPECT_CALL(upCallbacks, onMessageComplete(1, _));\n  upCodec.onIngress(*buf.front());\n}\n\nTEST(HTTP1xCodecTest, Chunkify100) {\n  // Send a 1.1 request and a 1.0 non-chunked response.  The codec should chunk\n  HTTP1xCodec upCodec(TransportDirection::UPSTREAM, false);\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/\");\n  req.setMethod(HTTPMethod::GET);\n  req.getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  HTTPCodec::StreamID id = upCodec.createStream();\n  upCodec.generateHeader(buf, id, req, false);\n\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM, /*force11*/ true);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  codec.onIngress(*buf.front());\n  buf.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(100);\n  resp.setIsChunked(false);\n  codec.generateHeader(buf, id, resp, false);\n\n  resp.setStatusCode(200);\n  resp.setHTTPVersion(1, 0);\n  resp.setIsChunked(false);\n  std::string body(10, 'a');\n  codec.generateHeader(buf, id, resp, false);\n  codec.generateBody(buf,\n                     id,\n                     folly::IOBuf::wrapBuffer(body.data(), body.size()),\n                     folly::none,\n                     false);\n  codec.generateEOM(buf, id);\n\n  StrictMock<MockHTTPCodecCallback> upCallbacks;\n  upCodec.setCallback(&upCallbacks);\n  // No chunk header callbacks\n  EXPECT_CALL(upCallbacks, onMessageBegin(1, _)).Times(2);\n  EXPECT_CALL(upCallbacks, onHeadersComplete(1, _))\n      .WillOnce(\n          Invoke([](HTTPCodec::StreamID, std::shared_ptr<HTTPMessage> resp) {\n            EXPECT_EQ(resp->getStatusCode(), 100);\n            EXPECT_FALSE(resp->getHeaders().exists(HTTP_HEADER_CONNECTION));\n          }))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> resp) {\n        EXPECT_TRUE(resp->getHeaders().exists(HTTP_HEADER_TRANSFER_ENCODING));\n        EXPECT_EQ(resp->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"keep-alive\");\n      }));\n  EXPECT_CALL(upCallbacks, onChunkHeader(1, _));\n  EXPECT_CALL(upCallbacks, onBody(1, _, _));\n  EXPECT_CALL(upCallbacks, onChunkComplete(1));\n  EXPECT_CALL(upCallbacks, onMessageComplete(1, _));\n  upCodec.onIngress(*buf.front());\n}\n\nTEST(HTTP1xCodecTest, TrailersNonChunked) {\n  HTTP1xCodec codec(TransportDirection::UPSTREAM);\n  StrictMock<MockHTTPCodecCallback> callbacks;\n  codec.setCallback(&callbacks);\n  HTTPMessage req;\n  auto id = codec.createStream();\n  req.setHTTPVersion(1, 1);\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/\");\n  codec.setCallback(&callbacks);\n  folly::IOBufQueue buf;\n  codec.generateHeader(buf, id, req, true);\n\n  auto response = folly::to<std::string>(\n      \"HTTP/1.1 200 Ok\\r\\n\"\n      \"Content-Length: 10\\r\\n\"\n      \"\\r\\n\"\n      \"aaaaaaaaaa0\\r\\n\"\n      \"Trailer-1: trailer\\r\\n\\r\\n\");\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks, onBody(1, _, _));\n  EXPECT_CALL(callbacks, onMessageComplete(1, _));\n  // Trailers interpreted as another message, and error\n  EXPECT_CALL(callbacks, onMessageBegin(2, _));\n  EXPECT_CALL(callbacks, onError(2, _, _));\n\n  codec.onIngress(*folly::IOBuf::wrapBuffer(response.data(), response.size()));\n}\n\nTEST(HTTP1xCodecTest, HeaderCtls) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/false);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer =\n      folly::IOBuf::copyBuffer(string(\"GET / HTTP/1.1\\r\\n\"\n                                      \"Foo: \\\"\\\\\\r\\\\\\n\\\"\\r\\n\"\n                                      \"\\r\\n\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID, std::shared_ptr<HTTPMessage> m) {\n        EXPECT_EQ(m->getHeaders().getSingleOrEmpty(\"Foo\"), \"\\\"\\\\\\r\\\\\\n\\\"\");\n      }));\n  EXPECT_CALL(callbacks, onMessageComplete(1, _));\n  codec.onIngress(*buffer);\n\n  codec.setStrictValidation(true);\n  EXPECT_CALL(callbacks, onMessageBegin(2, _));\n  EXPECT_CALL(callbacks, onError(2, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, HeaderCtlsMiddle) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer =\n      folly::IOBuf::copyBuffer(string(\"GET / HTTP/1.1\\r\\n\"\n                                      \"Foo: \\\"\\\\\\r\\\\\\n\\\"\\r\\n\"\n                                      \"Bar: b\\r\\n\"\n                                      \"\\r\\n\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, TrailerCtls) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM,\n                    /*force1_1=*/true,\n                    /*strictValidation=*/true);\n  MockHTTPCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer =\n      folly::IOBuf::copyBuffer(string(\"GET / HTTP/1.1\\r\\n\"\n                                      \"Transfer-Encoding: chunked\\r\\n\"\n                                      \"\\r\\n\"\n                                      \"0\\r\\n\"\n                                      \"Foo: \\\"\\\\\\r\\\\\\n\\\"\\r\\n\"\n                                      \"\\r\\n\"));\n  EXPECT_CALL(callbacks, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks, onError(1, _, _))\n      .WillOnce(Invoke(\n          [&](HTTPCodec::StreamID, std::shared_ptr<HTTPException> error, bool) {\n            EXPECT_EQ(error->getHttpStatusCode(), 400);\n          }));\n  codec.onIngress(*buffer);\n}\n\nTEST(HTTP1xCodecTest, AbsoluteURLNoPath) {\n  HTTP1xCodec codec(TransportDirection::DOWNSTREAM, true);\n  HTTP1xCodecCallback callbacks;\n  codec.setCallback(&callbacks);\n  auto buffer = folly::IOBuf::copyBuffer(\n      string(\"GET http://www.foo.com HTTP/1.0\\r\\n\\r\\n\"));\n  codec.onIngress(*buffer);\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  EXPECT_EQ(callbacks.msg_->getPathAsStringPiece(), string(\"/\"));\n}\n\nclass ConnectionHeaderTest\n    : public TestWithParam<std::pair<std::list<string>, string>> {\n public:\n  using ParamType = std::pair<std::list<string>, string>;\n};\n\nTEST_P(ConnectionHeaderTest, TestConnectionHeaders) {\n  HTTP1xCodec upstream(TransportDirection::UPSTREAM);\n  HTTP1xCodec downstream(TransportDirection::DOWNSTREAM);\n  HTTP1xCodecCallback callbacks;\n  downstream.setCallback(&callbacks);\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::GET);\n  req.setURL(\"/\");\n  auto val = GetParam();\n  for (auto header : val.first) {\n    req.getHeaders().add(HTTP_HEADER_CONNECTION, header);\n  }\n  folly::IOBufQueue writeBuf(folly::IOBufQueue::cacheChainLength());\n  upstream.generateHeader(writeBuf, upstream.createStream(), req);\n  downstream.onIngress(*writeBuf.front());\n  EXPECT_EQ(callbacks.headersComplete, 1);\n  auto& headers = callbacks.msg_->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_CONNECTION), val.second);\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTP1xCodec,\n    ConnectionHeaderTest,\n    ::testing::Values(\n        // Moves close to the end\n        ConnectionHeaderTest::ParamType({\"foo\", \"bar\", \"close\", \"baz\"},\n                                        \"foo, bar, baz, close\"),\n        // has to resize token vector\n        ConnectionHeaderTest::ParamType({\"foo\", \"bar, close\", \"baz\"},\n                                        \"foo, bar, baz, close\"),\n        // whitespace trimming\n        ConnectionHeaderTest::ParamType({\" foo\", \"bar, close \", \" baz \"},\n                                        \"foo, bar, baz, close\"),\n        // No close token => keep-alive\n        ConnectionHeaderTest::ParamType({\"foo\", \"bar, boo\", \"baz\"},\n                                        \"foo, bar, boo, baz, keep-alive\"),\n        // close and keep-alive => close\n        ConnectionHeaderTest::ParamType({\"foo\", \"keep-alive, boo\", \"close\"},\n                                        \"foo, boo, close\"),\n        // upgrade gets no special treatment\n        ConnectionHeaderTest::ParamType({\"foo\", \"upgrade, boo\", \"baz\"},\n                                        \"foo, upgrade, boo, baz, keep-alive\")));\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTP2CodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n\n#include <folly/io/Cursor.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/test/HTTP2FramerTest.h>\n#include <proxygen/lib/http/codec/test/HTTPParallelCodecTest.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\nusing namespace proxygen::compress;\nusing namespace folly;\nusing namespace folly::io;\nusing namespace std;\nusing namespace testing;\n\nTEST(HTTP2CodecConstantsTest, HTTPContantsAreCommonHeaders) {\n  // The purpose of this test is to verify some basic assumptions that should\n  // never change but to make clear that the following http2 header constants\n  // map to the respective common headers.  Should this test ever fail, the\n  // H2Codec would need to be updated in the corresponding places when creating\n  // compress/Header objects.\n  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kMethod),\n            HTTP_HEADER_COLON_METHOD);\n  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kScheme),\n            HTTP_HEADER_COLON_SCHEME);\n  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kPath), HTTP_HEADER_COLON_PATH);\n  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kAuthority),\n            HTTP_HEADER_COLON_AUTHORITY);\n  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kStatus),\n            HTTP_HEADER_COLON_STATUS);\n}\n\nclass HTTP2CodecTest : public HTTPParallelCodecTest {\n public:\n  HTTP2CodecTest() : HTTPParallelCodecTest(upstreamCodec_, downstreamCodec_) {\n    upstreamCodec_.enableDoubleGoawayDrain();\n    downstreamCodec_.enableDoubleGoawayDrain();\n  }\n\n  void SetUp() override {\n    HTTPParallelCodecTest::SetUp();\n    // make it transparent to the tests that we've received a settings frame\n    upstreamCodec_.generateSettings(output_);\n    parse();\n    callbacks_.reset();\n  }\n  void testHeaderListSize(bool oversized);\n  void testFrameSizeLimit(bool oversized);\n\n  void writeHeaders(folly::IOBufQueue& writeBuf,\n                    std::unique_ptr<folly::IOBuf> headers,\n                    uint32_t stream,\n                    folly::Optional<uint8_t> padding,\n                    bool endStream,\n                    bool endHeaders) {\n    auto headersLen = headers ? headers->computeChainDataLength() : 0;\n    auto headerSize =\n        http2::calculatePreHeaderBlockSize(false, false, padding.has_value());\n    auto header = writeBuf.preallocate(headerSize, 32);\n    writeBuf.postallocate(headerSize);\n    writeBuf.append(std::move(headers));\n    http2::writeHeaders((uint8_t*)header.first,\n                        header.second,\n                        writeBuf,\n                        headersLen,\n                        stream,\n                        padding,\n                        endStream,\n                        endHeaders);\n  }\n\n protected:\n  HTTP2Codec upstreamCodec_{TransportDirection::UPSTREAM};\n  HTTP2Codec downstreamCodec_{TransportDirection::DOWNSTREAM};\n};\n\nTEST_F(HTTP2CodecTest, IgnoreUnknownSettings) {\n  auto numSettings = downstreamCodec_.getIngressSettings()->getNumSettings();\n  std::deque<SettingPair> settings;\n  for (uint32_t i = 200; i < (200 + 1024); i++) {\n    settings.emplace_back(SettingsId(i), i);\n  }\n  http2::writeSettings(output_, settings);\n  parse();\n\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(numSettings,\n            downstreamCodec_.getIngressSettings()->getNumSettings());\n}\n\n// Some tests rely on the fact that we haven't flushed and parsed the preface\n// and settings frame.\nclass HTTP2CodecTestOmitParsePreface : public HTTP2CodecTest {\n  void SetUp() override {\n    HTTPParallelCodecTest::SetUp();\n  }\n};\n\nTEST_F(HTTP2CodecTest, NoExHeaders) {\n  SetUpUpstreamTest();\n\n  EXPECT_EQ(callbacks_.settings, 0);\n  EXPECT_EQ(callbacks_.numSettings, 0);\n  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());\n\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.settings, 1);\n  // only 3 standard settings: HEADER_TABLE_SIZE, ENABLE_PUSH, MAX_FRAME_SIZE.\n  EXPECT_EQ(callbacks_.numSettings, 3);\n  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());\n}\n\nTEST_F(HTTP2CodecTest, BasicHeader) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  req.getHeaders().add(\"tab-hdr\", \"coolio\\tv2\");\n  // Connection header will get dropped\n  req.getHeaders().add(HTTP_HEADER_CONNECTION, \"Love\");\n  req.setSecure(true);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  callbacks_.expectMessage(true, 3, \"/guacamole\");\n  EXPECT_TRUE(callbacks_.msg->isSecure());\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"coolio\", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));\n  EXPECT_EQ(\"coolio\\tv2\", headers.getSingleOrEmpty(\"tab-hdr\"));\n  EXPECT_EQ(\"www.foo.com\", headers.getSingleOrEmpty(HTTP_HEADER_HOST));\n}\n\nTEST_F(HTTP2CodecTest, GenerateExtraHeaders) {\n  HTTPMessage req = getGetRequest(\"/fish_taco\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"157\");\n  HTTPHeaders extraHeaders;\n  extraHeaders.add(HTTP_HEADER_PRIORITY, \"u=0\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_,\n                                id,\n                                req,\n                                true,\n                                nullptr /* headerSize */,\n                                std::move(extraHeaders));\n\n  parse();\n  // There is also a HOST header\n  callbacks_.expectMessage(true, 3, \"/fish_taco\");\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"157\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH));\n  EXPECT_EQ(\"u=0\", headers.getSingleOrEmpty(HTTP_HEADER_PRIORITY));\n}\n\nTEST_F(HTTP2CodecTestOmitParsePreface, OmitSettingsAfterConnPrefaceError) {\n  HTTPMessage req = getGetRequest(\"/test\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"rand-user\");\n  req.setSecure(true);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true, /*headerSize=*/nullptr);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 0);\n  EXPECT_EQ(callbacks_.numSettings, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),\n            ErrorCode::PROTOCOL_ERROR);\n}\n\nTEST_F(HTTP2CodecTest, BadHeaders) {\n  static const std::string v1(\"GET\");\n  static const std::string v2(\"/\");\n  static const std::string v3(\"http\");\n  static const std::string v4(\"foo.com\");\n  static const vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kPath, v2),\n      Header::makeHeaderForTest(headers::kScheme, v3),\n      Header::makeHeaderForTest(headers::kAuthority, v4),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  HTTPCodec::StreamID stream = 1;\n  // missing fields (missing authority is OK)\n  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {\n    std::vector<proxygen::compress::Header> allHeaders = reqHeaders;\n    allHeaders.erase(allHeaders.begin() + i);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n  // dup fields\n  std::string v(\"foomonkey\");\n  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {\n    std::vector<proxygen::compress::Header> allHeaders = reqHeaders;\n    auto h = allHeaders[i];\n    h.value = &v;\n    allHeaders.push_back(h);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1 + 7);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 7);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadPseudoHeaders) {\n  static const std::string v1(\"POST\");\n  static const std::string v2(\"http\");\n  static const std::string n3(\"foo\");\n  static const std::string v3(\"bar\");\n  static const std::string v4(\"/\");\n  static const vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kScheme, v2),\n      Header::makeHeaderForTest(n3, v3),\n      Header::makeHeaderForTest(headers::kPath, v4),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  HTTPCodec::StreamID stream = 1;\n  std::vector<proxygen::compress::Header> allHeaders = reqHeaders;\n  auto encodedHeaders = headerCodec.encode(allHeaders);\n  writeHeaders(output_,\n               std::move(encodedHeaders),\n               stream,\n               http2::kNoPadding,\n               true,\n               true);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(), kErrorParseHeader);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadHeaderValues) {\n  static const std::string v1(\"--1\");\n  static const std::string v2(\"\\13\\10protocol-attack\");\n  static const std::string v3(\"\\13\");\n  static const std::string v4(\"abc.com\\\\13\\\\10\");\n  static const vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kPath, v2),\n      Header::makeHeaderForTest(headers::kScheme, v3),\n      Header::makeHeaderForTest(headers::kAuthority, v4),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  HTTPCodec::StreamID stream = 1;\n  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {\n    std::vector<proxygen::compress::Header> allHeaders;\n    allHeaders.push_back(reqHeaders[i]);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 4);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 4);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(),\n            kErrorHeaderContentValidation);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, HostAuthority) {\n  static const std::string v1(\"GET\");\n  static const std::string v2(\"/\");\n  static const std::string v3(\"http\");\n  static const std::string v4(\"foo.com\");\n\n  static const vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kPath, v2),\n      Header::makeHeaderForTest(headers::kScheme, v3),\n      Header::makeHeaderForTest(headers::kAuthority, v4),\n  };\n\n  HTTPCodec::StreamID stream = 1;\n  for (auto i = 0; i < 2; i++, stream += 2) {\n    auto allHeaders = reqHeaders;\n    std::string v5(i == 0 ? v4 : \"nope\");\n    allHeaders.emplace_back(HTTP_HEADER_HOST, v5);\n    HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n    parse();\n    if (i == 0) {\n      // same value, ok\n      callbacks_.expectMessage(true, stream, \"/\");\n      const auto& headers = callbacks_.msg->getHeaders();\n      EXPECT_EQ(v4, headers.getSingleOrEmpty(HTTP_HEADER_HOST));\n    } else {\n      // different values, error\n      EXPECT_EQ(callbacks_.streamErrors, 1);\n      EXPECT_EQ(callbacks_.lastParseError->getHttpStatusCode(), 400);\n    }\n  }\n}\n\nTEST_F(HTTP2CodecTest, HighAscii) {\n  auto g =\n      folly::makeGuard([this] { downstreamCodec_.setStrictValidation(false); });\n  downstreamCodec_.setStrictValidation(true);\n  HTTPMessage req1 = getGetRequest(\"/guacamole\\xff\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id, req1, true, nullptr /* headerSize */);\n  HTTPMessage req2 = getGetRequest(\"/guacamole\");\n  req2.getHeaders().set(HTTP_HEADER_HOST, std::string(\"foo.com\\xff\"));\n  auto id2 = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id2, req2, true, nullptr /* headerSize */);\n  HTTPMessage req3 = getGetRequest(\"/guacamole\");\n  req3.getHeaders().set(folly::StringPiece(\"Foo\\xff\"), \"bar\");\n  auto id3 = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id3, req3, true, nullptr /* headerSize */);\n  HTTPMessage req4 = getGetRequest(\"/guacamole\");\n  req4.getHeaders().set(\"Foo\", std::string(\"bar\\xff\"));\n  auto id4 = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id4, req4, true, nullptr /* headerSize */);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 4);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 4);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(),\n            kErrorHeaderContentValidation);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  HTTPMessage req5 = getGetRequest(\"/guacamole\");\n  req5.getHeaders().set(HTTP_HEADER_USER_AGENT, \"ꪶ𝛸ꫂ_𝐹𝛩𝑅𝐶𝛯_𝑉2\");\n  auto id5 = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id5, req5, true, nullptr /* headerSize */);\n  callbacks_.reset();\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(),\n            kErrorHeaderContentValidation);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, EmptyPath) {\n  auto g =\n      folly::makeGuard([this] { downstreamCodec_.setStrictValidation(false); });\n  downstreamCodec_.setStrictValidation(true);\n  HTTPMessage req1 = getGetRequest(\"\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(\n      output_, id, req1, true, nullptr /* headerSize */);\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(), kErrorParseHeader);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\n/**\n * Ingress bytes with an empty header name\n */\nconst uint8_t kBufEmptyHeader[] = {\n    0x00, 0x00, 0x1d, 0x01, 0x04, 0x00, 0x00, 0x00, 0x01, 0x82,\n    0x87, 0x44, 0x87, 0x62, 0x6b, 0x46, 0x41, 0xd2, 0x7a, 0x0b,\n    0x41, 0x89, 0xf1, 0xe3, 0xc2, 0xf2, 0x9c, 0xeb, 0x90, 0xf4,\n    0xff, 0x40, 0x80, 0x84, 0x2d, 0x35, 0xa7, 0xd7};\n\nTEST_F(HTTP2CodecTest, EmptyHeaderName) {\n  output_.append(IOBuf::copyBuffer(kBufEmptyHeader, sizeof(kBufEmptyHeader)));\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getProxygenError(), kErrorParseHeader);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicConnect) {\n  std::string authority = \"myhost:1234\";\n  HTTPMessage request;\n  request.setMethod(HTTPMethod::CONNECT);\n  request.getHeaders().add(proxygen::HTTP_HEADER_HOST, authority);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, request, false /* eom */);\n\n  parse();\n  callbacks_.expectMessage(false, 1, \"\");\n  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(authority, headers.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));\n}\n\nTEST_F(HTTP2CodecTest, BadConnect) {\n  std::string v1 = \"CONNECT\";\n  std::string v2 = \"somehost:576\";\n  std::vector<proxygen::compress::Header> goodHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kAuthority, v2),\n  };\n\n  // See https://tools.ietf.org/html/rfc7540#section-8.3\n  std::string v3 = \"/foobar\";\n  std::vector<proxygen::compress::Header> badHeaders = {\n      Header::makeHeaderForTest(headers::kScheme, headers::kHttp),\n      Header::makeHeaderForTest(headers::kPath, v3),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  HTTPCodec::StreamID stream = 1;\n\n  for (size_t i = 0; i < badHeaders.size(); i++, stream += 2) {\n    auto allHeaders = goodHeaders;\n    allHeaders.push_back(badHeaders[i]);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, badHeaders.size());\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, badHeaders.size());\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, TemplateDrivenConnect) {\n  // See https://fburl.com/nql0na8x for definition\n  std::string method = \"CONNECT\";\n  std::string authority = \"request-proxy.example\";\n  std::string path = \"/proxy?target_host=192.0.2.1,2001:db8::1&tcp_port=443\";\n  std::string protocol = \"connect-tcp\";\n\n  std::vector<proxygen::compress::Header> headers = {\n      Header::makeHeaderForTest(headers::kMethod, method),\n      Header::makeHeaderForTest(headers::kAuthority, authority),\n      Header::makeHeaderForTest(headers::kScheme, headers::kHttps),\n      Header::makeHeaderForTest(headers::kPath, path),\n      Header::makeHeaderForTest(headers::kProtocol, protocol),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  HTTPCodec::StreamID stream = 1;\n\n  auto encodedHeaders = headerCodec.encode(headers);\n  writeHeaders(output_,\n               std::move(encodedHeaders),\n               stream,\n               http2::kNoPadding /* padding */,\n               true,\n               true);\n\n  parse();\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());\n  EXPECT_EQ(path, callbacks_.msg->getURL());\n  const auto& parsedHeaders = callbacks_.msg->getHeaders();\n  EXPECT_EQ(authority,\n            parsedHeaders.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));\n  EXPECT_EQ(protocol, *callbacks_.msg->getUpgradeProtocol());\n}\n\nvoid HTTP2CodecTest::testHeaderListSize(bool oversized) {\n  if (oversized) {\n    auto settings = downstreamCodec_.getEgressSettings();\n    settings->setSetting(SettingsId::MAX_HEADER_LIST_SIZE, 37);\n  }\n\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  req.getHeaders().add(\"x-long-long-header\",\n                       \"supercalafragalisticexpialadoshus\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  // session error\n  EXPECT_EQ(callbacks_.messageBegin, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.headersComplete, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.messageComplete, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, oversized ? 1 : 0);\n}\n\nvoid HTTP2CodecTest::testFrameSizeLimit(bool oversized) {\n  HTTPMessage req = getBigGetRequest(\"/guacamole\");\n  auto settings = downstreamCodec_.getEgressSettings();\n\n  if (oversized) {\n    // trick upstream for sending a 2x bigger HEADERS frame\n    settings->setSetting(SettingsId::MAX_FRAME_SIZE,\n                         http2::kMaxFramePayloadLengthMin * 2);\n    downstreamCodec_.generateSettings(output_);\n    parseUpstream();\n  }\n\n  settings->setSetting(SettingsId::MAX_FRAME_SIZE,\n                       http2::kMaxFramePayloadLengthMin);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  // session error\n  EXPECT_EQ(callbacks_.messageBegin, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.headersComplete, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.messageComplete, oversized ? 0 : 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, oversized ? 1 : 0);\n}\n\nTEST_F(HTTP2CodecTest, NormalSizeHeader) {\n  testHeaderListSize(false);\n}\n\nTEST_F(HTTP2CodecTest, OversizedHeader) {\n  testHeaderListSize(true);\n}\n\nTEST_F(HTTP2CodecTest, NormalSizeFrame) {\n  testFrameSizeLimit(false);\n}\n\nTEST_F(HTTP2CodecTest, OversizedFrame) {\n  testFrameSizeLimit(true);\n}\n\nTEST_F(HTTP2CodecTest, BigHeaderCompressed) {\n  SetUpUpstreamTest();\n  auto settings = downstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::MAX_HEADER_LIST_SIZE, 37);\n  downstreamCodec_.generateSettings(output_);\n  parseUpstream();\n\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  // session error\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, BasicHeaderReply) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n  downstreamCodec_.generateEOM(output_, 1);\n\n  parseUpstream();\n  callbacks_.expectMessage(true, 2, 200);\n  const auto& headers = callbacks_.msg->getHeaders();\n  // HTTP/2 doesnt support serialization - instead you get the default\n  EXPECT_EQ(\"OK\", callbacks_.msg->getStatusMessage());\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n}\n\nTEST_F(HTTP2CodecTest, DontDoubleDate) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_DATE, \"Today!\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n  downstreamCodec_.generateEOM(output_, 1);\n\n  parseUpstream();\n  callbacks_.expectMessage(true, 2, 200);\n  const auto& headers = callbacks_.msg->getHeaders();\n  // HTTP/2 doesnt support serialization - instead you get the default\n  EXPECT_EQ(\"OK\", callbacks_.msg->getStatusMessage());\n  EXPECT_EQ(1,\n            callbacks_.msg->getHeaders().getNumberOfValues(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n}\n\nTEST_F(HTTP2CodecTest, BadHeadersReply) {\n  static const std::string v1(\"200\");\n  static const vector<proxygen::compress::Header> respHeaders = {\n      Header::makeHeaderForTest(headers::kStatus, v1),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::DOWNSTREAM);\n  HTTPCodec::StreamID stream = 1;\n  // missing fields (missing authority is OK)\n  for (size_t i = 0; i < respHeaders.size(); i++, stream += 2) {\n    std::vector<proxygen::compress::Header> allHeaders = respHeaders;\n    allHeaders.erase(allHeaders.begin() + i);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n  // dup fields\n  std::string v(\"foomonkey\");\n  for (size_t i = 0; i < respHeaders.size(); i++, stream += 2) {\n    std::vector<proxygen::compress::Header> allHeaders = respHeaders;\n    auto h = allHeaders[i];\n    h.value = &v;\n    allHeaders.push_back(h);\n    auto encodedHeaders = headerCodec.encode(allHeaders);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 true,\n                 true);\n  }\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 2);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 2);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, StripLwsHeaderValue) {\n  static std::string lwsString(\"\\r\\n\\t \");\n  // first request\n  HTTPMessage req = getGetRequest(\"/test\");\n  req.getHeaders().add(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE,\n                       \" application/x-fb\");\n  // test header value just whitespace\n  req.getHeaders().add(HTTPHeaderCode::HTTP_HEADER_USER_AGENT, lwsString);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, /*eom=*/true);\n\n  auto parseAndCheckExpectations = [&]() {\n    parse();\n    EXPECT_EQ(callbacks_.messageBegin, 1);\n    EXPECT_EQ(callbacks_.headersComplete, 1);\n    EXPECT_EQ(callbacks_.messageComplete, 1);\n    EXPECT_EQ(callbacks_.streamErrors, 0);\n    EXPECT_EQ(callbacks_.sessionErrors, 0);\n    ASSERT_TRUE(callbacks_.msg);\n  };\n\n  parseAndCheckExpectations();\n  auto parsedHeaders = callbacks_.msg->extractHeaders();\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_USER_AGENT));\n  EXPECT_TRUE(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_USER_AGENT)\n          .empty());\n  EXPECT_EQ(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE),\n      \"application/x-fb\");\n  callbacks_.reset();\n\n  // second request\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  static std::string kContentTypeValue(\"\\tapplication/x-fb\");\n  static const std::string v1(\"GET\");\n  static const std::string v2(\"/\");\n  static const std::string v3(\"http\");\n  static const std::string v4(\"foo.com\");\n  std::vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, v1),\n      Header::makeHeaderForTest(headers::kPath, v2),\n      Header::makeHeaderForTest(headers::kScheme, v3),\n      Header::makeHeaderForTest(headers::kAuthority, v4),\n      Header(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE, kContentTypeValue),\n      Header(HTTPHeaderCode::HTTP_HEADER_USER_AGENT, lwsString)};\n\n  auto encodedHeaders = headerCodec.encode(reqHeaders);\n  id = upstreamCodec_.createStream();\n  writeHeaders(\n      output_, std::move(encodedHeaders), id, http2::kNoPadding, true, true);\n\n  parseAndCheckExpectations();\n  parsedHeaders = callbacks_.msg->extractHeaders();\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_TRUE(parsedHeaders.exists(HTTPHeaderCode::HTTP_HEADER_USER_AGENT));\n  EXPECT_TRUE(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_USER_AGENT)\n          .empty());\n  EXPECT_EQ(\n      parsedHeaders.getSingleOrEmpty(HTTPHeaderCode::HTTP_HEADER_CONTENT_TYPE),\n      \"application/x-fb\");\n}\n\nTEST_F(HTTP2CodecTest, Cookies) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(\"Cookie\", \"chocolate-chip=1\");\n  req.getHeaders().add(\"Cookie\", \"rainbow-chip=2\");\n  req.getHeaders().add(\"Cookie\", \"butterscotch=3\");\n  req.getHeaders().add(\"Cookie\", \"oatmeal-raisin=4\");\n  req.setSecure(true);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, 2, \"/guacamole\");\n  EXPECT_EQ(callbacks_.msg->getCookie(\"chocolate-chip\"), \"1\");\n  EXPECT_EQ(callbacks_.msg->getCookie(\"rainbow-chip\"), \"2\");\n  EXPECT_EQ(callbacks_.msg->getCookie(\"butterscotch\"), \"3\");\n  EXPECT_EQ(callbacks_.msg->getCookie(\"oatmeal-raisin\"), \"4\");\n}\n\nTEST_F(HTTP2CodecTest, BasicContinuation) {\n  HTTPMessage req = getBigGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, -1, \"/\");\n#ifndef NDEBUG\n  EXPECT_GT(downstreamCodec_.getReceivedFrameCount(), 1);\n#endif\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"coolio\", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, ContinuationChain) {\n  auto settings = (HTTPSettings*)upstreamCodec_.getIngressSettings();\n  settings->setSetting(SettingsId::MAX_FRAME_SIZE, 500);\n\n  HTTPMessage req = getBigGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, -1, \"/\");\n#ifndef NDEBUG\n  EXPECT_GT(downstreamCodec_.getReceivedFrameCount(), 1);\n#endif\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"coolio\", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicContinuationEndStream) {\n  // CONTINUATION with END_STREAM flag set on the preceding HEADERS frame\n  HTTPMessage req = getBigGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  callbacks_.expectMessage(true, -1, \"/\");\n#ifndef NDEBUG\n  EXPECT_GT(downstreamCodec_.getReceivedFrameCount(), 1);\n#endif\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(\"coolio\", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadContinuation) {\n  // CONTINUATION with no preceding HEADERS\n  auto fakeHeaders = makeBuf(5);\n  http2::writeContinuation(output_, 3, true, std::move(fakeHeaders));\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, MissingContinuation) {\n  IOBufQueue output(IOBufQueue::cacheChainLength());\n  HTTPMessage req = getBigGetRequest();\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n  // empirically determined the size of continuation frame, and strip it\n  output_.trimEnd(http2::kFrameHeaderSize + 4134);\n\n  // insert a non-continuation (but otherwise valid) frame\n  http2::writeGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n#ifndef NDEBUG\n  // frames = 3 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, MissingContinuationBadFrame) {\n  IOBufQueue output(IOBufQueue::cacheChainLength());\n  HTTPMessage req = getBigGetRequest();\n  upstreamCodec_.generateHeader(output_, 1, req);\n\n  // empirically determined the size of continuation frame, and fake it\n  output_.trimEnd(http2::kFrameHeaderSize + 4134);\n\n  // insert an invalid frame\n  auto frame = makeBuf(http2::kFrameHeaderSize + 4134);\n  *((uint32_t*)frame->writableData()) = 0xfa000000;\n  output_.append(std::move(frame));\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n#ifndef NDEBUG\n  // frames = 3 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, BadContinuationStream) {\n  HTTPMessage req = getBigGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  // empirically determined the size of continuation frame, and fake it\n  output_.trimEnd(http2::kFrameHeaderSize + 4134);\n  auto fakeHeaders = makeBuf(4134);\n  http2::writeContinuation(output_, 3, true, std::move(fakeHeaders));\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n#ifndef NDEBUG\n  // frames = 3 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, ContinuationLimit) {\n  auto fakeHeaders = makeBuf(10);\n  writeHeaders(output_,\n               std::move(fakeHeaders),\n               1,\n               folly::none,\n               false /* endStream */,\n               false /* endHeaders */);\n\n  for (uint32_t i = 0; i < 150; i++) {\n    http2::writeContinuation(output_, 1, false, makeBuf(0));\n  }\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, ContinuationUnderLimit) {\n  // getBigGetRequest generates ~2 CONTINUATION frames at default\n  // MAX_FRAME_SIZE (16384), well under kMaxContinuationFramesPerHeaderBlock.\n  HTTPMessage req = getBigGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, -1, \"/\");\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, FrameTooLarge) {\n  writeFrameHeaderManual(output_, 1 << 15, 0, 0, 1);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_TRUE(callbacks_.lastParseError->hasCodecStatusCode());\n  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),\n            ErrorCode::FRAME_SIZE_ERROR);\n}\n\nTEST_F(HTTP2CodecTest, DataFrameZeroLengthWithEOM) {\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n  // Write Zero length Data frame (just a Frame header) with end of stream set\n  writeFrameHeaderManual(output_,\n                         0,\n                         (uint8_t)http2::FrameType::DATA,\n                         (uint8_t)http2::Flags::END_STREAM,\n                         1);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, UnknownFrameType) {\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n\n  // unknown frame type 17\n  writeFrameHeaderManual(output_, 17, 37, 0, 1);\n  output_.append(\"wicked awesome!!!\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, 2, \"\"); // + host\n}\n\nTEST_F(HTTP2CodecTest, JunkAfterConnError) {\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n\n  // write headers frame for stream 0\n  writeFrameHeaderManual(output_, 0, (uint8_t)http2::FrameType::HEADERS, 0, 0);\n  // now write a valid headers frame, should never be parsed\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, BasicData) {\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  upstreamCodec_.generateBody(\n      output_, 2, std::move(buf), HTTPCodec::NoPadding, true);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), data);\n}\n\nTEST_F(HTTP2CodecTest, LongData) {\n  // Hack the max frame size artificially low\n  auto* settings = (HTTPSettings*)upstreamCodec_.getIngressSettings();\n  settings->setSetting(SettingsId::MAX_FRAME_SIZE, 16);\n  auto buf = makeBuf(100);\n  upstreamCodec_.generateBody(\n      output_, 1, buf->clone(), HTTPCodec::NoPadding, true);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 7);\n  EXPECT_EQ(callbacks_.bodyLength, 100);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), buf->moveToFbString());\n}\n\nTEST_F(HTTP2CodecTest, PushPromiseContinuation) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  upstreamCodec_.generateSettings(output_);\n  upstreamCodec_.createStream();\n\n  SetUpUpstreamTest();\n  // Hack the max frame size artificially low\n  settings = (HTTPSettings*)downstreamCodec_.getIngressSettings();\n  settings->setSetting(SettingsId::MAX_FRAME_SIZE, 5);\n  HTTPHeaderSize size;\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(\"foomonkey\", \"george\");\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1, false, &size);\n\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.pushId, 2);\n  EXPECT_EQ(callbacks_.assocStreamId, 1);\n  EXPECT_EQ(callbacks_.headersCompleteId, 2);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, MalformedPaddingLength) {\n  const uint8_t badInput[] = {0x50,\n                              0x52,\n                              0x49,\n                              0x20,\n                              0x2a,\n                              0x20,\n                              0x48,\n                              0x54,\n                              0x54,\n                              0x50,\n                              0x2f,\n                              0x32,\n                              0x2e,\n                              0x30,\n                              0x0d,\n                              0x0a,\n                              0x0d,\n                              0x0a,\n                              0x53,\n                              0x4d,\n                              0x0d,\n                              0x0a,\n                              0x0d,\n                              0x0a,\n                              0x00,\n                              0x00,\n                              0x7e,\n                              0x00,\n                              0x6f,\n                              0x6f,\n                              0x6f,\n                              0x6f,\n                              // The padding length byte below is 0x82 (130\n                              // in decimal) which is greater than the length\n                              // specified by the header's length field, 126\n                              0x01,\n                              0x82,\n                              0x87,\n                              0x44,\n                              0x87,\n                              0x92,\n                              0x97,\n                              0x92,\n                              0x92,\n                              0x92,\n                              0x7a,\n                              0x0b,\n                              0x41,\n                              0x89,\n                              0xf1,\n                              0xe3,\n                              0xc0,\n                              0xf2,\n                              0x9c,\n                              0xdd,\n                              0x90,\n                              0xf4,\n                              0xff,\n                              0x40,\n                              0x80,\n                              0x84,\n                              0x2d,\n                              0x35,\n                              0xa7,\n                              0xd7};\n  output_.reset();\n  output_.append(badInput, sizeof(badInput));\n  EXPECT_EQ(output_.chainLength(), sizeof(badInput));\n\n  EXPECT_FALSE(parse());\n}\n\nTEST_F(HTTP2CodecTest, MalformedPadding) {\n  const uint8_t badInput[] = {0x00, 0x00, 0x0d, 0x01, 0xbe, 0x63, 0x0d, 0x0a,\n                              0x0d, 0x0a, 0x00, 0x73, 0x00, 0x00, 0x06, 0x08,\n                              0x72, 0x00, 0x24, 0x00, 0xfa, 0x4d, 0x0d};\n  output_.append(badInput, sizeof(badInput));\n\n  EXPECT_FALSE(parse());\n}\n\nTEST_F(HTTP2CodecTestOmitParsePreface, NoAppByte) {\n  const uint8_t noAppByte[] = {\n      0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f,\n      0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a,\n      0x0d, 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,\n      0x00, 0x00, 0x56, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x01, 0x55, 0x00};\n  output_.reset();\n  output_.append(noAppByte, sizeof(noAppByte));\n  EXPECT_EQ(output_.chainLength(), sizeof(noAppByte));\n\n  EXPECT_TRUE(parse());\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.numSettings, 0);\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, DataFramePartialDataOnFrameHeaderCall) {\n  using namespace testing;\n  NiceMock<MockHTTPCodecCallback> mockCallback;\n  EXPECT_CALL(mockCallback, onFrameHeader(_, _, _, _, _));\n\n  const size_t bufSize = 10;\n  auto buf = makeBuf(bufSize);\n  const size_t padding = 10;\n  upstreamCodec_.generateBody(output_, 1, buf->clone(), padding, true);\n  // 9 (frame header) + 1 (padding length) + 10 (body) + 10 (padding)\n  EXPECT_EQ(output_.chainLength(), 30);\n\n  downstreamCodec_.setCallback(&mockCallback);\n\n  auto ingress = output_.move();\n  ingress->coalesce();\n  // Copy partial byte to a new buffer\n  auto ingress1 = IOBuf::copyBuffer(ingress->data(), 34);\n  downstreamCodec_.onIngress(*ingress1);\n}\n\nTEST_F(HTTP2CodecTest, DataFramePartialDataWithNoAppByte) {\n  const size_t bufSize = 10;\n  auto buf = makeBuf(bufSize);\n  const size_t padding = 10;\n  upstreamCodec_.generateBody(output_, 1, buf->clone(), padding, true);\n  // 9 (frame header) + 1 (padding length) + 10 (body) + 10 (padding)\n  EXPECT_EQ(output_.chainLength(), 30);\n\n  auto ingress = output_.move();\n  ingress->coalesce();\n  // Copy up to the padding length byte to a new buffer\n  auto ingress1 = IOBuf::copyBuffer(ingress->data(), 10);\n  size_t parsed = downstreamCodec_.onIngress(*ingress1);\n  // The 10th byte is the padding length byte which should not be parsed\n  EXPECT_EQ(parsed, 9);\n  // Copy from the padding length byte to the end\n  auto ingress2 = IOBuf::copyBuffer(ingress->data() + 9, 21);\n  parsed = downstreamCodec_.onIngress(*ingress2);\n  // The padding length byte should be parsed this time along with 10 bytes of\n  // application data and 10 bytes of padding\n  EXPECT_EQ(parsed, 21);\n\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, bufSize);\n  // Total padding is the padding length byte and the padding bytes\n  EXPECT_EQ(callbacks_.paddingBytes, padding + 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), buf->moveToFbString());\n}\n\nTEST_F(HTTP2CodecTest, BasicRst) {\n  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::ENHANCE_YOUR_CALM);\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.aborts, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicRstInvalidCode) {\n  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::STREAM_CLOSED);\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.aborts, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicPing) {\n  upstreamCodec_.generatePingRequest(output_);\n  upstreamCodec_.generatePingReply(output_, 17);\n\n  uint64_t pingReq;\n  parse([&](IOBuf* ingress) {\n    folly::io::Cursor c(ingress);\n    c.skip(http2::kFrameHeaderSize);\n    pingReq = c.read<uint64_t>();\n  });\n\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.recvPingRequest, pingReq);\n  EXPECT_EQ(callbacks_.recvPingReply, 17);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicWindow) {\n  // This test would fail if the codec had window state\n  upstreamCodec_.generateWindowUpdate(output_, 0, 10);\n  upstreamCodec_.generateWindowUpdate(output_, 0, http2::kMaxWindowUpdateSize);\n  upstreamCodec_.generateWindowUpdate(output_, 1, 12);\n  upstreamCodec_.generateWindowUpdate(output_, 1, http2::kMaxWindowUpdateSize);\n\n  parse();\n  EXPECT_EQ(callbacks_.windowUpdateCalls, 4);\n  EXPECT_EQ(callbacks_.windowUpdates[0],\n            std::vector<uint32_t>({10, http2::kMaxWindowUpdateSize}));\n  EXPECT_EQ(callbacks_.windowUpdates[1],\n            std::vector<uint32_t>({12, http2::kMaxWindowUpdateSize}));\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, ZeroWindow) {\n  auto streamID = HTTPCodec::StreamID(1);\n  // First generate a frame with delta=1 so as to pass the checks, and then\n  // hack the frame so that delta=0 without modifying other checks\n  upstreamCodec_.generateWindowUpdate(output_, streamID, 1);\n  output_.trimEnd(http2::kFrameWindowUpdateSize);\n  QueueAppender appender(&output_, http2::kFrameWindowUpdateSize);\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(0));\n\n  parse();\n  // This test doesn't ensure that RST_STREAM is generated\n  EXPECT_EQ(callbacks_.windowUpdateCalls, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),\n            ErrorCode::PROTOCOL_ERROR);\n}\n\nTEST_F(HTTP2CodecTest, BasicGoaway) {\n  std::unique_ptr<folly::IOBuf> debugData =\n      folly::IOBuf::copyBuffer(\"debugData\");\n  upstreamCodec_.generateGoaway(\n      output_, 17, ErrorCode::ENHANCE_YOUR_CALM, std::move(debugData));\n\n  parse();\n  EXPECT_EQ(callbacks_.goaways, 1);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), \"debugData\");\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadGoaway) {\n  std::unique_ptr<folly::IOBuf> debugData =\n      folly::IOBuf::copyBuffer(\"debugData\");\n  upstreamCodec_.generateGoaway(\n      output_, 17, ErrorCode::ENHANCE_YOUR_CALM, std::move(debugData));\n  auto bytes =\n      upstreamCodec_.generateGoaway(output_, 27, ErrorCode::ENHANCE_YOUR_CALM);\n  ;\n  EXPECT_EQ(bytes, 0);\n}\n\nTEST_F(HTTP2CodecTest, RstStreamNoError) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"OK\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n  downstreamCodec_.generateBody(\n      output_, 1, makeBuf(10), HTTPCodec::NoPadding, true);\n  downstreamCodec_.generateRstStream(output_, 1, ErrorCode::NO_ERROR);\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.aborts, 1);\n}\n\nTEST_F(HTTP2CodecTest, DoubleGoaway) {\n  SetUpUpstreamTest();\n  downstreamCodec_.generateGoaway(output_);\n  EXPECT_TRUE(downstreamCodec_.isWaitingToDrain());\n  EXPECT_TRUE(downstreamCodec_.isReusable());\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(2));\n\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(2));\n  EXPECT_TRUE(upstreamCodec_.isReusable());\n\n  parseUpstream();\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(2));\n  EXPECT_FALSE(upstreamCodec_.isReusable());\n  EXPECT_EQ(callbacks_.goaways, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  downstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);\n  EXPECT_FALSE(downstreamCodec_.isWaitingToDrain());\n  EXPECT_FALSE(downstreamCodec_.isReusable());\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(2));\n\n  parseUpstream();\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(2));\n  EXPECT_EQ(callbacks_.goaways, 2);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  upstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);\n  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(2));\n  parse();\n  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));\n  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(1));\n  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(2));\n}\n\nTEST_F(HTTP2CodecTest, DoubleGoawayWithError) {\n  SetUpUpstreamTest();\n  std::unique_ptr<folly::IOBuf> debugData =\n      folly::IOBuf::copyBuffer(\"debugData\");\n  downstreamCodec_.generateGoaway(output_,\n                                  std::numeric_limits<int32_t>::max(),\n                                  ErrorCode::ENHANCE_YOUR_CALM,\n                                  std::move(debugData));\n  EXPECT_FALSE(downstreamCodec_.isWaitingToDrain());\n  EXPECT_FALSE(downstreamCodec_.isReusable());\n  auto ret = downstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);\n  EXPECT_EQ(ret, 0);\n\n  parseUpstream();\n  EXPECT_EQ(callbacks_.goaways, 1);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), \"debugData\");\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, GoawayHandling) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  upstreamCodec_.generateSettings(output_);\n\n  // send request\n  HTTPMessage req = getGetRequest();\n  HTTPHeaderSize size;\n  size.uncompressed = size.compressed = 0;\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true, &size);\n  EXPECT_GT(size.uncompressed, 0);\n  parse();\n  callbacks_.expectMessage(true, 1, \"/\");\n  callbacks_.reset();\n\n  SetUpUpstreamTest();\n  // drain after this message\n  downstreamCodec_.generateGoaway(output_, 1, ErrorCode::NO_ERROR);\n  parseUpstream();\n  // upstream cannot generate id > 1\n  upstreamCodec_.generateHeader(output_, 3, req, false, &size);\n  EXPECT_EQ(size.uncompressed, 0);\n  upstreamCodec_.generateWindowUpdate(output_, 3, 100);\n  upstreamCodec_.generateBody(\n      output_, 3, makeBuf(10), HTTPCodec::NoPadding, false);\n  upstreamCodec_.generateEOM(output_, 3);\n  upstreamCodec_.generateRstStream(output_, 3, ErrorCode::CANCEL);\n  EXPECT_EQ(output_.chainLength(), 0);\n\n  // send a push promise that will be rejected by downstream\n  req.getHeaders().add(\"foomonkey\", \"george\");\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1, false, &size);\n  EXPECT_GT(size.uncompressed, 0);\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  // send a push response that will be ignored\n  downstreamCodec_.generateHeader(output_, 2, resp, false, &size);\n  // window update for push doesn't make any sense, but whatever\n  downstreamCodec_.generateWindowUpdate(output_, 2, 100);\n  downstreamCodec_.generateBody(\n      output_, 2, makeBuf(10), HTTPCodec::NoPadding, false);\n  writeFrameHeaderManual(output_, 20, (uint8_t)http2::FrameType::DATA, 0, 2);\n  output_.append(makeBuf(10));\n\n  // tell the upstream no pushing, and parse the first batch\n  IOBufQueue dummy;\n  upstreamCodec_.generateGoaway(dummy, 0, ErrorCode::NO_ERROR);\n  parseUpstream();\n\n  output_.append(makeBuf(10));\n  downstreamCodec_.generateEOM(output_, 2);\n  downstreamCodec_.generateRstStream(output_, 2, ErrorCode::CANCEL);\n\n  // send a response that will be accepted, headers should be ok\n  downstreamCodec_.generateHeader(output_, 1, resp, true, &size);\n  EXPECT_GT(size.uncompressed, 0);\n\n  // parse the remainder\n  parseUpstream();\n  callbacks_.expectMessage(true, 1, 200);\n}\n\nTEST_F(HTTP2CodecTest, GoawayReply) {\n  upstreamCodec_.createStream();\n  upstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);\n\n  parse();\n  EXPECT_EQ(callbacks_.goaways, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  SetUpUpstreamTest();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n  downstreamCodec_.generateEOM(output_, 1);\n  parseUpstream();\n  callbacks_.expectMessage(true, 1, 200);\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n}\n\nTEST_F(HTTP2CodecTest, BasicSetting) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS, 37);\n  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE, 12345);\n  upstreamCodec_.generateSettings(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.maxStreams, 37);\n  EXPECT_EQ(callbacks_.windowSize, 12345);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, SettingsAck) {\n  upstreamCodec_.generateSettingsAck(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 0);\n  EXPECT_EQ(callbacks_.settingsAcks, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadSettings) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE, 0xffffffff);\n  upstreamCodec_.generateSettings(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTestOmitParsePreface, BadPushSettings) {\n  auto settings = downstreamCodec_.getEgressSettings();\n  settings->clearSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 0);\n  SetUpUpstreamTest();\n\n  parseUpstream([&](IOBuf* ingress) {\n    EXPECT_EQ(ingress->computeChainDataLength(), http2::kFrameHeaderSize);\n  });\n  EXPECT_FALSE(upstreamCodec_.supportsPushTransactions());\n  // Only way to disable push for downstreamCodec_ is to read\n  // ENABLE_PUSH:0 from client\n  EXPECT_TRUE(downstreamCodec_.supportsPushTransactions());\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, SettingsTableSize) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 8192);\n  upstreamCodec_.generateSettings(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  downstreamCodec_.generateSettingsAck(output_);\n  parseUpstream();\n\n  callbacks_.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  parseUpstream();\n  callbacks_.expectMessage(false, 2, 200);\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n}\n\nTEST_F(HTTP2CodecTestOmitParsePreface, BadSettingsTableSize) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 8192);\n  // This sets the max decoder table size to 8k\n  upstreamCodec_.generateSettings(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  callbacks_.reset();\n\n  // Attempt to set a new max table size.  This is a no-op because the first,\n  // setting is unacknowledged.  The upstream encoder will up the table size to\n  // 8k per the first settings frame and the HPACK codec will send a code to\n  // update the decoder.\n  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 4096);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  SetUpUpstreamTest();\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  parseUpstream();\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n}\n\nTEST_F(HTTP2CodecTest, SettingsTableSizeEarlyShrink) {\n  // Lower size to 2k\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 2048);\n  upstreamCodec_.generateSettings(output_);\n\n  parse();\n  EXPECT_EQ(callbacks_.settings, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  downstreamCodec_.generateSettingsAck(output_);\n  // Parsing SETTINGS ack updates upstream decoder to 2k\n  parseUpstream();\n\n  callbacks_.reset();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  // downstream encoder will send TSU/2k\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  // sets pending table size to 512, but doesn't update it yet\n  settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 512);\n  IOBufQueue tmp{IOBufQueue::cacheChainLength()};\n  upstreamCodec_.generateSettings(tmp);\n\n  // Previous code would barf here, since TSU/2k is a violation of the current\n  // max=512\n  parseUpstream();\n  callbacks_.expectMessage(false, 2, 200);\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n}\n\nTEST_F(HTTP2CodecTest, ConcurrentStreams) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS, 0);\n  upstreamCodec_.generateSettings(output_);\n  HTTPMessage req = getGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  EXPECT_EQ(callbacks_.maxStreams, 0);\n\n  callbacks_.reset();\n  SetUpUpstreamTest();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  downstreamCodec_.generateHeader(output_, 1, resp, false /* eom */);\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1);\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 2);\n  EXPECT_EQ(callbacks_.headersComplete, 2);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_NE(callbacks_.msg, nullptr);\n  EXPECT_EQ(callbacks_.pushId, 2);\n  EXPECT_EQ(callbacks_.assocStreamId, 1);\n  callbacks_.reset();\n  downstreamCodec_.generateHeader(output_, 2, resp, true /* eom */);\n  parseUpstream();\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),\n            ErrorCode::REFUSED_STREAM);\n}\n\nTEST_F(HTTP2CodecTest, BasicRFC9218Priority) {\n  auto pri = HTTPPriority{7, true};\n  upstreamCodec_.generatePriority(output_, 1, pri);\n\n  parse();\n  EXPECT_EQ(callbacks_.urgency, pri.urgency);\n  EXPECT_EQ(callbacks_.incremental, pri.incremental);\n}\n\nTEST_F(HTTP2CodecTest, BadRFC9218Priority) {\n  auto pri = HTTPPriority{7, true};\n  upstreamCodec_.generatePriority(output_, 1, pri);\n\n  EXPECT_TRUE(parse([&](IOBuf* ingress) {\n    folly::io::RWPrivateCursor c(ingress);\n    c.skip(http2::kFrameHeaderSize + sizeof(uint32_t));\n    c.writeBE<uint32_t>(static_cast<uint32_t>(1));\n  }));\n\n  // ill-formatted priority, so returns default priority\n  EXPECT_EQ(callbacks_.urgency, 3);\n  EXPECT_EQ(callbacks_.incremental, 1);\n}\n\nclass DummyQueue : public HTTPCodec::PriorityQueue {\n public:\n  DummyQueue() = default;\n  ~DummyQueue() override = default;\n  void addPriorityNode(HTTPCodec::StreamID id, HTTPCodec::StreamID) override {\n    nodes_.push_back(id);\n  }\n\n  std::vector<HTTPCodec::StreamID> nodes_;\n};\n\nTEST_F(HTTP2CodecTest, BasicPushPromise) {\n  upstreamCodec_.generateSettings(output_);\n  parse();\n  EXPECT_FALSE(upstreamCodec_.supportsPushTransactions());\n  EXPECT_FALSE(downstreamCodec_.supportsPushTransactions());\n\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  upstreamCodec_.generateSettings(output_);\n  parse();\n  EXPECT_TRUE(upstreamCodec_.supportsPushTransactions());\n  EXPECT_TRUE(downstreamCodec_.supportsPushTransactions());\n\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  upstreamCodec_.createStream();\n  upstreamCodec_.createStream();\n\n  // Test had previously hardcoded 7\n  HTTPCodec::StreamID assocStream = upstreamCodec_.createStream();\n  for (auto i = 0; i < 2; i++) {\n    // Push promise\n    HTTPCodec::StreamID pushStream = downstreamCodec_.createStream();\n    HTTPMessage req = getGetRequest();\n    req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n    downstreamCodec_.generatePushPromise(output_, pushStream, req, assocStream);\n\n    parseUpstream();\n    callbacks_.expectMessage(false, 2, \"/\"); // + host\n    EXPECT_EQ(callbacks_.assocStreamId, assocStream);\n    EXPECT_EQ(callbacks_.headersCompleteId, pushStream);\n    auto& headers = callbacks_.msg->getHeaders();\n    EXPECT_EQ(\"coolio\", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));\n    callbacks_.reset();\n\n    // Actual reply headers\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"text/plain\");\n    downstreamCodec_.generateHeader(output_, pushStream, resp);\n\n    parseUpstream();\n    callbacks_.expectMessage(false, 2, 200);\n    EXPECT_EQ(callbacks_.headersCompleteId, pushStream);\n    EXPECT_EQ(callbacks_.assocStreamId, 0);\n    EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n    EXPECT_EQ(\"text/plain\",\n              callbacks_.msg->getHeaders().getSingleOrEmpty(\n                  HTTP_HEADER_CONTENT_TYPE));\n    callbacks_.reset();\n  }\n}\n\nTEST_F(HTTP2CodecTest, DuplicatePushPromise) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  upstreamCodec_.generateSettings(output_);\n  parse();\n  EXPECT_TRUE(upstreamCodec_.supportsPushTransactions());\n  EXPECT_TRUE(downstreamCodec_.supportsPushTransactions());\n\n  SetUpUpstreamTest();\n\n  HTTPCodec::StreamID assocStream = 7;\n  HTTPCodec::StreamID pushStream = downstreamCodec_.createStream();\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  downstreamCodec_.generatePushPromise(output_, pushStream, req, assocStream);\n  downstreamCodec_.generatePushPromise(output_, pushStream, req, assocStream);\n\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, BadPushPromise) {\n  // ENABLE_PUSH is now 0 by default\n  SetUpUpstreamTest();\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1);\n\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.assocStreamId, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, BadPushPromiseResets) {\n  auto settings = upstreamCodec_.getEgressSettings();\n  settings->setSetting(SettingsId::ENABLE_PUSH, 1);\n  upstreamCodec_.generateSettings(output_);\n  SetUpUpstreamTest();\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"200\");\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1);\n\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.assocStreamId, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 2);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicCertificateRequest) {\n  uint16_t requestId = 17;\n  std::unique_ptr<folly::IOBuf> authRequest =\n      folly::IOBuf::copyBuffer(\"authRequestData\");\n  upstreamCodec_.generateCertificateRequest(\n      output_, requestId, std::move(authRequest));\n\n  parse();\n  EXPECT_EQ(callbacks_.certificateRequests, 1);\n  EXPECT_EQ(callbacks_.lastCertRequestId, requestId);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), \"authRequestData\");\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BasicCertificate) {\n  uint16_t certId = 17;\n  std::unique_ptr<folly::IOBuf> authenticator =\n      folly::IOBuf::copyBuffer(\"authenticatorData\");\n  upstreamCodec_.generateCertificate(output_, certId, std::move(authenticator));\n\n  parse();\n  EXPECT_EQ(callbacks_.certificates, 1);\n  EXPECT_EQ(callbacks_.lastCertId, certId);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), \"authenticatorData\");\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, BadServerPreface) {\n  output_.reset();\n  downstreamCodec_.generateWindowUpdate(output_, 0, 10);\n  parseUpstream();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.assocStreamId, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, Normal1024Continuation) {\n  HTTPMessage req = getGetRequest();\n  string bigval(8691, '!');\n  bigval.append(8691, '@');\n  req.getHeaders().add(\"x-headr\", bigval);\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  parse();\n  callbacks_.expectMessage(false, -1, \"/\");\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_EQ(bigval, headers.getSingleOrEmpty(\"x-headr\"));\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n\n  upstreamCodec_.generateSettingsAck(output_);\n  parse();\n  EXPECT_EQ(callbacks_.settingsAcks, 1);\n}\n\nTEST_F(HTTP2CodecTest, StreamIdOverflow) {\n  HTTP2Codec codec(TransportDirection::UPSTREAM);\n\n  HTTPCodec::StreamID streamId;\n  codec.setNextEgressStreamId(std::numeric_limits<int32_t>::max() - 10);\n  while (codec.isReusable()) {\n    streamId = codec.createStream();\n  }\n  EXPECT_EQ(streamId, std::numeric_limits<int32_t>::max() - 2);\n}\n\nTEST_F(HTTP2CodecTest, TestMultipleDifferentContentLengthHeaders) {\n  // Generate a POST request with two Content-Length headers\n  // NOTE: getPostRequest already adds the content-length\n  HTTPMessage req = getPostRequest();\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"300\");\n  EXPECT_EQ(req.getHeaders().getNumberOfValues(HTTP_HEADER_CONTENT_LENGTH), 2);\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n  parse();\n\n  // Check that the request fails before the codec finishes parsing the headers\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.lastParseError->getHttpStatusCode(), 400);\n}\n\nTEST_F(HTTP2CodecTest, TestMultipleIdenticalContentLengthHeaders) {\n  // Generate a POST request with two Content-Length headers\n  // NOTE: getPostRequest already adds the content-length\n  HTTPMessage req = getPostRequest();\n  req.getHeaders().add(\"content-length\", \"200\");\n  EXPECT_EQ(req.getHeaders().getNumberOfValues(\"content-length\"), 2);\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n  parse();\n\n  // Check that the headers parsing completes correctly\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n}\n\nnamespace {\n} // namespace\n\nTEST_F(HTTP2CodecTest, HTTP2SettingsFailure) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  // no settings\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n\n  HTTPHeaders& headers = req.getHeaders();\n\n  // Not base64_url settings\n  headers.set(http2::kProtocolSettingsHeader, \"????\");\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n  headers.set(http2::kProtocolSettingsHeader, \"AAA\");\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n\n  // Too big\n  string bigSettings((http2::kMaxFramePayloadLength + 1) * 4 / 3, 'A');\n  headers.set(http2::kProtocolSettingsHeader, bigSettings);\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n\n  // Malformed (not a multiple of 6)\n  headers.set(http2::kProtocolSettingsHeader, \"AAAA\");\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n\n  // Two headers\n  headers.set(http2::kProtocolSettingsHeader, \"AAAAAAAA\");\n  headers.add(http2::kProtocolSettingsHeader, \"AAAAAAAA\");\n  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));\n}\n\nTEST_F(HTTP2CodecTest, HTTP2EnableConnect) {\n  SetUpUpstreamTest();\n  // egress settings have no connect settings.\n  auto ws_enable = upstreamCodec_.getEgressSettings()->getSetting(\n      SettingsId::ENABLE_CONNECT_PROTOCOL);\n  // enable connect settings, and check.\n  upstreamCodec_.getEgressSettings()->setSetting(\n      SettingsId::ENABLE_CONNECT_PROTOCOL, 1);\n  ws_enable = upstreamCodec_.getEgressSettings()->getSetting(\n      SettingsId::ENABLE_CONNECT_PROTOCOL);\n  EXPECT_EQ(ws_enable->value, 1);\n  // generateSettings.\n  // pass the buffer to be parsed by the codec and check for ingress settings.\n  upstreamCodec_.generateSettings(output_);\n  parseUpstream();\n  EXPECT_EQ(1, upstreamCodec_.peerHasWebsockets());\n}\n\nTEST_F(HTTP2CodecTest, WebsocketUpgrade) {\n  HTTPMessage req = getGetRequest(\"/apples\");\n  req.setSecure(true);\n  req.setEgressWebsocketUpgrade();\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, false);\n  parse();\n\n  EXPECT_TRUE(callbacks_.msg->isIngressWebsocketUpgrade());\n  EXPECT_NE(nullptr, callbacks_.msg->getUpgradeProtocol());\n  EXPECT_EQ(headers::kWebsocketString, *callbacks_.msg->getUpgradeProtocol());\n}\n\nTEST_F(HTTP2CodecTest, WebsocketBadHeader) {\n  const std::string kConnect{\"CONNECT\"};\n  const std::string kWebsocketPath{\"/websocket\"};\n  const std::string kSchemeHttps{\"https\"};\n  vector<proxygen::compress::Header> reqHeaders = {\n      Header::makeHeaderForTest(headers::kMethod, kConnect),\n      Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),\n  };\n  vector<proxygen::compress::Header> optionalHeaders = {\n      Header::makeHeaderForTest(headers::kPath, kWebsocketPath),\n      Header::makeHeaderForTest(headers::kScheme, kSchemeHttps),\n  };\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  int stream = 1;\n  for (size_t i = 0; i < optionalHeaders.size(); ++i, stream += 2) {\n    auto headers = reqHeaders;\n    headers.push_back(optionalHeaders[i]);\n    auto encodedHeaders = headerCodec.encode(headers);\n    writeHeaders(output_,\n                 std::move(encodedHeaders),\n                 stream,\n                 http2::kNoPadding,\n                 false,\n                 true);\n    parse();\n  }\n\n  EXPECT_EQ(callbacks_.messageBegin, optionalHeaders.size());\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, optionalHeaders.size());\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, WebsocketDupProtocol) {\n  const std::string kConnect{\"CONNECT\"};\n  const std::string kWebsocketPath{\"/websocket\"};\n  const std::string kSchemeHttps{\"https\"};\n  vector<proxygen::compress::Header> headers = {\n      Header::makeHeaderForTest(headers::kMethod, kConnect),\n      Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),\n      Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),\n      Header::makeHeaderForTest(headers::kPath, kWebsocketPath),\n      Header::makeHeaderForTest(headers::kScheme, kSchemeHttps),\n  };\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  auto encodedHeaders = headerCodec.encode(headers);\n  writeHeaders(\n      output_, std::move(encodedHeaders), 1, http2::kNoPadding, false, true);\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, WebsocketIncorrectResponse) {\n  output_.reset();\n  HTTPMessage resp;\n  resp.setStatusCode(400);\n  resp.setStatusMessage(\"Bad Request\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n  parseUpstream();\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, WebTransportProtocol) {\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::CONNECT);\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/wt\");\n  req.setSecure(true);\n  req.setUpgradeProtocol(\"webtransport\");\n\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, false);\n  parse();\n\n  EXPECT_FALSE(callbacks_.msg->isIngressWebsocketUpgrade());\n  EXPECT_NE(nullptr, callbacks_.msg->getUpgradeProtocol());\n  EXPECT_EQ(\"webtransport\", *callbacks_.msg->getUpgradeProtocol());\n}\n\nTEST_F(HTTP2CodecTest, TestAllEgressFrameTypeCallbacks) {\n  class CallbackTypeTracker {\n    std::set<uint8_t> types;\n\n   public:\n    void add(uint8_t, uint8_t type, uint64_t, uint16_t) {\n      types.insert(type);\n    }\n\n    bool isAllFrameTypesReceived() {\n      http2::FrameType expectedTypes[] = {\n          http2::FrameType::DATA,\n          http2::FrameType::HEADERS,\n          http2::FrameType::RFC9218_PRIORITY,\n          http2::FrameType::RST_STREAM,\n          http2::FrameType::SETTINGS,\n          http2::FrameType::PUSH_PROMISE,\n          http2::FrameType::PING,\n          http2::FrameType::GOAWAY,\n          http2::FrameType::WINDOW_UPDATE,\n          http2::FrameType::CONTINUATION,\n      };\n\n      for (http2::FrameType type : expectedTypes) {\n        EXPECT_TRUE(types.find(static_cast<uint8_t>(type)) != types.end())\n            << \"callback missing for type \" << static_cast<uint8_t>(type);\n      }\n      return types.size() == (sizeof(expectedTypes) / sizeof(http2::FrameType));\n    }\n  };\n\n  CallbackTypeTracker callbackTypeTracker;\n\n  NiceMock<MockHTTPCodecCallback> mockCallback;\n  upstreamCodec_.setCallback(&mockCallback);\n  downstreamCodec_.setCallback(&mockCallback);\n  EXPECT_CALL(mockCallback, onGenerateFrameHeader(_, _, _, _))\n      .WillRepeatedly(Invoke(&callbackTypeTracker, &CallbackTypeTracker::add));\n\n  // DATA frame\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  upstreamCodec_.generateBody(\n      output_, 2, std::move(buf), HTTPCodec::NoPadding, true);\n\n  HTTPHeaderSize size;\n  size.uncompressed = size.compressed = 0;\n  HTTPMessage req = getGetRequest();\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true, &size);\n\n  upstreamCodec_.generatePriority(output_, 3, HTTPPriority{1, true});\n  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::ENHANCE_YOUR_CALM);\n  upstreamCodec_.generateSettings(output_);\n  downstreamCodec_.generatePushPromise(output_, 2, req, 1);\n  upstreamCodec_.generatePingRequest(output_);\n\n  std::unique_ptr<folly::IOBuf> debugData =\n      folly::IOBuf::copyBuffer(\"debugData\");\n  upstreamCodec_.generateGoaway(\n      output_, 17, ErrorCode::ENHANCE_YOUR_CALM, std::move(debugData));\n\n  upstreamCodec_.generateWindowUpdate(output_, 0, 10);\n\n  // Tests the continuation frame\n  req = getBigGetRequest();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  EXPECT_TRUE(callbackTypeTracker.isAllFrameTypesReceived());\n}\n\nTEST_F(HTTP2CodecTest, Trailers) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  upstreamCodec_.generateBody(\n      output_, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  upstreamCodec_.generateTrailers(output_, 1, trailers);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  EXPECT_EQ(callbacks_.trailers, 1);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n#ifndef NDEBUG\n  // frames = 4 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersWithPseudoHeaders) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  upstreamCodec_.generateBody(\n      output_, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);\n\n  HPACKCodec headerCodec(TransportDirection::UPSTREAM);\n  std::string post(\"POST\");\n  std::vector<proxygen::compress::Header> trailers = {\n      Header::makeHeaderForTest(headers::kMethod, post)};\n  auto encodedTrailers = headerCodec.encode(trailers);\n  writeHeaders(\n      output_, std::move(encodedTrailers), 1, http2::kNoPadding, true, true);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  EXPECT_EQ(callbacks_.trailers, 0);\n  EXPECT_EQ(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, TrailersNoBody) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  upstreamCodec_.generateTrailers(output_, id, trailers);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.bodyLength, 0);\n  EXPECT_EQ(callbacks_.trailers, 1);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n#ifndef NDEBUG\n  // frames = 3 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersContinuation) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  trailers.add(\"x-huge-trailer\",\n               std::string(http2::kMaxFramePayloadLengthMin, '!'));\n  upstreamCodec_.generateTrailers(output_, 1, trailers);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_NE(callbacks_.msg, nullptr);\n  EXPECT_EQ(callbacks_.trailers, 1);\n  EXPECT_NE(callbacks_.msg->getTrailers(), nullptr);\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(std::string(http2::kMaxFramePayloadLengthMin, '!'),\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-huge-trailer\"));\n#ifndef NDEBUG\n  // frames = 4 to account for settings frame after H2 conn preface\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersReply) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  downstreamCodec_.generateBody(\n      output_, 1, std::move(buf), HTTPCodec::NoPadding, false);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  trailers.add(\"x-trailer-2\", \"chicken-kyiv\");\n  downstreamCodec_.generateTrailers(output_, 1, trailers);\n\n  parseUpstream();\n\n  callbacks_.expectMessage(true, 2, 200);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_EQ(1, callbacks_.trailers);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(\"chicken-kyiv\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-2\"));\n#ifndef NDEBUG\n  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersReplyEmpty) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  downstreamCodec_.generateBody(\n      output_, 1, std::move(buf), HTTPCodec::NoPadding, false);\n\n  HTTPHeaders trailers;\n  auto trailerSz = downstreamCodec_.generateTrailers(output_, 1, trailers);\n  // Just a frame header (DATA + END_STREAM).\n  EXPECT_EQ(trailerSz, 9);\n\n  parseUpstream();\n\n  callbacks_.expectMessage(true, 2, 200);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_EQ(0, callbacks_.trailers);\n  EXPECT_EQ(nullptr, callbacks_.msg->getTrailers());\n#ifndef NDEBUG\n  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersReplyWithNoData) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  downstreamCodec_.generateTrailers(output_, 1, trailers);\n\n  parseUpstream();\n\n  callbacks_.expectMessage(true, 2, 200);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.bodyLength, 0);\n  const auto& headers = callbacks_.msg->getHeaders();\n  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));\n  EXPECT_EQ(\"x-coolio\", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));\n  EXPECT_EQ(1, callbacks_.trailers);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n#ifndef NDEBUG\n  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 3);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersReplyWithPseudoHeaders) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setStatusMessage(\"nifty-nice\");\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"x-coolio\");\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  downstreamCodec_.generateBody(\n      output_, 1, std::move(buf), HTTPCodec::NoPadding, false);\n\n  HPACKCodec headerCodec(TransportDirection::DOWNSTREAM);\n  std::string post(\"POST\");\n  std::vector<proxygen::compress::Header> trailers = {\n      Header::makeHeaderForTest(headers::kMethod, post)};\n  auto encodedTrailers = headerCodec.encode(trailers);\n  writeHeaders(\n      output_, std::move(encodedTrailers), 1, http2::kNoPadding, true, true);\n  parseUpstream();\n\n  // Unfortunately, you get 2x messageBegin calls for parse error in\n  // upstream trailers\n  EXPECT_EQ(callbacks_.messageBegin, 2);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.trailers, 0);\n  EXPECT_EQ(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, TrailersReplyContinuation) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  trailers.add(\"x-huge-trailer\",\n               std::string(http2::kMaxFramePayloadLengthMin, '!'));\n  downstreamCodec_.generateTrailers(output_, 1, trailers);\n\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_NE(callbacks_.msg, nullptr);\n  EXPECT_EQ(callbacks_.msg->getStatusCode(), 200);\n  EXPECT_EQ(1, callbacks_.trailers);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(std::string(http2::kMaxFramePayloadLengthMin, '!'),\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-huge-trailer\"));\n#ifndef NDEBUG\n  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersReplyMissingContinuation) {\n  SetUpUpstreamTest();\n  upstreamCodec_.createStream();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  trailers.add(\"x-huge-trailer\",\n               std::string(http2::kMaxFramePayloadLengthMin, '!'));\n  downstreamCodec_.generateTrailers(output_, 1, trailers);\n  // empirically determined the size of continuation frame, and strip it\n  output_.trimEnd(http2::kFrameHeaderSize + 4132);\n\n  // insert a non-continuation (but otherwise valid) frame\n  http2::writeGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM);\n\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n#ifndef NDEBUG\n  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, TrailersNotLatest) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n  auto id2 = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id2, req);\n\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"pico-de-gallo\");\n  upstreamCodec_.generateTrailers(output_, id, trailers);\n  upstreamCodec_.generateHeader(output_, id2, req);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 2);\n  EXPECT_EQ(callbacks_.headersComplete, 2);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.trailers, 1);\n  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());\n  EXPECT_EQ(\"pico-de-gallo\",\n            callbacks_.msg->getTrailers()->getSingleOrEmpty(\"x-trailer-1\"));\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 1);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, ResponseWithoutRequest) {\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  downstreamCodec_.generateHeader(output_, 1, resp);\n\n  parseUpstream();\n\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 1);\n}\n\nTEST_F(HTTP2CodecTest, PaddingFrame) {\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n\n  upstreamCodec_.generatePadding(output_, 2, 80);\n  upstreamCodec_.generateBody(\n      output_, 2, std::move(buf), HTTPCodec::NoPadding, false);\n  upstreamCodec_.generatePadding(output_, 2, 40);\n  upstreamCodec_.generateEOM(output_, 2);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), data);\n#ifndef NDEBUG\n  // Settings (SetUp) + Pad + Body + Pad + EOM = 5 frames\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 5);\n#endif\n}\n\nTEST_F(HTTP2CodecTest, MaxPadding) {\n  string data(\"abcde\");\n  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());\n  const auto padding = numeric_limits<uint16_t>::max();\n  [[maybe_unused]] const auto paddingFrames =\n      padding / http2::kMaxFramePayloadLengthMin +\n      (padding % http2::kMaxFramePayloadLengthMin == 0 ? 0 : 1);\n\n  upstreamCodec_.generateBody(\n      output_, 2, std::move(buf), HTTPCodec::NoPadding, false);\n  upstreamCodec_.generatePadding(output_, 2, padding);\n  upstreamCodec_.generateEOM(output_, 2);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 0);\n  EXPECT_EQ(callbacks_.headersComplete, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 1);\n  EXPECT_EQ(callbacks_.bodyLength, 5);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.data_.move()->moveToFbString(), data);\n#ifndef NDEBUG\n  // Settings (SetUp) + Body + Pad (paddingFrames) + EOM\n  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3 + paddingFrames);\n#endif\n}\n\nclass HTTP2CodecDoubleGoawayTest : public HTTPParallelCodecTest {\n public:\n  HTTP2CodecDoubleGoawayTest()\n      : HTTPParallelCodecTest(upstreamCodec_, downstreamCodec_) {\n  }\n\n  void SetUp() override {\n    HTTPParallelCodecTest::SetUp();\n  }\n\n protected:\n  HTTP2Codec upstreamCodec_{TransportDirection::UPSTREAM};\n  HTTP2Codec downstreamCodec_{TransportDirection::DOWNSTREAM};\n};\n\nTEST_F(HTTP2CodecDoubleGoawayTest, EnableDoubleGoawayAfterClose) {\n  SetUpUpstreamTest();\n  upstreamCodec_.generateGoaway(output_);\n  // Now a no-op\n  upstreamCodec_.enableDoubleGoawayDrain();\n  parseUpstream();\n  EXPECT_EQ(callbacks_.goaways, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, SendEmptyTrailers) {\n  HTTPMessage req = getGetRequest(\"/guacamole\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req);\n\n  // Send a single trailer \"Connection\" \"keep-alive\"\n  HTTPHeaders trailers;\n  trailers.add(\"Connection\", \"keep-alive\");\n  upstreamCodec_.generateTrailers(output_, id, trailers);\n\n  parse();\n\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.bodyCalls, 0);\n  EXPECT_EQ(callbacks_.trailers, 0);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n  EXPECT_EQ(callbacks_.msg->getTrailers(), nullptr);\n}\n\nTEST_F(HTTP2CodecTest, GenerateHeadersWithEmptyRequest) {\n  HTTPMessage req;\n  req.getClientAddress(); // Force it to be a request\n  auto id = upstreamCodec_.createStream();\n  upstreamCodec_.generateHeader(output_, id, req, true /* eom */);\n\n  parse();\n  EXPECT_EQ(callbacks_.messageBegin, 1);\n  EXPECT_EQ(callbacks_.headersComplete, 1);\n  EXPECT_EQ(callbacks_.messageComplete, 1);\n  EXPECT_EQ(callbacks_.streamErrors, 0);\n  EXPECT_EQ(callbacks_.sessionErrors, 0);\n}\n\nTEST_F(HTTP2CodecTest, SetIfNotPresent) {\n  auto* egressSettings = CHECK_NOTNULL(downstreamCodec_.getEgressSettings());\n  // WT_MAX_SESSIONS not currently present\n  EXPECT_TRUE(egressSettings->setIfNotPresent(SettingsId::WT_MAX_SESSIONS, 1));\n  // no-op since added above\n  EXPECT_FALSE(egressSettings->setIfNotPresent(SettingsId::WT_MAX_SESSIONS, 2));\n  // expected value is 1\n  auto* wtMaxSessions = egressSettings->getSetting(SettingsId::WT_MAX_SESSIONS);\n  EXPECT_TRUE(wtMaxSessions && wtMaxSessions->value == 1);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTP2FramerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/HTTP2FramerTest.h>\n\n#include <folly/Random.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n\nusing namespace folly::io;\nusing namespace folly;\nusing namespace proxygen::http2;\nusing namespace proxygen;\nusing namespace std;\n\nvoid writeFrameHeaderManual(IOBufQueue& queue,\n                            uint32_t length,\n                            uint8_t type,\n                            uint8_t flags,\n                            uint32_t stream) {\n  QueueAppender appender(&queue, kFrameHeaderSize);\n  uint32_t lengthAndType = length << 8 | type;\n  appender.writeBE<uint32_t>(lengthAndType);\n  appender.writeBE<uint8_t>(flags);\n  appender.writeBE<uint32_t>(stream);\n}\n\nclass HTTP2FramerTest : public testing::Test {\n public:\n  HTTP2FramerTest() = default;\n\n  template <typename ParseFunc, typename... Args>\n  void parse(ParseFunc&& parseFn, FrameHeader& outHeader, Args&&... outArgs) {\n    parse(queue_.front(), parseFn, outHeader, std::forward<Args>(outArgs)...);\n  }\n\n  template <typename ParseFunc, typename... Args>\n  void parse(const IOBuf* data,\n             ParseFunc&& parseFn,\n             FrameHeader& outHeader,\n             Args&&... outArgs) {\n    Cursor cursor(data);\n    auto ret1 = parseFrameHeader(cursor, outHeader);\n    ASSERT_EQ(ret1, ErrorCode::NO_ERROR);\n    auto ret2 = (*parseFn)(cursor, outHeader, std::forward<Args>(outArgs)...);\n    ASSERT_EQ(ret2, ErrorCode::NO_ERROR);\n  }\n\n  void dataFrameTest(uint32_t dataLen, folly::Optional<uint8_t> padLen) {\n    auto body = makeBuf(dataLen);\n    dataFrameTest(std::move(body), dataLen, padLen);\n  }\n\n  void dataFrameTest(std::unique_ptr<IOBuf> body,\n                     uint32_t dataLen,\n                     folly::Optional<uint8_t> padLen) {\n    auto frameLen = uint32_t(dataLen);\n    if (padLen) {\n      frameLen += 1 + *padLen;\n    }\n    if (frameLen > kMaxFramePayloadLength) {\n      EXPECT_DEATH_NO_CORE(\n          writeData(queue_, body->clone(), 1, padLen, false, true), \".*\");\n    } else {\n      writeData(queue_, body->clone(), 1, padLen, false, true);\n\n      FrameHeader outHeader;\n      std::unique_ptr<IOBuf> outBuf;\n      uint16_t padding = 0;\n      parse(&parseData, outHeader, outBuf, padding);\n\n      EXPECT_EQ(outBuf->moveToFbString(), body->moveToFbString());\n      EXPECT_EQ(padding, padLen ? (*padLen + 1) : 0);\n    }\n    queue_.move(); // reset everything\n  }\n\n  IOBufQueue queue_{IOBufQueue::cacheChainLength()};\n};\n\nTEST_F(HTTP2FramerTest, WriteAndParseSmallDataNoPad) {\n  dataFrameTest(20, kNoPadding);\n}\n\nTEST_F(HTTP2FramerTest, WriteAndParseSmallDataZeroPad) {\n  dataFrameTest(20, Padding(0));\n}\n\nTEST_F(HTTP2FramerTest, WriteAndParseSmallDataSmallPad) {\n  dataFrameTest(20, Padding(20));\n}\n\nTEST_F(HTTP2FramerTest, ManyDataFrameSizes) {\n  dataFrameTest(0, kNoPadding);\n  for (uint32_t dataLen = 1; dataLen < kMaxFramePayloadLength; dataLen <<= 1) {\n    auto body = makeBuf(dataLen);\n    dataFrameTest(body->clone(), dataLen, Padding(0));\n    dataFrameTest(0, Padding(dataLen));\n    for (uint8_t padding = 1; padding != 0; padding <<= 1) {\n      dataFrameTest(body->clone(), dataLen, Padding(padding));\n    }\n  }\n}\n\nTEST_F(HTTP2FramerTest, BadStreamData) {\n  writeFrameHeaderManual(\n      queue_, 0, static_cast<uint8_t>(FrameType::DATA), 0, 0);\n  FrameHeader outHeader;\n  std::unique_ptr<IOBuf> outBuf;\n  uint16_t padding = 0;\n  Cursor cursor(queue_.front());\n  EXPECT_EQ(parseFrameHeader(cursor, outHeader), ErrorCode::NO_ERROR);\n  EXPECT_EQ(parseData(cursor, outHeader, outBuf, padding),\n            ErrorCode::PROTOCOL_ERROR);\n}\n\nTEST_F(HTTP2FramerTest, BadStreamSettings) {\n  writeFrameHeaderManual(\n      queue_, 0, static_cast<uint8_t>(FrameType::SETTINGS), ACK, 1);\n  FrameHeader outHeader;\n  std::deque<SettingPair> outSettings;\n  Cursor cursor(queue_.front());\n  EXPECT_EQ(parseFrameHeader(cursor, outHeader), ErrorCode::NO_ERROR);\n  EXPECT_EQ(parseSettings(cursor, outHeader, outSettings),\n            ErrorCode::PROTOCOL_ERROR);\n}\n\nTEST_F(HTTP2FramerTest, BadPad) {\n  auto body = makeBuf(5);\n  writeData(queue_, body->clone(), 1, Padding(5), false, true);\n  queue_.trimEnd(5);\n  queue_.append(string(\"abcde\"));\n\n  FrameHeader outHeader;\n  std::unique_ptr<IOBuf> outBuf;\n  uint16_t padding = 0;\n  Cursor cursor(queue_.front());\n  EXPECT_EQ(parseFrameHeader(cursor, outHeader), ErrorCode::NO_ERROR);\n  EXPECT_EQ(parseData(cursor, outHeader, outBuf, padding),\n            ErrorCode::PROTOCOL_ERROR);\n}\n\nTEST_F(HTTP2FramerTest, NoHeadroomOptimization) {\n  queue_.move();\n  auto buf = folly::IOBuf::create(200);\n  // This should make enough headroom so we will apply the optimization:\n  auto headRoomSize = kFrameHeaderSize + kFramePrioritySize * 2 + 10;\n  buf->advance(headRoomSize);\n  buf->append(20); // make a positive length\n  writeData(queue_,\n            std::move(buf),\n            1,\n            Padding(0),\n            false,\n            false /* reuseIOBufHeadroom */);\n  auto queueHead = queue_.front();\n  EXPECT_TRUE(queueHead->isChained());\n  // This is our original iobuf:\n  auto queueNode = queueHead->prev();\n  EXPECT_TRUE(queueNode != nullptr);\n  // And its headroom or length is untouched:\n  EXPECT_EQ(headRoomSize, queueNode->headroom());\n  EXPECT_EQ(20, queueNode->length());\n}\n\nTEST_F(HTTP2FramerTest, UseHeadroomOptimization) {\n  queue_.move();\n  auto buf = folly::IOBuf::create(200);\n  // This should make enough headroom so we will apply the optimization:\n  auto headRoomSize = kFrameHeaderSize + kFramePrioritySize * 2 + 10;\n  buf->advance(headRoomSize);\n  buf->append(20); // make a positive length\n  writeData(queue_,\n            std::move(buf),\n            1,\n            Padding(0),\n            false,\n            true /* reuseIOBufHeadroom */);\n  // There won't be a chain, frame header is written in-place into our IOBuf:\n  auto queueNode = queue_.front();\n  EXPECT_FALSE(queueNode->isChained());\n  // And its headroom has been shrinked:\n  EXPECT_LT(queueNode->headroom(), headRoomSize);\n}\n\nTEST_F(HTTP2FramerTest, BadStreamId) {\n  // We should crash on DBG builds if the stream id > 2^31 - 1\n  EXPECT_DEATH_NO_CORE(writeRstStream(queue_,\n                                      static_cast<uint32_t>(-1),\n                                      ErrorCode::PROTOCOL_ERROR),\n                       \".*\");\n}\n\nTEST_F(HTTP2FramerTest, RstStream) {\n  writeRstStream(queue_, 1, ErrorCode::CANCEL);\n\n  FrameHeader header;\n  ErrorCode outCode{};\n  parse(&parseRstStream, header, outCode);\n\n  ASSERT_EQ(FrameType::RST_STREAM, header.type);\n  ASSERT_EQ(1, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameRstStreamSize, header.length);\n  ASSERT_EQ(ErrorCode::CANCEL, outCode);\n}\n\nTEST_F(HTTP2FramerTest, Goaway) {\n  writeGoaway(queue_, 0, ErrorCode::FLOW_CONTROL_ERROR);\n\n  FrameHeader header;\n  uint32_t lastStreamID = 0;\n  ErrorCode outCode{};\n  std::unique_ptr<IOBuf> outDebugData;\n  parse(&parseGoaway, header, lastStreamID, outCode, outDebugData);\n\n  ASSERT_EQ(FrameType::GOAWAY, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameGoawaySize, header.length);\n  ASSERT_EQ(ErrorCode::FLOW_CONTROL_ERROR, outCode);\n  ASSERT_EQ(0, lastStreamID);\n  ASSERT_FALSE(outDebugData);\n}\n\nTEST_F(HTTP2FramerTest, GoawayDebugData) {\n  string data(\"abcde\");\n  auto debugData = makeBuf(5);\n  memcpy(debugData->writableData(), data.data(), data.length());\n  writeGoaway(queue_, 0, ErrorCode::FLOW_CONTROL_ERROR, std::move(debugData));\n\n  FrameHeader header;\n  uint32_t lastStreamID = 0;\n  ErrorCode outCode{};\n  std::unique_ptr<IOBuf> outDebugData;\n  parse(&parseGoaway, header, lastStreamID, outCode, outDebugData);\n\n  ASSERT_EQ(FrameType::GOAWAY, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameGoawaySize + data.length(), header.length);\n  ASSERT_EQ(ErrorCode::FLOW_CONTROL_ERROR, outCode);\n  ASSERT_EQ(0, lastStreamID);\n  ASSERT_EQ(outDebugData->computeChainDataLength(), data.length());\n  EXPECT_EQ(outDebugData->moveToFbString(), data);\n}\n\n// An invalid error code. Used to test a bug where\n// a macro expansion caused us to read the error code twice\nTEST_F(HTTP2FramerTest, GoawayDoubleRead) {\n  writeFrameHeaderManual(\n      queue_, kFrameGoawaySize, static_cast<uint8_t>(FrameType::GOAWAY), 0, 0);\n\n  QueueAppender appender(&queue_, kFrameGoawaySize);\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(0));\n  // Here's the invalid value:\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(0xffffffff));\n\n  uint32_t outLastStreamID = 0;\n  ErrorCode outCode{};\n  std::unique_ptr<IOBuf> outDebugData;\n  FrameHeader outHeader;\n  Cursor cursor(queue_.front());\n\n  auto ret1 = parseFrameHeader(cursor, outHeader);\n  ASSERT_EQ(ErrorCode::NO_ERROR, ret1);\n  ASSERT_EQ(FrameType::GOAWAY, outHeader.type);\n  auto ret2 =\n      parseGoaway(cursor, outHeader, outLastStreamID, outCode, outDebugData);\n  ASSERT_EQ(ErrorCode::PROTOCOL_ERROR, ret2);\n}\n\nTEST_F(HTTP2FramerTest, BasicRFC9218Priority) {\n  auto pri = httpPriorityToString(HTTPPriority{7, true});\n  writeRFC9218Priority(queue_, 1, pri);\n  FrameHeader header{};\n  std::string priority;\n  uint32_t stream = 0;\n  parse(&parseRFC9218Priority, header, stream, priority);\n\n  ASSERT_EQ(FrameType::RFC9218_PRIORITY, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(1, stream);\n  ASSERT_EQ(\"u=7,i\", priority);\n}\n\nTEST_F(HTTP2FramerTest, BadRFC9218Priority) {\n  auto pri = httpPriorityToString(HTTPPriority{\n      255, true}); // httpPriorityToString() takes the min of the urgency passed\n                   // in and the \"highest\" possible urgency (7)\n  writeRFC9218Priority(queue_, 1, pri);\n  FrameHeader header{};\n  std::string priority;\n  uint32_t stream = 0;\n  parse(&parseRFC9218Priority, header, stream, priority);\n\n  ASSERT_EQ(FrameType::RFC9218_PRIORITY, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(1, stream);\n  ASSERT_EQ(\"u=7,i\", priority);\n}\n\n#if 0\nTEST_F(HTTP2FramerTest, HeadersWithPaddingAndPriority) {\n  auto headersLen = 500;\n  auto body = makeBuf(headersLen);\n  http2::PriorityUpdate pri{0, true, 12};\n  uint8_t padding = 200;\n  auto headerSize = calculatePreHeaderBlockSize(false, true, true);\n  auto fheader = queue_.preallocate(headerSize, 32);\n  queue_.postallocate(headerSize);\n  queue_.append(body->clone());\n  writeHeaders((uint8_t*)fheader.first,\n               fheader.second,\n               queue_,\n               headersLen,\n               1,\n               pri,\n               padding,\n               false,\n               false);\n\n  FrameHeader header;\n  std::unique_ptr<IOBuf> outBuf;\n  parse(&parseHeaders, header, outBuf);\n\n  ASSERT_EQ(FrameType::HEADERS, header.type);\n  ASSERT_EQ(1, header.stream);\n  ASSERT_TRUE(PRIORITY & header.flags);\n  ASSERT_FALSE(END_STREAM & header.flags);\n  ASSERT_FALSE(END_HEADERS & header.flags);\n  EXPECT_EQ(outBuf->moveToFbString(), body->moveToFbString());\n}\n#endif\n\nTEST_F(HTTP2FramerTest, Ping) {\n  uint64_t data = folly::Random::rand64();\n  writePing(queue_, data, false);\n\n  FrameHeader header;\n  uint64_t outData = 0;\n  parse(&parsePing, header, outData);\n\n  ASSERT_EQ(FrameType::PING, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  EXPECT_EQ(data, outData);\n}\n\nTEST_F(HTTP2FramerTest, WindowUpdate) {\n  writeWindowUpdate(queue_, 33, 120);\n\n  FrameHeader header;\n  uint32_t amount = 0;\n  parse(&parseWindowUpdate, header, amount);\n\n  ASSERT_EQ(FrameType::WINDOW_UPDATE, header.type);\n  ASSERT_EQ(33, header.stream);\n  ASSERT_EQ(0, header.flags);\n  EXPECT_EQ(120, amount);\n}\n\nTEST_F(HTTP2FramerTest, CertificateRequest) {\n  string data(\"abcdef\");\n  auto authRequest = makeBuf(6);\n  memcpy(authRequest->writableData(), data.data(), data.length());\n  uint16_t requestId = 120;\n  writeCertificateRequest(queue_, requestId, std::move(authRequest));\n\n  FrameHeader header;\n  uint16_t outRequestId = 0;\n  std::unique_ptr<IOBuf> outAuthRequest;\n  parse(&parseCertificateRequest, header, outRequestId, outAuthRequest);\n\n  ASSERT_EQ(FrameType::CERTIFICATE_REQUEST, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameCertificateRequestSizeBase + data.length(), header.length);\n  ASSERT_EQ(outRequestId, requestId);\n  ASSERT_EQ(outAuthRequest->computeChainDataLength(), data.length());\n  EXPECT_EQ(outAuthRequest->moveToFbString(), data);\n}\n\nTEST_F(HTTP2FramerTest, EmptyCertificateRequest) {\n  uint16_t requestId = 120;\n  writeCertificateRequest(queue_, requestId, nullptr);\n\n  FrameHeader header;\n  uint16_t outRequestId = 0;\n  std::unique_ptr<IOBuf> outAuthRequest;\n  parse(&parseCertificateRequest, header, outRequestId, outAuthRequest);\n\n  ASSERT_EQ(FrameType::CERTIFICATE_REQUEST, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameCertificateRequestSizeBase, header.length);\n  ASSERT_EQ(outRequestId, requestId);\n  ASSERT_FALSE(outAuthRequest);\n}\n\nTEST_F(HTTP2FramerTest, ShortCertificateRequest) {\n  // length field is too short for frame type\n  writeFrameHeaderManual(\n      queue_, 1, static_cast<uint8_t>(FrameType::CERTIFICATE_REQUEST), 0, 0);\n  QueueAppender appender(&queue_, 1);\n  appender.writeBE<uint8_t>(static_cast<uint8_t>(1));\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  uint16_t outRequestId = 0;\n  std::unique_ptr<IOBuf> outAuthRequest;\n  parseFrameHeader(cursor, header);\n  auto ret =\n      parseCertificateRequest(cursor, header, outRequestId, outAuthRequest);\n  ASSERT_EQ(ErrorCode::FRAME_SIZE_ERROR, ret);\n}\n\nTEST_F(HTTP2FramerTest, CertificateRequestOnNonzeroStream) {\n  // received CERTIFICATE_REQUEST frame on nonzero stream\n  writeFrameHeaderManual(\n      queue_, 2, static_cast<uint8_t>(FrameType::CERTIFICATE_REQUEST), 0, 1);\n  QueueAppender appender(&queue_, 1);\n  appender.writeBE<uint16_t>(static_cast<uint16_t>(1));\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  uint16_t outRequestId = 0;\n  std::unique_ptr<IOBuf> outAuthRequest;\n  parseFrameHeader(cursor, header);\n  auto ret =\n      parseCertificateRequest(cursor, header, outRequestId, outAuthRequest);\n  ASSERT_EQ(ErrorCode::PROTOCOL_ERROR, ret);\n}\n\nTEST_F(HTTP2FramerTest, EndingCertificate) {\n  string data(\"abcdef\");\n  auto authenticator = makeBuf(6);\n  memcpy(authenticator->writableData(), data.data(), data.length());\n  uint16_t certId = 120;\n  // The last CERTIFICATE frame containing authenticator fragment.\n  writeCertificate(queue_, certId, std::move(authenticator), false);\n\n  FrameHeader header;\n  uint16_t outCertId = 0;\n  std::unique_ptr<IOBuf> outAuthenticator;\n  parse(&parseCertificate, header, outCertId, outAuthenticator);\n\n  ASSERT_EQ(FrameType::CERTIFICATE, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(kFrameCertificateSizeBase + data.length(), header.length);\n  ASSERT_EQ(outCertId, certId);\n  ASSERT_EQ(outAuthenticator->computeChainDataLength(), data.length());\n  EXPECT_EQ(outAuthenticator->moveToFbString(), data);\n}\n\nTEST_F(HTTP2FramerTest, Certificate) {\n  string data(\"abcdef\");\n  auto authenticator = makeBuf(6);\n  memcpy(authenticator->writableData(), data.data(), data.length());\n  uint16_t certId = 120;\n  // CERTIFICATE frame containing an authenticator fragment but not the last\n  // fragment.\n  writeCertificate(queue_, certId, std::move(authenticator), true);\n\n  FrameHeader header;\n  uint16_t outCertId = 0;\n  std::unique_ptr<IOBuf> outAuthenticator;\n  parse(&parseCertificate, header, outCertId, outAuthenticator);\n\n  ASSERT_EQ(FrameType::CERTIFICATE, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_TRUE(TO_BE_CONTINUED & header.flags);\n  ASSERT_EQ(kFrameCertificateSizeBase + data.length(), header.length);\n  ASSERT_EQ(outCertId, certId);\n  ASSERT_EQ(outAuthenticator->computeChainDataLength(), data.length());\n  EXPECT_EQ(outAuthenticator->moveToFbString(), data);\n}\n\nTEST_F(HTTP2FramerTest, EmptyCertificate) {\n  uint16_t certId = 120;\n  writeCertificate(queue_, certId, nullptr, true);\n\n  FrameHeader header;\n  uint16_t outCertId = 0;\n  std::unique_ptr<IOBuf> outAuthenticator;\n  parse(&parseCertificate, header, outCertId, outAuthenticator);\n\n  ASSERT_EQ(FrameType::CERTIFICATE, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_TRUE(TO_BE_CONTINUED & header.flags);\n  ASSERT_EQ(kFrameCertificateSizeBase, header.length);\n  ASSERT_EQ(outCertId, certId);\n  ASSERT_FALSE(outAuthenticator);\n}\n\nTEST_F(HTTP2FramerTest, ShortCertificate) {\n  // length field is too short for frame type\n  writeFrameHeaderManual(\n      queue_, 1, static_cast<uint8_t>(FrameType::CERTIFICATE), 0, 0);\n  QueueAppender appender(&queue_, 1);\n  appender.writeBE<uint8_t>(static_cast<uint8_t>(1));\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  uint16_t outCertId = 0;\n  std::unique_ptr<IOBuf> outAuthenticator;\n  parseFrameHeader(cursor, header);\n  auto ret = parseCertificate(cursor, header, outCertId, outAuthenticator);\n  ASSERT_EQ(ErrorCode::FRAME_SIZE_ERROR, ret);\n}\n\nTEST_F(HTTP2FramerTest, CertificateOnNonzeroStream) {\n  // received CERTIFICATE frame on nonzero stream\n  writeFrameHeaderManual(\n      queue_, 2, static_cast<uint8_t>(FrameType::CERTIFICATE), 0, 1);\n  QueueAppender appender(&queue_, 1);\n  appender.writeBE<uint16_t>(static_cast<uint16_t>(1));\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  uint16_t outCertId = 0;\n  std::unique_ptr<IOBuf> outAuthenticator;\n  parseFrameHeader(cursor, header);\n  auto ret = parseCertificate(cursor, header, outCertId, outAuthenticator);\n  ASSERT_EQ(ErrorCode::PROTOCOL_ERROR, ret);\n}\n\n// TODO: auto generate this test for all frame types (except DATA)\nTEST_F(HTTP2FramerTest, ShortWindowUpdate) {\n  // length field is too short for frame type\n  writeFrameHeaderManual(\n      queue_, 0, static_cast<uint8_t>(FrameType::WINDOW_UPDATE), 0, 0);\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  uint32_t amount = 0;\n  parseFrameHeader(cursor, header);\n  auto ret2 = parseWindowUpdate(cursor, header, amount);\n  ASSERT_EQ(ErrorCode::FRAME_SIZE_ERROR, ret2);\n}\n\nTEST_F(HTTP2FramerTest, Uint32MaxWindowUpdate) {\n  const uint32_t uint31Max = 0x7fffffff;\n  writeWindowUpdate(queue_, 33, 1); // we will overwrite the update size\n  auto buf = queue_.move();\n  buf->coalesce();\n  // Set update size to unit32_t max\n  for (unsigned i = 0; i < 4; ++i) {\n    buf->writableData()[kFrameHeaderSize + i] = 0xff;\n  }\n\n  FrameHeader header;\n  uint32_t amount = 0;\n  parse(buf.get(), &parseWindowUpdate, header, amount);\n\n  ASSERT_EQ(FrameType::WINDOW_UPDATE, header.type);\n  ASSERT_EQ(33, header.stream);\n  ASSERT_EQ(0, header.flags);\n  // We should ignore the top bit\n  EXPECT_EQ(uint31Max, amount);\n}\n\nTEST_F(HTTP2FramerTest, AltSvc) {\n  string protocol = \"special-proto\";\n  string host = \"special-host\";\n  string origin = \"special-origin\";\n  writeAltSvc(queue_, 2, 150, 8080, protocol, host, origin);\n\n  FrameHeader header;\n  uint32_t outMaxAge = 0;\n  uint32_t outPort = 0;\n  string outProtocol;\n  string outHost;\n  string outOrigin;\n  parse(&parseAltSvc,\n        header,\n        outMaxAge,\n        outPort,\n        outProtocol,\n        outHost,\n        outOrigin);\n\n  ASSERT_EQ(FrameType::ALTSVC, header.type);\n  ASSERT_EQ(2, header.stream);\n  ASSERT_EQ(0, header.flags);\n  EXPECT_EQ(150, outMaxAge);\n  EXPECT_EQ(8080, outPort);\n  EXPECT_EQ(protocol, outProtocol);\n  EXPECT_EQ(host, outHost);\n  EXPECT_EQ(origin, outOrigin);\n}\n\nTEST_F(HTTP2FramerTest, Settings) {\n  const deque<SettingPair> settings = {{SettingsId::HEADER_TABLE_SIZE, 3},\n                                       {SettingsId::MAX_CONCURRENT_STREAMS, 4}};\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<SettingPair> outSettings;\n  parse(&parseSettings, header, outSettings);\n\n  ASSERT_EQ(settings.size() * 6, header.length);\n  ASSERT_EQ(FrameType::SETTINGS, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(settings, outSettings);\n}\n\nTEST_F(HTTP2FramerTest, SettingsAck) {\n  writeSettingsAck(queue_);\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  auto ret = parseFrameHeader(cursor, header);\n  ASSERT_EQ(ErrorCode::NO_ERROR, ret);\n\n  ASSERT_EQ(0, header.length);\n  ASSERT_EQ(FrameType::SETTINGS, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(ACK, header.flags);\n  std::deque<SettingPair> outSettings;\n  EXPECT_EQ(parseSettings(cursor, header, outSettings), ErrorCode::NO_ERROR);\n}\n\nTEST_F(HTTP2FramerTest, BadSettingsAck) {\n  writeSettingsAck(queue_);\n  // Add an extra byte to the frame\n  uint32_t lengthAndType = htonl(1 << 8 | uint8_t(FrameType::SETTINGS));\n  memcpy(((IOBuf*)queue_.front())->writableData(), &lengthAndType, 4);\n  queue_.append(\"o\");\n\n  Cursor cursor(queue_.front());\n  FrameHeader header;\n  auto ret = parseFrameHeader(cursor, header);\n  ASSERT_EQ(ErrorCode::NO_ERROR, ret);\n\n  ASSERT_EQ(FrameType::SETTINGS, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(ACK, header.flags);\n  std::deque<SettingPair> outSettings;\n  EXPECT_EQ(parseSettings(cursor, header, outSettings),\n            ErrorCode::FRAME_SIZE_ERROR);\n}\n\nTEST_F(HTTP2FramerTest, UnknownSetting) {\n  const deque<SettingPair> settings = {{(proxygen::SettingsId)31337, 3},\n                                       {(proxygen::SettingsId)0, 4}};\n  writeSettings(queue_, settings);\n\n  FrameHeader header;\n  std::deque<SettingPair> outSettings;\n  parse(&parseSettings, header, outSettings);\n\n  ASSERT_EQ(settings.size() * 6, header.length);\n  ASSERT_EQ(FrameType::SETTINGS, header.type);\n  ASSERT_EQ(0, header.stream);\n  ASSERT_EQ(0, header.flags);\n  ASSERT_EQ(settings, outSettings);\n}\n\nTEST_F(HTTP2FramerTest, PushPromise) {\n  auto headersLen = 500;\n  auto body = makeBuf(headersLen);\n  uint8_t padding = 255;\n  auto headerSize = calculatePreHeaderBlockSize(22, false, true);\n  auto fheader = queue_.preallocate(headerSize, 32);\n  queue_.postallocate(headerSize);\n  queue_.append(body->clone());\n  writePushPromise((uint8_t*)fheader.first,\n                   fheader.second,\n                   queue_,\n                   21,\n                   22,\n                   headersLen,\n                   padding,\n                   true);\n\n  FrameHeader header;\n  uint32_t promisedStream = 0;\n  std::unique_ptr<IOBuf> outBuf;\n  parse(&parsePushPromise, header, promisedStream, outBuf);\n\n  ASSERT_EQ(FrameType::PUSH_PROMISE, header.type);\n  ASSERT_EQ(21, header.stream);\n  ASSERT_EQ(22, promisedStream);\n  EXPECT_EQ(outBuf->moveToFbString(), body->moveToFbString());\n}\n\nTEST_F(HTTP2FramerTest, Continuation) {\n  auto body = makeBuf(800);\n  writeContinuation(queue_, 1, true, body->clone());\n\n  FrameHeader header;\n  std::unique_ptr<IOBuf> outBuf;\n  parse(&parseContinuation, header, outBuf);\n\n  ASSERT_EQ(FrameType::CONTINUATION, header.type);\n  ASSERT_TRUE(END_HEADERS & header.flags);\n  ASSERT_FALSE(PADDED & header.flags);\n  ASSERT_EQ(1, header.stream);\n  EXPECT_EQ(outBuf->moveToFbString(), body->moveToFbString());\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTP2FramerTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/IOBufQueue.h>\n\n// Writes out the common frame header without checks\nvoid writeFrameHeaderManual(folly::IOBufQueue& queue,\n                            uint32_t length,\n                            uint8_t type,\n                            uint8_t flags,\n                            uint32_t stream);\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTPBinaryCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/HTTPBinaryCodec.h>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\nnamespace proxygen::test {\n\nclass HTTPBinaryCodecForTest : public HTTPBinaryCodec {\n public:\n  explicit HTTPBinaryCodecForTest(TransportDirection direction)\n      : HTTPBinaryCodec{direction} {\n  }\n  explicit HTTPBinaryCodecForTest(TransportDirection direction,\n                                  bool knownLength)\n      : HTTPBinaryCodec{direction, knownLength} {\n  }\n  ParseResult parseFramingIndicator(folly::io::Cursor& cursor,\n                                    bool& request,\n                                    bool& knownLength) {\n    return HTTPBinaryCodec::parseFramingIndicator(cursor, request, knownLength);\n  }\n\n  ParseResult parseRequestControlData(folly::io::Cursor& cursor,\n                                      size_t remaining,\n                                      HTTPMessage& msg) {\n    return HTTPBinaryCodec::parseRequestControlData(cursor, remaining, msg);\n  }\n\n  ParseResult parseResponseControlData(folly::io::Cursor& cursor,\n                                       size_t remaining,\n                                       HTTPMessage& msg) {\n    return HTTPBinaryCodec::parseResponseControlData(cursor, remaining, msg);\n  }\n\n  ParseResult parseHeaders(folly::io::Cursor& cursor,\n                           size_t remaining,\n                           HeaderDecodeInfo& decodeInfo,\n                           bool knownLength) {\n    return HTTPBinaryCodec::parseHeaders(\n        cursor, remaining, decodeInfo, knownLength);\n  }\n\n  ParseResult parseContent(folly::io::Cursor& cursor, size_t remaining) {\n    return HTTPBinaryCodec::parseContent(cursor, remaining);\n  }\n\n  folly::IOBuf& getMsgBody() {\n    return *msgBody_;\n  }\n};\n\ntemplate <TransportDirection dir>\nclass HTTPBinaryCodecTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    binaryCodecKnownLength_ = std::make_unique<HTTPBinaryCodecForTest>(dir);\n    binaryCodecIndeterminateLength_ =\n        std::make_unique<HTTPBinaryCodecForTest>(dir, false /* knownLength */);\n  }\n\n  void TearDown() override {\n  }\n\n  std::unique_ptr<HTTPBinaryCodecForTest> binaryCodecKnownLength_;\n  std::unique_ptr<HTTPBinaryCodecForTest> binaryCodecIndeterminateLength_;\n};\n\nclass HttpBinaryUpstreamCodecTest\n    : public HTTPBinaryCodecTest<TransportDirection::UPSTREAM> {};\n\nclass HttpBinaryDownstreamCodecTest\n    : public HTTPBinaryCodecTest<TransportDirection::DOWNSTREAM> {};\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseFramingIndicatorSuccess) {\n  // Test Known Length Request\n  const std::vector<uint8_t> framingIndicatorKnownRequest{0x00};\n  auto framingIndicatorIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(framingIndicatorKnownRequest.data(),\n                       framingIndicatorKnownRequest.size()));\n  folly::io::Cursor cursor(framingIndicatorIOBuf.get());\n\n  bool request = false;\n  bool knownLength = false;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseFramingIndicator(cursor, request, knownLength)\n                .bytesParsed_,\n            1);\n  EXPECT_EQ(request, true);\n  EXPECT_EQ(knownLength, true);\n\n  // Test Indeterminate Length Response\n  const std::vector<uint8_t> framingIndicatorIndeterminateResponse{0x03};\n  framingIndicatorIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(framingIndicatorIndeterminateResponse.data(),\n                       framingIndicatorIndeterminateResponse.size()));\n  cursor = folly::io::Cursor(framingIndicatorIOBuf.get());\n\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseFramingIndicator(cursor, request, knownLength)\n                .bytesParsed_,\n            1);\n  EXPECT_EQ(request, false);\n  EXPECT_EQ(knownLength, false);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseFramingIndicatorFailure) {\n  // Test Invalid Framing Indicator\n  const std::vector<uint8_t> framingIndicatorInvalidResponse{0x04};\n  auto framingIndicatorIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(framingIndicatorInvalidResponse.data(),\n                       framingIndicatorInvalidResponse.size()));\n  folly::io::Cursor cursor(framingIndicatorIOBuf.get());\n\n  bool request = false;\n  bool knownLength = false;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseFramingIndicator(cursor, request, knownLength)\n                .error_,\n            \"Invalid Framing Indicator: 4\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseRequestControlDataSuccess) {\n  // Format is `*GET*https*www.example.com*/hello.txt` where `*` represents\n  // the length of each subsequent string\n  const std::vector<uint8_t> controlDataRequest{\n      0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73,\n      0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70,\n      0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68, 0x65,\n      0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74};\n  auto controlDataIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(controlDataRequest.data(), controlDataRequest.size()));\n  folly::io::Cursor cursor(controlDataIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(\n      binaryCodecKnownLength_\n          ->parseRequestControlData(cursor, controlDataRequest.size(), msg)\n          .bytesParsed_,\n      controlDataRequest.size());\n  EXPECT_EQ(msg.isSecure(), true);\n  EXPECT_EQ(msg.getMethod(), proxygen::HTTPMethod::GET);\n  EXPECT_EQ(msg.getURL(), \"/hello.txt\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseRequestControlDataWaiting) {\n  // Format is `*GET*https*www.example.com*/hello.txt` where `*` before\n  // /hello.txt is value 11 instead of value 10, which should cause the parsing\n  // to wait for more data\n  const std::vector<uint8_t> controlDataInvalidRequest{\n      0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73,\n      0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70,\n      0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0b, 0x2f, 0x68, 0x65,\n      0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74};\n  auto controlDataIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      controlDataInvalidRequest.data(), controlDataInvalidRequest.size()));\n  folly::io::Cursor cursor(controlDataIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseRequestControlData(\n                    cursor, controlDataInvalidRequest.size(), msg)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseRequestControlDataFailure) {\n  // Format is `*GET*http*www.example.com*/hello.txt` where `*` represents the\n  // length of each subsequent string.\n  const std::vector<uint8_t> controlDataInvalidScheme{\n      0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x74,\n      0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70,\n      0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68, 0x65,\n      0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74};\n  auto controlDataIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      controlDataInvalidScheme.data(), controlDataInvalidScheme.size()));\n  auto cursor = folly::io::Cursor(controlDataIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseRequestControlData(\n                    cursor, controlDataInvalidScheme.size(), msg)\n                .error_,\n            \"Failure to parse: scheme. Should be 'http' or 'https'\");\n\n  // Format is `*GET*https*www.example.com*hello.tx[\\x1]` where `*` represents\n  // the length of each subsequent string.\n  const std::vector<uint8_t> controlDataInvalidPath{\n      0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0f, 0x77,\n      0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63,\n      0x6f, 0x6d, 0x09, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x01};\n  controlDataIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      controlDataInvalidPath.data(), controlDataInvalidPath.size()));\n  cursor = folly::io::Cursor(controlDataIOBuf.get());\n\n  EXPECT_EQ(\n      binaryCodecKnownLength_\n          ->parseRequestControlData(cursor, controlDataInvalidPath.size(), msg)\n          .error_,\n      \"Failure to parse: invalid URL path 'hello.tx\\x1'\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseResponseControlDataSuccess) {\n  // Reponse Code 200 OK\n  folly::IOBufQueue controlDataIOBuf;\n  folly::io::QueueAppender appender(&controlDataIOBuf, 0);\n  auto parsedBytes = quic::encodeQuicInteger(200, [&appender](auto val) {\n                       appender.writeBE(folly::tag<decltype(val)>, val);\n                     }).value();\n  folly::io::Cursor cursor(controlDataIOBuf.front());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseResponseControlData(cursor, parsedBytes, msg)\n                .bytesParsed_,\n            parsedBytes);\n  EXPECT_EQ(msg.getStatusCode(), 200);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseResponseControlDataFailure) {\n  // Invalid Status Code\n  folly::IOBufQueue controlInvalidDataIOBuf;\n  folly::io::QueueAppender appender(&controlInvalidDataIOBuf, 0);\n  auto parsedBytes = quic::encodeQuicInteger(600, [&appender](auto val) {\n                       appender.writeBE(folly::tag<decltype(val)>, val);\n                     }).value();\n  folly::io::Cursor cursor(controlInvalidDataIOBuf.front());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseResponseControlData(cursor, parsedBytes, msg)\n                .error_,\n            \"Invalid response status code: 600\");\n\n  folly::IOBufQueue controlInvalidDataInformationalResponseIOBuf;\n  appender = folly::io::QueueAppender(\n      &controlInvalidDataInformationalResponseIOBuf, 0);\n  parsedBytes = quic::encodeQuicInteger(101, [&appender](auto val) {\n                  appender.writeBE(folly::tag<decltype(val)>, val);\n                }).value();\n  cursor =\n      folly::io::Cursor(controlInvalidDataInformationalResponseIOBuf.front());\n\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseResponseControlData(cursor, parsedBytes, msg)\n                .error_,\n            \"Invalid response status code: 101\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseKnownLengthHeadersSuccess) {\n  // Format is `**user-agent*curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language*en, mi` where `*`\n  // represents the length of the each subsequent string. The first `*` is\n  // actually a Quic Integer that takes 2 bytes (and encodes the length of the\n  // overall header as 108)\n  const std::vector<uint8_t> headers{\n      0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65,\n      0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31,\n      0x36, 0x2e, 0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c,\n      0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65,\n      0x6e, 0x53, 0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c,\n      0x20, 0x7a, 0x6c, 0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33,\n      0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65,\n      0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f,\n      0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67,\n      0x75, 0x61, 0x67, 0x65, 0x06, 0x65, 0x6e, 0x2c, 0x20, 0x6d, 0x69};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(headers.data(), headers.size()));\n  folly::io::Cursor cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseHeaders(\n                    cursor, headers.size(), decodeInfo, /*knownLength=*/true)\n                .bytesParsed_,\n            headers.size());\n\n  HTTPHeaders httpHeaders = decodeInfo.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.exists(\"user-agent\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"host\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"accept-language\"), true);\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"user-agent\"),\n            \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"host\"), \"www.example.com\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"accept-language\"), \"en, mi\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseKnownLengthHeadersEmpty) {\n  const std::vector<uint8_t> invalidHeadersCount{0x00};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(invalidHeadersCount.data(), invalidHeadersCount.size()));\n  folly::io::Cursor cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseHeaders(cursor, 1, decodeInfo, /*knownLength=*/true)\n                .bytesParsed_,\n            1);\n}\nTEST_F(HttpBinaryUpstreamCodecTest, testParseKnownLengthHeadersWaiting) {\n  // Format is `**user-agent*curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language.en, mi` where the `*` after\n  // accept-language is value 7 instead of 6 which should cause parsing to wait\n  // to get more data\n  const std::vector<uint8_t> invalidHeadersLength{\n      0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65,\n      0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31,\n      0x36, 0x2e, 0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c,\n      0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65,\n      0x6e, 0x53, 0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c,\n      0x20, 0x7a, 0x6c, 0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33,\n      0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65,\n      0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f,\n      0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67,\n      0x75, 0x61, 0x67, 0x65, 0x07, 0x65, 0x6e, 0x2c, 0x20, 0x6d, 0x69};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      invalidHeadersLength.data(), invalidHeadersLength.size()));\n  auto cursor = folly::io::Cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseHeaders(cursor,\n                               invalidHeadersLength.size(),\n                               decodeInfo,\n                               /*knownLength=*/true)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n\n  // Format is `**a*b` where the first `*` represents a too long length\n  const std::vector<uint8_t> invalidHeadersUnderflow{\n      0x09, 0x01, 0x61, 0x01, 0x62};\n  headersIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      invalidHeadersUnderflow.data(), invalidHeadersUnderflow.size()));\n  cursor = folly::io::Cursor(headersIOBuf.get());\n\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseHeaders(cursor,\n                               invalidHeadersUnderflow.size(),\n                               decodeInfo,\n                               /*knownLength=*/true)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n\n  // Format is `*` where the first `*` represents an underflowed quic integer\n  const std::vector<uint8_t> invalidHeadersUnderflowQuic{0x99};\n  headersIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      invalidHeadersUnderflowQuic.data(), invalidHeadersUnderflowQuic.size()));\n  cursor = folly::io::Cursor(headersIOBuf.get());\n\n  EXPECT_EQ(binaryCodecKnownLength_\n                ->parseHeaders(cursor,\n                               invalidHeadersUnderflowQuic.size(),\n                               decodeInfo,\n                               /*knownLength=*/true)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testParseIndeterminateLengthHeadersSuccess) {\n  // Format is `*user-agent*curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language*en, mi0` where `*`\n  // represents the length of the each subsequent string. The stream is\n  // terminated by a 0 byte\n  const std::vector<uint8_t> headers{\n      0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,\n      0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e,\n      0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37,\n      0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53,\n      0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a,\n      0x6c, 0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68,\n      0x6f, 0x73, 0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61,\n      0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63,\n      0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61,\n      0x67, 0x65, 0x06, 0x65, 0x6e, 0x2c, 0x20, 0x6d, 0x69, 0x00};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(headers.data(), headers.size()));\n  folly::io::Cursor cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecIndeterminateLength_\n                ->parseHeaders(\n                    cursor, headers.size(), decodeInfo, /*knownLength=*/false)\n                .bytesParsed_,\n            headers.size());\n\n  HTTPHeaders httpHeaders = decodeInfo.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.exists(\"user-agent\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"host\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"accept-language\"), true);\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"user-agent\"),\n            \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"host\"), \"www.example.com\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"accept-language\"), \"en, mi\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseIndeterminateLengthHeadersEmpty) {\n  const std::vector<uint8_t> invalidHeadersCount{0x00};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(invalidHeadersCount.data(), invalidHeadersCount.size()));\n  folly::io::Cursor cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecIndeterminateLength_\n                ->parseHeaders(cursor, 1, decodeInfo, /*knownLength=*/false)\n                .bytesParsed_,\n            1);\n  HTTPHeaders httpHeaders = decodeInfo.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.size(), 0);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testParseIndeterminateLengthHeadersWaiting) {\n  // Format is `*user-agent*curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language*en, mi0` where the\n  // `*` after accept-language is value 7 instead of 6 which should cause\n  // parsing to wait to get more data\n  const std::vector<uint8_t> invalidHeadersLength{\n      0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x34,\n      0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20,\n      0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36,\n      0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c, 0x2f, 0x30,\n      0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c, 0x69, 0x62, 0x2f, 0x31,\n      0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f, 0x77, 0x77,\n      0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,\n      0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 0x61, 0x6e,\n      0x67, 0x75, 0x61, 0x67, 0x65, 0x07, 0x65, 0x6e, 0x2c, 0x20, 0x6d, 0x69};\n  auto headersIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      invalidHeadersLength.data(), invalidHeadersLength.size()));\n  auto cursor = folly::io::Cursor(headersIOBuf.get());\n\n  HeaderDecodeInfo decodeInfo;\n  decodeInfo.init(true /* request */,\n                  false /* isRequestTrailers */,\n                  true /* validate */,\n                  false /* strictValidation */,\n                  false /* allowEmptyPath */);\n  EXPECT_EQ(binaryCodecIndeterminateLength_\n                ->parseHeaders(cursor,\n                               invalidHeadersLength.size(),\n                               decodeInfo,\n                               /*knownLength=*/false)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n\n  // Format is `.a.b` where we don't have a 0 byte at the end\n  const std::vector<uint8_t> invalidHeadersUnderflow{0x01, 0x61, 0x01, 0x62};\n  headersIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      invalidHeadersUnderflow.data(), invalidHeadersUnderflow.size()));\n  cursor = folly::io::Cursor(headersIOBuf.get());\n\n  EXPECT_EQ(binaryCodecIndeterminateLength_\n                ->parseHeaders(cursor,\n                               invalidHeadersUnderflow.size(),\n                               decodeInfo,\n                               /*knownLength=*/false)\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseContentKnownLengthSuccess) {\n  // Format is `*hello`\n  const std::vector<uint8_t> content{\n      0x07, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a};\n  auto contentIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(content.data(), content.size()));\n  folly::io::Cursor cursor(contentIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_->parseContent(cursor, content.size())\n                .bytesParsed_,\n            content.size());\n  EXPECT_EQ(binaryCodecKnownLength_->getMsgBody().to<std::string>(),\n            \"hello\\r\\n\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testParseContentKnownLengthFailure) {\n  // Format is `*hello` where * is value 8 instead of 7\n  const std::vector<uint8_t> contentInvalid{\n      0x08, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a};\n  auto contentIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(contentInvalid.data(), contentInvalid.size()));\n  folly::io::Cursor cursor(contentIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_->parseContent(cursor, contentInvalid.size())\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testParseContentIndeterminateLengthSuccess) {\n  // Format is `*hello0`\n  const std::vector<uint8_t> content{\n      0x07, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a, 0x00};\n  auto contentIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(content.data(), content.size()));\n  folly::io::Cursor cursor(contentIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(\n      binaryCodecIndeterminateLength_->parseContent(cursor, content.size())\n          .bytesParsed_,\n      content.size());\n  EXPECT_EQ(binaryCodecIndeterminateLength_->getMsgBody().to<std::string>(),\n            \"hello\\r\\n\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testParseContentIndeterminateLengthWaiting) {\n  // Format is `*hello` where * is value 8 instead of 7\n  const std::vector<uint8_t> contentInvalid{\n      0x08, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a};\n  auto contentIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(contentInvalid.data(), contentInvalid.size()));\n  folly::io::Cursor cursor(contentIOBuf.get());\n\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecIndeterminateLength_\n                ->parseContent(cursor, contentInvalid.size())\n                .parseResultState_,\n            ParseResultState::WAITING_FOR_MORE_DATA);\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest, testOnIngressSuccess) {\n  // Format is `**GET*https.www.example.com*/hello.txt**user-agent*curl/7.16.3\n  // libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language.en, mi`\n  const std::vector<uint8_t> binaryHTTPMessage{\n      0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0f,\n      0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,\n      0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74,\n      0x78, 0x74, 0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67,\n      0x65, 0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31,\n      0x36, 0x2e, 0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f,\n      0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53,\n      0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c,\n      0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73,\n      0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,\n      0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,\n      0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x06, 0x65, 0x6e,\n      0x2c, 0x20, 0x6d, 0x69, 0x00, 0x00};\n  auto binaryHTTPMessageIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(binaryHTTPMessage.data(), binaryHTTPMessage.size()));\n  folly::io::Cursor cursor(binaryHTTPMessageIOBuf.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBuf);\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was not called for the callback\n  EXPECT_EQ(callback.lastParseError, nullptr);\n\n  // Check msg and header fields\n  EXPECT_EQ(callback.msg->isSecure(), true);\n  EXPECT_EQ(callback.msg->getMethod(), proxygen::HTTPMethod::GET);\n  EXPECT_EQ(callback.msg->getURL(), \"/hello.txt\");\n  HTTPHeaders httpHeaders = callback.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.exists(\"user-agent\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"host\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"accept-language\"), true);\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"user-agent\"),\n            \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"host\"), \"www.example.com\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"accept-language\"), \"en, mi\");\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest, testOnIngressSuccessForControlData) {\n  // Format is `**GET*https*www.example.com*/`\n  const std::vector<uint8_t> binaryHTTPMessage{\n      0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74,\n      0x70, 0x73, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,\n      0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x01, 0x2f};\n  auto binaryHTTPMessageIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(binaryHTTPMessage.data(), binaryHTTPMessage.size()));\n  folly::io::Cursor cursor(binaryHTTPMessageIOBuf.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBuf);\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was not called for the callback\n  EXPECT_EQ(callback.lastParseError, nullptr);\n\n  // Check msg and header fields\n  EXPECT_EQ(callback.msg->isSecure(), true);\n  EXPECT_EQ(callback.msg->getMethod(), proxygen::HTTPMethod::GET);\n  EXPECT_EQ(callback.msg->getURL(), \"/\");\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest,\n       testOnIngressSuccessChunkedOnBoundaryMessage) {\n  // Format is chunk1 = `**GET`, chunk2 =\n  // `*https*www.example.com*/hello.txt**user-agent*curl/7.16.3 libcurl/7.16.3\n  // OpenSSL/0.9.7l zlib/1.2.3.host*www.example.com*accept-language*en, mi`\n  const std::vector<uint8_t> binaryHTTPMessageChunk1{\n      0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70,\n      0x73, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d,\n      0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68,\n      0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74};\n  const std::vector<uint8_t> binaryHTTPMessageChunk2{\n      0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e,\n      0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e,\n      0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e,\n      0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c,\n      0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c, 0x69, 0x62,\n      0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f,\n      0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,\n      0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,\n      0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x06, 0x65, 0x6e, 0x2c, 0x20,\n      0x6d, 0x69, 0x00, 0x00};\n\n  auto binaryHTTPMessageIOBufChunk1 = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      binaryHTTPMessageChunk1.data(), binaryHTTPMessageChunk1.size()));\n  folly::io::Cursor cursor1(binaryHTTPMessageIOBufChunk1.get());\n  auto binaryHTTPMessageIOBufChunk2 = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      binaryHTTPMessageChunk2.data(), binaryHTTPMessageChunk2.size()));\n  folly::io::Cursor cursor2(binaryHTTPMessageIOBufChunk2.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBufChunk1);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBufChunk2);\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was not called for the callback\n  EXPECT_EQ(callback.lastParseError, nullptr);\n\n  // Check msg and header fields\n  EXPECT_EQ(callback.msg->isSecure(), true);\n  EXPECT_EQ(callback.msg->getMethod(), proxygen::HTTPMethod::GET);\n  EXPECT_EQ(callback.msg->getURL(), \"/hello.txt\");\n  HTTPHeaders httpHeaders = callback.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.exists(\"user-agent\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"host\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"accept-language\"), true);\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"user-agent\"),\n            \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"host\"), \"www.example.com\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"accept-language\"), \"en, mi\");\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest, testOnIngressSuccessOneByteChunks) {\n  // Format is `**GET*https.www.example.com*/hello.txt**user-agent*curl/7.16.3\n  // libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language*en, mi`\n  const std::vector<uint8_t> binaryHTTPMessage{\n      0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0f,\n      0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,\n      0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74,\n      0x78, 0x74, 0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67,\n      0x65, 0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31,\n      0x36, 0x2e, 0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f,\n      0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53,\n      0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c,\n      0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73,\n      0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,\n      0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,\n      0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x06, 0x65, 0x6e,\n      0x2c, 0x20, 0x6d, 0x69, 0x00, 0x00};\n  auto binaryHTTPMessageIOBuf = folly::IOBuf::wrapBuffer(\n      folly::ByteRange(binaryHTTPMessage.data(), binaryHTTPMessage.size()));\n  folly::io::Cursor cursor(binaryHTTPMessageIOBuf.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  for (auto& byte : binaryHTTPMessage) {\n    auto stp = folly::IOBuf::wrapBuffer(folly::ByteRange(&byte, 1));\n    binaryCodecKnownLength_->onIngress(*stp);\n  }\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was not called for the callback\n  EXPECT_EQ(callback.lastParseError, nullptr);\n\n  // Check msg and header fields\n  EXPECT_EQ(callback.msg->isSecure(), true);\n  EXPECT_EQ(callback.msg->getMethod(), proxygen::HTTPMethod::GET);\n  EXPECT_EQ(callback.msg->getURL(), \"/hello.txt\");\n  HTTPHeaders httpHeaders = callback.msg->getHeaders();\n  EXPECT_EQ(httpHeaders.exists(\"user-agent\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"host\"), true);\n  EXPECT_EQ(httpHeaders.exists(\"accept-language\"), true);\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"user-agent\"),\n            \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"host\"), \"www.example.com\");\n  EXPECT_EQ(httpHeaders.getSingleOrEmpty(\"accept-language\"), \"en, mi\");\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest, testOnIngressFailureMalformedMessage) {\n  // Format is `**GET*https*www.example.com*/hello.txt**user-agent*curl/7.16.3\n  // libcurl/7.16.3 OpenSSL/0.9.7l\n  // zlib/1.2.3*host*www.example.com*accept-language*en, mi` where the last `*`\n  // is value 7 instead of 6\n  const std::vector<uint8_t> binaryInvalidHTTPMessage{\n      0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0f,\n      0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,\n      0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74,\n      0x78, 0x74, 0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67,\n      0x65, 0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31,\n      0x36, 0x2e, 0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f,\n      0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53,\n      0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c,\n      0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73,\n      0x74, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,\n      0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,\n      0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x07, 0x65, 0x6e,\n      0x2c, 0x20, 0x6d, 0x69};\n  auto binaryHTTPMessageIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      binaryInvalidHTTPMessage.data(), binaryInvalidHTTPMessage.size()));\n  folly::io::Cursor cursor(binaryHTTPMessageIOBuf.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBuf);\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was called with the correct error\n  EXPECT_EQ(std::string(callback.lastParseError.get()->what()),\n            \"Incomplete message received\");\n}\n\nTEST_F(HttpBinaryDownstreamCodecTest, testOnIngressFailureIncompleteMessage) {\n  // Contains only framing indicator\n  const std::vector<uint8_t> binaryInvalidHTTPMessage{0x00};\n  auto binaryHTTPMessageIOBuf = folly::IOBuf::wrapBuffer(folly::ByteRange(\n      binaryInvalidHTTPMessage.data(), binaryInvalidHTTPMessage.size()));\n  folly::io::Cursor cursor(binaryHTTPMessageIOBuf.get());\n\n  FakeHTTPCodecCallback callback;\n  binaryCodecKnownLength_->setCallback(&callback);\n  binaryCodecKnownLength_->onIngress(*binaryHTTPMessageIOBuf);\n  binaryCodecKnownLength_->onIngressEOF();\n\n  // Check onError was called with the correct error\n  EXPECT_EQ(std::string(callback.lastParseError.get()->what()),\n            \"Incomplete message received, either empty or only contains \"\n            \"framing indicator\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testGenerateKnownLengthHeaders) {\n  // Create HTTPMessage and encode it to a buffer\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"GET\");\n  msgEncoded.setSecure(true);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n\n  folly::IOBufQueue writeBuffer;\n  binaryCodecKnownLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Now, decode the HTTPMessage from the buffer and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testGenerateIndeterminateLengthHeaders) {\n  // Create HTTPMessage and encode it to a buffer\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"GET\");\n  msgEncoded.setSecure(true);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n\n  folly::IOBufQueue writeBuffer;\n  binaryCodecIndeterminateLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Now, decode the HTTPMessage from the buffer and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testGenerateKnownLengthBody) {\n  // Create Test Body and encode\n  std::string body = \"Sample Test Body!\";\n  std::unique_ptr<folly::IOBuf> testBody =\n      folly::IOBuf::wrapBuffer(body.data(), body.size());\n\n  folly::IOBufQueue writeBuffer;\n  binaryCodecKnownLength_->generateBody(writeBuffer, 0, std::move(testBody));\n\n  // Decode Test Body and check\n  folly::io::Cursor cursor(writeBuffer.front());\n  HTTPMessage msg;\n  EXPECT_EQ(binaryCodecKnownLength_->parseContent(cursor, 18).bytesParsed_, 18);\n  EXPECT_EQ(binaryCodecKnownLength_->getMsgBody().to<std::string>(),\n            \"Sample Test Body!\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testGenerateIndeterminateLengthBody) {\n  // Create Test Body and encode\n  std::string body = \"Sample Test Body!\";\n  std::unique_ptr<folly::IOBuf> testBody =\n      folly::IOBuf::wrapBuffer(body.data(), body.size());\n\n  folly::IOBufQueue writeBuffer;\n  binaryCodecIndeterminateLength_->generateBody(\n      writeBuffer, 0, std::move(testBody), folly::none, true);\n\n  // Decode Test Body and check\n  folly::io::Cursor cursor(writeBuffer.front());\n  HTTPMessage msg;\n  // 18 bytes + 1 for the Content Terminator\n  EXPECT_EQ(\n      binaryCodecIndeterminateLength_->parseContent(cursor, 19).bytesParsed_,\n      19);\n  EXPECT_EQ(binaryCodecIndeterminateLength_->getMsgBody().to<std::string>(),\n            \"Sample Test Body!\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testEncodeAndDecodeKnownLengthRequest) {\n  // Create full request encode it to a buffer\n  folly::IOBufQueue writeBuffer;\n\n  // Encode Framing Indicator, Control Data, and Headers\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"POST\");\n  msgEncoded.setSecure(false);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n  binaryCodecKnownLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Encode Body\n  std::string body = \"Sample Test Body!\";\n  std::unique_ptr<folly::IOBuf> testBody =\n      folly::IOBuf::wrapBuffer(body.data(), body.size());\n  binaryCodecKnownLength_->generateBody(writeBuffer, 0, std::move(testBody));\n\n  // Encode Trailing Headers\n  std::unique_ptr<HTTPHeaders> trailersEncoded =\n      std::make_unique<HTTPHeaders>();\n  trailersEncoded->set(\"test-trailer\", \"test-trailer-value\");\n  msgEncoded.setTrailers(std::move(trailersEncoded));\n  binaryCodecKnownLength_->generateTrailers(\n      writeBuffer, 0, *msgEncoded.getTrailers());\n\n  // Now, decode the request and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n\n  EXPECT_EQ(callback.data_.move()->to<std::string>(), \"Sample Test Body!\");\n\n  auto trailersDecoded = *callback.msg->getTrailers();\n  EXPECT_EQ(trailersDecoded.size(), 1);\n  EXPECT_EQ(trailersDecoded.exists(\"test-trailer\"), true);\n  EXPECT_EQ(trailersDecoded.getSingleOrEmpty(\"test-trailer\"),\n            \"test-trailer-value\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, testEncodeAndDecodeRequestEmptyBody) {\n  // Create full request encode it to a buffer\n  folly::IOBufQueue writeBuffer;\n\n  // Encode Framing Indicator, Control Data, and Headers\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"POST\");\n  msgEncoded.setSecure(false);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n  binaryCodecKnownLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Encode Empty Body\n  std::unique_ptr<folly::IOBuf> emptyBody;\n  binaryCodecKnownLength_->generateBody(writeBuffer, 0, std::move(emptyBody));\n\n  // Encode Trailing Headers\n  std::unique_ptr<HTTPHeaders> trailersEncoded =\n      std::make_unique<HTTPHeaders>();\n  trailersEncoded->set(\"test-trailer\", \"test-trailer-value\");\n  msgEncoded.setTrailers(std::move(trailersEncoded));\n  binaryCodecKnownLength_->generateTrailers(\n      writeBuffer, 0, *msgEncoded.getTrailers());\n\n  // Now, decode the request and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n\n  auto trailersDecoded = *callback.msg->getTrailers();\n  EXPECT_EQ(trailersDecoded.size(), 1);\n  EXPECT_EQ(trailersDecoded.exists(\"test-trailer\"), true);\n  EXPECT_EQ(trailersDecoded.getSingleOrEmpty(\"test-trailer\"),\n            \"test-trailer-value\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testEncodeAndDecodeIndeterminateLengthRequest) {\n  // Create full request encode it to a buffer\n  folly::IOBufQueue writeBuffer;\n\n  // Encode Framing Indicator, Control Data, and Headers\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"POST\");\n  msgEncoded.setSecure(false);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n  binaryCodecIndeterminateLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Encode Body\n  std::string body = \"Sample Test Body!\";\n  std::unique_ptr<folly::IOBuf> testBody =\n      folly::IOBuf::wrapBuffer(body.data(), body.size());\n  binaryCodecIndeterminateLength_->generateBody(\n      writeBuffer, 0, std::move(testBody), folly::none, true);\n\n  // Encode Trailing Headers\n  std::unique_ptr<HTTPHeaders> trailersEncoded =\n      std::make_unique<HTTPHeaders>();\n  trailersEncoded->set(\"test-trailer\", \"test-trailer-value\");\n  msgEncoded.setTrailers(std::move(trailersEncoded));\n  binaryCodecIndeterminateLength_->generateTrailers(\n      writeBuffer, 0, *msgEncoded.getTrailers());\n\n  // Now, decode the request and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n\n  EXPECT_EQ(callback.data_.move()->to<std::string>(), \"Sample Test Body!\");\n\n  auto trailersDecoded = *callback.msg->getTrailers();\n  EXPECT_EQ(trailersDecoded.size(), 1);\n  EXPECT_EQ(trailersDecoded.exists(\"test-trailer\"), true);\n  EXPECT_EQ(trailersDecoded.getSingleOrEmpty(\"test-trailer\"),\n            \"test-trailer-value\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest,\n       testEncodeAndDecodeIndeterminateLengthRequestEmptyBody) {\n  // Create full request encode it to a buffer\n  folly::IOBufQueue writeBuffer;\n\n  // Encode Framing Indicator, Control Data, and Headers\n  HTTPMessage msgEncoded;\n  msgEncoded.setMethod(\"POST\");\n  msgEncoded.setSecure(false);\n  msgEncoded.setURL(\"/hello.txt\");\n  HTTPHeaders& headersEncoded = msgEncoded.getHeaders();\n  headersEncoded.set(\"user-agent\",\n                     \"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\");\n  headersEncoded.set(\"host\", \"www.example.com\");\n  headersEncoded.set(\"accept-language\", \"en, mi\");\n  binaryCodecIndeterminateLength_->generateHeader(writeBuffer, 0, msgEncoded);\n\n  // Encode Empty Body\n  std::unique_ptr<folly::IOBuf> emptyBody;\n  binaryCodecIndeterminateLength_->generateBody(\n      writeBuffer, 0, std::move(emptyBody), folly::none, true);\n\n  // Encode Trailing Headers\n  std::unique_ptr<HTTPHeaders> trailersEncoded =\n      std::make_unique<HTTPHeaders>();\n  trailersEncoded->set(\"test-trailer\", \"test-trailer-value\");\n  msgEncoded.setTrailers(std::move(trailersEncoded));\n  binaryCodecIndeterminateLength_->generateTrailers(\n      writeBuffer, 0, *msgEncoded.getTrailers());\n\n  // Now, decode the request and check values\n  FakeHTTPCodecCallback callback;\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  downstreamCodec.setCallback(&callback);\n  downstreamCodec.onIngress(*writeBuffer.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(callback.msg->getMethod(), msgEncoded.getMethod());\n  EXPECT_EQ(callback.msg->isSecure(), msgEncoded.isSecure());\n  EXPECT_EQ(callback.msg->getURL(), msgEncoded.getURL());\n  auto headersDecoded = callback.msg->getHeaders();\n  EXPECT_EQ(headersDecoded.size(), headersEncoded.size());\n  headersEncoded.forEach([&headersDecoded](const std::string& headerName,\n                                           const std::string& headerValue) {\n    EXPECT_EQ(headersDecoded.exists(headerName), true);\n    EXPECT_EQ(headersDecoded.getSingleOrEmpty(headerName), headerValue);\n  });\n\n  auto trailersDecoded = *callback.msg->getTrailers();\n  EXPECT_EQ(trailersDecoded.size(), 1);\n  EXPECT_EQ(trailersDecoded.exists(\"test-trailer\"), true);\n  EXPECT_EQ(trailersDecoded.getSingleOrEmpty(\"test-trailer\"),\n            \"test-trailer-value\");\n}\n\nTEST_F(HttpBinaryUpstreamCodecTest, IndeterminateLenReqKnownLengthResp) {\n  folly::IOBufQueue upstreamEgress;\n  HTTPMessage req;\n  req.setURL(\"/\");\n  req.getHeaders().set(\"host\", \"test.com\");\n  // serialize headers + 10-byte body on a codec w/ indeterminate length\n  binaryCodecIndeterminateLength_->generateHeader(\n      upstreamEgress, /*txn=*/0, req);\n  binaryCodecIndeterminateLength_->generateBody(\n      upstreamEgress, /*txn=*/0, /*chain=*/makeBuf(10));\n\n  // generate known length 200/ok resp; parse via upstream codec\n  auto resp = folly::IOBuf::fromString(folly::unhexlify(\"0140c800\"));\n  FakeHTTPCodecCallback upstreamCodecCb;\n  binaryCodecIndeterminateLength_->setCallback(&upstreamCodecCb);\n  binaryCodecIndeterminateLength_->onIngress(*resp);\n  binaryCodecIndeterminateLength_->onIngressEOF();\n  EXPECT_EQ(upstreamCodecCb.msg->getStatusCode(), 200);\n\n  // upstream codec serialize a 10-byte body chunk + eom\n  binaryCodecIndeterminateLength_->generateBody(\n      upstreamEgress, /*txn=*/0, /*chain=*/makeBuf(10));\n  // egress known length still expected to be false => ::generateEOM > 0 bytes\n  EXPECT_GT(\n      binaryCodecIndeterminateLength_->generateEOM(upstreamEgress, /*txn=*/0),\n      0);\n\n  // downstream codec parse upstream req\n  HTTPBinaryCodec downstreamCodec{TransportDirection::DOWNSTREAM};\n  FakeHTTPCodecCallback downstreamCodecCb;\n  downstreamCodec.setCallback(&downstreamCodecCb);\n  downstreamCodec.onIngress(*upstreamEgress.front());\n  downstreamCodec.onIngressEOF();\n\n  EXPECT_EQ(downstreamCodecCb.msg->getURL(), \"/\");\n  EXPECT_EQ(downstreamCodecCb.msg->getHeaders().getSingleOrEmpty(\"host\"),\n            \"test.com\");\n  EXPECT_FALSE(downstreamCodecCb.lastParseError);\n}\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/HTTPParallelCodecTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HTTPParallelCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/utils/Logging.h>\n\nclass HTTPParallelCodecTest : public testing::Test {\n public:\n  HTTPParallelCodecTest(proxygen::HTTPParallelCodec& upstreamCodec,\n                        proxygen::HTTPParallelCodec& downstreamCodec)\n      : upstreamCodec_(upstreamCodec), downstreamCodec_(downstreamCodec) {\n  }\n\n  void SetUp() override {\n    downstreamCodec_.setCallback(&callbacks_);\n    upstreamCodec_.setCallback(&callbacks_);\n    // Most tests are downstream tests, so generate the upstream conn preface\n    // by default\n    upstreamCodec_.generateConnectionPreface(output_);\n  }\n\n  void SetUpUpstreamTest() {\n    output_.move();\n    downstreamCodec_.generateConnectionPreface(output_); // no-op\n    downstreamCodec_.generateSettings(output_);\n  }\n\n  bool parse(std::function<void(folly::IOBuf*)> hackIngress =\n                 std::function<void(folly::IOBuf*)>()) {\n    return parseImpl(downstreamCodec_, hackIngress);\n  }\n\n  bool parseUpstream(std::function<void(folly::IOBuf*)> hackIngress =\n                         std::function<void(folly::IOBuf*)>()) {\n    return parseImpl(upstreamCodec_, hackIngress);\n  }\n\n  /*\n   * hackIngress is used to keep the codec's strict checks while having\n   * separate checks for tests\n   */\n  bool parseImpl(proxygen::HTTPParallelCodec& codec,\n                 std::function<void(folly::IOBuf*)> hackIngress) {\n    dumpToFile(codec.getTransportDirection() ==\n               proxygen::TransportDirection::UPSTREAM);\n    auto ingress = output_.move();\n    if (hackIngress) {\n      hackIngress(ingress.get());\n    }\n    size_t parsed = codec.onIngress(*ingress);\n    return (parsed == ingress->computeChainDataLength());\n  }\n\n  /*\n   * dumpToFile dumps binary frames to files (\"/tmp/http2_*.bin\"),\n   * allowing debugging individual frames.\n   * @note: assign true to dump_ to turn on dumpToFile\n   */\n  void dumpToFile(bool isUpstream = false) {\n    if (!dump_) {\n      return;\n    }\n    auto endpoint = isUpstream ? \"client\" : \"server\";\n    auto filename = folly::to<std::string>(\n        \"/tmp/parallel_\", endpoint, \"_\", testInfo_->name(), \".bin\");\n    proxygen::dumpBinToFile(filename, output_.front());\n  }\n\n protected:\n  proxygen::FakeHTTPCodecCallback callbacks_;\n  proxygen::HTTPParallelCodec& upstreamCodec_;\n  proxygen::HTTPParallelCodec& downstreamCodec_;\n  folly::IOBufQueue output_{folly::IOBufQueue::cacheChainLength()};\n  const testing::TestInfo* testInfo_{\n      testing::UnitTest::GetInstance()->current_test_info()};\n  bool dump_{false};\n};\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/MockHTTPCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\n#include <memory>\n\nnamespace proxygen {\n\n#if defined(__clang__) && __clang_major__ >= 3 && __clang_minor__ >= 6\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Winconsistent-missing-override\"\n#endif\n\nclass MockHTTPCodec : public HTTPCodec {\n public:\n  MOCK_METHOD(CodecProtocol, getProtocol, (), (const));\n  MOCK_METHOD(const std::string&, getUserAgent, (), (const));\n  MOCK_METHOD(TransportDirection, getTransportDirection, (), (const));\n  MOCK_METHOD(bool, supportsStreamFlowControl, (), (const));\n  MOCK_METHOD(bool, supportsSessionFlowControl, (), (const));\n  MOCK_METHOD(HTTPCodec::StreamID, createStream, ());\n  MOCK_METHOD(void, setCallback, (Callback*));\n  MOCK_METHOD(bool, isBusy, (), (const));\n  MOCK_METHOD(bool, hasPartialTransaction, (), (const));\n  MOCK_METHOD(void, setParserPaused, (bool));\n  MOCK_METHOD(bool, isParserPaused, (), (const));\n  MOCK_METHOD(size_t, onIngress, (const folly::IOBuf&));\n  MOCK_METHOD(void, onIngressEOF, ());\n  MOCK_METHOD(bool, isReusable, (), (const));\n  MOCK_METHOD(bool, isWaitingToDrain, (), (const));\n  MOCK_METHOD(bool, closeOnEgressComplete, (), (const));\n  MOCK_METHOD(bool, supportsParallelRequests, (), (const));\n  MOCK_METHOD(bool, supportsPushTransactions, (), (const));\n  MOCK_METHOD(void,\n              generateHeader,\n              (folly::IOBufQueue&,\n               HTTPCodec::StreamID,\n               const HTTPMessage&,\n               bool eom,\n               HTTPHeaderSize*,\n               const folly::Optional<HTTPHeaders>&));\n  MOCK_METHOD(void,\n              generatePushPromise,\n              (folly::IOBufQueue&,\n               HTTPCodec::StreamID,\n               const HTTPMessage&,\n               HTTPCodec::StreamID,\n               bool eom,\n               HTTPHeaderSize*));\n  MOCK_METHOD(size_t,\n              generateBody,\n              (folly::IOBufQueue&,\n               HTTPCodec::StreamID,\n               std::shared_ptr<folly::IOBuf>,\n               folly::Optional<uint8_t>,\n               bool));\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      HTTPCodec::StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override {\n    return generateBody(writeBuf,\n                        stream,\n                        std::shared_ptr<folly::IOBuf>(chain.release()),\n                        padding,\n                        eom);\n  }\n  MOCK_METHOD(size_t,\n              generateChunkHeader,\n              (folly::IOBufQueue&, HTTPCodec::StreamID, size_t));\n  MOCK_METHOD(size_t,\n              generateChunkTerminator,\n              (folly::IOBufQueue&, HTTPCodec::StreamID));\n  MOCK_METHOD(size_t,\n              generateTrailers,\n              (folly::IOBufQueue&, HTTPCodec::StreamID, const HTTPHeaders&));\n  MOCK_METHOD(size_t,\n              generatePadding,\n              (folly::IOBufQueue&, HTTPCodec::StreamID, uint16_t));\n  MOCK_METHOD(size_t, generateEOM, (folly::IOBufQueue&, HTTPCodec::StreamID));\n  MOCK_METHOD(size_t,\n              generateRstStream,\n              (folly::IOBufQueue&, HTTPCodec::StreamID, ErrorCode));\n  MOCK_METHOD(\n      size_t,\n      generateGoaway,\n      (folly::IOBufQueue&, StreamID, ErrorCode, std::shared_ptr<folly::IOBuf>));\n  size_t generateGoaway(folly::IOBufQueue& writeBuf,\n                        StreamID lastStream,\n                        ErrorCode statusCode,\n                        std::unique_ptr<folly::IOBuf> debugData) override {\n    return generateGoaway(writeBuf,\n                          lastStream,\n                          statusCode,\n                          std::shared_ptr<folly::IOBuf>(debugData.release()));\n  }\n\n  MOCK_METHOD(size_t, generatePingRequest, (folly::IOBufQueue&));\n  size_t generatePingRequest(folly::IOBufQueue& writeBuf,\n                             folly::Optional<uint64_t> /* data */) override {\n    return generatePingRequest(writeBuf);\n  }\n\n  MOCK_METHOD(size_t, generatePingReply, (folly::IOBufQueue&, uint64_t));\n  MOCK_METHOD(size_t, generateSettings, (folly::IOBufQueue&));\n  MOCK_METHOD(size_t, generateSettingsAck, (folly::IOBufQueue&));\n  MOCK_METHOD(size_t,\n              generateWindowUpdate,\n              (folly::IOBufQueue&, StreamID, uint32_t));\n  MOCK_METHOD(size_t,\n              generateCertificateRequest,\n              (folly::IOBufQueue&, uint16_t, std::shared_ptr<folly::IOBuf>));\n  size_t generateCertificateRequest(\n      folly::IOBufQueue& writeBuf,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> authRequest) override {\n    return generateCertificateRequest(\n        writeBuf,\n        requestId,\n        std::shared_ptr<folly::IOBuf>(authRequest.release()));\n  }\n  MOCK_METHOD(size_t,\n              generateCertificate,\n              (folly::IOBufQueue&, uint16_t, std::shared_ptr<folly::IOBuf>));\n  size_t generateCertificate(\n      folly::IOBufQueue& writeBuf,\n      uint16_t certId,\n      std::unique_ptr<folly::IOBuf> authenticator) override {\n    return generateCertificate(\n        writeBuf,\n        certId,\n        std::shared_ptr<folly::IOBuf>(authenticator.release()));\n  }\n  MOCK_METHOD(HTTPSettings*, getEgressSettings, ());\n  MOCK_METHOD(const HTTPSettings*, getIngressSettings, (), (const));\n  MOCK_METHOD(void, enableDoubleGoawayDrain, ());\n  MOCK_METHOD(uint32_t, getDefaultWindowSize, (), (const));\n  MOCK_METHOD(size_t,\n              addPriorityNodes,\n              (PriorityQueue&, folly::IOBufQueue&, uint8_t));\n  MOCK_METHOD(HTTPCodec::StreamID, mapPriorityToDependency, (uint8_t), (const));\n};\n\nclass MockHTTPCodecCallback : public HTTPCodec::Callback {\n public:\n  MOCK_METHOD(void, onMessageBegin, (HTTPCodec::StreamID, HTTPMessage*));\n  MOCK_METHOD(void,\n              onPushMessageBegin,\n              (HTTPCodec::StreamID, HTTPCodec::StreamID, HTTPMessage*));\n  MOCK_METHOD(void,\n              onHeadersComplete,\n              (HTTPCodec::StreamID, std::shared_ptr<HTTPMessage>));\n  void onHeadersComplete(HTTPCodec::StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override {\n    onHeadersComplete(stream, std::shared_ptr<HTTPMessage>(msg.release()));\n  }\n  MOCK_METHOD(void,\n              onBody,\n              (HTTPCodec::StreamID, std::shared_ptr<folly::IOBuf>, uint8_t));\n  void onBody(HTTPCodec::StreamID stream,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override {\n    onBody(stream, std::shared_ptr<folly::IOBuf>(chain.release()), padding);\n  }\n  MOCK_METHOD(void, onChunkHeader, (HTTPCodec::StreamID, size_t));\n  MOCK_METHOD(void, onChunkComplete, (HTTPCodec::StreamID));\n  MOCK_METHOD(void,\n              onTrailersComplete,\n              (HTTPCodec::StreamID, std::shared_ptr<HTTPHeaders>));\n  void onTrailersComplete(HTTPCodec::StreamID stream,\n                          std::unique_ptr<HTTPHeaders> trailers) override {\n    onTrailersComplete(stream,\n                       std::shared_ptr<HTTPHeaders>(trailers.release()));\n  }\n  MOCK_METHOD(void, onMessageComplete, (HTTPCodec::StreamID, bool));\n  MOCK_METHOD(void,\n              onError,\n              (HTTPCodec::StreamID, std::shared_ptr<HTTPException>, bool));\n  void onError(HTTPCodec::StreamID stream,\n               const HTTPException& exc,\n               bool newStream) override {\n    onError(stream, std::make_shared<HTTPException>(exc), newStream);\n  }\n  MOCK_METHOD(void,\n              onFrameHeader,\n              (uint64_t, uint8_t, uint64_t, uint64_t, uint16_t));\n  MOCK_METHOD(void, onAbort, (HTTPCodec::StreamID, ErrorCode));\n  MOCK_METHOD(void,\n              onGoaway,\n              (uint64_t, ErrorCode, std::shared_ptr<folly::IOBuf>));\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData) override {\n    onGoaway(lastGoodStreamID,\n             code,\n             std::shared_ptr<folly::IOBuf>(debugData.release()));\n  }\n  MOCK_METHOD(void, onUnknownFrame, (uint64_t, uint64_t));\n  MOCK_METHOD(void, onPingRequest, (uint64_t));\n  MOCK_METHOD(void, onPingReply, (uint64_t));\n  MOCK_METHOD(void, onWindowUpdate, (HTTPCodec::StreamID, uint32_t));\n  MOCK_METHOD(void, onSettings, (const SettingsList&));\n  MOCK_METHOD(void, onSettingsAck, ());\n  MOCK_METHOD(void,\n              onPriority,\n              (HTTPCodec::StreamID, const HTTPMessage::HTTP2Priority&));\n  MOCK_METHOD(void, onPriority, (HTTPCodec::StreamID, const HTTPPriority&));\n  MOCK_METHOD(void,\n              onCertificateRequest,\n              (uint16_t, std::shared_ptr<folly::IOBuf>));\n  void onCertificateRequest(\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> certRequestData) override {\n    onCertificateRequest(\n        requestId, std::shared_ptr<folly::IOBuf>(certRequestData.release()));\n  }\n  MOCK_METHOD(void, onCertificate, (uint16_t, std::shared_ptr<folly::IOBuf>));\n  void onCertificate(uint16_t certId,\n                     std::unique_ptr<folly::IOBuf> certData) override {\n    onCertificate(certId, std::shared_ptr<folly::IOBuf>(certData.release()));\n  }\n  MOCK_METHOD(void,\n              onGenerateFrameHeader,\n              (HTTPCodec::StreamID, uint8_t, uint64_t, uint16_t));\n  MOCK_METHOD(uint32_t, numOutgoingStreams, (), (const));\n  MOCK_METHOD(uint32_t, numIncomingStreams, (), (const));\n};\n\n#if defined(__clang__) && __clang_major__ >= 3 && __clang_minor__ >= 6\n#pragma clang diagnostic pop\n#endif\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/TestUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n\n#include <folly/Random.h>\n#include <folly/io/Cursor.h>\n\nusing namespace folly::io;\nusing namespace std;\nusing namespace testing;\n\nnamespace proxygen {\n\nconst HTTPSettings kDefaultIngressSettings{\n    {SettingsId::INITIAL_WINDOW_SIZE, 65536}};\n\nstd::unique_ptr<folly::IOBuf> makeBuf(uint32_t size) {\n  auto out = folly::IOBuf::create(size);\n  out->append(size);\n  // fill with random junk\n  RWPrivateCursor cursor(out.get());\n  while (cursor.length() >= 8) {\n    cursor.write<uint64_t>(folly::Random::rand64());\n  }\n  while (cursor.length()) {\n    cursor.write<uint8_t>((uint8_t)folly::Random::rand32());\n  }\n  return out;\n}\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeMockParallelCodec(\n    TransportDirection dir) {\n  auto codec = std::make_unique<testing::NiceMock<MockHTTPCodec>>();\n  EXPECT_CALL(*codec, supportsParallelRequests())\n      .WillRepeatedly(testing::Return(true));\n  EXPECT_CALL(*codec, getProtocol())\n      .WillRepeatedly(testing::Return(CodecProtocol::HTTP_2));\n  EXPECT_CALL(*codec, isReusable()).WillRepeatedly(testing::Return(true));\n  EXPECT_CALL(*codec, getTransportDirection())\n      .WillRepeatedly(testing::Return(dir));\n  EXPECT_CALL(*codec, getIngressSettings())\n      .WillRepeatedly(testing::Return(&kDefaultIngressSettings));\n  return codec;\n}\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>>\nmakeDownstreamParallelCodec() {\n  return makeMockParallelCodec(TransportDirection::DOWNSTREAM);\n}\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeUpstreamParallelCodec() {\n  return makeMockParallelCodec(TransportDirection::UPSTREAM);\n}\n\nHTTPMessage getGetRequest(const std::string& url) {\n  HTTPMessage req;\n  req.setMethod(\"GET\");\n  req.setURL(url);\n  req.setHTTPVersion(1, 1);\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  return req;\n}\n\nHTTPMessage getBigGetRequest(const std::string& url) {\n  HTTPMessage req;\n  req.setMethod(\"GET\");\n  req.setURL(url);\n  req.setHTTPVersion(1, 1);\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  req.getHeaders().add(HTTP_HEADER_USER_AGENT, \"coolio\");\n  req.getHeaders().add(\"x-huge-header\",\n                       std::string(http2::kMaxFramePayloadLengthMin, '!'));\n  return req;\n}\n\nstd::unique_ptr<HTTPMessage> makeGetRequest() {\n  return std::make_unique<HTTPMessage>(getGetRequest());\n}\n\nHTTPMessage getPostRequest(uint32_t contentLength) {\n  HTTPMessage req;\n  req.setMethod(\"POST\");\n  req.setURL<string>(\"/\");\n  req.setHTTPVersion(1, 1);\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  req.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH,\n                       folly::to<string>(contentLength));\n  return req;\n}\n\nHTTPMessage getChunkedPostRequest() {\n  HTTPMessage req;\n  req.setMethod(\"POST\");\n  req.setURL<string>(\"/\");\n  req.setHTTPVersion(1, 1);\n  req.setIsChunked(true);\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  req.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  return req;\n}\n\nstd::unique_ptr<HTTPMessage> makePostRequest(uint32_t contentLength) {\n  return std::make_unique<HTTPMessage>(getPostRequest(contentLength));\n}\n\nHTTPMessage getPubRequest(const std::string& url) {\n  HTTPMessage req;\n  req.setMethod(\"PUB\");\n  req.setURL(url);\n  req.setHTTPVersion(1, 1);\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  return req;\n}\n\nHTTPMessage getResponse(uint32_t code, uint32_t bodyLen) {\n  HTTPMessage resp;\n  resp.setStatusCode(code);\n  if (bodyLen > 0) {\n    resp.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH,\n                          folly::to<string>(bodyLen));\n  }\n  return resp;\n}\n\nstd::unique_ptr<HTTPMessage> makeResponse(uint16_t statusCode) {\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(statusCode);\n  resp->setHTTPVersion(1, 1);\n  return resp;\n}\n\nstd::tuple<std::unique_ptr<HTTPMessage>, std::unique_ptr<folly::IOBuf>>\nmakeResponse(uint16_t statusCode, size_t len) {\n  auto resp = makeResponse(statusCode);\n  resp->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, folly::to<string>(len));\n  return std::make_pair(std::move(resp), makeBuf(len));\n}\n\nHTTPMessage getUpgradeRequest(const std::string& upgradeHeader,\n                              HTTPMethod method,\n                              uint32_t bodyLen) {\n  HTTPMessage req = getGetRequest();\n  req.setMethod(method);\n  req.getHeaders().set(HTTP_HEADER_UPGRADE, upgradeHeader);\n  if (bodyLen > 0) {\n    req.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH,\n                         folly::to<std::string>(bodyLen));\n  }\n  return req;\n}\n\nHTTPMessage getResponseWithInvalidBodyLength() {\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  auto bodyLen = \"invalid\";\n  resp.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, bodyLen);\n  return resp;\n}\n\nbool isH3GreaseId(uint64_t id) {\n  if (id < 0x21 || id > 0x3FFFFFFFFFFFFFFF) {\n    return false;\n  }\n  return (((id - 0x21) % 0x1F) == 0);\n}\n\nvoid fakeMockCodec(MockHTTPCodec& codec) {\n  // For each generate* function, write some data to the chain\n  EXPECT_CALL(codec, generateHeader(_, _, _, _, _, _))\n      .WillRepeatedly(Invoke(\n          [](folly::IOBufQueue& writeBuf,\n             HTTPCodec::StreamID /*stream*/,\n             const HTTPMessage& /*msg*/,\n             bool /*eom*/,\n             HTTPHeaderSize* /*size*/,\n             folly::Optional<HTTPHeaders>) { writeBuf.append(makeBuf(10)); }));\n\n  EXPECT_CALL(codec, generatePushPromise(_, _, _, _, _, _))\n      .WillRepeatedly(Invoke(\n          [](folly::IOBufQueue& writeBuf,\n             HTTPCodec::StreamID /*stream*/,\n             const HTTPMessage& /*msg*/,\n             HTTPCodec::StreamID /*assocStream*/,\n             bool /*eom*/,\n             HTTPHeaderSize* /*size*/) { writeBuf.append(makeBuf(10)); }));\n\n  EXPECT_CALL(codec, generateBody(_, _, _, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                HTTPCodec::StreamID /*stream*/,\n                                std::shared_ptr<folly::IOBuf> chain,\n                                folly::Optional<uint8_t> /*padding*/,\n                                bool /*eom*/) {\n        auto len = chain->computeChainDataLength();\n        writeBuf.append(chain->clone());\n        return len;\n      }));\n\n  EXPECT_CALL(codec, generateChunkHeader(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                HTTPCodec::StreamID /*stream*/,\n                                size_t length) {\n        writeBuf.append(makeBuf(length));\n        return length;\n      }));\n\n  EXPECT_CALL(codec, generateChunkTerminator(_, _))\n      .WillRepeatedly(Invoke(\n          [](folly::IOBufQueue& writeBuf, HTTPCodec::StreamID /*stream*/) {\n            writeBuf.append(makeBuf(4));\n            return 4;\n          }));\n\n  EXPECT_CALL(codec, generateTrailers(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                HTTPCodec::StreamID /*stream*/,\n                                const HTTPHeaders& /*trailers*/) {\n        writeBuf.append(makeBuf(30));\n        return 30;\n      }));\n\n  EXPECT_CALL(codec, generateEOM(_, _))\n      .WillRepeatedly(Invoke(\n          [](folly::IOBufQueue& writeBuf, HTTPCodec::StreamID /*stream*/) {\n            writeBuf.append(makeBuf(6));\n            return 6;\n          }));\n\n  EXPECT_CALL(codec, generateRstStream(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                HTTPCodec::StreamID /*stream*/,\n                                ErrorCode /*code*/) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generateGoaway(_, _, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                uint32_t /*lastStream*/,\n                                ErrorCode,\n                                std::shared_ptr<folly::IOBuf>) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generatePingRequest(_))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generatePingReply(_, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf, uint64_t /*id*/) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generateSettings(_))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generateWindowUpdate(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                HTTPCodec::StreamID /*stream*/,\n                                uint32_t /*delta*/) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generateCertificateRequest(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                uint16_t /*requestId*/,\n                                std::shared_ptr<folly::IOBuf>) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n\n  EXPECT_CALL(codec, generateCertificate(_, _, _))\n      .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,\n                                uint16_t /*certId*/,\n                                std::shared_ptr<folly::IOBuf>) {\n        writeBuf.append(makeBuf(6));\n        return 6;\n      }));\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/test/TestUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/utils/TestUtils.h>\n\nnamespace proxygen {\n\nbool isH3GreaseId(uint64_t id);\n\n/**\n * parse the input data using codec, using atOnce to determine how much data\n * should go through the parser at one time\n *\n * atOnce < 0: use random chunk lengths\n * atOnce = 0: single chunk\n * atOnce > 0: use specified chunk length\n */\ntemplate <class T>\nsize_t parse(\n    T* codec,\n    const uint8_t* inputData,\n    uint32_t length,\n    int32_t atOnce = 0,\n    std::function<bool()> stopFn = [] { return false; }) {\n\n  const uint8_t* start = inputData;\n  size_t consumed = 0;\n  std::uniform_int_distribution<uint32_t> lenDistribution(1, length / 2 + 1);\n  std::mt19937 rng;\n\n  if (atOnce == 0) {\n    atOnce = length;\n  }\n\n  folly::IOBufQueue input(folly::IOBufQueue::cacheChainLength());\n\n  // allow testing of error case for length 0\n  if (length == 0) {\n    input.append(folly::IOBuf::copyBuffer(start, length));\n    return codec->onIngress(*input.front());\n  }\n\n  while (length > 0 && !stopFn()) {\n    if (consumed == 0) {\n      // Parser wants more data\n      uint32_t len = atOnce;\n      if (atOnce < 0) {\n        // use random chunks\n        len = lenDistribution(rng);\n      }\n      uint32_t chunkLen = std::min(length, len);\n      input.append(folly::IOBuf::copyBuffer(start, chunkLen));\n      start += chunkLen;\n      length -= chunkLen;\n    }\n    consumed = codec->onIngress(*input.front());\n    input.split(consumed);\n    if (input.front() == nullptr && consumed > 0) {\n      consumed = 0;\n    }\n  }\n  return input.chainLength();\n}\n\ntemplate <class T>\nsize_t parseUnidirectional(\n    T* codec,\n    const uint8_t* inputData,\n    uint32_t length,\n    int32_t atOnce = 0,\n    std::function<bool()> stopFn = [] { return false; }) {\n\n  const uint8_t* start = inputData;\n  size_t consumed = 0;\n  std::uniform_int_distribution<uint32_t> lenDistribution(1, length / 2 + 1);\n  std::mt19937 rng;\n\n  if (atOnce == 0) {\n    atOnce = length;\n  }\n\n  folly::IOBufQueue input(folly::IOBufQueue::cacheChainLength());\n  while (length > 0 && !stopFn()) {\n    if (consumed == 0) {\n      // Parser wants more data\n      uint32_t len = atOnce;\n      if (atOnce < 0) {\n        // use random chunks\n        len = lenDistribution(rng);\n      }\n      uint32_t chunkLen = std::min(length, len);\n      input.append(folly::IOBuf::copyBuffer(start, chunkLen));\n      start += chunkLen;\n      length -= chunkLen;\n    }\n    auto initialLength = input.chainLength();\n    auto ret = codec->onUnidirectionalIngress(input.move());\n    input.append(std::move(ret));\n    consumed = initialLength - input.chainLength();\n    if (input.front() == nullptr && consumed > 0) {\n      consumed = 0;\n    }\n  }\n  return input.chainLength();\n}\n\nclass FakeHTTPCodecCallback : public HTTPCodec::Callback {\n public:\n  FakeHTTPCodecCallback() = default;\n\n  void onMessageBegin(HTTPCodec::StreamID /*stream*/, HTTPMessage*) override {\n    messageBegin++;\n  }\n  void onPushMessageBegin(HTTPCodec::StreamID pushPromiseId,\n                          HTTPCodec::StreamID assocStream,\n                          HTTPMessage*) override {\n    messageBegin++;\n    pushId = pushPromiseId;\n    assocStreamId = assocStream;\n  }\n  void onHeadersComplete(HTTPCodec::StreamID stream,\n                         std::unique_ptr<HTTPMessage> inMsg) override {\n    headersComplete++;\n    headersCompleteId = stream;\n    msg = std::move(inMsg);\n  }\n  void onBody(HTTPCodec::StreamID /*stream*/,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override {\n    bodyCalls++;\n    paddingBytes += padding;\n    bodyLength += chain->computeChainDataLength();\n    data_.append(std::move(chain));\n  }\n  void onChunkHeader(HTTPCodec::StreamID /*stream*/,\n                     size_t /*length*/) override {\n    chunkHeaders++;\n  }\n  void onChunkComplete(HTTPCodec::StreamID /*stream*/) override {\n    chunkComplete++;\n  }\n  void onTrailersComplete(HTTPCodec::StreamID /*stream*/,\n                          std::unique_ptr<HTTPHeaders> inTrailers) override {\n    trailers++;\n    if (msg) {\n      msg->setTrailers(std::move(inTrailers));\n    }\n  }\n  void onMessageComplete(HTTPCodec::StreamID /*stream*/,\n                         bool /*upgrade*/) override {\n    messageComplete++;\n  }\n  void onError(HTTPCodec::StreamID stream,\n               const HTTPException& error,\n               bool /*newStream*/) override {\n    if (stream != sessionStreamId) {\n      streamErrors++;\n    } else {\n      sessionErrors++;\n    }\n    lastParseError = std::make_unique<HTTPException>(error);\n  }\n\n  void onAbort(HTTPCodec::StreamID /*stream*/, ErrorCode code) override {\n    ++aborts;\n    lastErrorCode = code;\n  }\n\n  void onFrameHeader(HTTPCodec::StreamID /*streamId*/,\n                     uint8_t /*flags*/,\n                     uint64_t /*length*/,\n                     uint64_t /*type*/,\n                     uint16_t /*version*/) override {\n    ++headerFrames;\n  }\n\n  void onGoaway(uint64_t lastStreamId,\n                ErrorCode,\n                std::unique_ptr<folly::IOBuf> debugData) override {\n    ++goaways;\n    goawayStreamIds.emplace_back(lastStreamId);\n    data_.append(std::move(debugData));\n  }\n\n  void onUnknownFrame(uint64_t /*streamId*/, uint64_t frameType) override {\n    ++unknownFrames;\n    if (isH3GreaseId(frameType)) {\n      ++greaseFrames;\n    }\n  }\n\n  void onPingRequest(uint64_t data) override {\n    recvPingRequest = data;\n  }\n\n  void onPingReply(uint64_t data) override {\n    recvPingReply = data;\n  }\n\n  void onPriority(HTTPCodec::StreamID, const HTTPPriority& pri) override {\n    urgency = pri.urgency;\n    incremental = pri.incremental;\n  }\n\n  void onWindowUpdate(HTTPCodec::StreamID stream, uint32_t amount) override {\n    windowUpdateCalls++;\n    windowUpdates[stream].push_back(amount);\n  }\n\n  void onSettings(const SettingsList& inSettings) override {\n    settings++;\n    numSettings += inSettings.size();\n    for (auto& setting : inSettings) {\n      if (setting.id == SettingsId::INITIAL_WINDOW_SIZE) {\n        windowSize = setting.value;\n      } else if (setting.id == SettingsId::MAX_CONCURRENT_STREAMS) {\n        maxStreams = setting.value;\n      } else if (setting.id == SettingsId::_HQ_DATAGRAM) {\n        datagramEnabled = setting.value;\n      } else if (setting.id == SettingsId::WT_INITIAL_MAX_DATA) {\n        wtInitialMaxData = setting.value;\n      }\n    }\n  }\n\n  void onSettingsAck() override {\n    settingsAcks++;\n  }\n\n  void onCertificateRequest(\n      uint16_t requestId, std::unique_ptr<folly::IOBuf> authRequest) override {\n    certificateRequests++;\n    lastCertRequestId = requestId;\n    data_.append(std::move(authRequest));\n  }\n\n  void onCertificate(uint16_t certId,\n                     std::unique_ptr<folly::IOBuf> authenticator) override {\n    certificates++;\n    lastCertId = certId;\n    data_.append(std::move(authenticator));\n  }\n\n  uint32_t numOutgoingStreams() const override {\n    return 0;\n  }\n\n  uint32_t numIncomingStreams() const override {\n    return messageBegin;\n  }\n\n  void expectMessage(bool eom,\n                     int32_t headerCount,\n                     const std::string& url) const {\n    expectMessageHelper(eom, headerCount, url, -1);\n  }\n  void expectMessage(bool eom, int32_t headerCount, int32_t statusCode) const {\n    expectMessageHelper(eom, headerCount, \"\", statusCode);\n  }\n\n  void expectMessageHelper(bool eom,\n                           int32_t headerCount,\n                           const std::string& url,\n                           int32_t statusCode) const {\n    EXPECT_EQ(messageBegin, 1);\n    EXPECT_EQ(headersComplete, 1);\n    EXPECT_EQ(messageComplete, eom ? 1 : 0);\n    EXPECT_EQ(streamErrors, 0);\n    EXPECT_EQ(sessionErrors, 0);\n    EXPECT_NE(msg, nullptr);\n    if (headerCount >= 0) {\n      EXPECT_EQ(msg->getHeaders().size(), headerCount);\n    }\n    if (!url.empty()) {\n      EXPECT_EQ(msg->getURL(), url);\n    } else if (statusCode > 0) {\n      if (msg->isResponse()) {\n        EXPECT_EQ(msg->getStatusCode(), statusCode);\n      } else {\n        EXPECT_EQ(msg->getPushStatusCode(), statusCode);\n      }\n    }\n  }\n\n  bool sessionError() const {\n    return sessionErrors > 0;\n  }\n\n  std::function<bool()> getStopFn() {\n    return [this] { return sessionError(); };\n  }\n\n  void setSessionStreamId(HTTPCodec::StreamID streamId) {\n    sessionStreamId = streamId;\n  }\n\n  void reset() {\n    headersCompleteId = 0;\n    assocStreamId = 0;\n    pushId = 0;\n    controlStreamId = 0;\n    isUnidirectional = false;\n    messageBegin = 0;\n    headersComplete = 0;\n    messageComplete = 0;\n    bodyCalls = 0;\n    bodyLength = 0;\n    paddingBytes = 0;\n    chunkHeaders = 0;\n    chunkComplete = 0;\n    trailers = 0;\n    aborts = 0;\n    goaways = 0;\n    sessionErrors = 0;\n    streamErrors = 0;\n    recvPingRequest = 0;\n    recvPingReply = 0;\n    windowUpdateCalls = 0;\n    settings = 0;\n    numSettings = 0;\n    settingsAcks = 0;\n    certificateRequests = 0;\n    lastCertRequestId = 0;\n    certificates = 0;\n    lastCertId = 0;\n    windowSize = 0;\n    maxStreams = 0;\n    wtInitialMaxData = 0;\n    datagramEnabled = 0;\n    headerFrames = 0;\n    unknownFrames = 0;\n    greaseFrames = 0;\n    priority = HTTPMessage::HTTP2Priority(0, false, 0);\n    urgency = 0;\n    incremental = false;\n    windowUpdates.clear();\n    data_.move();\n    msg.reset();\n    lastParseError.reset();\n    lastErrorCode = ErrorCode::NO_ERROR;\n  }\n\n  void dumpCounters(int verbosity) const {\n    VLOG(verbosity) << \"Dumping HTTP codec callback counters\";\n    VLOG(verbosity) << \"headersCompleteId: \" << headersCompleteId;\n    VLOG(verbosity) << \"assocStreamId: \" << assocStreamId;\n    VLOG(verbosity) << \"pushId: \" << pushId;\n    VLOG(verbosity) << \"controlStreamId: \" << controlStreamId;\n    VLOG(verbosity) << \"unidirectional: \" << isUnidirectional;\n    VLOG(verbosity) << \"messageBegin: \" << messageBegin;\n    VLOG(verbosity) << \"headersComplete: \" << headersComplete;\n    VLOG(verbosity) << \"bodyCalls: \" << bodyCalls;\n    VLOG(verbosity) << \"bodyLength: \" << bodyLength;\n    VLOG(verbosity) << \"paddingBytes: \" << paddingBytes;\n    VLOG(verbosity) << \"chunkHeaders: \" << chunkHeaders;\n    VLOG(verbosity) << \"chunkComplete: \" << chunkComplete;\n    VLOG(verbosity) << \"trailers: \" << trailers;\n    VLOG(verbosity) << \"aborts: \" << aborts;\n    VLOG(verbosity) << \"goaways: \" << goaways;\n    VLOG(verbosity) << \"sessionErrors: \" << sessionErrors;\n    VLOG(verbosity) << \"streamErrors: \" << streamErrors;\n    VLOG(verbosity) << \"recvPingRequest: \" << recvPingRequest;\n    VLOG(verbosity) << \"recvPingReply: \" << recvPingReply;\n    VLOG(verbosity) << \"windowUpdateCalls: \" << windowUpdateCalls;\n    VLOG(verbosity) << \"settings: \" << settings;\n    VLOG(verbosity) << \"settingsAcks: \" << settingsAcks;\n    VLOG(verbosity) << \"certificateRequests: \" << certificateRequests;\n    VLOG(verbosity) << \"lastCertRequestId: \" << lastCertRequestId;\n    VLOG(verbosity) << \"certificates: \" << certificates;\n    VLOG(verbosity) << \"lastCertId: \" << lastCertId;\n    VLOG(verbosity) << \"windowSize: \" << windowSize;\n    VLOG(verbosity) << \"maxStreams: \" << maxStreams;\n    VLOG(verbosity) << \"datagramEnabled: \" << datagramEnabled;\n    VLOG(verbosity) << \"headerFrames: \" << headerFrames;\n  }\n\n  HTTPCodec::StreamID headersCompleteId{0};\n  HTTPCodec::StreamID assocStreamId{0};\n  HTTPCodec::StreamID pushId{0};\n  HTTPCodec::StreamID controlStreamId{0};\n  bool isUnidirectional{false};\n  HTTPCodec::StreamID sessionStreamId{0};\n  uint32_t messageBegin{0};\n  uint32_t headersComplete{0};\n  uint32_t messageComplete{0};\n  uint32_t bodyCalls{0};\n  uint32_t bodyLength{0};\n  uint32_t paddingBytes{0};\n  uint32_t chunkHeaders{0};\n  uint32_t chunkComplete{0};\n  uint32_t trailers{0};\n  uint32_t aborts{0};\n  uint32_t goaways{0};\n  uint32_t sessionErrors{0};\n  uint32_t streamErrors{0};\n  uint64_t recvPingRequest{0};\n  uint64_t recvPingReply{0};\n  uint32_t windowUpdateCalls{0};\n  uint32_t settings{0};\n  uint64_t numSettings{0};\n  uint32_t settingsAcks{0};\n  uint32_t certificateRequests{0};\n  uint16_t lastCertRequestId{0};\n  uint32_t certificates{0};\n  uint16_t lastCertId{0};\n  uint64_t windowSize{0};\n  uint64_t maxStreams{0};\n  uint64_t wtInitialMaxData{0};\n  uint64_t datagramEnabled{0};\n  uint32_t headerFrames{0};\n  uint32_t greaseFrames{0};\n  uint32_t unknownFrames{0};\n  HTTPMessage::HTTP2Priority priority{0, false, 0};\n  uint8_t urgency{0};\n  bool incremental{false};\n  std::map<proxygen::HTTPCodec::StreamID, std::vector<uint32_t>> windowUpdates;\n  folly::IOBufQueue data_;\n\n  std::unique_ptr<HTTPMessage> msg;\n  std::unique_ptr<HTTPException> lastParseError;\n  ErrorCode lastErrorCode;\n  std::vector<HTTPCodec::StreamID> goawayStreamIds;\n};\n\nMATCHER_P(PtrBufHasLen, n, \"\") {\n  return arg->computeChainDataLength() == n;\n}\n\nstd::unique_ptr<HTTPMessage> getPriorityMessage(uint8_t priority);\n\nstd::unique_ptr<folly::IOBuf> makeBuf(uint32_t size = 10);\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeDownstreamParallelCodec();\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeUpstreamParallelCodec();\n\nHTTPMessage getGetRequest(const std::string& url = std::string(\"/\"));\nHTTPMessage getBigGetRequest(const std::string& url = std::string(\"/\"));\nHTTPMessage getPostRequest(uint32_t contentLength = 200);\nHTTPMessage getPubRequest(const std::string& url = std::string(\"/\"));\nHTTPMessage getChunkedPostRequest();\nHTTPMessage getResponse(uint32_t code, uint32_t bodyLen = 0);\nHTTPMessage getUpgradeRequest(const std::string& upgradeHeader,\n                              HTTPMethod method = HTTPMethod::GET,\n                              uint32_t bodyLen = 0);\nHTTPMessage getResponseWithInvalidBodyLength();\n\nstd::unique_ptr<HTTPMessage> makeGetRequest();\nstd::unique_ptr<HTTPMessage> makePostRequest(uint32_t contentLength = 200);\nstd::unique_ptr<HTTPMessage> makeResponse(uint16_t statusCode);\n\nstd::tuple<std::unique_ptr<HTTPMessage>, std::unique_ptr<folly::IOBuf>>\nmakeResponse(uint16_t statusCode, size_t len);\n\n// Takes a MockHTTPCodec and fakes out its interface\nvoid fakeMockCodec(MockHTTPCodec& codec);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_codec_webtransport_webtransport_framer\n  SRCS\n    WebTransportFramer.cpp\n  DEPS\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_folly_utils\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_http_codec_capsule_codec\n)\n\nproxygen_add_library(proxygen_http_codec_webtransport_webtransport_capsule_codec\n  SRCS\n    WebTransportCapsuleCodec.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_capsule_codec\n    proxygen_http_codec_webtransport_webtransport_framer\n)\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n\n#define ret_if_err(res)                          \\\n  do {                                           \\\n    if ((res).hasError()) {                      \\\n      return folly::makeUnexpected(res.error()); \\\n    }                                            \\\n  } while (0);\n\nnamespace proxygen {\n\nfolly::Expected<folly::Unit, CapsuleCodec::ErrorCode>\nWebTransportCapsuleCodec::parseCapsule(folly::io::Cursor& cursor) {\n  switch (curCapsuleType_) {\n    case folly::to_underlying(CapsuleType::PADDING): {\n      auto res = parsePadding(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onPaddingCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_RESET_STREAM): {\n      auto res = parseWTResetStream(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTResetStreamCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_STOP_SENDING): {\n      auto res = parseWTStopSending(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTStopSendingCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_STREAM):\n    case folly::to_underlying(CapsuleType::WT_STREAM_WITH_FIN): {\n      bool fin = curCapsuleType_ ==\n                 (folly::to_underlying(CapsuleType::WT_STREAM_WITH_FIN));\n      auto res = parseWTStream(cursor, curCapsuleLength_, fin);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTStreamCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_MAX_DATA): {\n      auto res = parseWTMaxData(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTMaxDataCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_MAX_STREAM_DATA): {\n      auto res = parseWTMaxStreamData(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTMaxStreamDataCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_MAX_STREAMS_BIDI): {\n      auto res = parseWTMaxStreams(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTMaxStreamsBidiCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_MAX_STREAMS_UNI): {\n      auto res = parseWTMaxStreams(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTMaxStreamsUniCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_DATA_BLOCKED): {\n      auto res = parseWTDataBlocked(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTDataBlockedCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_STREAM_DATA_BLOCKED): {\n      auto res = parseWTStreamDataBlocked(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTStreamDataBlockedCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_STREAMS_BLOCKED_BIDI): {\n      auto res = parseWTStreamsBlocked(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTStreamsBlockedBidiCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::WT_STREAMS_BLOCKED_UNI): {\n      auto res = parseWTStreamsBlocked(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onWTStreamsBlockedUniCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::DATAGRAM): {\n      auto res = parseDatagram(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onDatagramCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::CLOSE_WEBTRANSPORT_SESSION): {\n      auto res = parseCloseWebTransportSession(cursor, curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onCloseWTSessionCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    case folly::to_underlying(CapsuleType::DRAIN_WEBTRANSPORT_SESSION): {\n      auto res = parseDrainWebTransportSession(curCapsuleLength_);\n      ret_if_err(res);\n      if (callback_) {\n        callback_->onDrainWTSessionCapsule(std::move(res.value()));\n      }\n      break;\n    }\n    default:\n      return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return folly::unit;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n\nnamespace proxygen {\n\nenum class CodecVersion { H2, H3 };\n\nclass WebTransportCapsuleCodec : public CapsuleCodec {\n public:\n  class Callback : public CapsuleCodec::Callback {\n   public:\n    ~Callback() noexcept override = default;\n\n    virtual void onPaddingCapsule(PaddingCapsule capsule) noexcept = 0;\n    virtual void onWTResetStreamCapsule(\n        WTResetStreamCapsule capsule) noexcept = 0;\n    virtual void onWTStopSendingCapsule(\n        WTStopSendingCapsule capsule) noexcept = 0;\n    virtual void onWTStreamCapsule(WTStreamCapsule capsule) noexcept = 0;\n    virtual void onWTMaxDataCapsule(WTMaxDataCapsule capsule) noexcept = 0;\n    virtual void onWTMaxStreamDataCapsule(\n        WTMaxStreamDataCapsule capsule) noexcept = 0;\n    virtual void onWTMaxStreamsBidiCapsule(\n        WTMaxStreamsCapsule capsule) noexcept = 0;\n    virtual void onWTMaxStreamsUniCapsule(\n        WTMaxStreamsCapsule capsule) noexcept = 0;\n    virtual void onWTDataBlockedCapsule(\n        WTDataBlockedCapsule capsule) noexcept = 0;\n    virtual void onWTStreamDataBlockedCapsule(\n        WTStreamDataBlockedCapsule capsule) noexcept = 0;\n    virtual void onWTStreamsBlockedBidiCapsule(\n        WTStreamsBlockedCapsule capsule) noexcept = 0;\n    virtual void onWTStreamsBlockedUniCapsule(\n        WTStreamsBlockedCapsule capsule) noexcept = 0;\n    virtual void onDatagramCapsule(DatagramCapsule capsule) noexcept = 0;\n    virtual void onCloseWTSessionCapsule(\n        CloseWebTransportSessionCapsule capsule) noexcept = 0;\n    virtual void onDrainWTSessionCapsule(\n        DrainWebTransportSessionCapsule capsule) noexcept = 0;\n  };\n\n  WebTransportCapsuleCodec(Callback* callback, CodecVersion version)\n      : CapsuleCodec(callback), callback_(callback), version_(version) {\n  }\n\n  ~WebTransportCapsuleCodec() override = default;\n\n  void setCallback(Callback* callback) {\n    CapsuleCodec::setCallback(callback);\n    callback_ = callback;\n  }\n\n private:\n  bool canParseCapsule(uint64_t capsuleType) noexcept override {\n    switch (capsuleType) {\n      // Common H2 and H3 capsule types\n      case folly::to_underlying(CapsuleType::WT_MAX_DATA):\n      case folly::to_underlying(CapsuleType::WT_MAX_STREAMS_BIDI):\n      case folly::to_underlying(CapsuleType::WT_MAX_STREAMS_UNI):\n      case folly::to_underlying(CapsuleType::WT_DATA_BLOCKED):\n      case folly::to_underlying(CapsuleType::WT_STREAMS_BLOCKED_BIDI):\n      case folly::to_underlying(CapsuleType::WT_STREAMS_BLOCKED_UNI):\n      case folly::to_underlying(CapsuleType::CLOSE_WEBTRANSPORT_SESSION):\n      case folly::to_underlying(CapsuleType::DRAIN_WEBTRANSPORT_SESSION):\n        return true;\n      // H2 only capsule types\n      case folly::to_underlying(CapsuleType::PADDING):\n      case folly::to_underlying(CapsuleType::WT_RESET_STREAM):\n      case folly::to_underlying(CapsuleType::WT_STOP_SENDING):\n      case folly::to_underlying(CapsuleType::WT_STREAM):\n      case folly::to_underlying(CapsuleType::WT_STREAM_WITH_FIN):\n      case folly::to_underlying(CapsuleType::WT_MAX_STREAM_DATA):\n      case folly::to_underlying(CapsuleType::WT_STREAM_DATA_BLOCKED):\n      case folly::to_underlying(CapsuleType::DATAGRAM):\n        return version_ == CodecVersion::H2;\n      default:\n        return false;\n    }\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> parseCapsule(\n      folly::io::Cursor& cursor) override;\n\n  Callback* callback_{nullptr};\n  CodecVersion version_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/WebTransportFramer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBufQueue.h>\n\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\nnamespace {\nconst size_t kDefaultBufferGrowth = 32;\n}\n\nnamespace proxygen {\n\nfolly::Expected<PaddingCapsule, CapsuleCodec::ErrorCode> parsePadding(\n    folly::io::Cursor& cursor, size_t length) {\n  cursor.skip(length);\n  return PaddingCapsule{length};\n}\n\nfolly::Expected<WTResetStreamCapsule, CapsuleCodec::ErrorCode>\nparseWTResetStream(folly::io::Cursor& cursor, size_t length) {\n  WTResetStreamCapsule wtResetStreamCapsule{};\n  auto streamIdOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!streamIdOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= streamIdOpt->second;\n  wtResetStreamCapsule.streamId = streamIdOpt->first;\n  auto appProtocolErrorCodeOpt =\n      quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!appProtocolErrorCodeOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= appProtocolErrorCodeOpt->second;\n  wtResetStreamCapsule.appProtocolErrorCode =\n      static_cast<uint32_t>(appProtocolErrorCodeOpt->first);\n  auto reliableSizeOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!reliableSizeOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= reliableSizeOpt->second;\n  wtResetStreamCapsule.reliableSize = reliableSizeOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtResetStreamCapsule;\n}\n\nfolly::Expected<WTStopSendingCapsule, CapsuleCodec::ErrorCode>\nparseWTStopSending(folly::io::Cursor& cursor, size_t length) {\n  WTStopSendingCapsule wtStopSendingCapsule{};\n  auto streamIdOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!streamIdOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= streamIdOpt->second;\n  wtStopSendingCapsule.streamId = streamIdOpt->first;\n  auto appProtocolErrorCodeOpt =\n      quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!appProtocolErrorCodeOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= appProtocolErrorCodeOpt->second;\n  wtStopSendingCapsule.appProtocolErrorCode =\n      static_cast<uint32_t>(appProtocolErrorCodeOpt->first);\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtStopSendingCapsule;\n}\n\nfolly::Expected<WTStreamCapsule, CapsuleCodec::ErrorCode> parseWTStream(\n    folly::io::Cursor& cursor, size_t length, bool fin) {\n  WTStreamCapsule wtStreamCapsule{};\n  auto streamIdOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!streamIdOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= streamIdOpt->second;\n  wtStreamCapsule.streamId = streamIdOpt->first;\n  if (!cursor.canAdvance(length)) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  cursor.cloneAtMost(wtStreamCapsule.streamData, length);\n  wtStreamCapsule.fin = fin;\n  return wtStreamCapsule;\n}\n\nfolly::Expected<WTMaxDataCapsule, CapsuleCodec::ErrorCode> parseWTMaxData(\n    folly::io::Cursor& cursor, size_t length) {\n  WTMaxDataCapsule wtMaxDataCapsule{};\n  auto maximumDataOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumDataOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumDataOpt->second;\n  wtMaxDataCapsule.maximumData = maximumDataOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtMaxDataCapsule;\n}\n\nfolly::Expected<WTMaxStreamDataCapsule, CapsuleCodec::ErrorCode>\nparseWTMaxStreamData(folly::io::Cursor& cursor, size_t length) {\n  WTMaxStreamDataCapsule wtMaxStreamDataCapsule{};\n  auto streamIdOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!streamIdOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= streamIdOpt->second;\n  wtMaxStreamDataCapsule.streamId = streamIdOpt->first;\n  auto maximumStreamDataOpt =\n      quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumStreamDataOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumStreamDataOpt->second;\n  wtMaxStreamDataCapsule.maximumStreamData = maximumStreamDataOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtMaxStreamDataCapsule;\n}\n\nfolly::Expected<WTMaxStreamsCapsule, CapsuleCodec::ErrorCode> parseWTMaxStreams(\n    folly::io::Cursor& cursor, size_t length) {\n  WTMaxStreamsCapsule wtMaxStreamsCapsule{};\n  auto maximumStreamsOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumStreamsOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumStreamsOpt->second;\n  wtMaxStreamsCapsule.maximumStreams = maximumStreamsOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtMaxStreamsCapsule;\n}\n\nfolly::Expected<WTDataBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTDataBlocked(folly::io::Cursor& cursor, size_t length) {\n  WTDataBlockedCapsule wtDataBlockedCapsule{};\n  auto maximumDataOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumDataOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumDataOpt->second;\n  wtDataBlockedCapsule.maximumData = maximumDataOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtDataBlockedCapsule;\n}\n\nfolly::Expected<WTStreamDataBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTStreamDataBlocked(folly::io::Cursor& cursor, size_t length) {\n  WTStreamDataBlockedCapsule wtStreamDataBlockedCapsule{};\n  auto streamIdOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!streamIdOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= streamIdOpt->second;\n  wtStreamDataBlockedCapsule.streamId = streamIdOpt->first;\n  auto maximumStreamDataOpt =\n      quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumStreamDataOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumStreamDataOpt->second;\n  wtStreamDataBlockedCapsule.maximumStreamData = maximumStreamDataOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtStreamDataBlockedCapsule;\n}\n\nfolly::Expected<WTStreamsBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTStreamsBlocked(folly::io::Cursor& cursor, size_t length) {\n  WTStreamsBlockedCapsule wtStreamsBlockedCapsule{};\n  auto maximumStreamsOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!maximumStreamsOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  length -= maximumStreamsOpt->second;\n  wtStreamsBlockedCapsule.maximumStreams = maximumStreamsOpt->first;\n  if (length > 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return wtStreamsBlockedCapsule;\n}\n\nfolly::Expected<DatagramCapsule, CapsuleCodec::ErrorCode> parseDatagram(\n    folly::io::Cursor& cursor, size_t length) {\n  DatagramCapsule datagramCapsule{};\n  if (!cursor.canAdvance(length)) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  cursor.cloneAtMost(datagramCapsule.httpDatagramPayload, length);\n  return datagramCapsule;\n}\n\nfolly::Expected<CloseWebTransportSessionCapsule, CapsuleCodec::ErrorCode>\nparseCloseWebTransportSession(folly::io::Cursor& cursor, size_t length) {\n  CloseWebTransportSessionCapsule closeWebTransportSessionCapsule{};\n  auto errorCodeOpt = quic::follyutils::decodeQuicInteger(cursor, length);\n  if (!errorCodeOpt) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  if (errorCodeOpt->first > std::numeric_limits<uint32_t>::max()) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  length -= errorCodeOpt->second;\n  closeWebTransportSessionCapsule.applicationErrorCode =\n      static_cast<uint32_t>(errorCodeOpt->first);\n  if (!cursor.canAdvance(length)) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n  }\n  closeWebTransportSessionCapsule.applicationErrorMessage.resize(length);\n  cursor.pull(closeWebTransportSessionCapsule.applicationErrorMessage.data(),\n              length);\n  return closeWebTransportSessionCapsule;\n}\n\nfolly::Expected<DrainWebTransportSessionCapsule, CapsuleCodec::ErrorCode>\nparseDrainWebTransportSession(size_t length) {\n  DrainWebTransportSessionCapsule drainWebTransportSessionCapsule{};\n  if (length != 0) {\n    return folly::makeUnexpected(CapsuleCodec::ErrorCode::PARSE_ERROR);\n  }\n  return drainWebTransportSessionCapsule;\n}\n\nnamespace {\n// Helper to write a variable-length integer\nvoid writeVarint(folly::IOBufQueue& buf,\n                 uint64_t value,\n                 size_t& size,\n                 bool& error) noexcept {\n  if (error) {\n    return;\n  }\n  folly::io::QueueAppender appender(&buf, kDefaultBufferGrowth);\n  auto appenderOp = [&](auto val) mutable {\n    appender.writeBE(folly::tag<decltype(val)>, val);\n  };\n  auto res = quic::encodeQuicInteger(value, appenderOp);\n  if (res.hasError()) {\n    error = true;\n  } else {\n    size += *res;\n  }\n}\n\n// Helper to write the capsule type and length\nvoid writeCapsuleHeader(folly::IOBufQueue& queue,\n                        CapsuleType capsuleType,\n                        size_t& size,\n                        bool& error,\n                        uint64_t length) {\n  writeVarint(queue, folly::to_underlying(capsuleType), size, error);\n  writeVarint(queue, length, size, error);\n}\n\n// Helper to calculate the capsule size based on varint encoding + actual\n// payload size for string fields\ntemplate <const size_t N, const size_t M>\nfolly::Expected<uint64_t, quic::QuicError> getCapsuleSize(\n    std::array<uint64_t, N> varints, std::array<uint64_t, M> lengths = {}) {\n  size_t size = 0;\n  for (auto v : varints) {\n    auto res = quic::getQuicIntegerSize(v);\n    if (!res) {\n      return folly::makeUnexpected(res.error());\n    }\n    size += *res;\n  }\n  for (auto l : lengths) {\n    size += l;\n  }\n  return size;\n}\n} // namespace\n\nWriteResult writePadding(folly::IOBufQueue& queue,\n                         const PaddingCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  writeCapsuleHeader(\n      queue, CapsuleType::PADDING, size, error, capsule.paddingLength);\n  std::string padding(capsule.paddingLength, 0);\n  queue.append(padding.data(), padding.size());\n  size += padding.size();\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTResetStream(folly::IOBufQueue& queue,\n                               const WTResetStreamCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<3, 0>(\n      {capsule.streamId, capsule.appProtocolErrorCode, capsule.reliableSize},\n      {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::WT_RESET_STREAM, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.streamId, size, error);\n  writeVarint(queue, capsule.appProtocolErrorCode, size, error);\n  writeVarint(queue, capsule.reliableSize, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTStopSending(folly::IOBufQueue& queue,\n                               const WTStopSendingCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<2, 0>(\n      {capsule.streamId, capsule.appProtocolErrorCode}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::WT_STOP_SENDING, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.streamId, size, error);\n  writeVarint(queue, capsule.appProtocolErrorCode, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTStream(folly::IOBufQueue& queue,\n                          const WTStreamCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleType =\n      capsule.fin ? CapsuleType::WT_STREAM_WITH_FIN : CapsuleType::WT_STREAM;\n  const auto dataLen =\n      capsule.streamData ? capsule.streamData->computeChainDataLength() : 0;\n  auto capsuleLen = getCapsuleSize<1, 1>({capsule.streamId}, {dataLen});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(queue, capsuleType, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.streamId, size, error);\n  if (dataLen) {\n    // TODO(@damlaj): replace const WtStreamCapsule& w/ WtStreamCapsule&&\n    queue.append(capsule.streamData->clone());\n  }\n  size += dataLen;\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTMaxData(folly::IOBufQueue& queue,\n                           const WTMaxDataCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<1, 0>({capsule.maximumData}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::WT_MAX_DATA, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.maximumData, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTMaxStreamData(folly::IOBufQueue& queue,\n                                 const WTMaxStreamDataCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen =\n      getCapsuleSize<2, 0>({capsule.streamId, capsule.maximumStreamData}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::WT_MAX_STREAM_DATA, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.streamId, size, error);\n  writeVarint(queue, capsule.maximumStreamData, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTMaxStreams(folly::IOBufQueue& queue,\n                              const WTMaxStreamsCapsule& capsule,\n                              bool isBidi) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<1, 0>({capsule.maximumStreams}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  auto type = isBidi ? CapsuleType::WT_MAX_STREAMS_BIDI\n                     : CapsuleType::WT_MAX_STREAMS_UNI;\n  writeCapsuleHeader(queue, type, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.maximumStreams, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTDataBlocked(folly::IOBufQueue& queue,\n                               const WTDataBlockedCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<1, 0>({capsule.maximumData}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::WT_DATA_BLOCKED, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.maximumData, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTStreamDataBlocked(\n    folly::IOBufQueue& queue, const WTStreamDataBlockedCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen =\n      getCapsuleSize<2, 0>({capsule.streamId, capsule.maximumStreamData}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(queue,\n                     CapsuleType::WT_STREAM_DATA_BLOCKED,\n                     size,\n                     error,\n                     capsuleLen.value());\n  writeVarint(queue, capsule.streamId, size, error);\n  writeVarint(queue, capsule.maximumStreamData, size, error);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeWTStreamsBlocked(folly::IOBufQueue& queue,\n                                  const WTStreamsBlockedCapsule& capsule,\n                                  bool isBidi) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<1, 0>({capsule.maximumStreams}, {});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  auto type = isBidi ? CapsuleType::WT_STREAMS_BLOCKED_BIDI\n                     : CapsuleType::WT_STREAMS_BLOCKED_UNI;\n  writeCapsuleHeader(queue, type, size, error, capsuleLen.value());\n  writeVarint(queue, capsule.maximumStreams, size, error);\n\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeDatagram(folly::IOBufQueue& queue,\n                          const DatagramCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<0, 1>(\n      {}, {capsule.httpDatagramPayload->computeChainDataLength()});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(\n      queue, CapsuleType::DATAGRAM, size, error, capsuleLen.value());\n  queue.append(capsule.httpDatagramPayload->clone());\n  size += capsule.httpDatagramPayload->computeChainDataLength();\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeCloseWebTransportSession(\n    folly::IOBufQueue& queue, const CloseWebTransportSessionCapsule& capsule) {\n  size_t size = 0;\n  bool error = false;\n  auto capsuleLen = getCapsuleSize<1, 1>(\n      {capsule.applicationErrorCode}, {capsule.applicationErrorMessage.size()});\n  if (capsuleLen.hasError()) {\n    return std::nullopt;\n  }\n  writeCapsuleHeader(queue,\n                     CapsuleType::CLOSE_WEBTRANSPORT_SESSION,\n                     size,\n                     error,\n                     capsuleLen.value());\n  writeVarint(queue, capsule.applicationErrorCode, size, error);\n  queue.append(\n      folly::IOBuf::copyBuffer(capsule.applicationErrorMessage.data(),\n                               capsule.applicationErrorMessage.size()));\n  size += capsule.applicationErrorMessage.size();\n  return error ? std::nullopt : std::make_optional(size);\n}\n\nWriteResult writeDrainWebTransportSession(folly::IOBufQueue& queue) {\n  size_t size = 0;\n  bool error = false;\n  writeCapsuleHeader(\n      queue, CapsuleType::DRAIN_WEBTRANSPORT_SESSION, size, error, 0);\n  return error ? std::nullopt : std::make_optional(size);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/WebTransportFramer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n\nnamespace proxygen {\n\n//////// Types ////////\n\nenum class CapsuleType : uint32_t {\n  PADDING = 0x190B4D38,\n  WT_RESET_STREAM = 0x190B4D39,\n  WT_STOP_SENDING = 0x190B4D3A,\n  WT_STREAM = 0x190B4D3B,\n  WT_STREAM_WITH_FIN = 0x190B4D3C,\n  WT_MAX_DATA = 0x190B4D3D,\n  WT_MAX_STREAM_DATA = 0x190B4D3E,\n  WT_MAX_STREAMS_BIDI = 0x190B4D3F,\n  WT_MAX_STREAMS_UNI = 0x190B4D40,\n  WT_DATA_BLOCKED = 0x190B4D41,\n  WT_STREAM_DATA_BLOCKED = 0x190B4D42,\n  WT_STREAMS_BLOCKED_BIDI = 0x190B4D43,\n  WT_STREAMS_BLOCKED_UNI = 0x190B4D44,\n  DATAGRAM = 0x00,\n  CLOSE_WEBTRANSPORT_SESSION = 0x190B4D45,\n  DRAIN_WEBTRANSPORT_SESSION = 0x190B4D46\n};\n\nstruct PaddingCapsule {\n  size_t paddingLength{0};\n};\n\nstruct WTResetStreamCapsule {\n  uint64_t streamId{0};\n  uint32_t appProtocolErrorCode{0};\n  uint64_t reliableSize{0};\n};\n\nstruct WTStopSendingCapsule {\n  uint64_t streamId{0};\n  uint32_t appProtocolErrorCode{0};\n};\n\nstruct WTStreamCapsule {\n  uint64_t streamId{0};\n  std::unique_ptr<folly::IOBuf> streamData{};\n  bool fin{};\n};\n\nstruct WTMaxDataCapsule {\n  uint64_t maximumData{0};\n};\n\nstruct WTMaxStreamDataCapsule {\n  uint64_t streamId{0};\n  uint64_t maximumStreamData{0};\n};\n\nstruct WTMaxStreamsCapsule {\n  uint64_t maximumStreams{0};\n};\n\nstruct WTDataBlockedCapsule {\n  uint64_t maximumData{0};\n};\n\nstruct WTStreamDataBlockedCapsule {\n  uint64_t streamId{0};\n  uint64_t maximumStreamData{0};\n};\n\nstruct WTStreamsBlockedCapsule {\n  uint64_t maximumStreams{0};\n};\n\nstruct DatagramCapsule {\n  std::unique_ptr<folly::IOBuf> httpDatagramPayload{};\n};\n\nstruct CloseWebTransportSessionCapsule {\n  uint32_t applicationErrorCode{0};\n  std::string applicationErrorMessage{};\n};\n\nstruct DrainWebTransportSessionCapsule {\n  // no additional fields, length is 0\n};\n\n// Function declarations for parsing each capsule type\nfolly::Expected<PaddingCapsule, CapsuleCodec::ErrorCode> parsePadding(\n    folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTResetStreamCapsule, CapsuleCodec::ErrorCode>\nparseWTResetStream(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTStopSendingCapsule, CapsuleCodec::ErrorCode>\nparseWTStopSending(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTStreamCapsule, CapsuleCodec::ErrorCode> parseWTStream(\n    folly::io::Cursor& cursor, size_t length, bool fin);\n\nfolly::Expected<WTMaxDataCapsule, CapsuleCodec::ErrorCode> parseWTMaxData(\n    folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTMaxStreamDataCapsule, CapsuleCodec::ErrorCode>\nparseWTMaxStreamData(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTMaxStreamsCapsule, CapsuleCodec::ErrorCode> parseWTMaxStreams(\n    folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTDataBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTDataBlocked(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTStreamDataBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTStreamDataBlocked(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<WTStreamsBlockedCapsule, CapsuleCodec::ErrorCode>\nparseWTStreamsBlocked(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<DatagramCapsule, CapsuleCodec::ErrorCode> parseDatagram(\n    folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<CloseWebTransportSessionCapsule, CapsuleCodec::ErrorCode>\nparseCloseWebTransportSession(folly::io::Cursor& cursor, size_t length);\n\nfolly::Expected<DrainWebTransportSessionCapsule, CapsuleCodec::ErrorCode>\nparseDrainWebTransportSession(size_t length);\n\n// change from std::optional<size_t> -> size_t (0 can signal error)?\nusing WriteResult = std::optional<size_t>;\n\n// Function declarations for serializing each capsule type\nWriteResult writePadding(folly::IOBufQueue& queue,\n                         const PaddingCapsule& capsule);\nWriteResult writeWTResetStream(folly::IOBufQueue& queue,\n                               const WTResetStreamCapsule& capsule);\nWriteResult writeWTStopSending(folly::IOBufQueue& queue,\n                               const WTStopSendingCapsule& capsule);\nWriteResult writeWTStream(folly::IOBufQueue& queue,\n                          const WTStreamCapsule& capsule);\nWriteResult writeWTMaxData(folly::IOBufQueue& queue,\n                           const WTMaxDataCapsule& capsule);\nWriteResult writeWTMaxStreamData(folly::IOBufQueue& queue,\n                                 const WTMaxStreamDataCapsule& capsule);\nWriteResult writeWTMaxStreams(folly::IOBufQueue& queue,\n                              const WTMaxStreamsCapsule& capsule,\n                              bool isBidi);\nWriteResult writeWTDataBlocked(folly::IOBufQueue& queue,\n                               const WTDataBlockedCapsule& capsule);\nWriteResult writeWTStreamDataBlocked(folly::IOBufQueue& queue,\n                                     const WTStreamDataBlockedCapsule& capsule);\nWriteResult writeWTStreamsBlocked(folly::IOBufQueue& queue,\n                                  const WTStreamsBlockedCapsule& capsule,\n                                  bool isBidi);\nWriteResult writeDatagram(folly::IOBufQueue& queue,\n                          const DatagramCapsule& capsule);\nWriteResult writeCloseWebTransportSession(\n    folly::IOBufQueue& queue, const CloseWebTransportSessionCapsule& capsule);\nWriteResult writeDrainWebTransportSession(folly::IOBufQueue& queue);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/test/WebTransportCapsuleCodecTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <gmock/gmock.h>\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nclass WebTransportCapsuleCodecTest : public Test {\n protected:\n  void SetUp() override {\n    h2_codec_ = std::make_unique<WebTransportCapsuleCodec>(&callback_,\n                                                           CodecVersion::H2);\n    h3_codec_ = std::make_unique<WebTransportCapsuleCodec>(&callback_,\n                                                           CodecVersion::H3);\n  }\n\n  class TestWebTransportCapsuleCodecCallback\n      : public WebTransportCapsuleCodec::Callback {\n   public:\n    MOCK_METHOD(void, onPaddingCapsule, (PaddingCapsule), (override, noexcept));\n    MOCK_METHOD(void,\n                onWTResetStreamCapsule,\n                (WTResetStreamCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTStopSendingCapsule,\n                (WTStopSendingCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTStreamCapsule,\n                (WTStreamCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTMaxDataCapsule,\n                (WTMaxDataCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTMaxStreamDataCapsule,\n                (WTMaxStreamDataCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTMaxStreamsBidiCapsule,\n                (WTMaxStreamsCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTMaxStreamsUniCapsule,\n                (WTMaxStreamsCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTDataBlockedCapsule,\n                (WTDataBlockedCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTStreamDataBlockedCapsule,\n                (WTStreamDataBlockedCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTStreamsBlockedBidiCapsule,\n                (WTStreamsBlockedCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onWTStreamsBlockedUniCapsule,\n                (WTStreamsBlockedCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onDatagramCapsule,\n                (DatagramCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onCloseWTSessionCapsule,\n                (CloseWebTransportSessionCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onDrainWTSessionCapsule,\n                (DrainWebTransportSessionCapsule),\n                (override, noexcept));\n    MOCK_METHOD(void,\n                onConnectionError,\n                (CapsuleCodec::ErrorCode),\n                (override, noexcept));\n  };\n\n  // Simulate PARSE_UNDERFLOW by modifying buf to signify 8-byte varint. For all\n  // tests, the first payload byte will be the fifth byte due to all types being\n  // 4 bytes and the length being 1 byte.\n  void testParseUnderflow(CapsuleCodec::ErrorCode err, CodecVersion version) {\n    auto buf = queue_.move();\n    buf->writableData()[5] = 0xFF;\n    EXPECT_CALL(callback_, onConnectionError(_))\n        .WillOnce(Invoke([&](auto errorCode) { EXPECT_EQ(errorCode, err); }));\n    auto codec = (version == CodecVersion::H2) ? std::move(h2_codec_)\n                                               : std::move(h3_codec_);\n    codec->onIngress(std::move(buf), true);\n  }\n\n  std::unique_ptr<WebTransportCapsuleCodec> h2_codec_;\n  std::unique_ptr<WebTransportCapsuleCodec> h3_codec_;\n  TestWebTransportCapsuleCodecCallback callback_;\n  folly::IOBufQueue queue_;\n};\n\nTEST_F(WebTransportCapsuleCodecTest, OnPaddingCapsule) {\n  PaddingCapsule capsule{100};\n  writePadding(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onPaddingCapsule(_))\n      .WillOnce(Invoke([&](PaddingCapsule capsule) {\n        EXPECT_EQ(capsule.paddingLength, 100);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTResetStreamCapsule) {\n  WTResetStreamCapsule capsule{\n      .streamId = 1, .appProtocolErrorCode = 2, .reliableSize = 3};\n  writeWTResetStream(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTResetStreamCapsule(_))\n      .WillOnce(Invoke([&](WTResetStreamCapsule capsule) {\n        EXPECT_EQ(capsule.streamId, 1);\n        EXPECT_EQ(capsule.appProtocolErrorCode, 2);\n        EXPECT_EQ(capsule.reliableSize, 3);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTResetStreamCapsuleError) {\n  WTResetStreamCapsule capsule{\n      .streamId = 1,\n      .appProtocolErrorCode = std::numeric_limits<uint32_t>::max(),\n      .reliableSize = std::numeric_limits<uint32_t>::max()};\n  writeWTResetStream(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStopSendingCapsule) {\n  WTStopSendingCapsule capsule{.streamId = 1, .appProtocolErrorCode = 2};\n  writeWTStopSending(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStopSendingCapsule(_))\n      .WillOnce(Invoke([&](WTStopSendingCapsule capsule) {\n        EXPECT_EQ(capsule.streamId, 1);\n        EXPECT_EQ(capsule.appProtocolErrorCode, 2);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStopSendingCapsuleError) {\n  WTStopSendingCapsule capsule{.streamId = 1,\n                               .appProtocolErrorCode =\n                                   std::numeric_limits<uint32_t>::max()};\n  writeWTStopSending(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStreamCapsule) {\n  WTStreamCapsule capsule{.streamId = 1,\n                          .streamData = folly::IOBuf::copyBuffer(\"roast beef\"),\n                          .fin = true};\n  writeWTStream(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamCapsule(_))\n      .WillOnce(Invoke([&](WTStreamCapsule capsule) {\n        EXPECT_EQ(capsule.streamId, 1);\n        EXPECT_EQ(capsule.streamData->moveToFbString().toStdString(),\n                  \"roast beef\");\n        EXPECT_TRUE(capsule.fin);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStreamCapsuleError) {\n  WTStreamCapsule capsule{.streamId = 1,\n                          .streamData = folly::IOBuf::copyBuffer(\"roast\"),\n                          .fin = true};\n  writeWTStream(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxDataCapsule) {\n  WTMaxDataCapsule capsule{100};\n  writeWTMaxData(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxDataCapsule(_))\n      .WillOnce(Invoke([&](WTMaxDataCapsule capsule) {\n        EXPECT_EQ(capsule.maximumData, 100);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxDataCapsule) {\n  WTMaxDataCapsule capsule{100};\n  writeWTMaxData(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxDataCapsule(_))\n      .WillOnce(Invoke([&](WTMaxDataCapsule capsule) {\n        EXPECT_EQ(capsule.maximumData, 100);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxDataCapsuleError) {\n  WTMaxDataCapsule capsule{100};\n  writeWTMaxData(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxDataCapsuleError) {\n  WTMaxDataCapsule capsule{100};\n  writeWTMaxData(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTMaxStreamDataCapsule) {\n  WTMaxStreamDataCapsule capsule{.streamId = 1, .maximumStreamData = 100};\n  writeWTMaxStreamData(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxStreamDataCapsule(_))\n      .WillOnce(Invoke([&](WTMaxStreamDataCapsule capsule) {\n        EXPECT_EQ(capsule.streamId, 1);\n        EXPECT_EQ(capsule.maximumStreamData, 100);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTMaxStreamDataCapsuleError) {\n  WTMaxStreamDataCapsule capsule{\n      .streamId = 1, .maximumStreamData = std::numeric_limits<uint32_t>::max()};\n  writeWTMaxStreamData(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxStreamsCapsule) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, true);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxStreamsBidiCapsule(_))\n      .WillOnce(Invoke([&](WTMaxStreamsCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 200);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxStreamsCapsule) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, true);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxStreamsBidiCapsule(_))\n      .WillOnce(Invoke([&](WTMaxStreamsCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 200);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxStreamsCapsuleError) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, true);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxStreamsCapsuleError) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, true);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxStreamsUniCapsule) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, false);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxStreamsUniCapsule(_))\n      .WillOnce(Invoke([&](WTMaxStreamsCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 200);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxStreamsUniCapsule) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, false);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTMaxStreamsUniCapsule(_))\n      .WillOnce(Invoke([&](WTMaxStreamsCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 200);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTMaxStreamsUniCapsuleError) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, false);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTMaxStreamsUniCapsuleError) {\n  WTMaxStreamsCapsule capsule{200};\n  writeWTMaxStreams(queue_, capsule, false);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnDataBlockedCapsule) {\n  WTDataBlockedCapsule capsule{300};\n  writeWTDataBlocked(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTDataBlockedCapsule(_))\n      .WillOnce(Invoke([&](WTDataBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumData, 300);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnDataBlockedCapsule) {\n  WTDataBlockedCapsule capsule{300};\n  writeWTDataBlocked(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTDataBlockedCapsule(_))\n      .WillOnce(Invoke([&](WTDataBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumData, 300);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnDataBlockedCapsuleError) {\n  WTDataBlockedCapsule capsule{300};\n  writeWTDataBlocked(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnDataBlockedCapsuleError) {\n  WTDataBlockedCapsule capsule{300};\n  writeWTDataBlocked(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTStreamsBlockedCapsule) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, true);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamsBlockedBidiCapsule(_))\n      .WillOnce(Invoke([&](WTStreamsBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 400);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTStreamsBlockedCapsule) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, true);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamsBlockedBidiCapsule(_))\n      .WillOnce(Invoke([&](WTStreamsBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 400);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTStreamsBlockedCapsuleError) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, true);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTStreamsBlockedCapsuleError) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, true);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTStreamsBlockedUniCapsule) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, false);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamsBlockedUniCapsule(_))\n      .WillOnce(Invoke([&](WTStreamsBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 400);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTStreamsBlockedUniCapsule) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, false);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamsBlockedUniCapsule(_))\n      .WillOnce(Invoke([&](WTStreamsBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.maximumStreams, 400);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnWTStreamsBlockedUniCapsuleError) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, false);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnWTStreamsBlockedUniCapsuleError) {\n  WTStreamsBlockedCapsule capsule{400};\n  writeWTStreamsBlocked(queue_, capsule, false);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStreamDataBlockedCapsule) {\n  WTStreamDataBlockedCapsule capsule{.streamId = 1, .maximumStreamData = 100};\n  writeWTStreamDataBlocked(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onWTStreamDataBlockedCapsule(_))\n      .WillOnce(Invoke([&](WTStreamDataBlockedCapsule capsule) {\n        EXPECT_EQ(capsule.streamId, 1);\n        EXPECT_EQ(capsule.maximumStreamData, 100);\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnWTStreamDataBlockedCapsuleError) {\n  WTStreamDataBlockedCapsule capsule{\n      .streamId = 1, .maximumStreamData = std::numeric_limits<uint32_t>::max()};\n  writeWTStreamDataBlocked(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, OnDatagramCapsule) {\n  DatagramCapsule capsule{folly::IOBuf::copyBuffer(\"breakfast special\")};\n  writeDatagram(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onDatagramCapsule(_))\n      .WillOnce(Invoke([&](DatagramCapsule capsule) {\n        EXPECT_EQ(capsule.httpDatagramPayload->moveToFbString().toStdString(),\n                  \"breakfast special\");\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnCloseWebTransportSessionCapsule) {\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 500,\n                                          .applicationErrorMessage = \"BAD!\"};\n  writeCloseWebTransportSession(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onCloseWTSessionCapsule(_))\n      .WillOnce(Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n        EXPECT_EQ(capsule.applicationErrorCode, 500);\n        EXPECT_EQ(capsule.applicationErrorMessage, \"BAD!\");\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnCloseWebTransportSessionCapsule) {\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 500,\n                                          .applicationErrorMessage = \"BAD!\"};\n  writeCloseWebTransportSession(queue_, capsule);\n\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onCloseWTSessionCapsule(_))\n      .WillOnce(Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n        EXPECT_EQ(capsule.applicationErrorCode, 500);\n        EXPECT_EQ(capsule.applicationErrorMessage, \"BAD!\");\n      }));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnCloseWebTransportSessionCapsuleError) {\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 500,\n                                          .applicationErrorMessage = \"BAD!\"};\n  writeCloseWebTransportSession(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H2);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnCloseWebTransportSessionCapsuleError) {\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 500,\n                                          .applicationErrorMessage = \"BAD!\"};\n  writeCloseWebTransportSession(queue_, capsule);\n  testParseUnderflow(CapsuleCodec::ErrorCode::PARSE_UNDERFLOW,\n                     CodecVersion::H3);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H2OnDrainWebTransportSessionCapsule) {\n  writeDrainWebTransportSession(queue_);\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onDrainWTSessionCapsule(_));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h2_codec_->onIngress(std::move(buf), true);\n}\n\nTEST_F(WebTransportCapsuleCodecTest, H3OnDrainWebTransportSessionCapsule) {\n  writeDrainWebTransportSession(queue_);\n  auto buf = queue_.move();\n  EXPECT_CALL(callback_, onDrainWTSessionCapsule(_));\n  EXPECT_CALL(callback_, onConnectionError(_)).Times(0);\n  h3_codec_->onIngress(std::move(buf), true);\n}\n"
  },
  {
    "path": "proxygen/lib/http/codec/webtransport/test/WebTransportFramerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"gtest/gtest.h\"\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/CapsuleCodec.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n#include <quic/folly_utils/Utils.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nclass WebTransportFramerTest : public Test {};\n\nuint64_t parseCapsuleHeader(folly::io::Cursor& cursor, uint64_t type) {\n  // parse the type\n  auto typeOpt = quic::follyutils::decodeQuicInteger(cursor);\n  EXPECT_EQ(typeOpt->first, type);\n\n  // parse + return the length\n  auto length = quic::follyutils::decodeQuicInteger(cursor);\n  return length->first;\n}\n\nTEST(WebTransportFramerTest, Padding) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  PaddingCapsule capsule{10};\n  auto writeRes = writePadding(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen =\n      parseCapsuleHeader(cursor, static_cast<uint64_t>(CapsuleType::PADDING));\n  size_t cursorPositionBefore = cursor.getCurrentPosition();\n\n  // parse the capsule payload\n  auto parsedCapsule = parsePadding(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->paddingLength, capsule.paddingLength);\n\n  // check that we skipped over the padding\n  size_t cursorPositionAfter = cursor.getCurrentPosition();\n  EXPECT_EQ(cursorPositionAfter - cursorPositionBefore, capsule.paddingLength);\n}\n\nTEST(WebTransportFramerTest, WTResetStream) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTResetStreamCapsule capsule{.streamId = 0x1,\n                               .appProtocolErrorCode = 0x12345678,\n                               .reliableSize = 0x10};\n  auto writeRes = writeWTResetStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_RESET_STREAM));\n\n  auto parsedCapsule = parseWTResetStream(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n  ASSERT_EQ(parsedCapsule->appProtocolErrorCode, capsule.appProtocolErrorCode);\n  ASSERT_EQ(parsedCapsule->reliableSize, capsule.reliableSize);\n}\n\nTEST(WebTransportFramerTest, WTResetStreamStreamIdError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTResetStreamCapsule capsule{.streamId = 0x1,\n                               .appProtocolErrorCode = 0x12345678,\n                               .reliableSize = 0x10};\n  auto writeRes = writeWTResetStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that streamId (1 byte), appProtocolErrorCode (4 bytes), and\n  // reliableSize (1 byte) are empty\n  buf->trimEnd(6);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_RESET_STREAM));\n\n  auto parsedCapsule = parseWTResetStream(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTResetStreamAppProtocolErrorCodeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTResetStreamCapsule capsule{.streamId = 0x1,\n                               .appProtocolErrorCode = 0x12345678,\n                               .reliableSize = 0x10};\n  auto writeRes = writeWTResetStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that appProtocolErrorCode (4 bytes) and reliableSize (1 byte)\n  // are empty\n  buf->trimEnd(5);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_RESET_STREAM));\n\n  auto parsedCapsule = parseWTResetStream(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTResetStreamReliableSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTResetStreamCapsule capsule{.streamId = 0x1,\n                               .appProtocolErrorCode = 0x12345678,\n                               .reliableSize = 0x10};\n  auto writeRes = writeWTResetStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that reliableSize (1 byte) is empty\n  buf->trimEnd(1);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_RESET_STREAM));\n\n  auto parsedCapsule = parseWTResetStream(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTResetStreamCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTResetStreamCapsule capsule{.streamId = std::numeric_limits<uint64_t>::max(),\n                               .appProtocolErrorCode = 0x12345678,\n                               .reliableSize = 0x10};\n  auto writeRes = writeWTResetStream(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTStopSending) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStopSendingCapsule capsule{.streamId = 0x01,\n                               .appProtocolErrorCode = 0x12345678};\n  auto writeRes = writeWTStopSending(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STOP_SENDING));\n\n  auto parsedCapsule = parseWTStopSending(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n  ASSERT_EQ(parsedCapsule->appProtocolErrorCode, capsule.appProtocolErrorCode);\n}\n\nTEST(WebTransportFramerTest, WTStopSendingStreamIdError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStopSendingCapsule capsule{.streamId = 0x01,\n                               .appProtocolErrorCode = 0x12345678};\n  auto writeRes = writeWTStopSending(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that streamId (1 byte) and appProtocolErrorCode (4 bytes) are\n  // empty\n  buf->trimEnd(5);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STOP_SENDING));\n\n  auto parsedCapsule = parseWTStopSending(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStopSendingAppProtocolErrorCodeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStopSendingCapsule capsule{.streamId = 0x01,\n                               .appProtocolErrorCode = 0x12345678};\n  auto writeRes = writeWTStopSending(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that appProtocolErrorCode (4 bytes) is empty\n  buf->trimEnd(4);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STOP_SENDING));\n\n  auto parsedCapsule = parseWTStopSending(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStopSendingCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStopSendingCapsule capsule{.streamId = std::numeric_limits<uint64_t>::max(),\n                               .appProtocolErrorCode = 0x12345678};\n  auto writeRes = writeWTStopSending(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTStream) {\n  {\n    folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n    WTStreamCapsule capsule{.streamId = 0x01,\n                            .streamData =\n                                folly::IOBuf::copyBuffer(\"Hello, World!\"),\n                            .fin = true};\n    auto writeRes = writeWTStream(queue, capsule);\n    ASSERT_TRUE(writeRes.has_value());\n    auto buf = queue.move();\n    auto cursor = folly::io::Cursor(buf.get());\n    auto payloadLen = parseCapsuleHeader(\n        cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_WITH_FIN));\n\n    auto parsedCapsule = parseWTStream(cursor, payloadLen, capsule.fin);\n    ASSERT_TRUE(parsedCapsule.hasValue());\n    ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n    ASSERT_TRUE(\n        folly::IOBufEqualTo()(parsedCapsule->streamData, capsule.streamData));\n    ASSERT_EQ(parsedCapsule->fin, capsule.fin);\n  }\n\n  {\n    // test nullptr streamData\n    folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n    WTStreamCapsule capsule{\n        .streamId = 0x01, .streamData = nullptr, .fin = true};\n    auto writeRes = writeWTStream(queue, capsule);\n    ASSERT_TRUE(writeRes.has_value());\n    auto buf = queue.move();\n    auto cursor = folly::io::Cursor(buf.get());\n    auto payloadLen = parseCapsuleHeader(\n        cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_WITH_FIN));\n\n    auto parsedCapsule = parseWTStream(cursor, payloadLen, capsule.fin);\n    ASSERT_TRUE(parsedCapsule.hasValue());\n    ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n    ASSERT_EQ(parsedCapsule->streamData->computeChainDataLength(), 0);\n    ASSERT_EQ(parsedCapsule->fin, capsule.fin);\n  }\n}\n\nTEST(WebTransportFramerTest, WTStreamStreamIdError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamCapsule capsule{.streamId = 0x1,\n                          .streamData =\n                              folly::IOBuf::copyBuffer(\"Hello, World!\"),\n                          .fin = true};\n  auto writeRes = writeWTStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  // hack buf so that streamId (1 byte) and streamData (13 bytes) are empty\n  queue.trimEnd(14);\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_WITH_FIN));\n\n  auto parsedCapsule = parseWTStream(cursor, payloadLen, capsule.fin);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamDataError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamCapsule capsule{.streamId = 0x1,\n                          .streamData =\n                              folly::IOBuf::copyBuffer(\"Hello, World!\"),\n                          .fin = true};\n  auto writeRes = writeWTStream(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  // hack buf so that streamData (13 bytes) is empty\n  queue.trimEnd(13);\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_WITH_FIN));\n\n  auto parsedCapsule = parseWTStream(cursor, payloadLen, capsule.fin);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamCapsule capsule{.streamId = std::numeric_limits<uint64_t>::max(),\n                          .streamData =\n                              folly::IOBuf::copyBuffer(\"Hello, World!\"),\n                          .fin = true};\n  auto writeRes = writeWTStream(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTMaxData) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxDataCapsule capsule{100};\n  auto writeRes = writeWTMaxData(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_DATA));\n\n  auto parsedCapsule = parseWTMaxData(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumData, capsule.maximumData);\n}\n\nTEST(WebTransportFramerTest, WTMaxDataError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxDataCapsule capsule{100};\n  auto writeRes = writeWTMaxData(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumData (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_DATA));\n\n  auto parsedCapsule = parseWTMaxData(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTMaxDataCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxDataCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTMaxData(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamData) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamDataCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTMaxStreamData(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAM_DATA));\n\n  auto parsedCapsule = parseWTMaxStreamData(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n  ASSERT_EQ(parsedCapsule->maximumStreamData, capsule.maximumStreamData);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamDataStreamIdError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamDataCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTMaxStreamData(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that streamId (1 byte) and maximumStreamData (2 bytes) are\n  // empty\n  buf->trimEnd(3);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAM_DATA));\n\n  auto parsedCapsule = parseWTMaxStreamData(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamDataError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamDataCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTMaxStreamData(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreamData (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAM_DATA));\n\n  auto parsedCapsule = parseWTMaxStreamData(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamDataCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamDataCapsule capsule{.streamId =\n                                     std::numeric_limits<uint64_t>::max(),\n                                 .maximumStreamData = 100};\n  auto writeRes = writeWTMaxStreamData(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTMaxStreams) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{100};\n  auto writeRes = writeWTMaxStreams(queue, capsule, true);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAMS_BIDI));\n\n  auto parsedCapsule = parseWTMaxStreams(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumStreams, capsule.maximumStreams);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamsError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{100};\n  auto writeRes = writeWTMaxStreams(queue, capsule, true);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreams (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAMS_BIDI));\n\n  auto parsedCapsule = parseWTMaxStreams(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamsCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTMaxStreams(queue, capsule, true);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamsUni) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{100};\n  auto writeRes = writeWTMaxStreams(queue, capsule, false);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAMS_UNI));\n\n  auto parsedCapsule = parseWTMaxStreams(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumStreams, capsule.maximumStreams);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamsUniError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{100};\n  auto writeRes = writeWTMaxStreams(queue, capsule, false);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreams (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_MAX_STREAMS_UNI));\n\n  auto parsedCapsule = parseWTMaxStreams(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTMaxStreamsUniCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTMaxStreamsCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTMaxStreams(queue, capsule, false);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTDataBlocked) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTDataBlockedCapsule capsule{100};\n  auto writeRes = writeWTDataBlocked(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_DATA_BLOCKED));\n\n  auto parsedCapsule = parseWTDataBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumData, capsule.maximumData);\n}\n\nTEST(WebTransportFramerTest, WTDataBlockedError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTDataBlockedCapsule capsule{100};\n  auto writeRes = writeWTDataBlocked(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumData (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_DATA_BLOCKED));\n\n  auto parsedCapsule = parseWTDataBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTDataBlockedCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTDataBlockedCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTDataBlocked(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTStreamDataBlocked) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamDataBlockedCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTStreamDataBlocked(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_DATA_BLOCKED));\n\n  auto parsedCapsule = parseWTStreamDataBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->streamId, capsule.streamId);\n  ASSERT_EQ(parsedCapsule->maximumStreamData, capsule.maximumStreamData);\n}\n\nTEST(WebTransportFramerTest, WTStreamDataBlockedStreamIdError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamDataBlockedCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTStreamDataBlocked(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that streamId (1 byte) and maximumStreamData (2 bytes) are\n  // empty\n  buf->trimEnd(3);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_DATA_BLOCKED));\n\n  auto parsedCapsule = parseWTStreamDataBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamDataBlockedMaxStreamDataError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamDataBlockedCapsule capsule{.streamId = 0x1, .maximumStreamData = 100};\n  auto writeRes = writeWTStreamDataBlocked(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreamData (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAM_DATA_BLOCKED));\n\n  auto parsedCapsule = parseWTStreamDataBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamDataBlockedCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamDataBlockedCapsule capsule{.streamId =\n                                         std::numeric_limits<uint64_t>::max(),\n                                     .maximumStreamData = 100};\n  auto writeRes = writeWTStreamDataBlocked(queue, capsule);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlocked) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{100};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, true);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAMS_BLOCKED_BIDI));\n\n  auto parsedCapsule = parseWTStreamsBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumStreams, capsule.maximumStreams);\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlockedError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{100};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, true);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreams (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAMS_BLOCKED_BIDI));\n\n  auto parsedCapsule = parseWTStreamsBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlockedCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, true);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlockedUni) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{100};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, false);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAMS_BLOCKED_UNI));\n\n  auto parsedCapsule = parseWTStreamsBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->maximumStreams, capsule.maximumStreams);\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlockedUniError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{100};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, false);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  // hack buf so that maximumStreams (2 bytes) is empty\n  buf->trimEnd(2);\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::WT_STREAMS_BLOCKED_UNI));\n\n  auto parsedCapsule = parseWTStreamsBlocked(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, WTStreamsBlockedUniCapsuleSizeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  WTStreamsBlockedCapsule capsule{std::numeric_limits<uint64_t>::max()};\n  auto writeRes = writeWTStreamsBlocked(queue, capsule, false);\n  ASSERT_FALSE(writeRes.has_value());\n}\n\nTEST(WebTransportFramerTest, Datagram) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  DatagramCapsule capsule{folly::IOBuf::copyBuffer(\"Hello, World!\")};\n  auto writeRes = writeDatagram(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen =\n      parseCapsuleHeader(cursor, static_cast<uint64_t>(CapsuleType::DATAGRAM));\n\n  auto parsedCapsule = parseDatagram(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_TRUE(folly::IOBufEqualTo()(parsedCapsule->httpDatagramPayload,\n                                    capsule.httpDatagramPayload));\n}\n\nTEST(WebTransportFramerTest, DatagramError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  DatagramCapsule capsule{folly::IOBuf::copyBuffer(\"Hello, World!\")};\n  auto writeRes = writeDatagram(queue, capsule);\n  // hack buf so that httpDatagramPayload (13 bytes) is empty\n  queue.trimEnd(13);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen =\n      parseCapsuleHeader(cursor, static_cast<uint64_t>(CapsuleType::DATAGRAM));\n\n  auto parsedCapsule = parseDatagram(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, CloseWebTransportSession) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 0x12345678,\n                                          .applicationErrorMessage = \"BAD!\"};\n  auto writeRes = writeCloseWebTransportSession(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::CLOSE_WEBTRANSPORT_SESSION));\n\n  auto parsedCapsule = parseCloseWebTransportSession(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n  ASSERT_EQ(parsedCapsule->applicationErrorCode, capsule.applicationErrorCode);\n  ASSERT_EQ(parsedCapsule->applicationErrorMessage,\n            capsule.applicationErrorMessage);\n}\n\nTEST(WebTransportFramerTest, CloseWebTransportSessionErrorCodeError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 0x12345678,\n                                          .applicationErrorMessage = \"BAD!\"};\n  auto writeRes = writeCloseWebTransportSession(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  // hack buf so that applicationErrorCode (4 bytes) and applicationErrorMessage\n  // (4 bytes) are empty\n  queue.trimEnd(8);\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::CLOSE_WEBTRANSPORT_SESSION));\n\n  auto parsedCapsule = parseCloseWebTransportSession(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, CloseWebTransportSessionErrorMsgError) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = 0x12345678,\n                                          .applicationErrorMessage = \"BAD!\"};\n  auto writeRes = writeCloseWebTransportSession(queue, capsule);\n  ASSERT_TRUE(writeRes.has_value());\n  // hack buf so that applicationErrorMessage (4 bytes) is empty\n  queue.trimEnd(4);\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::CLOSE_WEBTRANSPORT_SESSION));\n\n  auto parsedCapsule = parseCloseWebTransportSession(cursor, payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasError());\n  EXPECT_EQ(parsedCapsule.error(), CapsuleCodec::ErrorCode::PARSE_UNDERFLOW);\n}\n\nTEST(WebTransportFramerTest, DrainWebTransportSession) {\n  folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()};\n  auto writeRes = writeDrainWebTransportSession(queue);\n  ASSERT_TRUE(writeRes.has_value());\n  auto buf = queue.move();\n  auto cursor = folly::io::Cursor(buf.get());\n  auto payloadLen = parseCapsuleHeader(\n      cursor, static_cast<uint64_t>(CapsuleType::DRAIN_WEBTRANSPORT_SESSION));\n\n  auto parsedCapsule = parseDrainWebTransportSession(payloadLen);\n  ASSERT_TRUE(parsedCapsule.hasValue());\n}\n"
  },
  {
    "path": "proxygen/lib/http/connpool/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_connpool_session_holder\n  SRCS\n    SessionHolder.cpp\n  DEPS\n    Folly::folly_io_async_async_socket\n    Folly::folly_random\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_session_session\n    Folly::folly_intrusive_list\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_http_connpool\n  SRCS\n    ServerIdleSessionController.cpp\n    SessionPool.cpp\n    ThreadIdleSessionController.cpp\n  DEPS\n    Folly::folly_io_async_event_base_manager\n  EXPORTED_DEPS\n    proxygen_http_connpool_session_holder\n    Folly::folly_futures_core\n    Folly::folly_io_async_async_base\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/connpool/Endpoint.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/SocketAddress.h>\n\nnamespace proxygen {\n\n/**\n * Simple class representing an endpoint by (hostname, port, http/s).\n * Optionally used by SessionHolder if caller wants to store sessions from\n * different endpoints in a single SessionPool.\n */\nclass Endpoint {\n public:\n  explicit Endpoint(const std::string& hostname, uint16_t port, bool isSecure)\n      : hostname_(hostname), port_(port), isSecure_(isSecure) {\n    hash_ =\n        std::hash<std::string>()(hostname_) ^ (port_ << 1) ^ (isSecure_ << 17);\n  }\n\n  explicit Endpoint(const folly::SocketAddress& addr, bool isSecure)\n      : Endpoint(addr.getAddressStr(), addr.getPort(), isSecure) {\n  }\n\n  [[nodiscard]] const std::string& getHostname() const {\n    return hostname_;\n  }\n\n  [[nodiscard]] uint16_t getPort() const {\n    return port_;\n  }\n\n  [[nodiscard]] bool isSecure() const {\n    return isSecure_;\n  }\n\n  [[nodiscard]] size_t getHash() const {\n    return hash_;\n  }\n\n  void describe(std::ostream& os) const {\n    os << hostname_ << \":\" << port_ << \":\" << (isSecure_ ? \"\" : \"in\")\n       << \"secure\";\n  }\n\n private:\n  std::string hostname_;\n  uint16_t port_{0};\n  std::size_t hash_{0};\n  bool isSecure_{false};\n};\n\nstruct EndpointHash {\n  std::size_t operator()(const Endpoint& e) const {\n    return e.getHash();\n  }\n};\n\nstruct EndpointEqual {\n  bool operator()(const Endpoint& lhs, const Endpoint& rhs) const {\n    return lhs.getHostname() == rhs.getHostname() &&\n           lhs.getPort() == rhs.getPort() && lhs.isSecure() == rhs.isSecure();\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/ServerIdleSessionController.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/connpool/ServerIdleSessionController.h>\n\nnamespace proxygen {\n\nfolly::Future<HTTPSessionBase*> ServerIdleSessionController::getIdleSession() {\n  folly::Promise<HTTPSessionBase*> promise;\n  folly::Future<HTTPSessionBase*> future = promise.getFuture();\n  SessionPool* maxPool = nullptr;\n\n  {\n    std::lock_guard<std::mutex> lock(lock_);\n    maxPool = popBestIdlePool();\n    if (markedForDeath_ || !maxPool || !maxPool->getEventBase()) {\n      return folly::makeFuture<HTTPSessionBase*>(nullptr);\n    }\n  }\n\n  if (maxPool->getEventBase()->isInEventBaseThread()) {\n    LOG(ERROR) << \"Idle session already belongs to current thread!\";\n    return folly::makeFuture<HTTPSessionBase*>(nullptr);\n  }\n\n  maxPool->getEventBase()->runInEventBaseThread(\n      [this, maxPool, promise = std::move(promise)]() mutable {\n        // Caller (in this case Server::getTransaction()) needs to guarantee\n        // that 'this' still exists.\n        HTTPSessionBase* session =\n            isMarkedForDeath() ? nullptr : maxPool->removeOldestIdleSession();\n        if (session) {\n          session->detachThreadLocals(true);\n        }\n        promise.setValue(session);\n      });\n  return future;\n}\n\nvoid ServerIdleSessionController::addIdleSession(const HTTPSessionBase* session,\n                                                 SessionPool* sessionPool) {\n  std::lock_guard<std::mutex> lock(lock_);\n  if (sessionMap_.find(session) != sessionMap_.end()) {\n    // removeIdleSession should've been called before re-adding\n    LOG(ERROR) << \"Session \" << session << \" already exists!\";\n    return;\n  }\n  if (sessionsByIdleAge_.size() < maxIdleCount_) {\n    auto newIt = sessionsByIdleAge_.insert(sessionsByIdleAge_.end(),\n                                           {session, sessionPool});\n    sessionMap_[session] = newIt;\n  }\n}\n\nvoid ServerIdleSessionController::removeIdleSession(\n    const HTTPSessionBase* session) {\n  std::lock_guard<std::mutex> lock(lock_);\n  auto it = sessionMap_.find(session);\n  if (it != sessionMap_.end()) {\n    sessionsByIdleAge_.erase(it->second);\n    sessionMap_.erase(it);\n  }\n}\n\nvoid ServerIdleSessionController::markForDeath() {\n  std::lock_guard<std::mutex> lock(lock_);\n  markedForDeath_ = true;\n  sessionMap_.clear();\n  sessionsByIdleAge_.clear();\n}\n\n// must be called under lock_\nSessionPool* FOLLY_NULLABLE ServerIdleSessionController::popBestIdlePool() {\n  if (!sessionsByIdleAge_.empty()) {\n    auto ret = *sessionsByIdleAge_.begin();\n    sessionsByIdleAge_.erase(sessionsByIdleAge_.begin());\n    sessionMap_.erase(ret.session);\n    return ret.sessionPool;\n  }\n  return nullptr;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/ServerIdleSessionController.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/connpool/SessionPool.h>\n\n#include <folly/futures/Future.h>\n\nnamespace proxygen {\n\n/**\n * ServerIdleSessionController keeps track of all idle sessions belonging\n * to a **single server** across all threads.\n *\n * Server class uses it to move idle transactions between threads, if necessary.\n * All public methods are thread-safe.\n */\nclass ServerIdleSessionController {\n public:\n  explicit ServerIdleSessionController() = default;\n\n  /**\n   * Transfer idle session from another thread, if available.\n   * Returns nullptr if nothing is available.\n   */\n  folly::Future<HTTPSessionBase*> getIdleSession();\n\n  /**\n   * Add/remove session info (called by SessionPool when state changes).\n   */\n  void addIdleSession(const HTTPSessionBase* session, SessionPool* sessionPool);\n  void removeIdleSession(const HTTPSessionBase* session);\n\n  /**\n   * Stop all session transfers.\n   */\n  void markForDeath();\n\n  /**\n   * Resize idle pool.\n   */\n  void setMaxIdleCount(unsigned int maxIdleCount) {\n    std::lock_guard<std::mutex> lock(lock_);\n    maxIdleCount_ = maxIdleCount;\n  }\n\n protected:\n  struct IdleSessionInfo {\n    const HTTPSessionBase* session;\n    SessionPool* sessionPool;\n  };\n\n  using IdleSessionList = std::list<IdleSessionInfo>;\n  using IdleSessionListIter = IdleSessionList::iterator;\n\n  /**\n   * Find available session pool (thread) to tranfer an idle session from.\n   * Remove it from the map.\n   */\n  SessionPool* FOLLY_NULLABLE popBestIdlePool();\n\n  bool isMarkedForDeath() {\n    std::lock_guard<std::mutex> lock(lock_);\n    return markedForDeath_;\n  }\n\n  std::mutex lock_;\n  /*\n   * List of idle sessions. Normally, addIdleSession() adds to the end and\n   * popBestIdlePool() removes from the beginning, thus keeping the list sorted\n   * by idle age.\n   * Additionally, we also support arbitrary removals if some session stops\n   * being idle or dies or gets re-used out of order if many threads attempt\n   * session transfer at the same time.\n   */\n  IdleSessionList sessionsByIdleAge_;\n  // Store iterators in sessionsByIdleAge_ to be able to find sessions.\n  std::unordered_map<const HTTPSessionBase*, IdleSessionListIter> sessionMap_;\n  bool markedForDeath_{false};\n\n  // Default idle pool size to 2.\n  unsigned int maxIdleCount_{2};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/SessionHolder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/connpool/SessionHolder.h>\n\n#include <folly/Random.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <glog/logging.h>\n\nusing folly::AsyncSocket;\nusing folly::SocketAddress;\n\nnamespace {\nconst double kJitterPct = 0.3;\n}\n\nnamespace proxygen {\n\nSessionHolder::SessionHolder(HTTPSessionBase* sess,\n                             Callback* parent,\n                             Stats* stats,\n                             Endpoint endpoint)\n    : session_(CHECK_NOTNULL(sess)),\n      parent_(CHECK_NOTNULL(parent)),\n      stats_(stats),\n      jitter_(folly::Random::randDouble(-kJitterPct, kJitterPct)),\n      endpoint_(std::move(endpoint)),\n      originalSessionInfoCb_(sess->getInfoCallback()) {\n  session_->setInfoCallback(this);\n}\n\nSessionHolder::~SessionHolder() {\n  CHECK(state_ == ListState::DETACHED);\n  CHECK(!listHook.is_linked());\n  CHECK(!secondaryListHook.is_linked());\n}\n\nbool SessionHolder::isPoolable(const HTTPSessionBase* sess) {\n  return !sess->isClosing() &&\n         (sess->getNumOutgoingStreams() || sess->isReusable());\n}\n\nbool SessionHolder::shouldAgeOut(std::chrono::milliseconds maxAge) const {\n  if (maxAge.count() <= 0) {\n    return false;\n  }\n  double sessMaxAge = (1 + jitter_) * maxAge.count();\n  auto age = millisecondsSince(session_->getSetupTransportInfo().acceptTime);\n  return age >= std::chrono::milliseconds(int64_t(sessMaxAge));\n}\n\nconst HTTPSessionBase& SessionHolder::getSession() const {\n  return *session_;\n}\n\nHTTPTransaction* SessionHolder::newTransaction(\n    HTTPTransaction::Handler* handler) {\n  return session_->newTransaction(handler);\n}\n\nstd::chrono::steady_clock::time_point SessionHolder::getLastUseTime() const {\n  return lastUseTime_;\n}\n\nvoid SessionHolder::drain() {\n  VLOG(4) << \"draining holder=\" << *this;\n  if (state_ != ListState::DETACHED) {\n    unlink();\n  }\n  if (stats_) {\n    // The connection hasn't closed yet, but it will. We won't find out\n    // about when it actually closes since we are setting the info\n    // callback to nullptr.\n    stats_->onConnectionClosed();\n    if (session_->hasActiveTransactions()) {\n      stats_->onConnectionDeactivated();\n    }\n  }\n  session_->setInfoCallback(originalSessionInfoCb_);\n  originalSessionInfoCb_ = nullptr;\n  parent_->addDrainingSession(session_);\n  session_->drain();\n  delete this;\n}\n\nvoid SessionHolder::closeWithReset() {\n  if (state_ != ListState::DETACHED) {\n    unlink();\n  }\n  if (stats_) {\n    // The connection hasn't closed yet, but it will. We won't find out\n    // about when it actually closes since we are setting the info\n    // callback to nullptr.\n    stats_->onConnectionClosed();\n    if (session_->hasActiveTransactions()) {\n      stats_->onConnectionDeactivated();\n    }\n  }\n  session_->setInfoCallback(originalSessionInfoCb_);\n  originalSessionInfoCb_ = nullptr;\n  session_->dropConnection();\n  delete this;\n}\n\nvoid SessionHolder::unlink() {\n  CHECK(parent_);\n  CHECK(listHook.is_linked());\n\n  switch (state_) {\n    case ListState::IDLE:\n      parent_->detachIdle(this);\n      break;\n    case ListState::PARTIAL:\n      parent_->detachPartiallyFilled(this);\n      break;\n    case ListState::FULL:\n      parent_->detachFilled(this);\n      break;\n    case ListState::DETACHED:\n      LOG(FATAL) << \"Inconsistentency between listHook.is_linked() and state_\";\n  }\n  state_ = ListState::DETACHED;\n}\n\nvoid SessionHolder::link() {\n  CHECK(state_ == ListState::DETACHED);\n  if (!parent_) {\n    return;\n  }\n\n  if (!isPoolable(session_)) {\n    VLOG(4) << *this << \" Not pooling session since it is not poolable\";\n    drain();\n    return;\n  }\n  lastUseTime_ = std::chrono::steady_clock::now();\n  auto curTxnCount = session_->getNumOutgoingStreams();\n  if (!session_->supportsMoreTransactions()) {\n    state_ = ListState::FULL;\n    parent_->attachFilled(this);\n  } else if (curTxnCount == 0 &&\n             session_->isDetachable(/*checkSocket=*/false)) {\n    state_ = ListState::IDLE;\n    parent_->attachIdle(this);\n  } else {\n    state_ = ListState::PARTIAL;\n    parent_->attachPartiallyFilled(this);\n  }\n}\n\nvoid SessionHolder::onCreate(const HTTPSessionBase&) {\n  LOG(FATAL) << \"onCreate() should not be reachable.\";\n}\n\nvoid SessionHolder::onIngressError(const HTTPSessionBase& session,\n                                   ProxygenError error) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onIngressError(session, error);\n  }\n}\n\nvoid SessionHolder::onRead(const HTTPSessionBase& session, size_t bytesRead) {\n  onRead(session, bytesRead, folly::none);\n}\nvoid SessionHolder::onRead(const HTTPSessionBase& session,\n                           size_t bytesRead,\n                           folly::Optional<HTTPCodec::StreamID> streamId) {\n  if (stats_) {\n    stats_->onRead(bytesRead);\n  }\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onRead(session, bytesRead, streamId);\n  }\n}\n\nvoid SessionHolder::onWrite(const HTTPSessionBase& session,\n                            size_t bytesWritten) {\n  if (stats_) {\n    stats_->onWrite(bytesWritten);\n  }\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onWrite(session, bytesWritten);\n  }\n}\n\nvoid SessionHolder::onRequestBegin(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onRequestBegin(session);\n  }\n}\n\nvoid SessionHolder::onRequestEnd(const HTTPSessionBase& session,\n                                 uint32_t maxIngressQueueSize) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onRequestEnd(session, maxIngressQueueSize);\n  }\n}\n\nvoid SessionHolder::onActivateConnection(const HTTPSessionBase& session) {\n  if (stats_) {\n    stats_->onConnectionActivated();\n  }\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onActivateConnection(session);\n  }\n}\n\nvoid SessionHolder::onDeactivateConnection(const HTTPSessionBase& sess) {\n  if (stats_) {\n    stats_->onConnectionDeactivated();\n  }\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onDeactivateConnection(sess);\n  }\n  handleTransactionDetached();\n}\n\nvoid SessionHolder::onDestroy(const HTTPSessionBase& session) {\n  if (state_ != ListState::DETACHED) {\n    unlink();\n  }\n  if (stats_) {\n    stats_->onConnectionClosed();\n  }\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onDestroy(session);\n  }\n  VLOG(4) << *this << \" connection to server was destroyed\";\n  delete this;\n}\n\nvoid SessionHolder::onIngressMessage(const HTTPSessionBase& session,\n                                     const HTTPMessage& msg) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onIngressMessage(session, msg);\n  }\n}\n\nvoid SessionHolder::onIngressLimitExceeded(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onIngressLimitExceeded(session);\n  }\n}\n\nvoid SessionHolder::onIngressPaused(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onIngressPaused(session);\n  }\n}\n\nvoid SessionHolder::onTransactionAttached(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onTransactionAttached(session);\n  }\n}\n\nvoid SessionHolder::onTransactionDetached(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onTransactionDetached(session);\n  }\n  handleTransactionDetached();\n}\n\nvoid SessionHolder::onPingReplySent(int64_t latency) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onPingReplySent(latency);\n  }\n}\n\nvoid SessionHolder::onPingReplyReceived() {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onPingReplyReceived();\n  }\n}\n\nvoid SessionHolder::onSettingsOutgoingStreamsFull(\n    const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onSettingsOutgoingStreamsFull(session);\n  }\n  if (state_ != ListState::DETACHED && state_ != ListState::FULL) {\n    unlink();\n    link();\n  }\n}\n\nvoid SessionHolder::onSettingsOutgoingStreamsNotFull(\n    const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onSettingsOutgoingStreamsNotFull(session);\n  }\n  if (state_ != ListState::DETACHED && state_ == ListState::FULL) {\n    unlink();\n    link();\n  }\n}\n\nvoid SessionHolder::onFlowControlWindowClosed(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onFlowControlWindowClosed(session);\n  }\n}\n\nvoid SessionHolder::onEgressBuffered(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onEgressBuffered(session);\n  }\n}\n\nvoid SessionHolder::onEgressBufferCleared(const HTTPSessionBase& session) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onEgressBufferCleared(session);\n  }\n}\n\nvoid SessionHolder::onSettings(const HTTPSessionBase& sess,\n                               const SettingsList& settings) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onSettings(sess, settings);\n  }\n}\n\nvoid SessionHolder::onSettingsAck(const HTTPSessionBase& sess) {\n  if (originalSessionInfoCb_) {\n    originalSessionInfoCb_->onSettingsAck(sess);\n  }\n}\n\nvoid SessionHolder::describe(std::ostream& os) const {\n  const auto transport = session_->getTransport();\n  if (!transport) {\n    os << \"(nullptr)\";\n    return;\n  }\n  const AsyncSocket* sock = transport->getUnderlyingTransport<AsyncSocket>();\n  if (sock) {\n    os << \"fd=\" << sock->getNetworkSocket().toFd();\n\n    SocketAddress localAddr, serverAddr;\n    try {\n      sock->getLocalAddress(&localAddr);\n      sock->getPeerAddress(&serverAddr);\n    } catch (...) {\n      // The socket might have been disconnected.\n    }\n\n    if (localAddr.isInitialized()) {\n      os << \",local=\" << localAddr;\n    } else {\n      os << \",lp=-1\";\n    }\n\n    if (serverAddr.isInitialized()) {\n      os << \",\" << serverAddr;\n    } else {\n      os << \",-\";\n    }\n  } else {\n    os << \"fd=-1,lp=-1,-\";\n  }\n  os << \",listState=\" << uint32_t(state_);\n}\n\nvoid SessionHolder::handleTransactionDetached() {\n  CHECK(state_ != ListState::DETACHED);\n  unlink();\n  link();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/SessionHolder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/IntrusiveList.h>\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <proxygen/lib/http/connpool/Endpoint.h>\n#include <proxygen/lib/http/session/HTTPSessionBase.h>\n\nnamespace proxygen {\n\nclass HTTPMessage;\n\n/**\n * This class is essentially an implementation detail for SessionPool. It\n * encapsulates a single HTTPSessionBase and manages which list in the\n * SessionPool it should be a part of.\n */\nclass SessionHolder : private HTTPSessionBase::InfoCallback {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void detachIdle(SessionHolder*) = 0;\n    virtual void detachPartiallyFilled(SessionHolder*) = 0;\n    virtual void detachFilled(SessionHolder*) = 0;\n    virtual void attachIdle(SessionHolder*) = 0;\n    virtual void attachPartiallyFilled(SessionHolder*) = 0;\n    virtual void attachFilled(SessionHolder*) = 0;\n    virtual void addDrainingSession(HTTPSessionBase*) = 0;\n  };\n\n  class Stats {\n   public:\n    virtual ~Stats() = default;\n    virtual void onConnectionCreated() = 0;\n    virtual void onConnectionClosed() = 0;\n    virtual void onConnectionActivated() = 0;\n    virtual void onConnectionDeactivated() = 0;\n    virtual void onRead(size_t bytesRead) = 0;\n    virtual void onWrite(size_t bytesWritten) = 0;\n  };\n\n  explicit SessionHolder(HTTPSessionBase*,\n                         Callback*,\n                         Stats* = nullptr,\n                         Endpoint = Endpoint(\"\", 0, false));\n  ~SessionHolder() override;\n\n  HTTPSessionBase* release() {\n    if (listHook.is_linked()) {\n      unlink();\n    } else {\n      state_ = ListState::DETACHED;\n    }\n    auto session = session_;\n    session->setInfoCallback(originalSessionInfoCb_);\n    session_ = nullptr;\n    delete this;\n    return session;\n  }\n\n  /**\n   * Returns true if the given session can be wrapped in a\n   * SessionHolder. This function does *not* imply that the session is\n   * or isn't idle. It returns true iff a SessionHolder can be constructed\n   * around it.\n   */\n  static bool isPoolable(const HTTPSessionBase*);\n\n  [[nodiscard]] const HTTPSessionBase& getSession() const;\n  HTTPTransaction* newTransaction(HTTPTransaction::Handler* upstreamHandler);\n  void drain();\n\n  /**\n   * This will immediately delete the SessionHolder and the underlying session.\n   * When this function returns, the SessionHolder has been deleted.\n   */\n  void closeWithReset();\n\n  [[nodiscard]] std::chrono::steady_clock::time_point getLastUseTime() const;\n\n  /**\n   * Unlink this session holder instance from the necessary session lists..\n   * This is achieved by calling the SessionHolder::Callbacks.\n   */\n  void unlink();\n\n  /**\n   * Link this session holder instance to the necessary session lists. This is\n   * achieved by calling the SessionHolder::Callbacks.\n   */\n  void link();\n\n  [[nodiscard]] bool shouldAgeOut(std::chrono::milliseconds maxAge) const;\n  void describe(std::ostream& os) const;\n\n  Endpoint getEndpoint() {\n    return endpoint_;\n  }\n\n  friend std::ostream& operator<<(std::ostream& os, const SessionHolder& conn) {\n    conn.describe(os);\n    return os;\n  }\n\n  // HTTPSession::InfoCallback\n  void onCreate(const HTTPSessionBase&) override;\n  void onIngressError(const HTTPSessionBase&, ProxygenError) override;\n  void onIngressEOF() override {\n  }\n  void onRead(const HTTPSessionBase&, size_t bytesRead) override;\n  void onRead(const HTTPSessionBase& sess,\n              size_t bytesRead,\n              folly::Optional<HTTPCodec::StreamID> /*stream id*/) override;\n  void onWrite(const HTTPSessionBase&, size_t bytesWritten) override;\n  void onRequestBegin(const HTTPSessionBase&) override;\n  void onRequestEnd(const HTTPSessionBase&,\n                    uint32_t maxIngressQueueSize) override;\n  void onActivateConnection(const HTTPSessionBase&) override;\n  void onDeactivateConnection(const HTTPSessionBase&) override;\n  void onDestroy(const HTTPSessionBase&) override;\n  void onIngressMessage(const HTTPSessionBase&, const HTTPMessage&) override;\n  void onIngressLimitExceeded(const HTTPSessionBase&) override;\n  void onIngressPaused(const HTTPSessionBase&) override;\n  void onTransactionAttached(const HTTPSessionBase&) override;\n  void onTransactionDetached(const HTTPSessionBase&) override;\n  void onPingReplySent(int64_t latency) override;\n  void onPingReplyReceived() override;\n  void onSettingsOutgoingStreamsFull(const HTTPSessionBase&) override;\n  void onSettingsOutgoingStreamsNotFull(const HTTPSessionBase&) override;\n  void onFlowControlWindowClosed(const HTTPSessionBase&) override;\n  void onEgressBuffered(const HTTPSessionBase&) override;\n  void onEgressBufferCleared(const HTTPSessionBase&) override;\n  void onSettings(const HTTPSessionBase&, const SettingsList&) override;\n  void onSettingsAck(const HTTPSessionBase&) override;\n\n  // Hook in the first session pool list.\n  folly::SafeIntrusiveListHook listHook;\n\n  // Hook in the second session pool list if necessary.\n  folly::SafeIntrusiveListHook secondaryListHook;\n\n private:\n  void handleTransactionDetached();\n\n  enum class ListState {\n    DETACHED = 0,\n    IDLE = 1,\n    PARTIAL = 2,\n    FULL = 3,\n  };\n\n  HTTPSessionBase* session_;\n  Callback* parent_;\n  Stats* stats_;\n  std::chrono::steady_clock::time_point lastUseTime_; // init'd in link()\n  double jitter_;\n  ListState state_{ListState::DETACHED};\n  Endpoint endpoint_;\n  HTTPSessionBase::InfoCallback* originalSessionInfoCb_;\n};\nusing SessionList =\n    folly::CountedIntrusiveList<SessionHolder, &SessionHolder::listHook>;\n\nusing SecondarySessionList =\n    folly::CountedIntrusiveList<SessionHolder,\n                                &SessionHolder::secondaryListHook>;\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/SessionPool.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/connpool/ServerIdleSessionController.h>\n#include <proxygen/lib/http/connpool/SessionPool.h>\n#include <proxygen/lib/http/connpool/ThreadIdleSessionController.h>\n\n#include <chrono>\n#include <folly/io/async/EventBaseManager.h>\n\nnamespace proxygen {\n\nSessionPool::SessionPool(\n    SessionHolder::Stats* stats,\n    uint32_t maxConns,\n    std::chrono::milliseconds timeout,\n    std::chrono::milliseconds maxAge,\n    ThreadIdleSessionController* threadIdleSessionController,\n    ServerIdleSessionController* serverIdleSessionController)\n    : stats_(stats),\n      maxConns_(maxConns),\n      timeout_(timeout),\n      maxAge_(maxAge),\n      threadIdleSessionController_(threadIdleSessionController),\n      serverIdleSessionController_(serverIdleSessionController),\n      // Here we rely on workers setting evb in EventBaseManager.\n      evb_(folly::EventBaseManager::get()->getEventBase()) {\n}\n\nSessionPool::~SessionPool() {\n  drainSessionList(idleSessionList_);\n  drainSessionList(unfilledSessionList_);\n  drainSessionList(fullSessionList_);\n  DCHECK(empty());\n}\n\nvoid SessionPool::setMaxIdleSessions(uint32_t num) {\n  maxConns_ = num;\n  purgeExcessIdleSessions();\n}\n\nuint32_t SessionPool::getMaxIdleSessions() const {\n  return maxConns_;\n}\n\nvoid SessionPool::setTimeout(std::chrono::milliseconds duration) {\n  timeout_ = duration;\n}\n\nstd::chrono::milliseconds SessionPool::getTimeout() const {\n  return timeout_;\n}\n\nuint32_t SessionPool::getNumIdleSessions() const {\n  return idleSessionList_.size();\n}\n\nuint32_t SessionPool::getNumActiveSessions() const {\n  return unfilledSessionList_.size() + fullSessionList_.size();\n}\n\nuint32_t SessionPool::getNumActiveNonFullSessions() const {\n  return unfilledSessionList_.size();\n}\n\nuint32_t SessionPool::getNumFullSessions() const {\n  return fullSessionList_.size();\n}\n\nuint32_t SessionPool::getNumSessions() const {\n  return idleSessionList_.size() + unfilledSessionList_.size() +\n         fullSessionList_.size();\n}\n\nbool SessionPool::empty() const {\n  return idleSessionList_.empty() && unfilledSessionList_.empty() &&\n         fullSessionList_.empty();\n}\n\nvoid SessionPool::putSession(HTTPSessionBase* session) {\n  if (SessionHolder::isPoolable(session)) {\n    // Constructing the session holder automatically puts it on the\n    // correct list (one of [idle, unfilled, full])\n    auto holder = new SessionHolder(session, this, stats_);\n    holder->link();\n  } else {\n    // this is equivalent to what happens in SessionHolder::link which is\n    // called from the SessionHolder ctor, but handle separately to avoid\n    // needless allocation/deallocation.\n    addDrainingSession(session);\n    session->drain();\n  }\n  purgeExcessIdleSessions();\n}\n\nHTTPTransaction* SessionPool::getTransaction(\n    HTTPTransaction::Handler* upstreamHandler) {\n  auto txn = attemptOpenTransaction(upstreamHandler, unfilledSessionList_);\n  if (!txn) {\n    purgeExcessIdleSessions();\n    txn = attemptOpenTransaction(upstreamHandler, idleSessionList_);\n  }\n  return txn;\n}\n\nvoid SessionPool::purgeExcessIdleSessions() {\n  auto thresh = std::chrono::steady_clock::now() - getTimeout();\n\n  CHECK_LE(idleSessionList_.size(), std::numeric_limits<uint32_t>::max());\n  int64_t excess =\n      static_cast<int64_t>(idleSessionList_.size()) - getMaxIdleSessions();\n  while (!idleSessionList_.empty()) {\n    SessionHolder* holder = &idleSessionList_.front();\n    if (holder->getLastUseTime() > thresh && excess <= 0) {\n      break;\n    }\n    --excess;\n    holder->drain();\n  }\n}\n\nHTTPSessionBase* FOLLY_NULLABLE SessionPool::removeOldestIdleSession() {\n  if (!idleSessionList_.empty()) {\n    SessionHolder* holder = &idleSessionList_.front();\n    CHECK_NOTNULL(holder);\n    return holder->release();\n  }\n  return nullptr;\n}\n\nvoid SessionPool::drainAllSessions() {\n  drainSessionList(idleSessionList_);\n  drainSessionList(unfilledSessionList_);\n  drainSessionList(fullSessionList_);\n}\n\nvoid SessionPool::closeWithReset() {\n  closeSessionListWithReset(idleSessionList_);\n  closeSessionListWithReset(unfilledSessionList_);\n  closeSessionListWithReset(fullSessionList_);\n}\n\nvoid SessionPool::drainSessionList(SessionList& list) {\n  while (!list.empty()) {\n    SessionHolder& holder = list.back();\n    holder.drain();\n  }\n}\n\nvoid SessionPool::closeSessionListWithReset(SessionList& list) {\n  while (!list.empty()) {\n    SessionHolder& holder = list.back();\n    holder.closeWithReset();\n  }\n}\n\nHTTPTransaction* SessionPool::attemptOpenTransaction(\n    HTTPTransaction::Handler* upstreamHandler, SessionList& list) {\n  SessionHolder* holder = nullptr;\n  while (!list.empty()) {\n    holder = &list.front();\n    if (holder->shouldAgeOut(maxAge_)) {\n      holder->drain(); // implicit unlink and delete\n      continue;\n    }\n    auto txn = holder->newTransaction(upstreamHandler);\n    holder->unlink();\n    holder->link();\n    if (txn) {\n      return txn;\n    }\n    // If we weren't able to get a transaction, then link() caused it to\n    // move to the full list, so this loop should eventually terminate\n  }\n  return nullptr;\n}\n\n// SessionHolder::Callback methods\n\nvoid SessionPool::detachIdle(SessionHolder* sess) {\n  idleSessionList_.erase(idleSessionList_.iterator_to(*sess));\n\n  if (threadIdleSessionController_) {\n    threadIdleSessionController_->onDetachIdle(sess);\n  }\n  if (serverIdleSessionController_) {\n    serverIdleSessionController_->removeIdleSession(&sess->getSession());\n  }\n}\n\nvoid SessionPool::detachPartiallyFilled(SessionHolder* sess) {\n  unfilledSessionList_.erase(unfilledSessionList_.iterator_to(*sess));\n}\n\nvoid SessionPool::detachFilled(SessionHolder* sess) {\n  fullSessionList_.erase(fullSessionList_.iterator_to(*sess));\n}\n\n/**\n * Always push to the back and pop from the front (FIFO). This prevents\n * slow servers from being excessively re-used and drawing load to themselves.\n */\nvoid SessionPool::attachIdle(SessionHolder* sess) {\n  if (getMaxIdleSessions() == 0 ||\n      !sess->getSession().supportsMoreTransactions() ||\n      sess->shouldAgeOut(maxAge_)) {\n    idleSessionList_.push_back(*sess);\n    sess->drain();\n  } else {\n    idleSessionList_.push_back(*sess);\n    if (serverIdleSessionController_) {\n      serverIdleSessionController_->addIdleSession(&sess->getSession(), this);\n    }\n    if (threadIdleSessionController_) {\n      threadIdleSessionController_->onAttachIdle(sess);\n    }\n    purgeExcessIdleSessions();\n  }\n}\n\nvoid SessionPool::attachPartiallyFilled(SessionHolder* sess) {\n  // round robin partially full sessions to reduce the chance of GOAWAY\n  // race conditions from the server.\n  unfilledSessionList_.push_back(*sess);\n}\n\nvoid SessionPool::attachFilled(SessionHolder* sess) {\n  fullSessionList_.push_back(*sess);\n}\n\nvoid SessionPool::addDrainingSession(HTTPSessionBase* /*session*/) {\n}\n\nstd::ostream& operator<<(std::ostream& os, const SessionPool& pool) {\n  os << \"[idle=\" << pool.getNumIdleSessions()\n     << \", partial=\" << pool.getNumActiveNonFullSessions()\n     << \", full=\" << +pool.getNumFullSessions() << \"]\";\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/SessionPool.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n\n#include <proxygen/lib/http/connpool/SessionHolder.h>\n\nnamespace proxygen {\n\nclass HTTPTransaction;\nclass ThreadIdleSessionController;\nclass ServerIdleSessionController;\n\n/**\n * This class stores HTTPSessionBase objects. It simplifies a lot of\n * the process of checking when a session goes bad, invalidating\n * references to it if it is destroyed, and tracking which sessions can\n * support more transactions and which cannot (i.e. it can store both\n * HTTP/1.1 and HTTP2 sessions at the same time).\n *\n * It can only be used from one thread, and sessions are not shared\n * between threads.\n *\n * Destroying a SessionPool will cause all upstream sessions to be\n * immediately forced closed. To drain this session pool gracefully, call\n * drain(). This will immediately close with FIN any idle sessions. As\n * active sessions become idle, they will eventually be closed too.\n *\n * After the drain timeout is reached, delete the SessionPool to force\n * closed any remaining sessions. Remember that this object must be\n * deleted in the event base thread it was used in.\n */\nclass SessionPool : private SessionHolder::Callback {\n public:\n  /**\n   * Construct an empty SessionPool.\n   *\n   * @param stats An optional interface for stats.\n   * @param maxPooledSessions The number of sessions allowed in this pool\n   * @param idleTimeout The duration in milliseconds after which a session\n   *                    that has no outgoing transactions may be purged\n   *                    from the pool.\n   * @param maxAge The maximum lifetime of a session in milliseconds.  After\n   *               this period elapses the session will be (somewhat lazily)\n   *               drained and purged from the pool.\n   * @param threadIdleSessionController - An optional component that allows\n   * pruning idle sessions across different sessions pools.\n   * @param serverIdleSessionController - An optional component that allows\n   * moving idle sessions between threads.\n   */\n\n  explicit SessionPool(\n      SessionHolder::Stats* stats = nullptr,\n      uint32_t maxPooledSessions = 1,\n      std::chrono::milliseconds idleTimeout = std::chrono::milliseconds(1000),\n      std::chrono::milliseconds maxAge = std::chrono::milliseconds(0),\n      ThreadIdleSessionController* threadIdleSessionController = nullptr,\n      ServerIdleSessionController* serverIdleSessionController = nullptr);\n  /**\n   * Destroying a SessionPool causes the sessions within it to drain. When\n   * a session becomes idle (no outgoing transactions) it will close\n   * automatically.\n   */\n  ~SessionPool() override;\n\n  /**\n   * Set/get the maximum number of idle sessions that can be\n   * pooled. Lowering the maximum number of sessions purges excess idle\n   * sessions.\n   */\n  void setMaxIdleSessions(uint32_t num);\n  [[nodiscard]] uint32_t getMaxIdleSessions() const;\n\n  /**\n   * Set/get the number of milliseconds that a session must remain\n   * unused before it may be purged.\n   */\n  void setTimeout(std::chrono::milliseconds);\n  [[nodiscard]] std::chrono::milliseconds getTimeout() const;\n\n  /**\n   * Returns the number of idle sessions. That is, sessions with no open\n   * outgoing transactions.\n   */\n  [[nodiscard]] uint32_t getNumIdleSessions() const;\n\n  /**\n   * Returns the number of sessions that already have at least one\n   * outgoing transaction open yet still support opening at least one more\n   * outgoing transaction.\n   */\n  [[nodiscard]] uint32_t getNumActiveNonFullSessions() const;\n\n  /**\n   * Returns the number of sessions that already have the maximum number\n   * of outgoing transactions open on them.\n   */\n  [[nodiscard]] uint32_t getNumFullSessions() const;\n\n  /**\n   * Returns the number of active sessions (txns > 0).\n   */\n  [[nodiscard]] uint32_t getNumActiveSessions() const;\n\n  /**\n   * Returns the total number of pooled sessions, regardless of activity.\n   */\n  [[nodiscard]] uint32_t getNumSessions() const;\n\n  /**\n   * Returns true if this SessionPool has no sessions in it. This implies\n   * getNumSessions() == 0\n   */\n  [[nodiscard]] bool empty() const;\n\n  /**\n   * Call this function to add a newly-created session to this\n   * server to this object's session pool. This SessionPool object will\n   * now manage the session. After passing a session to this function, you\n   * must not directly open any more transactions on it. If the session is\n   * not in a good state, it will be drained and eventually closed.\n   *\n   * If the session has an info callback set, it would continue to be invoked\n   * until the session is destroyed. Ensure the callback lifetime is at least as\n   * long as the session lifetime or reset the info callback to nullptr before\n   * putting the session in the pool.\n   *\n   * @param session The session to put in the pool\n   */\n  void putSession(HTTPSessionBase* session);\n\n  /**\n   * Gets a transaction from the first usable session in the session\n   * pools. There are 3 session session pools: one pool contains\n   * sessions with no outgoing transactions in 'idleSessionList_'.\n   * 'fullSessionList_' contains sessions  that are already completely in\n   * use and don't support any more transactions. The last pool is\n   * 'unfilledSessionList_' and contains sessions that are in\n   * use, but that can support more outgoing transactions.\n   *\n   * This function checks 'unfilledSessionList_' first. If no sessions are\n   * found, it checks idleSessionList_. If still no session is found,\n   * nullptr is returned.\n   */\n  HTTPTransaction* getTransaction(HTTPTransaction::Handler*);\n\n  /**\n   * Remove oldest idle session from idleSessionList_.\n   */\n  HTTPSessionBase* FOLLY_NULLABLE removeOldestIdleSession();\n\n  /**\n   * Drain all sessions even if the idle timeout has not expired.\n   */\n  void drainAllSessions();\n\n  /**\n   * Immediately close the socket in both directions for all sessions in the\n   * pool, discarding any queued writes that haven't yet been transferred to\n   * the kernel, and send a RST to the client.\n   *\n   * All transactions receive onWriteError with errorCode\n   */\n  void closeWithReset();\n\n  folly::EventBase* getEventBase() {\n    return evb_;\n  }\n\n private:\n  /**\n   * Purge the excess idle sessions according to the configured limits.\n   */\n  void purgeExcessIdleSessions();\n\n  /**\n   * Calls drain() on all the sessions in the list and empties the list.\n   */\n  void drainSessionList(SessionList& list);\n\n  /**\n   * Calls dropConnection() and all sessions to empty the list.\n   */\n  void closeSessionListWithReset(SessionList& list);\n\n  /**\n   * Attempt to open a transaction on one of the sessions in the given\n   * list. Return the transaction if successful, else nullptr.\n   */\n  HTTPTransaction* attemptOpenTransaction(\n      HTTPTransaction::Handler* upstreamHandler, SessionList& list);\n\n  // SessionHolder::Callback methods\n  void detachIdle(SessionHolder*) override;\n  void detachPartiallyFilled(SessionHolder*) override;\n  void detachFilled(SessionHolder*) override;\n  void attachIdle(SessionHolder*) override;\n  void attachPartiallyFilled(SessionHolder*) override;\n  void attachFilled(SessionHolder*) override;\n  void addDrainingSession(HTTPSessionBase*) override;\n\n  SessionHolder::Stats* stats_{nullptr};\n  // Max number of connections stored in the pool.\n  uint32_t maxConns_;\n  std::chrono::milliseconds timeout_;\n  std::chrono::milliseconds maxAge_;\n\n  // List of all idle sessions in this SessionPool. Sessions\n  // are sorted in descending order of lastUseTime in the list.\n  SessionList idleSessionList_;\n  // List of active sessions which may have space for\n  // another transaction. Sessions are sorted in descending order of\n  // lastUseTime in the list. Note that this list will never contain\n  // sessions that are using a serial L7 protocol like HTTP/1.0 (and\n  // 1.1 since we don't support pipelining).\n  SessionList unfilledSessionList_;\n  // List of active sessions are full and cannot open any more\n  // transactions.\n  SessionList fullSessionList_;\n  // Manages idle sessions for the same thread across servers.\n  ThreadIdleSessionController* threadIdleSessionController_{nullptr};\n  // Manages idle sessions for the same server across threads.\n  ServerIdleSessionController* serverIdleSessionController_{nullptr};\n\n  folly::EventBase* const evb_{nullptr};\n};\n\nstd::ostream& operator<<(std::ostream& os, const SessionPool& pool);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/ThreadIdleSessionController.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/connpool/ThreadIdleSessionController.h>\n\nnamespace proxygen {\n\nThreadIdleSessionController::ThreadIdleSessionController(\n    uint32_t totalIdleSessions)\n    : totalIdleSessions_(totalIdleSessions) {\n}\n\nvoid ThreadIdleSessionController::onAttachIdle(SessionHolder* holder) {\n  idleSessionsLRU_.push_back(*holder);\n  purgeExcessIdleSessions();\n}\n\nvoid ThreadIdleSessionController::onDetachIdle(SessionHolder* holder) {\n  idleSessionsLRU_.erase(idleSessionsLRU_.iterator_to(*holder));\n}\n\nvoid ThreadIdleSessionController::purgeExcessIdleSessions() {\n  while (idleSessionsLRU_.size() > totalIdleSessions_) {\n    SessionHolder& holder = idleSessionsLRU_.front();\n    holder.drain();\n  }\n}\n\nuint32_t ThreadIdleSessionController::getTotalIdleSessions() const {\n  return idleSessionsLRU_.size();\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/ThreadIdleSessionController.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/connpool/SessionHolder.h>\n#include <proxygen/lib/http/connpool/SessionPool.h>\n\n/**\n * ThreadIdleSessionController keeps track of all idle sessions in a set of\n * session pools belonging to a **single thread** but potentially different\n * servers.\n *\n * The component will observe idle session pool changes and will proactively\n * purge idle sessions once a given limit is reached.\n *\n * Some benefits of using the ThreadIdleSessionController are the following:\n *    * Define a tighter bound on the total number of idle connections that are\n *    open in the system.\n *    * If you have an uneven number of connections to upstream destinations,\n *    reclaiming connections to lesser used destinations will allow for better\n *    usage of the resources in the system.\n */\nnamespace proxygen {\n\nclass ThreadIdleSessionController {\n public:\n  /**\n   * Construct an idle session controller.\n   * @param totalIdleSessions The maximum number of idle sessions on a given\n   * thread.\n   */\n  explicit ThreadIdleSessionController(uint32_t totalIdleSessions = 0);\n  /*\n   * Callback used to indicate a new session has been attached.\n   */\n  void onAttachIdle(SessionHolder*);\n\n  /*\n   * Callback used a session has been detached.\n   */\n  void onDetachIdle(SessionHolder*);\n\n  /**\n   * Purges the excess idle sessions in the system.\n   */\n  void purgeExcessIdleSessions();\n\n  /**\n   * Get the number of total idle sessions.\n   */\n  [[nodiscard]] uint32_t getTotalIdleSessions() const;\n\n private:\n  uint32_t totalIdleSessions_;\n  SecondarySessionList idleSessionsLRU_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/connpool/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET ConnpoolTests\n  SOURCES\n    SessionPoolTest.cpp\n  DEPENDS\n    proxygen\n    testtransport\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/connpool/test/SessionPoolTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/connpool/test/SessionPoolTestFixture.h>\n\n#include <proxygen/lib/http/connpool/ServerIdleSessionController.h>\n#include <proxygen/lib/http/connpool/SessionHolder.h>\n#include <proxygen/lib/http/connpool/SessionPool.h>\n#include <proxygen/lib/http/connpool/ThreadIdleSessionController.h>\n\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GFlags.h>\n#include <folly/synchronization/Baton.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nTEST_F(SessionPoolFixture, ParallelPoolChangedMaxSessions) {\n  SessionPool p(this, 1);\n  HTTPCodec::Callback* cb = nullptr;\n  auto codec = makeParallelCodec();\n  EXPECT_CALL(*codec, setCallback(_)).WillRepeatedly(SaveArg<0>(&cb));\n  p.putSession(makeSession(std::move(codec)));\n\n  // inserted session should be in IDLE state\n  EXPECT_EQ(p.getNumSessions(), 1);\n  EXPECT_EQ(p.getNumIdleSessions(), 1);\n\n  // blocked on opening streams should transition the session to filled state\n  cb->onSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 0}});\n  evb_.loop();\n\n  EXPECT_EQ(p.getNumSessions(), 1);\n  EXPECT_EQ(p.getNumFullSessions(), 1);\n}\n\nTEST_F(SessionPoolFixture, SerialPoolBasic) {\n  SessionPool p(this, 1);\n  p.putSession(makeSerialSession());\n  auto txn = p.getTransaction(this);\n  ASSERT_TRUE(txn != nullptr);\n  ASSERT_TRUE(p.getTransaction(this) == nullptr);\n\n  // Clear the pool\n  p.setMaxIdleSessions(0);\n\n  // Drop the transaction. All transactions on the sessions in the pool\n  // must be completed before the pool can be destroyed\n  txn->sendAbort();\n\n  ASSERT_EQ(activated_, 1);\n  ASSERT_EQ(deactivated_, 1);\n  evb_.loop();\n  ASSERT_EQ(closed_, 1);\n}\n\nTEST_F(SessionPoolFixture, ParallelPoolBasic) {\n  const int numTxns = 32;\n  HTTPTransaction* txns[numTxns];\n\n  SessionPool p(this, 1);\n  p.putSession(makeParallelSession());\n\n  for (int i = 0; i < numTxns; ++i) {\n    txns[i] = p.getTransaction(this);\n    ASSERT_TRUE(txns[i] != nullptr);\n  }\n\n  // Clear the pool\n  p.setMaxIdleSessions(0);\n\n  // Drop the transactions. All transactions on the sessions in the pool\n  // must be completed before the pool can be destroyed\n  for (int i = 0; i < numTxns; ++i) {\n    txns[i]->sendAbort();\n  }\n\n  ASSERT_EQ(activated_, 1);\n  ASSERT_EQ(deactivated_, 1);\n  evb_.loop();\n  ASSERT_EQ(closed_, 1);\n}\n\nTEST_F(SessionPoolFixture, SerialPoolPurge) {\n  // Put more sessions into the pool than can fit. Then open several\n  // transactions on this pool and make sure we can't get out more\n  // transactions than the size of the pool.\n  const int sessionLimit = 10;\n  const int sessionPut = 12;\n  HTTPTransaction* txns[sessionPut];\n\n  SessionPool p(this, sessionLimit);\n  for (int i = 0; i < sessionPut; ++i) {\n    p.putSession(makeSerialSession());\n  }\n\n  evb_.loop();\n  ASSERT_EQ(activated_, 0);\n  ASSERT_EQ(deactivated_, 0);\n  ASSERT_EQ(closed_, sessionPut - sessionLimit);\n\n  for (int i = 0; i < sessionPut; ++i) {\n    txns[i] = p.getTransaction(this);\n    if (i < sessionLimit) {\n      ASSERT_TRUE(txns[i] != nullptr);\n    } else {\n      ASSERT_TRUE(txns[i] == nullptr);\n    }\n  }\n  ASSERT_EQ(activated_, sessionLimit);\n\n  // Clear the pool\n  p.setMaxIdleSessions(0);\n  // The txns are still active, so nothing should have been deactivated yet.\n  ASSERT_EQ(deactivated_, 0);\n\n  // Drop the transactions. All transactions on the sessions in the pool\n  // must be completed before the pool can be destroyed\n  for (int i = 0; i < sessionPut; ++i) {\n    if (txns[i]) {\n      txns[i]->sendAbort();\n    }\n  }\n  evb_.loop();\n  ASSERT_EQ(activated_, sessionLimit);\n  ASSERT_EQ(deactivated_, sessionLimit);\n  ASSERT_EQ(closed_, sessionPut);\n}\n\nTEST_F(SessionPoolFixture, ParallelPoolLists) {\n  // Test where we put in 2 parallel sessions into the pool. Ensure that\n  // the first one fills up before we start using the second.\n  SessionPool p(this, 2);\n  std::vector<HTTPTransaction*> txnsSess1;\n  std::vector<HTTPTransaction*> txnsSess2;\n\n  auto sess1 = makeParallelSession();\n  auto sess2 = makeParallelSession();\n  p.putSession(sess1);\n  p.putSession(sess2);\n  txnsSess1.push_back(CHECK_NOTNULL(p.getTransaction(this)));\n  // Since these two sessions are equally old, it's ok for either one to\n  // be selected to become \"active\"\n  if (sess2->getNumOutgoingStreams() > sess1->getNumOutgoingStreams()) {\n    std::swap(sess1, sess2);\n  }\n  ASSERT_EQ(sess1->getNumOutgoingStreams(), 1);\n  ASSERT_EQ(sess2->getNumOutgoingStreams(), 0);\n  ASSERT_EQ(activated_, 1);\n  ASSERT_EQ(deactivated_, 0);\n  ASSERT_EQ(closed_, 0);\n\n  // sess1 should be filled completely before we add any transactions to sess2\n  while (sess1->supportsMoreTransactions()) {\n    txnsSess1.push_back(CHECK_NOTNULL(p.getTransaction(this)));\n  }\n  ASSERT_EQ(sess1->getNumOutgoingStreams(),\n            sess1->getMaxConcurrentOutgoingStreams());\n  ASSERT_EQ(sess2->getNumOutgoingStreams(), 0);\n  ASSERT_EQ(activated_, 1);\n  ASSERT_EQ(deactivated_, 0);\n  ASSERT_EQ(closed_, 0);\n\n  // Now fill sess2\n  while (sess2->supportsMoreTransactions()) {\n    txnsSess2.push_back(CHECK_NOTNULL(p.getTransaction(this)));\n  }\n  // The two sessions should be completely full\n  ASSERT_EQ(sess1->getNumOutgoingStreams(),\n            sess1->getMaxConcurrentOutgoingStreams());\n  ASSERT_EQ(sess2->getNumOutgoingStreams(),\n            sess2->getMaxConcurrentOutgoingStreams());\n  ASSERT_EQ(activated_, 2);\n  ASSERT_EQ(deactivated_, 0);\n  ASSERT_EQ(closed_, 0);\n\n  // Adding 1 more txn should fail since both sessions are full\n  CHECK(nullptr == p.getTransaction(this));\n\n  evb_.loop();\n  ASSERT_EQ(sess1->getNumOutgoingStreams(),\n            sess1->getMaxConcurrentOutgoingStreams());\n  ASSERT_EQ(sess2->getNumOutgoingStreams(),\n            sess2->getMaxConcurrentOutgoingStreams());\n  ASSERT_EQ(activated_, 2);\n  ASSERT_EQ(deactivated_, 0);\n  ASSERT_EQ(closed_, 0);\n\n  // Dropping all the txns on sess1 should \"deactivate\" that session\n  for (auto& txn : txnsSess1) {\n    txn->sendAbort();\n  }\n  ASSERT_EQ(activated_, 2);\n  ASSERT_EQ(deactivated_, 1);\n  ASSERT_EQ(closed_, 0);\n\n  // Just for kicks, ensure that no session is scheduled to be closed\n  // right now\n  evb_.loop();\n  ASSERT_EQ(closed_, 0);\n\n  // Decrease the session pool limit to 0 and purge the idle sessions.\n  // This should close sess1 (the only idle session at this time)\n  p.setMaxIdleSessions(0);\n  evb_.loop();\n  ASSERT_EQ(closed_, 1);\n\n  // Drop 1 txn from the completely full sess2, then get another txn. This\n  // should succeed. This shouldn't \"deactivate\" sess2.\n  txnsSess2.back()->sendAbort();\n  txnsSess2.pop_back();\n  ASSERT_EQ(deactivated_, 1);\n  txnsSess2.push_back(CHECK_NOTNULL(p.getTransaction(this)));\n\n  // Set the max pooled sessions to zero and drop all txns from\n  // sess2. The session should be automatically closed when it hits\n  // idle (i.e., no transactions open)\n  for (auto& txn : txnsSess2) {\n    ASSERT_EQ(closed_, 1);\n    txn->sendAbort();\n  }\n  ASSERT_EQ(activated_, 2);\n  ASSERT_EQ(deactivated_, 2);\n  ASSERT_EQ(closed_, 2);\n  evb_.loop();\n}\n\nTEST_F(SessionPoolFixture, OutstandingWrites) {\n  auto codec = makeSerialCodec();\n  EXPECT_CALL(*codec, generateHeader(_, _, _, _, _, _))\n      .WillOnce(Invoke([](folly::IOBufQueue& writeBuf,\n                          HTTPCodec::StreamID /*id*/,\n                          const HTTPMessage& /*msg*/,\n                          bool /*eom*/,\n                          HTTPHeaderSize* size,\n                          folly::Optional<HTTPHeaders>) {\n        writeBuf.append(\"somedata\");\n        if (size) {\n          size->uncompressed = 8;\n        }\n      }));\n  auto sess = makeSession(std::move(codec));\n\n  SessionPool p(this, 1);\n  p.putSession(sess);\n  ASSERT_FALSE(attached_);\n  ASSERT_FALSE(sess->isClosing());\n  auto txn = CHECK_NOTNULL(p.getTransaction(this));\n  ASSERT_TRUE(attached_);\n  txn->sendHeaders(HTTPMessage());\n  txn->sendAbort();\n  // Because the session has outstanding writes and is not parallel, the\n  // session is not reusable, and so it will be drained and closed.\n  ASSERT_TRUE(sess->isClosing());\n\n  evb_.loop();\n  // The session's writes are now drained\n  ASSERT_FALSE(attached_);\n  ASSERT_EQ(deactivated_, 1);\n  ASSERT_EQ(closed_, 1);\n}\n\nTEST_F(SessionPoolFixture, OutstandingTransaction) {\n  // Similar to the previous test, but even stricter. When the pool is\n  // reset, there are outstanding transactions. So, the pool should stay\n  // open longer to let the transaction finish.\n  auto sess = makeSerialSession();\n  HTTPTransaction* txn = nullptr;\n  {\n    SessionPool p(this, 1);\n    p.putSession(sess);\n    ASSERT_FALSE(attached_);\n    txn = p.getTransaction(this);\n    CHECK_NOTNULL(txn);\n    ASSERT_TRUE(attached_);\n    ASSERT_EQ(deactivated_, 0);\n    ASSERT_EQ(closed_, 0);\n    ASSERT_EQ(p.getNumSessions(), 1);\n  }\n  // Destroying the SessionPool starts draining all the sessions.\n  // We get the stats for the deactivation and closing early since later\n  // there is no guarantee the stats object will be around.\n  ASSERT_EQ(deactivated_, 1);\n  ASSERT_EQ(closed_, 1);\n\n  // Dropping the transaction will let the session close\n  ASSERT_TRUE(attached_);\n  txn->sendAbort();\n  ASSERT_FALSE(attached_);\n}\n\nTEST_F(SessionPoolFixture, DroppedRequestNotPooled) {\n  // Let the session pool have 10 max\n  SessionPool p(this, 10, std::chrono::seconds(4));\n  auto codec = makeSerialCodec();\n  // In this test, the codec starts off not busy, but then we do some\n  // processing on it, and it should remain busy for the remainder of the\n  // test since the response never arrives\n  bool shouldBeBusy = false;\n  EXPECT_CALL(*codec, isBusy()).WillRepeatedly(Invoke([&]() {\n    return shouldBeBusy;\n  }));\n  auto sess = makeSession(std::move(codec));\n  p.putSession(sess);\n  auto txn1 = p.getTransaction(this);\n  ASSERT_EQ(p.getNumIdleSessions(), 0);\n  ASSERT_EQ(p.getNumActiveSessions(), 1);\n  ASSERT_EQ(p.getNumSessions(), 1);\n\n  HTTPMessage req;\n  req.setMethod(\"GET\");\n  req.setURL<string>(\"/\");\n  req.setHTTPVersion(1, 1);\n  txn1->sendHeaders(req);\n  txn1->sendEOM();\n  shouldBeBusy = true;\n  ASSERT_TRUE(attached_);\n  evb_.loop();\n  // Writes have now succeeded. We should still be attached since we\n  // terminated the loop before the timeouts fired\n  CHECK(!timeout_);\n  ASSERT_TRUE(attached_);\n\n  // Now drop the txn before the response comes back\n  ASSERT_EQ(p.getNumIdleSessions(), 0);\n  ASSERT_EQ(p.getNumActiveSessions(), 1);\n  ASSERT_EQ(p.getNumSessions(), 1);\n  txn1->sendAbort();\n  // The drop should have closed the session since the codec was busy when\n  // the transaction count went to zero\n\n  // We should fail to get another txn out of the pool\n  ASSERT_EQ(p.getNumIdleSessions(), 0);\n  ASSERT_EQ(p.getNumActiveSessions(), 0);\n  ASSERT_EQ(p.getNumSessions(), 0);\n  ASSERT_TRUE(nullptr == p.getTransaction(this));\n  evb_.loop();\n}\n\nTEST_F(SessionPoolFixture, InsertIntoZeroSizePool) {\n  // Let the session pool have 0 max sessions\n  SessionPool p(this, 0, std::chrono::seconds(4));\n  p.putSession(makeSerialSession());\n  CHECK(nullptr == p.getTransaction(this));\n  evb_.loop();\n}\n\nTEST_F(SessionPoolFixture, DrainSessionLater) {\n  // Mark a session in the pool to drain, then make sure the pool works.\n  SessionPool p(this, 10, std::chrono::seconds(4));\n  auto session = makeParallelSession();\n  p.putSession(session);\n\n  auto txn1 = CHECK_NOTNULL(p.getTransaction(this));\n  auto txn2 = CHECK_NOTNULL(p.getTransaction(this));\n  session->drain();\n  ASSERT_EQ(p.getNumSessions(), 1); // don't detect until getTransaction()\n  CHECK(nullptr == p.getTransaction(this));\n  ASSERT_EQ(p.getNumSessions(), 0);\n  // now let the session be destroyed\n  txn1->sendAbort();\n  txn2->sendAbort();\n}\n\nTEST_F(SessionPoolFixture, InsertDrainedSession) {\n  // Put a draining session into the pool. Make sure the pool ignores it.\n  auto session = makeParallelSession();\n  CHECK_NOTNULL(session->newTransaction(this));\n  session->drain();\n\n  auto cm = wangle::ConnectionManager::makeUnique(\n      &evb_, std::chrono::milliseconds(1), nullptr);\n  SessionPool p(this, 10, std::chrono::seconds(4), std::chrono::seconds(0));\n  cm->addConnection(session);\n  p.putSession(session);\n  ASSERT_EQ(p.getNumSessions(), 0);\n  CHECK(nullptr == p.getTransaction(this));\n\n  // this session needs to be owned by the cm\n  cm->dropAllConnections();\n  EXPECT_EQ(numSessions_, 0);\n}\n\nTEST_F(SessionPoolFixture, CloseNotReusable) {\n  // Make sure if a connection becomes not reusable, that it is removed\n  // from the pool after the txn completes\n  SessionPool p(this, 10, std::chrono::seconds(4));\n\n  // Codec expectations\n  bool reusable = true;\n  auto codec = std::make_unique<NiceMock<MockHTTPCodec>>();\n  EXPECT_CALL(*codec, getTransportDirection())\n      .WillRepeatedly(Return(TransportDirection::UPSTREAM));\n  EXPECT_CALL(*codec, createStream()).WillOnce(Return(1));\n  EXPECT_CALL(*codec, isReusable()).WillRepeatedly(ReturnPointee(&reusable));\n  EXPECT_CALL(*codec, supportsParallelRequests()).WillRepeatedly(Return(false));\n  EXPECT_CALL(*codec, getProtocol())\n      .WillRepeatedly(Return(CodecProtocol::HTTP_2));\n\n  p.putSession(makeSession(std::move(codec)));\n  ASSERT_EQ(p.getNumSessions(), 1);\n  auto txn = CHECK_NOTNULL(p.getTransaction(this));\n  reusable = false; // Mark the session as not reusable, e.g. if it got\n                    // Connection: close\n  txn->sendAbort();\n  ASSERT_EQ(p.getNumSessions(), 0); // Non-reusable conn should be dropped\n}\n\nTEST_F(SessionPoolFixture, InsertOldSession) {\n  // Put a session into the pool that is over max-age. Make sure the\n  // pool ignores it.\n  auto session = makeParallelSession();\n\n  SessionPool p(\n      this, 10, std::chrono::seconds(4), std::chrono::milliseconds(50));\n  usleep(70000); // exceeds max jitter (currently 65ms)\n  p.putSession(session);\n  ASSERT_EQ(p.getNumSessions(), 0);\n  CHECK(nullptr == p.getTransaction(this));\n}\n\nTEST_F(SessionPoolFixture, IdleTmeout) {\n  // Put a session into the pool, let it timeout and ask for a new txn\n  // pool ignores it.\n  auto session = makeParallelSession();\n\n  SessionPool p(\n      this, 10, std::chrono::milliseconds(250), std::chrono::seconds(4));\n  p.putSession(session);\n  ASSERT_TRUE(p.getNumSessions() == 1);\n  auto txn = CHECK_NOTNULL(p.getTransaction(this));\n  txn->sendAbort();\n  /* sleep override */ usleep(260000); // > 250ms\n  CHECK(nullptr == p.getTransaction(this));\n  ASSERT_EQ(p.getNumSessions(), 0);\n}\n\nTEST_F(SessionPoolFixture, AgeOut) {\n  // Put a session into the pool, let it age out and ask for a new txn\n  // pool ignores it.\n  auto session = makeParallelSession();\n\n  SessionPool p(\n      this, 10, std::chrono::seconds(4), std::chrono::milliseconds(250));\n  p.putSession(session);\n  // possible flake if this process takes a dirtnap for >= 175ms\n  ASSERT_TRUE(p.getNumSessions() == 1);\n  auto txn = CHECK_NOTNULL(p.getTransaction(this));\n  txn->sendAbort();\n  usleep(350000); // over max jitter (325 ms)\n  CHECK(nullptr == p.getTransaction(this));\n  ASSERT_EQ(p.getNumSessions(), 0);\n}\n\nTEST_F(SessionPoolFixture, DrainOnShutdown) {\n  // Verify that the session is drained if the socket is closing/closed\n  auto session = makeParallelSession();\n\n  SessionPool p(this, 10, std::chrono::milliseconds(4));\n  p.putSession(session);\n  ASSERT_EQ(p.getNumSessions(), 1);\n  ASSERT_EQ(p.getNumIdleSessions(), 1);\n  ASSERT_EQ(p.getNumActiveSessions(), 0);\n\n  auto txn = p.getTransaction(this);\n  ASSERT_TRUE(txn != nullptr);\n  ASSERT_EQ(p.getNumSessions(), 1);\n  ASSERT_EQ(p.getNumIdleSessions(), 0);\n  ASSERT_EQ(p.getNumActiveSessions(), 1);\n  ASSERT_EQ(p.getNumActiveNonFullSessions(), 1);\n  ASSERT_EQ(session->getNumOutgoingStreams(), 1);\n\n  // Manually close the socket\n  session->getTransport()->closeNow();\n  ASSERT_EQ(session->getConnectionCloseReason(),\n            ConnectionCloseReason::kMAX_REASON);\n\n  // New transaction creation fails and drains the session\n  ASSERT_TRUE(p.getTransaction(this) == nullptr);\n  ASSERT_EQ(session->getConnectionCloseReason(),\n            ConnectionCloseReason::SHUTDOWN);\n  ASSERT_EQ(p.getNumSessions(), 0);\n  ASSERT_EQ(session->getNumOutgoingStreams(), 1);\n\n  // Now let the session be destroyed\n  txn->sendAbort();\n}\n\nclass TestIdleController : public ServerIdleSessionController {\n public:\n  // expose this method as public for tests.\n  SessionPool* popBestIdlePool() {\n    return ServerIdleSessionController::popBestIdlePool();\n  }\n};\n\nTEST_F(SessionPoolFixture, MoveIdleSessionBetweenThreadsTest) {\n  TestIdleController ctrl;\n  HTTPUpstreamSession* session = nullptr;\n\n  folly::Baton<> t1InitBaton, t2InitBaton, transferBaton;\n  // Create two threads, each looping on their own event base\n  std::thread t1([&] {\n    folly::EventBaseManager::get()->setEventBase(&evb_, false);\n    SessionPool p1(this,\n                   10,\n                   std::chrono::seconds(30),\n                   std::chrono::milliseconds(0),\n                   nullptr,\n                   &ctrl);\n    // Put an (idle) session on p1.\n    session = makeParallelSession();\n    p1.putSession(session);\n    t1InitBaton.post();\n    evb_.loopForever();\n  });\n\n  // Wait for t1 to start before starting t2\n  // to ensure it is picked by popBestIdlePool()\n  t1InitBaton.wait();\n\n  folly::EventBase evb2;\n  std::thread t2([&] {\n    folly::EventBaseManager::get()->setEventBase(&evb2, false);\n    SessionPool p2(this,\n                   10,\n                   std::chrono::seconds(30),\n                   std::chrono::milliseconds(0),\n                   nullptr,\n                   &ctrl);\n    t2InitBaton.post();\n    evb2.loopForever();\n  });\n\n  t2InitBaton.wait();\n  // Simulate thread2 asking thread1 for an idle session\n  evb2.runInEventBaseThread([&] {\n    ctrl.getIdleSession().via(&evb2).thenValue(\n        [&](HTTPSessionBase* idleSession) {\n          ASSERT_EQ(idleSession, session);\n          // Not re-attaching it to thread2 so ctrl will be empty\n          transferBaton.post();\n        });\n  });\n  transferBaton.wait();\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n\n  session->drain();\n  evb_.terminateLoopSoon();\n  evb2.terminateLoopSoon();\n  t1.join();\n  t2.join();\n}\n\nTEST_F(SessionPoolFixture, PurgeAddedSessionTest) {\n  TestIdleController ctrl;\n  HTTPUpstreamSession* session = makeParallelSession();\n  SessionPool p1(this,\n                 1,\n                 /*idleTimeout=*/std::chrono::milliseconds(0),\n                 std::chrono::milliseconds(0),\n                 nullptr,\n                 &ctrl);\n\n  // This is about to purge session immediately and should not crash.\n  p1.putSession(session);\n}\n\nTEST_F(SessionPoolFixture, ServerIdleSessionControllerTest) {\n  TestIdleController ctrl;\n  SessionPool p1, p2;\n  auto s1 = makeParallelSession(), s2 = makeParallelSession(),\n       s3 = makeParallelSession();\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n\n  ctrl.addIdleSession(s1, &p1);\n  EXPECT_EQ(ctrl.popBestIdlePool(), &p1);\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n\n  ctrl.addIdleSession(s3, &p2);\n  ctrl.addIdleSession(s1, &p1);\n  EXPECT_EQ(ctrl.popBestIdlePool(), &p2);\n  EXPECT_EQ(ctrl.popBestIdlePool(), &p1);\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n\n  ctrl.addIdleSession(s1, &p1);\n  ctrl.addIdleSession(s2, &p1);\n  ctrl.removeIdleSession(s1);\n  ctrl.addIdleSession(s3, &p2);\n  EXPECT_EQ(ctrl.popBestIdlePool(), &p1);\n  EXPECT_EQ(ctrl.popBestIdlePool(), &p2);\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n\n  s1->drain();\n  s2->drain();\n  s3->drain();\n}\n\nTEST_F(SessionPoolFixture, WritePausedSessionNotMarkedAsIdle) {\n  auto codec = makeParallelCodec();\n  EXPECT_CALL(*codec, generateHeader(_, _, _, _, _, _))\n      .WillOnce(Invoke([](folly::IOBufQueue& writeBuf,\n                          HTTPCodec::StreamID /*id*/,\n                          const HTTPMessage& /*msg*/,\n                          bool /*eom*/,\n                          HTTPHeaderSize* size,\n                          folly::Optional<HTTPHeaders>) {\n        writeBuf.append(\"somedata\");\n        if (size) {\n          size->uncompressed = 8;\n        }\n      }));\n  auto session = makeSession(std::move(codec));\n\n  TestIdleController ctrl;\n  SessionPool p1(this,\n                 10,\n                 std::chrono::seconds(30),\n                 std::chrono::milliseconds(0),\n                 nullptr,\n                 &ctrl);\n  p1.putSession(session);\n\n  auto txn = p1.getTransaction(this);\n  ASSERT_NE(txn, (HTTPTransaction*)nullptr);\n  auto* transport =\n      session->getTransport()->getUnderlyingTransport<TestAsyncTransport>();\n  transport->pauseWrites();\n  HTTPMessage req;\n  req.setMethod(\"GET\");\n  req.setURL<string>(\"/\");\n  req.setHTTPVersion(1, 1);\n  txn->sendHeaders(req);\n  evb_.loopOnce();\n  txn->sendAbort();\n\n  transport->resumeWrites();\n  evb_.loopOnce();\n\n  // Session should not be marked as idle.\n  EXPECT_EQ(ctrl.popBestIdlePool(), nullptr);\n  EXPECT_EQ(p1.getNumSessions(), 1);\n  EXPECT_EQ(p1.getNumIdleSessions(), 0);\n  EXPECT_EQ(p1.getNumActiveSessions(), 1);\n  EXPECT_EQ(p1.getNumActiveNonFullSessions(), 1);\n}\n\nTEST_F(SessionPoolFixture, ThreadIdleSessionControllerLimitsTotalIdle) {\n  ThreadIdleSessionController controller(3);\n  SessionPool p1(this,\n                 2,\n                 std::chrono::milliseconds(50),\n                 std::chrono::milliseconds(50),\n                 &controller);\n  SessionPool p2(this,\n                 2,\n                 std::chrono::milliseconds(50),\n                 std::chrono::milliseconds(50),\n                 &controller);\n\n  // Add two sessions on each pool.\n  p1.putSession(makeSerialSession());\n  p1.putSession(makeSerialSession());\n\n  p2.putSession(makeSerialSession());\n  p2.putSession(makeSerialSession());\n\n  // Validate ThreadIdleSessionController limited it to 3 at most.\n  EXPECT_EQ(controller.getTotalIdleSessions(), 3);\n\n  auto txn = p1.getTransaction(this);\n  ASSERT_TRUE(txn != nullptr);\n\n  // Drop the transaction. All transactions on the sessions in the pool\n  // must be completed before the pool can be destroyed\n  txn->sendAbort();\n\n  evb_.loop();\n  ASSERT_EQ(closed_, 2);\n  EXPECT_EQ(controller.getTotalIdleSessions(), 2);\n  EXPECT_EQ(p1.getNumIdleSessions(), 0);\n  EXPECT_EQ(p2.getNumIdleSessions(), 2);\n}\n\nTEST_F(SessionPoolFixture, ThreadIdleSessionControllerTrackSessionPoolChanges) {\n  ThreadIdleSessionController controller(5);\n  SessionPool p1(this,\n                 2,\n                 std::chrono::milliseconds(50),\n                 std::chrono::milliseconds(50),\n                 &controller);\n  SessionPool p2(this,\n                 2,\n                 std::chrono::milliseconds(50),\n                 std::chrono::milliseconds(50),\n                 &controller);\n\n  // Add two sessions on each pool.\n  p1.putSession(makeSerialSession());\n  p1.putSession(makeSerialSession());\n\n  p2.putSession(makeSerialSession());\n  p2.putSession(makeSerialSession());\n\n  // Validate ThreadIdleSessionController sees all sessions.\n  EXPECT_EQ(controller.getTotalIdleSessions(), 4);\n\n  // Drop a number of sessions.\n  p1.setMaxIdleSessions(1);\n  p2.setMaxIdleSessions(1);\n\n  EXPECT_EQ(controller.getTotalIdleSessions(), 2);\n  EXPECT_EQ(p1.getNumIdleSessions(), 1);\n  EXPECT_EQ(p2.getNumIdleSessions(), 1);\n}\n\nTEST_F(SessionPoolFixture, DescribeWithNullTransport) {\n  MockHQSession session;\n  MockSessionHolderCallback cb;\n  SessionHolder sessionHolder(&session, &cb);\n\n  // This should not crash.\n  LOG(INFO) << sessionHolder;\n  // This is to appease mock session destructor.\n  session.setInfoCallback(nullptr);\n}\n\n// So we can have -v work\nint main(int argc, char** argv) {\n  testing::InitGoogleTest(&argc, argv);\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "proxygen/lib/http/connpool/test/SessionPoolTestFixture.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/SocketAddress.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n#include <proxygen/lib/test/TestAsyncTransport.h>\n\n#include <proxygen/lib/http/connpool/SessionHolder.h>\n\nnamespace proxygen {\n\nfolly::SocketAddress local(\"127.0.0.1\", 80);\nfolly::SocketAddress peer(\"127.0.0.1\", 12345);\n\nclass MockSessionHolderCallback : public SessionHolder::Callback {\n public:\n  MOCK_METHOD(void, detachIdle, (SessionHolder*), ());\n  MOCK_METHOD(void, detachPartiallyFilled, (SessionHolder*), ());\n  MOCK_METHOD(void, detachFilled, (SessionHolder*), ());\n  MOCK_METHOD(void, attachIdle, (SessionHolder*), ());\n  MOCK_METHOD(void, attachPartiallyFilled, (SessionHolder*), ());\n  MOCK_METHOD(void, attachFilled, (SessionHolder*), ());\n  MOCK_METHOD(void, addDrainingSession, (HTTPSessionBase*), ());\n};\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeCodecCommon() {\n  static int txnIdx = 1;\n  auto codec = std::make_unique<testing::NiceMock<MockHTTPCodec>>();\n  EXPECT_CALL(*codec, getTransportDirection())\n      .WillRepeatedly(testing::Return(TransportDirection::UPSTREAM));\n  EXPECT_CALL(*codec, createStream())\n      .WillRepeatedly(testing::InvokeWithoutArgs([&]() { return txnIdx++; }));\n  EXPECT_CALL(*codec, isReusable()).WillRepeatedly(testing::Return(true));\n  EXPECT_CALL(*codec, getProtocol())\n      .WillRepeatedly(testing::Return(CodecProtocol::HTTP_2));\n  return codec;\n}\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeSerialCodec() {\n  auto codec = makeCodecCommon();\n  EXPECT_CALL(*codec, supportsParallelRequests())\n      .WillRepeatedly(testing::Return(false));\n  EXPECT_CALL(*codec, getProtocol())\n      .WillRepeatedly(testing::Return(CodecProtocol::HTTP_1_1));\n  return codec;\n}\n\nstd::unique_ptr<testing::NiceMock<MockHTTPCodec>> makeParallelCodec() {\n  auto codec = makeCodecCommon();\n  EXPECT_CALL(*codec, supportsParallelRequests())\n      .WillRepeatedly(testing::Return(true));\n  EXPECT_CALL(*codec, generateRstStream(testing::_, testing::_, testing::_))\n      .WillRepeatedly(testing::Return(1));\n  EXPECT_CALL(*codec, getProtocol())\n      .WillRepeatedly(testing::Return(CodecProtocol::HTTP_2));\n  return codec;\n}\n\nclass SessionPoolFixture\n    : public testing::Test\n    , public SessionHolder::Stats\n    , public HTTPTransaction::Handler\n    , public HTTPSessionBase::InfoCallback {\n public:\n  void SetUp() override {\n  }\n\n  void TearDown() override {\n    evb_.loop();\n  }\n\n  HTTPUpstreamSession* makeSerialSession() {\n    return makeSession(makeSerialCodec());\n  }\n\n  HTTPUpstreamSession* makeParallelSession() {\n    return makeSession(makeParallelCodec());\n  }\n\n  HTTPUpstreamSession* makeSession(std::unique_ptr<HTTPCodec> codec) {\n    auto sock = folly::AsyncTransport::UniquePtr(new TestAsyncTransport(&evb_));\n    wangle::TransportInfo tinfo;\n    tinfo.acceptTime = getCurrentTime();\n    return new HTTPUpstreamSession(timeouts_.get(),\n                                   std::move(sock),\n                                   local,\n                                   peer,\n                                   std::move(codec),\n                                   tinfo,\n                                   this);\n  }\n\n  void onCreate(const HTTPSessionBase&) override {\n    numSessions_++;\n  }\n  // Note: you must not start any asynchronous work from onDestroy()\n  void onDestroy(const HTTPSessionBase&) override {\n    numSessions_--;\n  }\n  using HTTPSessionBase::InfoCallback::onRead;\n  using HTTPSessionBase::InfoCallback::onWrite;\n\n  // SessionHolder::Stats\n  void onConnectionCreated() override {\n  }\n\n  void onConnectionClosed() override {\n    ++closed_;\n  }\n  void onConnectionActivated() override {\n    ++activated_;\n  }\n  void onConnectionDeactivated() override {\n    ++deactivated_;\n  }\n  void onRead(size_t /*bytesRead*/) override {\n  }\n  void onWrite(size_t /*bytesWritten*/) override {\n  }\n\n  // HTTPTransaction::Handler\n  void setTransaction(HTTPTransaction* /*txn*/) noexcept override {\n    attached_ = true;\n  }\n  void detachTransaction() noexcept override {\n    attached_ = false;\n  }\n  void onHeadersComplete(\n      std::unique_ptr<HTTPMessage> /*msg*/) noexcept override {\n  }\n  void onBody(std::unique_ptr<folly::IOBuf> /*chain*/) noexcept override {\n  }\n  void onChunkHeader(size_t /*length*/) noexcept override {};\n  void onChunkComplete() noexcept override {};\n  void onTrailers(std::unique_ptr<HTTPHeaders> /*trailers*/) noexcept override {\n  }\n  void onEOM() noexcept override {\n  }\n  void onUpgrade(UpgradeProtocol /*protocol*/) noexcept override {\n  }\n  void onError(const HTTPException& /*error*/) noexcept override {\n  }\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n\n protected:\n  folly::EventBase evb_;\n  folly::HHWheelTimer::UniquePtr timeouts_{folly::HHWheelTimer::newTimer(\n      &evb_,\n      std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      folly::TimeoutManager::InternalEnum::INTERNAL,\n      std::chrono::milliseconds(100))};\n  uint32_t closed_{0};\n  uint32_t activated_{0};\n  uint32_t deactivated_{0};\n  uint32_t numSessions_{0};\n  bool attached_{false};\n  bool timeout_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/coro/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_handler_chain\n  SRCS\n    HTTPHandlerChain.cpp\n  EXPORTED_DEPS\n    proxygen_coro\n)\n\nproxygen_add_library(proxygen_http_coro_filter_factory_handler\n  SRCS\n    HTTPFilterFactoryHandler.cpp\n  DEPS\n    proxygen_coro_source\n    proxygen_http_coro_filters_FilterFactory\n  EXPORTED_DEPS\n    proxygen_http_coro_handler_chain\n)\n\nproxygen_add_library(proxygen_coro_source\n  SRCS\n    HTTPError.cpp\n    HTTPEvents.cpp\n    HTTPSourceFilter.cpp\n  DEPS\n    proxygen_http_http_utils\n    Folly::folly_container_f14_hash\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_codec_error_code\n    proxygen_http_coro_util_timed_baton\n    proxygen_http_h3_errors\n    proxygen_http_http_header_size\n    proxygen_http_message\n    proxygen_utils_parse_url\n    proxygen_utils_weak_ref_counted_ptr\n    mvfst::mvfst_common_buf_util\n    Folly::folly_conv\n    Folly::folly_coro_task\n    Folly::folly_function\n    Folly::folly_io_iobuf\n    Folly::folly_logging_logging\n    Folly::folly_optional\n    Folly::folly_scope_guard\n)\n\nproxygen_add_library(proxygen_http_coro_coro_reader\n  SRCS\n    HTTPSourceReader.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_coro_util_timed_baton\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_coro_stream_source\n  SRCS\n    HTTPStreamSource.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_codec_codec_common\n    proxygen_http_coro_util_timed_baton\n    proxygen_http_coro_util_window_contiainer\n    proxygen_http_session_http_state_machine_lib\n)\n\nproxygen_add_library(proxygen_coro\n  SRCS\n    HTTPBodyEventQueue.cpp\n    HTTPByteEventHelpers.cpp\n    HTTPCoroSession.cpp\n  DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_coro_util_CoroWtSession\n    proxygen_http_priority_functions\n    proxygen_http_session_stats\n    proxygen_http_webtransport_httpwebtransport\n    mvfst::mvfst_state_quic_stream_utilities\n    Folly::folly_conv\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_hq_codec\n    proxygen_http_codec_rate_limit_filters\n    proxygen_http_coro_coro_stream_source\n    proxygen_http_coro_util_DetachableExecutor\n    proxygen_http_coro_util_awaitable_keepalive\n    proxygen_http_coro_util_cancellable_baton\n    proxygen_http_coro_util_window_contiainer\n    proxygen_http_session_quic_protocol_info\n    proxygen_http_session_stream_dispatcher\n    proxygen_utils_util_inl\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_common_optional\n    mvfst::mvfst_priority_http_priority_queue\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_container_evicting_cache_map\n    Folly::folly_container_f14_hash\n    Folly::folly_coro_async_scope\n    Folly::folly_coro_task\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_coro_socket\n    Folly::folly_logging_logging\n    Folly::folly_utility\n)\n\nproxygen_add_library(proxygen_coro_stream_source_sink\n  SRCS\n    HTTPStreamSourceSink.cpp\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_coro_source\n    proxygen_http_coro_coro_reader\n    proxygen_http_coro_coro_stream_source\n    proxygen_http_session_http_transaction\n    proxygen_http_sink_client_sink_interface\n    proxygen_utils_conditional_gate\n)\n\nproxygen_add_library(proxygen_http_coro_http_transaction_adaptor_source\n  SRCS\n    HTTPTransactionAdaptorSource.cpp\n  DEPS\n    proxygen_http_coro_coro_reader\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_coro_coro_stream_source\n    proxygen_http_session_http_transaction\n    proxygen_utils_conditional_gate\n)\n\nadd_subdirectory(client)\nadd_subdirectory(filters)\nadd_subdirectory(server)\nadd_subdirectory(transport)\nadd_subdirectory(util)\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPBodyEventQueue.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPBodyEventQueue.h\"\n\n#include <folly/Conv.h>\n\nusing folly::coro::co_error;\nusing folly::coro::co_nothrow;\nusing folly::coro::co_safe_point;\n\nnamespace proxygen::coro {\n\nfolly::coro::Task<HTTPHeaderEvent> HTTPBodyEventQueue::readHeaderEvent() {\n  auto res = co_await co_nothrow(source_.readHeaderEvent());\n  if (res.headers->isResponse() && res.headers->getStatusCode() == 304) {\n    skipContentLengthValidation();\n  } else if (shouldValidateContentLength_ && res.isFinal()) {\n    setExpectedEgressContentLength(\n        res.headers->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH),\n        res.eom);\n  }\n\n  co_return res;\n}\n\nfolly::coro::Task<HTTPBodyEventQueue::ReadBodyResult>\nHTTPBodyEventQueue::readBodyEvent(uint32_t max) {\n  XCHECK(source_);\n  XLOG(DBG4) << \"Waiting for buffer space\";\n  auto bufferSpace = availableBuffer();\n  if (!bufferSpace) {\n    bufferSpace = co_await co_nothrow(waitAvailableBuffer());\n  }\n  XLOG(DBG4) << \"Got buffer space len=\" << bufferSpace\n             << \" waiting for body event\";\n  max = std::min(max, uint32_t(bufferSpace));\n  auto bodyEvent = co_await co_nothrow(source_.readBodyEvent(max));\n  const auto& ct = co_await folly::coro::co_current_cancellation_token;\n  if (ct.isCancellationRequested()) {\n    co_yield co_error(HTTPError{HTTPErrorCode::CORO_CANCELLED});\n  }\n\n  if (bodyEvent.eventType == HTTPBodyEvent::SUSPEND) {\n    co_return ReadBodyResult({.resume = std::move(bodyEvent.event.resume)});\n  }\n  if (bodyEvent.eventType == HTTPBodyEvent::BODY &&\n      !bodyEvent.event.body.empty()) {\n    callback_.onEgressBytesBuffered(\n        static_cast<int64_t>(bodyEvent.event.body.chainLength()));\n  }\n  co_return ReadBodyResult({\n      .resume = folly::none,\n      .eom = processBodyEvent(std::move(bodyEvent)),\n  });\n}\n\nvoid HTTPBodyEventQueue::setExpectedEgressContentLength(\n    const std::string& contentLen, bool eom) {\n  if (contentLen.empty()) {\n    shouldValidateContentLength_ = false;\n    return;\n  }\n\n  auto convResult = folly::tryTo<uint64_t>(contentLen);\n  if (convResult.hasError()) {\n    XLOG(ERR)\n        << \"Invalid content-length: \" << contentLen << \", ex=\"\n        << folly::makeConversionError(convResult.error(), contentLen).what();\n    shouldValidateContentLength_ = false;\n    return;\n  }\n\n  expectedContentLength_ = convResult.value();\n  validateContentLength(eom);\n}\n\nvoid HTTPBodyEventQueue::clear(const HTTPError& err) {\n  size_t bytesBuffered{0};\n  for (auto& ev : bodyQueue_) {\n    for (auto& reg : ev.byteEventRegistrations) {\n      reg.cancel(err, folly::none);\n    }\n    if (ev.eventType == HTTPBodyEvent::EventType::BODY) {\n      bytesBuffered += ev.event.body.chainLength();\n    }\n  }\n  bodyQueue_.clear();\n  callback_.onEgressBytesBuffered(-static_cast<int64_t>(bytesBuffered));\n}\n\nHTTPBodyEvent HTTPBodyEventQueue::dequeueBodyEvent(uint32_t max) {\n  XCHECK(!bodyQueue_.empty());\n  auto bodyEvent = std::move(bodyQueue_.front());\n  bodyQueue_.pop_front();\n  if (bodyEvent.eventType == HTTPBodyEvent::BODY) {\n    auto length = bodyEvent.event.body.chainLength();\n    auto toReturn = std::min(max, uint32_t(length));\n    XCHECK_GE(bufferedBodyBytes_, toReturn);\n    bool wasOverLimit = bufferedBodyBytes_ >= limit_;\n    bufferedBodyBytes_ -= toReturn;\n    if (wasOverLimit && bufferedBodyBytes_ < limit_) {\n      event_.signal();\n    }\n    if (toReturn < length) {\n      // only return part of it, split the buffer and re-push it to the front\n      auto resultBuf = bodyEvent.event.body.splitAtMost(toReturn);\n      bodyQueue_.emplace_front(std::move(bodyEvent));\n      return {std::move(resultBuf), false};\n    }\n  }\n  return bodyEvent;\n}\n\nfolly::coro::Task<size_t> HTTPBodyEventQueue::waitAvailableBuffer() {\n  while (bufferedBodyBytes_ >= limit_) {\n    event_.reset();\n    auto status = co_await event_.timedWait(evb_, writeTimeout_);\n    if (status == TimedBaton::Status::timedout) {\n      co_yield folly::coro::co_error(HTTPError(\n          HTTPErrorCode::READ_TIMEOUT, \"timed out waiting for buffer space\"));\n    } else if (status == TimedBaton::Status::cancelled) {\n      co_yield folly::coro::co_error(HTTPError(\n          HTTPErrorCode::CORO_CANCELLED, \"cancelled waiting for buffer space\"));\n    }\n  }\n  co_return limit_ - bufferedBodyBytes_;\n}\n\nbool HTTPBodyEventQueue::processBodyEvent(HTTPBodyEvent&& bodyEvent) {\n  XLOG(DBG4) << \"Queuing body event\";\n  for (auto& reg : bodyEvent.byteEventRegistrations) {\n    if (!reg.streamID) {\n      reg.streamID = id_;\n    }\n  }\n  bool eom = bodyEvent.eom;\n  size_t addedBodyLength = 0;\n  if (bodyEvent.eventType == HTTPBodyEvent::BODY) {\n    addedBodyLength = bodyEvent.event.body.chainLength();\n    bufferedBodyBytes_ += addedBodyLength;\n    observedBodyLength_ += addedBodyLength;\n    if (!bodyQueue_.empty() &&\n        bodyQueue_.back().eventType == HTTPBodyEvent::BODY &&\n        bodyQueue_.back().byteEventRegistrations.empty()) {\n      // Coalesce this bodyEvent into the last one in queue\n      auto& back = bodyQueue_.back();\n      back.event.body.append(bodyEvent.event.body.move());\n      back.eom = eom;\n      back.byteEventRegistrations = std::move(bodyEvent.byteEventRegistrations);\n    } else {\n      bodyQueue_.emplace_back(std::move(bodyEvent));\n    }\n  } else {\n    bodyQueue_.emplace_back(std::move(bodyEvent));\n  }\n  validateContentLength(eom);\n  return eom;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPBodyEventQueue.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n#include <folly/coro/Task.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\nnamespace proxygen::coro {\n\n/**\n * This class supports egress buffering and prioritization for HTTPCoroSession.\n * It will read and queue body events from a source, as long as there is space\n * available in its buffer.  Errors are returned direclty, but for non-error,\n * only the EOM flag is returned from readBodyEvent.\n *\n * The session will later call dequeueBodyEvent to retrieve the events for\n * serialization.\n */\nclass HTTPBodyEventQueue {\n public:\n  class Callback {\n   public:\n    virtual void onEgressBytesBuffered(int64_t bytes) noexcept = 0;\n    virtual ~Callback() = default;\n  };\n\n  HTTPBodyEventQueue(\n      folly::EventBase* evb,\n      HTTPCodec::StreamID id,\n      Callback& callback,\n      size_t limit = 65535,\n      std::chrono::milliseconds writeTimeout = std::chrono::seconds(5))\n      : id_(id),\n        callback_(callback),\n        evb_(evb),\n        writeTimeout_(writeTimeout),\n        limit_(limit) {\n  }\n\n  virtual ~HTTPBodyEventQueue() = default;\n\n  void setSource(HTTPSource* source) {\n    XCHECK(!source_);\n    source_.setSource(source);\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent();\n\n  // Like HTTPSource::readBodyEvent, but returns only eom signal and buffers\n  // the event.  Errors go right through.\n  struct ReadBodyResult {\n    folly::Optional<folly::coro::Task<TimedBaton::Status>> resume{folly::none};\n    bool eom{false};\n  };\n  folly::coro::Task<ReadBodyResult> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max());\n\n  uint64_t observedBodyLength() const {\n    return observedBodyLength_;\n  }\n\n  virtual void contentLengthMismatch() {\n    XLOG(ERR) << folly::to<std::string>(\n        \"Content-Length/body mismatch on egress: expected= \",\n        expectedContentLength_,\n        \", actual= \",\n        observedBodyLength_);\n    shouldValidateContentLength_ = false;\n  }\n\n  void validateContentLength(bool eom = false) {\n    if (shouldValidateContentLength_ &&\n        (observedBodyLength_ > expectedContentLength_ ||\n         (eom && observedBodyLength_ < expectedContentLength_))) {\n      contentLengthMismatch();\n    }\n  }\n\n  void setExpectedEgressContentLength(const std::string& contentLen, bool eom);\n\n  bool empty() const {\n    return bodyQueue_.empty();\n  }\n\n  void clear(const HTTPError& err);\n\n  HTTPBodyEvent dequeueBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max());\n\n  void stopReading(HTTPErrorCode error) {\n    if (source_) {\n      source_.stopReading(error);\n    }\n    // TODO: if this is called while a coroutine is blocked on events, the\n    // reading coroutine would be stuck.  The intent of the API not to do that.\n  }\n\n  void skipContentLengthValidation() {\n    shouldValidateContentLength_ = false;\n  }\n\n private:\n  /**\n   * return the amount of space available in the underlying buffer, comapred to\n   * limit_.  If the buffer is over the limit, wait for buffer space to become\n   * free.\n   *\n   * There's nothing \"HTTP\" specific here except we're using the HTTPError\n   * for convenience\n   */\n  folly::coro::Task<size_t> waitAvailableBuffer();\n\n  bool processBodyEvent(HTTPBodyEvent&& bodyEvent);\n\n  size_t availableBuffer() {\n    return limit_ >= bufferedBodyBytes_ ? limit_ - bufferedBodyBytes_ : 0;\n  }\n\n  HTTPCodec::StreamID id_;\n  Callback& callback_;\n  folly::EventBase* evb_;\n  std::chrono::milliseconds writeTimeout_;\n  detail::CancellableBaton event_;\n  HTTPSourceHolder source_;\n  std::list<HTTPBodyEvent> bodyQueue_;\n  size_t bufferedBodyBytes_{0};\n  size_t limit_;\n  uint64_t expectedContentLength_{0};\n  uint64_t observedBodyLength_{0};\n  bool shouldValidateContentLength_{true};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPByteEventHelpers.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPByteEventHelpers.h\"\n\nnamespace {\nconstexpr uint32_t kMaxTxAckEvents = 96;\n} // namespace\n\nnamespace proxygen::coro {\n\nsize_t PendingByteEvent::fireEvents(std::list<PendingByteEvent>& events,\n                                    uint64_t offset) {\n  size_t nEvents = 0;\n  while (!events.empty() && events.front().sessionOffset <= offset) {\n    auto& event = events.front();\n    auto cb = std::move(event.callback);\n    if (cb) {\n      cb->onByteEvent(std::move(event.byteEvent));\n    }\n    events.pop_front();\n    nEvents++;\n  }\n  return nEvents;\n}\n\nvoid PendingByteEvent::cancelEvents(std::list<PendingByteEvent>& events,\n                                    const HTTPError& error) {\n  while (!events.empty()) {\n    auto& event = events.front();\n    auto cb = std::move(event.callback);\n    if (cb) {\n      cb->onByteEventCanceled(std::move(event.byteEvent), error);\n    }\n    events.pop_front();\n  }\n}\n\nfolly::WriteFlags AsyncSocketByteEventObserver::TxAckEvent::writeFlags() {\n  folly::WriteFlags writeFlags = folly::WriteFlags::NONE;\n  for (auto& regAndEvent : regAndEvents) {\n    for (auto eventType : HTTPByteEvent::kByteEventTypes) {\n      if (regAndEvent.registration.events & uint8_t(eventType)) {\n        switch (eventType) {\n          case HTTPByteEvent::Type::NIC_TX:\n            writeFlags |= folly::WriteFlags::TIMESTAMP_TX;\n            break;\n          case HTTPByteEvent::Type::CUMULATIVE_ACK:\n            writeFlags |= folly::WriteFlags::TIMESTAMP_ACK;\n            break;\n          default:\n            break;\n        }\n      }\n    }\n  }\n  if (writeFlags != folly::WriteFlags::NONE) {\n    // Observer needs TIMESTAMP_WRITE to register for TIMESTAMP_TX/ACK\n    writeFlags |= folly::WriteFlags::TIMESTAMP_WRITE;\n  }\n  return writeFlags;\n}\n\nbool AsyncSocketByteEventObserver::canRegister(uint8_t nEvents) const {\n  return txAckByteEventsEnabled_ && nEvents > 0 &&\n         numPendingTxAckEvents_ + txEvents_.size() + ackEvents_.size() +\n                 nEvents <=\n             kMaxTxAckEvents;\n}\n\n/* We register for TIMESTAMP_WRITE events as well as TX/ACK so we can track\n * the state of the raw transport.  When HTTPCoroSession finishes its write(),\n * maxTransportWriteOffset_ will contain the raw transport offset of the last\n * byte of the write.\n */\nvoid AsyncSocketByteEventObserver::byteEvent(\n    folly::AsyncSocket* /* socket */,\n    const folly::AsyncSocketObserverInterface::ByteEvent& event) noexcept {\n  size_t nEvents = 0;\n  // Note: if there are other observers on this socket that register for TX or\n  // ACK events, this observer will also fire, even if it did _not_ register.\n  XLOG(DBG5) << \"byteEvent type=\" << uint32_t(event.type)\n             << \" off=\" << event.offset;\n  if (event.type ==\n      folly::AsyncSocketObserverInterface::ByteEvent::Type::WRITE) {\n    maxTransportWriteOffset_ = event.offset;\n  } else if (event.type ==\n             folly::AsyncSocketObserverInterface::ByteEvent::Type::TX) {\n    maxTransportTxOffset_ = event.offset;\n    nEvents = PendingByteEvent::fireEvents(txEvents_, event.offset);\n  } else if (event.type ==\n             folly::AsyncSocketObserverInterface::ByteEvent::Type::ACK) {\n    maxTransportAckOffset_ = event.offset;\n    nEvents = PendingByteEvent::fireEvents(ackEvents_, event.offset);\n  }\n  decRef(nEvents);\n}\n\n/* scheduleOrFireTxAckEvent will emplace the TX/ACK event, and fire it if\n * somehow we've already seen the TX/ACK of that offset in the observer.\n * Otherwise it bumps the refcount of outstanding byte events and schedules a\n * timeout.\n */\nvoid AsyncSocketByteEventObserver::scheduleOrFireTxAckEvent(\n    RegAndEvent regAndEvent) {\n  if (regAndEvent.registration.events & uint8_t(HTTPByteEvent::Type::NIC_TX)) {\n    XLOG(DBG5) << \"Scheduling TX event off=\" << maxTransportWriteOffset_;\n    // copy the ByteEvent, we are setting the type and moving it to txEvents_\n    auto event = regAndEvent.byteEvent;\n    event.type = HTTPByteEvent::Type::NIC_TX;\n    txEvents_.emplace_back(maxTransportWriteOffset_,\n                           std::move(event),\n                           regAndEvent.registration.callback);\n    if (maxTransportTxOffset_ >= maxTransportWriteOffset_) {\n      auto nEvents =\n          PendingByteEvent::fireEvents(txEvents_, maxTransportTxOffset_);\n      XCHECK_EQ(nEvents, 1u);\n    } else {\n      XCHECK(timer_);\n      refCount_.incRef();\n      txEvents_.back().refcount = &refCount_;\n      timer_->scheduleTimeout(&txEvents_.back(), byteEventTimeout_);\n    }\n  }\n  if (regAndEvent.registration.events &\n      uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK)) {\n    XLOG(DBG5) << \"Scheduling ACK event off=\" << maxTransportWriteOffset_;\n    regAndEvent.byteEvent.type = HTTPByteEvent::Type::CUMULATIVE_ACK;\n    ackEvents_.emplace_back(maxTransportWriteOffset_,\n                            std::move(regAndEvent.byteEvent),\n                            regAndEvent.registration.callback);\n    if (maxTransportAckOffset_ >= maxTransportWriteOffset_) {\n      auto nEvents =\n          PendingByteEvent::fireEvents(ackEvents_, maxTransportAckOffset_);\n      XCHECK_EQ(nEvents, 1u);\n    } else {\n      refCount_.incRef();\n      XCHECK(timer_);\n      ackEvents_.back().refcount = &refCount_;\n      timer_->scheduleTimeout(&ackEvents_.back(), byteEventTimeout_);\n    }\n  }\n  regAndEvent.registration.callback.reset();\n}\n\nvoid AsyncSocketByteEventObserver::registerByteEvents(\n    uint64_t streamID,\n    uint64_t sessionByteOffset,\n    uint64_t sessionBytesScheduled,\n    uint64_t streamOffset,\n    const folly::Optional<HTTPByteEvent::FieldSectionInfo>& fsInfo,\n    uint64_t bodyOffset,\n    std::vector<HTTPByteEventRegistration>&& registrations,\n    bool eom) {\n  auto localRegistrations = std::move(registrations);\n  for (auto& reg : localRegistrations) {\n    if (reg.events == 0 || !reg.callback) {\n      continue; // nothing to do\n    }\n    HTTPByteEvent ev;\n    ev.fieldSectionInfo = fsInfo;\n    ev.streamID = reg.streamID ? *reg.streamID : streamID;\n    ev.bodyOffset = bodyOffset;\n    ev.transportOffset = sessionByteOffset;\n    ev.streamOffset = streamOffset;\n    ev.eom = eom;\n    if (reg.events & uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE)) {\n      ev.type = HTTPByteEvent::Type::TRANSPORT_WRITE;\n      transportWriteEvents_.emplace_back(sessionByteOffset, ev, reg.callback);\n      reg.events &= ~uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE);\n    }\n    if (reg.events & uint8_t(HTTPByteEvent::Type::KERNEL_WRITE)) {\n      ev.type = HTTPByteEvent::Type::KERNEL_WRITE;\n      kernelWriteEvents_.emplace_back(sessionByteOffset, ev, reg.callback);\n      reg.events &= ~uint8_t(HTTPByteEvent::Type::KERNEL_WRITE);\n    }\n    auto numEvents = numTxAckEventFlags(reg.events);\n    if (numEvents > 0) {\n      if (canRegister(numEvents)) {\n        // If writeBuf_ is empty (sessionByteOffset == sessionBytesScheduled),\n        // schedule or fire the event immediately instead of queueing it.\n        if (sessionByteOffset == sessionBytesScheduled) {\n          scheduleOrFireTxAckEvent(\n              RegAndEvent({.registration = std::move(reg), .byteEvent = ev}));\n        } else {\n          if (txAckEvents_.empty() ||\n              txAckEvents_.back().sessionByteOffset != sessionByteOffset) {\n            txAckEvents_.emplace_back(sessionByteOffset);\n          }\n          txAckEvents_.back().regAndEvents.emplace_back(\n              RegAndEvent({.registration = std::move(reg), .byteEvent = ev}));\n          numPendingTxAckEvents_ += numEvents;\n        }\n      } else {\n        // Transport byte events not enabled, or at max TX/ACK events\n        std::string errorMessage = !txAckByteEventsEnabled_\n                                       ? \"TX/ACK Events disabled\"\n                                       : \"Too many TX/ACK Events registered\";\n        reg.cancel(HTTPError(HTTPErrorCode::CANCEL, std::move(errorMessage)),\n                   std::move(ev));\n      }\n    } // implicit cancellation but there's no events left\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPByteEventHelpers.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPEvents.h\"\n#include \"proxygen/lib/http/coro/util/Refcount.h\"\n\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <quic/api/QuicSocket.h>\n\n#include <folly/Utility.h>\n\nnamespace proxygen::coro {\n\nclass PendingByteEvent : public folly::HHWheelTimer::Callback {\n public:\n  PendingByteEvent(uint64_t inSessionOffset,\n                   HTTPByteEvent inByteEvent,\n                   HTTPByteEventCallbackPtr inCallback)\n      : sessionOffset(inSessionOffset),\n        byteEvent(std::move(inByteEvent)),\n        callback(std::move(inCallback)) {\n  }\n\n  ~PendingByteEvent() override {\n    if (callback) {\n      XLOG(DFATAL) << \"Cancelled PendingByteEvent with active callback\";\n      HTTPError err(HTTPErrorCode::CANCEL, \"ByteEvent Cancelled\");\n      callback->onByteEventCanceled(byteEvent, std::move(err));\n    }\n  }\n\n  void timeoutExpired() noexcept override {\n    XLOG(DBG4) << \"Timed out waiting for ByteEvent\";\n    if (auto cb = std::move(callback)) {\n      cb->onByteEventCanceled(byteEvent,\n                              HTTPError(HTTPErrorCode::READ_TIMEOUT,\n                                        \"Timed out waiting for ByteEvent\"));\n    }\n    XCHECK(refcount);\n    // incRef called when timeout scheduled in scheduleOrFireTxAckEvent\n    refcount->decRef();\n    refcount = nullptr;\n  }\n\n  void callbackCanceled() noexcept override {\n    XCHECK(refcount);\n    refcount->decRef();\n    refcount = nullptr;\n  }\n\n  static size_t fireEvents(std::list<PendingByteEvent>& events,\n                           uint64_t offset);\n\n  static void cancelEvents(std::list<PendingByteEvent>& events,\n                           const HTTPError& error);\n\n  uint64_t sessionOffset;\n  HTTPByteEvent byteEvent;\n  HTTPByteEventCallbackPtr callback;\n  // Initialized in scheduleOrFireTxAckEvent\n  Refcount* refcount{nullptr};\n};\n\n// Holds byte events discovered during an iteration of writeLoop, and\n// registered after QuicSocket::writeChain\nstruct QuicWriteLoopByteEvent {\n  QuicWriteLoopByteEvent(\n      std::vector<HTTPByteEventRegistration> inReg,\n      folly::Optional<HTTPByteEvent::FieldSectionInfo> inFsInfo,\n      uint64_t inBodyOffset,\n      uint64_t inEventOffset)\n      : byteEventRegistrations(std::move(inReg)),\n        fieldSectionInfo(std::move(inFsInfo)),\n        bodyOffset(inBodyOffset),\n        eventOffset(inEventOffset) {\n  }\n  std::vector<HTTPByteEventRegistration> byteEventRegistrations;\n  folly::Optional<HTTPByteEvent::FieldSectionInfo> fieldSectionInfo;\n  uint64_t bodyOffset;\n  uint64_t eventOffset;\n};\n\nclass QuicByteEventCallback : public quic::ByteEventCallback {\n public:\n  QuicByteEventCallback(Refcount& refcount,\n                        HTTPByteEvent httpByteEvent,\n                        HTTPByteEventCallbackPtr callback)\n      : refcount_(refcount),\n        httpByteEvent_(std::move(httpByteEvent)),\n        callback_(std::move(callback)) {\n  }\n\n  [[nodiscard]] quic::ByteEvent::Type quicByteEventType() const {\n    switch (httpByteEvent_.type) {\n      case HTTPByteEvent::Type::NIC_TX:\n      case HTTPByteEvent::Type::KERNEL_WRITE:\n        return quic::ByteEvent::Type::TX;\n      case HTTPByteEvent::Type::CUMULATIVE_ACK:\n        return quic::ByteEvent::Type::ACK;\n      case HTTPByteEvent::Type::TRANSPORT_WRITE:\n      default:\n        XLOG(FATAL) << \"Invalid event type \"\n                    << folly::to_underlying(httpByteEvent_.type);\n    }\n  }\n\n  void cancel() {\n    callback_->onByteEventCanceled(\n        httpByteEvent_, HTTPError(HTTPErrorCode::TRANSPORT_WRITE_ERROR));\n    delete this;\n  }\n\n private:\n  void onByteEventRegistered(quic::ByteEvent byteEvent) override {\n    XLOG(DBG4) << \"onByteEventRegistered for id=\" << byteEvent.id;\n    refcount_.incRef();\n  }\n\n  void onByteEvent(quic::ByteEvent byteEvent) override {\n    XLOG(DBG4) << \"onByteEvent for id=\" << byteEvent.id;\n    callback_->onByteEvent(httpByteEvent_);\n    refcount_.decRef();\n    delete this;\n  }\n\n  void onByteEventCanceled(quic::ByteEvent byteEvent) override {\n    XLOG(DBG4) << \"onByteEventCanceled for id=\" << byteEvent.id;\n    auto& refcount = refcount_;\n    cancel();\n    refcount.decRef();\n  }\n\n  Refcount& refcount_;\n  HTTPByteEvent httpByteEvent_;\n  HTTPByteEventCallbackPtr callback_;\n};\n\nclass AsyncSocketByteEventObserver\n    : public folly::AsyncSocket::LegacyLifecycleObserver {\n public:\n  struct RegAndEvent {\n    HTTPByteEventRegistration registration;\n    HTTPByteEvent byteEvent;\n  };\n  struct TxAckEvent {\n    explicit TxAckEvent(uint64_t offset) : sessionByteOffset(offset) {\n    }\n    const uint64_t sessionByteOffset;\n    std::vector<RegAndEvent> regAndEvents;\n    folly::WriteFlags writeFlags();\n\n    void cancel(const HTTPError& error) {\n      auto vec = std::move(regAndEvents);\n      for (auto& regAndEvent : vec) {\n        regAndEvent.registration.cancel(error,\n                                        std::move(regAndEvent.byteEvent));\n      }\n    }\n  };\n\n  AsyncSocketByteEventObserver()\n      : LegacyLifecycleObserver(getConfigWithByteEventsEnabled()) {\n  }\n\n  void setByteEventTimeout(std::chrono::milliseconds timeout) {\n    byteEventTimeout_ = timeout;\n  }\n\n  void cancelEvents(const HTTPError& error) {\n    PendingByteEvent::cancelEvents(transportWriteEvents_, error);\n    PendingByteEvent::cancelEvents(kernelWriteEvents_, error);\n    for (auto& txAckEvent : txAckEvents_) {\n      txAckEvent.cancel(error);\n    }\n    txAckEvents_.clear();\n    numPendingTxAckEvents_ = 0;\n    PendingByteEvent::cancelEvents(txEvents_, error);\n    PendingByteEvent::cancelEvents(ackEvents_, error);\n  }\n\n  void registerByteEvents(\n      uint64_t streamID,\n      uint64_t sessionByteOffset,\n      uint64_t sessionBytesScheduled,\n      uint64_t streamOffset,\n      const folly::Optional<HTTPByteEvent::FieldSectionInfo>& fsInfo,\n      uint64_t bodyOffset,\n      std::vector<HTTPByteEventRegistration>&& registrations,\n      bool eom);\n\n  void transportWrite(uint64_t sessionWrittenOffset) {\n    PendingByteEvent::fireEvents(transportWriteEvents_, sessionWrittenOffset);\n  }\n\n  void transportWriteComplete(uint64_t sessionWrittenOffset,\n                              folly::Optional<TxAckEvent> txAckEvent) {\n    PendingByteEvent::fireEvents(kernelWriteEvents_, sessionWrittenOffset);\n    if (txAckEvent) {\n      for (auto& regAndEvent : txAckEvent->regAndEvents) {\n        scheduleOrFireTxAckEvent(std::move(regAndEvent));\n      }\n    }\n  }\n\n  folly::coro::Task<TimedBaton::Status> zeroRefs() {\n    return refCount_.zeroRefs();\n  }\n\n  folly::Optional<TxAckEvent> nextTxAckEvent() {\n    if (txAckEvents_.empty()) {\n      return folly::none;\n    } else {\n      TxAckEvent txAckEvent(std::move(txAckEvents_.front()));\n      txAckEvents_.pop_front();\n      for (auto& regAndEvent : txAckEvent.regAndEvents) {\n        numPendingTxAckEvents_ -=\n            numTxAckEventFlags(regAndEvent.registration.events);\n      }\n      return txAckEvent;\n    }\n  }\n\n  [[nodiscard]] bool isRegistered() const {\n    return timer_ != nullptr;\n  }\n\n  // AsyncSocket::LegacyLifecycleObserver overrides\n  void observerAttach(folly::AsyncSocket* sock) noexcept override {\n    timer_ = &sock->getEventBase()->timer();\n  }\n  void observerDetach(folly::AsyncSocket*) noexcept override {\n    timer_ = nullptr;\n  }\n  void destroy(folly::AsyncSocket* /* socket */) noexcept override {\n  }\n  void byteEvent(folly::AsyncSocket*,\n                 const folly::AsyncSocketObserverInterface::ByteEvent&\n                     event) noexcept override;\n\n  void byteEventsEnabled(folly::AsyncSocket*) noexcept override {\n    txAckByteEventsEnabled_ = true;\n  }\n\n  void byteEventsUnavailable(\n      folly::AsyncSocket*,\n      const folly::AsyncSocketException&) noexcept override {\n    txAckByteEventsEnabled_ = false;\n  }\n\n private:\n  static Config getConfigWithByteEventsEnabled() {\n    Config config;\n    config.byteEvents = true;\n    return config;\n  }\n\n  uint8_t numTxAckEventFlags(uint8_t events) {\n    bool txEvent = events & uint8_t(HTTPByteEvent::Type::NIC_TX);\n    bool ackEvent = events & uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK);\n    return (txEvent ? 1 : 0) + (ackEvent ? 1 : 0);\n  }\n\n  // There's a maximum number of outstanding events, returns true if we can\n  // register nEvents more without exceeding the limit\n  bool canRegister(uint8_t nEvents) const;\n\n  void scheduleOrFireTxAckEvent(RegAndEvent regAndEvent);\n\n  void decRef(size_t nEvents) {\n    while (nEvents-- > 0) {\n      refCount_.decRef();\n    }\n  }\n\n  folly::HHWheelTimer* timer_{nullptr};\n  size_t maxTransportWriteOffset_{0};\n  size_t maxTransportTxOffset_{0};\n  size_t maxTransportAckOffset_{0};\n  std::chrono::milliseconds byteEventTimeout_{std::chrono::seconds(10)};\n  Refcount refCount_;\n  std::list<PendingByteEvent> transportWriteEvents_;\n  std::list<PendingByteEvent> kernelWriteEvents_;\n  std::list<TxAckEvent> txAckEvents_;\n  std::list<PendingByteEvent> txEvents_;\n  std::list<PendingByteEvent> ackEvents_;\n  uint8_t numPendingTxAckEvents_{0};\n  bool txAckByteEventsEnabled_{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPByteEvents.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include <folly/Optional.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/utils/WeakRefCountedPtr.h>\n\nnamespace proxygen::coro {\n\n/**\n * An HTTPByteEvent is an informational structure provided to an\n * HTTPByteEventCallback when the event fires, or is canceled.  Every byte event\n * has a Type:\n *\n *  TRANSPORT_WRITE: Event has been framed at the HTTP layer and is about to be\n *                   written to the transport\n *  KERNEL_WRITE:    Event has been successfully written to the kernel\n *  NIC_TX:          Event has been transmitted by the kernel to the network\n *  CUMULATIVE_ACK:  All bytes on this stream up to this Event have been\n *                   acknowledged by the peer.\n *\n * For HTTP/1x and HTTP/2, transportOffset is the offset of the event within the\n * HTTP session's egress byte stream.\n *\n * For HTTP/3, transportOffset is the offset within the QUIC stream carrying the\n * event.\n */\nstruct HTTPByteEvent {\n  enum class Type : uint8_t {\n    TRANSPORT_WRITE = 0x01,\n    KERNEL_WRITE = 0x02,\n    NIC_TX = 0x04,\n    CUMULATIVE_ACK = 0x08,\n  };\n  static constexpr std::array<Type, 4> kByteEventTypes = {Type::TRANSPORT_WRITE,\n                                                          Type::KERNEL_WRITE,\n                                                          Type::NIC_TX,\n                                                          Type::CUMULATIVE_ACK};\n\n  // The stream ID may not be known in all cases\n  folly::Optional<uint64_t> streamID;\n  // fieldSectionInfo contains information about how the field section was\n  // serialized and compressed, for HEADERS, TRAILERS or PUSH_PROMISE.  If the\n  // event was cancelled before serialization, or for body events, this field is\n  // not set.\n  struct FieldSectionInfo {\n    enum class Type { HEADERS, PUSH_PROMISE, TRAILERS };\n    Type type;\n    bool finalHeaders;\n    HTTPHeaderSize size;\n  };\n  folly::Optional<FieldSectionInfo> fieldSectionInfo;\n  uint64_t bodyOffset{0};      // body offset of the last byte of event\n  uint64_t transportOffset{0}; // transport offset of the last byte of event\n  uint64_t streamOffset{0};    // stream offset of the last byte of event\n\n  bool eom{false};\n  Type type;\n};\n\n/**\n * Callback class for receiving byte event notifications.\n */\nclass HTTPByteEventCallback\n    : public EnableWeakRefCountedPtr<HTTPByteEventCallback> {\n public:\n  ~HTTPByteEventCallback() override = default;\n\n  /**\n   * Invoked when the byte event has occurred.\n   */\n  virtual void onByteEvent(HTTPByteEvent byteEvent) = 0;\n\n  /**\n   * Invoked if byte event is canceled due to reset, shutdown, or other error.\n   *\n   * The bodyOffset, transportOffset and eom flag of cancelledByteEvent may not\n   * be known yet.\n   */\n  virtual void onByteEventCanceled(HTTPByteEvent cancelledByteEvent,\n                                   HTTPError error) = 0;\n\n protected:\n  // By default, the library uses HTTPByteEventCallback's like std::weak_ptr --\n  // if the target has been deleted, the callback is ignored when the event\n  // fires.  To guarantee callback delivery, delay destruction until\n  // numWeakRefCountedPtrs() in onWeakRefCountedPtrDestroy() reaches 0.\n  // See P583861615 for a helper class that can be used with std::shared_ptr\n  void onWeakRefCountedPtrDestroy() override {\n  }\n  friend class WeakRefCountedPtr<HTTPByteEventCallback>;\n};\n\nusing HTTPByteEventCallbackPtr = WeakRefCountedPtr<HTTPByteEventCallback>;\n\n/**\n * This structure is supplied to an HTTPHeaderEvent or HTTPBodyEvent to register\n * for callbacks about its progress.  Once a registration has been created,\n * either onByteEvent or onByteEventCanceled will be invoked for each event\n * type.\n */\nstruct HTTPByteEventRegistration {\n  folly::Optional<uint64_t> streamID; // if known\n  uint8_t events{0};                  // Bitwise OR of HTTPByteEvent::Type\n  HTTPByteEventCallbackPtr callback;\n\n  HTTPByteEventRegistration() = default;\n  HTTPByteEventRegistration(HTTPByteEventRegistration&& goner) = default;\n  HTTPByteEventRegistration& operator=(HTTPByteEventRegistration&& goner) =\n      default;\n  ~HTTPByteEventRegistration() {\n    cancel(\n        HTTPError(HTTPErrorCode::CANCEL, \"ByteEvent registration cancelled\"));\n  }\n  void cancel(const HTTPError& error,\n              folly::Optional<HTTPByteEvent> byteEvent = folly::none) {\n    if (!byteEvent) {\n      byteEvent.emplace();\n      byteEvent->streamID = streamID;\n    }\n    for (auto t : HTTPByteEvent::kByteEventTypes) {\n      if (events & uint8_t(t) && callback) {\n        byteEvent->type = t;\n        callback->onByteEventCanceled(*byteEvent, error);\n      }\n    }\n    callback.reset();\n  }\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPCoroSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n#include <proxygen/lib/http/codec/HTTPParallelCodec.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/http/webtransport/HTTPWebTransport.h>\n\n#include <folly/logging/xlog.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n#include <quic/state/QuicStreamUtilities.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\n#include <proxygen/lib/http/coro/util/CoroWtSession.h>\n\nnamespace {\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nconstexpr size_t kMinReadSize = 1460;\nconstexpr size_t kReadBufNewAllocSize = 4000;\n// 16 is the default reads-per-loop for AsyncSocket\nconstexpr uint64_t kMaxReadDataPerLoop = kReadBufNewAllocSize * 16;\n// 64 data (default fcw) + 9 byte stream headers for 100 (default) streams\nconstexpr uint32_t kWriteBufLimit = 65535 + 900;\nconstexpr uint64_t kMaxQuarterStreamId = (1ull << 60) - 1;\n\nconst quic::QuicError kHTTPNoError{\n    quic::ApplicationErrorCode(HTTP3::ErrorCode::HTTP_NO_ERROR), \"\"};\n\nconst HTTPPriority kDefaultPriority{3, false};\n\n//  ErrorCodes that can be generated from within HTTPStreamSource\nHTTPErrorCode sourceCompleteErr2ErrorCode(HTTPErrorCode ec) {\n  switch (ec) {\n    case HTTPErrorCode::NO_ERROR:\n    case HTTPErrorCode::CANCEL:\n    case HTTPErrorCode::FLOW_CONTROL_ERROR:\n      return ec;\n    case HTTPErrorCode::CORO_CANCELLED:\n      // Called when a reader cancels a reading coroutine\n      return HTTPErrorCode::CANCEL;\n    case HTTPErrorCode::READ_TIMEOUT:\n      return HTTPErrorCode::CANCEL;\n    case HTTPErrorCode::INVALID_STATE_TRANSITION:\n      return HTTPErrorCode::MESSAGE_ERROR;\n    case HTTPErrorCode::CONTENT_LENGTH_MISMATCH:\n      return HTTPErrorCode::CONTENT_LENGTH_MISMATCH;\n    default:\n      XLOG(FATAL) << \"Invalid error from sourceComplete ec=\" << uint16_t(ec);\n  }\n}\n\n#define SESS_STATS(method, ...)                                             \\\n  if (sessionStats_) {                                                      \\\n    folly::invoke(&HTTPSessionStats::method, sessionStats_, ##__VA_ARGS__); \\\n  }                                                                         \\\n  static_assert(true, \"semicolon required\")\n\nvoid setSecureMsg(HTTPMessage& msg,\n                  const wangle::TransportInfo& setupTransportInfo) {\n  msg.setSecureInfo(setupTransportInfo.sslVersion,\n                    setupTransportInfo.sslCipher\n                        ? setupTransportInfo.sslCipher->c_str()\n                        : nullptr);\n  msg.setSecure(setupTransportInfo.secure);\n}\n\nusing CoroWtSession = proxygen::coro::detail::CoroWtSession;\nclass CoroWtSessionImpl : public CoroWtSession {\n public:\n  using CoroWtSession::CoroWtSession;\n  HTTPSessionContextPtr ka{}; // ensures session isn't destroyed before WtHandle\n  const folly::SocketAddress& getLocalAddress() const noexcept override {\n    return ka->getLocalAddress();\n  }\n  const folly::SocketAddress& getPeerAddress() const noexcept override {\n    return ka->getPeerAddress();\n  }\n};\n\n} // namespace\n\nusing folly::coro::co_error;\nusing folly::coro::co_nothrow;\nusing folly::coro::co_withCancellation;\n\nnamespace proxygen::coro {\n\nHTTPSource* getErrorResponse(uint16_t statusCode, const std::string& body) {\n  auto resp = proxygen::coro::HTTPFixedSource::makeFixedResponse(\n      statusCode, folly::IOBuf::copyBuffer(body));\n  resp->msg_->setWantsKeepalive(false);\n  return resp;\n}\n\nstruct HTTPCoroSession::WtHelper {\n  HTTPCoroSession& sess;\n  auto createEgressSource() const noexcept {\n    return detail::EgressSourcePtr(new detail::EgressSource(\n        sess.eventBase_.get(),\n        /*id=*/folly::none,\n        /*callback=*/nullptr,\n        /*egressBufferSize=*/sess.getStreamSendFlowControlWindow()));\n  }\n  auto createHttpSourceTransport(detail::EgressSourcePtr&& egress,\n                                 HTTPSourceHolder&& ingress) const noexcept {\n    return detail::makeHttpSourceTransport(\n        sess.eventBase_.get(), std::move(egress), std::move(ingress));\n  }\n  auto createWtSession(std::unique_ptr<folly::coro::TransportIf> transport,\n                       WebTransportHandler::Ptr wtHandler) noexcept {\n    using namespace proxygen::detail;\n    auto dir = sess.isDownstream() ? WtDir::Server : WtDir::Client;\n    auto wt = std::make_shared<CoroWtSessionImpl>(\n        sess.eventBase_.get(),\n        dir,\n        getWtConfig(sess.codec_->getIngressSettings(),\n                    sess.codec_->getEgressSettings()),\n        std::move(wtHandler),\n        std::move(transport));\n    wt->ka = sess.acquireKeepAlive();\n    wt->start(wt);\n    return wt;\n  }\n};\n\nstruct HTTPCoroSession::StreamState {\n private:\n  // Detach Criteria: source and egressCoro start as complete until the stream\n  // enters a state where they are relevant.\n  bool streamSourceComplete_ : 1;\n  bool egressComplete_ : 1;\n  bool egressCoroComplete_ : 1;\n\n  bool deferredStopSending_ : 1;\n  bool pendingEgressEOM_ : 1; // required for QUIC with no body\n\n  // Egress State\n  bool egressStarted_ : 1;\n\n  enum class Upgrade : uint8_t {\n    NONE,\n    PENDING,\n    UPGRADED\n  } upgrade_{Upgrade::NONE};\n  Window sendWindow_;\n  HTTPBodyEventQueue bodyEventQueue_;\n  folly::IOBufQueue* writeBuf_{nullptr};\n  folly::IOBufQueue hqWriteBuf_{folly::IOBufQueue::cacheChainLength()};\n  HTTPPriority priority_;\n  uint64_t streamOffset_{0}; // total bytes including codec framing overhead\n\n public:\n  HTTPStreamSource streamSource;\n  struct {\n    folly::CancellationSource ingress, egress;\n  } cs;\n\n  // handlers can only egress this status code on some ingress errors\n  uint16_t errorStatusCode{0};\n\n  // Push state - these are left public because the session usually\n  // has to manipulate state of two streams at the same time (parent & push)\n\n  // Only set for push streams\n  std::optional<HTTPCodec::StreamID> parent;\n  std::optional<uint64_t> currentPushID;\n\n  constexpr static size_t kEgressBufferLimit = 65535;\n\n  StreamState(folly::EventBase* evb,\n              HTTPCodec::StreamID id,\n              HTTPCoroSession& session,\n              uint32_t sendFlowControlWindow,\n              uint32_t recvFlowControlWindow,\n              std::chrono::milliseconds readTimeout,\n              std::chrono::milliseconds writeTimeout)\n      : sendWindow_(sendFlowControlWindow),\n        bodyEventQueue_(evb, id, session, kEgressBufferLimit, writeTimeout),\n        streamSource(evb, id, session, recvFlowControlWindow, readTimeout) {\n    streamSourceComplete_ = true;\n    egressComplete_ = false;\n    egressCoroComplete_ = true;\n    deferredStopSending_ = false;\n    pendingEgressEOM_ = false;\n    egressStarted_ = false;\n  }\n\n  HTTPCodec::StreamID getID() const {\n    return streamSource.getID();\n  }\n\n  void setWriteBuf(folly::IOBufQueue* writeBuf) {\n    if (writeBuf) {\n      writeBuf_ = writeBuf;\n    } else {\n      writeBuf_ = &hqWriteBuf_;\n    }\n  }\n\n  folly::IOBufQueue& getWriteBuf() {\n    return *writeBuf_;\n  }\n\n  void abortIngress(HTTPErrorCode err, std::string_view details = \"\") {\n    streamSource.abort(err, details);\n    cs.ingress.requestCancellation();\n  }\n\n  void setReadTimeout(std::chrono::milliseconds readTimeout) {\n    // A caller may have already set a specific timeout for this source\n    if (streamSource.getReadTimeout() == std::chrono::milliseconds(0)) {\n      streamSource.setReadTimeout(readTimeout);\n    }\n  }\n\n  HTTPPriority getPriority() const {\n    return priority_;\n  }\n\n  void setPriority(HTTPPriority priority) {\n    priority_ = priority;\n  }\n\n  uint64_t getStreamOffset() const {\n    return streamOffset_;\n  }\n\n  void addToStreamOffset(uint64_t bytes) {\n    streamOffset_ += bytes;\n  }\n\n  void initIngressPush(HTTPCodec::StreamID assocStreamID) {\n    parent = assocStreamID;\n    markEgressComplete();\n  }\n\n  void streamSourceActive() {\n    // Called when the streamSource is handed outside the class for reading\n    streamSourceComplete_ = false;\n  }\n\n  void startEgressCoro() {\n    // Called when this CoroSession starts a coroutine reading from an\n    // external source to produce egress on this stream.\n    egressCoroComplete_ = false;\n  }\n\n  // Returns true if there is an upgrade in progress or completed\n  bool checkForUpgrade(const HTTPMessage& msg, bool isIngress) {\n    bool isWebSocketUpgrade = (isIngress && msg.isIngressWebsocketUpgrade()) ||\n                              (!isIngress && msg.isEgressWebsocketUpgrade());\n    if (msg.isRequest() &&\n        (msg.getMethod() == HTTPMethod::CONNECT || isWebSocketUpgrade)) {\n      XCHECK(upgrade_ == Upgrade::NONE);\n      upgrade_ = Upgrade::PENDING;\n    } else if (upgrade_ == Upgrade::PENDING && msg.isResponse() &&\n               (msg.getStatusCode() == 200 || isWebSocketUpgrade)) {\n      upgrade_ = Upgrade::UPGRADED;\n    }\n    return upgrade_ != Upgrade::NONE;\n  }\n\n  bool isUpgraded() const {\n    return upgrade_ == Upgrade::UPGRADED;\n  }\n\n  void markIngressAsHeadResponse() {\n    streamSource.skipContentLengthValidation();\n  }\n\n  void markStreamSourceComplete() {\n    XCHECK(!streamSourceComplete_);\n    streamSourceComplete_ = true;\n  }\n\n  void markEgressComplete() {\n    egressComplete_ = true;\n  }\n\n  void markEgressCoroComplete() {\n    XCHECK(!egressCoroComplete_);\n    egressCoroComplete_ = true;\n  }\n\n  bool canSendHeaders() const {\n    return !egressStarted_ && !egressComplete_;\n  }\n\n  bool isEgressComplete() const {\n    return egressComplete_;\n  }\n\n  bool isDetachable() const {\n    // streamSourceComplete -> external source reader finished or stopReading\n    // egressComplete -> we have egressed a FIN, RST or received a RST\n    // egressCoroComplete -> internal coroutine producing stream egress\n    // terminated\n    return (streamSourceComplete_ && egressComplete_ && egressCoroComplete_);\n  }\n\n  uint64_t observedBodyLength() const {\n    return bodyEventQueue_.observedBodyLength();\n  }\n\n  void setPendingEgressEOM() {\n    pendingEgressEOM_ = true;\n  }\n\n  bool pendingEgressEOM() const {\n    return pendingEgressEOM_;\n  }\n\n  bool isBodyQueueEmpty() const {\n    return bodyEventQueue_.empty();\n  }\n\n  void setEgressSource(HTTPSource* source) {\n    bodyEventQueue_.setSource(source);\n  }\n\n  void setErrorStatusCode(uint16_t statusCode) {\n    XCHECK(canSendHeaders());\n    XCHECK(!egressCoroComplete_);\n    errorStatusCode = statusCode;\n    cs.ingress.requestCancellation();\n    std::exchange(cs.egress, {}).requestCancellation();\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> nextHeaderEvent() {\n    // TODO: do we want to throttle reading response headers on buffer space\n    auto headerEvent = co_await co_nothrow(bodyEventQueue_.readHeaderEvent());\n    egressStarted_ = headerEvent.isFinal();\n    co_return headerEvent;\n  }\n\n  folly::coro::Task<HTTPBodyEventQueue::ReadBodyResult> nextBodyEvent() {\n    return bodyEventQueue_.readBodyEvent();\n  }\n\n  void stopReading(HTTPErrorCode error) {\n    bodyEventQueue_.stopReading(error);\n  }\n\n  std::pair<HTTPBodyEvent, bool> nextEgressEvent(uint32_t maxToSend) {\n    bool flowControlBlocked = false;\n    maxToSend = std::min(maxToSend, sendWindow_.getNonNegativeSize());\n    HTTPBodyEvent bodyEvent(bodyEventQueue_.dequeueBodyEvent(maxToSend));\n    if (bodyEvent.eventType == HTTPBodyEvent::BODY) {\n      auto length = bodyEvent.event.body.chainLength();\n      XLOG(DBG4) << \"Sending body length=\" << length << \" id=\" << getID()\n                 << \" eom=\" << uint32_t(bodyEvent.eom);\n      if (length == 0 && !bodyEvent.eom) {\n        flowControlBlocked = true;\n      } else {\n        XCHECK(sendWindow_.reserve(length));\n      }\n    }\n    XCHECK(!bodyEvent.eom || bodyEventQueue_.empty())\n        << \"stream returned EOM with pending events\";\n    if (bodyEvent.eom) {\n      markEgressComplete();\n    }\n    return {std::move(bodyEvent), flowControlBlocked};\n  }\n\n  folly::Expected<bool, folly::Unit> onWindowUpdate(uint32_t amount) {\n    auto wasBlocked = (sendWindow_.getSize() <= 0);\n    if (!sendWindow_.free(amount)) {\n      return folly::makeUnexpected(folly::Unit());\n    }\n    auto isBlocked = (sendWindow_.getSize() <= 0);\n    return wasBlocked && !isBlocked && !bodyEventQueue_.empty();\n  }\n\n  void setSendWindow(uint32_t capacity) {\n    sendWindow_.setCapacity(capacity);\n  }\n\n  bool isFlowControlBlocked() const {\n    return sendWindow_.getSize() <= 0;\n  }\n\n  void resetStream(const HTTPError& err) {\n    markEgressComplete();\n    if (!bodyEventQueue_.empty()) {\n      XLOG(DBG4) << \"Discarding pending egress on reset for stream=\" << getID();\n      bodyEventQueue_.clear(err);\n    }\n    cs.egress.requestCancellation();\n    if (streamSourceComplete_) {\n      cs.ingress.requestCancellation();\n    }\n    deferredStopSending_ = false;\n  }\n\n  bool isStateReset() const {\n    return isBodyQueueEmpty() && cs.egress.isCancellationRequested();\n  }\n\n  void setExpectedEgressContentLength(const std::string& contentLen, bool eom) {\n    bodyEventQueue_.setExpectedEgressContentLength(contentLen, eom);\n  }\n\n  void skipEgressContentLengthValidation() {\n    bodyEventQueue_.skipContentLengthValidation();\n  }\n\n  void setDeferredStopSending(bool deferredStopSending) {\n    deferredStopSending_ = deferredStopSending;\n  }\n\n  bool getDeferredStopSending() const {\n    return deferredStopSending_;\n  }\n};\n\nusing quic::HTTPPriorityQueue;\n\nHTTPCoroSession::HTTPCoroSession(folly::EventBase* eventBase,\n                                 folly::SocketAddress localAddr,\n                                 folly::SocketAddress peerAddr,\n                                 std::unique_ptr<HTTPCodec> codec,\n                                 wangle::TransportInfo tinfo,\n                                 std::shared_ptr<HTTPHandler> handler)\n    : eventBase_(eventBase),\n      direction_(handler ? TransportDirection::DOWNSTREAM\n                         : TransportDirection::UPSTREAM),\n      localAddr_(std::move(localAddr)),\n      peerAddr_(std::move(peerAddr)),\n      codec_(std::move(codec)),\n      handler_(std::move(handler)),\n      setupTransportInfo_(std::move(tinfo)),\n      sendWindow_(codec_->supportsSessionFlowControl()\n                      ? codec_->getDefaultWindowSize()\n                      : std::numeric_limits<int32_t>::max()),\n      recvWindow_(codec_->supportsSessionFlowControl()\n                      ? codec_->getDefaultWindowSize()\n                      : std::numeric_limits<int32_t>::max()) {\n  localAddr_.tryConvertToIPv4();\n  peerAddr_.tryConvertToIPv4();\n}\n\nHTTPCoroSession::~HTTPCoroSession() {\n  XLOG(DBG4) << \"Destroying \" << *this;\n  XCHECK(streams_.empty());\n  XCHECK_EQ(numPushStreams_, 0UL);\n  SESS_STATS(recordTransactionsServed, nextStreamSequenceNumber_);\n  deliverLifecycleEvent(&LifecycleObserver::onDestroy, *this);\n}\n\nHTTPQuicCoroSession::HTTPQuicCoroSession(\n    std::shared_ptr<quic::QuicSocket> sock,\n    std::unique_ptr<hq::HQMultiCodec> codec,\n    wangle::TransportInfo tinfo,\n    std::shared_ptr<HTTPHandler> handler)\n    : HTTPCoroSession(sock->getEventBase()\n                          ->getTypedEventBase<quic::FollyQuicEventBase>()\n                          ->getBackingEventBase(),\n                      sock->getLocalAddress(),\n                      sock->getPeerAddress(),\n                      std::move(codec),\n                      std::move(tinfo),\n                      std::move(handler)),\n      quicSocket_(std::move(sock)),\n      multiCodec_(static_cast<hq::HQMultiCodec*>(codec_.getChainEndPtr())),\n      qpackEncoderCodec_(multiCodec_->getQPACKCodec(), *this),\n      qpackDecoderCodec_(multiCodec_->getQPACKCodec(), *this),\n      uniStreamDispatcher_(*this, direction_) {\n  start();\n}\nHTTPQuicCoroSession::~HTTPQuicCoroSession() = default;\n\nHTTPCoroSession* HTTPCoroSession::makeUpstreamCoroSession(\n    std::unique_ptr<folly::coro::TransportIf> coroTransport,\n    std::unique_ptr<HTTPCodec> codec,\n    wangle::TransportInfo tinfo) {\n  XCHECK(proxygen::isUpstream(codec->getTransportDirection()));\n  return new HTTPUniplexTransportSession(\n      std::move(coroTransport), std::move(codec), std::move(tinfo));\n}\n\nHTTPCoroSession* HTTPCoroSession::makeDownstreamCoroSession(\n    std::unique_ptr<folly::coro::TransportIf> coroTransport,\n    std::shared_ptr<HTTPHandler> handler,\n    std::unique_ptr<HTTPCodec> codec,\n    wangle::TransportInfo tinfo) {\n  XCHECK(proxygen::isDownstream(codec->getTransportDirection()));\n  return new HTTPUniplexTransportSession(std::move(coroTransport),\n                                         std::move(codec),\n                                         std::move(tinfo),\n                                         std::move(handler));\n}\n\nHTTPCoroSession* HTTPCoroSession::makeUpstreamCoroSession(\n    std::shared_ptr<quic::QuicSocket> sock,\n    std::unique_ptr<hq::HQMultiCodec> codec,\n    wangle::TransportInfo tinfo) {\n  XCHECK(proxygen::isUpstream(codec->getTransportDirection()));\n  return new HTTPQuicCoroSession(\n      std::move(sock), std::move(codec), std::move(tinfo));\n}\n\nHTTPCoroSession* HTTPCoroSession::makeDownstreamCoroSession(\n    std::shared_ptr<quic::QuicSocket> sock,\n    std::shared_ptr<HTTPHandler> handler,\n    std::unique_ptr<hq::HQMultiCodec> codec,\n    wangle::TransportInfo tinfo) {\n  XCHECK(proxygen::isDownstream(codec->getTransportDirection()));\n  return new HTTPQuicCoroSession(\n      std::move(sock), std::move(codec), std::move(tinfo), std::move(handler));\n}\n\nvoid HTTPUniplexTransportSession::start() {\n  maybeEnableByteEvents();\n  if (!codec_->supportsParallelRequests()) {\n    maxConcurrentOutgoingStreamsRemote_ = isDownstream() ? 0 : 1;\n  }\n\n  codec_.add<HTTPChecks>();\n  if (codec_->supportsParallelRequests() &&\n      proxygen::isDownstream(codec_->getTransportDirection())) {\n    auto rateLimitFilter =\n        std::make_unique<RateLimitFilter>(&eventBase_->timer(), sessionStats_);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::HEADERS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::MISC_CONTROL_MSGS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::RSTS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::DIRECT_ERROR_HANDLING);\n    rateLimitFilter->setRateLimitParams(\n        RateLimiter::Type::RSTS,\n        /*maxEventsPerInterval=*/1'000,\n        /*intervalDuration=*/std::chrono::milliseconds(100));\n    rateLimitFilter_ = rateLimitFilter.get();\n    codec_.addFilters(std::move(rateLimitFilter));\n  }\n  codec_.setCallback(this);\n  ::proxygen::detail::setEgressWtHttpSettings(codec_->getEgressSettings());\n  sendPreface();\n}\n\nvoid HTTPQuicCoroSession::start() {\n  // This bizarre behavior is copied from HQSession - all TransportInfo's share\n  // the same shared_ptr to the current protocol info.\n  initQuicProtocolInfo(*currentQuicProtocolInfo_, *quicSocket_);\n  setupTransportInfo_.protocolInfo = currentQuicProtocolInfo_;\n  quicSocket_->setConnectionCallback(this);\n  quicSocket_->setPingCallback(this);\n  codec_.add<HTTPChecks>();\n\n  if (proxygen::isDownstream(codec_->getTransportDirection())) {\n    auto rateLimitFilter =\n        std::make_unique<RateLimitFilter>(&eventBase_->timer(), sessionStats_);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::HEADERS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::MISC_CONTROL_MSGS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::RSTS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::DIRECT_ERROR_HANDLING);\n    rateLimitFilter->setRateLimitParams(\n        RateLimiter::Type::RSTS,\n        /*maxEventsPerInterval=*/1'000,\n        /*intervalDuration=*/std::chrono::milliseconds(100));\n    codec_.addFilters(std::move(rateLimitFilter));\n  }\n\n  if (!createControlStream(\n          hq::UnidirectionalStreamType::CONTROL, writeBuf_, controlStreamID_) ||\n      !createControlStream(hq::UnidirectionalStreamType::QPACK_ENCODER,\n                           multiCodec_->getQPACKEncoderWriteBuf(),\n                           qpackEncoderStreamID_) ||\n      !createControlStream(hq::UnidirectionalStreamType::QPACK_DECODER,\n                           multiCodec_->getQPACKDecoderWriteBuf(),\n                           qpackDecoderStreamID_)) {\n    // connection is unusable\n    XLOG(DBG4) << \"Failed to create control stream; sess=\" << *this;\n    return;\n  }\n  multiCodec_->setQPACKEncoderMaxDataFn([this] {\n    auto res = quicSocket_->getStreamFlowControl(qpackEncoderStreamID_);\n    if (res.hasError()) {\n      return uint64_t(0);\n    }\n    return res->sendWindowAvailable;\n  });\n\n  if (isDatagramEnabled()) {\n    quicSocket_->setDatagramCallback(this);\n  }\n\n  sendPreface();\n  codec_.setCallback(this);\n}\n\nvoid HTTPUniplexTransportSession::setRateLimitParams(\n    RateLimiter::Type type,\n    uint32_t maxEventsPerInterval,\n    std::chrono::milliseconds intervalDuration) {\n  if (rateLimitFilter_) {\n    rateLimitFilter_->setRateLimitParams(\n        type, maxEventsPerInterval, intervalDuration);\n  }\n}\n\nvoid HTTPQuicCoroSession::onPriority(quic::StreamId streamId,\n                                     const HTTPPriority& pri) {\n  XCHECK(isDownstream());\n  if (isDraining() || !quicSocket_->good()) {\n    return;\n  }\n\n  if (!findStream(streamId)) {\n    priorityUpdatesBuffer_.insert(streamId, pri);\n    return;\n  }\n\n  quicSocket_->setStreamPriority(\n      streamId, HTTPPriorityQueue::Priority(pri.urgency, pri.incremental));\n}\n\n/**\n * Calling sendPriority(pri) does both of the following:\n *  - Sends the priority_update frame to remote (server)\n *  - Sets the local egress priority to the same level\n */\nsize_t HTTPQuicCoroSession::sendPriority(quic::StreamId id, HTTPPriority pri) {\n  XCHECK(isUpstream());\n\n  if (!quic::isClientBidirectionalStream(id) || !findStream(id)) {\n    XLOG(WARNING) << \"sendPriority streamID = \" << id\n                  << \" not found; sess=\" << *this;\n    return 0;\n  }\n\n  quicSocket_->setStreamPriority(\n      id, HTTPPriorityQueue::Priority(pri.urgency, pri.incremental));\n\n  auto ret = codec_->generatePriority(writeBuf_, id, pri);\n  writeEvent_.signal();\n\n  return ret;\n}\n\nsize_t HTTPQuicCoroSession::sendPushPriority(uint64_t pushId,\n                                             HTTPPriority pri) {\n  XCHECK(isUpstream());\n\n  // Find push stream\n  auto found = pushStreamsAwaitingStreamID_.contains(pushId);\n\n  // TODO: improve efficiency – this is linear with respect to streams_\n  if (!found) {\n    found = std::find_if(streams_.cbegin(), streams_.cend(), [=](auto& entry) {\n              return quic::isServerUnidirectionalStream(entry.first) &&\n                     entry.second->currentPushID &&\n                     *entry.second->currentPushID == pushId;\n            }) != streams_.cend();\n  }\n\n  if (!found) {\n    XLOG(WARNING) << \"sendPushPriority pushId = \" << pushId\n                  << \" was not found; sess=\" << *this;\n    return 0;\n  }\n\n  auto ret = codec_->generatePushPriority(writeBuf_, pushId, pri);\n\n  writeEvent_.signal();\n  return ret;\n}\n\nbool HTTPQuicCoroSession::createControlStream(\n    hq::UnidirectionalStreamType streamType,\n    folly::IOBufQueue& writeBuf,\n    quic::StreamId& id) {\n  auto newId = quicSocket_->createUnidirectionalStream();\n  if (!newId.hasError()) {\n    id = *newId;\n    auto res = hq::writeStreamPreface(writeBuf, uint64_t(streamType));\n    if (!res.hasError()) {\n      quicSocket_->setControlStream(id);\n      writeEvent_.signal();\n      return true;\n    }\n  }\n  XLOG(ERR) << __func__ << \" failed for type=\" << uint64_t(streamType)\n            << \" sess=\" << *this;\n  connectionError(\n      HTTPErrorCode::STREAM_CREATION_ERROR,\n      folly::to<std::string>(\"Failed to create HTTP control stream, type=\",\n                             streamType));\n  return false;\n}\n\nfolly::coro::TaskWithExecutor<void> HTTPUniplexTransportSession::run() {\n  return co_withExecutor(&readExec_, runImpl());\n}\n\nfolly::coro::Task<void> HTTPUniplexTransportSession::runImpl() {\n  XLOG(DBG6) << \"starting run sess=\" << *this;\n  // Start the write loop asynchronously.  The writeLoop coroutine is not\n  // chained for cancellation.  If readLoop detects cancellation, it will\n  // trigger writeLoop to exit.\n  co_withExecutor(&writeExec_, writeLoop()).start();\n\n  co_await co_awaitTry(readLoop());\n\n  // Wait for writes to complete\n  co_await writesFinished_;\n  XLOG(DBG6) << \"waiting for outstanding refs sess=\" << *this;\n  // expects a post outside of evb (i.e. our readExec_ will trip a check here)\n  co_await co_withExecutor(eventBase_, zeroRefs()).startInlineUnsafe();\n  XLOG(DBG6) << \"terminating run sess=\" << *this;\n  destroy();\n}\n\nvoid HTTPCoroSession::sendPreface() {\n  codec_->generateConnectionPreface(writeBuf_);\n  if (isUpstream()) {\n    setSetting(SettingsId::ENABLE_PUSH, 1);\n  }\n  applyEgressSettings();\n  codec_->generateSettings(writeBuf_);\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::applyEgressSettings() {\n  for (auto& setting : multiCodec_->getEgressSettings()->getAllSettings()) {\n    auto id = hq::httpToHqSettingsId(setting.id);\n    if (id) {\n      switch (*id) {\n        case hq::SettingId::HEADER_TABLE_SIZE:\n          multiCodec_->getQPACKCodec().setDecoderHeaderTableMaxSize(\n              setting.value);\n          break;\n        case hq::SettingId::QPACK_BLOCKED_STREAMS:\n          multiCodec_->getQPACKCodec().setMaxBlocking(setting.value);\n          break;\n        case hq::SettingId::MAX_HEADER_LIST_SIZE:\n          break;\n        case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n        case hq::SettingId::H3_DATAGRAM:\n        case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n        case hq::SettingId::H3_DATAGRAM_RFC:\n          break;\n        case hq::SettingId::ENABLE_WEBTRANSPORT:\n        case hq::SettingId::H3_WT_MAX_SESSIONS:\n        case hq::SettingId::WT_INITIAL_MAX_DATA:\n          // TODO\n          break;\n      }\n    }\n  }\n}\n\nHTTPCoroSession::StreamState& HTTPCoroSession::createNewStream(\n    HTTPCodec::StreamID streamID, bool fromSendRequest) {\n  auto res = streams_.emplace(\n      streamID,\n      std::make_unique<StreamState>(\n          eventBase_.get(),\n          streamID,\n          *this,\n          getStreamSendFlowControlWindow(),\n          getStreamRecvFlowControlWindow(),\n          fromSendRequest ? std::chrono::milliseconds(0) : streamReadTimeout_,\n          writeTimeout_));\n  XCHECK(res.second) << \"Duplicate stream\";\n  XLOG(DBG4) << \"Creating id=\" << streamID << \" sess=\" << *this;\n  if (sessionStats_) {\n    sessionStats_->recordTransactionOpened();\n    if (nextStreamSequenceNumber_ > 0) {\n      sessionStats_->recordSessionReused();\n    }\n  }\n\n  nextStreamSequenceNumber_++;\n  if (!fromSendRequest) {\n    transactionAttached();\n  }\n  setupStreamWriteBuf(*res.first->second, writeBuf_);\n  return *res.first->second;\n}\n\nvoid HTTPUniplexTransportSession::setupStreamWriteBuf(\n    StreamState& stream, folly::IOBufQueue& sessionWriteBuf) {\n  stream.setWriteBuf(&sessionWriteBuf);\n}\n\nvoid HTTPQuicCoroSession::setupStreamWriteBuf(StreamState& stream,\n                                              folly::IOBufQueue& /*unused*/) {\n  stream.setWriteBuf(nullptr);\n}\n\nHTTPCoroSession::StreamState* HTTPCoroSession::findStream(\n    HTTPCodec::StreamID id) {\n  auto it = streams_.find(id);\n  if (it == streams_.end()) {\n    XLOG(DBG4) << \"Stream not found sess=\" << *this << \" id=\" << id;\n    return nullptr;\n  }\n  return it->second.get();\n}\n\nvoid HTTPCoroSession::insertWithPriority(const StreamState& stream) {\n  auto id = quic::PriorityQueue::Identifier::fromStreamID(stream.getID());\n  auto pri = stream.getPriority();\n  auto httpPri =\n      HTTPPriorityQueue::Priority{pri.urgency, pri.incremental, pri.orderId};\n  writableStreams_.insertOrUpdate(id, httpPri);\n}\n\nvoid HTTPCoroSession::onMessageBegin(HTTPCodec::StreamID streamID,\n                                     HTTPMessage* /*msg*/) {\n  XLOG(DBG6) << __func__ << \" streamId=\" << streamID << \" sess=\" << *this;\n  auto it = streams_.find(streamID);\n  if (it == streams_.end()) {\n    if (isUpstream()) {\n      // Headers on a stream that has already been reset?\n      if (!codec_->supportsParallelRequests()) {\n        connectionError(HTTPErrorCode::PROTOCOL_ERROR,\n                        \"HTTP response without request\");\n      }\n      return;\n    }\n  } else {\n    if (isDownstream()) {\n      // The codecs shouldn't do this, but HQ manually invokes onMessageBegin\n      XLOG_IF(DFATAL, codec_->getProtocol() != CodecProtocol::HQ)\n          << \"Duplicate stream sess=\" << *this << \" id=\" << streamID;\n    } // upstream, it can happen\n    return;\n  }\n\n  // http/1.1 edge-case – read returned bytes after cancel; to prevent new\n  // streams from being created, we simply pause the parser here\n  if (!codec_->isReusable()) {\n    codec_->setParserPaused(true);\n    return;\n  }\n\n  deliverLifecycleEvent(&LifecycleObserver::onRequestBegin, *this);\n  auto& stream = createNewStream(streamID);\n  if (isDownstream()) {\n    if (!codec_->supportsParallelRequests() && streams_.size() > 1) {\n      codec_->setParserPaused(true);\n    }\n    // Start a task to read the response to this request (downstream only)\n    // onMessageBegin can only be invoked once per downstream txn\n    stream.startEgressCoro();\n    co_withExecutor(\n        eventBase_,\n        co_withCancellation(stream.cs.egress.getToken(),\n                            readResponse(stream, handleRequest(stream))))\n        .start();\n  }\n}\n\nvoid HTTPUniplexTransportSession::onPushMessageBegin(\n    HTTPCodec::StreamID streamID,\n    HTTPCodec::StreamID assocStreamID,\n    HTTPMessage* /*promise*/) {\n  XLOG(DBG6) << __func__ << \" sess=\" << *this;\n  deliverLifecycleEvent(&LifecycleObserver::onRequestBegin, *this);\n  if (findStream(streamID)) {\n    XLOG(DBG4) << \"Duplicate push sess=\" << *this << \" id=\" << streamID;\n    return;\n  }\n\n  auto parent = findStream(assocStreamID);\n  if (!parent) {\n    // Either the parent has been reset, or never existed.  Either way, this\n    // push is going to fail, but it's handled in onHeadersComplete.\n    return;\n  }\n\n  auto& pushStream = createNewStream(streamID);\n  pushStream.initIngressPush(assocStreamID);\n  pushStream.currentPushID = streamID;\n  numPushStreams_++;\n}\n\nvoid HTTPQuicCoroSession::onPushMessageBegin(HTTPCodec::StreamID pushID,\n                                             HTTPCodec::StreamID streamID,\n                                             HTTPMessage* /*promise*/) {\n  XLOG(DBG6) << __func__ << \" sess=\" << *this;\n  deliverLifecycleEvent(&LifecycleObserver::onRequestBegin, *this);\n  auto parent = findStream(streamID);\n  // In HQ this comes from parsing on the actual parent stream\n  XCHECK(parent);\n  XCHECK(!parent->currentPushID);\n  parent->currentPushID = pushID;\n}\n\nvoid HTTPCoroSession::onHeadersComplete(HTTPCodec::StreamID streamID,\n                                        std::unique_ptr<HTTPMessage> msg\n                                        /* bool eom!*/) {\n  XLOG(DBG4) << \"onHeadersComplete streamId=\" << streamID << \" sess=\" << *this;\n  msg->dumpMessage(4);\n  deliverLifecycleEvent(\n      &LifecycleObserver::onIngressMessage, *this, *msg.get());\n  auto stream = findStream(streamID);\n  if (!stream) {\n    // Has been reset since onMessageBegin/onPushMessageBegin\n    return;\n  }\n  if (checkAndHandlePushPromiseComplete(*stream, msg)) {\n    return;\n  }\n\n  auto upgrade = stream->checkForUpgrade(*msg, /*isIngress=*/true);\n  if (isDownstream() && upgrade && !codec_->supportsParallelRequests()) {\n    codec_->setParserPaused(true);\n    // TODO: pause reading from transport too?\n  }\n  if (isDownstream() && msg->isRequest() &&\n      msg->getMethod() == HTTPMethod::HEAD) {\n    stream->skipEgressContentLengthValidation();\n  }\n\n  // Upstream/Downstream receiving headers\n  setSecureMsg(*msg, setupTransportInfo_);\n  msg->setSeqNo(HTTPCodec::streamIDToSeqNo(codec_->getProtocol(), streamID));\n  auto priority = httpPriorityFromHTTPMessage(*msg);\n  stream->setPriority(priority.value_or(kDefaultPriority));\n  stream->streamSource.headers(std::move(msg));\n  streamHeadersComplete(*stream);\n}\n\nvoid HTTPQuicCoroSession::streamHeadersComplete(StreamState& stream) {\n  if (isDatagramEnabled() && !datagramsBuffer_.empty()) {\n    auto itr = datagramsBuffer_.find(stream.getID());\n    if (itr != datagramsBuffer_.end()) {\n      auto& vec = itr->second;\n      for (auto& datagram : vec) {\n        stream.streamSource.datagram(std::move(datagram));\n      }\n      datagramsBuffer_.erase(itr);\n    }\n  }\n}\n\nvoid HTTPQuicCoroSession::onHeadersComplete(HTTPCodec::StreamID streamID,\n                                            std::unique_ptr<HTTPMessage> msg\n                                            /* bool eom!*/) {\n  auto priority = httpPriorityFromHTTPMessage(*msg);\n  HTTPCoroSession::onHeadersComplete(streamID, std::move(msg));\n\n  if (isDownstream()) {\n    auto itr = priorityUpdatesBuffer_.find(streamID);\n    if (itr != priorityUpdatesBuffer_.end()) {\n      quicSocket_->setStreamPriority(\n          streamID,\n          HTTPPriorityQueue::Priority(itr->second.urgency,\n                                      itr->second.incremental));\n      priorityUpdatesBuffer_.erase(itr);\n    } else if (priority) {\n      onPriority(streamID, *priority);\n    }\n  }\n}\n\nbool HTTPUniplexTransportSession::checkAndHandlePushPromiseComplete(\n    StreamState& stream, std::unique_ptr<HTTPMessage>& msg) {\n  auto streamID = stream.getID();\n  if (isUpstream() && stream.currentPushID) {\n    // Upstream push awaiting promise headers\n    stream.currentPushID.reset();\n\n    XCHECK(stream.parent);\n    auto parent = findStream(*stream.parent);\n    if (!parent) {\n      XLOG(ERR) << \"Received PUSH_PROMISE but parent stream is gone. Parent \"\n                << \"stream=\" << *stream.parent << \" push stream=\" << streamID\n                << \" sess=\" << *this;\n      egressResetStream(streamID, &stream, HTTPErrorCode::REFUSED_STREAM);\n    } else {\n      stream.streamSourceActive();\n      parent->streamSource.pushPromise(std::move(msg), &stream.streamSource);\n    }\n    return true;\n  }\n  return false;\n}\n\nbool HTTPQuicCoroSession::checkAndHandlePushPromiseComplete(\n    StreamState& stream, std::unique_ptr<HTTPMessage>& msg) {\n  if (!isUpstream() || !stream.currentPushID ||\n      !quic::isClientBidirectionalStream(stream.getID())) {\n    // Just regular headers\n    return false;\n  }\n  // This stream is awaiting the push promise\n  auto pushID = *stream.currentPushID;\n  stream.currentPushID.reset();\n  StreamState* pushStreamPtr = nullptr;\n  auto it = pushStreamsAwaitingPromises_.find(pushID);\n  if (it == pushStreamsAwaitingPromises_.end()) {\n    // stream has not yet arrived\n    XLOG(DBG4) << \"Push promise complete before stream, pushID=\" << pushID\n               << \" parent=\" << stream.getID() << \" sess=\" << *this;\n    auto pushStream =\n        std::make_unique<StreamState>(eventBase_.get(),\n                                      HTTPCodec::MaxStreamID,\n                                      *this,\n                                      getStreamSendFlowControlWindow(),\n                                      getStreamRecvFlowControlWindow(),\n                                      streamReadTimeout_,\n                                      writeTimeout_);\n    pushStreamPtr = pushStream.get();\n    pushStreamsAwaitingStreamID_.emplace(pushID, std::move(pushStream));\n  } else {\n    auto pushStreamID = it->second;\n    XLOG(DBG4) << \"Push promise matched stream=\" << pushStreamID\n               << \" pushID=\" << pushID << \" parent=\" << stream.getID()\n               << \" sess=\" << *this;\n    pushStreamsAwaitingPromises_.erase(it);\n    auto res = quicSocket_->resumeRead(pushStreamID);\n    if (res.hasError()) {\n      XLOG(ERR) << \"Failed to resume push stream=\" << pushStreamID\n                << \" sess=\" << *this;\n      // This push promise just gets silently abandoned\n      return true;\n    } else {\n      auto& pushStream = createNewStream(pushStreamID);\n      multiCodec_->addCodec(pushStreamID);\n      pushStreamPtr = &pushStream;\n      numPushStreams_++;\n      co_withExecutor(eventBase_,\n                      co_withCancellation(pushStream.cs.ingress.getToken(),\n                                          readLoop(pushStreamID)))\n          .start();\n    }\n  }\n  pushStreamPtr->initIngressPush(stream.getID());\n  pushStreamPtr->streamSourceActive();\n  pushStreamPtr->currentPushID = pushID;\n  stream.streamSource.pushPromise(std::move(msg), &pushStreamPtr->streamSource);\n  return true;\n}\n\nvoid HTTPQuicCoroSession::dispatchPushStream(quic::StreamId id,\n                                             uint64_t pushID) {\n  multiCodec_->onIngressPushId(pushID);\n  auto it = pushStreamsAwaitingStreamID_.find(pushID);\n  if (it == pushStreamsAwaitingStreamID_.end()) {\n    // promise has not arrived yet, pause the push stream\n    XLOG(DBG4) << \"Push stream before push promise, pushID=\" << pushID\n               << \" stream=\" << id << \" sess=\" << *this;\n    quicSocket_->pauseRead(id);\n    // TODO: duplicate possibility?\n    pushStreamsAwaitingPromises_.emplace(pushID, id);\n  } else {\n    auto pushStream = std::move(it->second);\n    XCHECK(pushStream->parent);\n    XLOG(DBG4) << \"Push stream matched promise, stream id=\" << id\n               << \" pushID=\" << pushID << \" parent=\" << *pushStream->parent\n               << \" sess=\" << *this;\n    pushStream->streamSource.setStreamID(id);\n    multiCodec_->addCodec(id);\n    numPushStreams_++;\n    co_withExecutor(\n        &readExec_,\n        co_withCancellation(pushStream->cs.ingress.getToken(), readLoop(id)))\n        .start();\n    streams_.emplace(id, std::move(pushStream));\n    pushStreamsAwaitingStreamID_.erase(it);\n  }\n}\n\nvoid HTTPCoroSession::onBody(HTTPCodec::StreamID streamID,\n                             std::unique_ptr<folly::IOBuf> chain,\n                             uint16_t padding\n                             /* bool eom!*/) {\n  XLOG(DBG6) << __func__ << \" streamId=\" << streamID << \" sess=\" << *this;\n  BufQueue body(std::move(chain));\n  auto length = body.chainLength();\n  if (!recvWindow_.reserve(length, padding)) {\n    // flow control error\n    connectionError(HTTPErrorCode::FLOW_CONTROL_ERROR,\n                    \"Peer exceeded connection flow control\");\n    return;\n  }\n\n  SESS_STATS(recordPendingBufferedReadBytes, static_cast<int64_t>(length));\n\n  auto* stream = findStream(streamID);\n  HTTPStreamSource::FlowControlState fcRes =\n      stream ? stream->streamSource.body(std::move(body), padding)\n             : HTTPStreamSource::FlowControlState::ERROR;\n\n  if (fcRes == HTTPStreamSource::FlowControlState::ERROR) {\n    // release conn flow control if stream doesn't exist or peer violated stream\n    // fc window\n    bytesProcessed(streamID, length, 0);\n  }\n\n  if (stream && ingressLimitExceeded(*stream)) {\n    handleIngressLimitExceeded(stream->getID());\n  }\n}\n\nvoid HTTPUniplexTransportSession::handleIngressLimitExceeded(\n    HTTPCodec::StreamID) {\n  // only applies to h1 since h2 has session fc protocol semantics\n  if (!codec_->supportsSessionFlowControl()) {\n    flowControlBaton_.reset();\n  }\n}\n\nvoid HTTPQuicCoroSession::handleIngressLimitExceeded(HTTPCodec::StreamID id) {\n  // pause reading from socket if bytes buffered exceeded threshold\n  quicSocket_->pauseRead(id);\n}\n\nvoid HTTPCoroSession::onTrailersComplete(\n    HTTPCodec::StreamID streamID, std::unique_ptr<HTTPHeaders> trailers) {\n  XLOG(DBG6) << __func__ << \" streamId=\" << streamID << \" sess=\" << *this;\n  auto stream = findStream(streamID);\n  if (!stream) {\n    return;\n  }\n  stream->streamSource.trailers(std::move(trailers));\n}\n\nvoid HTTPCoroSession::onMessageComplete(HTTPCodec::StreamID streamID,\n                                        bool upgrade) {\n  XLOG(DBG6) << __func__ << \" streamId=\" << streamID\n             << \" upgrade=\" << int(upgrade) << \" sess=\" << *this;\n  auto stream = findStream(streamID);\n  if (!stream) {\n    return;\n  }\n\n  // TODO: upgrade\n\n  // TODO: what about receiving an EOM (or headers/body for that\n  // matter) on an egress push stream?  streamSource.eom will ignore\n  // it, but shouldn't we return an error / drop the connection?\n\n  // It will mess up the logic in decrementPushStreamCount, because it will\n  // treat this as a decrementing event.  For now, gate\n  // decrementPushStreamCount to upstream only\n  if (isUpstream()) {\n    decrementPushStreamCount(*stream);\n  }\n  if (!upgrade) {\n    stream->streamSource.eom();\n  } // else, this is a 200 reply to a CONNECT?  Or WS?\n\n  // handle http/1.1 keep-alive=false\n  if (isUpstream() && !codec_->isReusable() &&\n      !codec_->supportsParallelRequests()) {\n    initiateDrain();\n  }\n}\n\nvoid HTTPCoroSession::onError(HTTPCodec::StreamID streamID,\n                              const HTTPException& error,\n                              bool /*newTxn*/) {\n  XLOG(DBG4) << \"Parse error, ex=\" << error.what() << \" id=\" << streamID\n             << \" sess=\" << *this;\n  if (streamID == getSessionStreamID()) {\n    // TODO: HTTPSession reports kErrorMessage here, but that doesn't\n    // seem right for H2.\n    deliverLifecycleEvent(\n        &LifecycleObserver::onIngressError, *this, kErrorMessage);\n\n    if (error.hasCodecStatusCode()) {\n      connectionError(ErrorCode2HTTPErrorCode(error.getCodecStatusCode()),\n                      error.what());\n    } else if (error.hasHttp3ErrorCode()) {\n      connectionError(HTTP3ErrorCode2HTTPErrorCode(error.getHttp3ErrorCode()),\n                      error.what());\n    } else {\n      // Session errors, only comes from H2/QCodecs, with an error code\n      XLOG(FATAL) << \"Session error with no protocol error code sess=\" << *this;\n    }\n    return;\n  }\n\n  // queue up an error for the handler if stream exists\n  auto ec = HTTPException2HTTPErrorCode(error);\n  auto* stream = findStream(streamID);\n  if (isDownstream() && stream && stream->canSendHeaders() &&\n      error.hasHttpStatusCode()) {\n    stream->abortIngress(ec, error.what());\n    stream->setErrorStatusCode(error.getHttpStatusCode());\n  } else {\n    egressResetStream(streamID, stream, ec);\n  }\n\n  deliverLifecycleEvent(&LifecycleObserver::onIngressError,\n                        *this,\n                        error.hasProxygenError() ? error.getProxygenError()\n                                                 : kErrorMessage);\n}\n\nvoid HTTPCoroSession::onAbort(HTTPCodec::StreamID streamID, ErrorCode code) {\n  onResetStream(streamID,\n                (code == ErrorCode::NO_ERROR) ? HTTPErrorCode::CANCEL\n                                              : ErrorCode2HTTPErrorCode(code));\n}\n\nvoid HTTPCoroSession::onResetStream(HTTPCodec::StreamID streamID,\n                                    HTTPErrorCode code) {\n  auto stream = findStream(streamID);\n  if (!stream) {\n    return;\n  }\n  XLOG(DBG4) << \"Received RST_STREAM code=\" << static_cast<uint32_t>(code)\n             << \" sess=\" << *this << \" id=\" << streamID;\n  decrementPushStreamCount(*stream);\n  deliverAbort(*stream, code, \"received RST_STREAM from peer\");\n}\n\nvoid HTTPCoroSession::deliverAbort(StreamState& stream,\n                                   HTTPErrorCode error,\n                                   std::string_view details) {\n  // This abort may never be read if the stream is streamSourceComplete.\n  // eg: a complete downstream request or downstream push.\n  stream.abortIngress(error, details);\n}\n\nvoid HTTPUniplexTransportSession::deliverAbort(StreamState& stream,\n                                               HTTPErrorCode error,\n                                               std::string_view details) {\n  HTTPCoroSession::deliverAbort(stream, error, details);\n  // HTTP/2 only, always reset the egress state here\n  // If there's still pending egress (eg readResponse is reading from a source),\n  // resetStreamState will cancel the coroutine.\n  resetStreamState(stream, HTTPError(error, \"Peer reset the stream\"));\n}\n\nvoid HTTPQuicCoroSession::deliverAbort(StreamState& stream,\n                                       HTTPErrorCode error,\n                                       std::string_view details) {\n  HTTPCoroSession::deliverAbort(stream, error, details);\n  // HTTP/3, egress a reset if egress is incomplete\n  if (!stream.isEgressComplete()) {\n    HTTPErrorCode egressError = HTTPErrorCode::REQUEST_CANCELLED;\n    if (isDownstream() && stream.streamSource.isUnprocessed()) {\n      // Server has not touched the request\n      egressError = HTTPErrorCode::REQUEST_REJECTED;\n    }\n    // TODO: the egress error message to onByteEventCanceled will be wrong\n    egressResetStream(stream.getID(), &stream, egressError);\n  }\n}\n\nvoid HTTPCoroSession::onGoaway(uint64_t lastGoodStreamID,\n                               ErrorCode code,\n                               std::unique_ptr<folly::IOBuf> /*debugData*/) {\n  XLOG(DBG4) << __func__ << \" lastGoodStreamID=\" << lastGoodStreamID\n             << \" code=\" << int(code) << \" sess=\" << *this;\n  // TODO: Queue an onGoaway event for all active transactions\n  //       Do something with debugData?\n\n  XCHECK(codec_->supportsParallelRequests()) << \"GOAWAY is for parallel codecs\";\n\n  // Notify the interested parties\n  deliverLifecycleEvent(\n      &LifecycleObserver::onGoaway, *this, lastGoodStreamID, code);\n\n  // Mark all transactions above lastGoodStreamID as REFUSED\n  std::vector<StreamState*> refusedStreams;\n  for (auto& [_, stream] : streams_) {\n    if (streamRefusedByGoaway(*stream, lastGoodStreamID)) {\n      refusedStreams.push_back(stream.get());\n    }\n  }\n\n  for (auto* stream : refusedStreams) {\n    // This stream never reached the peer\n    constexpr std::string_view kRefusedStreamMsg = \"Stream refused by peer\";\n    stream->abortIngress(HTTPErrorCode::REFUSED_STREAM, kRefusedStreamMsg);\n    resetStreamState(*stream,\n                     HTTPError(HTTPErrorCode::REFUSED_STREAM,\n                               std::string(kRefusedStreamMsg)));\n  }\n\n  initiateDrain();\n  if (code != ErrorCode::NO_ERROR) {\n    // The peer errored the connection, reset all open streams\n    resetOpenStreams(ErrorCode2HTTPErrorCode(code),\n                     \"Peer closed with connection error\");\n    codec_->generateImmediateGoaway(writeBuf_);\n    writeEvent_.signal();\n    // ingress callback, no need to interrupt reads\n  }\n}\n\nbool HTTPUniplexTransportSession::streamRefusedByGoaway(\n    StreamState& stream, HTTPCodec::StreamID lastGoodStreamID) {\n  return (HTTPParallelCodec::isInitiatedStream(direction_, stream.getID()) &&\n          stream.getID() > lastGoodStreamID);\n}\n\nbool HTTPQuicCoroSession::streamRefusedByGoaway(\n    StreamState& stream, HTTPCodec::StreamID lastGoodStreamID) {\n  return (isUpstream() && quicSocket_->isClientStream(stream.getID()) &&\n          stream.getID() >= lastGoodStreamID) ||\n         (isDownstream() && quicSocket_->isServerStream(stream.getID()) &&\n          stream.currentPushID && *stream.currentPushID >= lastGoodStreamID);\n}\n\nvoid HTTPCoroSession::drainStarted() {\n  XLOG(DBG4)\n      << \"Drain started, setting maxConcurrentOutgoingStreamsConfig_ to 0 \"\n      << \"sess=\" << *this;\n  const bool wasDraining = isDraining();\n  maxConcurrentOutgoingStreamsConfig_ = 0;\n  if (isDraining() && !wasDraining) {\n    deliverLifecycleEvent(&LifecycleObserver::onDrainStarted, *this);\n  }\n}\n\nvoid HTTPCoroSession::connectionError(\n    HTTPErrorCode httpError,\n    std::string msg,\n    folly::Optional<HTTPErrorCode> streamError) {\n  XLOG(DBG4) << \"connectionError msg=\" << msg << \" sess=\" << *this;\n  drainStarted();\n  handleConnectionError(httpError, msg);\n  resetOpenStreams(streamError.value_or(httpError), msg);\n  writeEvent_.signal();\n  interruptReadLoop();\n  XCHECK(!codec_->isWaitingToDrain());\n  writableStreams_.clear();\n}\n\nvoid HTTPUniplexTransportSession::handleConnectionError(HTTPErrorCode httpError,\n                                                        std::string msg) {\n  HTTPError err(httpError, msg);\n  byteEventObserver_.cancelEvents(err);\n  codec_->generateImmediateGoaway(writeBuf_,\n                                  HTTPErrorCode2ErrorCode(httpError, false),\n                                  folly::IOBuf::copyBuffer(msg));\n  if (!codec_->supportsParallelRequests()) {\n    resetAfterDrainingWrites_ = true;\n  }\n}\n\nvoid HTTPQuicCoroSession::handleConnectionError(HTTPErrorCode error,\n                                                std::string msg) {\n  codec_->generateImmediateGoaway(writeBuf_,\n                                  ErrorCode::PROTOCOL_ERROR, // ignored\n                                  nullptr);                  // ignored\n  connectionError_.emplace(\n      quic::ApplicationErrorCode(HTTPErrorCode2HTTP3ErrorCode(error, false)),\n      std::move(msg));\n}\n\nvoid HTTPCoroSession::initiateDrain() {\n  XLOG(DBG4) << __func__ << \" sess=\" << *this;\n  if (isDraining()) {\n    return;\n  }\n  drainStarted();\n  // Even if generateGoaway doesn't produce egress, it may change 'isReusable'\n  // So signal the writeEvent_.\n  codec_->generateGoaway(writeBuf_);\n  writeEvent_.signal();\n  interruptReadLoop();\n  if (codec_->isWaitingToDrain()) {\n    scheduleGoawayTimeout();\n  }\n}\n\nvoid HTTPCoroSession::closeWhenIdle() {\n  XLOG(DBG4) << __func__ << \" sess=\" << *this;\n  goawayTimeoutExpired();\n  goawayTimeout_.cancelTimeout();\n}\n\nvoid HTTPCoroSession::goawayTimeoutExpired() {\n  // This is the equivalent of HTTPSession::closeWhenIdle\n  drainStarted();\n  codec_->generateImmediateGoaway(writeBuf_);\n  writeEvent_.signal();\n  interruptReadLoop();\n}\n\nvoid HTTPCoroSession::dropConnection(const std::string& errorMsg) {\n  XLOG(DBG4) << __func__ << \" sess=\" << *this;\n\n  // TODO: dropping a connection with open streams will almost certainly not\n  // complete within the current event loop, which can be problematic.\n\n  // Even if there are NO streams, it will only complete in the current loop if\n  // the writeLoop finishes before readLoop.  If readLoop was already scheduled\n  // to run, it takes 1 more loop.  This is because TimedBaton::wait does not\n  // return true from await_ready() if it's already signalled.\n  connectionError(\n      HTTPErrorCode::INTERNAL_ERROR, errorMsg, HTTPErrorCode::DROPPED);\n}\n\nint HTTPUniplexTransportSession::getAsyncTransportFD() const {\n  folly::AsyncSocket* sock = nullptr;\n  auto transport = coroTransport_->getTransport();\n  if (transport) {\n    sock = transport->getUnderlyingTransport<folly::AsyncSocket>();\n  }\n  if (sock) {\n    return sock->getNetworkSocket().toFd();\n  }\n  return -1;\n}\n\nbool HTTPCoroSession::getCurrentTransportInfo(wangle::TransportInfo* tinfo,\n                                              bool setupFields) const {\n  if (setupFields) {\n    // some fields are the same with the setup transport info\n    tinfo->setupTime = setupTransportInfo_.setupTime;\n    tinfo->secure = setupTransportInfo_.secure;\n    tinfo->sslSetupTime = setupTransportInfo_.sslSetupTime;\n    tinfo->sslVersion = setupTransportInfo_.sslVersion;\n    tinfo->sslCipher = setupTransportInfo_.sslCipher;\n    tinfo->sslResume = setupTransportInfo_.sslResume;\n    tinfo->appProtocol = setupTransportInfo_.appProtocol;\n    tinfo->sslError = setupTransportInfo_.sslError;\n    tinfo->sslServerName = setupTransportInfo_.sslServerName;\n  }\n  return getCurrentTransportInfoImpl(tinfo);\n}\n\nbool HTTPUniplexTransportSession::getCurrentTransportInfoImpl(\n    wangle::TransportInfo* tinfo) const {\n  folly::AsyncSocket* sock = nullptr;\n  auto transport = coroTransport_->getTransport();\n  if (transport) {\n    sock = transport->getUnderlyingTransport<folly::AsyncSocket>();\n  }\n  if (sock) {\n    tinfo->initWithSocket(sock);\n#if defined(__linux__) || defined(__FreeBSD__)\n    tinfo->readTcpCongestionControl(sock);\n    tinfo->readMaxPacingRate(sock);\n    tinfo->recvwnd = tinfo->tcpinfo.tcpi_rcv_space\n                     << tinfo->tcpinfo.tcpi_rcv_wscale;\n#elif defined(__APPLE__)\n    tinfo->recvwnd = tinfo->tcpinfo.tcpi_rcv_wnd\n                     << tinfo->tcpinfo.tcpi_rcv_wscale;\n#endif // defined(__linux__) || defined(__FreeBSD__)\n    tinfo->totalBytes = sock->getRawBytesWritten();\n    return true;\n  }\n  return false;\n}\n\nbool HTTPQuicCoroSession::getCurrentTransportInfoImpl(\n    wangle::TransportInfo* tinfo) const {\n  if (quicSocket_->good()) {\n    auto connState = quicSocket_->getState();\n    auto quicTransportInfo = quicSocket_->getTransportInfo();\n\n    tinfo->rtt = quicTransportInfo.srtt;\n    tinfo->rtt_var = quicTransportInfo.rttvar.count();\n    tinfo->rtx = quicTransportInfo.packetsRetransmitted;\n    tinfo->rtx_tm = quicTransportInfo.totalPacketsMarkedLostByTimeout;\n    tinfo->rto = quicTransportInfo.pto.count();\n    tinfo->cwndBytes = int64_t(quicTransportInfo.congestionWindow);\n    tinfo->mss = int64_t(quicTransportInfo.mss);\n    tinfo->cwnd = tinfo->cwndBytes / tinfo->mss;\n    tinfo->totalBytes = int64_t(quicTransportInfo.bytesSent);\n    updateQuicProtocolInfo(*currentQuicProtocolInfo_, *quicSocket_);\n    tinfo->protocolInfo = currentQuicProtocolInfo_;\n    tinfo->validTcpinfo = true;\n\n    if (connState) {\n      tinfo->recvwnd = int64_t(connState->flowControlState.windowSize);\n\n      if (connState->congestionController) {\n        tinfo->caAlgo = quic::congestionControlTypeToString(\n            connState->congestionController->type());\n        if (connState->congestionController->type() ==\n            quic::CongestionControlType::Cubic) {\n          quic::CongestionControllerStats ccStats;\n          connState->congestionController->getStats(ccStats);\n          tinfo->ssthresh = int64_t(ccStats.cubicStats.ssthresh) / tinfo->mss;\n        }\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid HTTPUniplexTransportSession::onWindowUpdate(HTTPCodec::StreamID streamID,\n                                                 uint32_t amount) {\n  // for freeing up send window, either connection or stream\n  if (streamID == getSessionStreamID()) {\n    auto wasBlocked = (sendWindow_.getSize() <= 0);\n    if (!sendWindow_.free(amount)) {\n      connectionError(HTTPErrorCode::FLOW_CONTROL_ERROR,\n                      \"Peer overflowed connection window\");\n      return;\n    }\n    auto isBlocked = (sendWindow_.getSize() <= 0);\n    if (wasBlocked && !isBlocked && !writableStreams_.empty()) {\n      // More connection flow control could unblock writing\n      writeEvent_.signal();\n    }\n  } else {\n    auto stream = findStream(streamID);\n    if (!stream) {\n      return;\n    }\n    auto res = stream->onWindowUpdate(amount);\n    if (res.hasError()) {\n      // TODO: should this be a stream error?\n      connectionError(HTTPErrorCode::FLOW_CONTROL_ERROR,\n                      folly::to<std::string>(\n                          \"Peer overflowed stream window, stream=\", streamID));\n      return;\n    } else if (res.value()) {\n      XLOG(DBG4) << \"Adding previously blocked stream to writable id=\"\n                 << streamID << \" sess=\" << *this;\n      notifyBodyWrite(*stream);\n    }\n  }\n}\n\nvoid HTTPUniplexTransportSession::onSettings(const SettingsList& settings) {\n  deliverLifecycleEvent(&LifecycleObserver::onSettings, *this, settings);\n\n  for (auto& setting : settings) {\n    if (setting.id == SettingsId::INITIAL_WINDOW_SIZE) {\n      for (auto& it : streams_) {\n        it.second->setSendWindow(setting.value);\n      }\n    } else if (setting.id == SettingsId::MAX_CONCURRENT_STREAMS) {\n      auto maxStreams = setting.value;\n      XLOG(DBG4) << \"Got new max number of concurrent streams we can initiate: \"\n                 << maxStreams << \" sess=\" << *this;\n      bool didSupport = supportsMoreTransactions();\n      maxConcurrentOutgoingStreamsRemote_ = maxStreams;\n      onSetMaxInitiatedStreams(didSupport);\n    } else if (setting.id == SettingsId::SETTINGS_HTTP_CERT_AUTH) {\n      // TODO\n    }\n  }\n\n  codec_->generateSettingsAck(writeBuf_);\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::onSettings(const SettingsList& settings) {\n  deliverLifecycleEvent(&LifecycleObserver::onSettings, *this, settings);\n  uint32_t tableSize = hq::kDefaultIngressHeaderTableSize;\n  uint32_t blocked = hq::kDefaultIngressQpackBlockedStream;\n  for (auto& setting : settings) {\n    auto id = hq::httpToHqSettingsId(setting.id);\n    if (id) {\n      switch (*id) {\n        case hq::SettingId::HEADER_TABLE_SIZE:\n          tableSize = setting.value;\n          break;\n        case hq::SettingId::QPACK_BLOCKED_STREAMS:\n          blocked = setting.value;\n          break;\n        case hq::SettingId::MAX_HEADER_LIST_SIZE:\n          // this setting is stored in ingressSettings_ and enforced in the\n          // StreamCodec\n          break;\n        case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n        case hq::SettingId::H3_DATAGRAM:\n        case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n        case hq::SettingId::H3_DATAGRAM_RFC:\n          // If H3 datagram is enabled but datagram was not negotiated at the\n          // transport, close the connection\n          if (static_cast<bool>(setting.value) &&\n              quicSocket_->getDatagramSizeLimit() == 0) {\n            connectionError(HTTPErrorCode::SETTINGS_ERROR,\n                            \"H3_DATAGRAM without transport support\");\n            return;\n          }\n          break;\n        case hq::SettingId::ENABLE_WEBTRANSPORT:\n        case hq::SettingId::H3_WT_MAX_SESSIONS:\n        case hq::SettingId::WT_INITIAL_MAX_DATA:\n          // TODO\n          break;\n      }\n    }\n  }\n  multiCodec_->getQPACKCodec().setEncoderHeaderTableSize(tableSize);\n  multiCodec_->getQPACKCodec().setMaxVulnerable(blocked);\n  writeEvent_.signal();\n  XLOG(DBG3) << \"Applied SETTINGS sess=\" << *this << \" size=\" << tableSize\n             << \" blocked=\" << blocked;\n}\n\nvoid HTTPCoroSession::onSetMaxInitiatedStreams(bool didSupport) {\n  if (didSupport != supportsMoreTransactions()) {\n    if (didSupport) {\n      deliverLifecycleEvent(&LifecycleObserver::onSettingsOutgoingStreamsFull,\n                            *this);\n    } else {\n      deliverLifecycleEvent(\n          &LifecycleObserver::onSettingsOutgoingStreamsNotFull, *this);\n    }\n  }\n}\n\nvoid HTTPUniplexTransportSession::onSettingsAck() {\n  deliverLifecycleEvent(&LifecycleObserver::onSettingsAck, *this);\n}\n\n// HTTPStreamSource::Callback\nvoid HTTPCoroSession::bytesProcessed(HTTPCodec::StreamID id,\n                                     size_t delta,\n                                     size_t toAckStream) {\n  bool pendingWrite = false;\n  if (toAckStream) {\n    pendingWrite |= sendFlowControlUpdate(id, toAckStream);\n  }\n  auto toAckSession = recvWindow_.processed(delta);\n  if (toAckSession) {\n    pendingWrite |= sendFlowControlUpdate(0, toAckSession);\n  }\n  if (pendingWrite) {\n    writeEvent_.signal();\n  }\n  SESS_STATS(recordPendingBufferedReadBytes, -static_cast<int64_t>(delta));\n}\n\nvoid HTTPUniplexTransportSession::bytesProcessed(HTTPCodec::StreamID id,\n                                                 size_t delta,\n                                                 size_t toAckStream) {\n  HTTPCoroSession::bytesProcessed(id, delta, toAckStream);\n  if (!codec_->supportsSessionFlowControl()) {\n    auto stream = findStream(id);\n    if (stream && shouldResumeIngress(*stream, delta)) {\n      XLOG(DBG4) << \"resuming stream previously ingress limited; sess=\"\n                 << *this;\n      flowControlBaton_.signal();\n    }\n  }\n}\n\nvoid HTTPQuicCoroSession::bytesProcessed(HTTPCodec::StreamID id,\n                                         size_t delta,\n                                         size_t toAckStream) {\n  HTTPCoroSession::bytesProcessed(id, delta, toAckStream);\n\n  auto stream = findStream(id);\n  if (stream && shouldResumeIngress(*stream, delta)) {\n    XLOG(DBG4) << \"resuming stream previously ingress limited; sess=\" << *this;\n    // We were previously read limited and now we're not, so we can resume the\n    // read loop only if we're not qpack blocked.\n    if (multiCodec_->setCurrentStream(id) && !multiCodec_->isParserPaused()) {\n      quicSocket_->resumeRead(id);\n    }\n  }\n}\n\nbool HTTPUniplexTransportSession::sendFlowControlUpdate(HTTPCodec::StreamID id,\n                                                        size_t delta) {\n  return codec_->generateWindowUpdate(writeBuf_, id, delta);\n}\n\nvoid HTTPCoroSession::sourceComplete(HTTPCodec::StreamID id,\n                                     folly::Optional<HTTPError> error) {\n  XLOG(DBG6) << __func__ << \" sess=\" << *this << \" id=\" << id\n             << \" error=\" << (error.has_value() ? error->describe() : \"\");\n  auto stream = CHECK_NOTNULL(findStream(id));\n  stream->markStreamSourceComplete();\n  if (stream->streamSource.isEOMSeen() && !error) {\n    checkForDetach(*stream);\n    return;\n  }\n  XCHECK(error) << \"StreamSource must supply an error when source complete \"\n                << \"but not EOM seen\";\n  if (isDownstream() && stream->canSendHeaders() &&\n      (stream->errorStatusCode || error->code == HTTPErrorCode::READ_TIMEOUT)) {\n    // If there's an error source, don't reset, otherwise it's a timeout\n    if (!stream->errorStatusCode) {\n      stream->setErrorStatusCode(408);\n    }\n  } else if (!stream->streamSource.isEOMSeen() || !stream->isEgressComplete()) {\n    // Need to send a RST_STREAM\n    auto egressErrorCode = sourceCompleteErr2ErrorCode(error->code);\n    if (!stream->isUpgraded() ||\n        egressErrorCode == HTTPErrorCode::FLOW_CONTROL_ERROR ||\n        egressErrorCode == HTTPErrorCode::PROTOCOL_ERROR) {\n      if (isNoError(egressErrorCode) && isDownstream()) {\n        // delay STOP_SENDING w/ NO_ERROR until egress EOM (except for\n        // http/1.1)\n        stream->setDeferredStopSending(codec_->supportsParallelRequests());\n      } else {\n        // egress bidirectional reset\n        egressResetStream(id,\n                          stream,\n                          isNoError(egressErrorCode) ? HTTPErrorCode::CANCEL\n                                                     : egressErrorCode);\n      }\n    } else {\n      // Abandoned CONNECT streams are a no-op\n      checkForDetach(*stream);\n    }\n  } else {\n    // The stream is complete, but there's nothing to egress on the wire.\n    XLOG(DBG4) << \"Clearing stream state for stream=\" << id\n               << \" sess=\" << *this;\n    resetStreamState(*stream, *error);\n  }\n  writeEvent_.signal();\n}\n\nvoid HTTPUniplexTransportSession::handleDeferredStopSending(\n    HTTPCodec::StreamID id) {\n  generateResetStream(id, HTTPErrorCode::NO_ERROR, /*fromSource=*/false);\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::handleDeferredStopSending(HTTPCodec::StreamID id) {\n  generateStopSending(id, HTTPErrorCode::NO_ERROR, /*fromSource=*/false);\n}\n\nvoid HTTPCoroSession::egressFinished(StreamState& stream) {\n  XLOG(DBG4) << \"egress finished for stream id=\" << stream.getID()\n             << \" sess=\" << *this;\n  decrementPushStreamCount(stream,\n                           /*eomMarkedEgressComplete=*/true);\n  deliverLifecycleEvent(&LifecycleObserver::onRequestEnd, *this, 0);\n  if (stream.getDeferredStopSending()) {\n    handleDeferredStopSending(stream.getID());\n  }\n  checkForDetach(stream);\n}\n\nfolly::coro::Task<HTTPSourceHolder> HTTPCoroSession::handleRequest(\n    StreamState& stream) {\n  XLOG(DBG6) << \"starting handleRequest id=\" << stream.getID()\n             << \" sess=\" << *this;\n  HTTPSourceHolder responseSource{nullptr};\n  // Don't invoke handleRequest if egress is already complete\n  if (!stream.isEgressComplete()) {\n    stream.streamSourceActive();\n    auto res = co_await co_awaitTry(handler_->handleRequest(\n        eventBase_.get(), acquireKeepAlive(), &stream.streamSource));\n    if (auto* ex = res.tryGetExceptionObject()) {\n      XLOG(DBG4) << \"Handler generated exception: \" << ex->what();\n      responseSource = getErrorResponse(500, ex->what());\n    } else {\n      responseSource = std::move(res.value());\n    }\n  }\n  co_return responseSource;\n}\n\nfolly::coro::Task<void> HTTPCoroSession::readResponse(\n    StreamState& stream,\n    folly::coro::Task<HTTPSourceHolder> getRespSourceTask) {\n  auto cleanup = [this, &stream] {\n    stream.markEgressCoroComplete();\n    checkForDetach(stream);\n  };\n  auto g = folly::makeGuard(cleanup);\n\n  auto responseSource = co_await std::move(getRespSourceTask);\n  if (stream.isEgressComplete()) {\n    XLOG(DBG4)\n        << \"readResponse egressComplete responseSource=\" << int(responseSource)\n        << \" id=\" << stream.getID() << \" sess=\" << *this;\n    co_return;\n  }\n  if (!responseSource) {\n    XLOG(DBG3) << \"Handler did not provide a response source, returning 500\"\n               << \" id=\" << stream.getID() << \" sess=\" << *this;\n    responseSource.setSource(getErrorResponse(500));\n  }\n  stream.setEgressSource(responseSource.release());\n\n  const auto& wcs = stream.cs.egress;\n  ResponseState responseState = ResponseState::HEADERS;\n  do {\n    responseState = processResponseHeaderEvent(\n        stream,\n        co_await co_awaitTry(\n            co_withCancellation(wcs.getToken(), stream.nextHeaderEvent())));\n    if (responseState == ResponseState::DONE) {\n      co_return;\n    }\n  } while (responseState == ResponseState::HEADERS);\n  XLOG(DBG6) << \"Done generating headers sess=\" << *this;\n  g.dismiss();\n  co_withExecutor(eventBase_,\n                  co_withCancellation(wcs.getToken(),\n                                      transferBody(stream, std::move(cleanup))))\n      .startInlineUnsafe();\n}\n\nHTTPCoroSession::ResponseState HTTPCoroSession::processResponseHeaderEvent(\n    StreamState& stream, folly::Try<HTTPHeaderEvent> headerEvent) {\n  if (stream.isEgressComplete()) {\n    return ResponseState::DONE;\n  }\n\n  auto switchToErrSource = [&](HTTPErrorCode ec) {\n    XLOG(DBG4) << \"switchToErrSource sc=\" << stream.errorStatusCode;\n    stream.stopReading(ec);\n    stream.setEgressSource(\n        getErrorResponse(std::exchange(stream.errorStatusCode, 0)));\n    return ResponseState::HEADERS;\n  };\n\n  if (headerEvent.hasException()) {\n    auto ex = getHTTPError(headerEvent);\n    XLOG(DBG4) << \"Error getting response headers sess=\" << *this\n               << \" ex=\" << ex.msg;\n    if (stream.errorStatusCode) {\n      return switchToErrSource(ex.code);\n    }\n    stream.stopReading(ex.code);\n    egressResetStream(stream.getID(), &stream, ex.code, true);\n    return ResponseState::DONE;\n  }\n\n  const auto& headers = *headerEvent->headers;\n  if (stream.errorStatusCode &&\n      headers.getStatusCode() != stream.errorStatusCode) {\n    return switchToErrSource(HTTPErrorCode::INTERNAL_ERROR);\n  }\n\n  XLOG(DBG4) << \"Got response headers eom=\" << uint32_t(headerEvent->eom)\n             << \" sess=\" << *this;\n  headers.dumpMessage(4);\n  auto upgrade = stream.checkForUpgrade(headers, /*isIngress=*/false);\n  if (upgrade & !codec_->supportsParallelRequests()) {\n    codec_->setParserPaused(false);\n  }\n\n  if (auto pri = headerEvent->headers->getHTTPPriority()) {\n    onPriority(stream.getID(), *pri);\n  }\n\n  HTTPHeaderSize size;\n  codec_->generateHeader(\n      stream.getWriteBuf(), stream.getID(), headers, headerEvent->eom, &size);\n  stream.addToStreamOffset(size.compressed);\n  HTTPByteEvent::FieldSectionInfo fsInfo = {\n      HTTPByteEvent::FieldSectionInfo::Type::HEADERS,\n      headerEvent->isFinal(),\n      size};\n  registerByteEvents(stream.getID(),\n                     stream.getStreamOffset(),\n                     fsInfo,\n                     /*bodyOffset=*/0,\n                     std::move(headerEvent->byteEventRegistrations),\n                     headerEvent->eom);\n  if (headerEvent->egressHeadersFn) {\n    headerEvent->egressHeadersFn(size);\n  }\n  notifyHeaderWrite(stream, headerEvent->eom);\n  if (headerEvent->eom) {\n    return ResponseState::DONE;\n  }\n  return !headerEvent->isFinal() ? ResponseState::HEADERS : ResponseState::BODY;\n}\n\nvoid HTTPUniplexTransportSession::notifyHeaderWrite(StreamState& stream,\n                                                    bool eom) {\n  if (eom) {\n    stream.markEgressComplete();\n    egressFinished(stream);\n  }\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::notifyHeaderWrite(StreamState& stream, bool eom) {\n  notifyBodyWrite(stream);\n  if (eom) {\n    stream.setPendingEgressEOM();\n  }\n}\n\nbool HTTPQuicCoroSession::handleWrite(HTTPCodec::StreamID id,\n                                      folly::IOBufQueue& writeBuf,\n                                      bool eom) {\n  if (writeBuf.empty() && !eom) {\n    return true;\n  }\n  deliverLifecycleEvent(\n      &LifecycleObserver::onWrite, *this, writeBuf.chainLength());\n  XLOG(DBG4) << \"Writing id=\" << id << \" len=\" << writeBuf.chainLength()\n             << \" eom=\" << (eom ? \"true\" : \"false\") << \" sess=\" << *this;\n  auto res = quicSocket_->writeChain(\n      id, writeBuf.move(), eom, eom ? &deliveryCallback_ : nullptr);\n  if (res.hasError()) {\n    XLOG(DBG3) << \"Error writing to stream, err=\" << quic::toString(res.error())\n               << \" id=\" << id << \" sess=\" << *this;\n    connectionError(HTTPErrorCode::INTERNAL_ERROR, \"Write failed\");\n    return false;\n  }\n  return true;\n}\n\nfolly::coro::Task<void> HTTPCoroSession::transferBody(\n    StreamState& stream, std::function<void()> guard) {\n  XLOG(DBG6) << __func__;\n  auto errorCode = HTTPErrorCode::NO_ERROR;\n  SCOPE_EXIT {\n    stream.stopReading(errorCode);\n    guard();\n  };\n  bool eom = false;\n  do {\n    XLOG(DBG4) << \"Waiting for a body event id=\" << stream.getID()\n               << \" sess=\" << *this;\n    auto eomEvent = co_await co_awaitTry(stream.nextBodyEvent());\n    if (eomEvent.hasException()) {\n      auto err = getHTTPError(eomEvent);\n      XLOG(DBG4) << \"Error getting body sess=\" << *this\n                 << \" id=\" << stream.getID() << \" err=\" << err.msg;\n      if (!stream.isEgressComplete()) {\n        egressResetStream(stream.getID(), &stream, err.code, true);\n      }\n      errorCode = err.code;\n      co_return;\n    }\n    if (!stream.isEgressComplete()) {\n      if (eomEvent->resume.has_value()) {\n        co_await std::move(eomEvent->resume.value());\n      } else {\n        notifyBodyWrite(stream);\n      }\n      eom = eomEvent->eom;\n    } else {\n      eom = true;\n    }\n  } while (!eom);\n}\n\nvoid HTTPUniplexTransportSession::registerByteEvents(\n    HTTPCodec::StreamID id,\n    folly::Optional<uint64_t> streamByteOffset, // stream codec bytes\n    folly::Optional<HTTPByteEvent::FieldSectionInfo> fsInfo,\n    uint64_t bodyOffset,\n    std::vector<HTTPByteEventRegistration>&& regs,\n    bool eom) {\n  XCHECK(streamByteOffset) << \"streamByteOffset required for uniplex\";\n  auto sessionByteOffset = sessionBytesScheduled_ + writeBuf_.chainLength();\n  byteEventObserver_.registerByteEvents(id,\n                                        sessionByteOffset,\n                                        sessionBytesScheduled_,\n                                        *streamByteOffset,\n                                        fsInfo,\n                                        bodyOffset,\n                                        std::move(regs),\n                                        eom);\n}\n\nvoid HTTPQuicCoroSession::registerByteEvents(\n    HTTPCodec::StreamID id,\n    folly::Optional<uint64_t> streamByteOffset,\n    folly::Optional<HTTPByteEvent::FieldSectionInfo> fsInfo,\n    uint64_t bodyOffset,\n    std::vector<HTTPByteEventRegistration>&& registrations,\n    bool eom) {\n  auto localRegistrations = std::move(registrations);\n  if (!streamByteOffset) {\n    auto maybeStreamByteOffset = quicSocket_->getStreamWriteOffset(id);\n    if (maybeStreamByteOffset.has_value()) {\n      // TODO: could save this lookup by passing stream*\n      auto stream = findStream(id);\n      XCHECK(stream);\n      streamByteOffset =\n          *maybeStreamByteOffset + stream->getWriteBuf().chainLength();\n    } else {\n      HTTPError err(HTTPErrorCode::INTERNAL_ERROR, \"Stream offset unavailable\");\n      for (auto& reg : localRegistrations) {\n        reg.cancel(err);\n      }\n      return;\n    }\n  }\n  for (auto& reg : localRegistrations) {\n    if (reg.events == 0 || !reg.callback) {\n      continue;\n    }\n    HTTPByteEvent httpByteEvent;\n    httpByteEvent.streamID = reg.streamID.value_or(id);\n    httpByteEvent.fieldSectionInfo = fsInfo;\n    httpByteEvent.bodyOffset = bodyOffset;\n    httpByteEvent.transportOffset = *streamByteOffset;\n    httpByteEvent.streamOffset = *streamByteOffset; // same as transportOffset\n    httpByteEvent.eom = eom;\n    for (auto eventType : HTTPByteEvent::kByteEventTypes) {\n      httpByteEvent.type = eventType;\n      if (reg.events & uint8_t(eventType)) {\n        if (eventType == HTTPByteEvent::Type::TRANSPORT_WRITE) {\n          reg.callback->onByteEvent(httpByteEvent);\n        } else {\n          auto cb = new QuicByteEventCallback(\n              byteEventRefcount_, httpByteEvent, reg.callback);\n          auto res = quicSocket_->registerByteEventCallback(\n              cb->quicByteEventType(), id, *streamByteOffset, cb);\n          if (!res) {\n            cb->cancel();\n          }\n        }\n      }\n    }\n    // clear the callback now so the dtor doesn't cancel\n    reg.callback.reset();\n  }\n}\n\nvoid HTTPCoroSession::notifyBodyWrite(StreamState& stream) {\n  insertWithPriority(stream);\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::onDatagramsAvailable() noexcept {\n  auto result = quicSocket_->readDatagrams();\n  if (result.hasError()) {\n    XLOG(ERR) << \"Got error while reading datagrams: error=\"\n              << toString(result.error());\n    connectionError(HTTPErrorCode::INTERNAL_ERROR,\n                    \"H3_DATAGRAM: internal error\");\n    return;\n  }\n  XLOG(DBG4) << \"Received \" << result.value().size()\n             << \" datagrams. sess=\" << *this;\n  for (auto& datagram : result.value()) {\n    folly::io::Cursor cursor(datagram.bufQueue().front());\n    auto quarterStreamId = quic::follyutils::decodeQuicInteger(cursor);\n    if (!quarterStreamId || quarterStreamId->first > kMaxQuarterStreamId) {\n      connectionError(HTTPErrorCode::GENERAL_PROTOCOL_ERROR,\n                      \"H3_DATAGRAM: error decoding stream-id\");\n      break;\n    }\n\n    quic::BufQueue datagramQ;\n    datagramQ.append(datagram.bufQueue().move());\n    datagramQ.trimStart(quarterStreamId->second);\n\n    auto streamId = quarterStreamId->first * 4;\n    auto stream = findStream(streamId);\n\n    if (!stream || stream->streamSource.isUnprocessed()) {\n      XLOG(DBG5) << \"Stream cannot receive datagrams. streamId=\" << streamId\n                 << \" len=\" << datagramQ.chainLength() << \" sess=\" << *this;\n      // TODO: a possible optimization would be to discard datagrams destined\n      // to streams that were already closed\n      auto itr = datagramsBuffer_.find(streamId);\n      if (itr == datagramsBuffer_.end()) {\n        itr = datagramsBuffer_.insert(streamId, {}).first;\n      }\n      auto& vec = itr->second;\n      if (vec.size() < vec.max_size()) {\n        vec.emplace_back(datagramQ.move());\n      } else {\n        // buffer is full: discard the datagram\n        datagramQ.move();\n      }\n      continue;\n    }\n\n    XLOG(DBG5) << \"Received datagram for streamId=\" << streamId\n               << \" len=\" << datagramQ.chainLength() << \" sess=\" << *this;\n    stream->streamSource.datagram(datagramQ.move());\n  }\n}\n\nuint16_t HTTPQuicCoroSession::getDatagramSizeLimit() const {\n  if (!isDatagramEnabled() || !quicSocket_->good()) {\n    return 0;\n  }\n  auto transportMaxDatagramSize = quicSocket_->getDatagramSizeLimit();\n  if (transportMaxDatagramSize < kMaxDatagramHeaderSize) {\n    return 0;\n  }\n  return quicSocket_->getDatagramSizeLimit() - kMaxDatagramHeaderSize;\n}\n\nbool HTTPQuicCoroSession::sendDatagram(HTTPCodec::StreamID id,\n                                       std::unique_ptr<folly::IOBuf> datagram) {\n  if (!isDatagramEnabled() || !quic::isClientBidirectionalStream(id) ||\n      !quicSocket_->good()) {\n    return false;\n  }\n  // Prepend the H3 Datagram header to the datagram payload\n  // HTTP/3 Datagram {\n  //   Quarter Stream ID (i),\n  //   HTTP/3 Datagram Payload (..),\n  // }\n  quic::BufPtr headerBuf =\n      quic::BufPtr(folly::IOBuf::create(quicSocket_->getDatagramSizeLimit()));\n  quic::BufAppender appender(headerBuf.get(), kMaxDatagramHeaderSize);\n  auto streamIdRes =\n      quic::encodeQuicInteger(id / 4, [&](auto val) { appender.writeBE(val); });\n  if (streamIdRes.hasError()) {\n    return false;\n  }\n  XLOG(DBG5) << \"Sending datagram for streamId=\" << id\n             << \" len=\" << datagram->computeChainDataLength()\n             << \" sess=\" << *this;\n  quic::BufQueue queue(std::move(headerBuf));\n  queue.append(std::move(datagram));\n  auto writeRes = quicSocket_->writeDatagram(queue.move());\n  if (writeRes.hasError()) {\n    XLOG(DBG5) << \"Failed to send datagram for streamId=\" << id;\n    return false;\n  }\n  return true;\n}\n\nvoid HTTPCoroSession::transactionAttached() noexcept {\n  if (numStreams() == 1) {\n    deliverLifecycleEvent(&LifecycleObserver::onActivateConnection, *this);\n    if (auto* connManager = getConnectionManager()) {\n      connManager->onActivated(*this);\n    }\n  }\n  deliverLifecycleEvent(&LifecycleObserver::onTransactionAttached, *this);\n}\n\nvoid HTTPCoroSession::transactionDetached() noexcept {\n  deliverLifecycleEvent(&LifecycleObserver::onTransactionDetached, *this);\n  if (numStreams() == 0) {\n    deliverLifecycleEvent(&LifecycleObserver::onDeactivateConnection, *this);\n    if (auto* connManager = getConnectionManager()) {\n      connManager->onDeactivated(*this);\n    }\n  }\n}\n\nfolly::coro::Task<HTTPSourceHolder> HTTPCoroSession::sendRequest(\n    HTTPSourceHolder requestSource) {\n  auto reservation = reserveRequest();\n  if (reservation.hasException()) {\n    return folly::coro::makeErrorTask<HTTPSourceHolder>(\n        std::move(reservation.exception()));\n  }\n  return sendRequest(std::move(requestSource), std::move(*reservation));\n}\n\nfolly::Try<HTTPCoroSession::RequestReservation>\nHTTPCoroSession::reserveRequest() {\n  XLOG(DBG6) << \"reserveRequest sess=\" << *this;\n  if (!supportsMoreTransactions()) {\n    XLOG(ERR) << \"Refusing to send request, streams=\" << numOutgoingStreams()\n              << \" sess=\" << *this;\n    return folly::Try<RequestReservation>(\n        HTTPError(HTTPErrorCode::REFUSED_STREAM, \"Exceeded stream limit\"));\n  }\n\n  RequestReservation reservation(this);\n  transactionAttached();\n  return folly::Try<RequestReservation>(std::move(reservation));\n}\n\nfolly::coro::Task<HTTPSourceHolder> HTTPCoroSession::sendRequest(\n    HTTPSourceHolder requestSource, RequestReservation reservation) {\n  if (!reservation.fromSession(this)) {\n    XLOG(DFATAL) << \"Invalid reservation sess=\" << *this;\n    co_yield co_error(\n        HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"Invalid reservation\"));\n  }\n  // TODO: do we want to throttle reading request headers on buffer space\n  auto headerEvent = co_await co_awaitTry(requestSource.readHeaderEvent());\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  if (cancelToken.isCancellationRequested()) {\n    XLOG(DBG4) << \"Egress coro cancelled for new stream, sess=\" << *this;\n    co_yield co_error(HTTPError(HTTPErrorCode::CORO_CANCELLED, \"Cancelled\"));\n  }\n  if (headerEvent.hasException()) {\n    XLOG(DBG4) << \"Error getting headers for sess=\" << *this;\n    co_yield co_error(getHTTPError(headerEvent));\n  }\n  if (isDraining()) {\n    XLOG(DBG3) << \"Refusing to send new stream on draining sess=\" << *this;\n    co_yield co_error(\n        HTTPError(HTTPErrorCode::REFUSED_STREAM, \"Session draining\"));\n  }\n  reservation.consume();\n  auto res =\n      sendRequestImpl(*headerEvent->headers,\n                      std::move(headerEvent->egressHeadersFn),\n                      std::move(headerEvent->byteEventRegistrations),\n                      headerEvent->eom ? nullptr : std::move(requestSource));\n  if (res.hasError()) {\n    co_yield co_error(std::move(res.error()));\n  }\n  co_return std::move(res.value());\n}\n\nfolly::Expected<HTTPSourceHolder, HTTPError> HTTPCoroSession::sendRequest(\n    RequestReservation reservation,\n    const HTTPMessage& headers,\n    HTTPSourceHolder bodySource) noexcept {\n  if (!reservation.fromSession(this)) {\n    XLOG(DFATAL) << \"Invalid reservation sess=\" << *this;\n    return folly::makeUnexpected(\n        HTTPError{HTTPErrorCode::INTERNAL_ERROR, \"Invalid reservation\"});\n  }\n  if (isDraining()) {\n    XLOG(DBG3) << \"Refusing to send new stream on draining sess=\" << *this;\n    return folly::makeUnexpected(\n        HTTPError(HTTPErrorCode::REFUSED_STREAM, \"Session draining\"));\n  }\n  reservation.consume();\n  return sendRequestImpl(/*headers=*/headers,\n                         /*egressHeadersFn=*/nullptr,\n                         /*byteEventRegistrations=*/{},\n                         /*bodySource=*/std::move(bodySource));\n}\n\nfolly::Expected<HTTPSourceHolder, HTTPError> HTTPCoroSession::sendRequestImpl(\n    const HTTPMessage& headers,\n    folly::Function<void(HTTPHeaderSize) noexcept>&& egressHeadersFn,\n    std::vector<HTTPByteEventRegistration>&& byteEventRegistrations,\n    HTTPSourceHolder bodySource) noexcept {\n  auto* stream = createReqStream();\n  if (!stream) {\n    XLOG(ERR) << \"Failed to create new stream\" << *this;\n    return folly::makeUnexpected(\n        HTTPError{HTTPErrorCode::REFUSED_STREAM, \"Create stream failed\"});\n  }\n  if (headers.getMethod() == HTTPMethod::HEAD) {\n    stream->markIngressAsHeadResponse();\n  }\n  auto streamID = stream->getID();\n  XLOG(DBG4) << \"Got request headers sess=\" << *this << \" id=\" << streamID;\n  headers.dumpMessage(4);\n  stream->checkForUpgrade(headers, /*isIngress=*/false);\n  // TODO: do we want to throttle reading request headers on buffer space\n  bool eom = !bodySource.readable();\n  HTTPHeaderSize size;\n  codec_->generateHeader(stream->getWriteBuf(), streamID, headers, eom, &size);\n  stream->addToStreamOffset(size.compressed);\n  XLOG(DBG6) << \"Done generating headers sess=\" << *this << \" id=\" << streamID;\n  HTTPByteEvent::FieldSectionInfo fsInfo = {\n      HTTPByteEvent::FieldSectionInfo::Type::HEADERS, true, size};\n  registerByteEvents(stream->getID(),\n                     stream->getStreamOffset(),\n                     fsInfo,\n                     /*bodyOffset=*/0,\n                     std::move(byteEventRegistrations),\n                     eom);\n  if (egressHeadersFn) {\n    egressHeadersFn(size);\n  }\n  // TODO: HTTPSession can defer sending the initial goaway until after\n  // egressing pending headers.  Is this necessary?\n  stream->streamSourceActive();\n  stream->setExpectedEgressContentLength(\n      headers.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH), eom);\n  notifyHeaderWrite(*stream, eom);\n  if (eom) {\n    deliverLifecycleEvent(&LifecycleObserver::onRequestEnd, *this, 0);\n    stream->setReadTimeout(streamReadTimeout_);\n  } else {\n    stream->startEgressCoro();\n    // inline for better perf; if bodyEvent is available, this optimization\n    // results in combined headers+body ::writeChain\n    co_withExecutor(eventBase_,\n                    co_withCancellation(\n                        stream->cs.egress.getToken(),\n                        transferRequestBody(*stream, std::move(bodySource))))\n        .startInlineUnsafe();\n  }\n  XLOG(DBG6) << \"terminating sendRequest sess=\" << *this << \" id=\" << streamID;\n\n  return &stream->streamSource;\n}\n\nHTTPCoroSession::StreamState* HTTPUniplexTransportSession::createReqStream() {\n  // create request stream with no timeout until EOM is sent\n  return &createNewStream(codec_->createStream(), /*fromSendRequest=*/true);\n}\n\nHTTPCoroSession::StreamState* HTTPQuicCoroSession::createReqStream() {\n  auto quicStreamId = quicSocket_->createBidirectionalStream();\n  if (!quicStreamId.has_value()) {\n    return nullptr;\n  }\n  multiCodec_->addCodec(*quicStreamId);\n  auto& stream = createNewStream(*quicStreamId, /*fromSendRequest=*/true);\n  backgroundScope_.add(\n      co_withExecutor(&readExec_,\n                      co_withCancellation(stream.cs.ingress.getToken(),\n                                          readLoop(*quicStreamId))));\n  return &stream;\n}\n\nfolly::coro::Task<void> HTTPCoroSession::transferRequestBody(\n    StreamState& stream, HTTPSourceHolder requestSource) {\n  stream.setEgressSource(requestSource.release());\n  co_await transferBody(stream, [] {});\n  stream.setReadTimeout(streamReadTimeout_);\n  stream.markEgressCoroComplete();\n  checkForDetach(stream);\n}\n\nvoid HTTPCoroSession::egressResetStream(HTTPCodec::StreamID id,\n                                        StreamState* stream,\n                                        HTTPErrorCode error,\n                                        bool fromSource,\n                                        bool bidirectionalReset) {\n  generateResetStream(id, error, fromSource, bidirectionalReset);\n  writeEvent_.signal();\n  if (!stream) {\n    stream = findStream(id);\n  }\n  if (stream) {\n    decrementPushStreamCount(*stream);\n    std::string_view details(\"Sent reset\");\n    if (bidirectionalReset) {\n      if (fromSource) {\n        error = HTTPErrorCode::CORO_CANCELLED;\n      } else if (isNoError(error)) {\n        error = HTTPErrorCode::CANCEL;\n      }\n      stream->abortIngress(error, details);\n    }\n    // TODO: message will be suboptimal when receiving a STOP_SENDING\n    resetStreamState(*stream, HTTPError(error, std::string(details)));\n  }\n}\n\nvoid HTTPUniplexTransportSession::generateResetStream(\n    HTTPCodec::StreamID id,\n    HTTPErrorCode error,\n    bool fromSource,\n    bool /*bidirectionalReset*/) {\n  // h1 & h2 resets are bidirectional (terminates ingress & egress)\n  if (!codec_->generateRstStream(\n          writeBuf_, id, HTTPErrorCode2ErrorCode(error, fromSource))) {\n    XLOG(DBG4) << \"resetAfterDrainingWrites sess=\" << *this;\n    resetAfterDrainingWrites_ = true;\n    drainStarted();\n  }\n}\n\nvoid HTTPQuicCoroSession::generateResetStream(HTTPCodec::StreamID id,\n                                              HTTPErrorCode error,\n                                              bool fromSource,\n                                              bool bidirectionalReset) {\n  HTTP3::ErrorCode h3Error = HTTPErrorCode2HTTP3ErrorCode(error, fromSource);\n  // TODO: don't egress RS/SS if that direction is already closed\n  // not valid for uni-ingress\n  if (quicSocket_->isBidirectionalStream(id) ||\n      (quicSocket_->isServerStream(id) == isDownstream())) {\n    XLOG(DBG4) << \"resetStream err=\" << uint32_t(error) << \" id=\" << id\n               << \" sess=\" << *this;\n    quicSocket_->resetStream(id, quic::ApplicationErrorCode(h3Error));\n  }\n\n  if (bidirectionalReset) {\n    generateStopSending(id, error, fromSource);\n  }\n  // egressResetStream calls writeEvent_.signal()\n}\n\nvoid HTTPQuicCoroSession::generateStopSending(HTTPCodec::StreamID id,\n                                              HTTPErrorCode error,\n                                              bool fromSource) {\n  // TODO: don't egress RS/SS if that direction is already closed\n  // not valid for uni-egress\n  if (quicSocket_->isBidirectionalStream(id) ||\n      quicSocket_->isServerStream(id) == isUpstream()) {\n\n    HTTP3::ErrorCode h3Error = HTTPErrorCode2HTTP3ErrorCode(error, fromSource);\n    XLOG(DBG4) << \"stopSending err=\" << uint32_t(error) << \" id=\" << id\n               << \" sess=\" << *this;\n\n    multiCodec_->encodeCancelStream(id);\n    quicSocket_->stopSending(id, quic::ApplicationErrorCode(h3Error));\n  }\n}\n\nvoid HTTPCoroSession::resetStreamState(StreamState& stream,\n                                       const HTTPError& err) {\n  // streamSourceComplete_ == true iff consumer has read an error or EOM.\n  stream.resetStream(err);\n  // unconditional erase could be gratuitous lookup\n  removeWritableStream(stream.getID());\n  bool signaled = checkForDetach(stream);\n  if (!signaled) {\n    writeEvent_.signal();\n  }\n}\n\nbool HTTPCoroSession::checkForDetach(StreamState& stream) {\n  if (stream.isDetachable()) {\n    XLOG(DBG4) << \"detaching stream=\" << stream.getID() << \" sess=\" << *this;\n    eraseStream(stream.getID());\n    transactionDetached();\n    if (!codec_->supportsParallelRequests() && streams_.size() <= 1) {\n      handlePipeliningOnDetach();\n    }\n    writeEvent_.signal();\n    return true;\n  }\n  return false;\n}\n\nvoid HTTPCoroSession::eraseStream(HTTPCodec::StreamID id) {\n  streams_.erase(id);\n  SESS_STATS(recordTransactionClosed);\n}\n\nvoid HTTPQuicCoroSession::eraseStream(HTTPCodec::StreamID id) {\n  HTTPCoroSession::eraseStream(id);\n  if (multiCodec_->setCurrentStream(id) && multiCodec_->isParserPaused()) {\n    multiCodec_->encodeCancelStream(id);\n  }\n  multiCodec_->removeCodec(id);\n}\n\nvoid HTTPUniplexTransportSession::handlePipeliningOnDetach() {\n  if (streams_.size() == 1 && resetAfterDrainingWrites_) {\n    // Super special case: a pipelined request created a second stream,\n    // but the active stream triggered a reset.  Just reset the pipelined\n    // stream, leave the parser paused, and signal the baton.\n    resetStreamState(*streams_.begin()->second,\n                     HTTPError(HTTPErrorCode::CANCEL, \"Pipeline cancel\"));\n  } else {\n    codec_->setParserPaused(false);\n  }\n  antiPipelineBaton_.signal();\n}\n\nvoid HTTPCoroSession::setSetting(SettingsId id, uint32_t value) {\n  auto settings = codec_->getEgressSettings();\n  if (settings) {\n    settings->setSetting(id, value);\n  }\n}\n\nvoid HTTPUniplexTransportSession::sendPing() {\n  codec_->generatePingRequest(writeBuf_, folly::none);\n  writeEvent_.signal();\n}\n\nvoid HTTPQuicCoroSession::sendPing() {\n  quicSocket_->sendPing(std::chrono::milliseconds(0));\n}\n\nvoid HTTPUniplexTransportSession::setConnectionFlowControl(\n    uint32_t connFlowControl) {\n  if (codec_->supportsSessionFlowControl()) {\n    auto delta = recvWindow_.setCapacity(connFlowControl);\n    if (delta) {\n      codec_->generateWindowUpdate(writeBuf_, 0, delta);\n      writeEvent_.signal();\n    }\n  }\n}\n\nvoid HTTPQuicCoroSession::setConnectionFlowControl(uint32_t connFlowControl) {\n  quicSocket_->setConnectionFlowControlWindow(connFlowControl);\n}\n\nbool HTTPUniplexTransportSession::shouldContinueReadLooping() const {\n  // Continue reading while there are open streams or the codec is reusable,\n  // unless writes have finished or there is a pending reset\n  bool continueLoop = !writesFinished_.ready() && !resetAfterDrainingWrites_ &&\n                      (codec_->isReusable() || !streams_.empty());\n  // clang-format off\n  XLOG(DBG4)\n    << __func__\n    << \" continue=\" << uint32_t(continueLoop)\n    << \" writesFinished_=\" << uint32_t(writesFinished_.ready())\n    << \" resetAfterDrainingWrites_=\" << uint32_t(resetAfterDrainingWrites_)\n    << \" isReusable=\" << uint32_t(codec_->isReusable())\n    << \" nStreams=\" << streams_.size()\n    << \" sess=\" << *this;\n  // clang-format on\n  return continueLoop;\n}\n\nfolly::coro::Task<void> HTTPUniplexTransportSession::readLoop() noexcept {\n  XLOG(DBG6) << \"starting readLoop sess=\" << *this;\n  folly::IOBufQueue readBuf(folly::IOBufQueue::cacheChainLength());\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      cancelToken, [this] { dropConnection(\"Connection dropped (cancel)\"); }};\n  while (shouldContinueReadLooping()) {\n    XLOG(DBG6) << \"before read sess=\" << *this;\n    auto cancellationToken = readCancellationSource_.getToken();\n    if (!flowControlBaton_.ready()) {\n      flowControlBaton_.reset();\n      auto status = co_await flowControlBaton_.timedWait(eventBase_.get(),\n                                                         connReadTimeout_);\n      if (status == TimedBaton::Status::timedout) {\n        XLOG(DBG4) << \"Timed out waiting for flow control sess=\" << *this;\n        connectionError(HTTPErrorCode::READ_TIMEOUT,\n                        \"ingress backpressure timeout\");\n        break;\n      }\n    }\n    folly::Try<size_t> rc;\n    {\n      auto guard = readExec_.acquireGuard();\n      rc = co_await co_awaitTry(co_withCancellation(\n          cancellationToken,\n          coroTransport_->read(\n              readBuf, kMinReadSize, kReadBufNewAllocSize, connReadTimeout_)));\n    }\n    if (cancellationToken.isCancellationRequested()) {\n      XLOG(DBG4) << \"Read cancelled sess=\" << *this;\n      XCHECK(!shouldContinueReadLooping());\n      if (rc.hasException()) {\n        continue;\n      }\n    }\n    if (rc.hasException()) {\n      auto ex = rc.exception().get_exception<folly::AsyncSocketException>();\n      XCHECK(ex) << \"Unexpected exception type \"\n                 << folly::exceptionStr(rc.exception());\n      if (ex->getType() == folly::coro::TransportIf::ErrorCode::TIMED_OUT) {\n        XLOG(DBG4) << \"Initiating drain due to read timeout sess=\" << *this;\n        initiateDrain();\n        continue;\n      }\n      deliverLifecycleEvent(\n          &LifecycleObserver::onIngressError, *this, kErrorConnectionReset);\n\n      XLOG(DBG4) << \"Read Error ex=\" << ex->what() << \" sess=\" << *this;\n      connectionError(HTTPErrorCode::TRANSPORT_READ_ERROR, ex->what());\n      break;\n    }\n    if (*rc == 0) { // peer closed the connection\n      XLOG(DBG4) << \"Read EOF sess=\" << *this;\n      deliverLifecycleEvent(&LifecycleObserver::onIngressEOF, *this);\n      codec_->onIngressEOF();\n      for (auto& [_, stream] : streams_) {\n        stream->abortIngress(HTTPErrorCode::TRANSPORT_EOF);\n      }\n      break;\n    }\n    XLOG(DBG6) << \"Read \" << *rc << \" bytes sess=\" << *this;\n    deliverLifecycleEvent(\n        &LifecycleObserver::onRead, *this, *rc, HTTPCodec::NoStream);\n    // We basically have two timeouts here\n    resetTimeout();\n    size_t bytesParsed = 0;\n    do {\n      bytesParsed = codec_->onIngress(*readBuf.front());\n      readBuf.trimStart(bytesParsed);\n      if (bytesParsed == 0 && !readBuf.empty() &&\n          !codec_->supportsParallelRequests() && streams_.size() > 1) {\n        XLOG(DBG4) << \"Waiting for previous transaction(s) to finish before \"\n                   << \"parsing more sess=\" << *this;\n        antiPipelineBaton_.reset();\n        auto status = co_await antiPipelineBaton_.wait();\n        XCHECK(status != TimedBaton::Status::timedout);\n        if (status != TimedBaton::Status::cancelled) {\n          // pretend we parsed something so we go around the loop again\n          bytesParsed = 1;\n        } // else, bytesParsed == 0, and cancelCallback initiated teardown\n      }\n    } while (bytesParsed > 0 && !readBuf.empty());\n    if (*rc >= kMaxReadDataPerLoop) {\n      // Maxed out this loop, give someone else a chance\n      co_await folly::coro::co_reschedule_on_current_executor;\n    }\n\n    // Never pause reading from H2 streams, memory is bounded by flow control\n    // limits, and control messages will be rate limited\n    // TODO: pause reading from H1 streams\n  }\n  readsClosed_ = true;\n  writeEvent_.signal();\n  XLOG(DBG6) << \"readLoop terminating sess=\" << *this;\n}\n\nvoid HTTPQuicCoroSession::onNewBidirectionalStream(quic::StreamId id) noexcept {\n  XLOG(DBG4) << \"New bidi stream=\" << id << \" sess=\" << *this;\n  resetIdleTimeout();\n  // TODO: downstream only, for now\n  if (isUpstream()) {\n    XLOG(DBG4) << \"Refusing server-init bidi id=\" << id << \" sess=\" << *this;\n    egressResetStream(id, nullptr, HTTPErrorCode::STREAM_CREATION_ERROR);\n    return;\n  }\n  if (!multiCodec_->isStreamIngressEgressAllowed(id)) {\n    XLOG(DBG4) << \"Refusing stream after GOAWAY id=\" << id << \" sess=\" << *this;\n    egressResetStream(id, nullptr, HTTPErrorCode::REQUEST_REJECTED);\n    return;\n  }\n\n  onMessageBegin(id, nullptr);\n  auto& stream = *CHECK_NOTNULL(findStream(id));\n  multiCodec_->addCodec(id);\n  backgroundScope_.add(co_withExecutor(\n      &readExec_,\n      co_withCancellation(stream.cs.ingress.getToken(), readLoop(id))));\n}\n\nvoid HTTPQuicCoroSession::onNewUnidirectionalStream(\n    quic::StreamId id) noexcept {\n  XLOG(DBG4) << \"New uni stream=\" << id << \" sess=\" << *this;\n  resetIdleTimeout();\n  uniStreamDispatcher_.takeTemporaryOwnership(id);\n  quicSocket_->setPeekCallback(id, &uniStreamDispatcher_);\n}\n\nvoid HTTPQuicCoroSession::onStopSending(\n    quic::StreamId id, quic::ApplicationErrorCode /*error*/) noexcept {\n  XLOG(DBG4) << \"onStopSending stream=\" << id << \" sess=\" << *this;\n  // Always reset the stream at the transport.  There are 4 cases:\n  // 1. Stream is not yet egress complete from session\n  // 2. Stream is egress complete from session, but not transport\n  // 3. Stream is egress complete from session and transport - reset is no-op\n  // 4. No stream state - Like 2 or 3, but session has removed stream state\n  egressResetStream(id,\n                    nullptr,\n                    HTTPErrorCode::REQUEST_CANCELLED,\n                    /*fromSource=*/false,\n                    /*bidirectionalReset=*/false);\n}\n\nvoid HTTPQuicCoroSession::onBidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (isUpstream()) {\n    XLOG(DBG4) << \"Got new max number of concurrent streams we can initiate: \"\n               << numStreamsAvailable << \" sess=\" << *this;\n    // Conservatively assume we were at 0\n    onSetMaxInitiatedStreams(/*didSupport=*/false);\n  }\n}\n\nvoid HTTPQuicCoroSession::onConnectionEnd() noexcept {\n  XLOG(DBG4) << \"onConnectionEnd sess=\" << *this;\n  onConnectionError(kHTTPNoError);\n}\n\nvoid HTTPQuicCoroSession::onConnectionError(quic::QuicError error) noexcept {\n  bool noError = false;\n  HTTPErrorCode httpError = HTTPErrorCode::TRANSPORT_READ_ERROR;\n  if (error.code.type() == quic::QuicErrorCode::Type::ApplicationErrorCode) {\n    auto code = (HTTP3::ErrorCode)*error.code.asApplicationErrorCode();\n    noError = (code == HTTP3::ErrorCode::HTTP_NO_ERROR);\n    httpError = noError ? HTTPErrorCode::INTERNAL_ERROR\n                        : HTTP3ErrorCode2HTTPErrorCode(code);\n  }\n  if (noError) {\n    // Delivers compression error when the connection ends with queued headers\n    multiCodec_->getQPACKCodec().encoderStreamEnd();\n    multiCodec_->getQPACKCodec().decoderStreamEnd();\n  }\n  if (!noError || !streams_.empty()) {\n    XLOG(ERR) << \"Connection error type=\" << int(error.code.type())\n              << \" err=\" << error.message << \" httpError=\" << (int)httpError\n              << \" sess=\" << *this;\n  }\n  connectionError(httpError, \"QUIC connection error\");\n  /**\n   * Matching both destructors in QuicClientTransport & QuicServerTransport;\n   * they invoke the private ::closeImpl(drainConnection = false) member fn,\n   * which is most similar to the ::closeNow public api\n   */\n  quicSocket_->closeNow(\n      quic::QuicError{quic::LocalErrorCode::SHUTTING_DOWN, \"shutdown\"});\n  idle_.signal();\n}\n\nfolly::coro::TaskWithExecutor<void> HTTPQuicCoroSession::run() {\n  return co_withExecutor(&readExec_, runImpl());\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::readLoop() noexcept {\n  // Idle loop\n  while (quicSocket_->good() && !connectionError_) {\n    idle_.reset();\n    auto guard = readExec_.acquireGuard();\n    auto res = co_await idle_.timedWait(eventBase_.get(), connReadTimeout_);\n    if (res == TimedBaton::Status::timedout) {\n      initiateDrain();\n    } else if (res == TimedBaton::Status::cancelled) {\n      dropConnection(\"Connection dropped (cancel)\");\n    }\n  }\n  XLOG(DBG6) << \"Idle loop complete sess=\" << *this;\n  multiCodec_->getQPACKCodec().encoderStreamEnd();\n  multiCodec_->getQPACKCodec().decoderStreamEnd();\n  writeEvent_.signal();\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::runImpl() {\n  backgroundScope_.add(co_withExecutor(&writeExec_, writeLoop()));\n  co_await readLoop();\n  co_await backgroundScope_.joinAsync();\n  XLOG(DBG6) << \"Background scope joined sess=\" << *this;\n  co_await co_withCancellation(/*cancelToken=*/{},\n                               waitForAllStreams()); // uncancellable\n  XLOG(DBG6) << \"All streams done sess=\" << *this;\n  // already uncancellable\n  // expects a post outside of evb (i.e. our readExec_ will trip a check here)\n  co_await co_withExecutor(eventBase_, zeroRefs()).startInlineUnsafe();\n  XLOG(DBG6) << \"terminating run sess=\" << *this;\n  destroy();\n}\n\nvoid HTTPQuicCoroSession::resetIdleTimeout() {\n  idle_.signal();\n}\n\nvoid HTTPQuicCoroSession::rejectStream(quic::StreamId id) {\n  quicSocket_->stopSending(\n      id,\n      (quic::ApplicationErrorCode)HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  quicSocket_->setPeekCallback(id, nullptr);\n  // when adding bidi, invoke resetStream too\n}\n\nvoid HTTPQuicCoroSession::dispatchControlStream(\n    quic::StreamId id,\n    hq::UnidirectionalStreamType streamType,\n    size_t toConsume) {\n  hq::HQUnidirectionalCodec* controlCodec{nullptr};\n  bool isQPACKEncoder = false;\n  switch (streamType) {\n    case hq::UnidirectionalStreamType::CONTROL:\n      controlCodec = multiCodec_;\n      break;\n    case hq::UnidirectionalStreamType::QPACK_ENCODER:\n      controlCodec = &qpackEncoderCodec_;\n      isQPACKEncoder = true;\n      break;\n    case hq::UnidirectionalStreamType::QPACK_DECODER:\n      controlCodec = &qpackDecoderCodec_;\n      break;\n    default:\n      XLOG(FATAL) << \"Invalid type=\" << streamType;\n  }\n  XLOG(DBG4) << \"Dispatching uni stream=\" << id << \" sess=\" << *this;\n  quicSocket_->consume(id, toConsume);\n  quicSocket_->setPeekCallback(id, nullptr);\n  co_withExecutor(&readExec_,\n                  readControlStream(id, *controlCodec, isQPACKEncoder))\n      .start();\n}\n\nvoid HTTPQuicCoroSession::dispatchPushStream(quic::StreamId id,\n                                             hq::PushId pushId,\n                                             size_t toConsume) {\n  // ingress push streams are not allowed on the server\n  XLOG_IF(WARNING, isDownstream())\n      << \"PUSH stream received on server, id=\" << id << \" sess=\" << *this;\n  quicSocket_->consume(id, toConsume);\n  quicSocket_->setPeekCallback(id, nullptr);\n  dispatchPushStream(id, pushId);\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::readControlStream(\n    quic::StreamId id, hq::HQUnidirectionalCodec& codec, bool isQPACKEncoder) {\n  class ControlRCB : public QuicReadCallback {\n   public:\n    ControlRCB(HTTPQuicCoroSession& session,\n               hq::HQUnidirectionalCodec& codec,\n               bool isQPACKEncoder)\n        : QuicReadCallback(session),\n          controlCodec_(codec),\n          isQPACKEncoder_(isQPACKEncoder) {\n    }\n    void readAvailable(quic::StreamId id) noexcept override {\n      XLOG(DBG4) << \"Read available on control stream id=\" << id\n                 << \" sess=\" << session_;\n      session_.resetIdleTimeout();\n      auto buf = session_.quicSocket_->read(id, 0);\n      if (buf.hasError()) {\n        XLOG(ERR)\n            << \"Error reading control stream err=\" << uint64_t(buf.error())\n            << \"  id=\" << id << \" sess=\" << session_;\n        // TODO: Is the stream state reset in this case?\n        baton_.signal();\n        return;\n      }\n\n      if (buf->first) {\n        session_.deliverLifecycleEvent(&LifecycleObserver::onRead,\n                                       session_,\n                                       buf->first->computeChainDataLength(),\n                                       id);\n        input_.append(std::move(buf->first));\n        if (!input_.empty()) {\n          XLOG(DBG4) << \"Parsing len=\" << input_.chainLength() << \" id=\" << id\n                     << \" sess=\" << session_;\n          input_.append(controlCodec_.onUnidirectionalIngress(input_.move()));\n          if (isQPACKEncoder_) {\n            // Trigger an iteration of the controlStreamWriteLoop.  This will\n            // check for unacknowledged inserts and emit an InsertCountInc\n            session_.writeEvent_.signal();\n          }\n        }\n      }\n      if (buf->second) {\n        XLOG(DBG4) << \"Parsing end of stream id=\" << id << \" sess=\" << session_;\n        controlCodec_.onUnidirectionalIngressEOF();\n        baton_.signal();\n      }\n    }\n    bool noError(quic::QuicErrorCode error) {\n      return (error.type() == quic::QuicErrorCode::Type::LocalErrorCode &&\n              (*error.asLocalErrorCode() == quic::LocalErrorCode::NO_ERROR ||\n               *error.asLocalErrorCode() ==\n                   quic::LocalErrorCode::IDLE_TIMEOUT)) ||\n             (error.type() == quic::QuicErrorCode::Type::ApplicationErrorCode &&\n              (HTTP3::ErrorCode(*error.asApplicationErrorCode()) ==\n                   HTTP3::ErrorCode::HTTP_NO_ERROR ||\n               uint16_t(*error.asApplicationErrorCode()) ==\n                   uint16_t(quic::GenericApplicationErrorCode::NO_ERROR))) ||\n             (error.type() == quic::QuicErrorCode::Type::TransportErrorCode &&\n              *error.asTransportErrorCode() ==\n                  quic::TransportErrorCode::NO_ERROR);\n    }\n    void readError(quic::StreamId id, quic::QuicError error) noexcept override {\n      if (!noError(error.code)) {\n        XLOG(ERR) << \"Error reading control stream type=\"\n                  << uint64_t(error.code.type()) << \" msg=\" << error.message\n                  << \", id=\" << id << \" sess=\" << session_;\n        session_.connectionError(HTTPErrorCode::CLOSED_CRITICAL_STREAM,\n                                 \"control stream read error\");\n      }\n      baton_.signal();\n    }\n\n    hq::HQUnidirectionalCodec& controlCodec_;\n    bool isQPACKEncoder_{false};\n  };\n\n  XLOG(DBG4) << __func__ << \" started id=\" << id << \" sess=\" << *this;\n  if (quicSocket_->good()) {\n    quicSocket_->setControlStream(id);\n    ControlRCB controlRcb(*this, codec, isQPACKEncoder);\n    quicSocket_->setReadCallback(id, &controlRcb, std::nullopt);\n    auto res = co_await controlRcb.getBaton().wait();\n    XCHECK(res != TimedBaton::Status::timedout);\n    // Do I need to clear the read callback for this stream?\n  }\n  XLOG(DBG4) << __func__ << \" complete, id=\" << id << \" sess=\" << *this;\n}\n\nvoid HTTPQuicCoroSession::StreamRCB::readAvailable(quic::StreamId id) noexcept {\n  if (inProcessRead_) {\n    return;\n  }\n  XLOG(DBG4) << \"Read available id=\" << id << \" sess=\" << session_;\n  session_.resetIdleTimeout();\n  auto buf = session_.quicSocket_->read(id, 0);\n  if (buf.hasError()) {\n    XLOG(ERR) << \"Error reading stream id=\" << id\n              << quic::toString(buf.error());\n    // TODO: Stream state reset?\n    baton_.signal();\n    return;\n  }\n  if (buf->first) {\n    auto length = buf->first->computeChainDataLength();\n    input_.append(std::move(buf->first));\n    if (length > 0) {\n      session_.deliverLifecycleEvent(\n          &LifecycleObserver::onRead, session_, length, id);\n    }\n  }\n  readEOF_ |= buf->second;\n  processRead(id);\n}\n\nvoid HTTPQuicCoroSession::StreamRCB::processRead(quic::StreamId id) {\n  if (inProcessRead_) {\n    return;\n  }\n  inProcessRead_ = true;\n  auto g = folly::makeGuard([this] { inProcessRead_ = false; });\n  if (!input_.empty()) {\n    XLOG(DBG4) << \"Parsing len=\" << input_.chainLength() << \" id=\" << id\n               << \" sess=\" << session_;\n    if (!session_.multiCodec_->setCurrentStream(id)) {\n      XLOG(DBG3) << \"No codec for stream=\" << id << \" sess=\" << session_;\n      // Stream has already detached, so a stop-sending must be in flight?\n      baton_.signal();\n      return;\n    }\n    auto parsed = session_.codec_->onIngress(*input_.front());\n    input_.trimStart(parsed);\n  }\n  if (input_.empty() && readEOF_) {\n    session_.quicSocket_->setReadCallback(id, nullptr, std::nullopt);\n    XLOG(DBG4) << \"Parsing end of stream id=\" << id << \" sess=\" << session_;\n    readEOF_ = false;\n    if (!session_.multiCodec_->setCurrentStream(id)) {\n      XLOG(DBG3) << \"No codec for stream=\" << id << \" sess=\" << session_;\n      // Stream has already detached, so a stop-sending must be in flight?\n      baton_.signal();\n      return;\n    }\n    session_.codec_->onIngressEOF();\n    baton_.signal();\n    return;\n  }\n  // Check at the end of this function, since the parser can be paused but\n  // we've received all the data from the transport for this stream\n  if (session_.multiCodec_->setCurrentStream(id) &&\n      session_.quicSocket_->good()) {\n    if (session_.multiCodec_->isParserPaused()) {\n      session_.quicSocket_->pauseRead(id);\n      isReadPaused_ = true;\n    } else if (isReadPaused_) {\n      if (!session_.isStreamIngressLimitExceeded(id)) {\n        session_.quicSocket_->resumeRead(id);\n      }\n      isReadPaused_ = false;\n    }\n  }\n}\n\nvoid HTTPQuicCoroSession::StreamRCB::resumeRead(quic::StreamId id) {\n  XLOG(DBG4) << \"Process read from resume id=\" << id << \" sess=\" << session_;\n  processRead(id);\n}\n\nvoid HTTPQuicCoroSession::StreamRCB::readError(quic::StreamId id,\n                                               quic::QuicError error) noexcept {\n  XLOG(DBG3) << \"Error reading stream id=\" << id << \" sess=\" << session_\n             << \" type=\" << uint32_t(error.code.type())\n             << \" err=\" << quic::toString(error.code);\n  HTTPErrorCode httpError = HTTPErrorCode::TRANSPORT_READ_ERROR;\n  if (error.code.type() == quic::QuicErrorCode::Type::ApplicationErrorCode) {\n    auto code = (HTTP3::ErrorCode)*error.code.asApplicationErrorCode();\n    httpError = (code == HTTP3::ErrorCode::HTTP_NO_ERROR)\n                    ? HTTPErrorCode::TRANSPORT_EOF\n                    : HTTP3ErrorCode2HTTPErrorCode(code);\n  } else if (error.code.type() == quic::QuicErrorCode::Type::LocalErrorCode) {\n    auto code = *error.code.asLocalErrorCode();\n    if (code == quic::LocalErrorCode::IDLE_TIMEOUT) {\n      httpError = HTTPErrorCode::TRANSPORT_EOF;\n    }\n  }\n  // TODO: more specific for Local vs. Transport errors?\n  session_.onResetStream(id, httpError);\n  session_.multiCodec_->getQPACKDecoderWriteBuf().append(\n      session_.multiCodec_->getQPACKCodec().encodeCancelStream(id));\n  session_.writeEvent_.signal();\n  session_.quicSocket_->setReadCallback(id, nullptr, std::nullopt);\n  baton_.signal();\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::readLoop(\n    quic::StreamId id) noexcept {\n  if (quicSocket_->good()) {\n    if (!multiCodec_->setCurrentStream(id)) {\n      XLOG(DBG2) << \"readLoop no-op, stream already cancelled id=\" << id\n                 << \" sess=\" << *this;\n      return folly::coro::makeTask(folly::Unit());\n    }\n    auto streamRCB = std::make_shared<StreamRCB>(*this);\n    std::weak_ptr<StreamRCB> weakStreamRCB(streamRCB);\n    multiCodec_->setResumeHook(id, [weakStreamRCB, id] {\n      if (auto lockedStreamRCB = weakStreamRCB.lock()) {\n        lockedStreamRCB->resumeRead(id);\n      }\n    });\n    auto rc = quicSocket_->setReadCallback(id, streamRCB.get(), std::nullopt);\n    if (rc.has_value()) {\n      return readLoopImpl(std::move(streamRCB), id);\n    } else {\n      XLOG(ERR) << rc.error();\n    }\n  }\n  return folly::coro::makeTask(folly::Unit());\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::readLoopImpl(\n    std::shared_ptr<StreamRCB> streamRCB, quic::StreamId id) noexcept {\n  XLOG(DBG4) << __func__ << \" started id=\" << id << \" sess=\" << *this;\n  auto res = co_await streamRCB->getBaton().wait();\n  XCHECK(res != TimedBaton::Status::timedout);\n  quicSocket_->setReadCallback(id, nullptr, std::nullopt);\n  XLOG(DBG4) << __func__ << \" complete id=\" << id << \" sess=\" << *this;\n}\n\nvoid HTTPCoroSession::handleWriteEventTimeout() {\n  // This only requires action if the conn or a stream is out of flow control.\n  // Otherwise, it just means that no one had anything to write for a given\n  // time period.\n  if (isConnectionFlowControlBlocked()) {\n    for (auto& [id, stream] : streams_) {\n      if (!stream->isBodyQueueEmpty()) {\n        XLOG(ERR) << \"Timed out waiting for conn flow control, sess=\" << *this;\n        connectionError(HTTPErrorCode::FLOW_CONTROL_ERROR,\n                        \"Timed out waiting for flow control\");\n        break;\n      }\n    }\n    // If we get here, then we did time out for conn fcw but there's nothing\n    // that needs it.\n  } else {\n    // Check for streams that are out of flow control\n    std::vector<HTTPCodec::StreamID> ids;\n    for (auto& [id, stream] : streams_) {\n      if (isStreamFlowControlBlocked(*stream)) {\n        ids.push_back(id);\n      }\n    }\n    for (auto id : ids) {\n      XLOG(DBG4) << \"Stream id=\" << id\n                 << \" flow control timeout, resetting, sess=\" << *this;\n      egressResetStream(id, nullptr, HTTPErrorCode::FLOW_CONTROL_ERROR);\n    }\n  }\n}\n\nvoid HTTPCoroSession::resetOpenStreams(HTTPErrorCode error,\n                                       std::string_view details) {\n  // Reset all open streams\n  std::vector<StreamState*> streams;\n  for (auto& [_, stream] : streams_) {\n    streams.push_back(stream.get());\n  }\n  for (auto* stream : streams) {\n    // Queue an ingress abort for any stream that hasn't seen EOM yet\n    stream->abortIngress(error, details);\n    // marks egress complete, clears queued egress, checks for detach\n    resetStreamState(*stream, HTTPError(error, \"Connection error\"));\n  }\n}\n\nvoid HTTPUniplexTransportSession::cleanupAfterWriteError(\n    const std::string& msg) {\n  // We terminate the actual writing part of the write loop if the socket\n  // gives a write error, but we still need to cleanup and wait for the streams\n  connectionError(HTTPErrorCode::TRANSPORT_WRITE_ERROR,\n                  folly::to<std::string>(\"write error: \", msg));\n  // Clear any unwritten egress\n  writeBuf_.move();\n}\n\nfolly::coro::Task<void> HTTPCoroSession::waitForAllStreams() {\n  // Wait for all the readers to read the errors out and detach\n  while (!streams_.empty() || pendingSendStreams_ > 0) {\n    writeEvent_.reset();\n    auto status =\n        co_await writeEvent_.timedWait(eventBase_.get(), writeTimeout_);\n    if (status == TimedBaton::Status::timedout) {\n      XLOG(DBG4) << \"Timeout waiting for stream to drain on error, nStreams=\"\n                 << streams_.size() << \" sess=\" << *this;\n    }\n  }\n}\n\nbool HTTPUniplexTransportSession::shouldContinueWriteLooping() const {\n  // We may need to terminate the write loop with open streams - if we need\n  // an EOM to terminate the current message, or a TCP RST\n  bool closeWithOpenStreams =\n      codec_->closeOnEgressComplete() || resetAfterDrainingWrites_;\n  // Continue waiting for write events while the socket is good and:\n  //   1) There is some data to write OR\n  //   2) There is at least one stream and !closeWithOpenStreams OR\n  //   3) New streams can be created (reads are open and codec is reusable) OR\n  //   4) There are requests waiting to start\n  // clang-format off\n  bool continueLoop =\n      (!writeBuf_.empty() ||\n       (!closeWithOpenStreams && !streams_.empty()) ||\n       (!readsClosed_ && codec_->isReusable()) ||\n       pendingSendStreams_ > 0);\n  // clang-format on\n  XLOG(DBG6)\n      << __func__ << \" continueLoop=\" << (continueLoop ? 1 : 0)\n      << \" pendingSendStreams_=\" << pendingSendStreams_\n      << \" closeOnEgressComplete=\" << (codec_->closeOnEgressComplete() ? 1 : 0)\n      << \" resetAfterDrainingWrites_=\" << (resetAfterDrainingWrites_ ? 1 : 0)\n      << \" nStreams=\" << streams_.size()\n      << \" writeBuf_.chainLength()=\" << writeBuf_.chainLength()\n      << \" readsClosed_=\" << (readsClosed_ ? 1 : 0)\n      << \" codec_->isReusable()=\" << (codec_->isReusable() ? 1 : 0)\n      << \" sess=\" << *this;\n\n  return continueLoop;\n}\n\nbool HTTPQuicCoroSession::shouldContinueWriteLooping() const {\n  auto continueLoop =\n      hasControlWrite() || codec_->isReusable() || !streams_.empty();\n  XLOG(DBG6) << __func__ << \" continueLoop=\" << (continueLoop ? 1 : 0)\n             << \" hasControlWrite=\" << hasControlWrite()\n             << \" codec_->isReusable()=\" << (codec_->isReusable() ? 1 : 0)\n             << \" nStreams=\" << streams_.size() << \" sess=\" << *this;\n  return continueLoop;\n}\n\nfolly::coro::Task<void> HTTPUniplexTransportSession::writeLoop() noexcept {\n  XLOG(DBG6) << \"starting writeLoop sess=\" << *this;\n  folly::Optional<std::string> writeError;\n  while (!writeError && shouldContinueWriteLooping()) {\n    if (writeBuf_.empty() &&\n        (writableStreams_.empty() || sendWindow_.getSize() == 0)) {\n      writeEvent_.reset();\n      XLOG(DBG6) << \"Waiting for writeEvent sess=\" << *this;\n      TimedBaton::Status status = TimedBaton::Status::signalled;\n      {\n        auto guard = writeExec_.acquireGuard();\n        status =\n            co_await writeEvent_.timedWait(eventBase_.get(), writeTimeout_);\n      }\n\n      XCHECK(status != TimedBaton::Status::cancelled)\n          << \"writeLoop not cancellable\";\n      if (status == TimedBaton::Status::timedout) {\n        handleWriteEventTimeout();\n        // fall through to check for writes below before terminating loop\n      }\n      XLOG(DBG4) << \"Got writeEvent sess=\" << *this;\n    }\n\n    // Add stream body data, if there's buffer space and flow control\n    if (writeBuf_.chainLength() < kWriteBufLimit && sendWindow_.getSize() > 0 &&\n        !writableStreams_.empty()) {\n      // HEADERS and other control traffic pre-empt the writing of stream\n      // bodies\n      int32_t bufSpace = kWriteBufLimit - writeBuf_.chainLength();\n      auto maxStreamToWrite = std::min(bufSpace, sendWindow_.getSize());\n      XCHECK_GT(maxStreamToWrite, 0);\n      XLOG(DBG4) << \"Egressing stream bodies up to max=\" << maxStreamToWrite\n                 << \" sess=\" << *this;\n      auto written = addStreamBodyDataToWriteBuf(maxStreamToWrite);\n      XLOG(DBG4) << \"Egressed len=\" << written << \" sess=\" << *this;\n    }\n\n    if (!writeBuf_.empty()) {\n      uint64_t writeLength = writeBuf_.chainLength();\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE;\n      auto txAckEvent = byteEventObserver_.nextTxAckEvent();\n      if (txAckEvent) {\n        // If there's a TX or ACK event, we have to split the write on the\n        // event offset, and update the writeFlags.\n        XLOG(DBG5) << \"Split writeBuf_ at \" << txAckEvent->sessionByteOffset;\n        XCHECK_GT(txAckEvent->sessionByteOffset, sessionBytesScheduled_);\n        writeLength = txAckEvent->sessionByteOffset - sessionBytesScheduled_;\n        writeFlags = txAckEvent->writeFlags();\n      }\n      folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n      writeBuf.append(writeBuf_.split(writeLength));\n      sessionBytesScheduled_ += writeLength;\n      XLOG(DBG4) << \"Writing length=\" << writeLength << \" sess=\" << *this;\n      byteEventObserver_.transportWrite(sessionBytesScheduled_);\n      auto result = co_await co_awaitTry(\n          coroTransport_->write(writeBuf, writeTimeout_, writeFlags));\n      if (result.hasException()) {\n        XLOG(DBG4) << \"Write error, err=\" << result.exception()\n                   << \" sess=\" << *this;\n        if (txAckEvent) {\n          txAckEvent->cancel(HTTPError(HTTPErrorCode::TRANSPORT_WRITE_ERROR,\n                                       result.exception().what().c_str()));\n        }\n        writeError.emplace(result.exception().what());\n      } else {\n        XLOG(DBG4) << \"Wrote length=\" << writeLength << \" sess=\" << *this;\n        deliverLifecycleEvent(&LifecycleObserver::onWrite, *this, writeLength);\n        byteEventObserver_.transportWriteComplete(sessionBytesScheduled_,\n                                                  std::move(txAckEvent));\n      }\n    }\n  }\n\n  if (writeError) {\n    cleanupAfterWriteError(*writeError);\n  }\n\n  XLOG(DBG6)\n      << \"writeLoop terminating, closing with \"\n      << ((resetAfterDrainingWrites_ || writeError) ? \"error\" : \"no error\")\n      << \" sess=\" << *this;\n  if (resetAfterDrainingWrites_ || writeError) {\n    coroTransport_->closeWithReset();\n  } else {\n    coroTransport_->shutdownWrite();\n  }\n  drainStarted();\n  if (!streams_.empty()) {\n    co_await waitForAllStreams();\n  }\n  co_await byteEventObserver_.zeroRefs();\n  writesFinished_.post();\n  readCancellationSource_.requestCancellation();\n}\n\nvoid HTTPUniplexTransportSession::maybeEnableByteEvents() {\n  if (!byteEventObserver_.isRegistered()) {\n    auto transport = coroTransport_->getTransport();\n    if (transport) {\n      auto sock = transport->getUnderlyingTransport<folly::AsyncSocket>();\n      if (sock) {\n        sock->addLifecycleObserver(&byteEventObserver_);\n      }\n    }\n  }\n}\n\nbool HTTPQuicCoroSession::hasControlWrite() const {\n  return !writeBuf_.empty() ||\n         !multiCodec_->getQPACKEncoderWriteBuf().empty() ||\n         !multiCodec_->getQPACKDecoderWriteBuf().empty();\n}\n\nfolly::coro::Task<void> HTTPQuicCoroSession::writeLoop() noexcept {\n  while (shouldContinueWriteLooping()) {\n    if (!hasControlWrite() && writableStreams_.empty()) {\n      writeEvent_.reset();\n      XLOG(DBG6) << \"Waiting for writeEvent sess=\" << *this;\n      auto guard = writeExec_.acquireGuard();\n      auto res =\n          co_await writeEvent_.timedWait(eventBase_.get(), writeTimeout_);\n      if (res == TimedBaton::Status::cancelled) {\n        break;\n      }\n      if (res == TimedBaton::Status::timedout) {\n        XLOG(DBG4) << \"writeEvent timeout sess=\" << *this;\n        handleWriteEventTimeout();\n      }\n      XLOG_IF(DBG4, res == TimedBaton::Status::signalled)\n          << \"Got writeEvent sess=\" << *this;\n    }\n    if (!quicSocket_->good()) {\n      // session already ended\n      co_return;\n    }\n\n    // write control stream data first\n    writeControlStream(controlStreamID_, writeBuf_);\n    writeControlStream(qpackEncoderStreamID_,\n                       multiCodec_->getQPACKEncoderWriteBuf());\n    multiCodec_->encodeInsertCountIncrement();\n    writeControlStream(qpackDecoderStreamID_,\n                       multiCodec_->getQPACKDecoderWriteBuf());\n\n    XLOG(DBG4) << \"Egressing stream bodies sess=\" << *this;\n    while (!writableStreams_.empty()) {\n      auto id = writableStreams_.getNextScheduledID(std::nullopt);\n      auto streamId = id.asStreamID();\n      auto stream = findStream(streamId);\n      if (!stream) {\n        XLOG(ERR) << \"Writable stream missing from streams_ id=\" << streamId\n                  << \" sess=\" << *this;\n        writableStreams_.erase(id);\n        continue;\n      }\n\n      auto maybeFC = quicSocket_->getMaxWritableOnStream(streamId);\n      if (!maybeFC || *maybeFC == 0) {\n        // blocked on flow control, attach cb\n        quicSocket_->notifyPendingWriteOnStream(streamId, this);\n        writableStreams_.erase(id);\n        continue;\n      }\n\n      bool eom = stream->pendingEgressEOM();\n      bool bodyQueueEmpty = stream->isBodyQueueEmpty();\n      uint64_t maxSend = *maybeFC;\n      uint64_t bytesWritten = 0;\n      std::vector<QuicWriteLoopByteEvent> byteEvents;\n      while (!eom && !bodyQueueEmpty) {\n        // exit loop if we egress more than maxSend\n        if (bytesWritten >= maxSend) {\n          break;\n        }\n        HTTPBodyEvent bodyEvent =\n            stream->nextEgressEvent(maxSend - bytesWritten).first;\n        folly::Optional<HTTPByteEvent::FieldSectionInfo> fieldSectionInfo;\n        bodyQueueEmpty = stream->isBodyQueueEmpty();\n        switch (bodyEvent.eventType) {\n          case HTTPBodyEvent::BODY: {\n            if (!bodyEvent.event.body.empty()) {\n              SESS_STATS(\n                  recordPendingBufferedWriteBytes,\n                  -static_cast<int64_t>(bodyEvent.event.body.chainLength()));\n\n              bytesWritten += codec_->generateBody(stream->getWriteBuf(),\n                                                   stream->getID(),\n                                                   bodyEvent.event.body.move(),\n                                                   HTTPCodec::NoPadding,\n                                                   bodyEvent.eom);\n            }\n            break;\n          }\n          case HTTPBodyEvent::UPGRADE:\n            // TODO\n            break;\n          case HTTPBodyEvent::SUSPEND:\n            XCHECK(false) << \"Bad event\";\n            break;\n          case HTTPBodyEvent::DATAGRAM: {\n            XCHECK(!bodyEvent.eom) << \"DATAGRAM can't be EOM\";\n            if (bodyEvent.event.datagram) {\n              sendDatagram(stream->getID(),\n                           std::move(bodyEvent.event.datagram));\n            }\n            break;\n          }\n          case HTTPBodyEvent::TRAILERS: {\n            XLOG(DBG4) << \"Sending trailers sess=\" << *this\n                       << \" id=\" << stream->getID();\n            auto sz = codec_->generateTrailers(stream->getWriteBuf(),\n                                               stream->getID(),\n                                               *bodyEvent.event.trailers);\n            bytesWritten += sz;\n            fieldSectionInfo.emplace<HTTPByteEvent::FieldSectionInfo>(\n                {HTTPByteEvent::FieldSectionInfo::Type::TRAILERS,\n                 true,\n                 {uint32_t(sz), uint32_t(sz), 0}});\n            XCHECK(bodyEvent.eom) << \"Trailers always EOM\";\n            break;\n          }\n          case HTTPBodyEvent::PUSH_PROMISE: {\n            auto sz = addPushPromiseToWriteBuf(*stream, bodyEvent);\n            bytesWritten += sz.compressed;\n            fieldSectionInfo.emplace<HTTPByteEvent::FieldSectionInfo>(\n                {HTTPByteEvent::FieldSectionInfo::Type::PUSH_PROMISE,\n                 true,\n                 sz});\n            break;\n          }\n          case HTTPBodyEvent::PADDING: {\n            bytesWritten +=\n                codec_->generatePadding(stream->getWriteBuf(),\n                                        stream->getID(),\n                                        bodyEvent.event.paddingSize);\n            break;\n          }\n        }\n        eom = bodyEvent.eom;\n        if (!bodyEvent.byteEventRegistrations.empty()) {\n          byteEvents.emplace_back(std::move(bodyEvent.byteEventRegistrations),\n                                  std::move(fieldSectionInfo),\n                                  stream->observedBodyLength(),\n                                  stream->getWriteBuf().chainLength());\n        }\n        stream->onWindowUpdate(bytesWritten); // simulate window update\n      }\n      XLOG(DBG4) << \"Egressed len=\" << bytesWritten << \" id=\" << stream->getID()\n                 << \" sess=\" << *this;\n\n      if (bodyQueueEmpty) {\n        writableStreams_.erase(id);\n      }\n\n      auto streamByteOffset =\n          quicSocket_->getStreamWriteOffset(stream->getID());\n      if (streamByteOffset) {\n        for (auto& byteEvent : byteEvents) {\n          registerByteEvents(stream->getID(),\n                             *streamByteOffset + byteEvent.eventOffset,\n                             byteEvent.fieldSectionInfo,\n                             byteEvent.bodyOffset,\n                             std::move(byteEvent.byteEventRegistrations),\n                             eom && &byteEvent == &byteEvents.back());\n        }\n      } // else the registrations will be implicitly canceled with no error\n      if (handleWrite(stream->getID(), stream->getWriteBuf(), eom) && eom) {\n        stream->markEgressComplete();\n        egressFinished(*stream);\n      } // handleWrite fails -> connectionError, loop terminates\n    }\n  }\n  if (quicSocket_->good()) {\n    XLOG(DBG4) << \"Closing QuicSocket from writeLoop with \"\n               << ((connectionError_) ? \"error\" : \"no error\")\n               << \" sess=\" << *this;\n    if (connectionError_) {\n      quicSocket_->close(connectionError_);\n    } else {\n      // normal close\n      registerControlDeliveryCallback(controlStreamID_);\n      registerControlDeliveryCallback(qpackEncoderStreamID_);\n      registerControlDeliveryCallback(qpackDecoderStreamID_);\n      XLOG(DBG4) << \"Waiting for all control data to be delivered count=\"\n                 << DeliveryCallback::kDeliveryCallbackTimeout.count()\n                 << \" sess=\" << *this;\n      co_await deliveryCallback_.zeroRefs(eventBase_.get());\n      XLOG(DBG4) << \"Waiting for outstanding byte events\";\n      co_await byteEventRefcount_.zeroRefs();\n      quicSocket_->close(kHTTPNoError);\n    }\n    XCHECK_EQ(byteEventRefcount_.count(), 0u); // either we waited or error'd\n    idle_.signal();\n    XLOG(DBG6) << __func__ << \" completed\";\n  }\n}\n\nvoid HTTPQuicCoroSession::writeControlStream(quic::StreamId id,\n                                             folly::IOBufQueue& writeBuf) {\n  if (id == quic::kInvalidStreamId) {\n    // This stream failed to create, clear the write buffer\n    writeBuf_.move();\n  } else if (!writeBuf.empty()) {\n    XLOG(DBG4) << \"Writing len=\" << writeBuf.chainLength()\n               << \" on control stream=\" << id << \" sess=\" << *this;\n    if (!handleWrite(id, writeBuf, false)) {\n      connectionError(HTTPErrorCode::CLOSED_CRITICAL_STREAM,\n                      \"Write failed on control stream\");\n    }\n  }\n}\n\nvoid HTTPQuicCoroSession::registerControlDeliveryCallback(quic::StreamId id) {\n  if (id == quic::kInvalidStreamId) {\n    return;\n  }\n  auto writeOffset = quicSocket_->getStreamWriteOffset(id);\n  auto writeBufferedBytes = quicSocket_->getStreamWriteBufferedBytes(id);\n  if (writeOffset.hasError() || writeBufferedBytes.hasError()) {\n    return;\n  }\n  auto totalStreamLength = *writeOffset + *writeBufferedBytes;\n  if (totalStreamLength > 0) {\n    // calls incRef from onByteEventRegistered, if successful\n    quicSocket_->registerByteEventCallback(quic::ByteEvent::Type::ACK,\n                                           id,\n                                           totalStreamLength - 1,\n                                           &deliveryCallback_);\n  }\n}\n\nsize_t HTTPUniplexTransportSession::addStreamBodyDataToWriteBuf(uint32_t max) {\n  // Precondition, max is at most connection flow control\n  size_t bytesWritten = 0;\n  size_t fcBytesWritten = 0;\n\n  while (!writableStreams_.empty() && bytesWritten < max) {\n    auto id = writableStreams_.getNextScheduledID(std::nullopt);\n    auto streamId = id.asStreamID();\n    auto stream = findStream(streamId);\n    if (!stream) {\n      XLOG(ERR) << \"Writable stream missing from streams_ id=\" << streamId\n                << \" sess=\" << *this;\n      writableStreams_.erase(id);\n      continue;\n    }\n    bool flowControlBlocked = false;\n    bool bodyQueueEmpty = false;\n    bool eom = false;\n    do {\n      XCHECK_GT(max, fcBytesWritten);\n      HTTPBodyEvent bodyEvent;\n      std::tie(bodyEvent, flowControlBlocked) =\n          stream->nextEgressEvent(max - fcBytesWritten);\n      bodyQueueEmpty = stream->isBodyQueueEmpty();\n      eom = bodyEvent.eom;\n      folly::Optional<HTTPByteEvent::FieldSectionInfo> fieldSectionInfo;\n      switch (bodyEvent.eventType) {\n        case HTTPBodyEvent::BODY: {\n          if (flowControlBlocked) {\n            SESS_STATS(recordTransactionStalled);\n            break;\n          }\n          auto length = bodyEvent.event.body.chainLength();\n          if (!sendWindow_.reserve(length)) {\n            XLOG(DFATAL) << \"Underflow connection send flow control\";\n            // In opt builds, continue and send anyways, the peer will close\n          }\n          // Simulate window updates for H1 if needed\n          if (!codec_->supportsSessionFlowControl()) {\n            onWindowUpdate(0, length);\n          }\n          if (!codec_->supportsStreamFlowControl()) {\n            onWindowUpdate(streamId, length);\n          }\n          if (sendWindow_.getSize() == 0) {\n            SESS_STATS(recordSessionStalled);\n            deliverLifecycleEvent(&LifecycleObserver::onFlowControlWindowClosed,\n                                  *this);\n          }\n          if (length == 0) {\n            XCHECK(bodyEvent.eom);\n            auto eomBytes = codec_->generateEOM(writeBuf_, streamId);\n            bytesWritten += eomBytes;\n            stream->addToStreamOffset(eomBytes);\n          } else {\n            auto genBytes = codec_->generateBody(writeBuf_,\n                                                 streamId,\n                                                 bodyEvent.event.body.move(),\n                                                 HTTPCodec::NoPadding,\n                                                 bodyEvent.eom);\n            XCHECK_GT(genBytes, 0ul);\n            fcBytesWritten += length;\n            bytesWritten += genBytes;\n            stream->addToStreamOffset(genBytes);\n          }\n          SESS_STATS(recordPendingBufferedWriteBytes,\n                     -static_cast<int64_t>(length));\n          break;\n        }\n        case HTTPBodyEvent::DATAGRAM:\n          // Drop H1/H2 datagram\n          // TODO: serialize it as a capsule in the body\n          break;\n        case HTTPBodyEvent::UPGRADE:\n          // TODO\n          break;\n        case HTTPBodyEvent::SUSPEND:\n          XCHECK(false) << \"Bad event\";\n          break;\n        case HTTPBodyEvent::TRAILERS: {\n          XLOG(DBG4) << \"Sending trailers sess=\" << *this << \" id=\" << streamId;\n          auto sz = codec_->generateTrailers(\n              writeBuf_, streamId, *bodyEvent.event.trailers);\n          bytesWritten += sz;\n          stream->addToStreamOffset(sz);\n          // Compression info not available for trailers\n          fieldSectionInfo.emplace<HTTPByteEvent::FieldSectionInfo>(\n              {HTTPByteEvent::FieldSectionInfo::Type::TRAILERS,\n               true,\n               {uint32_t(sz), uint32_t(sz), 0}});\n          XCHECK(bodyEvent.eom) << \"Trailers always EOM\";\n          break;\n        }\n        case HTTPBodyEvent::PADDING: {\n          size_t genBytes = codec_->generatePadding(\n              writeBuf_, streamId, bodyEvent.event.paddingSize);\n          bytesWritten += genBytes;\n          stream->addToStreamOffset(genBytes);\n          break;\n        }\n        case HTTPBodyEvent::PUSH_PROMISE: {\n          auto sz = addPushPromiseToWriteBuf(*stream, bodyEvent);\n          bytesWritten += sz.compressed;\n          fieldSectionInfo.emplace<HTTPByteEvent::FieldSectionInfo>(\n              {HTTPByteEvent::FieldSectionInfo::Type::PUSH_PROMISE, true, sz});\n          stream->addToStreamOffset(sz.compressed);\n          break;\n        }\n      }\n      registerByteEvents(streamId,\n                         stream->getStreamOffset(),\n                         fieldSectionInfo,\n                         stream->observedBodyLength(),\n                         std::move(bodyEvent.byteEventRegistrations),\n                         eom);\n    } while (!bodyQueueEmpty && bytesWritten < max && !flowControlBlocked);\n    if (bodyQueueEmpty || flowControlBlocked) {\n      // The stream ran out of events/flow control, erase from writableStreams_\n      XLOG(DBG4)\n          << \"Ran out of queued events or flow control for stream: blocked=\"\n          << uint32_t(flowControlBlocked) << \" eom=\" << uint32_t(eom)\n          << \" id=\" << streamId << \" sess=\" << *this;\n      writableStreams_.erase(id);\n      if (eom) {\n        egressFinished(*stream);\n      }\n    }\n  }\n  return bytesWritten;\n}\n\nfolly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>, ErrorCode>\nHTTPUniplexTransportSession::createEgressPushStream() {\n  auto pushStreamID = codec_->createStream();\n  std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID> res{pushStreamID,\n                                                          pushStreamID};\n  return res;\n}\n\nfolly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>, ErrorCode>\nHTTPQuicCoroSession::createEgressPushStream() {\n  auto pushStreamID = quicSocket_->createUnidirectionalStream();\n  if (pushStreamID.hasError()) {\n    XLOG(ERR) << \"Failed to create a uni stream for push sess=\" << *this;\n    return folly::makeUnexpected(ErrorCode::PROTOCOL_ERROR);\n  }\n  auto pushID = multiCodec_->nextPushID();\n  multiCodec_->addCodec(*pushStreamID);\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  if (hq::writeStreamPreface(writeBuf,\n                             uint64_t(hq::UnidirectionalStreamType::PUSH)) &&\n      hq::writeStreamPreface(writeBuf, pushID) &&\n      handleWrite(*pushStreamID, writeBuf, false)) {\n    std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID> res{pushID,\n                                                            *pushStreamID};\n    return res;\n  }\n  return folly::makeUnexpected(ErrorCode::PROTOCOL_ERROR);\n}\n\nHTTPHeaderSize HTTPCoroSession::addPushPromiseToWriteBuf(\n    StreamState& stream, HTTPBodyEvent& bodyEvent) {\n  XCHECK(isDownstream());\n  XLOG(DBG4) << \"Sending push promise sess=\" << *this\n             << \" id=\" << stream.getID();\n  if (!codec_->supportsPushTransactions()) {\n    XLOG(WARNING) << \"Ignoring push because peer does not support push\";\n    return {0, 0, 0};\n  }\n  if (!supportsMoreTransactions()) {\n    // TODO: technically you can send unlimited PUSH_PROMISES, but\n    // you need to throttle the streams themselves, but we just\n    // throttle the promises.\n    XLOG(ERR) << \"Exceeded outgoing stream limit sess=\" << *this;\n    return {0, 0, 0};\n  }\n  auto res = createEgressPushStream();\n  if (res.hasError()) {\n    return {0, 0, 0};\n  }\n  auto pushID = res->first;\n  auto pushStreamID = res->second;\n\n  auto& pushStream = createNewStream(pushStreamID);\n  pushStream.parent = stream.getID();\n  pushStream.currentPushID = pushID;\n  pushStream.streamSource.markEgressOnly();\n  pushStream.startEgressCoro();\n  numPushStreams_++;\n\n  HTTPHeaderSize size;\n  codec_->generatePushPromise(stream.getWriteBuf(),\n                              pushID,\n                              *bodyEvent.event.push.promise,\n                              stream.getID(),\n                              bodyEvent.eom,\n                              &size);\n\n  // A little strange to start a co-routine from the egress path\n  XLOG(DBG4) << \"Starting egress push readResponse for id=\"\n             << pushStream.getID();\n  co_withExecutor(eventBase_,\n                  co_withCancellation(\n                      pushStream.cs.egress.getToken(),\n                      readResponse(pushStream,\n                                   folly::coro::makeTask<HTTPSourceHolder>(\n                                       bodyEvent.event.push.movePushSource()))))\n      .start();\n  return size;\n}\n\nvoid HTTPCoroSession::decrementPushStreamCount(const StreamState& stream,\n                                               bool eomMarkedEgressComplete) {\n  if (!stream.parent) {\n    return;\n  }\n  // following this call, the state needs to transition\n  // upstream needs to go to EOM seen\n  // downstream needs to go to egress complete\n  if ((isUpstream() && !stream.streamSource.isEOMSeen()) ||\n      (isDownstream() &&\n       (!stream.isEgressComplete() || eomMarkedEgressComplete))) {\n    XCHECK_GT(numPushStreams_, 0UL);\n    numPushStreams_--;\n  }\n}\n\nbool HTTPCoroSession::isStreamFlowControlBlocked(StreamState& stream) {\n  return !stream.isEgressComplete() && stream.isFlowControlBlocked();\n}\n\nbool HTTPQuicCoroSession::isStreamFlowControlBlocked(StreamState& stream) {\n  auto streamFC = quicSocket_->getStreamFlowControl(stream.getID());\n  return streamFC && streamFC->sendWindowAvailable == 0;\n}\n\nbool HTTPCoroSession::ingressLimitExceeded(const StreamState& stream) const {\n  return stream.streamSource.bodyBytesBuffered() >= readBufferLimit_;\n}\n\nbool HTTPCoroSession::shouldResumeIngress(const StreamState& stream,\n                                          uint64_t delta) const {\n  bool wasIngressLimited =\n      (stream.streamSource.bodyBytesBuffered() + delta) >= readBufferLimit_;\n  return wasIngressLimited && !ingressLimitExceeded(stream);\n}\n\nbool HTTPCoroSession::isDetachable() const {\n  constexpr auto kDetachable = detail::DetachableExecutor::Detachable;\n  return isUpstream() && !isDraining() && numStreams() == 0 &&\n         readExec_.getState() == kDetachable &&\n         writeExec_.getState() == kDetachable;\n}\n\nvoid HTTPCoroSession::detachEvb() {\n  XLOG(DBG4) << __func__;\n  XCHECK(isDetachable());\n  XCHECK(eventBase_ && eventBase_->isInEventBaseThread());\n  eventBase_.reset();\n  readExec_.detachEvb();\n  writeExec_.detachEvb();\n  writeEvent_.detach();\n}\n\nvoid HTTPCoroSession::attachEvb(folly::EventBase* evb) {\n  XLOG(DBG4) << __func__;\n  XCHECK(evb->inRunningEventBaseThread());\n  eventBase_ = evb;\n  readExec_.attachEvb(evb);\n  writeExec_.attachEvb(evb);\n  writeEvent_.signal();\n}\n\nbool HTTPUniplexTransportSession::isDetachable() const {\n  return HTTPCoroSession::isDetachable() && shouldContinueReadLooping() &&\n         shouldContinueWriteLooping();\n}\n\nvoid HTTPUniplexTransportSession::detachEvb() {\n  HTTPCoroSession::detachEvb();\n  coroTransport_->detachEventBase();\n}\n\nvoid HTTPUniplexTransportSession::attachEvb(folly::EventBase* evb) {\n  HTTPCoroSession::attachEvb(evb);\n  coroTransport_->attachEventBase(evb);\n}\n\nbool HTTPQuicCoroSession::isDetachable() const {\n  return HTTPCoroSession::isDetachable() && quicSocket_->good() &&\n         uniStreamDispatcher_.numberOfStreams() == 0;\n}\n\nvoid HTTPQuicCoroSession::detachEvb() {\n  HTTPCoroSession::detachEvb();\n  quicSocket_->detachEventBase();\n  idle_.detach();\n}\n\nvoid HTTPQuicCoroSession::attachEvb(folly::EventBase* evb) {\n  HTTPCoroSession::attachEvb(evb);\n  quicSocket_->attachEventBase(std::make_shared<quic::FollyQuicEventBase>(evb));\n  idle_.signal();\n}\n\nvoid HTTPCoroSession::describe(std::ostream& os) const {\n  if (isDownstream()) {\n    os << \"downstream=\" << peerAddr_ << \", \" << localAddr_ << \"=local\"\n       << \", proto=\" << getCodecProtocolString(codec_->getProtocol());\n  } else {\n    os << \", local=\" << localAddr_ << \", \" << peerAddr_ << \"=upstream\"\n       << \", proto=\" << getCodecProtocolString(codec_->getProtocol());\n  }\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPCoroSession& session) {\n  session.describe(os);\n  return os;\n}\n\n// WebTransport related functions below\nnamespace {\n\nconstexpr std::string_view kWtNotSupported = \"WebTransport not supported\";\nconstexpr std::string_view kInvalidWtReq = \"Invalid WebTransport request\";\nusing WtReqResult = HTTPCoroSession::WtReqResult;\n\nfolly::coro::Task<WtReqResult> makeInternalEx(std::string_view err) {\n  return folly::coro::makeErrorTask<WtReqResult>(\n      HTTPError{HTTPErrorCode::INTERNAL_ERROR, std::string(err)});\n}\n\n}; // namespace\n\n/**\n * Common logic that can be used by derived classes to validate both that\n * WebTransport is supported and request is valid. Although this function is a\n * Task (for derived classes to override as those will have asynchrony), it is\n * sychronously resolved and should only be checked for errors via co_awaitTry()\n */\nfolly::coro::Task<WtReqResult> HTTPCoroSession::sendWtReq(\n    RequestReservation reservation,\n    const HTTPMessage& msg,\n    std::unique_ptr<WebTransportHandler>) noexcept {\n  // XLOG_IF(FATAL, !folly::kIsDebug) << \"wt wip\"; // crash in non-debug modes\n  if (!reservation.fromSession(this)) {\n    return makeInternalEx(\"Invalid reservation\");\n  }\n\n  const bool wtEnabled = ::proxygen::detail::supportsWt(\n      {codec_->getIngressSettings(), codec_->getEgressSettings()});\n  const bool validWtReq = HTTPWebTransport::isConnectMessage(msg);\n  if (!(wtEnabled && validWtReq)) {\n    auto err = !validWtReq ? kInvalidWtReq : kWtNotSupported;\n    XLOG(DBG6) << __func__ << \" err=\" << err << \"; sess=\" << *this;\n    return makeInternalEx(err);\n  }\n\n  // valid wt req\n  return folly::coro::makeTask<WtReqResult>({});\n}\n\nfolly::coro::Task<WtReqResult> HTTPUniplexTransportSession::sendWtReq(\n    RequestReservation reservation,\n    const HTTPMessage& msg,\n    std::unique_ptr<WebTransportHandler> wtHandler) noexcept {\n  auto valid = co_await co_awaitTry(\n      HTTPCoroSession::sendWtReq(std::move(reservation), msg, nullptr));\n  if (valid.hasException()) {\n    co_return valid;\n  }\n\n  // valid wt req\n  WtHelper wtHelper{*this};\n  auto egressSource = wtHelper.createEgressSource();\n  egressSource->validateHeadersAndSkip(msg);\n\n  auto res = sendRequestImpl(/*headers=*/msg,\n                             /*egressHeadersFn=*/nullptr,\n                             /*byteEventRegistrations=*/{},\n                             /*bodySource=*/egressSource.get());\n  XCHECK(res.hasValue()); // http/2 should always succeed here\n\n  WtReqResult ret;\n  do {\n    auto ev = co_await co_nothrow(res->readHeaderEvent());\n    ret.resp = std::move(ev.headers);\n  } while (!ret.resp->isFinal());\n\n  if (!ret.resp->is2xxResponse()) {\n    co_return ret; // failed upgrade => ret.wt == nullptr\n  }\n\n  // wt upgrade successful\n  auto transport = wtHelper.createHttpSourceTransport(std::move(egressSource),\n                                                      std::move(*res));\n  ret.wt = wtHelper.createWtSession(std::move(transport), std::move(wtHandler));\n  co_return ret;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPCoroSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPBodyEventQueue.h\"\n#include \"proxygen/lib/http/coro/HTTPByteEventHelpers.h\"\n#include \"proxygen/lib/http/coro/HTTPSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n#include \"proxygen/lib/http/coro/util/AwaitableKeepAlive.h\"\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n#include \"proxygen/lib/http/coro/util/DetachableExecutor.h\"\n#include \"proxygen/lib/http/coro/util/Refcount.h\"\n#include \"proxygen/lib/http/coro/util/WindowContainer.h\"\n#include <folly/container/EvictingCacheMap.h>\n#include <folly/container/F14Map.h>\n#include <folly/coro/AsyncScope.h>\n#include <folly/coro/Task.h>\n#include <folly/io/coro/Transport.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/HQMultiCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/codec/QPACKDecoderCodec.h>\n#include <proxygen/lib/http/codec/QPACKEncoderCodec.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n#include <proxygen/lib/http/session/HQStreamDispatcher.h>\n#include <proxygen/lib/http/session/QuicProtocolInfo.h>\n#include <proxygen/lib/utils/UtilInl.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/common/Optional.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n#include <wangle/acceptor/ManagedConnection.h>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\nclass HTTPSessionStats;\nclass WebTransport;\nclass WebTransportHandler;\n} // namespace proxygen\n\nnamespace proxygen::coro {\n\nclass HTTPCoroSession;\nclass LifecycleObserver;\n\nclass HTTPSessionContext\n    : public detail::EnableAwaitableKeepAlive<HTTPSessionContext> {\n public:\n  ~HTTPSessionContext() override = default;\n  virtual folly::EventBase* getEventBase() const = 0;\n  virtual void initiateDrain() = 0;\n  virtual bool isDownstream() const = 0;\n  virtual bool isUpstream() const = 0;\n  virtual uint64_t getSessionID() const = 0;\n  virtual CodecProtocol getCodecProtocol() const = 0;\n  virtual const folly::SocketAddress& getLocalAddress() const = 0;\n  virtual const folly::SocketAddress& getPeerAddress() const = 0;\n  FOLLY_DEPRECATED(\"Unsafe\")\n  virtual const folly::AsyncTransport* getAsyncTransport() const {\n    return nullptr;\n  }\n  virtual int getAsyncTransportFD() const {\n    return -1;\n  }\n  virtual quic::QuicSocket* getQUICTransport() const {\n    return nullptr;\n  }\n  virtual const wangle::TransportInfo& getSetupTransportInfo() const = 0;\n  virtual bool getCurrentTransportInfo(\n      wangle::TransportInfo* tinfo, bool includeSetupFields = false) const = 0;\n  virtual size_t getSequenceNumberFromStreamId(\n      HTTPCodec::StreamID streamId) const = 0;\n  virtual uint16_t getDatagramSizeLimit() const = 0;\n  virtual const folly::AsyncTransportCertificate* getPeerCertificate()\n      const = 0;\n  virtual void addLifecycleObserver(LifecycleObserver* cb) = 0;\n  virtual void removeLifecycleObserver(LifecycleObserver* cb) = 0;\n};\n\nusing HTTPSessionContextPtr = detail::KeepAlivePtr<HTTPSessionContext>;\n\n/**\n * Abstract handler for HTTP requests.\n */\nclass HTTPHandler {\n public:\n  virtual ~HTTPHandler() = default;\n  virtual folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) = 0;\n};\n\nclass LifecycleObserver {\n public:\n  virtual ~LifecycleObserver() = default;\n  // Note: you must not start any asynchronous work from onCreate()\n  virtual void onAttached(HTTPCoroSession&) {\n  }\n  /**\n   * Includes the stream these bytes belong to, or folly::none if unknown.\n   * bytesRead can be 0 if the stream ended.\n   */\n  virtual void onRead(const HTTPCoroSession& /*sess*/,\n                      size_t /*bytesRead*/,\n                      folly::Optional<HTTPCodec::StreamID> /*stream id*/) {\n  }\n  virtual void onWrite(const HTTPCoroSession&, size_t /*bytesWritten*/) {\n  }\n  virtual void onActivateConnection(const HTTPCoroSession&) {\n  }\n  virtual void onTransactionAttached(const HTTPCoroSession&) {\n  }\n  virtual void onTransactionDetached(const HTTPCoroSession&) {\n  }\n  virtual void onDeactivateConnection(const HTTPCoroSession&) {\n  }\n  virtual void onDrainStarted(const HTTPCoroSession&) {\n  }\n  virtual void onIngressError(const HTTPCoroSession&, ProxygenError) {\n  }\n  virtual void onIngressEOF(const HTTPCoroSession&) {\n  }\n  virtual void onRequestBegin(const HTTPCoroSession&) {\n  }\n  virtual void onRequestEnd(const HTTPCoroSession&,\n                            uint32_t /*maxIngressQueueSize*/) {\n  }\n  virtual void onIngressMessage(const HTTPCoroSession&, const HTTPMessage&) {\n  }\n  // Note: you must not start any asynchronous work from onDestroy()\n  virtual void onDestroy(const HTTPCoroSession&) {\n  }\n  virtual void onPingReplySent(int64_t /*latency*/) {\n  }\n  virtual void onPingReplyReceived() {\n  }\n  virtual void onSettingsOutgoingStreamsFull(const HTTPCoroSession&) {\n  }\n  virtual void onSettingsOutgoingStreamsNotFull(const HTTPCoroSession&) {\n  }\n  virtual void onFlowControlWindowClosed(const HTTPCoroSession&) {\n  }\n  virtual void onSettings(const HTTPCoroSession&, const SettingsList&) {\n  }\n  virtual void onSettingsAck(const HTTPCoroSession&) {\n  }\n  // GOAWAY notification\n  // Currently use ErrorCode instead of HTTPErrorCode to match the error code in\n  // HTTPCoroSession::onGoaway\n  virtual void onGoaway(const HTTPCoroSession&,\n                        const uint64_t /*lastGoodStreamID*/,\n                        const ErrorCode) {\n  }\n};\n\n/**\n * Class for managing an HTTP/1.x or HTTP/2 connection.\n *\n * At its core, it runs two coroutines, a read loop and a write loop.\n *\n * The read loop reads bytes from the socket and parses them with a codec,\n * populating HTTPStreamSource's with headers, body and the like.\n *\n * The writer awaits the write buffer to have something in it and writes it to\n * the socket.\n *\n * For DOWNSTREAM (eg server), the session starts a new coroutine when the\n * request headers arrive.  This coroutine uses the handler factory to\n * instantiate a handler.  The coroutine then invokes handler to the request\n * and return source for the response.  The coroutine reads the response\n * from the source and serializes it into the write buffer.\n *\n * For UPSTREAM (eg: client), the caller provides a source for the request to\n * the sendRequest method, which reads and serializes it to the write buffer.\n * If the request contains a body, a new coroutine is started to read and\n * serialize it into the write buffer.  Meanwhile, sendRequest returns a source\n * from which the caller can read the response.\n *\n * All HTTPSources are passes as a raw pointer, and self-manage their own\n * lifetime.\n *\n * Currently supports\n *  * trailers\n *  * flow-control\n *  * Non-default SETTINGS\n *  * Stream Limits\n *  * GOAWAY\n *  * Error handling\n *  * Custom timeouts\n *\n * Does not (yet) support\n *  * Priority\n */\nclass HTTPCoroSession\n    : public HTTPCodec::Callback\n    , public HTTPSessionContext\n    , public wangle::ManagedConnection\n    , private HTTPStreamSource::Callback\n    , private HTTPBodyEventQueue::Callback {\n protected:\n  struct StreamState;\n\n public:\n  static HTTPCoroSession* makeUpstreamCoroSession(\n      std::unique_ptr<folly::coro::TransportIf> coroTransport,\n      std::unique_ptr<HTTPCodec> codec,\n      wangle::TransportInfo tinfo);\n\n  static HTTPCoroSession* makeDownstreamCoroSession(\n      std::unique_ptr<folly::coro::TransportIf> coroTransport,\n      std::shared_ptr<HTTPHandler> handler,\n      std::unique_ptr<HTTPCodec> codec,\n      wangle::TransportInfo tinfo);\n\n  static HTTPCoroSession* makeUpstreamCoroSession(\n      std::shared_ptr<quic::QuicSocket> sock,\n      std::unique_ptr<hq::HQMultiCodec> codec,\n      wangle::TransportInfo tinfo);\n\n  static HTTPCoroSession* makeDownstreamCoroSession(\n      std::shared_ptr<quic::QuicSocket> sock,\n      std::shared_ptr<HTTPHandler> handler,\n      std::unique_ptr<hq::HQMultiCodec> codec,\n      wangle::TransportInfo tinfo);\n\n  ~HTTPCoroSession() override;\n\n  uint64_t getSessionID() const override {\n    return sessionID_;\n  }\n\n  uint32_t getNextStreamSeqNum() const {\n    return nextStreamSequenceNumber_;\n  }\n\n  CodecProtocol getCodecProtocol() const override {\n    return codec_->getProtocol();\n  }\n\n  size_t getSequenceNumberFromStreamId(\n      HTTPCodec::StreamID streamId) const override {\n    return HTTPCodec::streamIDToSeqNo(getCodecProtocol(), streamId);\n  }\n\n  folly::EventBase* getEventBase() const override {\n    return eventBase_.get();\n  }\n\n  virtual folly::coro::TaskWithExecutor<void> run() = 0;\n\n  void setSessionStats(HTTPSessionStats* stats) {\n    sessionStats_ = stats;\n  }\n\n  // Upstream API\n  // For most simple cases, call sendRequest without a reservation.  If there\n  // no available streams, it can fail with REFUSED_STREAM.\n  //\n  // When sharing a session (eg: session pooling), call reserveRequest first.\n  // This can also fail with REFUSED_STREAM, but does not consume a\n  // requestSource, so the caller can try to reserve on another session.\n  folly::coro::Task<HTTPSourceHolder> sendRequest(\n      HTTPSourceHolder requestSource);\n\n  struct RequestReservation {\n    RequestReservation() = default;\n    RequestReservation(RequestReservation&& goner) noexcept\n        : session_(goner.session_) {\n      goner.session_ = nullptr;\n    }\n    RequestReservation& operator=(RequestReservation&& goner) {\n      session_ = goner.session_;\n      goner.session_ = nullptr;\n      return *this;\n    }\n    ~RequestReservation() {\n      cancel();\n    }\n    void cancel() {\n      if (session_) {\n        auto session = session_;\n        consume();\n        session->transactionDetached();\n      }\n    }\n    bool fromSession(HTTPCoroSession* session) const {\n      return session_ == session;\n    }\n\n   private:\n    explicit RequestReservation(HTTPCoroSession* session) : session_(session) {\n      session_->pendingSendStreams_++;\n    }\n    void consume() {\n      if (session_) {\n        session_->pendingSendStreams_--;\n        session_->writeEvent_.signal();\n        session_ = nullptr;\n      }\n    }\n    friend class HTTPCoroSession;\n    HTTPCoroSession* session_{nullptr};\n  };\n\n  folly::Try<RequestReservation> reserveRequest();\n\n  virtual folly::coro::Task<HTTPSourceHolder> sendRequest(\n      HTTPSourceHolder requestSource, RequestReservation reservation);\n\n  /**\n   * ::sendRequest variant used if HTTPMessage is synchronously available:\n   * !bodySource.readable() => headerEvent w/ eom\n   * bodySource.readable() => only ::readBodyEvent will be invoked\n   */\n  virtual folly::Expected<HTTPSourceHolder, HTTPError> sendRequest(\n      RequestReservation reservation,\n      const HTTPMessage& headers,\n      HTTPSourceHolder bodySource) noexcept;\n\n  /**\n   * verifies a WebTransport request is valid; yields an\n   * HTTPError(INTERNAL_ERROR) if invalid\n   *\n   * returns an asynchronous result containing the server's response and a\n   * WebTransport handle\n   */\n  struct WtReqResult {\n    std::unique_ptr<HTTPMessage> resp;\n    std::shared_ptr<WebTransport> wt;\n  };\n  virtual folly::coro::Task<WtReqResult> sendWtReq(\n      RequestReservation reservation,\n      const HTTPMessage& msg,\n      std::unique_ptr<WebTransportHandler>) noexcept;\n\n  void describe(std::ostream& os) const override;\n\n  void addLifecycleObserver(LifecycleObserver* cb) override {\n    // add to the beginning of the list, so that a currently iterating\n    // ::deliverLifecycleEvent does not call into this observer\n    lifecycleObservers_.push_front(CHECK_NOTNULL(cb));\n    cb->onAttached(*this);\n  }\n\n  void removeLifecycleObserver(LifecycleObserver* cb) override {\n    auto it =\n        std::find(lifecycleObservers_.begin(), lifecycleObservers_.end(), cb);\n    if (it != lifecycleObservers_.end()) {\n      lifecycleObservers_.erase(it);\n    }\n  }\n\n  template <typename Filter, typename... Args>\n  void addCodecFilter(Args&&... args) {\n    codec_.add<Filter>(std::forward<Args>(args)...);\n  }\n\n  void setSetting(SettingsId setting, uint32_t value);\n\n  virtual void sendPing() = 0;\n\n  virtual void setConnectionFlowControl(uint32_t connFlowControl) = 0;\n\n  void setMaxConcurrentOutgoingStreams(uint32_t maxConcurrentOutgoingStreams) {\n    if (maxConcurrentOutgoingStreams == 0) {\n      XLOG(ERR) << \"Cannot set maxConcurrentOutgoingStreams_ to 0\";\n      return;\n    }\n    if (codec_->supportsParallelRequests()) {\n      maxConcurrentOutgoingStreamsConfig_ = maxConcurrentOutgoingStreams;\n    }\n  }\n\n  uint32_t numOutgoingStreams() const override {\n    auto nStreams = streams_.size();\n    XCHECK_GE(nStreams, numPushStreams_);\n    XCHECK_LT(nStreams, std::numeric_limits<uint32_t>::max());\n    return isUpstream()\n               ? uint32_t(nStreams) - numPushStreams_ + pendingSendStreams_\n               : numPushStreams_;\n  }\n  uint32_t numIncomingStreams() const override {\n    auto nStreams = streams_.size();\n    XCHECK_GE(nStreams, numPushStreams_);\n    XCHECK_LT(nStreams, std::numeric_limits<uint32_t>::max());\n    return isDownstream() ? uint32_t(nStreams) - numPushStreams_\n                          : numPushStreams_;\n  }\n\n  uint64_t numStreams() const {\n    return numIncomingStreams() + numOutgoingStreams();\n  }\n\n  bool supportsMoreTransactions() const {\n    return numTransactionsAvailable() > 0;\n  }\n  virtual uint32_t numTransactionsAvailable() const = 0;\n\n  // If not already draining, sends a GOAWAY, and may trigger other actions.\n  void initiateDrain() override;\n  void closeWhenIdle() override;\n  void dropConnection(const std::string& errorMsg = \"\") override;\n\n  // How long to wait in read() before initiating a graceful close.\n  virtual void setConnectionReadTimeout(std::chrono::milliseconds timeout) {\n    connReadTimeout_ = timeout;\n  }\n\n  // How long streams will wait for input before a timeout error (408/504)\n  void setStreamReadTimeout(std::chrono::milliseconds timeout) {\n    streamReadTimeout_ = timeout;\n  }\n\n  // How long to wait in coro::Transport::write before erroring the connection.\n  // Also how long to wait for a \"write event\" when blocked on connection\n  // or stream flow control.\n  // TODO: Because not all write events indicate progress against blocked flow\n  // control, this timer may not function as designed?\n  void setWriteTimeout(std::chrono::milliseconds timeout) {\n    writeTimeout_ = timeout;\n  }\n\n  /**\n   * Default maximum number of cumulative bytes that can be buffered by the\n   * transactions in this session before applying backpressure. Can be\n   * overridden with setReadBufferLimit().\n   */\n  constexpr static size_t kIngressBufferLimit = 65535;\n  void setReadBufferLimit(uint64_t readBufferLimit) {\n    if (readBufferLimit != 0) {\n      readBufferLimit_ = readBufferLimit;\n    }\n  }\n\n  // whether we've exceeded the ingress buffer threshold.\n  bool ingressLimitExceeded(const StreamState& stream) const;\n  // whether we were previously in excess of ingress limit but no longer are.\n  bool shouldResumeIngress(const StreamState& stream, uint64_t delta) const;\n\n  // Set the amount of time to wait for a TX or ACK ByteEvent to fire once\n  // scheduled (bytes accepted by the transport).\n  // Currently only supported for TCP.\n  virtual void setByteEventTimeout(std::chrono::milliseconds) {\n  }\n\n  const folly::SocketAddress& getLocalAddress() const override {\n    return localAddr_;\n  }\n  const folly::SocketAddress& getPeerAddress() const noexcept override {\n    return peerAddr_;\n  }\n\n  const wangle::TransportInfo& getSetupTransportInfo() const override {\n    return setupTransportInfo_;\n  }\n\n  bool getCurrentTransportInfo(wangle::TransportInfo* tinfo,\n                               bool includeSetupFields = false) const override;\n\n  std::chrono::steady_clock::time_point getStartTime() const {\n    return setupTransportInfo_.acceptTime;\n  }\n\n  bool isDownstream() const override {\n    return direction_ == TransportDirection::DOWNSTREAM;\n  }\n\n  bool isUpstream() const override {\n    return direction_ == TransportDirection::UPSTREAM;\n  }\n\n  virtual void detachEvb();\n  virtual void attachEvb(folly::EventBase*);\n\n  virtual bool isDetachable() const;\n\n  // default impl is a no-op, implemented for HTTPUniplexTransportSession\n  virtual void setRateLimitParams(RateLimiter::Type,\n                                  uint32_t,\n                                  std::chrono::milliseconds) {\n  }\n\n private:\n  class GoawayTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit GoawayTimeout(HTTPCoroSession& session) : session_(session) {\n    }\n    void timeoutExpired() noexcept override {\n      session_.goawayTimeoutExpired();\n    }\n\n    // No-op\n    void callbackCanceled() noexcept override {\n    }\n\n   private:\n    HTTPCoroSession& session_;\n  };\n\n protected:\n  struct WtHelper;\n  friend struct WtHelper;\n\n  using StreamMap =\n      folly::F14FastMap<HTTPCodec::StreamID, std::unique_ptr<StreamState>>;\n\n  HTTPCoroSession(folly::EventBase* eventBase,\n                  folly::SocketAddress localAddr,\n                  folly::SocketAddress peerAddr,\n                  std::unique_ptr<HTTPCodec> codec,\n                  wangle::TransportInfo tinfo,\n                  std::shared_ptr<HTTPHandler> handler = nullptr);\n\n  uint64_t sessionID_{folly::Random::rand64()};\n  folly::Executor::KeepAlive<folly::EventBase> eventBase_;\n  TransportDirection direction_;\n  folly::SocketAddress localAddr_;\n  folly::SocketAddress peerAddr_;\n  HTTPCodecFilterChain codec_;\n  std::shared_ptr<HTTPHandler> handler_;\n  HTTPSessionStats* sessionStats_{nullptr};\n  wangle::TransportInfo setupTransportInfo_;\n  StreamMap streams_;\n  // Connection flow control\n  Window sendWindow_{http2::kInitialWindow};\n  WindowContainer recvWindow_;\n  uint64_t sessionBytesScheduled_{0};\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n  detail::DetachableCancellableBaton writeEvent_;\n  GoawayTimeout goawayTimeout_{*this};\n  std::chrono::milliseconds connReadTimeout_{std::chrono::seconds(5)};\n  std::chrono::milliseconds streamReadTimeout_{std::chrono::seconds(5)};\n  std::chrono::milliseconds writeTimeout_{std::chrono::seconds(5)};\n  uint64_t readBufferLimit_{kIngressBufferLimit};\n  std::list<LifecycleObserver*> lifecycleObservers_{};\n  uint32_t maxConcurrentOutgoingStreamsConfig_{100};\n  uint32_t numPushStreams_{0};\n  uint32_t pendingSendStreams_{0};\n  uint32_t nextStreamSequenceNumber_{0};\n  quic::HTTPPriorityQueue writableStreams_;\n  detail::DetachableExecutor readExec_{eventBase_.get()};\n  detail::DetachableExecutor writeExec_{eventBase_.get()};\n\n  void sendPreface();\n\n  StreamState& createNewStream(HTTPCodec::StreamID id,\n                               bool fromSendRequest = false);\n\n  StreamState* findStream(HTTPCodec::StreamID id);\n\n  void insertWithPriority(const StreamState&);\n\n  folly::coro::Task<HTTPSourceHolder> handleRequest(StreamState& stream);\n\n  folly::coro::Task<void> readResponse(\n      StreamState& stream,\n      folly::coro::Task<HTTPSourceHolder> responseSourceTask);\n\n  enum class ResponseState { HEADERS, BODY, DONE };\n  ResponseState processResponseHeaderEvent(\n      StreamState& stream, folly::Try<HTTPHeaderEvent> headerEvent);\n\n  folly::coro::Task<void> transferRequestBody(StreamState& stream,\n                                              HTTPSourceHolder requestSource);\n\n  folly::coro::Task<void> transferBody(StreamState& stream,\n                                       std::function<void()>);\n\n  virtual void registerByteEvents(\n      HTTPCodec::StreamID id,\n      folly::Optional<uint64_t> streamByteOffset,\n      folly::Optional<HTTPByteEvent::FieldSectionInfo> fsInfo,\n      uint64_t bodyOffset,\n      std::vector<HTTPByteEventRegistration>&& registrations,\n      bool eom) = 0;\n\n  void onResetStream(HTTPCodec::StreamID id, HTTPErrorCode error);\n  virtual void deliverAbort(StreamState& stream,\n                            HTTPErrorCode error,\n                            std::string_view details);\n\n  void egressFinished(StreamState& stream);\n  void egressResetStream(HTTPCodec::StreamID id,\n                         StreamState* stream,\n                         HTTPErrorCode error,\n                         bool fromSource = false,\n                         bool bidirectionalReset = true);\n  virtual void handleDeferredStopSending(HTTPCodec::StreamID id) = 0;\n\n  // error is delivered to any queued byte events that get cancelled\n  void resetStreamState(StreamState& stream, const HTTPError& err);\n\n  void decrementPushStreamCount(const StreamState& stream,\n                                bool eomMarkedEgressComplete = false);\n\n  bool checkForDetach(StreamState& stream);\n\n  uint32_t getStreamSendFlowControlWindow() const {\n    return getStreamFlowControlWindow(codec_->getIngressSettings());\n  }\n  uint32_t getStreamRecvFlowControlWindow() {\n    return getStreamFlowControlWindow(codec_->getEgressSettings());\n  }\n  uint32_t getStreamFlowControlWindow(const HTTPSettings* settings) const {\n    if (codec_->supportsStreamFlowControl()) {\n      XCHECK(settings) << \"H2 has settings and stream flow control\";\n      auto setting = settings->getSetting(SettingsId::INITIAL_WINDOW_SIZE);\n      return setting ? uint32_t(setting->value) : http2::kInitialWindow;\n    }\n    return std::numeric_limits<int32_t>::max();\n  }\n  void bytesProcessed(HTTPCodec::StreamID id,\n                      size_t delta,\n                      size_t toAck) override;\n  void sourceComplete(HTTPCodec::StreamID id,\n                      folly::Optional<HTTPError> error) override;\n  void onEgressBytesBuffered(int64_t bytes) noexcept override {\n    if (sessionStats_) {\n      sessionStats_->recordPendingBufferedWriteBytes(bytes);\n    }\n  }\n\n  // HTTPCodec callbacks\n  void onMessageBegin(HTTPCodec::StreamID streamID,\n                      HTTPMessage* /*msg*/) override;\n  // onPushMessageBegin\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         std::unique_ptr<HTTPMessage> msg\n                         /* bool eom!*/) override;\n\n  void onBody(HTTPCodec::StreamID streamID,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding\n              /* bool eom!*/) override;\n\n  virtual void handleIngressLimitExceeded(HTTPCodec::StreamID) = 0;\n\n  void onChunkHeader(HTTPCodec::StreamID /*stream*/,\n                     size_t /*length*/) override {\n  }\n  void onChunkComplete(HTTPCodec::StreamID /*stream*/) override {\n  }\n  void onTrailersComplete(HTTPCodec::StreamID streamID,\n                          std::unique_ptr<HTTPHeaders> trailers) override;\n  void onMessageComplete(HTTPCodec::StreamID streamID,\n                         bool /*upgrade*/) override;\n  void onError(HTTPCodec::StreamID /*streamID*/,\n               const HTTPException& /*error*/,\n               bool /*newTxn*/) override;\n  void onAbort(HTTPCodec::StreamID streamID, ErrorCode code) override;\n\n  void onGoaway(uint64_t /*lastGoodStreamID*/,\n                ErrorCode /*code*/,\n                std::unique_ptr<folly::IOBuf> /*debugData = nullptr*/) override;\n\n  // Default/Implemented in subclass:\n  //  onPingRequest/onPingReply\n  //  onWindowUpdate\n  //  onSettings\n  //  onSettingsAck\n\n  void onPriority(HTTPCodec::StreamID /*stream*/,\n                  const HTTPPriority& /*pri*/) override {\n  }\n\n  void onCertificateRequest(\n      uint16_t /*requestId*/,\n      std::unique_ptr<folly::IOBuf> /*authRequest*/) override {\n  }\n  void onCertificate(uint16_t /*certId*/,\n                     std::unique_ptr<folly::IOBuf> /*authenticator*/) override {\n  }\n\n  void timeoutExpired() noexcept override {\n    /* ignore, we have our own timeouts? */\n  }\n  bool isBusy() const override {\n    return !streams_.empty() || codec_->isBusy();\n  }\n  std::chrono::milliseconds getIdleTime() const override {\n    // TODO: implement\n    return std::chrono::milliseconds(0);\n  }\n  void notifyPendingShutdown() override {\n    /*\n     * Upstream HTTPCoroSessions should not be drained by a\n     * wangle::ConnectionManager. Instead, drain them manually via\n     * `::initiateDrain()` or held by a containing HTTPCoroSessionPool and drain\n     * that.\n     */\n    if (isDownstream()) {\n      initiateDrain();\n    }\n  }\n  void dumpConnectionState(uint8_t /*loglevel*/) override {\n  }\n\n  template <typename T, typename... Args>\n  void deliverLifecycleEvent(T callbackFn, Args&&... args) {\n    auto it = lifecycleObservers_.begin();\n    while (it != lifecycleObservers_.end()) {\n      auto* observer = *it++;\n      (*observer.*callbackFn)(std::forward<Args>(args)...);\n    }\n  }\n\n  void onSetMaxInitiatedStreams(bool didSupport);\n\n  void scheduleGoawayTimeout() {\n    // connReadTimeout_ is not the right value here, HTTPSession gets this\n    // value from its \"controller\"\n    eventBase_->timer().scheduleTimeout(&goawayTimeout_, connReadTimeout_);\n  }\n\n  void goawayTimeoutExpired();\n\n  folly::coro::Task<void> waitForAllStreams();\n\n  void drainStarted();\n\n  bool isDraining() const {\n    return maxConcurrentOutgoingStreamsConfig_ == 0;\n  }\n\n  void connectionError(\n      HTTPErrorCode httpError,\n      std::string msg,\n      folly::Optional<HTTPErrorCode> streamError = folly::none);\n\n  void resetOpenStreams(HTTPErrorCode error, std::string_view details);\n\n  HTTPHeaderSize addPushPromiseToWriteBuf(StreamState& stream,\n                                          HTTPBodyEvent& bodyEvent);\n\n  // TODO: should be in HTTPCodec\n  virtual HTTPCodec::StreamID getSessionStreamID() const = 0;\n  virtual void applyEgressSettings() {\n  }\n  virtual void handlePipeliningOnDetach() {\n  }\n  virtual bool getCurrentTransportInfoImpl(\n      wangle::TransportInfo* /*tinfo*/) const = 0;\n  virtual void handleConnectionError(HTTPErrorCode error, std::string msg) = 0;\n  virtual void removeWritableStream(HTTPCodec::StreamID id) {\n    auto identifier = quic::PriorityQueue::Identifier::fromStreamID(id);\n    writableStreams_.erase(identifier);\n  }\n  virtual void setupStreamWriteBuf(StreamState& stream,\n                                   folly::IOBufQueue& sessWriteBuf) = 0;\n\n  virtual void notifyHeaderWrite(StreamState& stream, bool eom) = 0;\n  void notifyBodyWrite(StreamState& stream);\n  virtual StreamState* createReqStream() = 0;\n  virtual bool streamRefusedByGoaway(StreamState& stream,\n                                     HTTPCodec::StreamID lastGoodStreamID) = 0;\n  virtual void generateResetStream(HTTPCodec::StreamID id,\n                                   HTTPErrorCode error,\n                                   bool fromSource,\n                                   bool bidirectionalReset) = 0;\n  virtual void eraseStream(HTTPCodec::StreamID id);\n  virtual void streamHeadersComplete(StreamState& /*stream*/) {\n  }\n  virtual bool checkAndHandlePushPromiseComplete(\n      StreamState& stream, std::unique_ptr<HTTPMessage>& msg) = 0;\n  virtual folly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>,\n                          ErrorCode>\n  createEgressPushStream() = 0;\n  virtual bool sendFlowControlUpdate(HTTPCodec::StreamID /*id*/,\n                                     size_t /*delta*/) {\n    return false;\n  }\n  virtual bool isConnectionFlowControlBlocked() {\n    return sendWindow_.getSize() == 0;\n  }\n  virtual bool isStreamFlowControlBlocked(StreamState& stream);\n  virtual void interruptReadLoop() {\n  }\n  void handleWriteEventTimeout();\n\n  void transactionAttached() noexcept;\n  void transactionDetached() noexcept;\n\n  // NOTE: this will never invoke ::readHeaderEvent on the bodySource\n  folly::Expected<HTTPSourceHolder, HTTPError> sendRequestImpl(\n      const HTTPMessage& headers,\n      folly::Function<void(HTTPHeaderSize) noexcept>&& egressHeadersFn,\n      std::vector<HTTPByteEventRegistration>&& byteEventRegistrations,\n      HTTPSourceHolder bodySource) noexcept;\n};\n\nclass HTTPUniplexTransportSession final : public HTTPCoroSession {\n public:\n  HTTPUniplexTransportSession(\n      std::unique_ptr<folly::coro::TransportIf> coroTransport,\n      std::unique_ptr<HTTPCodec> codec,\n      wangle::TransportInfo tinfo,\n      std::shared_ptr<HTTPHandler> handler = nullptr)\n      : HTTPCoroSession(coroTransport->getEventBase(),\n                        coroTransport->getLocalAddress(),\n                        coroTransport->getPeerAddress(),\n                        std::move(codec),\n                        std::move(tinfo),\n                        std::move(handler)),\n        coroTransport_(std::move(coroTransport)) {\n    flowControlBaton_.signal();\n    start();\n  }\n\n  ~HTTPUniplexTransportSession() override {\n    coroTransport_->close();\n  }\n\n  folly::coro::TaskWithExecutor<void> run() override;\n\n  void sendPing() override;\n\n  void setConnectionFlowControl(uint32_t connFlowControl) override;\n\n  folly::coro::Task<WtReqResult> sendWtReq(\n      RequestReservation reservation,\n      const HTTPMessage& msg,\n      std::unique_ptr<WebTransportHandler>) noexcept final;\n\n private:\n  folly::coro::Task<void> runImpl();\n  void handleIngressLimitExceeded(HTTPCodec::StreamID streamID) override;\n\n  void bytesProcessed(HTTPCodec::StreamID id,\n                      size_t delta,\n                      size_t toAck) override;\n\n  void detachEvb() override;\n  void attachEvb(folly::EventBase* evb) override;\n  bool isDetachable() const override;\n\n  std::unique_ptr<folly::coro::TransportIf> coroTransport_;\n  folly::coro::Baton writesFinished_;\n  detail::CancellableBaton antiPipelineBaton_;\n  detail::CancellableBaton flowControlBaton_;\n  uint32_t maxConcurrentOutgoingStreamsRemote_{100};\n  bool readsClosed_{false};\n  bool resetAfterDrainingWrites_{false};\n  std::list<PendingByteEvent> transportWriteEvents_;\n  std::list<PendingByteEvent> kernelWriteEvents_;\n  RateLimitFilter* rateLimitFilter_{nullptr};\n  folly::CancellationSource readCancellationSource_;\n  AsyncSocketByteEventObserver byteEventObserver_;\n\n public:\n  void start();\n\n  void setRateLimitParams(RateLimiter::Type type,\n                          uint32_t maxEventsPerInterval,\n                          std::chrono::milliseconds intervalDuration) override;\n\n  folly::coro::Task<void> readLoop() noexcept;\n  folly::coro::Task<void> writeLoop() noexcept;\n\n  size_t addStreamBodyDataToWriteBuf(uint32_t max);\n\n  // HTTPCoroSession overrides\n  bool sendFlowControlUpdate(HTTPCodec::StreamID /*id*/,\n                             size_t /*delta*/) override;\n\n  void handleConnectionError(HTTPErrorCode error, std::string msg) override;\n  void setupStreamWriteBuf(StreamState& stream,\n                           folly::IOBufQueue& sessWriteBuf) override;\n  bool getCurrentTransportInfoImpl(\n      wangle::TransportInfo* /*tinfo*/) const override;\n\n  FOLLY_DEPRECATED(\"Unsafe\")\n  const folly::AsyncTransport* getAsyncTransport() const override {\n    return coroTransport_->getTransport();\n  }\n  int getAsyncTransportFD() const override;\n  uint16_t getDatagramSizeLimit() const override {\n    return 0;\n  }\n  const folly::AsyncTransportCertificate* getPeerCertificate() const override {\n    return coroTransport_ ? coroTransport_->getPeerCertificate() : nullptr;\n  }\n\n  void notifyHeaderWrite(StreamState& stream, bool eom) override;\n  StreamState* createReqStream() override;\n  bool streamRefusedByGoaway(StreamState& stream,\n                             HTTPCodec::StreamID lastGoodStreamID) override;\n  void generateResetStream(HTTPCodec::StreamID id,\n                           HTTPErrorCode error,\n                           bool fromSource,\n                           bool bidirectionalReset = true) override;\n  void handleDeferredStopSending(HTTPCodec::StreamID id) override;\n  bool checkAndHandlePushPromiseComplete(\n      StreamState& stream, std::unique_ptr<HTTPMessage>& msg) override;\n  folly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>,\n                  ErrorCode>\n  createEgressPushStream() override;\n\n  // H1/2 specific HTTPCodec::Callback overrides\n  void onPushMessageBegin(HTTPCodec::StreamID streamID,\n                          HTTPCodec::StreamID assocStreamID,\n                          HTTPMessage* promise) override;\n  void onPingRequest(uint64_t data) override {\n    // TODO: try inserting at the beginning of writeBuf_ ?\n    // TODO: byte events for timing ping latency?\n    // TODO: tracking e2e RTT measurements\n    deliverLifecycleEvent(&LifecycleObserver::onPingReplySent, 0);\n    codec_->generatePingReply(writeBuf_, data);\n    writeEvent_.signal();\n  }\n  void onPingReply(uint64_t /*data*/) override {\n    deliverLifecycleEvent(&LifecycleObserver::onPingReplyReceived);\n  }\n  void onWindowUpdate(HTTPCodec::StreamID streamID, uint32_t amount) override;\n  void onSettings(const SettingsList& settings) override;\n  void onSettingsAck() override;\n  uint32_t getMaxConcurrentOutgoingStreams() const {\n    return std::min(maxConcurrentOutgoingStreamsConfig_,\n                    maxConcurrentOutgoingStreamsRemote_);\n  }\n  uint32_t numTransactionsAvailable() const override {\n    auto nOutgoing = numOutgoingStreams();\n    auto maxConcurrent = getMaxConcurrentOutgoingStreams();\n    return (nOutgoing < maxConcurrent) ? maxConcurrent - nOutgoing : 0;\n  }\n\n  void cleanupAfterWriteError(const std::string& msg);\n\n  bool shouldContinueReadLooping() const;\n  bool shouldContinueWriteLooping() const;\n  HTTPCodec::StreamID getSessionStreamID() const override {\n    return 0;\n  }\n  void deliverAbort(StreamState& stream,\n                    HTTPErrorCode error,\n                    std::string_view details) override;\n  void setByteEventTimeout(std::chrono::milliseconds timeout) override {\n    byteEventObserver_.setByteEventTimeout(timeout);\n  }\n  void registerByteEvents(\n      HTTPCodec::StreamID id,\n      folly::Optional<uint64_t> streamByteOffset,\n      folly::Optional<HTTPByteEvent::FieldSectionInfo> fsInfo,\n      uint64_t bodyOffset,\n      std::vector<HTTPByteEventRegistration>&& registrations,\n      bool eom) override;\n\n  void handlePipeliningOnDetach() override;\n  void interruptReadLoop() override {\n    if (!shouldContinueReadLooping()) {\n      readCancellationSource_.requestCancellation();\n    }\n  }\n  void maybeEnableByteEvents();\n};\n\nconstexpr uint8_t kMaxDatagramHeaderSize = 16;\n// Maximum number of datagrams to buffer per stream\nconstexpr uint8_t kDefaultMaxBufferedDatagrams = 5;\n// Maximum number of streams with datagrams buffered\nconstexpr uint8_t kMaxStreamsWithBufferedDatagrams = 10;\n\nclass HTTPQuicCoroSession final\n    : public HTTPCoroSession\n    , public quic::QuicSocket::ConnectionCallback\n    , public hq::HQUnidirectionalCodec::Callback\n    , public quic::QuicSocket::PingCallback\n    , public quic::QuicSocket::WriteCallback\n    , public quic::QuicSocket::DatagramCallback\n    , public HQUniStreamDispatcher::Callback {\n public:\n  HTTPQuicCoroSession(std::shared_ptr<quic::QuicSocket> sock,\n                      std::unique_ptr<hq::HQMultiCodec> codec,\n                      wangle::TransportInfo tinfo,\n                      std::shared_ptr<HTTPHandler> handler = nullptr);\n\n  ~HTTPQuicCoroSession() override;\n\n  folly::coro::TaskWithExecutor<void> run() override;\n\n  void sendPing() override;\n\n  void setConnectionFlowControl(uint32_t connFlowControl) override;\n\n  size_t sendPriority(quic::StreamId id, HTTPPriority pri);\n  size_t sendPushPriority(uint64_t pushId, HTTPPriority pri);\n\n  using HTTPCoroSession::onError;\n  folly::EventBase* getEventBase() const override {\n    return HTTPCoroSession::getEventBase();\n  }\n\n private:\n  folly::coro::Task<void> runImpl();\n  std::chrono::milliseconds getDispatchTimeout() const override {\n    return streamReadTimeout_;\n  }\n\n  void rejectStream(quic::StreamId id) override;\n\n  folly::Optional<hq::UnidirectionalStreamType> parseUniStreamPreface(\n      uint64_t preface) override {\n    hq::UnidirectionalTypeF parse = [](hq::UnidirectionalStreamType type)\n        -> folly::Optional<hq::UnidirectionalStreamType> { return type; };\n    return hq::withType(preface, parse);\n  }\n\n  void dispatchControlStream(quic::StreamId id,\n                             hq::UnidirectionalStreamType streamType,\n                             size_t toConsume) override;\n\n  void dispatchPushStream(quic::StreamId id,\n                          hq::PushId pushId,\n                          size_t toConsume) override;\n\n  void dispatchUniWTStream(quic::StreamId streamId,\n                           quic::StreamId /*sessionId*/,\n                           size_t /*toConsume*/) override {\n    // TODO: implement WebTransport for proxygen::coro\n    quicSocket_->stopSending(\n        streamId,\n        quic::ApplicationErrorCode(HTTPErrorCode::STREAM_CREATION_ERROR));\n    quicSocket_->setPeekCallback(streamId, nullptr);\n  }\n\n  std::shared_ptr<quic::QuicSocket> quicSocket_;\n  hq::HQMultiCodec* multiCodec_{nullptr};\n  hq::QPACKEncoderCodec qpackEncoderCodec_;\n  hq::QPACKDecoderCodec qpackDecoderCodec_;\n  quic::StreamId controlStreamID_{quic::kInvalidStreamId};\n  quic::StreamId qpackEncoderStreamID_{quic::kInvalidStreamId};\n  quic::StreamId qpackDecoderStreamID_{quic::kInvalidStreamId};\n  HQUniStreamDispatcher uniStreamDispatcher_;\n\n  void onError(HTTPCodec::StreamID streamID,\n               const HTTPException& error,\n               bool newTxn) override {\n    HTTPCoroSession::onError(streamID, error, newTxn);\n  }\n\n  // QuicSocket::ConnectionCallback overrides\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override;\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override;\n  void onStopSending(quic::StreamId id,\n                     quic::ApplicationErrorCode error) noexcept override;\n  void onConnectionEnd() noexcept override;\n  void onConnectionError(quic::QuicError error) noexcept override;\n  void onBidirectionalStreamsAvailable(\n      uint64_t numStreamsAvailable) noexcept override;\n\n  // quic::QuicSocket::PingCallback\n  void pingAcknowledged() noexcept override {\n  }\n  void pingTimeout() noexcept override {\n  }\n  void onPing() noexcept override {\n    resetIdleTimeout();\n  }\n\n  // QuicSocket::DatagramCallback overrides\n  void onDatagramsAvailable() noexcept override;\n\n  // HTTPCoroSession overrides\n  void handleConnectionError(HTTPErrorCode error, std::string msg) override;\n\n  void setupStreamWriteBuf(StreamState& stream,\n                           folly::IOBufQueue& sessWriteBuf) override;\n  void applyEgressSettings() override;\n  bool getCurrentTransportInfoImpl(\n      wangle::TransportInfo* /*tinfo*/) const override;\n  quic::QuicSocket* getQUICTransport() const override {\n    return quicSocket_.get();\n  }\n  uint16_t getDatagramSizeLimit() const override;\n  const folly::AsyncTransportCertificate* getPeerCertificate() const override {\n    return quicSocket_->getPeerCertificate().get();\n  }\n\n  void notifyHeaderWrite(StreamState& stream, bool eom) override;\n  StreamState* createReqStream() override;\n  bool streamRefusedByGoaway(StreamState& stream,\n                             HTTPCodec::StreamID lastGoodStreamID) override;\n  void generateResetStream(HTTPCodec::StreamID id,\n                           HTTPErrorCode error,\n                           bool fromSource,\n                           bool bidirectionalReset = true) override;\n  void handleDeferredStopSending(HTTPCodec::StreamID id) override;\n  void eraseStream(HTTPCodec::StreamID id) override;\n  uint32_t numTransactionsAvailable() const override {\n    if (!quicSocket_->good()) {\n      return 0;\n    }\n    uint32_t nStreams = numOutgoingStreams(); // includes pendingSendStreams_\n    if (maxConcurrentOutgoingStreamsConfig_ <= nStreams) {\n      return 0; // at config limit\n    }\n    uint32_t configAvailable = maxConcurrentOutgoingStreamsConfig_ - nStreams;\n    uint64_t transportAvailable =\n        isUpstream() ? quicSocket_->getNumOpenableBidirectionalStreams()\n                     : quicSocket_->getNumOpenableUnidirectionalStreams();\n    XCHECK(pendingSendStreams_ == 0 || isUpstream());\n    if (transportAvailable <= pendingSendStreams_) {\n      return 0; // at transport limit\n    }\n    transportAvailable -= pendingSendStreams_;\n    return std::min(configAvailable,\n                    clamped_downcast<uint32_t>(transportAvailable));\n  }\n  HTTPCodec::StreamID getSessionStreamID() const override {\n    return hq::kSessionStreamId;\n  }\n  void deliverAbort(StreamState& stream,\n                    HTTPErrorCode error,\n                    std::string_view details) override;\n  void streamHeadersComplete(StreamState& /*stream*/) override;\n  void setConnectionReadTimeout(std::chrono::milliseconds timeout) override {\n    HTTPCoroSession::setConnectionReadTimeout(timeout);\n    idle_.signal(); // this will set to the new timeout\n  }\n  void registerByteEvents(\n      HTTPCodec::StreamID id,\n      folly::Optional<uint64_t> streamByteOffset,\n      folly::Optional<HTTPByteEvent::FieldSectionInfo> fsInfo,\n      uint64_t bodyOffset,\n      std::vector<HTTPByteEventRegistration>&& registrations,\n      bool eom) override;\n  bool checkAndHandlePushPromiseComplete(\n      StreamState& stream, std::unique_ptr<HTTPMessage>& msg) override;\n  folly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>,\n                  ErrorCode>\n  createEgressPushStream() override;\n\n  void onPushMessageBegin(HTTPCodec::StreamID streamID,\n                          HTTPCodec::StreamID assocStreamID,\n                          HTTPMessage* promise) override;\n\n  void onSettings(const SettingsList& settings) override;\n\n  bool isConnectionFlowControlBlocked() override {\n    auto connFC = quicSocket_->getConnectionFlowControl();\n    return connFC && connFC->sendWindowAvailable == 0;\n  }\n\n  bool isStreamFlowControlBlocked(StreamState& stream) override;\n\n  /**\n   * bytesProcessed and onBody callbacks mirror each other in an opposite way.\n   * When bytesProcessed and onBody callbacks are invoked, we need to decrement\n   * or increment the stream's pendingReadSize.\n   *\n   * bytesProcessed may resume reading from the stream if the buffer size\n   * transitioned from being greater than or equal to kDefaultReadSize, but is\n   * now strictly less than kDefaultReadSize.\n   *\n   * onBody may pause reading from the stream if the buffer size now exceeds the\n   * kDefaultReadSize threshold.\n   */\n  void bytesProcessed(HTTPCodec::StreamID id,\n                      size_t delta,\n                      size_t toAck) override;\n\n  void handleIngressLimitExceeded(HTTPCodec::StreamID streamID) override;\n\n  void detachEvb() override;\n  void attachEvb(folly::EventBase* evb) override;\n  bool isDetachable() const override;\n\n  // HTTPQuicCoroSession only\n  void start();\n  folly::coro::Task<void> dispatchUnidirectionalStream(quic::StreamId id);\n  folly::coro::Task<void> readControlStream(quic::StreamId id,\n                                            hq::HQUnidirectionalCodec& codec,\n                                            bool isQPACKDecoder);\n  folly::coro::Task<void> readLoop(quic::StreamId id) noexcept;\n  bool shouldContinueWriteLooping() const;\n  class StreamRCB;\n  folly::coro::Task<void> readLoopImpl(std::shared_ptr<StreamRCB> readCallback,\n                                       quic::StreamId id) noexcept;\n  folly::coro::Task<void> readLoop() noexcept;\n  folly::coro::Task<void> writeLoop() noexcept;\n  bool hasControlWrite() const;\n  void writeControlStream(quic::StreamId id, folly::IOBufQueue& writeBuf);\n  bool createControlStream(hq::UnidirectionalStreamType streamType,\n                           folly::IOBufQueue& writeBuf,\n                           quic::StreamId& id);\n  void registerControlDeliveryCallback(quic::StreamId id);\n  bool handleWrite(HTTPCodec::StreamID id,\n                   folly::IOBufQueue& writeBuf,\n                   bool eom);\n  void dispatchPushStream(quic::StreamId id, uint64_t pushID);\n  bool isDatagramEnabled() const {\n    SettingsId setting = *hq::hqToHttpSettingsId(hq::SettingId::H3_DATAGRAM);\n    return multiCodec_->getEgressSettings()->getSetting(setting) &&\n           (!multiCodec_->receivedSettings() ||\n            codec_->getIngressSettings()->getSetting(setting));\n  }\n  bool sendDatagram(HTTPCodec::StreamID id,\n                    std::unique_ptr<folly::IOBuf> datagram);\n\n  void resetIdleTimeout();\n\n  void onPriority(quic::StreamId, const HTTPPriority&) override;\n  void onPushPriority(quic::StreamId, const HTTPPriority&) override {\n  }\n\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         std::unique_ptr<HTTPMessage> msg\n                         /* bool eom!*/) override;\n\n  bool isStreamIngressLimitExceeded(HTTPCodec::StreamID streamID) {\n    auto stream = findStream(streamID);\n    return stream && ingressLimitExceeded(*stream);\n  }\n\n  void onStreamWriteReady(quic::StreamId id,\n                          uint64_t /*maxSend*/) noexcept override {\n    if (auto* stream = findStream(id)) {\n      notifyBodyWrite(*stream);\n    }\n  }\n  void generateStopSending(HTTPCodec::StreamID id,\n                           HTTPErrorCode error,\n                           bool fromSource);\n\n  class QuicReadCallback : public quic::QuicSocket::ReadCallback {\n   public:\n    detail::CancellableBaton& getBaton() {\n      return baton_;\n    }\n\n   protected:\n    explicit QuicReadCallback(HTTPQuicCoroSession& session)\n        : session_(session) {\n    }\n\n    HTTPQuicCoroSession& session_;\n    detail::CancellableBaton baton_;\n    folly::IOBufQueue input_{folly::IOBufQueue::cacheChainLength()};\n    bool readEOF_{false};\n  };\n\n  class StreamRCB : public QuicReadCallback {\n   public:\n    explicit StreamRCB(HTTPQuicCoroSession& session)\n        : QuicReadCallback(session) {\n    }\n    void readAvailable(quic::StreamId id) noexcept override;\n    void processRead(quic::StreamId id);\n    void resumeRead(quic::StreamId id);\n    void readError(quic::StreamId id, quic::QuicError error) noexcept override;\n\n   private:\n    bool inProcessRead_{false};\n    bool isReadPaused_{false};\n  };\n\n  class DeliveryCallback\n      : public quic::ByteEventCallback\n      , protected Refcount {\n   public:\n    static constexpr uint8_t kDeliveryCallbackRefcount = 0;\n    static constexpr std::chrono::milliseconds kDeliveryCallbackTimeout =\n        std::chrono::seconds(5);\n\n    DeliveryCallback() : Refcount(kDeliveryCallbackRefcount) {\n    }\n\n    void onByteEventRegistered(quic::ByteEvent) override {\n      incRef();\n    }\n\n    void onByteEvent(quic::ByteEvent byteEvent) override {\n      XLOG(DBG4) << \"onDeliveryAck for id=\" << byteEvent.id;\n      decRef();\n    }\n\n    void onByteEventCanceled(quic::ByteEvent byteEvent) override {\n      XLOG(DBG4) << \"onCanceled for id=\" << byteEvent.id;\n      decRef();\n    }\n\n    folly::coro::Task<TimedBaton::Status> zeroRefs(folly::EventBase* evb) {\n      return baton_.timedWait(evb, kDeliveryCallbackTimeout);\n    }\n  };\n\n  // Maximum number of priority updates received when stream is not available\n  constexpr static uint8_t kMaxBufferedPriorityUpdates = 10;\n\n  folly::coro::AsyncScope backgroundScope_;\n  detail::DetachableCancellableBaton idle_;\n  quic::Optional<quic::QuicError> connectionError_;\n  DeliveryCallback deliveryCallback_;\n  Refcount byteEventRefcount_{0};\n  // PushID -> StreamState\n  folly::F14FastMap<uint64_t, std::unique_ptr<StreamState>>\n      pushStreamsAwaitingStreamID_;\n  // PushID -> StreamID\n  folly::F14FastMap<uint64_t, quic::StreamId> pushStreamsAwaitingPromises_;\n  // Buffer for priority updates without an active stream\n  folly::EvictingCacheMap<quic::StreamId, HTTPPriority> priorityUpdatesBuffer_{\n      kMaxBufferedPriorityUpdates};\n  // Buffer for datagrams waiting for a stream to be assigned to\n  folly::EvictingCacheMap<\n      quic::StreamId,\n      folly::small_vector<\n          std::unique_ptr<folly::IOBuf>,\n          kDefaultMaxBufferedDatagrams,\n          folly::small_vector_policy::policy_in_situ_only<true>>>\n      datagramsBuffer_{kMaxStreamsWithBufferedDatagrams};\n  std::shared_ptr<QuicProtocolInfo> currentQuicProtocolInfo_{\n      std::make_shared<QuicProtocolInfo>()};\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPCoroSession& session);\n\nHTTPSource* getErrorResponse(uint16_t statusCode, const std::string& body = \"\");\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPError.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include <folly/logging/xlog.h>\n\n#include <folly/container/F14Map.h>\n#include <proxygen/lib/http/HTTPException.h>\n\nnamespace {\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nbool checkInvalidAppErrorCode(HTTPErrorCode ec) {\n  switch (ec) {\n    // Internal H2 errors\n    case HTTPErrorCode::PROTOCOL_ERROR:\n    case HTTPErrorCode::SETTINGS_TIMEOUT:\n    case HTTPErrorCode::STREAM_CLOSED:\n    case HTTPErrorCode::FRAME_SIZE_ERROR:\n    case HTTPErrorCode::COMPRESSION_ERROR:\n\n    // Internal lib errors\n    case HTTPErrorCode::INVALID_STATE_TRANSITION:\n    case HTTPErrorCode::WRITE_TIMEOUT:\n    case HTTPErrorCode::TRANSPORT_EOF:\n    case HTTPErrorCode::TRANSPORT_READ_ERROR:\n    case HTTPErrorCode::TRANSPORT_WRITE_ERROR:\n    case HTTPErrorCode::HEADER_PARSE_ERROR:\n    case HTTPErrorCode::BODY_PARSE_ERROR:\n    case HTTPErrorCode::DROPPED:\n    case HTTPErrorCode::CORO_CANCELLED:\n      XLOG(WARNING) << \"Application supplied internal error code ec=\"\n                    << uint16_t(ec);\n      return true;\n\n    // Internal H3 errors\n    case HTTPErrorCode::GENERAL_PROTOCOL_ERROR:\n    case HTTPErrorCode::STREAM_CREATION_ERROR:\n    case HTTPErrorCode::CLOSED_CRITICAL_STREAM:\n    case HTTPErrorCode::FRAME_UNEXPECTED:\n    case HTTPErrorCode::FRAME_ERROR:\n    case HTTPErrorCode::ID_ERROR:\n    case HTTPErrorCode::SETTINGS_ERROR:\n    case HTTPErrorCode::MISSING_SETTINGS:\n    case HTTPErrorCode::INCOMPLETE_REQUEST:\n    case HTTPErrorCode::QPACK_DECOMPRESSION_FAILED:\n    case HTTPErrorCode::QPACK_ENCODER_STREAM_ERROR:\n    case HTTPErrorCode::QPACK_DECODER_STREAM_ERROR:\n      XLOG(WARNING) << \"Application supplied internal H3 error code ec=\"\n                    << uint16_t(ec);\n      return true;\n    default:\n      return false;\n  }\n}\n\n// Maybe tolower or camel case?\n#define HTTP_ERROR_SET_NAME(error) name = #error;\n#define HTTP_ERROR_SET_VALUE_TO_NAME(value) \\\n  errorStringMap[HTTPErrorCode(value)] = std::move(name)\n\nfolly::F14FastMap<HTTPErrorCode, std::string> getErrorStringMap() {\n  folly::F14FastMap<HTTPErrorCode, std::string> errorStringMap;\n  std::string name;\n  HTTP_ERROR_GEN_VALUES(HTTP_ERROR_SET_NAME, HTTP_ERROR_SET_VALUE_TO_NAME);\n  return errorStringMap;\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nconst std::string& getErrorString(HTTPErrorCode ec) {\n  static const auto sErrorStringMap = getErrorStringMap();\n  auto it = sErrorStringMap.find(ec);\n  if (it == sErrorStringMap.end()) {\n    static const std::string unknownError(\"UNKNOWN_ERROR\");\n    return unknownError;\n  }\n  return it->second;\n}\n\n// Convert an error from an HTTPSource to one that can be sent using H2.\nErrorCode HTTPErrorCode2ErrorCode(HTTPErrorCode ec, bool fromSource) {\n  if (fromSource && checkInvalidAppErrorCode(ec)) {\n    return ErrorCode::INTERNAL_ERROR;\n  }\n  switch (ec) {\n    case HTTPErrorCode::READ_TIMEOUT:\n    case HTTPErrorCode::_H3_INTERNAL_ERROR:\n    case HTTPErrorCode::INTERNAL_ERROR:\n      return ErrorCode::INTERNAL_ERROR;\n\n    case HTTPErrorCode::REQUEST_REJECTED:\n    case HTTPErrorCode::REFUSED_STREAM:\n      return ErrorCode::REFUSED_STREAM;\n\n    case HTTPErrorCode::REQUEST_CANCELLED:\n    case HTTPErrorCode::CANCEL:\n      return ErrorCode::CANCEL;\n\n    case HTTPErrorCode::_H3_CONNECT_ERROR:\n    case HTTPErrorCode::CONNECT_ERROR:\n      return ErrorCode::CONNECT_ERROR;\n\n    case HTTPErrorCode::EXCESSIVE_LOAD:\n    case HTTPErrorCode::ENHANCE_YOUR_CALM:\n      return ErrorCode::ENHANCE_YOUR_CALM;\n\n    case HTTPErrorCode::VERSION_FALLBACK:\n    case HTTPErrorCode::HTTP_1_1_REQUIRED:\n      return ErrorCode::HTTP_1_1_REQUIRED;\n\n    case HTTPErrorCode::_H3_NO_ERROR:\n    case HTTPErrorCode::NO_ERROR:\n      return ErrorCode::NO_ERROR;\n\n    // There is no H3 version of this\n    case HTTPErrorCode::INADEQUATE_SECURITY:\n      return ErrorCode(ec);\n\n    // This is really an internal code we need to map sometimes\n    case HTTPErrorCode::MESSAGE_ERROR:\n    case HTTPErrorCode::PROTOCOL_ERROR:\n    case HTTPErrorCode::GENERAL_PROTOCOL_ERROR:\n      XCHECK(!fromSource);\n      return ErrorCode::PROTOCOL_ERROR;\n\n    case HTTPErrorCode::FLOW_CONTROL_ERROR:\n      XCHECK(!fromSource);\n      return ErrorCode::FLOW_CONTROL_ERROR;\n\n    default:\n      // Any unknown custom error codes are mapped to INTERNAL_ERROR here.\n      return ErrorCode::INTERNAL_ERROR;\n  }\n}\n\n// Convert an error from an HTTPSource to one that can be sent using H3.\nHTTP3::ErrorCode HTTPErrorCode2HTTP3ErrorCode(HTTPErrorCode ec,\n                                              bool fromSource) {\n  if (fromSource && checkInvalidAppErrorCode(ec)) {\n    return HTTP3::ErrorCode::HTTP_INTERNAL_ERROR;\n  }\n  switch (ec) {\n    // These are all OK to come from the application, return the ErrorCode flav\n    case HTTPErrorCode::READ_TIMEOUT:\n    case HTTPErrorCode::_H3_INTERNAL_ERROR:\n    case HTTPErrorCode::INTERNAL_ERROR:\n      return HTTP3::ErrorCode::HTTP_INTERNAL_ERROR;\n\n    case HTTPErrorCode::REQUEST_REJECTED:\n    case HTTPErrorCode::REFUSED_STREAM:\n      return HTTP3::ErrorCode::HTTP_REQUEST_REJECTED;\n\n    case HTTPErrorCode::REQUEST_CANCELLED:\n    case HTTPErrorCode::CANCEL:\n      return HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED;\n\n    case HTTPErrorCode::_H3_CONNECT_ERROR:\n    case HTTPErrorCode::CONNECT_ERROR:\n      return HTTP3::ErrorCode::HTTP_CONNECT_ERROR;\n\n    case HTTPErrorCode::EXCESSIVE_LOAD:\n    case HTTPErrorCode::ENHANCE_YOUR_CALM:\n      return HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD;\n\n    case HTTPErrorCode::VERSION_FALLBACK:\n    case HTTPErrorCode::HTTP_1_1_REQUIRED:\n      return HTTP3::ErrorCode::HTTP_VERSION_FALLBACK;\n\n    case HTTPErrorCode::_H3_NO_ERROR:\n    case HTTPErrorCode::NO_ERROR:\n      return HTTP3::ErrorCode::HTTP_NO_ERROR;\n\n    // There is no H3 version of this\n    case HTTPErrorCode::INADEQUATE_SECURITY:\n      XLOG(WARNING) << \"Got INADEQUATE_SECURITY for H3 conn\";\n      return HTTP3::ErrorCode::HTTP_INTERNAL_ERROR;\n\n    // This is really an internal code we need to map sometimes\n    case HTTPErrorCode::MESSAGE_ERROR:\n    case HTTPErrorCode::PROTOCOL_ERROR:\n    case HTTPErrorCode::GENERAL_PROTOCOL_ERROR:\n      XCHECK(!fromSource);\n      return HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR;\n\n    default:\n      if (!fromSource && ((ec >= HTTPErrorCode::_H3_NO_ERROR &&\n                           ec <= HTTPErrorCode::VERSION_FALLBACK) ||\n                          (ec >= HTTPErrorCode::QPACK_DECOMPRESSION_FAILED &&\n                           ec <= HTTPErrorCode::QPACK_DECODER_STREAM_ERROR))) {\n        // Some other H3 error from the library\n        return HTTP3::ErrorCode(ec);\n      }\n      // Any unknown custom error codes are mapped to INTERNAL_ERROR here.\n      return HTTP3::ErrorCode::HTTP_INTERNAL_ERROR;\n  }\n}\n\n// TODO: for the below functions -- we should normalize the codec error codes\n// with the same meaning and return only one code\n// Convert an H2 error code read off the wire to an HTTPErrorCode\nHTTPErrorCode ErrorCode2HTTPErrorCode(ErrorCode ec) {\n  if (ec >= ErrorCode::NO_ERROR && ec <= ErrorCode::HTTP_1_1_REQUIRED) {\n    return HTTPErrorCode(ec);\n  }\n  // Any unknown custom error codes are mapped to PROTOCOL_ERROR here.\n  return HTTPErrorCode::PROTOCOL_ERROR;\n}\n\n// Convert an H3 error code read off the wire to an HTTPErrorCode\nHTTPErrorCode HTTP3ErrorCode2HTTPErrorCode(HTTP3::ErrorCode ec) {\n  if ((ec >= HTTP3::ErrorCode::HTTP_NO_ERROR &&\n       ec <= HTTP3::ErrorCode::HTTP_VERSION_FALLBACK) ||\n      (ec >= HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED &&\n       ec <= HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR)) {\n    return HTTPErrorCode(ec);\n  }\n  // Any unknown custom error codes are mapped to PROTOCOL_ERROR here.\n  return HTTPErrorCode::PROTOCOL_ERROR;\n}\n\n// Convert HTTPCodec::onError HTTPException to an HTTPErrorCode\nHTTPErrorCode HTTPException2HTTPErrorCode(const proxygen::HTTPException& ex) {\n  if (ex.hasCodecStatusCode()) {\n    // These come from HTTP2Codec\n    return ErrorCode2HTTPErrorCode(ex.getCodecStatusCode());\n  } else if (ex.hasHttp3ErrorCode()) {\n    return HTTP3ErrorCode2HTTPErrorCode(ex.getHttp3ErrorCode());\n  } else if (ex.hasProxygenError()) {\n    // These come from HTTP1xCodec\n    auto err = ex.getProxygenError();\n    XCHECK_NE(err, proxygen::kErrorNone);\n    switch (err) {\n      case proxygen::kErrorParseHeader:\n        return HTTPErrorCode::HEADER_PARSE_ERROR;\n      case proxygen::kErrorParseBody:\n        return HTTPErrorCode::BODY_PARSE_ERROR;\n      case proxygen::kErrorEOF:\n        return HTTPErrorCode::TRANSPORT_EOF;\n      case proxygen::kErrorUnknown:\n      default:\n        return HTTPErrorCode::PROTOCOL_ERROR;\n    }\n  }\n  return HTTPErrorCode::PROTOCOL_ERROR;\n}\n\nProxygenError HTTPErrorCode2ProxygenError(HTTPErrorCode code) {\n  switch (code) {\n    case HTTPErrorCode::NO_ERROR:\n    case HTTPErrorCode::_H3_NO_ERROR:\n      return kErrorNone;\n    case HTTPErrorCode::GENERAL_PROTOCOL_ERROR:\n    case HTTPErrorCode::MESSAGE_ERROR:\n      return kErrorMessage;\n    case HTTPErrorCode::STREAM_CLOSED:\n    case HTTPErrorCode::PROTOCOL_ERROR:\n      return kErrorStreamAbort;\n    case HTTPErrorCode::CANCEL:\n    case HTTPErrorCode::REQUEST_CANCELLED:\n    case HTTPErrorCode::CORO_CANCELLED:\n      return kErrorCanceled;\n    case HTTPErrorCode::REFUSED_STREAM:\n    case HTTPErrorCode::REQUEST_REJECTED:\n      return kErrorStreamUnacknowledged;\n    case HTTPErrorCode::INVALID_STATE_TRANSITION:\n      return kErrorIngressStateTransition;\n    case HTTPErrorCode::HEADER_PARSE_ERROR:\n      return kErrorParseHeader;\n    case HTTPErrorCode::BODY_PARSE_ERROR:\n    case HTTPErrorCode::CONTENT_LENGTH_MISMATCH:\n      return kErrorParseBody;\n    case HTTPErrorCode::TRANSPORT_READ_ERROR:\n      return kErrorConnectionReset;\n    case HTTPErrorCode::TRANSPORT_WRITE_ERROR:\n      return kErrorWrite;\n    case HTTPErrorCode::TRANSPORT_EOF:\n      return kErrorEOF;\n    case HTTPErrorCode::READ_TIMEOUT:\n      return kErrorTimeout;\n    case HTTPErrorCode::DROPPED:\n      return kErrorDropped;\n    default:\n      return kErrorStreamAbort;\n  }\n}\n\nHTTPException HTTPErrorToHTTPException(const HTTPError& err) {\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, err.what());\n  ex.setProxygenError(HTTPErrorCode2ProxygenError(err.code));\n  auto ec = static_cast<ErrorCode>(err.code);\n  if (ec >= ErrorCode::PROTOCOL_ERROR && ec <= ErrorCode::HTTP_1_1_REQUIRED) {\n    ex.setCodecStatusCode(ec);\n  }\n  if (err.httpMessage) {\n    ex.setPartialMsg(\n        std::make_unique<HTTPMessage>(std::move(*err.httpMessage)));\n  }\n  return ex;\n}\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPError.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <string>\n\nnamespace proxygen {\nclass HTTPException;\n}\n\nnamespace proxygen::coro {\n\n/**\n * HTTP Error Codes\n *\n * This error code can be returned when reading from an HTTPSource.  Users of\n * the library may read this error when reading from a source (either a server\n * reading a request or a client reading a response or push).\n *\n * The library itself may read it from the HTTPSource chain provided by a user\n * of the library (a request via sendRequest or a response via handleRequest).\n *\n * Applications using the library that wish to abort sending can return an error\n * code from readHeaderEvent or readBodyEvent.  Only a subset of codes make\n * sense for the application to send.  For H2, this set is:\n *   INTERNAL_ERROR, REFUSED_STREAM, CANCEL, CONNECT_ERROR, ENHANCE_YOUR_CALM,\n * INADEQUATE_SECURITY, HTTP_1_1_REQUIRED.\n *\n * Any other errors returned by applications will be mapped to one of these.\n *\n * Philosophy:\n *\n *  We want to have a single error code enum that captures the following:\n *\n *   1) Any error code that can be received on the wire according to any\n *      official HTTP protocol or supported extension.  This way, the library\n *      can pass the code directly to the caller.\n *   2) Errors detected by the library that are not passed on the wire, such as\n *      timeouts or transport errors.\n *\n * == DO NOT MODIFY this list of errors unless you know what you are doing ==\n */\n// clang-format off\n#define HTTP_ERROR_GEN_VALUES(n, v) \\\n  /* HTTP/2 error codes, from the RFC */ \\\n  n(NO_ERROR)               v(0),\\\n  n(PROTOCOL_ERROR)         v(1),\\\n  n(INTERNAL_ERROR)         v(2),\\\n  n(FLOW_CONTROL_ERROR)     v(3),\\\n  n(SETTINGS_TIMEOUT)       v(4),\\\n  n(STREAM_CLOSED)          v(5),\\\n  n(FRAME_SIZE_ERROR)       v(6),\\\n  n(REFUSED_STREAM)         v(7),\\\n  n(CANCEL)                 v(8),\\\n  n(COMPRESSION_ERROR)      v(9),\\\n  n(CONNECT_ERROR)         v(10),\\\n  n(ENHANCE_YOUR_CALM)     v(11),\\\n  n(INADEQUATE_SECURITY)   v(12),\\\n  n(HTTP_1_1_REQUIRED)     v(13),\\\n  \\\n  \\\n  /*  HTTP/3 error codes */                \\\n  n(_H3_NO_ERROR)                v(0x100),\\\n  n(GENERAL_PROTOCOL_ERROR)      v(0x101),\\\n  n(_H3_INTERNAL_ERROR)          v(0x102),\\\n  n(STREAM_CREATION_ERROR)       v(0x103),\\\n  n(CLOSED_CRITICAL_STREAM)      v(0x104),\\\n  n(FRAME_UNEXPECTED)            v(0x105),\\\n  n(FRAME_ERROR)                 v(0x106),\\\n  n(EXCESSIVE_LOAD)              v(0x107), /* ENHANCE_YOUR_CALM */ \\\n  n(ID_ERROR)                    v(0x108),\\\n  n(SETTINGS_ERROR)              v(0x109),\\\n  n(MISSING_SETTINGS)            v(0x10A),\\\n  n(REQUEST_REJECTED)            v(0x10B),  /* REFUSED_STREAM */ \\\n  n(REQUEST_CANCELLED)           v(0x10C), /* CANCEL */ \\\n  n(INCOMPLETE_REQUEST)          v(0x10D),\\\n  n(MESSAGE_ERROR)               v(0x10E),\\\n  n(_H3_CONNECT_ERROR)           v(0x10F),\\\n  n(VERSION_FALLBACK)            v(0x110),\\\n  n(QPACK_DECOMPRESSION_FAILED)  v(0x200),\\\n  n(QPACK_ENCODER_STREAM_ERROR)  v(0x201),\\\n  n(QPACK_DECODER_STREAM_ERROR)  v(0x202),\\\n  \\\n  \\\n  /* Higher level/more specific error codes returned by the library but */\\\n  /* never transmitted on the wire                                      */\\\n  n(INVALID_STATE_TRANSITION)     /* HTTP state machine violation */    \\\n                        v(0x1000),\\\n  n(HEADER_PARSE_ERROR) v(0x1001),/* Error parsing HTTP headers */ \\\n  n(BODY_PARSE_ERROR)   v(0x1002),/* Error parsing HTTP body */\\\n  n(READ_TIMEOUT)       v(0x1003),/* Timed out waiting for header/body event */\\\n  n(WRITE_TIMEOUT)      v(0x1004),/* Socket write timeout */\\\n  n(CORO_CANCELLED)     v(0x1005),/* Caller cancelled the coroutine */\\\n  n(TRANSPORT_EOF)      v(0x1006),/* EOF read before the stream was complete */\\\n  n(TRANSPORT_READ_ERROR)         /* Error reading from the transport */\\\n                        v(0x1007),\\\n  n(TRANSPORT_WRITE_ERROR)        /* Error writing to the transport */ \\\n                        v(0x1008),\\\n  n(DROPPED)            v(0x1009),/* Caller dropped session with open streams */\\\n  n(CONTENT_LENGTH_MISMATCH)      /* Content-Length did not match what was observed */\\\n                        v(0x100A)\n  // ==DO NOT MODIFY this list of errors unless you know what you are doing ==\n// clang-format on\n\n#define HTTP_ERROR_ENUM(error) error\n\n#define HTTP_ERROR_VALUE(value) = value\n\nenum class HTTPErrorCode : uint16_t {\n  HTTP_ERROR_GEN_VALUES(HTTP_ERROR_ENUM, HTTP_ERROR_VALUE)\n};\n\n#undef HTTP_ERROR_ENUM\n#undef HTTP_ERROR_VALUE\n\n#define HTTP_ERROR_NO_VALUE(value)\n#define HTTP_ERROR_GEN(x) HTTP_ERROR_GEN_VALUES(x, HTTP_ERROR_NO_VALUE)\n\nconst std::string& getErrorString(HTTPErrorCode ec);\n\n// Helpers to normalize H2/H3 wire error codes to a single semantic\ninline bool isInternalError(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::_H3_INTERNAL_ERROR ||\n         ec == HTTPErrorCode::INTERNAL_ERROR;\n}\ninline bool isRequestRejected(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::REQUEST_REJECTED ||\n         ec == HTTPErrorCode::REFUSED_STREAM;\n}\ninline bool isCancelled(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::REQUEST_CANCELLED || ec == HTTPErrorCode::CANCEL;\n}\n\ninline bool isConnectError(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::_H3_CONNECT_ERROR ||\n         ec == HTTPErrorCode::CONNECT_ERROR;\n}\n\ninline bool isExcessiveLoad(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::EXCESSIVE_LOAD ||\n         ec == HTTPErrorCode::ENHANCE_YOUR_CALM;\n}\n\ninline bool isVersionFallback(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::VERSION_FALLBACK ||\n         ec == HTTPErrorCode::HTTP_1_1_REQUIRED;\n}\n\ninline bool isProtocolError(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::PROTOCOL_ERROR ||\n         ec == HTTPErrorCode::GENERAL_PROTOCOL_ERROR;\n}\n\ninline bool isNoError(HTTPErrorCode ec) {\n  return ec == HTTPErrorCode::NO_ERROR || ec == HTTPErrorCode::_H3_NO_ERROR;\n}\n\n/**\n * Simple error structure\n */\nstruct HTTPError : public std::exception {\n  HTTPErrorCode code;\n  std::string msg;\n  std::unique_ptr<HTTPMessage> httpMessage;\n\n  explicit HTTPError(HTTPErrorCode inCode)\n      : code(inCode), msg(getErrorString(inCode)) {\n  }\n\n  HTTPError(HTTPErrorCode inCode, std::string inMsg)\n      : code(inCode), msg(std::move(inMsg)) {\n  }\n\n  HTTPError(HTTPError&& goner) = default;\n  HTTPError& operator=(HTTPError&& goner) = default;\n  HTTPError(const HTTPError& other) : code(other.code), msg(other.msg) {\n    if (other.httpMessage) {\n      httpMessage = std::make_unique<HTTPMessage>(*other.httpMessage);\n    }\n  }\n  HTTPError& operator=(const HTTPError& other) {\n    code = other.code;\n    msg = other.msg;\n    if (other.httpMessage) {\n      httpMessage = std::make_unique<HTTPMessage>(*other.httpMessage);\n    }\n    return *this;\n  }\n\n  [[nodiscard]] std::string describe() const {\n    return folly::to<std::string>(\n        \"error=\", getErrorString(code), \"(\", code, \") msg=\", msg);\n  }\n\n  [[nodiscard]] const char* what() const noexcept override {\n    return msg.c_str();\n  }\n};\n\n// Convert an error from an HTTPSource to one that can be sent using H2/H3.\n// Can be used for lib internal errors also, by passing fromSource=false\nErrorCode HTTPErrorCode2ErrorCode(HTTPErrorCode ec, bool fromSource = true);\nHTTP3::ErrorCode HTTPErrorCode2HTTP3ErrorCode(HTTPErrorCode ec,\n                                              bool fromSource = true);\n\n// Convert an H2/H3 error code read off the wire to an HTTPErrorCode\nHTTPErrorCode ErrorCode2HTTPErrorCode(ErrorCode ec);\nHTTPErrorCode HTTP3ErrorCode2HTTPErrorCode(HTTP3::ErrorCode ec);\n\n// Convert HTTP1xCodec::onError HTTPException to an HTTPErrorCode\nHTTPErrorCode HTTPException2HTTPErrorCode(const proxygen::HTTPException& ex);\n\n// convert HTTPError to ProxygenError (usage of ProxygenError is discouraged)\nProxygenError HTTPErrorCode2ProxygenError(HTTPErrorCode code);\n\n// convert HTTPError to HTTPException (usage of HTTPException is discouraged)\nHTTPException HTTPErrorToHTTPException(const HTTPError& err);\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPEvents.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPEvents.h\"\n#include \"proxygen/lib/http/coro/HTTPSource.h\"\n\nnamespace proxygen::coro {\n\nHTTPPushEvent::~HTTPPushEvent() {\n  if (pushSource_) {\n    pushSource_->stopReading(folly::none);\n    pushSource_ = nullptr;\n  }\n}\n\nvoid HTTPHeaderEvent::describe(std::ostream& os) const {\n  os << \"HTTPHeaderEvent, final=\" << ((isFinal()) ? \"true\" : \"false\")\n     << \", eom=\" << (eom ? \"true\" : \"false\");\n  os << *headers;\n}\n\nvoid HTTPPushEvent::describe(std::ostream& os) const {\n  os << *promise;\n}\n\nvoid HTTPBodyEvent::describe(std::ostream& os) const {\n  os << \"HTTPBodyEvent: eom=\" << (eom ? \"true\" : \"false\") << \", \";\n  switch (eventType) {\n    case BODY:\n      os << \"type=BODY, len=\" << event.body.chainLength();\n      break;\n    case DATAGRAM:\n      os << \"type=DATAGRAM, len=\" << event.datagram->computeChainDataLength();\n      break;\n    case PUSH_PROMISE:\n      os << \"type=PUSH_PROMISE, promise: \" << event.push;\n      break;\n    case TRAILERS:\n      os << \"type=TRAILERS, trailers: \";\n      event.trailers->forEach(\n          [&os](const std::string& name, const std::string& value) {\n            os << \" \" << stripCntrlChars(name) << \": \" << stripCntrlChars(value)\n               << std::endl;\n          });\n      break;\n    case SUSPEND:\n      os << \"type=SUSPEND\";\n      break;\n    default:\n      // no-op\n      break;\n  }\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPHeaderEvent& event) {\n  event.describe(os);\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPPushEvent& event) {\n  event.describe(os);\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPBodyEvent& event) {\n  event.describe(os);\n  return os;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPEvents.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPByteEvents.h\"\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n#include <folly/coro/Task.h>\n#include <folly/io/IOBuf.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <quic/common/BufUtil.h>\n\nnamespace proxygen::coro {\n\nclass HTTPSource;\n\nusing BufQueue = quic::BufQueue;\n\n/**\n * An Event containing HTTP headers.\n *\n * Request headers are always final.\n * Response headers can be non-final if they have a 1xx status code\n */\nstruct HTTPHeaderEvent {\n  std::unique_ptr<HTTPMessage> headers;\n  bool eom{false};\n  std::vector<HTTPByteEventRegistration> byteEventRegistrations;\n\n  // Placeholder until real ByteEvent callbacks are implemented\n  folly::Function<void(HTTPHeaderSize headerSize) noexcept> egressHeadersFn{\n      nullptr};\n\n  HTTPHeaderEvent(std::unique_ptr<HTTPMessage> inHeaders, bool inEOM)\n      : headers(std::move(inHeaders)), eom(inEOM) {\n    XCHECK(headers->isFinal() || !eom);\n  }\n\n  HTTPHeaderEvent(HTTPHeaderEvent&& goner) = default;\n  HTTPHeaderEvent& operator=(HTTPHeaderEvent&& goner) = default;\n\n  bool isFinal() const noexcept {\n    return headers->isFinal();\n  }\n\n  void describe(std::ostream& os) const;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPHeaderEvent& headerEvent);\n\nstruct HTTPPushEvent {\n  std::unique_ptr<HTTPMessage> promise;\n\n  HTTPPushEvent(std::unique_ptr<HTTPMessage> p, HTTPSource* pSource)\n      : promise(std::move(p)), pushSource_(pSource) {\n  }\n\n  HTTPPushEvent(HTTPPushEvent&& goner)\n      : promise(std::move(goner.promise)), pushSource_(goner.pushSource_) {\n    goner.pushSource_ = nullptr;\n  }\n\n  HTTPPushEvent& operator=(HTTPPushEvent&& goner) {\n    promise = std::move(goner.promise);\n    pushSource_ = goner.pushSource_;\n    goner.pushSource_ = nullptr;\n    return *this;\n  }\n\n  ~HTTPPushEvent();\n\n  // This object owns the push source and will automatically abort it on delete.\n  // Call this method to take ownership of the push source, at which point the\n  // caller must read to EOM or call stopReading.\n  HTTPSource* movePushSource() {\n    auto pushSource = pushSource_;\n    pushSource_ = nullptr;\n    return pushSource;\n  }\n\n  void describe(std::ostream& os) const;\n\n private:\n  HTTPSource* pushSource_;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPPushEvent& pushEvent);\n\n/**\n * An Event containing part of an HTTP stream after the headers.  It can include\n *\n * BODY - some data\n * TRAILERS - metadata block. The EOM flag is always true for trailers.\n * PUSH_PROMISE - The beginning of a server push\n *                Only response streams can contain PUSH_PROMISE\n *                This needs work.  Probably needs to include an HTTPSource\n *                For the push response?\n * UPGRADE - TODO - this is meant to handle HTTP/1.x or CONNECT style upgrades\n *\n */\nstruct HTTPBodyEvent {\n  enum EventType : uint8_t {\n    UPGRADE,\n    BODY,\n    DATAGRAM,\n    PUSH_PROMISE,\n    TRAILERS,\n    SUSPEND,\n    PADDING,\n  } eventType;\n  bool eom{false};\n\n  union EventData {\n    BufQueue body;\n    std::unique_ptr<folly::IOBuf> datagram;\n    HTTPPushEvent push;\n    std::unique_ptr<HTTPHeaders> trailers;\n    folly::coro::Task<TimedBaton::Status> resume;\n    // @lint-ignore CLANGTIDY cppcoreguidelines-pro-type-member-init\n    uint16_t paddingSize;\n    EventData() {\n    }\n    ~EventData() {\n    }\n  } event;\n\n  std::vector<HTTPByteEventRegistration> byteEventRegistrations;\n\n  HTTPBodyEvent(HTTPBodyEvent&& goner) {\n    eventType = goner.eventType;\n    eom = goner.eom;\n    byteEventRegistrations = std::move(goner.byteEventRegistrations);\n    switch (eventType) {\n      case BODY:\n        new (&event.body) BufQueue(std::move(goner.event.body));\n        break;\n      case DATAGRAM:\n        new (&event.datagram)\n            std::unique_ptr<folly::IOBuf>(std::move(goner.event.datagram));\n        break;\n      case PUSH_PROMISE:\n        new (&event.push) HTTPPushEvent(std::move(goner.event.push));\n        break;\n      case TRAILERS:\n        new (&event.trailers)\n            std::unique_ptr<HTTPHeaders>(std::move(goner.event.trailers));\n        break;\n      case SUSPEND:\n        new (&event.resume) folly::coro::Task<TimedBaton::Status>(\n            std::move(goner.event.resume));\n        break;\n      case PADDING:\n        event.paddingSize = goner.event.paddingSize;\n        break;\n      case UPGRADE:\n        [[fallthrough]];\n      default:\n        // no-op\n        break;\n    }\n  }\n\n  HTTPBodyEvent& operator=(HTTPBodyEvent&& goner) {\n    // Destroy this object first\n    this->~HTTPBodyEvent();\n\n    eventType = goner.eventType;\n    eom = goner.eom;\n    byteEventRegistrations = std::move(goner.byteEventRegistrations);\n    switch (eventType) {\n      case BODY:\n        new (&event.body) BufQueue(std::move(goner.event.body));\n        break;\n      case DATAGRAM:\n        new (&event.datagram)\n            std::unique_ptr<folly::IOBuf>(std::move(goner.event.datagram));\n        break;\n      case PUSH_PROMISE:\n        new (&event.push) HTTPPushEvent(std::move(goner.event.push));\n        break;\n      case TRAILERS:\n        new (&event.trailers)\n            std::unique_ptr<HTTPHeaders>(std::move(goner.event.trailers));\n        break;\n      case SUSPEND:\n        new (&event.resume) folly::coro::Task<TimedBaton::Status>(\n            std::move(goner.event.resume));\n        break;\n      case PADDING:\n        event.paddingSize = goner.event.paddingSize;\n        break;\n      case UPGRADE:\n        [[fallthrough]];\n      default:\n        // no-op\n        break;\n    }\n    return *this;\n  }\n\n  ~HTTPBodyEvent() {\n    switch (eventType) {\n      case BODY:\n        event.body.~BufQueue();\n        break;\n      case DATAGRAM:\n        event.datagram.reset();\n        break;\n      case PUSH_PROMISE:\n        event.push.~HTTPPushEvent();\n        break;\n      case TRAILERS:\n        event.trailers.reset();\n        break;\n      case SUSPEND:\n        event.resume.~Task<TimedBaton::Status>();\n        break;\n      case PADDING:\n        // Nothing special to clean up a uint16_t\n        [[fallthrough]];\n      case UPGRADE:\n        [[fallthrough]];\n      default:\n        // no-op\n        break;\n    }\n  }\n\n  // Default ctor makes an empty body event with no data and no EOM\n  HTTPBodyEvent()\n      : HTTPBodyEvent(std::unique_ptr<folly::IOBuf>(nullptr), false) {\n  }\n\n  HTTPBodyEvent(std::unique_ptr<folly::IOBuf> body, bool inEOM)\n      : eventType(EventType::BODY), eom(inEOM) {\n    new (&event.body) BufQueue(std::move(body));\n  }\n\n  HTTPBodyEvent(BufQueue body, bool inEOM)\n      : eventType(EventType::BODY), eom(inEOM) {\n    new (&event.body) BufQueue(std::move(body));\n  }\n\n  // Datagram can't have EOM\n  enum class Datagram {};\n  HTTPBodyEvent(Datagram, std::unique_ptr<folly::IOBuf> datagram)\n      : eventType(EventType::DATAGRAM), eom(false) {\n    new (&event.datagram) std::unique_ptr<folly::IOBuf>(std::move(datagram));\n  }\n\n  enum class Padding {};\n  HTTPBodyEvent(Padding, uint16_t padding)\n      : eventType(EventType::PADDING), eom(false) {\n    event.paddingSize = padding;\n  }\n\n  HTTPBodyEvent(std::unique_ptr<HTTPMessage> promise,\n                HTTPSource* source,\n                bool inEOM)\n      : eventType(EventType::PUSH_PROMISE), eom(inEOM) {\n    new (&event.push) HTTPPushEvent(std::move(promise), source);\n  }\n\n  explicit HTTPBodyEvent(std::unique_ptr<HTTPHeaders> trailers)\n      : eventType(EventType::TRAILERS), eom(true) {\n    new (&event.trailers) std::unique_ptr<HTTPHeaders>(std::move(trailers));\n  }\n\n  explicit HTTPBodyEvent(folly::coro::Task<TimedBaton::Status> resume)\n      : eventType(EventType::SUSPEND), eom(false) {\n    new (&event.resume)\n        folly::coro::Task<TimedBaton::Status>(std::move(resume));\n  }\n\n  void describe(std::ostream& os) const;\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPBodyEvent& bodyEvent);\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPFilterFactoryHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPFilterFactoryHandler.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilterChain.h\"\n#include \"proxygen/lib/http/coro/filters/FilterFactory.h\"\n\nnamespace proxygen::coro {\n\nfolly::coro::Task<HTTPSourceHolder> HTTPFilterFactoryHandler::handleRequest(\n    folly::EventBase* evb,\n    HTTPSessionContextPtr ctx,\n    HTTPSourceHolder requestSource) {\n  // construct request & response filter chains (in supplied order for request\n  // chain and reverse order for response chain) maintaining the order in which\n  // the filter factories were added\n  proxygen::coro::FilterChain reqChain, resChain;\n  reqChain.setSource(requestSource.release());\n  for (auto& filterFactory : filterFactories_) {\n    auto [reqFilter, resFilter] = filterFactory->makeFilters();\n    if (reqFilter) {\n      reqChain.insertFront(reqFilter);\n    }\n    if (resFilter) {\n      resChain.insertEnd(resFilter);\n    }\n  }\n\n  // hand off request to the next handler\n  CHECK(getNextHandler());\n  auto nextHandlerResult = co_await co_awaitTry(\n      getNextHandler()->handleRequest(evb, std::move(ctx), reqChain.release()));\n\n  if (nextHandlerResult.hasValue() && nextHandlerResult->readable()) {\n    resChain.setSource(nextHandlerResult->release());\n    co_return resChain.release();\n  }\n\n  /**\n   * If either handleRequest yielded an error, or unreadable source (i.e\n   * handleRequest returned nullptr), wrap in ErrorSource to execute response\n   * filters. If the user returned unreadable source, we have to create a 500\n   * status code response source (similar to HTTPCoroSession) here so we can\n   * execute the response filters.\n   */\n  auto* errorSource = nextHandlerResult.hasException()\n                          ? new HTTPErrorSource(getHTTPError(nextHandlerResult),\n                                                /*heapAllocated=*/true)\n                          : getErrorResponse(500);\n  resChain.setSource(errorSource);\n\n  co_return resChain.release();\n}\n\nHTTPFilterFactoryHandler&& HTTPFilterFactoryHandler::addFilterFactory(\n    std::shared_ptr<FilterFactory>&& ff) {\n  if (ff) {\n    filterFactories_.push_back(std::move(ff));\n  }\n  return std::move(*this);\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPFilterFactoryHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPHandlerChain.h\"\n\nnamespace proxygen::coro {\n\nclass FilterFactory;\n\n/*\n * This handler is designed to install a list of filters generated by\n * FilterFactories on a request and response. It doesn't do any actual\n * processing, so it entirely depends on at least one additional HTTPHandler to\n * carry out the request and generate a response.\n *\n * e.g. if FilterFactoryList looked like this:\n * {[reqFilterA, resFilterA], [reqFilterB, nullptr], [nullptr, resFilterC]}\n * the resulting chains would be:\n * request chain: reqFilterA -> reqFilterB -> requestSource\n * response chain: resFilterA -> resFilterC -> responseSource\n */\nclass HTTPFilterFactoryHandler : public HTTPChainHandler {\n public:\n  HTTPFilterFactoryHandler() = default;\n  ~HTTPFilterFactoryHandler() override = default;\n\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override;\n\n  HTTPFilterFactoryHandler&& addFilterFactory(\n      std::shared_ptr<FilterFactory>&& ff);\n\n private:\n  using FilterFactoryList = std::vector<std::shared_ptr<FilterFactory>>;\n  FilterFactoryList filterFactories_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPFixedSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/coro/HTTPSource.h>\n#include <proxygen/lib/utils/URL.h>\n\nnamespace proxygen::coro {\n\n/**\n * An HTTP source from an in memory source.\n *\n * Useful to be the head of a chain of sources\n *\n * h2upstreamSession.newRequest(HTTPRateLimitSource(HTTPFixedSource(msg,\n * body)));\n */\n\nclass HTTPFixedSource : public HTTPSource {\n public:\n  explicit HTTPFixedSource(std::unique_ptr<HTTPMessage> msg,\n                           std::unique_ptr<folly::IOBuf> body = nullptr)\n      : msg_(std::move(msg)) {\n    if (body) {\n      body_.append(std::move(body));\n      msg_->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH,\n                             folly::to<std::string>(body_.chainLength()));\n    }\n  }\n\n  explicit HTTPFixedSource(std::unique_ptr<folly::IOBuf> body) {\n    body_.append(std::move(body));\n  }\n\n  // Copy constructor that does a deep copy of the provided Source\n  HTTPFixedSource(const HTTPFixedSource& source)\n      : HTTPFixedSource(std::make_unique<HTTPMessage>(*source.msg_),\n                        source.body_.clone()) {\n  }\n\n  HTTPFixedSource(HTTPFixedSource&& source) noexcept\n      : HTTPFixedSource(std::move(source.msg_), source.body_.move()) {\n  }\n\n  static HTTPFixedSource* makeFixedSource(\n      std::unique_ptr<HTTPMessage> msg,\n      std::unique_ptr<folly::IOBuf> body = nullptr) {\n    auto source = new HTTPFixedSource(std::move(msg), std::move(body));\n    source->setHeapAllocated();\n    return source;\n  }\n\n  static HTTPFixedSource* makeFixedBody(std::unique_ptr<folly::IOBuf> body) {\n    auto resp = new HTTPFixedSource(std::move(body));\n    resp->setHeapAllocated();\n    return resp;\n  }\n\n  static HTTPFixedSource* makeFixedRequest(\n      URL url,\n      HTTPMethod method = HTTPMethod::GET,\n      std::unique_ptr<folly::IOBuf> body = nullptr) {\n    auto req = makeFixedRequest(url.makeRelativeURL(), method, std::move(body));\n    req->msg_->setSecure(url.isSecure());\n    if (url.hasHost()) {\n      req->msg_->getHeaders().set(HTTP_HEADER_HOST,\n                                  url.getHostAndPortOmitDefault());\n    }\n    return req;\n  }\n  static HTTPFixedSource* makeFixedRequest(\n      std::string path,\n      HTTPMethod method = HTTPMethod::GET,\n      std::unique_ptr<folly::IOBuf> body = nullptr) {\n    auto msg = std::make_unique<HTTPMessage>();\n    msg->setURL(std::move(path));\n    msg->setMethod(method);\n    msg->setHTTPVersion(1, 1);\n    auto resp = new HTTPFixedSource(std::move(msg), std::move(body));\n    resp->setHeapAllocated();\n    return resp;\n  }\n\n  static HTTPFixedSource* makeFixedResponse(uint16_t statusCode,\n                                            std::string body) {\n    return makeFixedResponse(statusCode,\n                             folly::IOBuf::fromString(std::move(body)));\n  }\n\n  static HTTPFixedSource* makeFixedResponse(\n      uint16_t statusCode, std::unique_ptr<folly::IOBuf> body = nullptr) {\n    auto msg = std::make_unique<HTTPMessage>();\n    msg->setStatusCode(statusCode);\n    msg->setStatusMessage(HTTPMessage::getDefaultReason(statusCode));\n    msg->setHTTPVersion(1, 1);\n    auto resp = new HTTPFixedSource(std::move(msg), std::move(body));\n    resp->setHeapAllocated();\n    return resp;\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    HTTPHeaderEvent event(std::move(msg_), body_.empty());\n    auto guard = folly::makeGuard(lifetime(event));\n    co_return event;\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    if (!pushes_.empty()) {\n      auto res = std::move(pushes_.front());\n      pushes_.pop_front();\n      co_return res;\n    }\n    if (body_.empty() && trailers_) {\n      HTTPBodyEvent event(std::move(trailers_));\n      auto guard = folly::makeGuard(lifetime(event));\n      co_return event;\n    } else {\n      auto body = body_.splitAtMost(max);\n      HTTPBodyEvent event(std::move(body), body_.empty() && !trailers_);\n      auto guard = folly::makeGuard(lifetime(event));\n      co_return event;\n    }\n  }\n\n  void stopReading(folly::Optional<const HTTPErrorCode>) override {\n    if (heapAllocated_) {\n      delete this;\n    }\n  }\n\n  void setReadTimeout(std::chrono::milliseconds) override {\n  }\n\n  std::unique_ptr<HTTPMessage> msg_;\n  std::list<HTTPBodyEvent> pushes_;\n  std::unique_ptr<HTTPHeaders> trailers_;\n  BufQueue body_;\n};\n\nclass HTTPErrorSource : public HTTPSource {\n public:\n  explicit HTTPErrorSource(HTTPError error, bool heapAllocated = true)\n      : error_(std::move(error)) {\n    if (heapAllocated) {\n      setHeapAllocated();\n    }\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    auto error = std::move(error_);\n    if (heapAllocated_) {\n      delete this;\n    }\n    co_yield folly::coro::co_error(error);\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t /*max = std::numeric_limits<uint32_t>::max()*/) override {\n    XLOG(FATAL) << \"Unreachable\";\n  }\n\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> = folly::none) override {\n    if (heapAllocated_) {\n      delete this;\n    }\n  }\n\n  void setReadTimeout(std::chrono::milliseconds) override {\n  }\n\n  HTTPError error_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPHandlerChain.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPHandlerChain.h\"\n\nnamespace proxygen::coro {\n\nvoid HTTPHandlerChain::insertFront(std::shared_ptr<HTTPChainHandler> handler) {\n  if (!handler) {\n    return;\n  }\n\n  if (front_) {\n    handler->setNextHandler(std::move(front_));\n  }\n  front_ = std::move(handler);\n\n  if (!back_) {\n    back_ = front_;\n  }\n}\n\nvoid HTTPHandlerChain::insertBack(std::shared_ptr<HTTPChainHandler> handler) {\n  if (!handler) {\n    return;\n  }\n\n  if (back_) {\n    back_->setNextHandler(handler);\n  }\n  back_ = std::move(handler);\n\n  if (!front_) {\n    front_ = back_;\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPHandlerChain.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n\nnamespace proxygen::coro {\n\nclass HTTPChainHandler : public HTTPHandler {\n public:\n  HTTPChainHandler() = default;\n  ~HTTPChainHandler() override = default;\n\n  std::shared_ptr<HTTPHandler> getNextHandler() {\n    return nextHandler_;\n  }\n\n  void setNextHandler(std::shared_ptr<HTTPHandler> nextHandler) {\n    nextHandler_ = std::move(nextHandler);\n  }\n\n protected:\n  std::shared_ptr<HTTPHandler> nextHandler_{nullptr};\n};\n\nclass HTTPHandlerChain {\n public:\n  HTTPHandlerChain() = default;\n  ~HTTPHandlerChain() = default;\n\n  std::shared_ptr<HTTPChainHandler> getFront() {\n    return front_;\n  }\n  std::shared_ptr<HTTPChainHandler> getBack() {\n    return back_;\n  }\n\n  void insertFront(std::shared_ptr<HTTPChainHandler> handler);\n  void insertBack(std::shared_ptr<HTTPChainHandler> handler);\n\n private:\n  std::shared_ptr<HTTPChainHandler> front_{nullptr};\n  std::shared_ptr<HTTPChainHandler> back_{nullptr};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPHybridSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n\nnamespace proxygen::coro {\n\n/**\n * A Hybrid source that uses a fixed HTTP message for the headers but another\n * source for body events.\n */\nclass HTTPHybridSource : public HTTPSourceFilter {\n public:\n  HTTPHybridSource(std::unique_ptr<HTTPMessage> headers, HTTPSource* source)\n      : headerEvent_(std::move(headers), !source) {\n    if (source) {\n      setSource(source);\n    }\n  }\n\n  HTTPHybridSource(std::unique_ptr<HTTPMessage> headers,\n                   HTTPSourceHolder source)\n      : headerEvent_(std::move(headers), !source) {\n    if (source) {\n      setSource(source.release());\n    }\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    if (headerEventAvailable_) {\n      headerEventAvailable_ = false;\n      auto guard = folly::makeGuard(lifetime(headerEvent_));\n      co_return std::move(headerEvent_);\n    } else {\n      co_return co_await readHeaderEventImpl();\n    }\n  }\n\n  void stopReading(folly::Optional<const HTTPErrorCode> error) override {\n    if (bool((HTTPSourceFilter&)*this)) {\n      HTTPSourceFilter::stopReading(error);\n    } else if (heapAllocated_) {\n      delete this;\n    }\n  }\n\n private:\n  bool headerEventAvailable_{true};\n  HTTPHeaderEvent headerEvent_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Function.h>\n#include <folly/ScopeGuard.h>\n#include <folly/coro/Task.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/coro/HTTPError.h>\n#include <proxygen/lib/http/coro/HTTPEvents.h>\n\nnamespace proxygen::coro {\n\n/*\n * Abstract class for producing an HTTP message.\n *\n * The normal flow is as follows:\n *\n * do {\n *   headerEvent = co_await co_awaitTry(source.readHeaderEvent());\n *   if (headerEvent.hasException()) {\n *     // handle error\n *     return;\n *   }\n *   // use headers\n * } while (!headerEvent->final);\n *\n * // Note: while loop is unnecessary for requests, the first header event\n * // will be final\n *\n * bool eom = headerEvent->eom;\n * while (!eom) {\n *   bodyEvent = co_await co_awaitTry(source.readBodyEvent(max));\n *   if (bodyEvent.hasException()) {\n *     // handle error\n *     return;\n *   }\n *   eom = bodyEvent->eom;\n *   switch (bodyEvent.eventType) {\n *     case BODY:\n *       // use body\n *     // other cases, PUSH_PROMISE, TRAILERS\n *   }\n * }\n *\n * Read events until you read an error, or an EOM.  Once you read an error or\n * EOM, assume your source is gone.  If you are implenting a source, you should\n * free all resources once you have returned error or EOM.\n *\n * If you wish to stop reading from a source before reading error or EOM,\n * you must call stopReading().\n *\n * To return an error from a source, use HTTPError - see HTTPError.h for details\n *   co_yield folly:coro::co_error(HTTPError(HTTPErrorCode, msg))\n *\n * Some sources could be allocated on coroutine stacks, or belong to bigger\n * objects, so do not need deallocation.  If you heap allocate a source, you\n * can use the lifetime function with folly::makeGuard to help ensure timely\n * destruction of your source.\n */\nclass HTTPSource {\n public:\n  virtual ~HTTPSource() = default;\n\n  virtual folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() = 0;\n\n  virtual folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) = 0;\n\n  // Call stopReading if you do not intend to read the object to completion\n  virtual void stopReading(\n      folly::Optional<const HTTPErrorCode> = folly::none) = 0;\n\n  [[nodiscard]] virtual folly::Optional<uint64_t> getStreamID() const {\n    return folly::none;\n  }\n\n  virtual void setReadTimeout(std::chrono::milliseconds /*timeout*/) {\n  }\n\n  void setHeapAllocated() {\n    heapAllocated_ = true;\n  }\n\n protected:\n  // Lambda helpers to pass to folly::makeGuard in derived implementations\n  // of readHeaderEvent and readBodyEvent\n  folly::Function<void()> lifetime(folly::Try<HTTPHeaderEvent>& event) {\n    return [this, &event] {\n      if (heapAllocated_ && (event.hasException() || event->eom)) {\n        delete this;\n      }\n    };\n  }\n  folly::Function<void()> lifetime(folly::Try<HTTPBodyEvent>& event) {\n    return [this, &event] {\n      if (heapAllocated_ && (event.hasException() || event->eom)) {\n        delete this;\n      }\n    };\n  }\n  folly::Function<void()> lifetime(HTTPHeaderEvent& event) {\n    return [this, &event] {\n      if (heapAllocated_ && event.eom) {\n        delete this;\n      }\n    };\n  }\n  folly::Function<void()> lifetime(HTTPBodyEvent& event) {\n    return [this, &event] {\n      if (heapAllocated_ && event.eom) {\n        delete this;\n      }\n    };\n  }\n\n  bool heapAllocated_{false};\n};\n\ntemplate <typename T>\nHTTPError getHTTPError(const folly::Try<T>& tryEvent) {\n  XCHECK(tryEvent.hasException());\n  auto httpErr = tryEvent.template tryGetExceptionObject<HTTPError>();\n  if (httpErr) {\n    return std::move(*httpErr);\n  }\n  auto ex = tryEvent.tryGetExceptionObject();\n  XCHECK(ex);\n  return HTTPError(HTTPErrorCode::INTERNAL_ERROR, ex->what());\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n\nnamespace proxygen::coro {\n\nHTTPSourceFilter::~HTTPSourceFilter() {\n  if (source_) {\n    source_->stopReading();\n  }\n}\n\nfolly::coro::Task<HTTPHeaderEvent> HTTPSourceFilter::readHeaderEventImpl(\n    bool deleteOnDone) {\n  XCHECK(source_);\n  auto event = co_await co_awaitTry(source_->readHeaderEvent());\n  if (event.hasException() || event->eom) {\n    source_ = nullptr;\n    if (heapAllocated_ && deleteOnDone) {\n      delete this;\n    }\n  }\n  co_return event;\n}\n\nfolly::coro::Task<HTTPBodyEvent> HTTPSourceFilter::readBodyEventImpl(\n    uint32_t max, bool deleteOnDone) {\n  XCHECK(source_);\n  auto event = co_await co_awaitTry(source_->readBodyEvent(max));\n  if (event.hasException() || event->eom) {\n    source_ = nullptr;\n    if (heapAllocated_ && deleteOnDone) {\n      delete this;\n    }\n  }\n  co_return event;\n}\n\nvoid HTTPSourceFilter::stopReading(folly::Optional<const HTTPErrorCode> error) {\n  XCHECK(source_);\n  source_->stopReading(error);\n  source_ = nullptr;\n  if (heapAllocated_) {\n    delete this;\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSource.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\n/**\n * A source that reads from another source.\n */\nclass HTTPSourceFilter : public HTTPSource {\n public:\n  HTTPSourceFilter() = default;\n  ~HTTPSourceFilter() override;\n\n  /* implicit */ HTTPSourceFilter(HTTPSource* source) : source_(source) {\n  }\n\n  // Delete implicit move/copy constructors and assignment operator overloads.\n  HTTPSourceFilter(HTTPSourceFilter&&) = delete;\n  HTTPSourceFilter& operator=(HTTPSourceFilter&&) = delete;\n  HTTPSourceFilter(const HTTPSourceFilter&) = delete;\n  HTTPSourceFilter& operator=(const HTTPSourceFilter&) = delete;\n\n  operator bool() const {\n    return readable();\n  }\n\n  [[nodiscard]] bool readable() const {\n    return source_;\n  }\n\n  // Don't call this while awaiting!\n  HTTPSource* release() {\n    return std::exchange(source_, nullptr);\n  }\n\n  void setSource(HTTPSource* source) {\n    if (auto* prevSource = std::exchange(source_, source)) {\n      prevSource->stopReading();\n    }\n  }\n\n  // HTTPSource overrides\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    return readHeaderEventImpl(/*deleteOnDone=*/true);\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    return readBodyEventImpl(max, /*deleteOnDone=*/true);\n  }\n\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> error = folly::none) override;\n\n  [[nodiscard]] folly::Optional<uint64_t> getStreamID() const override {\n    XCHECK(source_);\n    return source_->getStreamID();\n  }\n\n  void setReadTimeout(std::chrono::milliseconds timeout) override {\n    XCHECK(source_);\n    source_->setReadTimeout(timeout);\n  }\n\n protected:\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEventImpl(\n      bool deleteOnDone = false);\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEventImpl(\n      uint32_t max = std::numeric_limits<uint32_t>::max(),\n      bool deleteOnDone = false);\n\n private:\n  HTTPSource* source_{nullptr};\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceFilterChain.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n\nnamespace proxygen::coro {\n\n/**\n * Represents a chain of HTTPSourceFilter's that can be inserted at the head\n * or the tail.\n */\nclass FilterChain {\n public:\n  FilterChain() = default;\n  FilterChain(FilterChain&& goner) = default;\n  FilterChain& operator=(FilterChain&& goner) = default;\n\n  void setSource(HTTPSource* source) {\n    if (tail_) {\n      tail_->setSource(source);\n    } else {\n      head_.setSource(source);\n    }\n  }\n\n  HTTPSource* head() {\n    // only valid if the chain has a real source\n    if (tail_) {\n      if (*tail_) { // there's a filter and the last one has a source\n        return &head_;\n      }\n    } else if (head_) { // no filter but head has a source\n      return &head_;\n    }\n    return nullptr;\n  }\n\n  void insertFront(HTTPSourceFilter* filter) {\n    if (tail_ || head_) {\n      filter->setSource(head_.release());\n    }\n    head_.setSource(filter);\n    if (!tail_) {\n      tail_ = filter; // First filter, set tail\n    }\n  }\n\n  void insertEnd(HTTPSourceFilter* filter) {\n    if (tail_) {\n      // At least one filter, push onto tail\n      filter->setSource(tail_->release());\n      tail_->setSource(filter);\n      tail_ = filter;\n    } else {\n      insertFront(filter); // no filters, call insertFront\n    }\n  }\n\n  // release ownership of chain\n  HTTPSource* release() {\n    return head_.release();\n  }\n\n private:\n  HTTPSourceHolder head_;\n  HTTPSourceFilter* tail_{nullptr};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceHolder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n\nnamespace proxygen::coro {\n\n// A wrapper around an HTTPSource which ensures that either an EOM or error is\n// read, or stopReading is called on the source.\nclass HTTPSourceHolder : public HTTPSourceFilter {\n public:\n  HTTPSourceHolder() = default;\n\n  /* implicit */ HTTPSourceHolder(HTTPSource* source)\n      : HTTPSourceFilter(source) {\n  }\n\n  // Set move constructor and assignment operator overload\n  HTTPSourceHolder(HTTPSourceHolder&& moved) noexcept {\n    setSource(moved.release());\n  }\n\n  HTTPSourceHolder& operator=(HTTPSourceHolder&& moved) noexcept {\n    setSource(moved.release());\n    return *this;\n  }\n\n  // Delete implicit copy constructor and assignment operator overload\n  HTTPSourceHolder(const HTTPSourceHolder&) = delete;\n  HTTPSourceHolder& operator=(const HTTPSourceHolder&) = delete;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceReader.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nHTTPSourceReader& HTTPSourceReader::preRead(PreReadFn preReadFn) {\n  if (asyncPreReadFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  preReadFn_ = std::move(preReadFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::preReadAsync(AsyncPreReadFn preReadFn) {\n  if (preReadFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncPreReadFn_ = std::move(preReadFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onHeaders(HeaderFn headerFn) {\n  if (asyncHeaderFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  headerFn_ = std::move(headerFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onHeadersAsync(AsyncHeaderFn headerFn) {\n  if (headerFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncHeaderFn_ = std::move(headerFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onBody(BodyFn bodyFn) {\n  if (asyncBodyFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  bodyFn_ = std::move(bodyFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onBodyAsync(AsyncBodyFn bodyFn) {\n  if (bodyFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncBodyFn_ = std::move(bodyFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onDatagram(DatagramFn datagramFn) {\n  if (asyncDatagramFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  datagramFn_ = std::move(datagramFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onDatagramAsync(\n    AsyncDatagramFn datagramFn) {\n  if (datagramFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncDatagramFn_ = std::move(datagramFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onPushPromise(PushPromiseFn promiseFn) {\n  if (asyncPromiseFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  promiseFn_ = std::move(promiseFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onPushPromiseAsync(\n    AsyncPushPromiseFn promiseFn) {\n  if (promiseFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncPromiseFn_ = std::move(promiseFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onTrailers(TrailerFn trailerFn) {\n  if (asyncTrailerFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  trailerFn_ = std::move(trailerFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onTrailersAsync(AsyncTrailerFn trailerFn) {\n  if (trailerFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncTrailerFn_ = std::move(trailerFn);\n  return *this;\n}\n\nHTTPSourceReader& HTTPSourceReader::onError(ErrorFn errorFn) {\n  if (asyncErrorFn_) {\n    throw std::logic_error(\"The async callback has been already set\");\n  }\n\n  errorFn_ = std::move(errorFn);\n  return *this;\n}\nHTTPSourceReader& HTTPSourceReader::onErrorAsync(AsyncErrorFn errorFn) {\n  if (errorFn_) {\n    throw std::logic_error(\"The plain callback has been already set\");\n  }\n\n  asyncErrorFn_ = std::move(errorFn);\n  return *this;\n}\n\nfolly::coro::Task<void> HTTPSourceReader::read(uint32_t maxBodySize) {\n  auto head = filterChain_.release();\n  XCHECK(head);\n  bool readCompleted = false;\n  SCOPE_EXIT {\n    if (!readCompleted) {\n      head->stopReading();\n    }\n  };\n  bool stop = false;\n  folly::CancellationCallback cancellationCallback{\n      co_await folly::coro::co_current_cancellation_token,\n      [&stop] { stop = true; }};\n  while (!stop) {\n    if (preReadFn_) {\n      stop |= preReadFn_();\n    } else if (asyncPreReadFn_) {\n      stop |= co_await asyncPreReadFn_();\n    }\n    if (stop) {\n      break;\n    }\n\n    auto headerEvent = co_await co_awaitTry(head->readHeaderEvent());\n    if (headerEvent.hasException()) {\n      readCompleted = true;\n      if (errorFn_) {\n        errorFn_(ErrorContext::HEADERS, getHTTPError(headerEvent));\n      } else if (asyncErrorFn_) {\n        co_await asyncErrorFn_(ErrorContext::HEADERS,\n                               getHTTPError(headerEvent));\n      } else {\n        co_yield folly::coro::co_error(headerEvent.exception());\n      }\n      co_return;\n    }\n    auto finalHeaders = headerEvent->isFinal();\n    readCompleted = headerEvent->eom;\n    if (headerFn_) {\n      stop |= headerFn_(\n          std::move(headerEvent->headers), finalHeaders, headerEvent->eom);\n    } else if (asyncHeaderFn_) {\n      stop |= co_await asyncHeaderFn_(\n          std::move(headerEvent->headers), finalHeaders, headerEvent->eom);\n    }\n    if (headerEvent->eom) {\n      co_return;\n    }\n    if (finalHeaders) {\n      break;\n    }\n  }\n  while (!stop) {\n    if (preReadFn_) {\n      stop |= preReadFn_();\n    } else if (asyncPreReadFn_) {\n      stop |= co_await asyncPreReadFn_();\n    }\n    if (stop) {\n      break;\n    }\n\n    // TODO: support max arg to readBodyEvent, or change preRead to return\n    // buffer size?\n    auto bodyEvent = co_await co_awaitTry(head->readBodyEvent(maxBodySize));\n    if (bodyEvent.hasException()) {\n      readCompleted = true;\n      if (errorFn_) {\n        errorFn_(ErrorContext::BODY, getHTTPError(bodyEvent));\n      } else if (asyncErrorFn_) {\n        co_await asyncErrorFn_(ErrorContext::BODY, getHTTPError(bodyEvent));\n      } else {\n        co_yield folly::coro::co_error(bodyEvent.exception());\n      }\n      co_return;\n    }\n    readCompleted = bodyEvent->eom;\n    switch (bodyEvent->eventType) {\n      case HTTPBodyEvent::BODY:\n        XCHECK(!bodyEvent->event.body.empty() || bodyEvent->eom);\n        if (bodyFn_) {\n          stop |= bodyFn_(std::move(bodyEvent->event.body), bodyEvent->eom);\n        } else if (asyncBodyFn_) {\n          stop |= co_await asyncBodyFn_(std::move(bodyEvent->event.body),\n                                        bodyEvent->eom);\n        }\n        break;\n      case HTTPBodyEvent::DATAGRAM:\n        CHECK(bodyEvent->event.datagram);\n        CHECK(!bodyEvent->eom);\n        if (datagramFn_) {\n          stop |= datagramFn_(std::move(bodyEvent->event.datagram));\n        } else if (asyncDatagramFn_) {\n          stop |=\n              co_await asyncDatagramFn_(std::move(bodyEvent->event.datagram));\n        }\n        break;\n      case HTTPBodyEvent::PUSH_PROMISE:\n        if (promiseFn_) {\n          stop |= promiseFn_(std::move(bodyEvent->event.push.promise),\n                             bodyEvent->event.push.movePushSource(),\n                             bodyEvent->eom);\n        } else if (asyncPromiseFn_) {\n          stop |=\n              co_await asyncPromiseFn_(std::move(bodyEvent->event.push.promise),\n                                       bodyEvent->event.push.movePushSource(),\n                                       bodyEvent->eom);\n        }\n        break;\n      case HTTPBodyEvent::TRAILERS:\n        XCHECK(bodyEvent->eom) << \"Trailers implies EOM\";\n        if (trailerFn_) {\n          trailerFn_(std::move(bodyEvent->event.trailers));\n        } else if (asyncTrailerFn_) {\n          co_await asyncTrailerFn_(std::move(bodyEvent->event.trailers));\n        } else if (bodyFn_) {\n          bodyFn_(quic::BufQueue(nullptr), /*eom=*/true);\n        } else if (asyncBodyFn_) {\n          co_await asyncBodyFn_(quic::BufQueue(nullptr), /*eom=*/true);\n        }\n        break;\n      case HTTPBodyEvent::UPGRADE: // delete\n        break;\n      case HTTPBodyEvent::SUSPEND: {\n        auto res = co_await std::move(bodyEvent->event.resume);\n        if (res == TimedBaton::Status::cancelled) {\n          co_yield folly::coro::co_error(\n              HTTPError(HTTPErrorCode::CORO_CANCELLED, \"Read cancelled\"));\n        }\n        break;\n      }\n      case HTTPBodyEvent::PADDING: // no read API for padding\n        break;\n    }\n    if (bodyEvent->eom) {\n      co_return;\n    }\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPSourceReader.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilterChain.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\n/**\n * HTTPSourceReader is a convenience class for reading from an HTTPSource.\n *\n * The caller can set callbacks to run as various parts of the message are read.\n * The callbacks can be synchronous (onHeaders, onBody) or coroutines\n * (onHeadersAsync), and can be mixed and matched on the same reader.\n *\n * Some events are terminal - onHeaders or onBody with eom specified,\n * onTrailers, and onError.\n *\n * For non-terminal headers or body, the callback returns a value indicating\n * how to proceed - Continue to continue processing or Stop to cancel the read.\n * * Stop will call stopReading on source if it was incomplete.\n *\n * For example:\n *\n * HTTPSourceReader reader(source);\n *\n * reader\n *   .onHeaders([] (std::unique_ptr<HTTPMessage> headers,\n *                  bool isFinal,\n *                  bool eom) {\n *       // handle headers.  isFinal is false if headers is an HTTP 1xx response\n *       if (eom) {\n *         // handle eom\n *       }\n *       // Return code is ignored when eom is true\n *       return HTTPSourceReader::Continue;\n *     })\n *   .onBody([] (BufQueue body, bool eom) {\n *       // body may be empty when eom is true\n *       if (!body.empty()) {\n *         // handle body\n *       }\n *       if (eom) {\n *         // handle EOM\n *       }\n *       // Return code is ignored when eom is true\n *       return HTTPSourceReader::Continue;\n *     })\n *   .onError([] (ErrorContext errContext, HTTPError error) {\n *       // An error occurred.  errContext specifies if the error happened\n *       // while awaiting headers or body.\n *       // handle error\n *     });\n *\n * co_await reader.read();\n */\n\nclass HTTPSourceReader {\n public:\n  HTTPSourceReader() = default;\n\n  explicit HTTPSourceReader(HTTPSourceHolder source) {\n    filterChain_.setSource(source.release());\n  }\n\n  HTTPSourceReader& setSource(HTTPSource* source) {\n    filterChain_.setSource(source);\n    return *this;\n  }\n\n  HTTPSourceReader& setSource(HTTPSourceHolder source) {\n    return setSource(source.release());\n  }\n\n  HTTPSourceReader& insertFilter(HTTPSourceFilter* filter) {\n    filterChain_.insertEnd(filter);\n    return *this;\n  }\n\n  constexpr static bool Continue = false;\n  constexpr static bool Cancel = true;\n\n  // Pre-read\n  using PreReadFn = folly::Function<bool()>;\n  using AsyncPreReadFn = folly::Function<folly::coro::Task<bool>()>;\n  HTTPSourceReader& preRead(PreReadFn preReadFn);\n  HTTPSourceReader& preReadAsync(AsyncPreReadFn preReadFn);\n\n  // Headers, final, EOM\n  using HeaderFn =\n      folly::Function<bool(std::unique_ptr<HTTPMessage>, bool, bool)>;\n  using AsyncHeaderFn = folly::Function<folly::coro::Task<bool>(\n      std::unique_ptr<HTTPMessage>, bool, bool)>;\n  HTTPSourceReader& onHeaders(HeaderFn headerFn);\n  HTTPSourceReader& onHeadersAsync(AsyncHeaderFn headerFn);\n\n  // Body, EOM\n  using BodyFn = folly::Function<bool(BufQueue, bool)>;\n  using AsyncBodyFn = folly::Function<folly::coro::Task<bool>(BufQueue, bool)>;\n  HTTPSourceReader& onBody(BodyFn bodyFn);\n  HTTPSourceReader& onBodyAsync(AsyncBodyFn bodyFn);\n\n  // Datagram, EOM\n  using DatagramFn = folly::Function<bool(std::unique_ptr<folly::IOBuf>)>;\n  using AsyncDatagramFn =\n      folly::Function<folly::coro::Task<bool>(std::unique_ptr<folly::IOBuf>)>;\n  HTTPSourceReader& onDatagram(DatagramFn datagramFn);\n  HTTPSourceReader& onDatagramAsync(AsyncDatagramFn datagramFn);\n\n  // Promise, source for push, EOM\n  using PushPromiseFn = folly::Function<bool(\n      std::unique_ptr<HTTPMessage>, HTTPSourceHolder, bool)>;\n  using AsyncPushPromiseFn = folly::Function<folly::coro::Task<bool>(\n      std::unique_ptr<HTTPMessage>, HTTPSourceHolder, bool)>;\n  HTTPSourceReader& onPushPromise(PushPromiseFn promiseFn);\n  HTTPSourceReader& onPushPromiseAsync(AsyncPushPromiseFn promiseFn);\n\n  // Trailers (EOM implicit)\n  using TrailerFn = folly::Function<void(std::unique_ptr<HTTPHeaders>)>;\n  using AsyncTrailerFn =\n      folly::Function<folly::coro::Task<void>(std::unique_ptr<HTTPHeaders>)>;\n  HTTPSourceReader& onTrailers(TrailerFn trailerFn);\n  HTTPSourceReader& onTrailersAsync(AsyncTrailerFn trailerFn);\n\n  enum class ErrorContext { HEADERS, BODY };\n  using ErrorFn = folly::Function<void(ErrorContext, HTTPError)>;\n  using AsyncErrorFn =\n      folly::Function<folly::coro::Task<void>(ErrorContext, HTTPError)>;\n  HTTPSourceReader& onError(ErrorFn errorFn);\n  HTTPSourceReader& onErrorAsync(AsyncErrorFn errorFn);\n\n  // maxBodySize is passed into ::readBodyEvent(maxBodySize)\n  folly::coro::Task<void> read(\n      uint32_t maxBodySize = std::numeric_limits<uint32_t>::max());\n\n private:\n  FilterChain filterChain_;\n\n  PreReadFn preReadFn_;\n  HeaderFn headerFn_;\n  BodyFn bodyFn_;\n  DatagramFn datagramFn_;\n  PushPromiseFn promiseFn_;\n  TrailerFn trailerFn_;\n  ErrorFn errorFn_;\n\n  AsyncPreReadFn asyncPreReadFn_;\n  AsyncHeaderFn asyncHeaderFn_;\n  AsyncBodyFn asyncBodyFn_;\n  AsyncDatagramFn asyncDatagramFn_;\n  AsyncPushPromiseFn asyncPromiseFn_;\n  AsyncTrailerFn asyncTrailerFn_;\n  AsyncErrorFn asyncErrorFn_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSource.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n#include <folly/logging/xlog.h>\n\nconstexpr uint32_t kMaxBufferedDatagramSize = 65536;\n\nnamespace proxygen::coro {\n\nHTTPStreamSource::HTTPStreamSource(folly::EventBase* evb,\n                                   HTTPCodec::StreamID id,\n                                   Callback* callback,\n                                   uint32_t recvStreamFlowControlWindow,\n                                   std::chrono::milliseconds readTimeout,\n                                   bool strictFlowControl)\n    : id_(id),\n      callback_(callback),\n      recvWindow_(recvStreamFlowControlWindow),\n      event_(CHECK_NOTNULL(evb), readTimeout),\n      sinkMode_(false),\n      sourceComplete_(false),\n      strictFlowControl_(strictFlowControl),\n      shouldValidateContentLength_(true),\n      canSuspend_(true) {\n}\n\nHTTPStreamSource::HTTPStreamSource(folly::EventBase* evb,\n                                   HTTPCodec::StreamID id,\n                                   Callback& callback,\n                                   uint32_t recvStreamFlowControlWindow,\n                                   std::chrono::milliseconds readTimeout)\n    : HTTPStreamSource(\n          evb, id, &callback, recvStreamFlowControlWindow, readTimeout, true) {\n}\n\nHTTPStreamSource::HTTPStreamSource(folly::EventBase* evb,\n                                   folly::Optional<HTTPCodec::StreamID> id,\n                                   Callback* callback,\n                                   uint32_t egressBufferSize)\n    : HTTPStreamSource(evb,\n                       (id ? *id : HTTPCodec::MaxStreamID),\n                       callback,\n                       egressBufferSize,\n                       std::chrono::milliseconds(0),\n                       false) {\n}\n\nvoid HTTPStreamSource::headers(std::unique_ptr<HTTPMessage> msg, bool eom) {\n  if (!validateHeaders(*msg, eom)) {\n    return;\n  }\n\n  if (!sinkMode_) {\n    headerQueue_.emplace_back(std::move(msg), eom);\n    event_.signal();\n  }\n}\n\nauto HTTPStreamSource::body(BufQueue body, uint16_t padding, bool eom)\n    -> FlowControlState {\n  auto length = body.chainLength();\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onBody, eom)) {\n    return FlowControlState::ERROR;\n  }\n\n  validateContentLength(eom, length);\n\n  if (!recvWindow_.reserve(length, padding, strictFlowControl_)) {\n    setError(HTTPErrorCode::FLOW_CONTROL_ERROR, \"Sender exceeded flow control\");\n    return FlowControlState::ERROR;\n  }\n  if (sinkMode_) {\n    updateFlowControl(length);\n  } else {\n    // TODO: consider updateFlowControl(0) if padding is non-zero?\n    if (!bodyQueue_.empty() &&\n        bodyQueue_.back().eventType == HTTPBodyEvent::BODY) {\n      // When adding ByteEventRegistrations here, update coalescing\n      auto& back = bodyQueue_.back();\n      back.event.body.append(body.move());\n      back.eom = eom;\n    } else {\n      bodyQueue_.emplace_back(std::move(body), eom);\n    }\n    event_.signal();\n  }\n\n  return recvWindow_.getSize() > 0 ? FlowControlState::OPEN\n                                   : FlowControlState::CLOSED;\n}\n\nvoid HTTPStreamSource::datagram(std::unique_ptr<folly::IOBuf> datagram) {\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onBody,\n                               false) ||\n      !datagram) {\n    return;\n  }\n  auto length = datagram->computeChainDataLength();\n  if (bufferedDatagramSize_ + length > kMaxBufferedDatagramSize) {\n    XLOG(DBG2) << \"Dropping datagram length=\" << length\n               << \" bufferedDatagramSize_=\" << bufferedDatagramSize_;\n    return;\n  }\n  if (!sinkMode_) {\n    bufferedDatagramSize_ += length;\n    bodyQueue_.emplace_back(HTTPBodyEvent::Datagram(), std::move(datagram));\n    event_.signal();\n  }\n}\n\nvoid HTTPStreamSource::pushPromise(std::unique_ptr<HTTPMessage> promise,\n                                   HTTPSource* pushSource,\n                                   bool eom) {\n  // Treat this like a body event for state machine purposes.  Legal\n  // anytime after headers and before eom\n  XCHECK(pushSource);\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onBody, eom)) {\n    pushSource->stopReading();\n    return;\n  }\n  if (!sinkMode_) {\n    bodyQueue_.emplace_back(std::move(promise), pushSource, eom);\n    event_.signal();\n  } else {\n    pushSource->stopReading();\n  }\n}\n\nvoid HTTPStreamSource::trailers(std::unique_ptr<HTTPHeaders> trailers) {\n  // Trailers are supposed to signify EOM, but there's no requirement the\n  // FIN flag be present there, and there's likely an eom coming later, so\n  // have to pass eom=false here.\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onTrailers,\n                               false)) {\n    return;\n  }\n  validateContentLength(/*eom=*/true);\n  if (!sinkMode_) {\n    bodyQueue_.emplace_back(std::move(trailers));\n    event_.signal();\n  }\n}\n\nvoid HTTPStreamSource::padding(uint16_t bytes) {\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onBody,\n                               false)) {\n    return;\n  }\n  if (!sinkMode_) {\n    bodyQueue_.emplace_back(HTTPBodyEvent::Padding(), bytes);\n    event_.signal();\n  }\n}\n\nvoid HTTPStreamSource::eom() {\n  if (!validateStateTransition(HTTPTransactionIngressSM::Event::onEOM)) {\n    return;\n  }\n  validateContentLength(/*eom=*/true);\n  if (!sinkMode_) {\n    if (bodyQueue_.empty()) {\n      if (headerQueue_.empty()) {\n        // both queues are empty, put eom in body queue\n        bodyQueue_.emplace_back(nullptr, true);\n        event_.signal();\n        XLOG(DBG6) << \"placing eom in bodyQueue_ id=\" << id_;\n      } else {\n        // body queue is empty but header queue is not empty, put eom in\n        // header queue\n        headerQueue_.back().eom = true;\n        XLOG(DBG6) << \"placing eom in headerQueue_ id=\" << id_;\n      }\n    } else {\n      // body queue is non-empty, put eom in body queue, except datagram\n      if (bodyQueue_.back().eventType == HTTPBodyEvent::DATAGRAM) {\n        bodyQueue_.emplace_back(nullptr, true);\n      } else {\n        bodyQueue_.back().eom = true;\n      }\n      XLOG(DBG6) << \"placing eom in bodyQueue_ id=\" << id_;\n    }\n  } else {\n    XLOG(DBG4) << \"discarding eom for sinkMode_ id=\" << id_;\n  }\n}\n\nvoid HTTPStreamSource::abort(HTTPErrorCode error, std::string_view details) {\n  if (!isEOMSeen()) {\n    state_ = HTTPTransactionIngressSM::State::ReceivingDone;\n    setError(error,\n             folly::to<std::string>(\"HTTP source aborted err=\",\n                                    getErrorString(error),\n                                    details.empty() ? \"\" : \", details=\",\n                                    details),\n             /*ingress=*/true);\n  }\n}\n\nfolly::coro::Task<HTTPHeaderEvent> HTTPStreamSource::readHeaderEvent() {\n  XCHECK(event_.getEventBase()->isInEventBaseThread());\n\n  bool eomReturn = false;\n  auto guard =\n      folly::makeGuard([this, &eomReturn] { checkForCompletion(eomReturn); });\n  if (readState_ != ReadState::HeaderEvents) {\n    setError(HTTPErrorCode::INVALID_STATE_TRANSITION,\n             \"readHeaderEvent called after final headers read\");\n  }\n  if (!hasHeaderEvents()) {\n    // TODO: handle timeouts here\n    co_await waitForEvent();\n  }\n  if (error_) {\n    co_yield folly::coro::co_error(std::move(error_->first));\n  }\n  // Calling body() before headers() will error out of waitForEvent\n  XCHECK(!headerQueue_.empty());\n  auto res = std::move(headerQueue_.front());\n  headerQueue_.pop_front();\n  eomReturn = res.eom;\n  if (res.isFinal()) {\n    readState_ = ReadState::BodyEvents;\n  }\n\n  co_return res;\n}\n\nfolly::coro::Task<HTTPBodyEvent> HTTPStreamSource::readBodyEvent(uint32_t max) {\n  XCHECK_GT(max, 0UL);\n  XCHECK(event_.getEventBase()->isInEventBaseThread());\n  bool eomReturn = false;\n  auto guard =\n      folly::makeGuard([this, &eomReturn] { checkForCompletion(eomReturn); });\n  if (readState_ != ReadState::BodyEvents) {\n    setError(HTTPErrorCode::INVALID_STATE_TRANSITION,\n             \"readBodyEvent called before final headers read\");\n  }\n  if (!hasBodyEvents()) {\n    event_.reset();\n    if (canSuspend_) {\n      canSuspend_ = false;\n      auto t = folly::coro::co_invoke(\n          [this]() -> folly::coro::Task<TimedBaton::Status> {\n            return event_.wait();\n          });\n      co_return HTTPBodyEvent(std::move(t));\n    }\n    co_await waitForEvent();\n    canSuspend_ = true;\n  }\n  if (error_) {\n    // TODO: handle timeouts differently here?\n    co_yield folly::coro::co_error(std::move(error_->first));\n  }\n  // If there's no error, there must be queued body event\n  XCHECK(!bodyQueue_.empty());\n  auto res = std::move(bodyQueue_.front());\n  bodyQueue_.pop_front();\n  if (res.eventType == HTTPBodyEvent::BODY && !res.event.body.empty()) {\n    auto length = res.event.body.chainLength();\n    if (length > max) {\n      // If the first event is bigger than the caller wants,\n      // split the remainder and push it back onto the front of\n      // the queue. If this happens, EOM must be false.\n      BufQueue bodyBuf(std::move(res.event.body));\n      res.event.body = bodyBuf.splitAtMost(max);\n      bodyQueue_.emplace_front(bodyBuf.move(), res.eom);\n      res.eom = false;\n      length = max;\n    }\n    updateFlowControl(length);\n  } else if (res.eventType == HTTPBodyEvent::DATAGRAM) {\n    auto length = res.event.datagram->computeChainDataLength();\n    if (length > max) {\n      VLOG(2) << \"Returning DATAGRAM with length=\" << length\n              << \" exceeding max=\" << max;\n    }\n    DCHECK_GE(bufferedDatagramSize_, length);\n    bufferedDatagramSize_ -= length;\n  }\n  eomReturn = res.eom;\n  co_return res;\n}\n\nvoid HTTPStreamSource::stopReading(folly::Optional<const HTTPErrorCode> error) {\n  XCHECK(!sourceComplete_) << \"Cannot call stopReading twice, or after you \"\n                              \"have finished reading\";\n\n  setErrorImpl(error.value_or(HTTPErrorCode::NO_ERROR),\n               \"Abandoned reading\",\n               /*ingress=*/false);\n  checkForCompletion(false);\n}\n\nbool HTTPStreamSource::validateHeaders(const HTTPMessage& msg, bool eom) {\n  bool finalHeaders = msg.isFinal();\n  if (!validateStateTransition(\n          finalHeaders ? HTTPTransactionIngressSM::Event::onFinalHeaders\n                       : HTTPTransactionIngressSM::Event::onNonFinalHeaders,\n          eom)) {\n    return false;\n  }\n  if (msg.isResponse() && msg.getStatusCode() == 304) {\n    // TODO: 101 and 200/CONNECT\n    shouldValidateContentLength_ = false;\n  }\n  if (finalHeaders && shouldValidateContentLength_) {\n    const auto& contentLen =\n        msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH);\n    auto parsedContentLen = folly::tryTo<uint64_t>(contentLen);\n    if (parsedContentLen.hasError()) {\n      shouldValidateContentLength_ = false;\n      XLOG_IF(ERR, !contentLen.empty())\n          << \"Invalid content-length: \" << contentLen;\n    } else {\n      expectedIngressContentLength_ = expectedIngressContentLengthRemaining_ =\n          *parsedContentLen;\n      validateContentLength(eom);\n    }\n  }\n  return true;\n}\n\nvoid HTTPStreamSource::setError(HTTPErrorCode error,\n                                std::string msg,\n                                bool ingress) {\n  // Errors can either come from the ingress - eg: RST_STREAM on this\n  // stream, or they can be generated by processing logic within this class\n  // (flow control, state machine, etc).  We track the source of the error,\n  // and only return an \"egress\" error in ingressComplete.  This prevents\n  // sending a RST_STREAM in response to a RST_STREAM.\n  if (error_) {\n    // there's already an error queued, no-op\n    return;\n  }\n  XLOG(DBG4) << \"Encountered error on stream=\" << id_\n             << \" error=\" << uint64_t(error) << \" msg=\" << msg;\n  setErrorImpl(error, std::move(msg), ingress);\n}\n\nvoid HTTPStreamSource::setErrorImpl(HTTPErrorCode error,\n                                    std::string msg,\n                                    bool ingress) {\n  error_.emplace(HTTPError(error, std::move(msg)), ingress);\n  if (!headerQueue_.empty() && headerQueue_.back().headers->isFinal()) {\n    error_->first.httpMessage = std::move(headerQueue_.back().headers);\n  }\n  // no need to queue any more events\n  enableSinkMode();\n  event_.signal();\n}\n\nvoid HTTPStreamSource::enableSinkMode() {\n  XLOG(DBG4) << \"clearing source events for id_=\" << id_;\n  headerQueue_.clear();\n  while (!bodyQueue_.empty()) {\n    auto res = std::move(bodyQueue_.front());\n    if (res.eventType == HTTPBodyEvent::BODY && !res.event.body.empty()) {\n      auto length = res.event.body.chainLength();\n      updateFlowControl(length);\n    }\n    bodyQueue_.pop_front();\n  }\n  sinkMode_ = true;\n}\n\nfolly::coro::Task<void> HTTPStreamSource::waitForEvent() noexcept {\n  event_.reset();\n  // Really not sure why it can be > 1\n  XCHECK_LT(waiters_, std::numeric_limits<uint8_t>::max());\n  waiters_++;\n  auto status = co_await event_.wait();\n  waiters_--;\n  if (status == TimedBaton::Status::timedout) {\n    setError(HTTPErrorCode::READ_TIMEOUT,\n             folly::to<std::string>(\"Read timeout after \",\n                                    event_.getTimeout().count()));\n  } else if (status == TimedBaton::Status::cancelled) {\n    setError(HTTPErrorCode::CORO_CANCELLED, \"Read cancelled\");\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPSource.h\"\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n#include \"proxygen/lib/http/coro/util/WindowContainer.h\"\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/session/HTTPTransactionIngressSM.h>\n\n#include <list>\n\nnamespace proxygen::coro {\n/**\n * HTTPStreamSource is a source that is fed by a 'driver' that places events\n * into a queue.\n *\n * The proxygen coro session classes drive this class internally by reading and\n * parsing HTTP.\n *\n * Users of the library may use this class as an interface to non-coroutine\n * based asynchronous code that produces HTTP requests or responses.\n *\n * This class implements the consumer-only HTTPSource interface\n * (::readHeaderEvent, ::readBodyEvent), and also has a set of symmetric\n * producer methods to produce said events (::headers, ::body, etc.). The\n * consumer and producing threads may be different, however it only supports\n * single producer single consumer (SPSC).\n */\nclass HTTPStreamSource : public HTTPSource {\n public:\n  /**\n   * Callback for flow control and lifetime related events.  The callback must\n   * outlive the source.\n   *\n   * Async producers that want flow control feedback can interpret the return\n   * value from body to determine when to pause, and windowOpen to resume.\n   */\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    Callback() = default;\n    Callback(const Callback&) = delete;\n    Callback& operator=(const Callback&) = delete;\n    Callback(Callback&&) = delete;\n    Callback& operator=(Callback&&) = delete;\n\n    // The ID parameter to the callbacks can be HTTPCodec::kMaxStreamId if the\n    // source is an async producer that did not provide a stream ID.\n\n    // Called when amount of data is read out of this source. toAck is used by\n    // sessions to know when to send stream flow control updates\n    virtual void bytesProcessed(HTTPCodec::StreamID /*id*/,\n                                size_t /*amount*/,\n                                size_t /*toAck*/) {\n    }\n\n    // Called when the window changes from CLOSED -> OPEN\n    virtual void windowOpen(HTTPCodec::StreamID /*id*/) {\n    }\n\n    // Called when the reader of this source has read EOM or error, or has\n    // invoked stopReading\n    virtual void sourceComplete(HTTPCodec::StreamID /*id*/,\n                                folly::Optional<HTTPError> /*error*/) {\n    }\n  };\n\n  // Constructor for sessions\n  HTTPStreamSource(\n      folly::EventBase* evb,\n      HTTPCodec::StreamID id,\n      Callback& callback,\n      uint32_t recvStreamFlowControlWindow = 65535,\n      std::chrono::milliseconds readTimeout = std::chrono::seconds(5));\n\n  // Constructor for async producers\n  // TODO: evb should not be required here (remove from TimedBaton/0)\n  explicit HTTPStreamSource(\n      folly::EventBase* evb,\n      folly::Optional<HTTPCodec::StreamID> id = folly::none,\n      Callback* callback = nullptr,\n      uint32_t egressBufferSize = 65535);\n\n  ~HTTPStreamSource() override {\n    // must be destructed in evb thread\n    auto* evb = event_.getEventBase();\n    XCHECK(evb->isInEventBaseThread());\n  }\n  HTTPStreamSource(const HTTPStreamSource&) = delete;\n  HTTPStreamSource& operator=(const HTTPStreamSource&) = delete;\n  HTTPStreamSource(HTTPStreamSource&&) = delete;\n  HTTPStreamSource& operator=(HTTPStreamSource&&) = delete;\n\n  // Maybe collapse with getStreamID?\n  HTTPCodec::StreamID getID() const {\n    return id_;\n  }\n\n  // Calls from the 'driver' that fill the queue\n  void headers(std::unique_ptr<HTTPMessage> msg, bool eom = false);\n  enum class FlowControlState { OPEN, CLOSED, ERROR };\n  FlowControlState body(BufQueue body, uint16_t padding, bool eom = false);\n  void datagram(std::unique_ptr<folly::IOBuf> datagram);\n  void pushPromise(std::unique_ptr<HTTPMessage> promise,\n                   HTTPSource* pushSource,\n                   bool eom = false);\n  void trailers(std::unique_ptr<HTTPHeaders> trailers);\n  void padding(uint16_t bytes);\n  void eom();\n  void abort(HTTPErrorCode error, std::string_view details = \"\");\n\n  // HTTPSource methods\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n  void stopReading(folly::Optional<const HTTPErrorCode> error) override;\n\n  // This is only for H3 ingress push streams which can be created before a\n  // transport stream ID is assigned\n  void setStreamID(HTTPCodec::StreamID id) {\n    XCHECK_EQ(id_, HTTPCodec::MaxStreamID);\n    id_ = id;\n  }\n\n  folly::Optional<uint64_t> getStreamID() const override {\n    return id_;\n  }\n\n  void setReadTimeout(std::chrono::milliseconds timeout) override {\n    event_.setTimeout(timeout);\n  }\n\n  std::chrono::milliseconds getReadTimeout() const {\n    return event_.getTimeout();\n  }\n\n  bool isUnprocessed() const {\n    return state_ == HTTPTransactionIngressSM::State::Start;\n  }\n\n  bool headersAllowed() const {\n    return isUnprocessed() ||\n           state_ == HTTPTransactionIngressSM::State::NonFinalHeadersReceived;\n  }\n\n  // TODO: maybe remove this API\n  void markEgressOnly() {\n    state_ = HTTPTransactionIngressSM::State::ReceivingDone;\n    enableSinkMode();\n  }\n\n  void skipContentLengthValidation() {\n    shouldValidateContentLength_ = false;\n  }\n\n  bool isEOMSeen() const {\n    return state_ == HTTPTransactionIngressSM::State::EOMQueued ||\n           state_ == HTTPTransactionIngressSM::State::ReceivingDone;\n  }\n\n  bool inputFinished() const {\n    return error_ || isEOMSeen();\n  }\n\n  bool sourceComplete() const {\n    return sourceComplete_;\n  }\n\n  void setCallback(Callback* callback) {\n    callback_ = callback;\n  }\n\n  void describe(std::ostream& os) const {\n    os << \", streamID=\" << id_;\n  }\n\n  const auto& window() const {\n    return recvWindow_.getWindow();\n  }\n\n  uint32_t bodyBytesBuffered() const {\n    return recvWindow_.getWindow().getOutstanding();\n  }\n\n private:\n  bool hasHeaderEvents() const {\n    return error_ || !headerQueue_.empty();\n  }\n\n  bool hasBodyEvents() const {\n    return error_ || !bodyQueue_.empty();\n  }\n\n  void setError(HTTPErrorCode error, std::string msg, bool ingress = false);\n  void setErrorImpl(HTTPErrorCode error, std::string msg, bool ingress);\n\n  void enableSinkMode();\n\n  folly::coro::Task<void> waitForEvent() noexcept;\n\n  void contentLengthMismatchOnIngress(uint64_t observedBodySize) {\n    auto errorMsg = folly::to<std::string>(\n        \"Content-Length/body mismatch on ingress: expected= \",\n        expectedIngressContentLength_,\n        \", actual= \",\n        observedBodySize);\n    XLOG(ERR) << errorMsg << \" \" << this;\n    expectedIngressContentLengthRemaining_ = 0;\n    shouldValidateContentLength_ = false;\n    setError(HTTPErrorCode::CONTENT_LENGTH_MISMATCH, errorMsg);\n  }\n\n  // Because the driver can send events in any order, we must validate they\n  // occur in a valid order per the HTTP state machine.\n  bool validateStateTransition(HTTPTransactionIngressSM::Event event,\n                               bool eomFlag = false) {\n    if (!HTTPTransactionIngressSM::transit(state_, event)) {\n      auto msg =\n          folly::to<std::string>(\"Invalid ingress state transition, state=\",\n                                 state_,\n                                 \", event=\",\n                                 event,\n                                 \", streamID=\",\n                                 id_);\n      setError(HTTPErrorCode::INVALID_STATE_TRANSITION, std::move(msg));\n      return false;\n    }\n    if (eomFlag) {\n      return validateStateTransition(HTTPTransactionIngressSM::Event::onEOM,\n                                     false);\n    }\n    return true;\n  }\n\n  void validateContentLength(bool eom = false, size_t addedBodyLength = 0) {\n    if (!shouldValidateContentLength_) {\n      return;\n    }\n    if (addedBodyLength > expectedIngressContentLengthRemaining_) {\n      contentLengthMismatchOnIngress(\n          (addedBodyLength - expectedIngressContentLengthRemaining_) +\n          expectedIngressContentLength_);\n    } else {\n      expectedIngressContentLengthRemaining_ -= addedBodyLength;\n    }\n    if (eom) {\n      // Empty body ok with Content-Length set for HEAD responses and some 304\n      // responses\n      if (expectedIngressContentLengthRemaining_ > 0) {\n        contentLengthMismatchOnIngress(expectedIngressContentLength_ -\n                                       expectedIngressContentLengthRemaining_);\n      }\n    }\n  }\n\n  void updateFlowControl(size_t length) {\n    bool wasBlocked = !sinkMode_ && recvWindow_.getSize() <= 0;\n    auto toAck = recvWindow_.processed(length);\n    if (callback_) {\n      callback_->bytesProcessed(id_, length, inputFinished() ? 0 : toAck);\n      if (wasBlocked && recvWindow_.getSize() > 0) {\n        callback_->windowOpen(id_);\n      }\n    }\n  }\n\n  folly::Optional<HTTPError> getEgressError() {\n    if (!error_ || error_->second) {\n      return folly::none;\n    }\n    return error_->first;\n  }\n\n  void checkForCompletion(bool eomReturn) {\n    if (!sourceComplete_ && (error_ || eomReturn) && waiters_ == 0) {\n      sourceComplete_ = true;\n      auto heapAllocated = heapAllocated_;\n      if (callback_) {\n        callback_->sourceComplete(id_, getEgressError());\n      }\n      if (heapAllocated) {\n        delete this;\n      }\n    }\n  }\n\n  HTTPStreamSource(folly::EventBase* evb,\n                   HTTPCodec::StreamID id,\n                   Callback* callback,\n                   uint32_t recvStreamFlowControlWindow,\n                   std::chrono::milliseconds readTimeout,\n                   bool strictFlowControl);\n\n protected:\n  // consumer will be expected to only invoke ::readBodyEvent\n  void validateHeadersAndSkip(const HTTPMessage& msg, bool eom = false) {\n    XCHECK_EQ(readState_, ReadState::HeaderEvents);\n    XCHECK(headerQueue_.empty());\n    validateHeaders(msg, eom);\n    readState_ = ReadState::BodyEvents;\n  }\n\n private:\n  bool validateHeaders(const HTTPMessage& msg, bool eom = false);\n\n  HTTPCodec::StreamID id_;\n  Callback* callback_;\n  folly::Optional<std::pair<HTTPError, bool>> error_;\n  std::list<HTTPHeaderEvent> headerQueue_;\n  std::list<HTTPBodyEvent> bodyQueue_;\n  WindowContainer recvWindow_;\n  TimedBaton event_;\n  uint64_t expectedIngressContentLength_{0};\n  uint64_t expectedIngressContentLengthRemaining_{0};\n  uint32_t bufferedDatagramSize_{0};\n  uint8_t waiters_{0};\n  HTTPTransactionIngressSM::State state_{\n      HTTPTransactionIngressSM::getNewInstance()};\n  enum class ReadState : uint8_t {\n    HeaderEvents,\n    BodyEvents\n  } readState_{ReadState::HeaderEvents};\n  bool sinkMode_ : 1;\n  bool sourceComplete_ : 1;\n  bool strictFlowControl_ : 1;\n  bool shouldValidateContentLength_ : 1;\n  bool canSuspend_ : 1;\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSourceHolder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n\nnamespace proxygen::coro {\n\nclass HTTPStreamSourceHolder\n    : public HTTPStreamSource::Callback\n    , public std::enable_shared_from_this<HTTPStreamSourceHolder> {\n public:\n  using Ptr = std::shared_ptr<HTTPStreamSourceHolder>;\n  static Ptr make(folly::EventBase* evb,\n                  folly::Optional<HTTPCodec::StreamID> id = folly::none,\n                  uint32_t egressBufferSize = 65535) {\n    return std::shared_ptr<HTTPStreamSourceHolder>(\n        new HTTPStreamSourceHolder(evb, id, egressBufferSize));\n  }\n\n  ~HTTPStreamSourceHolder() override = default;\n\n  void start() {\n    libRef_ = shared_from_this();\n  }\n\n  void sourceComplete(HTTPCodec::StreamID /*id*/,\n                      folly::Optional<HTTPError> /*error*/) override {\n    /**\n     * ::sourceComplete is guaranteed to run inside HTTPStreamSource evb, so we\n     * can safely destruct here.\n     *\n     * WARNING: this is class is not thread safe and should not be used by\n     * different consumer and producer threads; setting sourceComplete_ flag and\n     * destructing source_ are not atomic operations.\n     */\n    egressFc_.post(); // unblock fc waiters\n    source_.reset();\n    libRef_.reset();\n  }\n\n  void windowOpen(HTTPCodec::StreamID) override {\n    egressFc_.post();\n    egressFc_.reset();\n  }\n\n  folly::coro::Task<void> awaitEgressBuffer() {\n    auto* source = get();\n    bool blocked = source && source->bodyBytesBuffered() >= egressBufferSize_;\n    if (blocked) {\n      co_await egressFc_;\n    }\n    co_return;\n  }\n\n  HTTPStreamSource* get() {\n    return source_.has_value() ? &source_.value() : nullptr;\n  }\n\n private:\n  HTTPStreamSourceHolder(folly::EventBase* evb,\n                         folly::Optional<HTTPCodec::StreamID> id,\n                         uint32_t egressBufferSize)\n      : source_(std::in_place, evb, id, this, egressBufferSize),\n        egressBufferSize_(egressBufferSize) {\n  }\n  std::optional<HTTPStreamSource> source_;\n  std::shared_ptr<HTTPStreamSourceHolder> libRef_;\n  folly::coro::Baton egressFc_;\n  uint32_t egressBufferSize_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSourceSink.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPStreamSourceSink.h\"\n\nnamespace {\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\nusing folly::coro::co_withCancellation;\n\nconstexpr uint32_t kMaxBodyChunkSize = 65'536;\n\nclass NoopTxnHandler : public HTTPTransactionHandler {\n public:\n  explicit NoopTxnHandler() = default;\n  ~NoopTxnHandler() override = default;\n  void setTransaction(HTTPTransaction*) noexcept override {\n  }\n  void detachTransaction() noexcept override {\n  }\n  void onHeadersComplete(std::unique_ptr<HTTPMessage>) noexcept override {\n  }\n  void onBody(std::unique_ptr<folly::IOBuf>) noexcept override {\n  }\n  void onChunkHeader(size_t) noexcept override {\n  }\n  void onChunkComplete() noexcept override {\n  }\n  void onTrailers(std::unique_ptr<HTTPHeaders>) noexcept override {\n  }\n  void onEOM() noexcept override {\n  }\n  void onUpgrade(UpgradeProtocol) noexcept override {\n  }\n  void onError(const HTTPException&) noexcept override {\n  }\n  void onInvariantViolation(const HTTPException&) noexcept override {\n  }\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n  void onPushedTransaction(HTTPTransaction*) noexcept override {\n  }\n  void onGoaway(ErrorCode) noexcept override {\n  }\n  void onDatagram(std::unique_ptr<folly::IOBuf>) noexcept override {\n  }\n  void onWebTransportBidiStream(\n      HTTPCodec::StreamID, WebTransport::BidiStreamHandle) noexcept override {\n  }\n  void onWebTransportUniStream(\n      HTTPCodec::StreamID, WebTransport::StreamReadHandle*) noexcept override {\n  }\n  void onWebTransportSessionClose(folly::Optional<uint32_t>) noexcept override {\n  }\n};\n\nNoopTxnHandler kNoopTxnHandler{};\n\n// helper source to defer eom; simply yields eom from ::readBodyEvent\nclass EomHttpSource : public HTTPSource {\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    folly::assume_unreachable();\n  }\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    co_return HTTPBodyEvent{/*body=*/nullptr, /*inEOM=*/true};\n  }\n  void stopReading(folly::Optional<const HTTPErrorCode>) override {\n  }\n};\n\ninline void handleIngressException(\n    HTTPTransactionHandler* handler,\n    const folly::exception_wrapper& ex) noexcept {\n  auto* httpError = CHECK_NOTNULL(ex.get_exception<HTTPError>());\n  handler->onError(HTTPErrorToHTTPException(*httpError));\n}\n\n/**\n * Bespoke InlineExecutor necessary since folly::InlineExecutor trips an\n * \"unsafe\" XCHECK in folly::coro::Task\n *\n * Context:\n * This class is an adapter for push-based proxygen/lib <-> poll-based\n * proxygen::coro. The write path of proxygen/lib requests more data inline\n * (via HTTPSink::resumeIngress), but due to the default nature of coroutines\n * the continutation to send such data (via HTTPTxnHandler::onBody) is done at a\n * later time.\n *\n * To achieve parity in proxygen/lib downstream egress behaviour with\n * proxygen::coro upstream, we must schedule the continutation to produce data\n * inline.\n */\nclass InlineExecutor : public folly::Executor {\n  void add(folly::Func f) override {\n    f();\n  }\n};\n\nstruct InlineAwaitable {\n  using WaitForIngressUnpaused =\n      folly::coro::TaskWithExecutor<TimedBaton::Status>;\n\n  friend folly::coro::ViaIfAsyncAwaitable<WaitForIngressUnpaused> co_viaIfAsync(\n      folly::Executor::KeepAlive<>, InlineAwaitable&& me) noexcept {\n    return folly::coro::co_viaIfAsync(me.task.executor(), std::move(me.task));\n  }\n\n  static InlineAwaitable make(folly::coro::Task<TimedBaton::Status>&& task,\n                              InlineExecutor& exec) {\n    return InlineAwaitable{co_withExecutor(&exec, std::move(task))};\n  }\n\n private:\n  explicit InlineAwaitable(WaitForIngressUnpaused&& taskIn) noexcept\n      : task(std::move(taskIn)) {\n  }\n\n  WaitForIngressUnpaused task;\n};\n\n} // namespace\n\nnamespace proxygen::coro {\n\nHTTPStreamSourceUpstreamSink::HTTPStreamSourceUpstreamSink(\n    folly::EventBase* handlerEvb,\n    HTTPSessionContextPtr sessionCtx,\n    HTTPTransactionHandler* handler)\n    : sessionCtx_(std::move(sessionCtx)),\n      egressSource_(handlerEvb, folly::none, this),\n      handler_(handler) {\n  ingressResumed_.signal();\n  gate_.then([this] {\n    auto self = std::move(self_);\n    handler_->detachTransaction();\n  });\n}\n\nHTTPStreamSourceUpstreamSink::~HTTPStreamSourceUpstreamSink() {\n  egressSource_.setCallback(nullptr);\n  cancellationSource_.requestCancellation();\n}\n\nvoid HTTPStreamSourceUpstreamSink::detachAndAbortIfIncomplete(\n    std::unique_ptr<HTTPSink> self) {\n  CHECK_EQ(self.get(), this);\n  self_ = std::move(self);\n  handler_ = &kNoopTxnHandler;\n  sendAbort();\n}\n\nvoid HTTPStreamSourceUpstreamSink::sendAbort() {\n  XLOG(DBG6) << __func__;\n  egressSource_.abort(HTTPErrorCode::CANCEL);\n  cancellationSource_.requestCancellation();\n}\n\nvoid HTTPStreamSourceUpstreamSink::sendHeadersWithOptionalEOM(\n    const HTTPMessage& headers, bool eom) {\n  egressHeaders_ = {.msg = &headers, .eom = eom};\n  // the session will only invoke ::readBodyEvent on egressSource_\n  egressSource_.validateHeadersAndSkip(*egressHeaders_.msg, egressHeaders_.eom);\n}\n\nvoid HTTPStreamSourceUpstreamSink::sendBody(\n    std::unique_ptr<folly::IOBuf> body) {\n  using FcState = HTTPStreamSource::FlowControlState;\n  windowState_ = egressSource_.body(std::move(body), /*padding=*/0, false);\n  if (windowState_ == FcState::CLOSED) {\n    handler_->onEgressPaused();\n  }\n}\n\nfolly::coro::Task<void> HTTPStreamSourceUpstreamSink::transact(\n    HTTPCoroSession* upstreamSession,\n    HTTPCoroSession::RequestReservation reservation) {\n  /**\n   * I hate this... proxy bailed req after ::transact() by sending direct resp\n   * TODO(@damlaj): make ::transact private and invoke interally from\n   * ::sendHeaders\n   */\n  if (egressHeaders_.msg == nullptr) {\n    gate_.set(Event::IngressComplete);\n    sourceComplete(/*id=*/HTTPCodec::MaxStreamID, folly::none);\n    co_return;\n  }\n  auto responseSource = upstreamSession->sendRequest(\n      std::move(reservation),\n      *egressHeaders_.msg,\n      egressHeaders_.eom ? nullptr : &egressSource_);\n  if (egressHeaders_.eom) {\n    sourceComplete(/*id=*/HTTPCodec::MaxStreamID, folly::none);\n  }\n\n  if (responseSource.hasError()) {\n    std::string err{responseSource.error().describe()};\n    XLOG(DBG6) << \"failed ::sendRequest; err=\" << err;\n    handler_->onError(\n        HTTPException(HTTPException::Direction::INGRESS_AND_EGRESS, err));\n    gate_.set(Event::IngressComplete);\n    co_return;\n  }\n\n  // respSource must have streamID\n  id_ = responseSource->getStreamID();\n  XCHECK(id_);\n  egressSource_.setStreamID(id_.value());\n\n  co_await folly::coro::co_withCancellation(cancellationSource_.getToken(),\n                                            read(std::move(*responseSource)));\n}\n\nfolly::coro::Task<void> HTTPStreamSourceUpstreamSink::read(\n    HTTPSourceHolder ingressSource) {\n  auto g = folly::makeGuard([this] { gate_.set(Event::IngressComplete); });\n  InlineExecutor inlineExec{};\n  auto token = cancellationSource_.getToken();\n  auto shouldContinue = [&]() -> bool {\n    return !token.isCancellationRequested() && bool(ingressSource);\n  };\n\n  EomHttpSource eomSource;\n  // read header events\n  while (shouldContinue()) {\n    co_await ingressResumed_.wait();\n    auto headerEvent = co_await co_awaitTry(ingressSource.readHeaderEvent());\n    if (headerEvent.hasException()) {\n      XLOG(DBG6) << \"headerEvent err=\" << headerEvent.exception().what();\n      handleIngressException(handler_, headerEvent.exception());\n      break;\n    }\n    if (token.isCancellationRequested()) {\n      break;\n    }\n\n    bool final = headerEvent->isFinal();\n    handler_->onHeadersComplete(std::move(headerEvent->headers));\n    // revproxy will *always* pause ingress after ::onHeaderComplete, switch to\n    // eomSource for simplicity\n    if (headerEvent->eom) {\n      ingressSource.setSource(&eomSource);\n      break;\n    } else if (final) {\n      break;\n    }\n  }\n\n  // read body events\n  while (shouldContinue()) {\n    co_await InlineAwaitable::make(\n        co_withCancellation(token, ingressResumed_.wait()), inlineExec);\n    auto bodyEvent =\n        co_await co_awaitTry(ingressSource.readBodyEvent(kMaxBodyChunkSize));\n    if (bodyEvent.hasException()) {\n      XLOG(DBG6) << \"bodyEvent err=\" << bodyEvent.exception().what();\n      handleIngressException(handler_, bodyEvent.exception());\n      break;\n    }\n    if (token.isCancellationRequested()) {\n      break;\n    }\n\n    switch (bodyEvent->eventType) {\n      case HTTPBodyEvent::BODY: {\n        auto& body = bodyEvent->event.body;\n        if (!body.empty()) {\n          handler_->onBody(body.move());\n        }\n        break;\n      }\n      case HTTPBodyEvent::SUSPEND: {\n        auto& resume = bodyEvent->event.resume;\n        co_await co_awaitTry(std::move(resume));\n        break;\n      }\n      case HTTPBodyEvent::TRAILERS: {\n        handler_->onTrailers(std::move(bodyEvent->event.trailers));\n        break;\n      }\n      // ignored\n      case HTTPBodyEvent::PADDING:\n      case HTTPBodyEvent::DATAGRAM:\n      case HTTPBodyEvent::PUSH_PROMISE:\n      case HTTPBodyEvent::UPGRADE:\n        break;\n    }\n\n    if (bodyEvent->eom) {\n      handler_->onEOM();\n    }\n  }\n  XLOG(DBG6) << __func__ << \"; done\";\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSourceSink.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/sink/HTTPSink.h>\n#include <proxygen/lib/utils/ConditionalGate.h>\n\n#include <utility>\n\nnamespace proxygen::coro {\n\n/**\n * This class is a bridge between the HTTPTransaction and HTTPSource APIs\n *\n * Upstream Usage\n *\n * HTTPCoroSession* upstreamSession\n * HTTPCoroSession::RequestReservation reservation;\n *\n * auto sink = std::make_unique<HTTPStreamSourceSink>(\n *    evb, upstreamSession->acquireKeepAlive(), handler);\n * sink->transact(upstreamSession, std::move(reservation))\n *   .scheduleOn(evb).start();\n */\nclass HTTPStreamSourceUpstreamSink\n    : public HTTPSink\n    , public HTTPStreamSource::Callback {\n public:\n  explicit HTTPStreamSourceUpstreamSink(folly::EventBase* handlerEvb,\n                                        HTTPSessionContextPtr sessionCtx,\n                                        HTTPTransactionHandler* handler);\n  ~HTTPStreamSourceUpstreamSink() override;\n\n  HTTPSource* getEgressSource() {\n    return &egressSource_;\n  }\n  [[nodiscard]] folly::Optional<HTTPCodec::StreamID> getStreamID()\n      const override {\n    return id_;\n  }\n  [[nodiscard]] CodecProtocol getCodecProtocol() const override {\n    return sessionCtx_->getCodecProtocol();\n  }\n  [[nodiscard]] folly::Optional<HTTPPriority> getHTTPPriority() const override {\n    // TODO: !\n    return folly::none;\n  }\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return sessionCtx_->getLocalAddress();\n  }\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return sessionCtx_->getPeerAddress();\n  }\n  [[nodiscard]] const folly::AsyncTransportCertificate* getPeerCertificate()\n      const override {\n    return sessionCtx_->getPeerCertificate();\n  }\n  [[nodiscard]] int getTCPTransportFD() const override {\n    return sessionCtx_->getAsyncTransportFD();\n  }\n  [[nodiscard]] quic::QuicSocket* getQUICTransport() const override {\n    return sessionCtx_->getQUICTransport();\n  }\n  [[nodiscard]] std::chrono::seconds getSessionIdleDuration() const override {\n    return std::chrono::seconds(0);\n  }\n  void getCurrentFlowControlInfo(FlowControlInfo*) const override {\n  }\n  [[nodiscard]] CompressionInfo getHeaderCompressionInfo() const override {\n    return {};\n  }\n  void detachAndAbortIfIncomplete(std::unique_ptr<HTTPSink> self) override;\n\n  // Sending data\n  void sendHeaders(const HTTPMessage& headers) override {\n    sendHeadersWithOptionalEOM(headers, false);\n  }\n  void sendHeadersWithEOM(const HTTPMessage& headers) override {\n    sendHeadersWithOptionalEOM(headers, true);\n  }\n  void sendHeadersWithOptionalEOM(const HTTPMessage& headers,\n                                  bool eom) override;\n  void sendBody(std::unique_ptr<folly::IOBuf> body) override;\n  void sendChunkHeader(size_t /*length*/) override {\n  }\n  void sendChunkTerminator() override {\n  }\n  void sendTrailers(const HTTPHeaders& trailers) override {\n    egressSource_.trailers(std::make_unique<HTTPHeaders>(trailers));\n  }\n  void sendPadding(uint16_t bytes) override {\n    egressSource_.padding(bytes);\n  }\n  void sendEOM() override {\n    egressSource_.eom();\n  }\n  bool isEgressEOMSeen() override {\n    return egressSource_.isEOMSeen();\n  }\n  void sendAbort() override;\n  void updateAndSendPriority(HTTPPriority /*priority*/) override {\n    // TODO: Implement changing priority\n  }\n  bool trackEgressBodyOffset(uint64_t, ByteEventFlags) override {\n    // TODO:\n    return false;\n  }\n  bool canSendHeaders() const override {\n    return egressSource_.headersAllowed();\n  }\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept override {\n    return sessionCtx_->getSetupTransportInfo();\n  }\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const override {\n    sessionCtx_->getCurrentTransportInfo(tinfo,\n                                         /*includeSetupFields=*/true);\n  }\n  // Flow control\n  void pauseIngress() override {\n    XLOG(DBG8) << __func__;\n    ingressResumed_.reset();\n  }\n  void resumeIngress() override {\n    XLOG(DBG8) << __func__;\n    ingressResumed_.signal();\n    /**\n     * due to InlineExecutor being used, this will resume read loop inline and\n     * may cause the HTTPTxn to detach; it's important nothing comes after this\n     * line or a potential uaf\n     */\n  }\n  bool isIngressPaused() const override {\n    return !ingressResumed_.ready();\n  }\n  bool isEgressPaused() const override {\n    return windowState_ == HTTPStreamSource::FlowControlState::CLOSED;\n  }\n  void setEgressRateLimit(uint64_t /*bitsPerSecond*/) override {\n    // We can implement this by declaring a RateLimitFilter here and returning\n    // it in a chain with the egressSource.  Unfortunately, we pay a cost for\n    // that extra filter and never use it (upstream, for now).  So just no-op.\n  }\n  // Timeout\n  void setIdleTimeout(std::chrono::milliseconds timeout) override {\n    // TODO(@damlaj): implement\n  }\n  void timeoutExpired() override {\n    cancellationSource_.requestCancellation();\n  }\n  // Capabilities\n  bool supportsPush() const override {\n    // Technically HTTPCoroSession supports server push, but we'd need to\n    // abstract out more of the push API.\n    return false;\n  }\n\n  // Logging\n  void describe(std::ostream& os) override {\n    auto& peerAddr = sessionCtx_->getPeerAddress();\n    auto& localAddr = sessionCtx_->getLocalAddress();\n    if (sessionCtx_->isDownstream()) {\n      os << \"downstream=\" << peerAddr << \", \" << localAddr << \"=local\";\n    } else {\n      os << \", local=\" << localAddr << \", \" << peerAddr << \"=upstream\";\n    }\n    os << \", streamID=\" << (id_.value_or(HTTPCodec::MaxStreamID));\n  }\n\n  virtual folly::coro::Task<void> transact(\n      HTTPCoroSession* upstreamSession,\n      HTTPCoroSession::RequestReservation reservation);\n\n private:\n  folly::coro::Task<void> read(HTTPSourceHolder ingressSource);\n\n  void windowOpen(HTTPCodec::StreamID) override {\n    windowState_ = HTTPStreamSource::FlowControlState::OPEN;\n    handler_->onEgressResumed();\n  }\n\n  void sourceComplete(HTTPCodec::StreamID /*id*/,\n                      folly::Optional<HTTPError> /*error*/) override {\n    gate_.set(Event::EgressComplete);\n  }\n\n  HTTPSessionContextPtr sessionCtx_;\n  struct EgressHeaders {\n    const HTTPMessage* msg{nullptr};\n    bool eom{false};\n  } egressHeaders_;\n\n  class EgressSource : public HTTPStreamSource {\n   public:\n    using HTTPStreamSource::HTTPStreamSource;\n    using HTTPStreamSource::validateHeadersAndSkip;\n  } egressSource_;\n  HTTPSourceHolder ingressSource_;\n  HTTPTransactionHandler* handler_{nullptr};\n  folly::CancellationSource cancellationSource_;\n  detail::CancellableBaton ingressResumed_;\n  HTTPStreamSource::FlowControlState windowState_{\n      HTTPStreamSource::FlowControlState::OPEN};\n  folly::Optional<HTTPCodec::StreamID> id_;\n  enum class Event { IngressComplete, EgressComplete };\n  ConditionalGate<Event, 2> gate_;\n  // after detachAndAbortIfIncomplete\n  std::unique_ptr<HTTPSink> self_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPStreamSourceSinkFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPStreamSourceSink.h\"\n\nnamespace proxygen::coro {\n\nclass HTTPStreamSourceSinkFactory {\n public:\n  HTTPStreamSourceSinkFactory() = default;\n  virtual ~HTTPStreamSourceSinkFactory() = default;\n\n  virtual std::unique_ptr<coro::HTTPStreamSourceUpstreamSink>\n  newHTTPStreamSourceSink(folly::EventBase* evb,\n                          HTTPSessionContextPtr sessionCtx,\n                          HTTPTransactionHandler* handler) {\n    return std::make_unique<coro::HTTPStreamSourceUpstreamSink>(\n        evb, std::move(sessionCtx), handler);\n  }\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPTransactionAdaptorSource.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPTransactionAdaptorSource.h\"\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n\nnamespace {\nconstexpr std::chrono::milliseconds kDefaultEgressTimeout =\n    std::chrono::milliseconds(0);\n}\n\nnamespace proxygen::coro {\nHTTPTransactionAdaptorSource::HTTPTransactionAdaptorSource(\n    folly::EventBase* evb)\n    : evb_(evb),\n      ingressSource_(evb, folly::none, this /*callback */),\n      egressResumed_(evb, kDefaultEgressTimeout) {\n\n  gate_.then([this] { delete this; });\n\n  // egress starts unpaused.\n  egressResumed_.signal();\n}\n\nHTTPTransactionAdaptorSource* HTTPTransactionAdaptorSource::create(\n    folly::EventBase* evb) {\n  return new HTTPTransactionAdaptorSource(evb);\n}\n\nHTTPTransactionAdaptorSource::~HTTPTransactionAdaptorSource() {\n  XCHECK(!txn_);\n  XCHECK(ingressSource_.inputFinished());\n}\n\nfolly::CancellationToken HTTPTransactionAdaptorSource::getCancelToken() {\n  return cancelSource_.getToken();\n}\n\nHTTPSource* HTTPTransactionAdaptorSource::getIngressSource() {\n  return &ingressSource_;\n}\n\nvoid HTTPTransactionAdaptorSource::setEgressSource(\n    HTTPSourceHolder egressSource) {\n  egressSource_ = std::move(egressSource);\n  startEgressLoop();\n}\n\nvoid HTTPTransactionAdaptorSource::setTransaction(\n    HTTPTransaction* txn) noexcept {\n  txn_ = txn;\n}\n\nvoid HTTPTransactionAdaptorSource::detachTransaction() noexcept {\n  requestCancellation();\n\n  if (egressSource_) {\n    egressSource_.stopReading();\n    egressSource_ = nullptr;\n  }\n\n  txn_ = nullptr;\n  gate_.set(Event::EgressComplete);\n}\n\nvoid HTTPTransactionAdaptorSource::onHeadersComplete(\n    std::unique_ptr<HTTPMessage> msg) noexcept {\n  // When using http/1.1, ingress must be paused during a WebSocket request.\n  if (msg->isIngressWebsocketUpgrade()) {\n    txn_->pauseIngress();\n  };\n  ingressSource_.headers(std::move(msg), false /*eom*/);\n}\n\nvoid HTTPTransactionAdaptorSource::onBody(\n    std::unique_ptr<folly::IOBuf> chain) noexcept {\n  windowState_ = ingressSource_.body(std::move(chain), false /*eom*/);\n  if (windowState_ == HTTPStreamSource::FlowControlState::CLOSED &&\n      hasTransaction()) {\n    txn_->pauseIngress();\n  }\n}\n\nvoid HTTPTransactionAdaptorSource::onTrailers(\n    std::unique_ptr<HTTPHeaders> trailers) noexcept {\n  ingressSource_.trailers(std::move(trailers));\n}\n\nvoid HTTPTransactionAdaptorSource::onEOM() noexcept {\n  ingressSource_.eom();\n}\n\nvoid HTTPTransactionAdaptorSource::onUpgrade(\n    UpgradeProtocol /* protocol */) noexcept {\n  // no-op\n}\n\nvoid HTTPTransactionAdaptorSource::onError(\n    const HTTPException& error) noexcept {\n  ingressSource_.abort(HTTPException2HTTPErrorCode(error));\n  txn_->sendAbort();\n  requestCancellation();\n}\n\nvoid HTTPTransactionAdaptorSource::onEgressPaused() noexcept {\n  egressResumed_.reset();\n}\n\nvoid HTTPTransactionAdaptorSource::onEgressResumed() noexcept {\n  egressResumed_.signal();\n}\n\nvoid HTTPTransactionAdaptorSource::windowOpen(HTTPCodec::StreamID /*id*/) {\n  windowState_ = HTTPStreamSource::FlowControlState::OPEN;\n  if (hasTransaction()) {\n    txn_->resumeIngress();\n  }\n}\n\nvoid HTTPTransactionAdaptorSource::sourceComplete(\n    HTTPCodec::StreamID /*id*/, folly::Optional<HTTPError> /*error*/) {\n  gate_.set(Event::IngressComplete);\n}\n\nbool HTTPTransactionAdaptorSource::hasTransaction() const {\n  return txn_;\n}\n\nvoid HTTPTransactionAdaptorSource::startEgressLoop() {\n  co_withExecutor(\n      evb_,\n      folly::coro::co_withCancellation(cancelSource_.getToken(), egressLoop()))\n      .start();\n}\n\nfolly::coro::Task<void> HTTPTransactionAdaptorSource::egressLoop() {\n\n  // Ensure egress source is still available before we start reading.\n  co_await folly::coro::co_safe_point;\n\n  HTTPSourceReader reader(std::move(egressSource_));\n  reader\n      .preReadAsync([&]() -> folly::coro::Task<bool> {\n        co_await egressResumed_.wait();\n        co_return HTTPSourceReader::Continue;\n      })\n      .onHeaders([&, token = cancelSource_.getToken()](\n                     std::unique_ptr<HTTPMessage> msg,\n                     auto /* isFinal */,\n                     bool eom) -> bool {\n        if (token.isCancellationRequested()) {\n          return HTTPSourceReader::Cancel;\n        }\n        txn_->sendHeadersWithOptionalEOM(*msg, eom);\n        // Resume ingress if it was previously paused (e.g. for WebSocket\n        // upgrade).\n        const bool resumeIngress =\n            !token.isCancellationRequested() &&\n            windowState_ == HTTPStreamSource::FlowControlState::OPEN;\n        if (resumeIngress) {\n          txn_->resumeIngress();\n        }\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&, token = cancelSource_.getToken()](BufQueue body,\n                                                    bool eom) -> bool {\n        if (token.isCancellationRequested()) {\n          return HTTPSourceReader::Cancel;\n        }\n        if (!body.empty()) {\n          txn_->sendBody(body.move());\n        }\n        if (eom) {\n          txn_->sendEOM();\n        }\n        return HTTPSourceReader::Continue;\n      })\n      .onTrailers([&](std::unique_ptr<HTTPHeaders> trailers) {\n        txn_->sendTrailers(*trailers);\n      })\n      .onError([&, token = cancelSource_.getToken()](\n                   HTTPSourceReader::ErrorContext /* ctx */,\n                   const HTTPError& error) {\n        if (token.isCancellationRequested()) {\n          return;\n        }\n\n        XLOG_EVERY_MS(ERR, 10000)\n            << \"Error while handling transaction\" << error.describe();\n        txn_->sendAbort();\n      });\n\n  co_await reader.read();\n}\n\nvoid HTTPTransactionAdaptorSource::requestCancellation() {\n  cancelSource_.requestCancellation();\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/HTTPTransactionAdaptorSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/utils/ConditionalGate.h>\n\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n\nnamespace proxygen::coro {\n/*\n * This class creates an adaptor for an HTTPTransaction to simplify migrations\n * to the Coro API.\n *\n * It behaves as a transaction handler but drives an ingress stream source that\n * can be used with a coroutine handler.\n *\n * An egress source can be set to drive the egress of the transaction.\n */\nclass HTTPTransactionAdaptorSource\n    : public HTTPTransactionHandler\n    , public HTTPStreamSource::Callback {\n public:\n  static HTTPTransactionAdaptorSource* create(folly::EventBase* evb);\n  ~HTTPTransactionAdaptorSource() override;\n\n  HTTPSource* getIngressSource();\n\n  void setEgressSource(HTTPSourceHolder egressSource);\n  folly::CancellationToken getCancelToken();\n\n private:\n  explicit HTTPTransactionAdaptorSource(folly::EventBase* evb);\n\n  // HTTPTransactionHandler callback.\n  void setTransaction(HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(UpgradeProtocol protocol) noexcept override;\n  void onError(const HTTPException& error) noexcept override;\n  void onEgressPaused() noexcept override;\n  void onEgressResumed() noexcept override;\n\n  bool hasTransaction() const;\n\n  void windowOpen(HTTPCodec::StreamID /*id*/) override;\n  void sourceComplete(HTTPCodec::StreamID /*id*/,\n                      folly::Optional<HTTPError> /*error*/) override;\n\n  void startEgressLoop();\n  folly::coro::Task<void> egressLoop();\n  void requestCancellation();\n\n  folly::EventBase* evb_;\n  folly::CancellationSource cancelSource_;\n  HTTPTransaction* txn_{nullptr};\n\n  HTTPStreamSource ingressSource_;\n  HTTPStreamSource::FlowControlState windowState_{\n      HTTPStreamSource::FlowControlState::OPEN};\n\n  HTTPSourceHolder egressSource_;\n  TimedBaton egressResumed_;\n\n  enum class Event { EgressComplete, IngressComplete };\n  ConditionalGate<Event, 2> gate_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/benchmark/HTTPCoroBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include \"proxygen/lib/http/coro/server/ScopedHTTPServer.h\"\n#include <folly/logging/xlog.h>\n\n#include \"folly/init/Init.h\"\n#include <folly/Benchmark.h>\n#include <folly/Range.h>\n#include <folly/coro/Collect.h>\n#include <folly/coro/Generator.h>\n#include <proxygen/httpserver/ScopedHTTPServer.h>\n#include <proxygen/httpserver/samples/hq/HQServer.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\nusing namespace quic::samples;\n\nDEFINE_string(cert,\n              \"\",\n              \"server certificate to use, defaults to client test cert\");\nDEFINE_string(key, \"\", \"server key to use, defaults to client test key\");\n\nnamespace {\nconst std::string kTestDir =\n    getContainingDirectory(XLOG_FILENAME).str() + \"../client/test/\";\n\nstd::string getServerCertPath() {\n  if (FLAGS_cert.empty()) {\n    return kTestDir + \"certs/test_cert1.pem\";\n  } else {\n    return FLAGS_cert;\n  }\n}\n\nstd::string getServerKeyPath() {\n  if (FLAGS_key.empty()) {\n    return kTestDir + \"certs/test_key1.pem\";\n  } else {\n    return FLAGS_key;\n  }\n}\n\n// This is an insecure certificate verifier and is not meant to be\n// used in production. Using it in production would mean that this will\n// leave everyone insecure.\nclass InsecureVerifierDangerousDoNotUseInProduction\n    : public fizz::CertificateVerifier {\n public:\n  ~InsecureVerifierDangerousDoNotUseInProduction() override = default;\n\n  [[nodiscard]] std::shared_ptr<const folly::AsyncTransportCertificate> verify(\n      const std::vector<std::shared_ptr<const fizz::PeerCert>>& certs)\n      const override {\n    return certs.front();\n  }\n\n  [[nodiscard]] std::vector<fizz::Extension> getCertificateRequestExtensions()\n      const override {\n    return {};\n  }\n};\n\nenum class TransportType { TCP, TLS, TLS_FIZZ, QUIC };\n\nHTTPClient::SecureTransportImpl transportImpl(TransportType transportType) {\n  switch (transportType) {\n    case TransportType::TCP:\n      return HTTPClient::SecureTransportImpl::NONE;\n    case TransportType::TLS:\n      return HTTPClient::SecureTransportImpl::TLS;\n    case TransportType::TLS_FIZZ:\n      return HTTPClient::SecureTransportImpl::FIZZ;\n    default:\n      XLOG(FATAL) << \"Don't call this for QUIC\";\n  }\n}\n\n// Handler that returns a fixed-size 200 response\nclass SizeHandler\n    : public HTTPHandler\n    , HTTPTransactionHandler {\n  std::unique_ptr<folly::IOBuf> respBody_;\n  HTTPTransaction* txn_{nullptr};\n\n public:\n  explicit SizeHandler(size_t size) : respBody_(makeBuf(size)) {\n  }\n\n  explicit SizeHandler(std::unique_ptr<folly::IOBuf> respBody)\n      : respBody_(std::move(respBody)) {\n  }\n\n  SizeHandler(const SizeHandler& other) : respBody_(other.respBody_->clone()) {\n  }\n\n  SizeHandler& operator=(const SizeHandler& other) {\n    if (this != &other) {\n      respBody_ = other.respBody_->clone();\n    }\n    return *this;\n  }\n\n  // Coro handler\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* /*evb*/,\n      HTTPSessionContextPtr /*ctx*/,\n      HTTPSourceHolder requestSource) override {\n    auto headerEvent = co_await requestSource.readHeaderEvent();\n    XCHECK(headerEvent.eom);\n    co_return HTTPFixedSource::makeFixedResponse(200, respBody_->clone());\n  }\n\n  // ScopedHTTPServer handler\n  void operator()(const HTTPMessage&,\n                  std::unique_ptr<folly::IOBuf>,\n                  ResponseBuilder& resp) {\n    resp.status(200, \"OK\");\n    resp.body(respBody_->clone());\n  }\n\n  static HTTPTransactionHandler* makeHandler(\n      HTTPMessage*, std::unique_ptr<folly::IOBuf> resp) {\n    return new SizeHandler(std::move(resp));\n  }\n\n  // HTTPTransactionHandler\n  void setTransaction(HTTPTransaction* txn) noexcept override {\n    txn_ = txn;\n  }\n  void detachTransaction() noexcept override {\n    txn_ = nullptr;\n    delete this;\n  }\n  void onHeadersComplete(std::unique_ptr<HTTPMessage>) noexcept override {\n  }\n  void onBody(std::unique_ptr<folly::IOBuf>) noexcept override {\n  }\n  void onTrailers(std::unique_ptr<HTTPHeaders>) noexcept override {\n  }\n  void onEOM() noexcept override {\n    if (txn_) {\n      txn_->sendHeaders(getResponse(200));\n      txn_->sendBody(respBody_->clone());\n      txn_->sendEOM();\n    }\n  }\n  void onUpgrade(UpgradeProtocol) noexcept override {\n  }\n  void onError(const HTTPException& error) noexcept override {\n    if (error.getDirection() == HTTPException::Direction::INGRESS && txn_) {\n      txn_->sendAbort();\n      txn_ = nullptr;\n    }\n  }\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n};\n\n/* Slightly different thatn folly::ScopedEventBaseThread - this class calls\n * loop() instead of loopForever().\n */\nclass ScopedEventBaseThread {\n public:\n  explicit ScopedEventBaseThread()\n      : thread_([this] {\n          started_.post();\n          scheduled_.wait();\n          evb_.loop();\n        }) {\n    started_.wait();\n  }\n\n  void runInThread(std::function<void(folly::EventBase*)> func) {\n    evb_.runInEventBaseThread([this, func = std::move(func)] { func(&evb_); });\n    scheduled_.post();\n  }\n\n  ~ScopedEventBaseThread() {\n    thread_.join();\n  }\n\n  folly::Baton<> started_;\n  folly::Baton<> scheduled_;\n  folly::EventBase evb_;\n  std::thread thread_;\n};\n\nclass BenchmarkFixture {\n public:\n  explicit BenchmarkFixture(TransportType type,\n                            size_t respSize = 0,\n                            size_t concurrentReqsPerSess = 1,\n                            size_t numClientThreads = 1,\n                            bool coro = true)\n      : transportType_(type),\n        respSize_(respSize),\n        concurrentReqsPerSess_(concurrentReqsPerSess),\n        numClientThreads_(numClientThreads),\n        coro_(coro) {\n    BENCHMARK_SUSPEND {\n      init();\n    }\n  }\n\n  ~BenchmarkFixture() {\n    BENCHMARK_SUSPEND {\n      tearDown();\n    }\n  }\n\n  void init() {\n    // Disable cert verification since the server is self-signed\n    HTTPClient::setDefaultCAPaths({});\n    HTTPClient::setDefaultFizzCertVerifier(\n        std::make_shared<InsecureVerifierDangerousDoNotUseInProduction>());\n    if (coro_) {\n      initCoro();\n    } else {\n      initLegacy();\n    }\n    req_ = getGetRequest();\n  }\n\n  void initCoro() {\n    proxygen::coro::HTTPServer::Config serverConfig;\n    auto tlsConfig = proxygen::coro::HTTPServer::getDefaultTLSConfig();\n    tlsConfig.isDefault = true;\n    tlsConfig.clientVerification =\n        folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n    tlsConfig.setNextProtocols({\"h2\", \"http/1.1\"});\n    try {\n      tlsConfig.setCertificate(getServerCertPath(), getServerKeyPath(), \"\");\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n    }\n    serverConfig.socketConfig.bindAddress.setFromLocalPort(uint16_t(0));\n    if (transportType_ != TransportType::TCP) {\n      serverConfig.socketConfig.sslContextConfigs.emplace_back(\n          std::move(tlsConfig));\n    }\n    if (transportType_ == TransportType::QUIC) {\n      serverConfig.quicConfig = proxygen::coro::HTTPServer::QuicConfig();\n      serverConfig.quicConfig->transportSettings.maxNumPTOs = 1000;\n      serverConfig.quicConfig->transportSettings.maxCwndInMss =\n          quic::kLargeMaxCwndInMss;\n      serverConfig.quicConfig->transportSettings.batchingMode =\n          quic::QuicBatchingMode::BATCHING_MODE_GSO;\n      serverConfig.quicConfig->transportSettings.maxBatchSize = 48;\n      serverConfig.quicConfig->transportSettings.dataPathType =\n          quic::DataPathType::ContinuousMemory;\n      serverConfig.quicConfig->transportSettings\n          .writeConnectionDataPacketsLimit = 48;\n    }\n    serverConfig.shutdownOnSignals = {};\n\n    coroServer_ = proxygen::coro::ScopedHTTPServer::start(\n        std::move(serverConfig), std::make_shared<SizeHandler>(respSize_));\n    serverAddress_ = *coroServer_->address();\n  }\n\n  void initLegacy() {\n    if (transportType_ == TransportType::QUIC) {\n      HQServerParams params;\n      params.serverThreads = 1;\n      params.transportSettings.maxNumPTOs = 1000;\n      params.transportSettings.maxCwndInMss = quic::kLargeMaxCwndInMss;\n      params.transportSettings.batchingMode =\n          quic::QuicBatchingMode::BATCHING_MODE_GSO;\n      params.transportSettings.dataPathType =\n          quic::DataPathType::ContinuousMemory;\n      params.transportSettings.maxBatchSize = 48;\n      params.transportSettings.writeConnectionDataPacketsLimit = 48;\n      resp_ = makeBuf(respSize_);\n      folly::SocketAddress localAddress;\n      localAddress.setFromLocalPort(uint16_t(0));\n      legacyQuicServer_ = ScopedHQServer::start(\n          params,\n          localAddress,\n          [this](HTTPMessage* msg) {\n            return SizeHandler::makeHandler(msg, resp_->clone());\n          },\n          getServerCertPath(),\n          getServerKeyPath(),\n          fizz::server::ClientAuthMode::None,\n          kDefaultSupportedAlpns);\n      serverAddress_ = legacyQuicServer_->getAddress();\n    } else {\n      std::unique_ptr<wangle::SSLContextConfig> sslCfg;\n      if (transportType_ != TransportType::TCP) {\n        sslCfg = std::make_unique<wangle::SSLContextConfig>();\n        sslCfg->isDefault = true;\n        sslCfg->clientVerification =\n            folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n        sslCfg->setNextProtocols({\"h2\", \"http/1.1\"});\n        sslCfg->setCertificate(getServerCertPath(), getServerKeyPath(), \"\");\n      }\n      legacyServer_ = proxygen::ScopedHTTPServer::start(SizeHandler(respSize_),\n                                                        /*port=*/0,\n                                                        /*numThreads=*/1,\n                                                        std::move(sslCfg));\n      serverAddress_ = legacyServer_->getAddresses()[0].address;\n    }\n  }\n\n  void tearDown() {\n    coroServer_.reset();\n    legacyServer_.reset();\n  }\n\n  void run(size_t iters) {\n    std::list<ScopedEventBaseThread> clientThreads;\n    BENCHMARK_SUSPEND {\n      clientThreads.resize(numClientThreads_);\n    }\n    iters /= numClientThreads_;\n    for (auto& thr : clientThreads) {\n      thr.runInThread([this, iters](folly::EventBase* evb) {\n        co_withExecutor(evb, runImpl(evb, iters)).start();\n      });\n    }\n  }\n\n private:\n  TransportType transportType_;\n  size_t respSize_{0};\n  size_t concurrentReqsPerSess_{100};\n  size_t connsPerThread_{15};\n  size_t numClientThreads_{1};\n  bool coro_{true};\n  std::unique_ptr<proxygen::coro::ScopedHTTPServer> coroServer_;\n  std::unique_ptr<proxygen::ScopedHTTPServer> legacyServer_;\n  std::unique_ptr<ScopedHQServer> legacyQuicServer_;\n  folly::SocketAddress serverAddress_;\n  HTTPMessage req_;\n  std::unique_ptr<folly::IOBuf> resp_;\n\n  // Make one HTTP request from a pool\n  folly::coro::Task<void> get(HTTPCoroSessionPool& pool) {\n    auto res = co_await co_awaitTry(pool.getSessionWithReservation());\n    if (res.hasException()) {\n      XLOG(ERR) << res.exception().what();\n      res.throwUnlessValue();\n    }\n    HTTPSourceReader reader;\n    reader\n        .onHeaders([](std::unique_ptr<HTTPMessage> resp, bool, bool) {\n          XLOG_IF(ERR, resp->getStatusCode() != 200)\n              << \"Error response, status=\" << resp->getStatusCode();\n          return HTTPSourceReader::Continue;\n        })\n        .onError([](HTTPSourceReader::ErrorContext, const HTTPError& err) {\n          XLOG(ERR) << err.msg;\n        });\n    auto maybe = co_await co_awaitTry(HTTPClient::request(\n        res->session,\n        std::move(res->reservation),\n        HTTPFixedSource::makeFixedSource(std::make_unique<HTTPMessage>(req_)),\n        std::move(reader)));\n    if (maybe.hasException()) {\n      XLOG(ERR) << maybe.exception().what();\n      res.throwUnlessValue();\n    }\n  }\n\n  std::unique_ptr<HTTPCoroSessionPool> makePool(folly::EventBase* evb) {\n    HTTPCoroConnector::SessionParams sessParams;\n    sessParams.connFlowControl = 1 << 21;\n    sessParams.maxConcurrentOutgoingStreams = concurrentReqsPerSess_;\n    std::unique_ptr<HTTPCoroSessionPool> pool;\n    auto poolParams = HTTPCoroSessionPool::defaultPoolParams();\n    poolParams.maxConnections = connsPerThread_;\n    if (transportType_ == TransportType::QUIC) {\n      auto qConnParams = HTTPClient::getQuicConnParams();\n      qConnParams.transportSettings.maxNumPTOs = 1000;\n      auto qConnParamsPtr =\n          std::make_shared<const HTTPCoroConnector::QuicConnectionParams>(\n              std::move(qConnParams));\n      pool =\n          std::make_unique<HTTPCoroSessionPool>(evb,\n                                                serverAddress_.getAddressStr(),\n                                                serverAddress_.getPort(),\n                                                poolParams,\n                                                std::move(qConnParamsPtr),\n                                                sessParams);\n    } else {\n      pool = std::make_unique<HTTPCoroSessionPool>(\n          evb,\n          serverAddress_.getAddressStr(),\n          serverAddress_.getPort(),\n          poolParams,\n          HTTPClient::getConnParams(transportImpl(transportType_)),\n          sessParams);\n    }\n    return pool;\n  }\n\n  // Single thread main\n  //\n  // Create a pool, then make iters requests,\n  // connsPerThread_ * concurrentReqsPerSess_ at a time\n  folly::coro::Task<void> runImpl(folly::EventBase* evb,\n                                  size_t iters) noexcept {\n    auto pool = makePool(evb);\n    auto generator = [this](HTTPCoroSessionPool& pool, size_t iters)\n        -> folly::coro::Generator<folly::coro::Task<void>&&> {\n      for (size_t i = 0; i < iters; ++i) {\n        co_yield get(pool);\n      }\n    };\n    co_await folly::coro::collectAllWindowed(\n        generator(*pool, iters), concurrentReqsPerSess_ * connsPerThread_);\n  }\n};\n} // namespace\n\n// Benchmarks\n\n// Size 0, Concurrency 1\nBENCHMARK(legacy_plaintext_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 0, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(plaintext_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 0, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_fizz_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 0, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 0, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 0, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 0, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 0, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_1_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 0, 1, 2);\n  fixture.run(iters);\n}\n\n// Size 0, concurrency 20\nBENCHMARK(legacy_fizz_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 0, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 0, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 0, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 0, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 0, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_20_size_0, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 0, 20, 2);\n  fixture.run(iters);\n}\n\n// Size 4096, concurrency 1\nBENCHMARK(legacy_plaintext_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 4096, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(plaintext_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 4096, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_fizz_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 4096, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 4096, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 4096, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 4096, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 4096, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_1_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 4096, 1, 2);\n  fixture.run(iters);\n}\n\n// Size 4096, concurrency 20\nBENCHMARK(legacy_fizz_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(\n      TransportType::TLS_FIZZ, 4096, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 4096, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 4096, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 4096, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 4096, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_20_size_4096, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 4096, 20, 2);\n  fixture.run(iters);\n}\n\n// Size 65535, concurrency 1\nBENCHMARK(legacy_plaintext_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 65535, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(plaintext_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 65535, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_fizz_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(\n      TransportType::TLS_FIZZ, 65535, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 65535, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 65535, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 65535, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 65535, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_1_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 65535, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_plaintext_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 1 << 20, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(plaintext_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(TransportType::TCP, 1 << 20, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_fizz_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(\n      TransportType::TLS_FIZZ, 1 << 20, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 1 << 20, 1, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 1 << 20, 1, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_1_size_1M, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 1 << 20, 1, 2);\n  fixture.run(iters);\n}\n\n// Size 65535, concurrency 20\nBENCHMARK(legacy_fizz_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(\n      TransportType::TLS_FIZZ, 65535, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(fizz_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS_FIZZ, 65535, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_openssl_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 65535, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(openssl_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::TLS, 65535, 20, 2);\n  fixture.run(iters);\n}\n\nBENCHMARK(legacy_quic_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 65535, 20, 2, /*coro=*/false);\n  fixture.run(iters);\n}\n\nBENCHMARK(quic_threads_2_concurrency_20_size_65535, iters) {\n  BenchmarkFixture fixture(TransportType::QUIC, 65535, 20, 2);\n  fixture.run(iters);\n}\n\nint main(int argc, char** argv) {\n  auto init = folly::Init(&argc, &argv);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_client_dns_resolver\n  SRCS\n    CoroDNSResolver.cpp\n  DEPS\n    proxygen_dns_dns_module\n    proxygen_dns_rfc6724\n    Folly::folly_coro_baton\n    Folly::folly_io_async_async_base\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_dns_dns_base\n    Folly::folly_coro_task\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_http_coro_client_http_connector\n  SRCS\n    HTTPCoroConnector.cpp\n  DEPS\n    proxygen_http_codec_codec_factory\n    proxygen_http_coro_transport_coro_ssl_transport\n    proxygen_http_coro_transport_http_connect_transport\n    proxygen_http_coro_util_Transport\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_client\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_common_udpsocket_folly_async_udp_socket\n    mvfst::mvfst_exception\n    mvfst::mvfst_fizz_client_handshake\n    wangle::wangle_ssl_ssl_stats\n    wangle::wangle_ssl_ssl_util\n    fizz::fizz\n    Folly::folly_coro_collect\n    Folly::folly_coro_shared_promise\n    Folly::folly_coro_sleep\n    Folly::folly_file_util\n    Folly::folly_io_async_async_ssl_socket\n    Folly::folly_io_async_ssl_options\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_compress_header_codec\n    proxygen_sampling\n    mvfst::mvfst_api_loop_detector_callback\n    mvfst::mvfst_fizz_client_handshake_psk_cache\n    mvfst::mvfst_logging_qlogger\n    mvfst::mvfst_state_stats_callback\n    mvfst::mvfst_state_transport_settings\n    fizz::fizz\n    Folly::folly_coro_task\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_async_ssl_context\n    Folly::folly_io_socket_option_map\n    Folly::folly_network_address\n    Folly::folly_ssl_ssl_session\n)\n\nproxygen_add_library(proxygen_http_coro_client_http_session_factory\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_http_coro_client_http_client_lib\n)\n\nproxygen_add_library(proxygen_http_coro_client_http_connpool\n  SRCS\n    HTTPCoroSessionPool.cpp\n  DEPS\n    proxygen_utils_time_util\n    Folly::folly_random\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_http_coro_client_http_connector\n    proxygen_http_coro_client_http_session_factory\n    Folly::folly_intrusive_list\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_client_cert_reload_session_pool\n  SRCS\n    CertReloadSessionPool.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_coro_client_http_connector\n    proxygen_http_coro_client_http_connpool\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_coro_client_http_client_connection_cache\n  SRCS\n    HTTPClientConnectionCache.cpp\n  DEPS\n    proxygen_http_coro_client_dns_resolver\n    proxygen_http_coro_client_http_client_lib\n  EXPORTED_DEPS\n    proxygen_http_coro_client_http_connpool\n    fizz::fizz\n    Folly::folly_container_evicting_cache_map\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_client_http_client_lib\n  SRCS\n    HTTPClient.cpp\n  DEPS\n    proxygen_coro_source\n    proxygen_http_coro_client_dns_resolver\n    Folly::folly_logging_logging\n    Folly::folly_network_address\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_http_coro_client_http_connector\n    proxygen_http_coro_coro_reader\n    proxygen_http_coro_filters_logger\n    proxygen_http_message\n    proxygen_utils_parse_url\n    Folly::folly_coro_task\n    Folly::folly_io_iobuf\n)\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/CertReloadSessionPool.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/coro/client/CertReloadSessionPool.h>\n\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nCertReloadSessionPool::~CertReloadSessionPool() {\n  XCHECK(getEventBase()->isInEventBaseThread());\n}\n\nvoid CertReloadSessionPool::setTimerCallback(\n    std::function<void(HTTPCoroSessionPool&)> cb,\n    std::chrono::milliseconds interval) {\n  XCHECK(getEventBase()->isInEventBaseThread());\n  if (interval.count() > 0) {\n    reloadTimer_.emplace(*this, std::move(cb), interval);\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/CertReloadSessionPool.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <folly/io/async/HHWheelTimer.h>\n#include <optional>\n#include <proxygen/lib/http/coro/client/HTTPCoroConnector.h>\n#include <proxygen/lib/http/coro/client/HTTPCoroSessionPool.h>\n\nnamespace proxygen::coro {\n\n/**\n * An HTTPCoroSessionPool that invokes a user-provided callback on a\n * configurable interval. This is useful for periodically reloading client\n * certificates to prevent expiration issues in long-lived tasks.\n *\n * This class derives from HTTPCoroSessionPool, inheriting all its constructors.\n * It can be used anywhere an HTTPCoroSessionPool is expected, including as a\n * proxyPool parameter.\n *\n * The user is responsible for providing a callback that performs the desired\n * action (e.g., reloading certificates and calling setConnParams()).\n *\n * Example usage:\n *   auto pool = std::make_shared<CertReloadSessionPool>(\n *       evb, \"server.example.com\", 443, poolParams, connParams, sessionParams);\n *\n *   pool->setTimerCallback(\n *       [](HTTPCoroSessionPool& p) {\n *         TLSParams tlsParams;\n *         tlsParams.clientCertPath = \"/path/to/cert\";\n *         tlsParams.clientKeyPath = \"/path/to/key\";\n *         auto connParams = HTTPCoroConnector::ConnectionParams{};\n *         connParams.fizzContextAndVerifier.fizzContext =\n *             HTTPCoroConnector::makeFizzClientContext(tlsParams);\n *         p.setConnParams(connParams);\n *       },\n *       std::chrono::milliseconds(600000)); // 10 minutes\n */\nclass CertReloadSessionPool : public HTTPCoroSessionPool {\n public:\n  using HTTPCoroSessionPool::HTTPCoroSessionPool;\n\n  ~CertReloadSessionPool() override;\n\n  CertReloadSessionPool(const CertReloadSessionPool&) = delete;\n  CertReloadSessionPool& operator=(const CertReloadSessionPool&) = delete;\n  CertReloadSessionPool(CertReloadSessionPool&&) = delete;\n  CertReloadSessionPool& operator=(CertReloadSessionPool&&) = delete;\n\n  void setTimerCallback(std::function<void(HTTPCoroSessionPool&)> cb,\n                        std::chrono::milliseconds interval);\n\n private:\n  class ReloadTimerCallback : public folly::HHWheelTimer::Callback {\n   public:\n    ReloadTimerCallback(CertReloadSessionPool& pool,\n                        std::function<void(HTTPCoroSessionPool&)> cb,\n                        std::chrono::milliseconds interval)\n        : pool_(pool), cb_(std::move(cb)), interval_(interval) {\n      schedule();\n    }\n\n    ~ReloadTimerCallback() override {\n      cancelTimeout();\n    }\n\n    ReloadTimerCallback(const ReloadTimerCallback&) = delete;\n    ReloadTimerCallback& operator=(const ReloadTimerCallback&) = delete;\n    ReloadTimerCallback(ReloadTimerCallback&&) = delete;\n    ReloadTimerCallback& operator=(ReloadTimerCallback&&) = delete;\n\n    void timeoutExpired() noexcept override {\n      cb_(pool_);\n      schedule();\n    }\n\n    void callbackCanceled() noexcept override {\n    }\n\n   private:\n    void schedule() {\n      pool_.getEventBase()->timer().scheduleTimeout(this, interval_);\n    }\n\n    CertReloadSessionPool& pool_;\n    std::function<void(HTTPCoroSessionPool&)> cb_;\n    std::chrono::milliseconds interval_;\n  };\n\n  std::optional<ReloadTimerCallback> reloadTimer_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/CoroDNSResolver.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/dns/DNSModule.h\"\n#include \"proxygen/lib/dns/DNSResolver.h\"\n#include \"proxygen/lib/dns/Rfc6724.h\"\n#include \"proxygen/lib/http/coro/client/CoroDNSResolver.h\"\n#include <folly/coro/Baton.h>\n#include <folly/io/async/EventBaseLocal.h>\n\nusing folly::coro::co_error;\nusing folly::coro::co_nothrow;\n\nnamespace {\nusing namespace proxygen;\n\nusing StatsCollector = DNSResolver::StatsCollector;\nstatic folly::EventBaseLocal<DNSResolver::UniquePtr> resolver_;\nstatic folly::EventBaseLocal<std::unique_ptr<StatsCollector>> stats_;\n\nbool hasDNSResolverInstance(folly::EventBase* evb) {\n  return resolver_.get(*evb) != nullptr;\n}\n\nbool hasStatsCollector(folly::EventBase* evb) {\n  return stats_.get(*evb);\n}\n\nStatsCollector* getStatsCollector(folly::EventBase* evb) {\n  XCHECK(hasStatsCollector(evb));\n  return stats_.get(*evb)->get();\n}\n\nDNSResolver::UniquePtr* getDNSResolverInstance(folly::EventBase* evb) {\n  if (hasDNSResolverInstance(evb)) {\n    return resolver_.get(*evb);\n  }\n\n  if (auto dnsModule = DNSModule::get()) {\n    auto resolver = dnsModule->provideDNSResolver(evb);\n    if (hasStatsCollector(evb)) {\n      resolver->setStatsCollector(getStatsCollector(evb));\n    }\n    resolver_.try_emplace(*evb, std::move(resolver));\n  }\n\n  // returns nullptr if DNSModule::get() == nullptr (i.e. on shutdown)\n  return resolver_.get(*evb);\n}\n\nclass CoroResolutionCallback : public DNSResolver::ResolutionCallback {\n public:\n  explicit CoroResolutionCallback(folly::coro::Baton& baton) : baton_(baton) {\n  }\n\n  void resolutionSuccess(\n      std::vector<DNSResolver::Answer> ans) noexcept override {\n    for (const auto& answer : ans) {\n      if (answer.type == DNSResolver::Answer::AT_ADDRESS) {\n        addrs.push_back(answer.address);\n      }\n    }\n    if (addrs.empty()) {\n      exception = DNSResolver::makeNoNameException();\n    } else {\n      // fallback to unsorted addresses if rfc6724_sort throws an exception\n      folly::makeTryWith([&]() { rfc6724_sort(addrs); });\n    }\n\n    baton_.post();\n  }\n  void resolutionError(const folly::exception_wrapper& exp) noexcept override {\n    exception = exp;\n    baton_.post();\n  }\n\n  std::vector<folly::SocketAddress> addrs;\n  folly::exception_wrapper exception;\n\n private:\n  folly::coro::Baton& baton_;\n};\n\n} // namespace\n\nnamespace proxygen::coro {\n\nvoid CoroDNSResolver::emplaceStatsCollector(\n    folly::EventBase* evb, std::unique_ptr<StatsCollector> statsCollector) {\n  stats_.try_emplace(*evb, std::move(statsCollector));\n}\n\nvoid CoroDNSResolver::resetDNSResolverInstance(folly::EventBase* evb,\n                                               DNSResolver::UniquePtr ptr) {\n  if (hasDNSResolverInstance(evb)) {\n    resolver_.erase(*evb);\n  }\n  resolver_.try_emplace(*evb, std::move(ptr));\n}\n\nfolly::coro::Task<std::vector<folly::SocketAddress>>\nCoroDNSResolver::resolveHostAll(folly::EventBase* evb,\n                                std::string hostname,\n                                std::chrono::milliseconds timeout,\n                                DNSResolver* resolver) {\n\n  folly::coro::Baton baton;\n  CoroResolutionCallback cb(baton);\n\n  if (resolver) {\n    resolver->resolveHostname(&cb, hostname, timeout, AF_UNSPEC);\n  } else if (auto* res = getDNSResolverInstance(evb)) {\n    res->get()->resolveHostname(&cb, hostname, timeout, AF_UNSPEC);\n  } else {\n    // fail dns request during shutdown; signal baton to avoid blocking\n    cb.exception = folly::make_exception_wrapper<DNSResolver::Exception>(\n        DNSResolver::SHUTDOWN, \"shutting down\");\n    baton.post();\n  }\n\n  co_await baton;\n\n  if (cb.exception) {\n    co_yield co_error(cb.exception);\n  }\n  XCHECK(!cb.addrs.empty());\n  co_return cb.addrs;\n}\n\nfolly::coro::Task<CoroDNSResolver::Result> CoroDNSResolver::resolveHost(\n    folly::EventBase* evb,\n    std::string hostname,\n    std::chrono::milliseconds timeout,\n    DNSResolver* resolver) {\n  auto addrs = co_await co_nothrow(CoroDNSResolver::resolveHostAll(\n      evb, std::move(hostname), timeout, resolver));\n\n  CoroDNSResolver::Result result;\n  result.primary = std::move(addrs.front());\n  for (auto it = addrs.begin() + 1; it != addrs.end(); ++it) {\n    if (it->getFamily() != result.primary.getFamily()) {\n      result.fallback = std::move(*it);\n      break;\n    }\n  }\n  co_return result;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/CoroDNSResolver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/SocketAddress.h>\n#include <folly/coro/Task.h>\n\n#include <proxygen/lib/dns/DNSResolver.h>\n\n#include <string>\n\nnamespace proxygen::coro {\n\n/**\n * Asynchronously reoslve a hostname to an IP address.\n */\nstruct CoroDNSResolver {\n  struct Result {\n    folly::SocketAddress primary;\n    folly::Optional<folly::SocketAddress> fallback;\n  };\n\n  /**\n   * for both ::resolveHost & ::resolveHostAll – \n   *\n   * resolves hostname using the DNSResolver obtained from the global DNSModule\n   * if the resolver argument is nullptr, otherwise uses resolver\n   */\n  static folly::coro::Task<Result> resolveHost(\n      folly::EventBase* evb,\n      std::string hostname,\n      std::chrono::milliseconds timeout,\n      DNSResolver* resolver = nullptr);\n\n  static folly::coro::Task<std::vector<folly::SocketAddress>> resolveHostAll(\n      folly::EventBase* evb,\n      std::string hostname,\n      std::chrono::milliseconds timeout,\n      DNSResolver* resolver = nullptr);\n\n  static void emplaceStatsCollector(\n      folly::EventBase* evb, std::unique_ptr<DNSResolver::StatsCollector>);\n\n  static void resetDNSResolverInstance(folly::EventBase* evb,\n                                       DNSResolver::UniquePtr ptr);\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPClient.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/client/CoroDNSResolver.h\"\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include <folly/SocketAddress.h>\n#include <proxygen/lib/utils/URL.h>\n\nusing folly::coro::co_error;\n\nnamespace {\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nconst uint32_t kDefaultStreamWindow = 1 << 20;\nconst uint32_t kDefaultSessionWindow = kDefaultStreamWindow * 10;\nconst std::list<std::string> kDefaultNextProtocols = {\"h2\", \"http/1.1\"};\nconst std::list<std::string> kDefaultQUICNextProtocols = {\"h3\"};\n\nHTTPCoroConnector::TLSParams makeTLSParams(\n    folly::StringPiece clientCertPath,\n    folly::StringPiece clientKeyPath,\n    std::list<std::string> nextProtocols) {\n  HTTPCoroConnector::TLSParams tlsParams(std::move(nextProtocols));\n  tlsParams.caPaths = HTTPClient::getDefaultCAPaths();\n  tlsParams.clientCertPath = clientCertPath;\n  tlsParams.clientKeyPath = clientKeyPath;\n  return tlsParams;\n}\n\nusing SecureTransportImpl = HTTPClient::SecureTransportImpl;\nvoid getConnParamsImpl(HTTPCoroConnector::ConnectionParams* connParams,\n                       HTTPCoroConnector::QuicConnectionParams* qconnParams,\n                       SecureTransportImpl secureTransportImpl,\n                       folly::StringPiece sni,\n                       const HTTPCoroConnector::TLSParams& tlsParams) {\n  XCHECK(!qconnParams || (secureTransportImpl == SecureTransportImpl::FIZZ));\n  HTTPCoroConnector::BaseConnectionParams* baseParams{nullptr};\n  if (secureTransportImpl != SecureTransportImpl::NONE) {\n    if (qconnParams) {\n      baseParams = qconnParams;\n      qconnParams->transportSettings\n          .advertisedInitialConnectionFlowControlWindow = kDefaultSessionWindow;\n      qconnParams->transportSettings\n          .advertisedInitialBidiLocalStreamFlowControlWindow =\n          kDefaultStreamWindow;\n      // No such thing as remote bidi HTTP stream, but set it anyways\n      qconnParams->transportSettings\n          .advertisedInitialBidiRemoteStreamFlowControlWindow =\n          kDefaultStreamWindow;\n      qconnParams->transportSettings.shouldUseRecvmmsgForBatchRecv = true;\n      qconnParams->transportSettings.maxRecvBatchSize = 64;\n      qconnParams->transportSettings.batchingMode =\n          quic::QuicBatchingMode::BATCHING_MODE_GSO;\n      qconnParams->transportSettings.numGROBuffers_ = 64;\n    } else {\n      baseParams = connParams;\n    }\n    if (secureTransportImpl == SecureTransportImpl::TLS) {\n      connParams->sslContext = HTTPCoroConnector::makeSSLContext(tlsParams);\n    } else {\n      HTTPCoroConnector::FizzContextAndVerifier fizzContextAndVerifier;\n\n      // This can throw\n      if (auto verifier = HTTPClient::getDefaultCertVerifier()) {\n        fizzContextAndVerifier.fizzContext =\n            HTTPCoroConnector::makeFizzClientContext(tlsParams);\n        fizzContextAndVerifier.fizzCertVerifier = verifier;\n      } else {\n        fizzContextAndVerifier =\n            HTTPCoroConnector::makeFizzClientContextAndVerifier(tlsParams);\n      }\n\n      baseParams->fizzContextAndVerifier = std::move(fizzContextAndVerifier);\n    }\n    baseParams->serverName = sni.str();\n  }\n}\n\nstd::unique_ptr<folly::IOBuf> stringToIOBuf(std::optional<std::string> str) {\n  std::unique_ptr<folly::IOBuf> strBuf;\n  if (str && !str->empty()) {\n    auto strPtr = std::make_unique<std::string>(std::move(str).value());\n    strBuf = folly::IOBuf::takeOwnership(\n        strPtr->data(),\n        strPtr->size(),\n        [](void*, void* data) { delete static_cast<std::string*>(data); },\n        strPtr.release());\n  }\n  return strBuf;\n}\n\nHTTPSource* makeHTTPRequestSource(\n    const URL& url,\n    HTTPMethod method,\n    HTTPClient::RequestHeaderMap requestHeaders =\n        HTTPClient::RequestHeaderMap(),\n    std::optional<std::string> body = std::nullopt) {\n  auto reqSource = HTTPFixedSource::makeFixedRequest(\n      url.makeRelativeURL(), method, stringToIOBuf(body));\n  for (auto& nameValue : requestHeaders) {\n    reqSource->msg_->getHeaders().add(nameValue.first,\n                                      std::move(nameValue.second));\n  }\n  if (!reqSource->msg_->getHeaders().exists(HTTP_HEADER_HOST)) {\n    reqSource->msg_->getHeaders().add(HTTP_HEADER_HOST,\n                                      url.getHostAndPortOmitDefault());\n  }\n  reqSource->msg_->setWantsKeepalive(true);\n  reqSource->msg_->setSecure(url.isSecure());\n  return reqSource;\n}\n\nHTTPSource* makeHTTPRequestSource(\n    const URL& url,\n    HTTPClient::RequestHeaderMap requestHeaders =\n        HTTPClient::RequestHeaderMap(),\n    std::optional<std::string> body = std::nullopt) {\n  HTTPMethod method = (body) ? HTTPMethod::POST : HTTPMethod::GET;\n  return makeHTTPRequestSource(\n      url, method, std::move(requestHeaders), std::move(body));\n}\n\nfolly::coro::Task<void> makeRequestReadResponse(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    HTTPSourceHolder reqSource,\n    HTTPSourceReader reader,\n    std::chrono::milliseconds timeout,\n    Logger::SampledLoggerPtr logFn = HTTPClient::getDefaultLogImpl()) {\n  Logger logger(session->acquireKeepAlive(), logFn);\n\n  auto respSource = co_await co_nothrow(session->sendRequest(\n      logger.getRequestFilter(std::move(reqSource)), std::move(reservation)));\n\n  if (timeout.count() > 0) {\n    respSource.setReadTimeout(timeout);\n  }\n  auto maybe = co_await co_awaitTry(\n      reader.setSource(logger.getResponseFilter(std::move(respSource))).read());\n  // Logger goes out of scope before reader, clear the reference\n  reader.setSource(nullptr);\n  // Wait for the log to happen even when cancelled\n  co_await folly::coro::co_withCancellation(folly::CancellationToken(),\n                                            Logger::logWhenDone(logger));\n  if (maybe.hasException()) {\n    co_yield co_error(std::move(maybe.exception()));\n  }\n  // session should be deleted automatically\n  co_return;\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nstd::vector<std::string>& HTTPClient::defaultCAPaths() {\n  static std::vector<std::string> defaultCAPaths{\"/etc/pki/tls/cert.pem\"};\n  return defaultCAPaths;\n}\n\nstd::shared_ptr<fizz::CertificateVerifier>& HTTPClient::defaultCertVerifier() {\n  static std::shared_ptr<fizz::CertificateVerifier> sDefaultCertVerifier;\n  return sDefaultCertVerifier;\n}\n\nLogger::SampledLoggerPtr& HTTPClient::defaultLogImpl() {\n  static Logger::SampledLoggerPtr logImpl =\n      std::make_shared<Logger::SampledLogger>();\n  return logImpl;\n}\n\nHTTPCoroConnector::ConnectionParams HTTPClient::getConnParams(\n    SecureTransportImpl secureTransportImpl,\n    folly::StringPiece sni,\n    folly::StringPiece clientCertPath,\n    folly::StringPiece clientKeyPath,\n    std::list<std::string> nextProtocols) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  if (secureTransportImpl != SecureTransportImpl::NONE &&\n      nextProtocols.empty()) {\n    nextProtocols = kDefaultNextProtocols;\n  }\n  auto tlsParams =\n      makeTLSParams(clientCertPath, clientKeyPath, std::move(nextProtocols));\n  getConnParamsImpl(&connParams, nullptr, secureTransportImpl, sni, tlsParams);\n  return connParams;\n}\n\nHTTPCoroConnector::QuicConnectionParams HTTPClient::getQuicConnParams(\n    folly::StringPiece sni,\n    folly::StringPiece clientCertPath,\n    folly::StringPiece clientKeyPath,\n    std::list<std::string> nextProtocols) {\n  if (nextProtocols.empty()) {\n    nextProtocols = kDefaultQUICNextProtocols;\n  }\n\n  HTTPCoroConnector::QuicConnectionParams connParams;\n  auto tlsParams = makeTLSParams(clientCertPath, clientKeyPath, nextProtocols);\n  getConnParamsImpl(\n      nullptr, &connParams, SecureTransportImpl::FIZZ, sni, tlsParams);\n  return connParams;\n}\n\nHTTPCoroConnector::SessionParams HTTPClient::getSessionParams(\n    std::optional<std::chrono::milliseconds> readTimeout) {\n  HTTPCoroConnector::SessionParams sessParams =\n      HTTPCoroConnector::defaultSessionParams();\n  // NOLINTNEXTLINE(modernize-use-emplace)\n  sessParams.settings.push_back(\n      {SettingsId::INITIAL_WINDOW_SIZE, kDefaultStreamWindow});\n  sessParams.connFlowControl = kDefaultSessionWindow;\n  sessParams.connReadTimeout = readTimeout;\n  sessParams.streamReadTimeout = readTimeout;\n  return sessParams;\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPClient::getHTTPSession(\n    folly::EventBase* evb,\n    std::string host,\n    uint16_t port,\n    bool isSecure,\n    bool useQuic,\n    std::chrono::milliseconds connectTimeout,\n    std::chrono::milliseconds readTimeout,\n    std::string clientCertPath,\n    std::string clientKeyPath,\n    folly::Optional<std::string> serverAddress) {\n  folly::SocketAddress serverAddr;\n  folly::Optional<folly::SocketAddress> fallbackAddress;\n\n  // Resolve hostname asynchronously, if necessary\n  if (serverAddress.has_value()) {\n    serverAddr.setFromIpPort(serverAddress.value(), port);\n  } else {\n    auto serverAddresses = co_await co_nothrow(\n        CoroDNSResolver::resolveHost(evb, host, connectTimeout));\n    serverAddr = std::move(serverAddresses.primary);\n    fallbackAddress = std::move(serverAddresses.fallback);\n    serverAddr.setPort(port);\n  }\n\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::QuicConnectionParams qconnParams;\n  auto nextProtocols =\n      useQuic ? kDefaultQUICNextProtocols : kDefaultNextProtocols;\n  auto tlsParams =\n      makeTLSParams(clientCertPath, clientKeyPath, std::move(nextProtocols));\n  getConnParamsImpl(&connParams,\n                    useQuic ? &qconnParams : nullptr,\n                    // always use fizz for internal\n                    ((isSecure || useQuic) ? SecureTransportImpl::FIZZ\n                                           : SecureTransportImpl::NONE),\n                    host,\n                    tlsParams);\n  auto sessParams = getSessionParams(readTimeout);\n  if (useQuic) {\n    co_return co_await co_nothrow(HTTPCoroConnector::connect(\n        evb, serverAddr, connectTimeout, qconnParams, sessParams));\n  }\n  if (fallbackAddress.has_value()) {\n    fallbackAddress.value().setPort(port);\n    co_return co_await co_nothrow(HTTPCoroConnector::happyEyeballsConnect(\n        evb,\n        std::move(serverAddr),\n        std::move(fallbackAddress.value()),\n        connectTimeout,\n        connParams,\n        sessParams));\n  }\n  co_return co_await co_nothrow(HTTPCoroConnector::connect(\n      evb, serverAddr, connectTimeout, connParams, sessParams));\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPClient::getHTTPSessionViaProxy(\n    HTTPCoroSession* proxySession,\n    std::string host,\n    uint16_t port,\n    bool connectUnique,\n    SecureTransportImpl secureTransportImpl,\n    std::chrono::milliseconds connectTimeout,\n    std::chrono::milliseconds readTimeout,\n    std::string clientCertPath,\n    std::string clientKeyPath) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  auto tlsParams =\n      makeTLSParams(clientCertPath, clientKeyPath, kDefaultNextProtocols);\n  getConnParamsImpl(&connParams, nullptr, secureTransportImpl, host, tlsParams);\n  auto reservation = proxySession->reserveRequest();\n  if (reservation.hasException()) {\n    co_yield co_error(std::move(reservation.exception()));\n  }\n  auto res = co_await co_nothrow(\n      HTTPCoroConnector::proxyConnect(proxySession,\n                                      std::move(*reservation),\n                                      folly::to<std::string>(host, \":\", port),\n                                      connectUnique,\n                                      connectTimeout,\n                                      connParams,\n                                      getSessionParams(readTimeout)));\n  co_return res;\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::get(\n    folly::EventBase* evb,\n    std::string urlStr,\n    std::chrono::milliseconds timeout,\n    bool useQuic,\n    RequestHeaderMap requestHeaders) {\n  Response resp;\n  co_await co_nothrow(get(evb,\n                          std::move(urlStr),\n                          timeout,\n                          makeDefaultReader(resp),\n                          useQuic,\n                          std::move(requestHeaders)));\n  co_return resp;\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::get(\n    HTTPCoroSession* session,\n    URL url,\n    std::chrono::milliseconds timeout,\n    RequestHeaderMap requestHeaders) {\n  Response resp;\n  co_await co_nothrow(get(session,\n                          std::move(url),\n                          makeDefaultReader(resp),\n                          timeout,\n                          std::move(requestHeaders)));\n  co_return resp;\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::get(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    URL url,\n    std::chrono::milliseconds timeout,\n    RequestHeaderMap requestHeaders) {\n  Response resp;\n  co_await co_nothrow(get(session,\n                          std::move(reservation),\n                          std::move(url),\n                          makeDefaultReader(resp),\n                          timeout,\n                          std::move(requestHeaders)));\n  co_return resp;\n}\n\nfolly::coro::Task<void> HTTPClient::get(HTTPCoroSession* session,\n                                        URL url,\n                                        HTTPSourceReader reader,\n                                        std::chrono::milliseconds timeout,\n                                        RequestHeaderMap requestHeaders) {\n  if (!url.isValid() || !url.hasHost()) {\n    co_yield co_error(std::runtime_error(\n        folly::to<std::string>(\"Invalid url: \", url.getUrl())));\n  }\n  auto reservation = session->reserveRequest();\n  if (reservation.hasException()) {\n    co_yield co_error(std::move(reservation.exception()));\n  }\n  co_await co_nothrow(makeRequestReadResponse(\n      session,\n      std::move(*reservation),\n      makeHTTPRequestSource(url, std::move(requestHeaders)),\n      std::move(reader),\n      timeout));\n}\n\nfolly::coro::Task<void> HTTPClient::get(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    URL url,\n    HTTPSourceReader reader,\n    std::chrono::milliseconds timeout,\n    RequestHeaderMap requestHeaders) {\n  if (!url.isValid() || !url.hasHost()) {\n    co_yield co_error(std::runtime_error(\n        folly::to<std::string>(\"Invalid url: \", url.getUrl())));\n  }\n  co_await co_nothrow(makeRequestReadResponse(\n      session,\n      std::move(reservation),\n      makeHTTPRequestSource(url, std::move(requestHeaders)),\n      std::move(reader),\n      timeout));\n}\n\nfolly::coro::Task<void> HTTPClient::get(folly::EventBase* evb,\n                                        std::string urlStr,\n                                        std::chrono::milliseconds timeout,\n                                        HTTPSourceReader reader,\n                                        bool useQuic,\n                                        RequestHeaderMap requestHeaders) {\n\n  URL url(urlStr);\n  if (!url.isValid() || !url.hasHost()) {\n    co_yield co_error(std::runtime_error(\n        folly::to<std::string>(\"Invalid url: \", url.getUrl())));\n  }\n  auto session = co_await getHTTPSession(evb,\n                                         url.getHost(),\n                                         url.getPort(),\n                                         url.isSecure(),\n                                         useQuic,\n                                         timeout,\n                                         timeout);\n\n  auto sessionHolder = session->acquireKeepAlive();\n  SCOPE_EXIT {\n    sessionHolder->initiateDrain();\n  };\n  co_await co_nothrow(\n      get(session, url, std::move(reader), timeout, std::move(requestHeaders)));\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::post(\n    folly::EventBase* evb,\n    std::string urlStr,\n    std::string body,\n    std::chrono::milliseconds timeout,\n    bool useQuic,\n    RequestHeaderMap requestHeaders) {\n  Response resp;\n  co_await co_nothrow(post(evb,\n                           std::move(urlStr),\n                           std::move(body),\n                           timeout,\n                           makeDefaultReader(resp),\n                           useQuic,\n                           std::move(requestHeaders)));\n  co_return resp;\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::post(\n    HTTPCoroSession* session,\n    URL url,\n    std::string body,\n    std::chrono::milliseconds timeout,\n    RequestHeaderMap requestHeaders) {\n  Response resp;\n  co_await co_nothrow(post(session,\n                           std::move(url),\n                           std::move(body),\n                           makeDefaultReader(resp),\n                           timeout,\n                           std::move(requestHeaders)));\n  co_return resp;\n}\n\nfolly::coro::Task<void> HTTPClient::post(folly::EventBase* evb,\n                                         std::string urlStr,\n                                         std::string body,\n                                         std::chrono::milliseconds timeout,\n                                         HTTPSourceReader reader,\n                                         bool useQuic,\n                                         RequestHeaderMap requestHeaders) {\n  URL url(urlStr);\n  if (!url.isValid() || !url.hasHost()) {\n    co_yield co_error(\n        std::runtime_error(folly::to<std::string>(\"Invalid url: \", urlStr)));\n  }\n\n  auto session = co_await co_nothrow(getHTTPSession(evb,\n                                                    url.getHost(),\n                                                    url.getPort(),\n                                                    url.isSecure(),\n                                                    useQuic,\n                                                    timeout,\n                                                    timeout));\n\n  auto sessionHolder = session->acquireKeepAlive();\n  SCOPE_EXIT {\n    sessionHolder->initiateDrain();\n  };\n  auto reservation = session->reserveRequest();\n  if (reservation.hasException()) {\n    co_yield co_error(std::move(reservation.exception()));\n  }\n  co_await co_nothrow(makeRequestReadResponse(\n      session,\n      std::move(*reservation),\n      makeHTTPRequestSource(url, std::move(requestHeaders), std::move(body)),\n      std::move(reader),\n      timeout));\n}\n\nfolly::coro::Task<void> HTTPClient::post(HTTPCoroSession* session,\n                                         URL url,\n                                         std::string body,\n                                         HTTPSourceReader reader,\n                                         std::chrono::milliseconds timeout,\n                                         RequestHeaderMap requestHeaders) {\n  if (!url.isValid() || !url.hasHost()) {\n    co_yield co_error(std::runtime_error(\n        folly::to<std::string>(\"Invalid url: \", url.getUrl())));\n  }\n  auto reservation = session->reserveRequest();\n  if (reservation.hasException()) {\n    co_yield co_error(std::move(reservation.exception()));\n  }\n  co_await co_nothrow(makeRequestReadResponse(\n      session,\n      std::move(*reservation),\n      makeHTTPRequestSource(url, std::move(requestHeaders), std::move(body)),\n      std::move(reader),\n      timeout));\n}\n\nfolly::coro::Task<HTTPClient::Response> HTTPClient::readResponse(\n    HTTPSourceHolder responseSource) {\n  Response resp;\n  HTTPSourceReader reader = makeDefaultReader(resp);\n  reader.setSource(std::move(responseSource));\n  co_await reader.read();\n  co_return resp;\n}\n\nfolly::coro::Task<void> HTTPClient::request(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    HTTPSourceHolder reqSource,\n    HTTPSourceReader reader,\n    std::chrono::milliseconds timeout,\n    Logger::SampledLoggerPtr logger) {\n  return makeRequestReadResponse(session,\n                                 std::move(reservation),\n                                 std::move(reqSource),\n                                 std::move(reader),\n                                 timeout,\n                                 std::move(logger));\n}\n\nfolly::coro::Task<void> HTTPClient::request(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    HTTPMethod method,\n    const URL& url,\n    RequestHeaderMap requestHeaders,\n    HTTPSourceReader reader,\n    std::optional<std::string> body,\n    std::chrono::milliseconds timeout,\n    Logger::SampledLoggerPtr logger) {\n  return makeRequestReadResponse(\n      session,\n      std::move(reservation),\n      makeHTTPRequestSource(\n          url, method, std::move(requestHeaders), std::move(body)),\n      std::move(reader),\n      timeout,\n      std::move(logger));\n}\n\nHTTPSourceReader HTTPClient::makeDefaultReader(HTTPClient::Response& response) {\n  HTTPSourceReader reader;\n  reader\n      .onHeaders([&response](std::unique_ptr<HTTPMessage> headers,\n                             bool /*isFinal*/,\n                             bool /*eom*/) {\n        response.headers = std::move(headers);\n        response.headers->dumpMessage(0);\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&response](BufQueue body, bool) {\n        response.body.append(body.move());\n        return HTTPSourceReader::Continue;\n      })\n      .onTrailers([&response](std::unique_ptr<HTTPHeaders> trailers) {\n        response.trailers = std::move(trailers);\n      })\n      .onError([](HTTPSourceReader::ErrorContext ec, const HTTPError& error) {\n        HTTPError e(error);\n        std::string_view ctx = (ec == HTTPSourceReader::ErrorContext::HEADERS)\n                                   ? \"headers\"\n                                   : \"body\";\n        e.msg = folly::to<std::string>(\n            \"Error receiving response \", ctx, \", err=\", error.msg);\n        throw e;\n      });\n  // ignore all other events\n  return reader;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPClient.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include \"proxygen/lib/http/coro/filters/Logger.h\"\n#include <folly/coro/Task.h>\n#include <folly/io/IOBufQueue.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/utils/URL.h>\n\n#include <string>\nnamespace fizz {\nclass CertificateVerifier;\n}\n\nnamespace proxygen::coro {\n\n/**\n * Extremely simple coroutine enabled HTTP client APIs.  These APIs take an\n * absolute URL, and return a struct containing the final headers and the body\n * of the response, or yield an exception.\n *\n * This is a work in progress.  Prefer extending or modifying these APIs as\n * needed, rather than copying and forking this code.\n *\n * Steps:\n *   1.  Parse the URL for host/port\n *   2.  Perform a DNS resolution, if needed\n *   3.  Establish TCP or TLS connection, as required\n *   4.  Send HTTP request\n *   5.  Read HTTP response, coalescing the body into a string\n *\n * If the response is non-200, an exception is thrown.  If any errors occur\n * before receiving the end of the response, and exception is thrown and the\n * partial response is not available.\n */\nclass HTTPClient {\n public:\n  struct Response {\n    std::unique_ptr<HTTPMessage> headers;\n    folly::IOBufQueue body{folly::IOBufQueue::cacheChainLength()};\n    std::unique_ptr<HTTPHeaders> trailers;\n  };\n\n  using RequestHeaderMap = std::map<std::string, std::string>;\n\n  // Reads the response into a Response object\n  static folly::coro::Task<Response> get(\n      folly::EventBase* evb,\n      std::string url,\n      std::chrono::milliseconds timeout,\n      bool useQuic = false,\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session.  timeout=0 defaults to the\n  // session timeout\n  static folly::coro::Task<Response> get(\n      HTTPCoroSession* session,\n      URL url,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session and reservation.\n  // timeout=0 defaults to the session timeout\n  static folly::coro::Task<Response> get(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      URL url,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Reads the response using a custom HTTPSourceReader\n  static folly::coro::Task<void> get(\n      folly::EventBase* evb,\n      std::string url,\n      std::chrono::milliseconds timeout,\n      HTTPSourceReader reader,\n      bool useQuic = false,\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session.  timeout=0 defaults to the\n  // session timeout\n  static folly::coro::Task<void> get(\n      HTTPCoroSession* session,\n      URL url,\n      HTTPSourceReader reader,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session and reservation.\n  // timeout=0 defaults to the session timeout\n  static folly::coro::Task<void> get(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      URL url,\n      HTTPSourceReader reader,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Sends a post and reads response into a Response object\n  static folly::coro::Task<Response> post(\n      folly::EventBase* evb,\n      std::string url,\n      std::string body,\n      std::chrono::milliseconds timeout,\n      bool useQuic = false,\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session.  timeout=0 defaults to the\n  // session timeout\n  static folly::coro::Task<Response> post(\n      HTTPCoroSession* session,\n      URL url,\n      std::string body,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Sends a post, and reads response using a custom HTTPSourceReader\n  static folly::coro::Task<void> post(\n      folly::EventBase* evb,\n      std::string url,\n      std::string body,\n      std::chrono::milliseconds timeout,\n      HTTPSourceReader reader,\n      bool useQuic = false,\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  // Same as above, but takes an existing session.  timeout=0 defaults to the\n  // session timeout\n  static folly::coro::Task<void> post(\n      HTTPCoroSession* session,\n      URL url,\n      std::string body,\n      HTTPSourceReader reader,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      RequestHeaderMap requestHeaders = RequestHeaderMap());\n\n  static folly::coro::Task<void> request(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      HTTPSourceHolder reqSource,\n      HTTPSourceReader reader,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      Logger::SampledLoggerPtr logger = nullptr);\n\n  static folly::coro::Task<void> request(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      HTTPMethod method,\n      const URL& url,\n      RequestHeaderMap requestHeaders,\n      HTTPSourceReader reader,\n      std::optional<std::string> body = std::nullopt,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      Logger::SampledLoggerPtr logger = nullptr);\n\n  // Reads the response from HTTPSourceHolder\n  static folly::coro::Task<Response> readResponse(\n      HTTPSourceHolder responseSource);\n\n  static HTTPSourceReader makeDefaultReader(HTTPClient::Response& response);\n\n  static void setDefaultCAPaths(std::vector<std::string> caPaths) {\n    defaultCAPaths() = std::move(caPaths);\n  }\n\n  static const std::vector<std::string>& getDefaultCAPaths() {\n    return defaultCAPaths();\n  }\n\n  static void setDefaultFizzCertVerifier(\n      std::shared_ptr<fizz::CertificateVerifier> v) {\n    defaultCertVerifier() = v;\n  }\n\n  static std::shared_ptr<fizz::CertificateVerifier> getDefaultCertVerifier() {\n    return defaultCertVerifier();\n  }\n\n  static void setDefaultLogImpl(Logger::SampledLoggerPtr logImpl) {\n    defaultLogImpl() = std::move(logImpl);\n  }\n\n  static Logger::SampledLoggerPtr& getDefaultLogImpl() {\n    return defaultLogImpl();\n  }\n\n  /**\n   * Get client connection parameters.  Can be used to help set up an\n   * HTTPCoroSessionPool.\n   */\n  enum class SecureTransportImpl { NONE, TLS, FIZZ };\n  static HTTPCoroConnector::ConnectionParams getConnParams(\n      SecureTransportImpl secureTransportImpl,\n      folly::StringPiece sni = folly::StringPiece(),\n      folly::StringPiece clientCertPath = folly::StringPiece(),\n      folly::StringPiece clientKeyPath = folly::StringPiece(),\n      std::list<std::string> nextProtocols = {});\n\n  static HTTPCoroConnector::QuicConnectionParams getQuicConnParams(\n      folly::StringPiece sni = folly::StringPiece(),\n      folly::StringPiece clientCertPath = folly::StringPiece(),\n      folly::StringPiece clientKeyPath = folly::StringPiece(),\n      std::list<std::string> nextProtocols = {});\n\n  /**\n   * Get a SessionParams with sensible defaults. This creates params with the\n   * same defaults as `getHTTPSession`, so it can be used with\n   * `HTTPCoroSession` pool to create sessions that have the same behavior as\n   * those acquired from `getHTTPSession`.\n   *\n   * `readTimeout` is used for both the connection and stream read timeout\n   * (`connReadTimeout` and `streamReadTimeout`).\n   */\n  static HTTPCoroConnector::SessionParams getSessionParams(\n      std::optional<std::chrono::milliseconds> readTimeout = std::nullopt);\n\n  /*\n   * Get an HTTPCoroSession.  This can be useful if you want to send multiple\n   * sequential requests from a single session.  For full pooling support see\n   * HTTPCoroSessionPool.\n   *\n   * `readTimeout` is used for both the connection and stream read timeout\n   * (`connReadTimeout` and `streamReadTimeout`).\n   */\n  static folly::coro::Task<HTTPCoroSession*> getHTTPSession(\n      folly::EventBase* evb,\n      std::string host,\n      uint16_t port,\n      bool isSecure, // todo, combine with useQuic\n      bool useQuic,\n      std::chrono::milliseconds connectTimeout,\n      std::chrono::milliseconds readTimeout,\n      std::string clientCertPath = \"\",\n      std::string clientKeyPath = \"\",\n      folly::Optional<std::string> serverAddress = folly::none);\n\n  /**\n   * Returns an HTTPCoroSession that is using an HTTP CONNECT stream on\n   * proxySession as its transport.\n   * The parameters refer to the destination (host, port,\n   *  secureTransportImpl, etc).\n   * connectUnique is true if this session should take ownership of the\n   * proxySession.  If false, proxySession can be used for other requests\n   * to to the proxy.\n   *\n   * `readTimeout` is used for both the connection and stream read timeout\n   * (`connReadTimeout` and `streamReadTimeout`).\n   */\n  static folly::coro::Task<HTTPCoroSession*> getHTTPSessionViaProxy(\n      HTTPCoroSession* proxySession,\n      std::string host,\n      uint16_t port,\n      bool connectUnique,\n      SecureTransportImpl secureTransportImpl,\n      std::chrono::milliseconds connectTimeout,\n      std::chrono::milliseconds readTimeout,\n      std::string clientCertPath = \"\",\n      std::string clientKeyPath = \"\");\n\n private:\n  static std::vector<std::string>& defaultCAPaths();\n\n  static std::shared_ptr<fizz::CertificateVerifier>& defaultCertVerifier();\n\n  static Logger::SampledLoggerPtr& defaultLogImpl();\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPClientConnectionCache.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/HTTPClientConnectionCache.h\"\n#include \"proxygen/lib/http/coro/client/CoroDNSResolver.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include <folly/logging/xlog.h>\n\nusing ConnectionParams = proxygen::coro::HTTPCoroConnector::ConnectionParams;\nusing SessionParams = proxygen::coro::HTTPCoroConnector::SessionParams;\n\nnamespace {\n\nconstexpr uint32_t kMaxConnections = 100;\nconstexpr std::chrono::seconds kConnectTimeout(3);\nconstexpr uint32_t kConnectAttempts = 1;\nconstexpr std::chrono::seconds kMaxAge(0);\nconstexpr size_t kMaxPoolsToTraverseDuringReap = 5;\nconstexpr bool kEnableSslSessionCaching = true;\n\nstd::string makePoolKey(folly::StringPiece address,\n                        uint16_t port,\n                        bool isSecure,\n                        const ConnectionParams *connParams) {\n  auto id =\n      folly::to<std::string>(\"http\", (isSecure ? \"s\" : \"\"), address, \":\", port);\n  if (!connParams) {\n    return id;\n  }\n\n  // connParams != nullptr indicates we should hash combine member fields to\n  // create a unique identified for this session pool\n  size_t hash = folly::hash::hash_combine(\n      connParams->sslContext.get(),\n      connParams->fizzContextAndVerifier.fizzContext.get(),\n      connParams->fizzContextAndVerifier.fizzCertVerifier.get());\n  folly::ByteRange br(reinterpret_cast<const uint8_t *>(&hash), sizeof(size_t));\n  return folly::to<std::string>(folly::hexlify(br), \"/\", id);\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nHTTPClientConnectionCache::~HTTPClientConnectionCache() {\n  eventBase_.runImmediatelyOrRunInEventBaseThreadAndWait([this]() { drain(); });\n}\n\nvoid HTTPClientConnectionCache::drain() {\n  eventBase_.checkIsInEventBaseThread();\n  cancellationSource_.requestCancellation();\n  proxyPool_.reset();\n  pools_.clear();\n}\n\nfolly::coro::Task<HTTPSessionFactory::GetSessionResult>\nHTTPClientConnectionCache::getSessionWithReservation(\n    std::string urlStr,\n    std::chrono::milliseconds connectTimeout,\n    const HTTPCoroConnector::ConnectionParams *connParams) {\n  URL url(urlStr);\n  if (!url.isValid() || !url.hasHost()) {\n    return folly::coro::makeErrorTask<HTTPCoroSessionPool::GetSessionResult>(\n        std::runtime_error(\"invalid url\"));\n  }\n\n  return getSessionWithReservation(url.getHost(),\n                                   url.getPort(),\n                                   url.getScheme() == \"https\",\n                                   connectTimeout,\n                                   connParams,\n                                   folly::none);\n}\n\nfolly::coro::Task<HTTPSessionFactory::GetSessionResult>\nHTTPClientConnectionCache::getSessionWithReservation(\n    std::string host,\n    uint16_t port,\n    bool isSecure,\n    std::chrono::milliseconds connectTimeout,\n    folly::Optional<std::string> serverAddress) {\n  return getSessionWithReservation(std::move(host),\n                                   port,\n                                   isSecure,\n                                   connectTimeout,\n                                   nullptr,\n                                   std::move(serverAddress));\n}\n\nfolly::coro::Task<HTTPSessionFactory::GetSessionResult>\nHTTPClientConnectionCache::getSessionWithReservation(\n    std::string host,\n    uint16_t port,\n    bool isSecure,\n    std::chrono::milliseconds connectTimeout,\n    const HTTPCoroConnector::ConnectionParams *connParams,\n    folly::Optional<std::string> serverAddress) {\n  const auto &reqCancelToken =\n      co_await folly::coro::co_current_cancellation_token;\n  auto drainCancelToken = cancellationSource_.getToken();\n\n  std::string addressStr;\n  if (proxyPool_) {\n    addressStr = host;\n  } else if (serverAddress.has_value()) {\n    addressStr = serverAddress.value();\n  } else {\n    auto serverAddresses = co_await CoroDNSResolver::resolveHost(\n        &eventBase_, host, connectTimeout);\n    // TODO: support happy eyeballs\n    addressStr = serverAddresses.primary.getAddressStr();\n  }\n\n  if (reqCancelToken.isCancellationRequested()) {\n    co_yield folly::coro::co_error(\n        HTTPError(HTTPErrorCode::CORO_CANCELLED, \"Cancelled\"));\n  }\n  if (drainCancelToken.isCancellationRequested()) {\n    co_yield folly::coro::co_error(HTTPCoroSessionPool::Exception(\n        HTTPCoroSessionPool::Exception::Type::Draining, \"Pool is draining\"));\n  }\n\n  // Reclaim some memory by reaping empty pools\n  reapEmptyPools(kMaxPoolsToTraverseDuringReap);\n\n  // get or create a session pool to this destination address\n  auto &pool = getPool(host, addressStr, port, isSecure, connParams);\n\n  /**\n   * Obtain a session from the pool, propagate errors\n   *\n   * HTTPCoroSessionPool::getSessionWithReservation() coroutine does not need to\n   * directly depend on drainCancelToken (cancelled via ::drain), since all\n   * pool are destroyed anyways in ::drain and the HTTPCoroSessionPool\n   * destructor cancels the pool's cancellation source. This allows\n   * HTTPCoroSessionPool to differentiate between drain and cancel exceptions.\n   */\n  co_return co_await pool.getSessionWithReservation();\n}\n\nvoid HTTPClientConnectionCache::reapEmptyPools(size_t maxPoolsToTraverse) {\n  for (auto it = pools_.cend(); maxPoolsToTraverse && it != pools_.cbegin();\n       --maxPoolsToTraverse) {\n    --it;\n    if (it->second->empty()) {\n      it = pools_.erase(it);\n    }\n  }\n}\n\nproxygen::coro::HTTPCoroSessionPool &HTTPClientConnectionCache::getPool(\n    folly::StringPiece host,\n    folly::StringPiece address,\n    uint16_t port,\n    bool isSecure,\n    const HTTPCoroConnector::ConnectionParams *maybeConnParams) {\n  if (usingProxy() && !useConnectForProxy_) {\n    XLOG(DBG4) << \"Not using CONNECT\";\n    // The caller will make a GET request via the proxy.\n    return *proxyPool_;\n  }\n  auto key = makePoolKey(address, port, isSecure, maybeConnParams);\n  auto it = pools_.find(key);\n  auto sni = folly::IPAddress::validate(host) ? \"\" : host;\n  if (it == pools_.end()) {\n    XLOG(DBG4) << \"Making a new pool for key: \" << key;\n    HTTPCoroConnector::ConnectionParams connParams;\n    if (maybeConnParams || connParams_) {\n      connParams = maybeConnParams ? *maybeConnParams : *connParams_;\n      if (isSecure) {\n        connParams.serverName = std::string(sni);\n      } else {\n        connParams.sslContext = nullptr;\n        connParams.fizzContextAndVerifier.fizzContext = nullptr;\n      }\n    } else {\n      // Use OpenSSL when connecting via proxy, target may not support TLS 1.3\n      connParams = HTTPClient::getConnParams(\n          isSecure ? (usingProxy() ? HTTPClient::SecureTransportImpl::TLS\n                                   : HTTPClient::SecureTransportImpl::FIZZ)\n                   : HTTPClient::SecureTransportImpl::NONE,\n          sni);\n      if (isSecure && connParams.fizzContextAndVerifier.fizzContext &&\n          !connParams.fizzContextAndVerifier.fizzContext->getPskCache()) {\n        auto newFizzCtx = std::make_shared<fizz::client::FizzClientContext>(\n            *connParams.fizzContextAndVerifier.fizzContext);\n        newFizzCtx->setPskCache(pskCache_);\n        connParams.fizzContextAndVerifier.fizzContext = std::move(newFizzCtx);\n      }\n    }\n    auto poolParams =\n        poolParams_.value_or(proxygen::coro::HTTPCoroSessionPool::PoolParams{\n            kMaxConnections,\n            kConnectAttempts,\n            kEnableSslSessionCaching,\n            kConnectTimeout,\n            kMaxAge});\n    auto sessParams = sessionParams_.value_or(HTTPClient::getSessionParams());\n    std::unique_ptr<HTTPCoroSessionPool> pool;\n    if (proxyPool_) {\n      pool = std::make_unique<HTTPCoroSessionPool>(&eventBase_,\n                                                   address.str(),\n                                                   port,\n                                                   proxyPool_,\n                                                   poolParams,\n                                                   std::move(connParams),\n                                                   std::move(sessParams));\n    } else {\n      pool = std::make_unique<HTTPCoroSessionPool>(&eventBase_,\n                                                   address.str(),\n                                                   port,\n                                                   poolParams,\n                                                   std::move(connParams),\n                                                   std::move(sessParams));\n    }\n    auto res = pools_.insert(key, std::move(pool));\n    it = res.first;\n  }\n  return *it->second;\n}\n\nbool HTTPClientConnectionCache::poolExists(folly::StringPiece address,\n                                           uint16_t port,\n                                           bool isSecure,\n                                           const ConnectionParams *connParams) {\n  auto key = makePoolKey(address, port, isSecure, connParams);\n  return pools_.exists(key);\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPClientConnectionCache.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include <fizz/client/SynchronizedLruPskCache.h>\n#include <folly/container/EvictingCacheMap.h>\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nconstexpr uint32_t kDefaultMaxConnectionPools = 10;\nconstexpr uint32_t kPskCacheSize = 10000;\n\n/**\n * This class holds an evicting cache map of session pools.  It can be used\n * by a client that will be used to make connections to a variety of\n * destinations over time.\n */\nclass HTTPClientConnectionCache : public HTTPSessionFactory {\n public:\n  struct ProxyParams {\n    std::string server;\n    uint16_t port;\n    bool useConnect{false};\n    HTTPCoroSessionPool::PoolParams poolParams;\n    HTTPCoroConnector::ConnectionParams connParams;\n    HTTPCoroConnector::SessionParams sessionParams;\n  };\n\n  explicit HTTPClientConnectionCache(\n      folly::EventBase& evb,\n      folly::Optional<ProxyParams> proxyParams = folly::none,\n      const uint32_t maxConnectionPools = kDefaultMaxConnectionPools)\n      : eventBase_(evb),\n        proxyPool_(proxyParams ? std::make_shared<HTTPCoroSessionPool>(\n                                     &eventBase_,\n                                     proxyParams->server,\n                                     proxyParams->port,\n                                     proxyParams->poolParams,\n                                     getProxyConnParams(\n                                         std::move(proxyParams->connParams)),\n                                     proxyParams->sessionParams,\n                                     /*allowNameLookup=*/true)\n                               : nullptr),\n        useConnectForProxy_(proxyParams && proxyParams->useConnect),\n        pools_(maxConnectionPools) {\n  }\n  ~HTTPClientConnectionCache() override;\n\n  [[nodiscard]] folly::EventBase* getEventBase() const {\n    return &eventBase_;\n  }\n\n  [[nodiscard]] bool requiresAbsoluteURLs() const override {\n    return usingProxy() && !useConnectForProxy_;\n  }\n\n  void setPoolParams(HTTPCoroSessionPool::PoolParams poolParams) {\n    poolParams_ = poolParams;\n  }\n\n  void setConnParams(HTTPCoroConnector::ConnectionParams connParams) {\n    XCHECK(connParams.sslContext ||\n           connParams.fizzContextAndVerifier.fizzContext)\n        << \"Must set secure ConnectionParams\";\n    XCHECK(!usingProxy() || useConnectForProxy_)\n        << \"Cannot set connParams with non CONNECT proxy\";\n    connParams_ = std::move(connParams);\n  }\n\n  void setSessionParams(HTTPCoroConnector::SessionParams sessParams) {\n    sessionParams_ = std::move(sessParams);\n  }\n\n  folly::coro::Task<GetSessionResult> getSessionWithReservation(\n      std::string url,\n      std::chrono::milliseconds timeout,\n      const HTTPCoroConnector::ConnectionParams* connParams = nullptr);\n\n  folly::coro::Task<GetSessionResult> getSessionWithReservation(\n      std::string host,\n      uint16_t port,\n      bool isSecure,\n      std::chrono::milliseconds timeout,\n      folly::Optional<std::string> serverAddress = folly::none) override;\n\n  folly::coro::Task<GetSessionResult> getSessionWithReservation(\n      std::string host,\n      uint16_t port,\n      bool isSecure,\n      std::chrono::milliseconds timeout,\n      const HTTPCoroConnector::ConnectionParams* connParams,\n      folly::Optional<std::string> serverAddress = folly::none);\n\n  [[nodiscard]] size_t getNumPools() const {\n    return pools_.size();\n  }\n\n  [[nodiscard]] size_t getMaxNumPools() const {\n    return pools_.getMaxSize();\n  }\n\n  void drain();\n\n  void reapEmptyPools(size_t maxPoolsToTraverse);\n\n  void reapAllEmptyPools() {\n    reapEmptyPools(pools_.size());\n  }\n\n protected:\n  // Checking if a pool exists does not cause a promotion in the pools LRU\n  bool poolExists(\n      folly::StringPiece address,\n      uint16_t port,\n      bool isSecure,\n      const HTTPCoroConnector::ConnectionParams* connParams = nullptr);\n\n  /**\n   * This function either creates a pool or returns an existing one if present.\n   * Pools are indexed by their (address, port, isSecure) tuple; however if\n   * multiple HTTPCoroSessionPools objects to the same (address, port, isSecure)\n   * tuple are needed, you may pass in a ConnectionParams ptr that extends the\n   * tuple index to become (address, port, isSecure, hash(&sslCtx,\n   * &fizzContextAndVerifier))\n   */\n  HTTPCoroSessionPool& getPool(\n      folly::StringPiece host,\n      folly::StringPiece address,\n      uint16_t port,\n      bool isSecure,\n      const HTTPCoroConnector::ConnectionParams* connParams = nullptr);\n\n private:\n  HTTPCoroConnector::ConnectionParams getProxyConnParams(\n      HTTPCoroConnector::ConnectionParams connParams) {\n    if (connParams.fizzContextAndVerifier.fizzContext &&\n        !connParams.fizzContextAndVerifier.fizzContext->getPskCache()) {\n      auto newFizzCtx = std::make_shared<fizz::client::FizzClientContext>(\n          *connParams.fizzContextAndVerifier.fizzContext);\n      newFizzCtx->setPskCache(pskCache_);\n      connParams.fizzContextAndVerifier.fizzContext = std::move(newFizzCtx);\n    }\n    return connParams;\n  }\n\n  [[nodiscard]] bool usingProxy() const {\n    return bool(proxyPool_);\n  }\n\n  folly::EventBase& eventBase_;\n  std::shared_ptr<fizz::client::SynchronizedLruPskCache> pskCache_{\n      std::make_shared<fizz::client::SynchronizedLruPskCache>(kPskCacheSize)};\n  folly::Optional<HTTPCoroSessionPool::PoolParams> poolParams_;\n  folly::Optional<HTTPCoroConnector::ConnectionParams> connParams_;\n  folly::Optional<HTTPCoroConnector::SessionParams> sessionParams_;\n  std::shared_ptr<HTTPCoroSessionPool> proxyPool_;\n  folly::CancellationSource cancellationSource_;\n  bool useConnectForProxy_{false};\n  folly::EvictingCacheMap<std::string, std::unique_ptr<HTTPCoroSessionPool>>\n      pools_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPCoroConnector.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/http/coro/transport/CoroSSLTransport.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectStream.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectTransport.h\"\n#include \"proxygen/lib/http/coro/util/Transport.h\"\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <fizz/client/AsyncFizzClient.h>\n#include <fizz/protocol/DefaultCertificateVerifier.h>\n#include <folly/FileUtil.h>\n#include <folly/SocketAddress.h>\n#include <folly/coro/Collect.h>\n#include <folly/coro/SharedPromise.h>\n#include <folly/coro/Sleep.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/io/async/SSLOptions.h>\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n#include <quic/QuicException.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/client/QuicClientTransport.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>\n#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>\n#include <wangle/ssl/SSLStats.h>\n#include <wangle/ssl/SSLUtil.h>\n\nusing folly::coro::co_error;\nusing folly::coro::co_nothrow;\nusing CoroTransport = folly::coro::Transport;\nusing CoroTransportIf = folly::coro::TransportIf;\n\nnamespace {\n\nusing namespace fizz;\nusing namespace fizz::client;\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\n// if the sni is an ip addr format or an empty string, we return folly::none\nfolly::Optional<std::string> getValidSni(std::string_view sni) {\n  if (sni.empty() || folly::IPAddress::validate(sni)) {\n    return folly::none;\n  }\n  return std::string(sni);\n}\n\n// default conn & stream fc are ~32MB & ~2MB respectively\nconstexpr size_t kDefaultConnFlowControl = 1u << 25;\nconstexpr size_t kDefaultStreamFlowControl = 1u << 21;\n\nclass ConnectCB\n    : public folly::AsyncSocket::ConnectCallback\n    , public AsyncFizzClient::HandshakeCallback {\n public:\n  explicit ConnectCB(\n      folly::EventBase* evb = nullptr,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0))\n      : baton(evb, timeout) {\n  }\n\n  void connectSuccess() noexcept override {\n    baton.signal();\n  }\n  void connectErr(const folly::AsyncSocketException& ex) noexcept override {\n    exception = ex;\n    baton.signal();\n  }\n\n  void fizzHandshakeSuccess(AsyncFizzClient* /*transport*/) noexcept override {\n    connectSuccess();\n  }\n\n  void fizzHandshakeError(AsyncFizzClient* /*transport*/,\n                          folly::exception_wrapper ex) noexcept override {\n    if (auto* exc = ex.get_exception<folly::AsyncSocketException>()) {\n      exception = std::move(*exc);\n    } else {\n      exception = folly::AsyncSocketException(\n          folly::AsyncSocketException::SSL_ERROR,\n          folly::to<std::string>(\"Fizz handshake error: \", ex.what()));\n    }\n\n    baton.signal();\n  }\n\n  std::optional<folly::AsyncSocketException> exception;\n  TimedBaton baton;\n};\n\nfolly::coro::Task<std::unique_ptr<CoroTransportIf>> connectTCP(\n    folly::EventBase* eventBase,\n    folly::SocketAddress connectAddr,\n    std::chrono::milliseconds timeoutMs,\n    const HTTPCoroConnector::ConnectionParams& connParams) {\n  auto asyncSocket = folly::AsyncSocket::newSocket(eventBase);\n  ConnectCB cb;\n  asyncSocket->connect(&cb,\n                       connectAddr,\n                       timeoutMs.count(),\n                       connParams.socketOptions,\n                       connParams.bindAddr);\n  co_await cb.baton.wait();\n  co_await folly::coro::co_safe_point;\n  if (cb.exception) {\n    co_yield co_error(*cb.exception);\n  }\n  if (connParams.congestionFlavor) {\n    asyncSocket->setCongestionFlavor(*connParams.congestionFlavor);\n  }\n  co_return std::make_unique<proxygen::coro::detail::Transport>(\n      eventBase, std::move(asyncSocket));\n}\n\nvoid initTransportInfoFromSSLSocket(wangle::TransportInfo& tinfo,\n                                    folly::AsyncSSLSocket& sslSocket) {\n  tinfo.secure = true;\n  tinfo.appProtocol =\n      std::make_shared<std::string>(sslSocket.getApplicationProtocol());\n  auto cipher = sslSocket.getNegotiatedCipherName();\n  tinfo.sslCipher = cipher ? std::make_shared<std::string>(cipher) : nullptr;\n  tinfo.sslVersion = sslSocket.getSSLVersion();\n  tinfo.sslResume = wangle::SSLUtil::getResumeState(&sslSocket);\n  tinfo.securityType = \"OpenSSL\";\n  // There are a bunch more TLS related fields that are only set on accept\n}\n\nvoid initTransportInfoFromCoroSSLTransport(wangle::TransportInfo& tinfo,\n                                           CoroSSLTransport& sslTransport) {\n  tinfo.secure = true;\n  tinfo.appProtocol =\n      std::make_shared<std::string>(sslTransport.getApplicationProtocol());\n  auto cipher = sslTransport.getNegotiatedCipherName();\n  tinfo.sslCipher = cipher ? std::make_shared<std::string>(cipher) : nullptr;\n  tinfo.sslVersion = sslTransport.getSSLVersion();\n  // Assuming it's tickets for now.  AsyncSSLSocket only sets session ID on\n  // the server.\n  tinfo.sslResume = sslTransport.getSSLSessionReused()\n                        ? wangle::SSLResumeEnum::RESUME_TICKET\n                        : wangle::SSLResumeEnum::HANDSHAKE;\n\n  // Fizz also sets \"securityType\"\n  // There are a bunch more TLS related fields that are only set on accept\n}\n\nvoid initTransportInfoFromFizz(wangle::TransportInfo& tinfo,\n                               AsyncFizzClient& fizzClient) {\n  tinfo.secure = true;\n  tinfo.appProtocol =\n      std::make_shared<std::string>(fizzClient.getApplicationProtocol());\n  auto negotiatedCipher = fizzClient.getState().cipher();\n  tinfo.sslCipher =\n      negotiatedCipher\n          ? std::make_shared<std::string>(fizz::toString(*negotiatedCipher))\n          : nullptr;\n  auto fizzVersion = fizzClient.getState().version();\n  tinfo.sslVersion = fizzVersion ? static_cast<int>(fizzVersion.value()) : 0;\n  auto pskType =\n      fizzClient.getState().pskType().value_or(fizz::PskType::NotAttempted);\n  tinfo.sslResume = (pskType == fizz::PskType::Resumption)\n                        ? wangle::SSLResumeEnum::RESUME_TICKET\n                        : wangle::SSLResumeEnum::HANDSHAKE;\n  tinfo.securityType = fizzClient.getSecurityProtocol();\n}\n\nvoid initTransportInfoFromQuic(wangle::TransportInfo& tinfo,\n                               quic::QuicClientTransport& quicClient) {\n  tinfo.secure = true;\n  tinfo.appProtocol =\n      std::make_shared<std::string>(quicClient.getAppProtocol().value_or(\"\"));\n  tinfo.securityType = \"Fizz\";\n}\n\nvoid setupCodec(HTTPCodec& codec,\n                const HTTPCoroConnector::SessionParams& sessionParams) {\n  auto settings = codec.getEgressSettings();\n  if (!settings) {\n    return;\n  }\n\n  // default stream fc window to ~2MB, will be overridden below with user\n  // supplied value if present\n  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                       kDefaultStreamFlowControl);\n  for (const auto& setting : sessionParams.settings) {\n    settings->setSetting(setting.id, setting.value);\n  }\n\n  if (sessionParams.headerCodecStats) {\n    codec.setHeaderCodecStats(sessionParams.headerCodecStats);\n  }\n}\n\nvoid setupSession(HTTPCoroSession* session,\n                  const HTTPCoroConnector::SessionParams& sessionParams) {\n  // default conn fc window to ~32MB\n  session->setConnectionFlowControl(\n      sessionParams.connFlowControl.value_or(kDefaultConnFlowControl));\n\n  if (sessionParams.maxConcurrentOutgoingStreams) {\n    session->setMaxConcurrentOutgoingStreams(\n        *sessionParams.maxConcurrentOutgoingStreams);\n  }\n  if (sessionParams.streamReadTimeout) {\n    session->setStreamReadTimeout(*sessionParams.streamReadTimeout);\n  }\n  if (sessionParams.connReadTimeout) {\n    session->setConnectionReadTimeout(*sessionParams.connReadTimeout);\n  }\n  if (sessionParams.writeTimeout) {\n    session->setWriteTimeout(*sessionParams.writeTimeout);\n  }\n  if (sessionParams.lifecycleObserver) {\n    session->addLifecycleObserver(sessionParams.lifecycleObserver);\n  }\n  if (sessionParams.sessionStats) {\n    session->setSessionStats(sessionParams.sessionStats);\n  }\n  session->run().start();\n}\n\nfolly::coro::Task<std::unique_ptr<CoroTransportIf>> connectFizz(\n    folly::EventBase* eventBase,\n    folly::SocketAddress connectAddr,\n    std::unique_ptr<HTTPConnectStream> connectStream,\n    std::chrono::milliseconds timeoutMs,\n    const HTTPCoroConnector::ConnectionParams& connParams,\n    wangle::TransportInfo& tinfo) {\n  ConnectCB cb;\n  AsyncFizzClient::UniquePtr fizzClient;\n  auto pskIdentity = connParams.fizzPskIdentity.value_or(\n      connParams.serverName.empty() ? connectAddr.getAddressStr()\n                                    : connParams.serverName);\n\n  auto sni = getValidSni(connParams.serverName);\n  if (connectStream) {\n    folly::AsyncTransportWrapper::UniquePtr asyncTransport{\n        new HTTPConnectAsyncTransport(std::move(connectStream))};\n    fizzClient.reset(\n        new AsyncFizzClient(std::move(asyncTransport),\n                            connParams.fizzContextAndVerifier.fizzContext));\n    fizzClient->connect(&cb,\n                        connParams.fizzContextAndVerifier.fizzCertVerifier,\n                        std::move(sni),\n                        pskIdentity,\n                        folly::none, /* echConfigs */\n                        timeoutMs /* timeout */);\n  } else {\n    fizzClient.reset(new AsyncFizzClient(\n        eventBase, connParams.fizzContextAndVerifier.fizzContext));\n    fizzClient->connect(connectAddr,\n                        &cb,\n                        connParams.fizzContextAndVerifier.fizzCertVerifier,\n                        std::move(sni),\n                        pskIdentity,\n                        timeoutMs, /* total timeout */\n                        timeoutMs, /* tcpConnectTimeout */\n                        connParams.socketOptions,\n                        connParams.bindAddr);\n  }\n\n  co_await cb.baton.wait();\n  co_await folly::coro::co_safe_point;\n  if (cb.exception) {\n    co_yield co_error(*cb.exception);\n  }\n  initTransportInfoFromFizz(tinfo, *fizzClient);\n  co_return std::make_unique<proxygen::coro::detail::Transport>(\n      eventBase, std::move(fizzClient));\n}\n\ninline std::shared_ptr<folly::ssl::SSLSession> getSslSession(\n    const HTTPCoroConnector::ConnectionParams& connParams) {\n  return connParams.sslSessionManager\n             ? connParams.sslSessionManager->getSslSession()\n             : nullptr;\n}\n\nfolly::coro::Task<std::unique_ptr<CoroTransportIf>> connectTLS(\n    folly::EventBase* eventBase,\n    folly::SocketAddress connectAddr,\n    std::unique_ptr<HTTPConnectStream> connectStream,\n    std::chrono::milliseconds timeoutMs,\n    const HTTPCoroConnector::ConnectionParams& connParams,\n    wangle::TransportInfo& tinfo) {\n\n  auto sslSession = getSslSession(connParams);\n  auto sni = getValidSni(connParams.serverName);\n  if (connectStream) {\n    auto sslTransport = std::make_unique<CoroSSLTransport>(\n        std::make_unique<HTTPConnectTransport>(std::move(connectStream)),\n        connParams.sslContext);\n    if (sslSession) {\n      sslTransport->setSSLSession(std::move(sslSession));\n    }\n\n    co_await sslTransport->connect(std::move(sni), timeoutMs);\n    co_await folly::coro::co_safe_point;\n    initTransportInfoFromCoroSSLTransport(tinfo, *sslTransport);\n    if (!sslTransport->getSSLSessionReused() && connParams.sslSessionManager) {\n      connParams.sslSessionManager->onNewSslSession(\n          sslTransport->getSSLSession());\n    }\n    co_return sslTransport;\n  } else {\n    folly::AsyncSSLSocket::UniquePtr sslSock(\n        new folly::AsyncSSLSocket(connParams.sslContext, eventBase));\n    if (sslSession) {\n\n      sslSock->setSSLSession(std::move(sslSession));\n    }\n    sslSock->setServerName(std::move(sni).value_or(\"\"));\n    sslSock->forceCacheAddrOnFailure(true);\n    ConnectCB cb;\n    sslSock->connect(&cb,\n                     connectAddr,\n                     timeoutMs.count(),\n                     connParams.socketOptions,\n                     connParams.bindAddr);\n    co_await cb.baton.wait();\n    co_await folly::coro::co_safe_point;\n    if (cb.exception) {\n      co_yield co_error(*cb.exception);\n    }\n    initTransportInfoFromSSLSocket(tinfo, *sslSock);\n    if (!sslSock->getSSLSessionReused() && connParams.sslSessionManager) {\n      connParams.sslSessionManager->onNewSslSession(sslSock->getSSLSession());\n    }\n    if (connParams.congestionFlavor) {\n      sslSock->setCongestionFlavor(*connParams.congestionFlavor);\n    }\n    co_return std::make_unique<proxygen::coro::detail::Transport>(\n        eventBase, std::move(sslSock));\n  }\n}\n\nclass QuicConnectCB\n    : public quic::QuicSocket::ConnectionSetupCallback\n    , public ConnectCB {\n public:\n  QuicConnectCB(folly::EventBase* evb,\n                std::chrono::milliseconds timeout,\n                std::shared_ptr<quic::QuicClientTransport> quicClient,\n                const HTTPCoroConnector::SessionParams& sessionParams,\n                folly::CancellationToken cancellationToken)\n      : ConnectCB(evb, timeout),\n        quicClient_(std::move(quicClient)),\n        sessionParams_(sessionParams),\n        cancellationToken_(std::move(cancellationToken)) {\n  }\n\n  folly::exception_wrapper quicException;\n  HTTPCoroSession* session{nullptr};\n\n private:\n  void quicConnectErr(folly::exception_wrapper ex) noexcept {\n    quicException = std::move(ex);\n    baton.signal();\n  }\n  void onConnectionSetupError(quic::QuicError error) noexcept override {\n    switch (error.code.type()) {\n      case quic::QuicErrorCode::Type::ApplicationErrorCode:\n        quicConnectErr(quic::QuicApplicationException(\n            error.message, *error.code.asApplicationErrorCode()));\n        break;\n      case quic::QuicErrorCode::Type::LocalErrorCode:\n        quicConnectErr(quic::QuicInternalException(\n            error.message, *error.code.asLocalErrorCode()));\n        break;\n      case quic::QuicErrorCode::Type::TransportErrorCode:\n        quicConnectErr(quic::QuicTransportException(\n            error.message, *error.code.asTransportErrorCode()));\n        break;\n    }\n  }\n  void onReplaySafe() noexcept override {\n    replaySafe_ = true;\n  }\n  void onTransportReady() noexcept override {\n    if (cancellationToken_.isCancellationRequested()) {\n      quicConnectErr(quic::QuicTransportException(\n          \"Connection has been cancelled\",\n          quic::TransportErrorCode::INTERNAL_ERROR));\n      return;\n    }\n    initTransportInfoFromQuic(tinfo_, *quicClient_);\n\n    auto codec = hq::HQMultiCodec::Factory::getCodec(\n        TransportDirection::UPSTREAM,\n        /*useStrictValidation=*/false,\n        sessionParams_.headerIndexingStrategy);\n    setupCodec(*codec, sessionParams_);\n\n    tinfo_.acceptTime = getCurrentTime();\n    tinfo_.sslSetupTime = millisecondsSince(startTime_);\n    // TODO: pass replaySafe_\n    session = HTTPCoroSession::makeUpstreamCoroSession(\n        std::move(quicClient_), std::move(codec), std::move(tinfo_));\n    setupSession(session, sessionParams_);\n    connectSuccess();\n  }\n  std::shared_ptr<quic::QuicClientTransport> quicClient_;\n  const HTTPCoroConnector::SessionParams& sessionParams_;\n  std::chrono::steady_clock::time_point startTime_{getCurrentTime()};\n  wangle::TransportInfo tinfo_;\n  bool replaySafe_{false};\n  folly::CancellationToken cancellationToken_;\n};\n\nfolly::coro::Task<HTTPCoroSession*> connectQuic(\n    folly::EventBase* eventBase,\n    folly::SocketAddress connectAddr,\n    std::chrono::milliseconds timeoutMs,\n    const HTTPCoroConnector::QuicConnectionParams connParams,\n    const HTTPCoroConnector::SessionParams sessionParams) {\n  auto qEvb = std::make_shared<quic::FollyQuicEventBase>(eventBase);\n  auto sock = std::make_unique<quic::FollyQuicAsyncUDPSocket>(qEvb);\n  auto quicClient = quic::QuicClientTransport::newClient(\n      std::move(qEvb),\n      std::move(sock),\n      quic::FizzClientQuicHandshakeContext::Builder()\n          .setFizzClientContext(connParams.fizzContextAndVerifier.fizzContext)\n          .setCertificateVerifier(\n              connParams.fizzContextAndVerifier.fizzCertVerifier)\n          .setPskCache(connParams.quicPskCache)\n          .build(),\n      /*connectionIdSize=*/0);\n\n  auto hostname = connParams.serverName.empty() ? connectAddr.getAddressStr()\n                                                : connParams.serverName;\n  quicClient->setHostname(std::move(hostname));\n  quicClient->addNewPeerAddress(connectAddr);\n  if (connParams.bindAddr != folly::AsyncSocket::anyAddress()) {\n    quicClient->setLocalAddress(connParams.bindAddr);\n  }\n  if (connParams.ccFactory) {\n    quicClient->setCongestionControllerFactory(connParams.ccFactory);\n  } else {\n    quicClient->setCongestionControllerFactory(\n        std::make_shared<quic::DefaultCongestionControllerFactory>());\n  }\n  if (connParams.qlogSampling.isLucky()) {\n    quicClient->setQLogger(connParams.qLogger);\n  }\n  quicClient->setLoopDetectorCallback(connParams.quicLoopDetectorCallback);\n  quicClient->setTransportStatsCallback(connParams.quicTransportStatsCallback);\n  quicClient->setSocketOptions(connParams.socketOptions);\n\n  // setTransportSettings has to be after the setTransportStatsCallback.\n  // Otherwise, the attempts to log stats while setting the transport\n  // settings are lost. The congestion controller type logged by the congestion\n  // controller factory is one such stat.\n  quicClient->setTransportSettings(connParams.transportSettings);\n\n  folly::CancellationToken cancellationToken =\n      co_await folly::coro::co_current_cancellation_token;\n  QuicConnectCB cb(eventBase,\n                   timeoutMs,\n                   quicClient,\n                   sessionParams,\n                   std::move(cancellationToken));\n  quicClient->start(&cb, nullptr);\n  auto res = co_await cb.baton.wait();\n  quicClient->setConnectionSetupCallback(nullptr);\n  if (res != TimedBaton::Status::signalled) {\n    auto err = HTTP3::ErrorCode::HTTP_NO_ERROR;\n    std::string errString = (res == TimedBaton::Status::timedout)\n                                ? \"Connect timed out\"\n                                : \"Connection cancelled\";\n    quicClient->close(\n        quic::QuicError(quic::QuicErrorCode(err), std::string(errString)));\n    co_yield co_error(quic::QuicInternalException(\n        errString, quic::LocalErrorCode::CONNECT_FAILED));\n  }\n  if (cb.quicException) {\n    co_yield co_error(std::move(cb.quicException));\n  }\n  if (connParams.onTransportCreated) {\n    connParams.onTransportCreated(*quicClient.get());\n  }\n  co_return cb.session;\n}\n\nfolly::coro::Task<HTTPCoroSession*> connectImpl(\n    folly::EventBase* evb,\n    folly::SocketAddress serverAddr,\n    std::unique_ptr<HTTPConnectStream> connectStream,\n    std::chrono::milliseconds timeout,\n    proxygen::coro::HTTPCoroConnector::ConnectionParams connParams,\n    proxygen::coro::HTTPCoroConnector::SessionParams sessionParams) {\n  auto startTime = getCurrentTime();\n  wangle::TransportInfo tinfo;\n\n  folly::Try<std::unique_ptr<CoroTransportIf>> socket;\n  bool isSecure = true;\n  if (connParams.fizzContextAndVerifier.fizzContext) {\n    socket = co_await co_awaitTry(connectFizz(\n        evb, serverAddr, std::move(connectStream), timeout, connParams, tinfo));\n  } else if (connParams.sslContext) {\n    socket = co_await co_awaitTry(connectTLS(\n        evb, serverAddr, std::move(connectStream), timeout, connParams, tinfo));\n  } else {\n    if (connectStream) {\n      socket.emplace(\n          std::make_unique<HTTPConnectTransport>(std::move(connectStream)));\n    } else {\n      socket = co_await co_awaitTry(\n          connectTCP(evb, serverAddr, timeout, connParams));\n    }\n    isSecure = false;\n  }\n  co_await folly::coro::co_safe_point;\n  if (socket.hasException()) {\n    XLOG(DBG4) << \"Failed to connect to: \" << serverAddr.describe()\n               << \" err=\" << socket.exception().what();\n    if (isSecure && connParams.tlsStats) {\n      bool verifyError = false; // TODO?\n      // TOOD: exclude OperationCancelled?\n      connParams.tlsStats->recordSSLUpstreamConnectionError(verifyError);\n    }\n\n    co_yield co_error(socket.exception());\n  }\n  if (isSecure && connParams.tlsStats) {\n    bool handshake = (tinfo.sslResume == wangle::SSLResumeEnum::HANDSHAKE);\n    connParams.tlsStats->recordSSLUpstreamConnection(handshake);\n  }\n\n  HTTPCodecFactory::CodecConfig codecConfig;\n  codecConfig.h1.forceHTTP1xCodecTo1_1 = true;\n  codecConfig.h2.headerIndexingStrategy = sessionParams.headerIndexingStrategy;\n  // Tries H1 for unknown/uninit proto\n  DefaultHTTPCodecFactory codecFactory(codecConfig);\n  const std::string* nextProto =\n      isSecure ? tinfo.appProtocol.get() : &connParams.plaintextProtocol;\n  auto codec =\n      codecFactory.getCodec(*nextProto, TransportDirection::UPSTREAM, isSecure);\n  setupCodec(*codec, sessionParams);\n\n  // TransportInfo has\n  tinfo.acceptTime = getCurrentTime();\n  if (isSecure) {\n    tinfo.sslSetupTime = millisecondsSince(startTime);\n  }\n  auto session = HTTPCoroSession::makeUpstreamCoroSession(\n      std::move(*socket), std::move(codec), std::move(tinfo));\n  setupSession(session, sessionParams);\n  co_return session;\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nfolly::coro::Task<HTTPCoroSession*> HTTPCoroConnector::connect(\n    folly::EventBase* evb,\n    folly::SocketAddress serverAddr,\n    std::chrono::milliseconds timeout,\n    const ConnectionParams& connParams,\n    const SessionParams& sessionParams) {\n  return connectImpl(\n      evb, std::move(serverAddr), nullptr, timeout, connParams, sessionParams);\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPCoroConnector::happyEyeballsConnect(\n    folly::EventBase* evb,\n    folly::SocketAddress primaryAddr,\n    folly::SocketAddress fallbackAddr,\n    std::chrono::milliseconds timeout,\n    const ConnectionParams& connParams,\n    const SessionParams& sessionParams,\n    std::chrono::milliseconds happyEyeballsDelay) {\n  folly::coro::SharedPromise<void> failedConnection;\n  auto primaryConnect = [](folly::EventBase* evb,\n                           folly::SocketAddress primaryAddr,\n                           std::chrono::milliseconds timeout,\n                           const ConnectionParams& connParams,\n                           const SessionParams& sessionParams,\n                           folly::coro::SharedPromise<void>& failedConnection)\n      -> folly::coro::Task<HTTPCoroSession*> {\n    auto sessionTry =\n        co_await folly::coro::co_awaitTry(HTTPCoroConnector::connect(\n            evb, std::move(primaryAddr), timeout, connParams, sessionParams));\n    if (sessionTry.hasException()) {\n      XLOG(DBG4) << \"Happy eyeballs primary connect failed: \"\n                 << sessionTry.exception().what();\n      failedConnection.setValue();\n    }\n    co_return sessionTry;\n  };\n\n  // Coroutine that handles the delay, then starts to connect\n  auto secondaryConnect =\n      [](folly::EventBase* evb,\n         folly::SocketAddress fallbackAddr,\n         std::chrono::milliseconds timeout,\n         std::chrono::milliseconds happyEyeballsDelay,\n         const ConnectionParams& connParams,\n         const SessionParams& sessionParams,\n         const folly::coro::SharedPromise<void>& failedConnection)\n      -> folly::coro::Task<HTTPCoroSession*> {\n    // Wait for happyEyeballsDelay or until the first attempt fails\n    co_await folly::coro::collectAny(\n        folly::coro::sleepReturnEarlyOnCancel(happyEyeballsDelay),\n        failedConnection.getFuture());\n    co_await folly::coro::co_safe_point;\n    XLOG(DBG4) << \"Happy eyeballs fallback attempt to \" << fallbackAddr;\n    co_return co_await co_nothrow(HTTPCoroConnector::connect(\n        evb, fallbackAddr, timeout, connParams, sessionParams));\n  };\n\n  auto res = co_await folly::coro::collectAnyWithoutException(\n      primaryConnect(evb,\n                     std::move(primaryAddr),\n                     timeout,\n                     connParams,\n                     sessionParams,\n                     failedConnection),\n      secondaryConnect(evb,\n                       std::move(fallbackAddr),\n                       timeout,\n                       happyEyeballsDelay,\n                       connParams,\n                       sessionParams,\n                       failedConnection));\n  co_return res.second;\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPCoroConnector::proxyConnect(\n    HTTPCoroSession* proxySession,\n    HTTPCoroSession::RequestReservation reservation,\n    std::string authority,\n    bool connectUnique,\n    std::chrono::milliseconds timeout,\n    const ConnectionParams& connParams,\n    const SessionParams& sessionParams) {\n\n  // egress bufer option?\n  XLOG(DBG2) << \"Sending CONNECT to \" << authority;\n  std::unique_ptr<HTTPConnectStream> connectStream;\n  if (connectUnique) {\n    connectStream = co_await co_nothrow(HTTPConnectStream::connectUnique(\n        proxySession, std::move(reservation), authority, timeout));\n  } else {\n    connectStream = co_await co_nothrow(HTTPConnectStream::connect(\n        proxySession, std::move(reservation), authority, timeout));\n  }\n  auto peerAddr = connectStream->peerAddr_;\n  co_return co_await co_nothrow(connectImpl(proxySession->getEventBase(),\n                                            std::move(peerAddr),\n                                            std::move(connectStream),\n                                            timeout,\n                                            connParams,\n                                            sessionParams));\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPCoroConnector::connect(\n    folly::EventBase* evb,\n    folly::SocketAddress serverAddr,\n    std::chrono::milliseconds timeout,\n    const QuicConnectionParams& connParams,\n    const SessionParams& sessionParams) {\n  return connectQuic(evb, serverAddr, timeout, connParams, sessionParams);\n}\n\nstd::shared_ptr<folly::SSLContext> HTTPCoroConnector::makeSSLContext(\n    const TLSParams& params) {\n  auto sslContext = std::make_shared<folly::SSLContext>();\n  sslContext->setOptions(SSL_OP_NO_COMPRESSION);\n  folly::ssl::setCipherSuites<folly::ssl::SSLCommonOptions>(*sslContext);\n\n  if (params.caPaths.size() > 0) {\n    sslContext->loadTrustedCertificates(params.caPaths);\n    folly::SSLContext::VerifyServerCertificate verify{\n        folly::SSLContext::VerifyServerCertificate::IF_PRESENTED};\n    sslContext->setVerificationOption(verify);\n  }\n  if (!params.clientCertPath.empty() && !params.clientKeyPath.empty()) {\n    sslContext->loadCertKeyPairFromFiles(params.clientCertPath.c_str(),\n                                         params.clientKeyPath.c_str());\n  }\n  if (!params.nextProtocols.empty()) {\n    sslContext->setAdvertisedNextProtocols(params.nextProtocols);\n  }\n  return sslContext;\n}\n\nstd::shared_ptr<const fizz::client::FizzClientContext>\nHTTPCoroConnector::makeFizzClientContext(const TLSParams& params) {\n  auto fizzContext = std::make_shared<fizz::client::FizzClientContext>();\n\n  std::string certData;\n  if (!params.clientCertPath.empty()) {\n    folly::readFile(params.clientCertPath.c_str(), certData);\n  }\n  std::string keyData;\n  if (!params.clientKeyPath.empty()) {\n    folly::readFile(params.clientKeyPath.c_str(), keyData);\n  }\n  if (!certData.empty() && !keyData.empty()) {\n    auto cert = fizz::openssl::CertUtils::makeSelfCert(std::move(certData),\n                                                       std::move(keyData));\n    auto certMgr = std::make_shared<fizz::client::CertManager>();\n    certMgr->addCert(std::move(cert));\n    fizzContext->setClientCertManager(std::move(certMgr));\n  }\n  fizzContext->setSupportedAlpns(\n      {params.nextProtocols.begin(), params.nextProtocols.end()});\n  fizzContext->setSendEarlyData(params.earlyData);\n  fizzContext->setPskCache(params.pskCache ? params.pskCache\n                                           : std::make_shared<BasicPskCache>());\n  return fizzContext;\n}\n\nstd::shared_ptr<const fizz::CertificateVerifier>\nHTTPCoroConnector::makeFizzCertVerifier(const TLSParams& params) {\n  std::shared_ptr<const fizz::CertificateVerifier> fizzCertVerifier;\n  if (params.caPaths.size() > 0) {\n    fizzCertVerifier = fizz::DefaultCertificateVerifier::createFromCAFiles(\n        fizz::VerificationContext::Client, params.caPaths);\n  }\n  return fizzCertVerifier;\n}\n\nHTTPCoroConnector::FizzContextAndVerifier\nHTTPCoroConnector::makeFizzClientContextAndVerifier(const TLSParams& params) {\n  FizzContextAndVerifier fizzCtx;\n  fizzCtx.fizzContext = makeFizzClientContext(params);\n  fizzCtx.fizzCertVerifier = makeFizzCertVerifier(params);\n  return fizzCtx;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPCoroConnector.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/client/FizzClientContext.h>\n#include <fizz/protocol/CertificateVerifier.h>\n#include <folly/SocketAddress.h>\n#include <folly/coro/Task.h>\n#include <folly/io/SocketOptionMap.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/SSLContext.h>\n#include <folly/ssl/SSLSession.h>\n#include <quic/api/LoopDetectorCallback.h>\n#include <quic/fizz/client/handshake/QuicPskCache.h>\n#include <quic/logging/QLogger.h>\n#include <quic/state/QuicTransportStatsCallback.h>\n#include <quic/state/TransportSettings.h>\n#include <string>\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/sampling/Sampling.h>\n\nnamespace wangle {\nclass SSLStats;\n}\n\nnamespace quic {\nclass QuicClientTransport;\n}\n\nnamespace proxygen {\nextern const std::string empty_string;\nclass HTTPSessionStats;\nclass HeaderIndexingStrategy;\nnamespace coro {\n\nclass HTTPCoroSession;\nclass LifecycleObserver;\n\n/**\n * Asynchronously establish a new connection to the specified server, and\n * construct an HTTPCoroSession around it, using the negotiated protocol.\n */\nclass HTTPCoroConnector {\n public:\n  class SslSessionManagerIf {\n   public:\n    virtual ~SslSessionManagerIf() = default;\n    using SslSessionPtr = std::shared_ptr<folly::ssl::SSLSession>;\n    virtual void onNewSslSession(SslSessionPtr session) noexcept = 0;\n    virtual SslSessionPtr getSslSession() noexcept = 0;\n  };\n\n  struct TLSParams {\n    explicit TLSParams(std::list<std::string> inNextProtos = {\"h2\", \"http/1.1\"})\n        : nextProtocols(std::move(inNextProtos)) {\n    }\n    std::vector<std::string> caPaths;\n    std::string clientCertPath;\n    std::string clientKeyPath;\n    // TODO: other fancy TLS stuff\n    bool earlyData{false}; // Support for TCP?\n    std::list<std::string> nextProtocols;\n    std::shared_ptr<fizz::client::PskCache> pskCache;\n  };\n\n  static const TLSParams& defaultTLSParams() {\n    static const TLSParams tlsParams;\n    return tlsParams;\n  }\n\n  struct FizzContextAndVerifier {\n    std::shared_ptr<const fizz::client::FizzClientContext> fizzContext;\n    std::shared_ptr<const fizz::CertificateVerifier> fizzCertVerifier;\n  };\n\n  static const TLSParams& defaultQuicTLSParams() {\n    static const TLSParams tlsParams({\"h3\"});\n    return tlsParams;\n  }\n\n  // Helpers to make a Fizz or SSL context\n  static FizzContextAndVerifier makeFizzClientContextAndVerifier(\n      const TLSParams& params);\n  static std::shared_ptr<const fizz::client::FizzClientContext>\n  makeFizzClientContext(const TLSParams& params);\n  static std::shared_ptr<const fizz::CertificateVerifier> makeFizzCertVerifier(\n      const TLSParams& params);\n\n  static std::shared_ptr<folly::SSLContext> makeSSLContext(\n      const TLSParams& params);\n\n  struct BaseConnectionParams {\n    folly::SocketOptionMap socketOptions{folly::emptySocketOptionMap};\n    folly::SocketAddress bindAddr{folly::AsyncSocket::anyAddress()};\n\n    // TLS Params\n    std::string serverName; // SNI\n\n    // TLS connections must supply either an sslContext or a fizzContext\n    FizzContextAndVerifier fizzContextAndVerifier;\n\n    wangle::SSLStats* tlsStats{nullptr};\n  };\n\n  struct ConnectionParams : public BaseConnectionParams {\n    folly::Optional<std::string> congestionFlavor;\n    folly::Optional<std::string> fizzPskIdentity;\n\n    std::shared_ptr<const folly::SSLContext> sslContext;\n    SslSessionManagerIf* sslSessionManager{nullptr};\n\n    // Next protocol for plaintext (TCP) connections\n    std::string plaintextProtocol;\n  };\n\n  static const ConnectionParams& defaultConnectionParams() {\n    static const ConnectionParams params;\n    return params;\n  }\n\n  struct QuicConnectionParams : public BaseConnectionParams {\n    quic::TransportSettings transportSettings;\n\n    std::shared_ptr<quic::QuicPskCache> quicPskCache;\n\n    Sampling qlogSampling;\n    std::shared_ptr<quic::QLogger> qLogger;\n    std::shared_ptr<quic::LoopDetectorCallback> quicLoopDetectorCallback;\n    std::shared_ptr<quic::QuicTransportStatsCallback>\n        quicTransportStatsCallback;\n    std::shared_ptr<quic::CongestionControllerFactory> ccFactory;\n    std::function<void(quic::QuicClientTransport&)> onTransportCreated;\n  };\n\n  struct SessionParams {\n    SettingsList settings;\n    std::optional<uint32_t> maxConcurrentOutgoingStreams;\n    std::optional<size_t> connFlowControl;\n    std::optional<std::chrono::milliseconds> streamReadTimeout;\n    std::optional<std::chrono::milliseconds> connReadTimeout;\n    std::optional<std::chrono::milliseconds> writeTimeout;\n    LifecycleObserver* lifecycleObserver{nullptr};\n    HTTPSessionStats* sessionStats{nullptr};\n    HeaderCodec::Stats* headerCodecStats{nullptr};\n    const HeaderIndexingStrategy* headerIndexingStrategy{nullptr};\n  };\n\n  static const SessionParams& defaultSessionParams() {\n    static const SessionParams params;\n    return params;\n  }\n\n  static folly::coro::Task<HTTPCoroSession*> connect(\n      folly::EventBase* evb,\n      folly::SocketAddress serverAddr,\n      std::chrono::milliseconds timeout,\n      const ConnectionParams& connParams = defaultConnectionParams(),\n      const SessionParams& sessionParams = defaultSessionParams());\n\n  static constexpr std::chrono::milliseconds kHappyEyeballsDelay{150};\n\n  static folly::coro::Task<HTTPCoroSession*> happyEyeballsConnect(\n      folly::EventBase* evb,\n      folly::SocketAddress primaryAddr,\n      folly::SocketAddress fallbackAddr,\n      std::chrono::milliseconds timeout,\n      const ConnectionParams& connParams = defaultConnectionParams(),\n      const SessionParams& sessionParams = defaultSessionParams(),\n      std::chrono::milliseconds happyEyeballsTimeout = kHappyEyeballsDelay);\n\n  // For HTTP connections over HTTP CONNECT\n  static folly::coro::Task<HTTPCoroSession*> proxyConnect(\n      HTTPCoroSession* proxySession,\n      HTTPCoroSession::RequestReservation reservation,\n      std::string authority,\n      bool connectUnique,\n      std::chrono::milliseconds timeout,\n      const ConnectionParams& connParams = defaultConnectionParams(),\n      const SessionParams& sessionParams = defaultSessionParams());\n\n  static folly::coro::Task<HTTPCoroSession*> connect(\n      folly::EventBase* evb,\n      folly::SocketAddress serverAddr,\n      std::chrono::milliseconds timeout,\n      const QuicConnectionParams& connParams,\n      const SessionParams& sessionParams = defaultSessionParams());\n};\n\n} // namespace coro\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPCoroSessionPool.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include <chrono>\n#include <folly/Random.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/utils/Time.h>\n\nusing folly::coro::co_error;\nusing folly::coro::co_withCancellation;\n\nnamespace {\nconstexpr double kJitterPct = 0.3;\n\nusing Ex = proxygen::coro::HTTPCoroSessionPool::Exception;\ninline Ex maxWaitersEx() {\n  return Ex{Ex::Type::MaxWaiters, \"max waiters exceeded\"};\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nHTTPCoroSessionPool::Holder::Holder(HTTPCoroSession& inSession,\n                                    HTTPCoroSessionPool& pool)\n    : session(inSession), pool_(pool) {\n  using std::chrono::duration;\n  using std::chrono::duration_cast;\n  using std::chrono::seconds;\n\n  inSession.addLifecycleObserver(this);\n\n  maxAge_ = duration_cast<seconds>(duration<double>(\n      pool_.poolParams_.maxAge.count() *\n      (1 + folly::Random::randDouble(-kJitterPct, kJitterPct))));\n}\n\nHTTPCoroSessionPool::Holder::~Holder() {\n  session.removeLifecycleObserver(this);\n  pool_.removeSession(*this, /*checkForWaiters=*/true);\n  if (state_ == Idle) {\n    pool_.notifyIdleSessionObserver();\n  }\n}\n\nvoid HTTPCoroSessionPool::Holder::onTransactionAttached(\n    const HTTPCoroSession& sess) {\n  XCHECK_EQ(&sess, &session);\n  // Shouldn't happen, but maybe there's a race and we should keep going\n  XLOG_IF(DFATAL, state_ == Full)\n      << \"onTransactionAttached to unavailable session\";\n  if (!session.supportsMoreTransactions()) {\n    pool_.moveToFull(*this);\n  } else if (state_ == Idle) {\n    pool_.moveToAvailable(*this);\n  } // else already available (neither full nor idle)\n}\n\nvoid HTTPCoroSessionPool::Holder::onTransactionDetached(\n    const HTTPCoroSession& sess) {\n  XCHECK_EQ(&sess, &session);\n  if (!pool_.poolingEnabled_) {\n    session.initiateDrain();\n  } else if (state_ == Full && session.supportsMoreTransactions()) {\n    pool_.moveToAvailable(*this);\n  }\n}\nvoid HTTPCoroSessionPool::Holder::onSettingsOutgoingStreamsFull(\n    const HTTPCoroSession& sess) {\n  XCHECK_EQ(&sess, &session);\n  pool_.moveToFull(*this);\n}\n\nvoid HTTPCoroSessionPool::Holder::onSettingsOutgoingStreamsNotFull(\n    const HTTPCoroSession& sess) {\n  onTransactionDetached(sess);\n}\n\nvoid HTTPCoroSessionPool::Holder::onDrainStarted(const HTTPCoroSession& sess) {\n  XLOG(DBG4) << \"onDrainStarted pool=\" << pool_ << \" session=\" << session;\n  onDestroy(sess);\n}\n\nvoid HTTPCoroSessionPool::Holder::onDestroy(const HTTPCoroSession& sess) {\n  XLOG(DBG4) << \"Destroying session holder in pool=\" << pool_\n             << \" session=\" << session;\n  XCHECK_EQ(&sess, &session);\n  delete this;\n}\n\nvoid HTTPCoroSessionPool::Holder::onDeactivateConnection(\n    const HTTPCoroSession& sess) {\n  XLOG(DBG4) << \"onDeactivateConnection pool=\" << pool_\n             << \" session=\" << session;\n  if (sess.supportsMoreTransactions()) {\n    pool_.moveToIdle(*this);\n  }\n}\n\nHTTPCoroSessionPool::HTTPCoroSessionPool(\n    folly::EventBase* evb,\n    const std::string& server,\n    uint16_t port,\n    PoolParams poolParams,\n    HTTPCoroConnector::ConnectionParams connParams,\n    HTTPCoroConnector::SessionParams sessionParams,\n    bool allowNameLookup,\n    Observer* observer)\n    : HTTPCoroSessionPool(evb,\n                          folly::SocketAddress{server, port, allowNameLookup},\n                          poolParams,\n                          std::move(connParams),\n                          std::move(sessionParams),\n                          observer) {\n}\n\nHTTPCoroSessionPool::HTTPCoroSessionPool(\n    folly::EventBase* evb,\n    folly::SocketAddress socketAddress,\n    PoolParams poolParams,\n    HTTPCoroConnector::ConnectionParams connParams,\n    HTTPCoroConnector::SessionParams sessionParams,\n    Observer* observer)\n    : eventBase_(evb),\n      observer_(observer),\n      serverAddress_(std::move(socketAddress)),\n      authority_(serverAddress_.describe()),\n      poolParams_(poolParams),\n      tcpConnParams_(std::move(connParams)),\n      sessionParams_(std::move(sessionParams)) {\n  setMaxConnections(poolParams.maxConnections);\n  setMaxConnectionAttempts(poolParams.maxConnectionAttempts);\n  XLOG(DBG4) << \"Creating pool for \" << *this\n             << \" maxConnections_=\" << poolParams_.maxConnections\n             << \" observer=\" << observer_;\n  setSslManager();\n}\n\nHTTPCoroSessionPool::HTTPCoroSessionPool(\n    folly::EventBase* evb,\n    const std::string& server,\n    uint16_t port,\n    std::shared_ptr<HTTPCoroSessionPool> proxyPool,\n    PoolParams poolParams,\n    HTTPCoroConnector::ConnectionParams connParams,\n    HTTPCoroConnector::SessionParams sessionParams,\n    Observer* observer)\n    : eventBase_(evb),\n      observer_(observer),\n      authority_(folly::to<std::string>(server, \":\", port)),\n      poolParams_(poolParams),\n      proxyPool_(std::move(proxyPool)),\n      tcpConnParams_(std::move(connParams)),\n      sessionParams_(std::move(sessionParams)) {\n  setMaxConnections(poolParams.maxConnections);\n  setMaxConnectionAttempts(poolParams.maxConnectionAttempts);\n  XLOG(DBG4) << \"Creating pool for \" << *this\n             << \" maxConnections_=\" << poolParams_.maxConnections\n             << \" observer=\" << observer_;\n  setSslManager();\n}\n\nHTTPCoroSessionPool::HTTPCoroSessionPool(\n    folly::EventBase* evb,\n    const std::string& server,\n    uint16_t port,\n    PoolParams poolParams,\n    std::shared_ptr<const HTTPCoroConnector::QuicConnectionParams> connParams,\n    HTTPCoroConnector::SessionParams sessionParams,\n    bool allowNameLookup,\n    Observer* observer)\n    : eventBase_(evb),\n      observer_(observer),\n      serverAddress_(server, port, allowNameLookup),\n      authority_(serverAddress_.describe()),\n      poolParams_(poolParams),\n      quicConnParams_(std::move(connParams)),\n      sessionParams_(std::move(sessionParams)) {\n  setMaxConnections(poolParams.maxConnections);\n  setMaxConnectionAttempts(poolParams.maxConnectionAttempts);\n  XLOG(DBG4) << \"Creating pool for \" << *this\n             << \" maxConnections_=\" << poolParams_.maxConnections\n             << \" observer=\" << observer_;\n}\n\nvoid HTTPCoroSessionPool::setSslManager() {\n  if (tcpConnParams_.sslSessionManager == nullptr &&\n      poolParams_.enableSslSessionCaching) {\n    tcpConnParams_.sslSessionManager = &sslSessionManager_;\n  }\n}\n\nHTTPCoroSessionPool::SessionList& HTTPCoroSessionPool::getSessionList(\n    HTTPCoroSessionPool::Holder& holder) {\n  switch (holder.state_) {\n    case Holder::State::Idle:\n      return idleSessions_;\n    case Holder::State::Available:\n      return availableSessions_;\n    case Holder::State::Full:\n      return fullSessions_;\n  }\n  folly::assume_unreachable();\n}\n\nvoid HTTPCoroSessionPool::moveToFull(HTTPCoroSessionPool::Holder& holder) {\n  XLOG(DBG4) << \"Session now full, pool=\" << *this\n             << \" session=\" << holder.session;\n  removeSession(holder, /*checkForWaiters=*/false);\n  addSession(holder, Holder::State::Full);\n}\n\n/*\n * Always push to the back and pop from the front (FIFO). This prevents\n * slow servers from being excessively re-used and drawing load to themselves.\n */\nvoid HTTPCoroSessionPool::moveToAvailable(HTTPCoroSessionPool::Holder& holder) {\n  XLOG(DBG4) << \"Session now available, pool=\" << *this\n             << \" session=\" << holder.session;\n  // TODO: consider leaving in fullSessions if the pool is over-full somehow?\n  removeSession(holder, /*checkForWaiters=*/false);\n  addSession(holder, Holder::State::Available);\n}\n\nvoid HTTPCoroSessionPool::moveToIdle(HTTPCoroSessionPool::Holder& holder) {\n  XLOG(DBG4) << \"Session now idle, pool=\" << *this\n             << \" session=\" << holder.session;\n  // TODO: consider leaving in fullSessions if the pool is over-full somehow?\n  removeSession(holder, /*checkForWaiters=*/false);\n  addSession(holder, Holder::State::Idle);\n}\n\nvoid HTTPCoroSessionPool::addSession(HTTPCoroSessionPool::Holder& holder,\n                                     Holder::State state) {\n  Holder::State prevState = std::exchange(holder.state_, state);\n  auto& sessionList = getSessionList(holder);\n  sessionList.push_back(holder);\n  if (state != Holder::State::Full) {\n    signalWaiters(holder.session.numTransactionsAvailable());\n  }\n\n  // if either removing or adding an idle session, invoke observer\n  if (prevState == Holder::State::Idle || state == Holder::State::Idle) {\n    notifyIdleSessionObserver();\n  }\n}\n\nvoid HTTPCoroSessionPool::removeSession(HTTPCoroSessionPool::Holder& holder,\n                                        bool checkForWaiters) {\n  auto& sessionList = getSessionList(holder);\n  sessionList.erase(sessionList.iterator_to(holder));\n\n  if (checkForWaiters && !isDraining() && !waiters_.empty() && !full()) {\n    XLOG(DBG5) << \"Initiating new connection attempt, pool=\" << *this;\n    connectsInProgress_++;\n    XLOG(DBG4) << *this << \":\" << serverAddress_\n               << \" ++connectsInProgress_ = \" << connectsInProgress_;\n    co_withExecutor(\n        eventBase_,\n        co_withCancellation(cancellationSource_.getToken(), addNewConnection()))\n        .start();\n  }\n}\n\nbool HTTPCoroSessionPool::shouldAgeOut(\n    HTTPCoroSessionPool::Holder& holder) const {\n  if (poolParams_.maxAge.count() == 0) {\n    return false;\n  }\n\n  auto age = secondsSince(holder.session.getStartTime());\n  return age >= holder.maxAge_;\n}\n\nfolly::coro::Task<HTTPCoroSessionPool::GetSessionResult>\nHTTPCoroSessionPool::getSessionWithReservation() {\n  // This coroutine performs late binding.  It will grab the head of the\n  // availableSessions_, or wait for it to become non-empty.\n  //\n  // It will optionally start a coro to create a new connection, if there is\n  // room in this pool.  The new connection will race with existing connections\n  // to become available.\n  auto start = getCurrentTime();\n  const auto& reqToken = co_await folly::coro::co_current_cancellation_token;\n  auto poolToken = cancellationSource_.getToken();\n  auto mergedToken = folly::cancellation_token_merge(reqToken, poolToken);\n\n  while (!mergedToken.isCancellationRequested()) {\n    while (hasAvailableSessions()) {\n      auto& holder = availableSessions_.empty() ? idleSessions_.front()\n                                                : availableSessions_.front();\n      bool supportsMoreTransactions = holder.session.supportsMoreTransactions();\n      if (supportsMoreTransactions && !shouldAgeOut(holder)) {\n        XLOG(DBG4) << \"Session available, pool=\" << *this\n                   << \" returning session=\" << holder.session;\n        auto& session = holder.session;\n        auto maybeReservation = session.reserveRequest();\n        if (maybeReservation.hasException()) {\n          co_yield co_error(std::move(maybeReservation.exception()));\n        }\n        co_return GetSessionResult(std::move(*maybeReservation), &session);\n      } else if (!supportsMoreTransactions) {\n        XLOG(ERR) << \"Full session in available!, pool=\" << *this\n                  << \" sess=\" << holder.session;\n        moveToFull(holder);\n      } else {\n        XLOG(DBG4) << \"Draining too-old session, pool=\" << *this\n                   << \" session=\" << holder.session;\n        holder.session.initiateDrain();\n      }\n    }\n    if (millisecondsSince(start) > poolParams_.connectTimeout) {\n      XLOG(ERR) << \"Timed out waiting for session, pool=\" << *this;\n      co_yield co_error(\n          Exception(Exception::Type::Timeout, \"Timed out waiting for session\"));\n    }\n    if (!full()) {\n      // A thundering herd can occur here for multiplexed protocols.  If the\n      // pool is empty, a rush of getSession calls will each start a new\n      // connection, even if the first one is multiplexed and can satisfy all\n      // of them.\n      // TODO: keep an average of the last N max_streams per conn, and delay\n      // creating a new connection if a pending one is likely to satisfy this\n      XLOG(DBG5) << \"Initiating new connection attempt, pool=\" << *this;\n      connectsInProgress_++;\n      XLOG(DBG4) << *this << \":\" << serverAddress_\n                 << \" ++connectsInProgress_ = \" << connectsInProgress_;\n      co_withExecutor(eventBase_,\n                      co_withCancellation(cancellationSource_.getToken(),\n                                          addNewConnection()))\n          .start();\n    }\n    Waiter waiter;\n    waiters_.push_back(waiter);\n    // we may be exceeding the max waiters\n    signalWaiters(0);\n    XLOG(DBG4) << \"No sessions available, waiting, pool=\" << *this;\n    auto res = co_await co_withCancellation(\n        waiter.cancellationSource.getToken(),\n        waiter.baton.timedWait(eventBase_, poolParams_.connectTimeout));\n    if (res == TimedBaton::Status::cancelled) {\n      XLOG_IF(DBG4, !poolToken.isCancellationRequested())\n          << \"getSession cancelled, pool=\" << *this;\n      if (waiter.exception) {\n        co_yield co_error(std::move(*waiter.exception));\n      } else {\n        co_yield co_error(Exception(Exception::Type::Cancelled, \"Cancelled\"));\n      }\n    }\n    if (waiter.listHook_.is_linked()) {\n      waiters_.erase(waiters_.iterator_to(waiter));\n    }\n    // loop around to check, including on timeout\n  }\n\n  if (reqToken.isCancellationRequested()) {\n    co_yield folly::coro::co_error(\n        Exception(Exception::Type::Cancelled, \"Cancelled\"));\n  }\n\n  co_yield co_error(Exception(Exception::Type::Draining, \"Pool is draining\"));\n}\n\nfolly::coro::Task<void> HTTPCoroSessionPool::addNewConnection() {\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  co_await folly::coro::co_safe_point;\n  auto g = folly::makeGuard([&, observer = observer_] {\n    if (observer) {\n      XLOG(DBG4) << \"--pendingUpstreamConnections\";\n      observer->incrementPendingUpstreamConnections(-1);\n    }\n    if (!cancelToken.isCancellationRequested()) {\n      connectsInProgress_--;\n      XLOG(DBG4) << *this << \":\" << serverAddress_\n                 << \" --connectsInProgress_ = \" << connectsInProgress_;\n    }\n  });\n  if (observer_) {\n    XLOG(DBG4) << \"++pendingUpstreamConnections, pool=\" << *this;\n    observer_->incrementPendingUpstreamConnections(1);\n  } else {\n    XLOG(DBG4) << \"observer not set, pool=\" << *this;\n  }\n  folly::exception_wrapper exceptionWrapper;\n  XLOG(DBG4) << \"Getting session, pool=\" << *this;\n  for (uint16_t attempt = 0; attempt < poolParams_.maxConnectionAttempts;\n       attempt++) {\n    folly::Try<HTTPCoroSession*> sessionTry;\n    if (quicConnParams_) {\n      sessionTry = co_await co_awaitTry(\n          HTTPCoroConnector::connect(eventBase_,\n                                     serverAddress_,\n                                     poolParams_.connectTimeout,\n                                     *quicConnParams_,\n                                     sessionParams_));\n    } else if (proxyPool_) {\n      auto proxySession =\n          co_await co_awaitTry(proxyPool_->getSessionWithReservation());\n      if (proxySession.hasException()) {\n        sessionTry.emplaceException(std::move(proxySession.exception()));\n      } else {\n        co_await folly::coro::co_safe_point;\n        sessionTry = co_await co_awaitTry(HTTPCoroConnector::proxyConnect(\n            proxySession->session,\n            std::move(proxySession->reservation),\n            authority_,\n            /*connectUnique=*/false,\n            poolParams_.connectTimeout,\n            tcpConnParams_,\n            sessionParams_));\n      }\n    } else {\n      sessionTry = co_await co_awaitTry(\n          HTTPCoroConnector::connect(eventBase_,\n                                     serverAddress_,\n                                     poolParams_.connectTimeout,\n                                     tcpConnParams_,\n                                     sessionParams_));\n    }\n    if (cancelToken.isCancellationRequested()) {\n      if (!sessionTry.hasException()) {\n        sessionTry.value()->initiateDrain();\n      }\n      co_yield folly::coro::co_stopped_may_throw;\n    }\n    if (sessionTry.hasException()) {\n      XLOG(DBG3) << \"Failed to get connection for pool=\" << *this\n                 << \" err=\" << sessionTry.exception().what()\n                 << \", server=\" << serverAddress_;\n      exceptionWrapper = sessionTry.exception();\n      if (overflowed()) {\n        XLOG(ERR) << \"Too many connections in progress=\" << \" pending=\"\n                  << connectsInProgress_ << \", idle=\" << idleSessions_.size()\n                  << \", available=\" << availableSessions_.size()\n                  << \", full=\" << fullSessions_.size();\n      }\n      if (waiters_.empty()) {\n        // No one is waiting\n        XLOG(INFO) << \"Giving up on a connection attempt, no waiters\";\n        break;\n      }\n      XLOG(DBG4) << *this << \":\" << serverAddress_\n                 << \" ++connectsInProgress_ = \" << connectsInProgress_;\n    } else {\n      auto session = *sessionTry;\n      XLOG(DBG4) << \"Got upstream, pool=\" << *this << \" session=\" << *session;\n      auto* holder = new Holder(*session, *this);\n      // defaulted available since immediate local use is expected\n      addSession(*holder, holder->state_);\n      co_return;\n    }\n  }\n  if (!waiters_.empty() && !hasSession() && connectsInProgress_ == 1) {\n    XLOG(DBG4) << \"Cancelling waiters in pool=\" << *this;\n    // The last running connect exited in failure, but there are getSession\n    // calls waiting.  Cancel them so they return.\n    cancelWaiters(Exception(Exception::Type::ConnectFailed,\n                            \"Connect Failed\",\n                            std::move(exceptionWrapper)));\n  }\n}\n\nvoid HTTPCoroSessionPool::signalWaiters(uint32_t n) {\n  XLOG(DBG4) << \"signalWaiters n=\" << n\n             << \", waiters_.size()=\" << waiters_.size();\n  while (!waiters_.empty() && n-- > 0) {\n    // Remove waiters as they are signalled\n    waiters_.front().baton.signal();\n    waiters_.pop_front();\n  }\n\n  // cancel excess waiters if full, until <= maxWaiters\n  const bool full = fullSessions_.size() >= poolParams_.maxConnections;\n  while (full && (waiters_.size() > poolParams_.maxWaiters)) {\n    waiters_.back().cancel(maxWaitersEx());\n    waiters_.pop_back();\n  }\n}\n\nvoid HTTPCoroSessionPool::Waiter::cancel(Exception ex) {\n  exception = std::move(ex);\n  cancellationSource.requestCancellation();\n}\n\nvoid HTTPCoroSessionPool::cancelWaiters(Exception ex) {\n  for (auto& waiter : waiters_) {\n    waiter.cancel(ex);\n  }\n  waiters_.clear();\n}\n\nvoid HTTPCoroSessionPool::drain() {\n  XCHECK(!eventBase_->isRunning() || eventBase_->isInEventBaseThread());\n  cancellationSource_.requestCancellation();\n  XLOG(DBG4) << *this << \":\" << serverAddress_\n             << \" connectsInProgress_ = \" << connectsInProgress_ << \" at drain\";\n  connectsInProgress_ = 0;\n  cancelWaiters(Exception(Exception::Type::Draining, \"Draining\"));\n  flush();\n  idleSessionObserver_ = nullptr;\n}\n\nvoid HTTPCoroSessionPool::flush() {\n  cancelWaiters(Exception(Exception::Type::Draining, \"Flushing\"));\n\n  // Draining a session will delete the holder and pop it off the list\n  while (!fullSessions_.empty()) {\n    auto& holder = fullSessions_.front();\n    holder.session.initiateDrain();\n  }\n\n  while (!availableSessions_.empty()) {\n    auto& holder = availableSessions_.front();\n    holder.session.initiateDrain();\n  }\n\n  while (!idleSessions_.empty()) {\n    auto& holder = idleSessions_.front();\n    holder.session.initiateDrain();\n  }\n}\n\nHTTPSessionContextPtr HTTPCoroSessionPool::detachIdleSession() noexcept {\n  XLOG(DBG6) << \"pool=\" << *this;\n  if (!hasIdleSessions() || !waiters_.empty()) {\n    return {};\n  }\n\n  for (auto& holder : idleSessions_) {\n    auto& session = holder.session;\n    if (session.isDetachable()) {\n      XLOG(DBG6) << __func__ << \"; found detachable sess=\" << session;\n      session.detachEvb();\n      delete &holder;\n      return session.acquireKeepAlive();\n    } else {\n      XLOG(ERR) << __func__ << \"idle session not detachable\";\n    }\n  }\n\n  return {}; // no detachable sessions\n}\n\nvoid HTTPCoroSessionPool::insertIdleSession(\n    HTTPSessionContextPtr&& sess) noexcept {\n  XLOG(DBG6) << __func__ << \"; sess=\" << sess.get();\n  auto* session = static_cast<HTTPCoroSession*>(sess.get());\n  XCHECK_EQ(sess->getEventBase(), eventBase_);\n  auto* holder = std::make_unique<Holder>(*session, *this).release();\n  // set as available since immediate local use is expected\n  addSession(*holder, Holder::State::Available);\n}\n\nvoid HTTPCoroSessionPool::notifyIdleSessionObserver() const noexcept {\n  if (idleSessionObserver_ && !isDraining()) {\n    idleSessionObserver_->onIdleSessionsChanged(*this);\n  }\n}\n\nbool HTTPCoroSessionPool::hasSession() const {\n  return !idleSessions_.empty() || !availableSessions_.empty() ||\n         !fullSessions_.empty();\n}\n\nvoid HTTPCoroSessionPool::describe(std::ostream& os) const {\n  os << \"server=\" << authority_ << \", idle=\" << idleSessions_.size()\n     << \", available=\" << availableSessions_.size()\n     << \", full=\" << fullSessions_.size()\n     << \", connectsInProgress=\" << connectsInProgress_\n     << \", waiters=\" << waiters_.size()\n     << \", poolingEnabled_=\" << poolingEnabled_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPCoroSessionPool& pool) {\n  pool.describe(os);\n  return os;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPCoroSessionPool.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include \"proxygen/lib/http/coro/client/HTTPSessionFactory.h\"\n#include <folly/logging/xlog.h>\n\n#include <folly/IntrusiveList.h>\n\nnamespace proxygen::coro {\n\nclass HTTPCoroSessionPool {\n private:\n  class Holder : private LifecycleObserver {\n   public:\n    Holder(HTTPCoroSession& inSession, HTTPCoroSessionPool& pool);\n    Holder(const Holder&) = delete;\n    Holder& operator=(const Holder&) = delete;\n    ~Holder() override;\n\n    HTTPCoroSession& session;\n\n   private:\n    void onTransactionAttached(const HTTPCoroSession& session) override;\n    void onTransactionDetached(const HTTPCoroSession& session) override;\n    void onSettingsOutgoingStreamsFull(const HTTPCoroSession& sess) override;\n    void onSettingsOutgoingStreamsNotFull(const HTTPCoroSession& sess) override;\n    void onDrainStarted(const HTTPCoroSession& session) override;\n    void onDestroy(const HTTPCoroSession& session) override;\n    void onDeactivateConnection(const HTTPCoroSession& session) override;\n\n    HTTPCoroSessionPool& pool_;\n    enum State : uint8_t { Idle = 0, Available, Full } state_{Available};\n    folly::SafeIntrusiveListHook listHook_;\n    // To make IntrusiveList work\n    friend class HTTPCoroSessionPool;\n    std::chrono::seconds maxAge_;\n  };\n  using SessionList = folly::CountedIntrusiveList<Holder, &Holder::listHook_>;\n\n public:\n  struct PoolParams {\n    uint32_t maxConnections{100};\n    uint8_t maxConnectionAttempts{3};\n    bool enableSslSessionCaching{true};\n    std::chrono::milliseconds connectTimeout{std::chrono::seconds(2)};\n    std::chrono::seconds maxAge{std::chrono::minutes(10)};\n    uint16_t maxWaiters{std::numeric_limits<uint16_t>::max()};\n  };\n\n  class Observer {\n   public:\n    void incrementPendingUpstreamConnections(int count) {\n      onPendingUpstreamConnectionIncrement(count);\n    }\n\n   protected:\n    ~Observer() = default;\n\n   private:\n    virtual void onPendingUpstreamConnectionIncrement(int count) = 0;\n  };\n\n  static PoolParams defaultPoolParams() {\n    static PoolParams defaultParams;\n    return defaultParams;\n  }\n\n  HTTPCoroSessionPool(folly::EventBase* evb,\n                      const std::string& server,\n                      uint16_t port,\n                      PoolParams poolParams = defaultPoolParams(),\n                      HTTPCoroConnector::ConnectionParams connParams =\n                          HTTPCoroConnector::defaultConnectionParams(),\n                      HTTPCoroConnector::SessionParams sessionParams =\n                          HTTPCoroConnector::defaultSessionParams(),\n                      bool allowNameLookup = false,\n                      Observer* observer = nullptr);\n\n  HTTPCoroSessionPool(folly::EventBase* evb,\n                      folly::SocketAddress socketAddress,\n                      PoolParams poolParams = defaultPoolParams(),\n                      HTTPCoroConnector::ConnectionParams connParams =\n                          HTTPCoroConnector::defaultConnectionParams(),\n                      HTTPCoroConnector::SessionParams sessionParams =\n                          HTTPCoroConnector::defaultSessionParams(),\n                      Observer* observer = nullptr);\n\n  HTTPCoroSessionPool(folly::EventBase* evb,\n                      const std::string& server,\n                      uint16_t port,\n                      std::shared_ptr<HTTPCoroSessionPool> proxyPool,\n                      PoolParams poolParams = defaultPoolParams(),\n                      HTTPCoroConnector::ConnectionParams connParams =\n                          HTTPCoroConnector::defaultConnectionParams(),\n                      HTTPCoroConnector::SessionParams sessionParams =\n                          HTTPCoroConnector::defaultSessionParams(),\n                      Observer* observer = nullptr);\n\n  HTTPCoroSessionPool(\n      folly::EventBase* evb,\n      const std::string& server,\n      uint16_t port,\n      PoolParams poolParams,\n      std::shared_ptr<const HTTPCoroConnector::QuicConnectionParams> connParams,\n      HTTPCoroConnector::SessionParams sessionParams =\n          HTTPCoroConnector::defaultSessionParams(),\n      bool allowNameLookup = false,\n      Observer* observer = nullptr);\n\n  HTTPCoroSessionPool(const HTTPCoroSessionPool& other) = delete;\n  HTTPCoroSessionPool& operator=(const HTTPCoroSessionPool& other) = delete;\n  HTTPCoroSessionPool(HTTPCoroSessionPool&& goner) = delete;\n  HTTPCoroSessionPool& operator=(HTTPCoroSessionPool&& goner) = delete;\n\n  virtual ~HTTPCoroSessionPool() {\n    if (!isDraining()) {\n      drain();\n    }\n    XCHECK_EQ(waiters_.size(), 0UL);\n    XCHECK_EQ(connectsInProgress_, 0UL);\n    XCHECK(idleSessions_.empty());\n    XCHECK(availableSessions_.empty());\n    XCHECK(fullSessions_.empty());\n  }\n\n  [[nodiscard]] bool isSecure() const {\n    return quicConnParams_ ||\n           tcpConnParams_.fizzContextAndVerifier.fizzContext ||\n           tcpConnParams_.sslContext;\n  }\n\n  void setMaxConnectionAttempts(uint8_t maxConnectionAttempts) {\n    poolParams_.maxConnectionAttempts =\n        std::max<uint8_t>(1u, maxConnectionAttempts);\n  }\n\n  void setPoolingEnabled(bool poolingEnabled) {\n    poolingEnabled_ = poolingEnabled;\n  }\n\n  void setMaxConnections(uint32_t maxConnections) {\n    // does not clear existing sessions\n    poolParams_.maxConnections = std::max(1u, maxConnections);\n    poolingEnabled_ = maxConnections > 0;\n  }\n\n  void setSessionParams(HTTPCoroConnector::SessionParams sessionParams) {\n    sessionParams_ = sessionParams;\n  }\n\n  void setMaxAge(std::chrono::seconds maxAge) {\n    poolParams_.maxAge = maxAge;\n  }\n\n  [[nodiscard]] bool full() const {\n    return idleSessions_.size() + availableSessions_.size() +\n               fullSessions_.size() + connectsInProgress_ >=\n           poolParams_.maxConnections;\n  }\n\n  [[nodiscard]] bool hasAvailableSessions() const {\n    return availableSessions_.size() + idleSessions_.size();\n  }\n\n  [[nodiscard]] bool hasIdleSessions() const {\n    return idleSessions_.size();\n  }\n\n  [[nodiscard]] bool overflowed() const {\n    return idleSessions_.size() + availableSessions_.size() +\n               fullSessions_.size() + connectsInProgress_ >\n           poolParams_.maxConnections;\n  }\n\n  [[nodiscard]] bool empty() const {\n    return !hasSession() && waiters_.empty() && connectsInProgress_ == 0;\n  }\n\n  class Exception : public std::runtime_error {\n   public:\n    enum class Type { ConnectFailed, Timeout, Cancelled, Draining, MaxWaiters };\n    explicit Exception(Type t,\n                       const std::string& msg,\n                       folly::exception_wrapper ex = folly::exception_wrapper())\n        : std::runtime_error(folly::to<std::string>(\n              msg, ex ? std::string(\": \" + ex.what()) : std::string())),\n          type(t),\n          connectException(std::move(ex)) {\n    }\n\n    Type type;\n    // Set when Type == ConnectFailed\n    folly::exception_wrapper connectException;\n  };\n\n  using GetSessionResult = HTTPSessionFactory::GetSessionResult;\n  virtual folly::coro::Task<GetSessionResult> getSessionWithReservation();\n\n  void drain();\n\n  void flush();\n\n  void describe(std::ostream& os) const;\n\n  [[nodiscard]] folly::EventBase* getEventBase() const {\n    return eventBase_;\n  }\n\n  void setConnParams(const HTTPCoroConnector::ConnectionParams& connParams) {\n    tcpConnParams_ = connParams;\n    setSslManager();\n  }\n\n  void setQuicConnectionParams(\n      std::shared_ptr<const HTTPCoroConnector::QuicConnectionParams>\n          quicConnParams) {\n    if (!quicConnParams_) {\n      return; // ignored for non-QUIC pools\n    }\n    quicConnParams_ = std::move(quicConnParams);\n  }\n\n  /**\n   * Observers are notified whenever an idle session is added & removed. The\n   * pool is passed in as an argument so callbacks can check if the pool has\n   * idle sessions via ::hasIdleSessions\n   */\n  class IdleSessionObserverIf {\n   public:\n    IdleSessionObserverIf() = default;\n    virtual ~IdleSessionObserverIf() = default;\n    virtual void onIdleSessionsChanged(\n        const HTTPCoroSessionPool& pool) noexcept = 0;\n  };\n\n protected:\n  void insertIdleSession(HTTPSessionContextPtr&& sess) noexcept;\n  HTTPSessionContextPtr detachIdleSession() noexcept;\n\n  void setIdleSessionObserver(IdleSessionObserverIf* obs) {\n    idleSessionObserver_ = obs;\n  }\n\n private:\n  SessionList& getSessionList(HTTPCoroSessionPool::Holder& holder);\n  void moveToFull(HTTPCoroSessionPool::Holder& holder);\n  void moveToAvailable(HTTPCoroSessionPool::Holder& holder);\n  void moveToIdle(HTTPCoroSessionPool::Holder& holder);\n  void addSession(HTTPCoroSessionPool::Holder& holder, Holder::State state);\n  void removeSession(HTTPCoroSessionPool::Holder& holder, bool checkForWaiters);\n  void notifyIdleSessionObserver() const noexcept;\n  bool shouldAgeOut(HTTPCoroSessionPool::Holder& holder) const;\n  folly::coro::Task<void> addNewConnection();\n  void signalWaiters(uint32_t n);\n  void cancelWaiters(Exception ex);\n  [[nodiscard]] bool hasSession() const;\n  void setSslManager();\n  [[nodiscard]] bool isDraining() const {\n    return cancellationSource_.isCancellationRequested();\n  }\n\n  folly::EventBase* eventBase_{nullptr};\n  Observer* observer_{nullptr};\n  folly::SocketAddress serverAddress_;\n  std::string authority_;\n  PoolParams poolParams_;\n  std::shared_ptr<HTTPCoroSessionPool> proxyPool_;\n  HTTPCoroConnector::ConnectionParams tcpConnParams_;\n  std::shared_ptr<const HTTPCoroConnector::QuicConnectionParams>\n      quicConnParams_;\n  HTTPCoroConnector::SessionParams sessionParams_;\n  SessionList idleSessions_;\n  SessionList availableSessions_;\n  SessionList fullSessions_;\n  folly::CancellationSource cancellationSource_;\n  uint64_t connectsInProgress_{0};\n  struct Waiter {\n    detail::CancellableBaton baton;\n    folly::CancellationSource cancellationSource;\n    folly::Optional<Exception> exception;\n    folly::SafeIntrusiveListHook listHook_;\n    void cancel(Exception ex);\n  };\n  using WaiterList = folly::CountedIntrusiveList<Waiter, &Waiter::listHook_>;\n  WaiterList waiters_;\n  class SslSessionManager : public HTTPCoroConnector::SslSessionManagerIf {\n    void onNewSslSession(SslSessionPtr session) noexcept override {\n      sslSession = std::move(session);\n    }\n    SslSessionPtr getSslSession() noexcept override {\n      return sslSession;\n    }\n\n   private:\n    std::shared_ptr<folly::ssl::SSLSession> sslSession{nullptr};\n  } sslSessionManager_;\n  bool poolingEnabled_{true};\n  IdleSessionObserverIf* idleSessionObserver_{nullptr};\n};\n\nstd::ostream& operator<<(std::ostream& os, const HTTPCoroSessionPool& pool);\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/HTTPSessionFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <utility>\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n\nnamespace proxygen::coro {\n\n/* Abstract class for making a session */\nclass HTTPSessionFactory {\n public:\n  virtual ~HTTPSessionFactory() = default;\n\n  struct GetSessionResult {\n    HTTPCoroSession::RequestReservation reservation;\n    HTTPCoroSession* session;\n    HTTPSessionContextPtr ctx;\n    GetSessionResult(HTTPCoroSession::RequestReservation res,\n                     HTTPCoroSession* sess)\n        : reservation(std::move(res)),\n          session(sess),\n          ctx(sess->acquireKeepAlive()) {\n    }\n    GetSessionResult(GetSessionResult&&) = default;\n    GetSessionResult& operator=(GetSessionResult&&) = default;\n  };\n\n  static constexpr std::chrono::seconds kDefaultConnectTimeout{2};\n\n  virtual folly::coro::Task<GetSessionResult> getSessionWithReservation(\n      std::string host,\n      uint16_t port,\n      bool isSecure,\n      std::chrono::milliseconds connectTimeout = kDefaultConnectTimeout,\n      folly::Optional<std::string> serverAddress = folly::none) = 0;\n\n  // Return true if the sessions returned by this factory require absolute URLs\n  // eg: they go via a forward proxy\n  [[nodiscard]] virtual bool requiresAbsoluteURLs() const {\n    return false;\n  }\n};\n\nclass SimpleHTTPSessionFactory : public HTTPSessionFactory {\n public:\n  SimpleHTTPSessionFactory() = delete;\n  explicit SimpleHTTPSessionFactory(folly::EventBase* evb) : evb_(evb) {\n  }\n  ~SimpleHTTPSessionFactory() override = default;\n\n  folly::coro::Task<GetSessionResult> getSessionWithReservation(\n      std::string host,\n      uint16_t port,\n      bool isSecure,\n      std::chrono::milliseconds connectTimeout,\n      folly::Optional<std::string> serverAddress = folly::none) override {\n    static constexpr std::chrono::seconds kDefaultStreamReadTimeout{5};\n    auto session =\n        co_await HTTPClient::getHTTPSession(evb_,\n                                            host,\n                                            port,\n                                            isSecure,\n                                            false,\n                                            connectTimeout,\n                                            kDefaultStreamReadTimeout,\n                                            \"\" /* clientCertPath */,\n                                            \"\" /* clientKeyPath */,\n                                            serverAddress);\n    auto reservation = session->reserveRequest();\n    if (reservation.hasException()) {\n      co_yield folly::coro::co_error(std::move(reservation.exception()));\n    }\n    co_return GetSessionResult(std::move(*reservation), session);\n  }\n\n private:\n  folly::EventBase* evb_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/main.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GFlags.h>\n\n#include <iostream>\n\nnamespace {\nfolly::coro::Task<void> fetchUrl(folly::EventBase* evb, std::string url) {\n  auto res = co_await co_awaitTry(proxygen::coro::HTTPClient::get(\n      evb, std::move(url), std::chrono::seconds(5)));\n  if (res.hasException()) {\n    std::cerr << res.exception().what() << std::endl;\n  } else {\n    std::cout << res->body.move()->moveToFbString() << std::endl;\n  }\n}\n} // namespace\n\nint main(int argc, char* argv[]) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  folly::EventBase evb;\n  if (argc < 2) {\n    std::cerr << \"usage: http_client <url>\" << std::endl;\n    return 1;\n  }\n  co_withExecutor(&evb, fetchUrl(&evb, argv[1])).start();\n  evb.loop();\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/samples/cocurl/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif (BUILD_SAMPLES)\n  add_executable(proxygen_cocurl CoCurl.cpp)\n\n  target_link_libraries(proxygen_cocurl PUBLIC proxygen)\n\n  target_compile_options(\n      proxygen_cocurl PRIVATE\n      ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n  )\n\n  install(\n      TARGETS proxygen_cocurl\n      EXPORT proxygen-exports\n      DESTINATION bin\n  )\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/samples/cocurl/CoCurl.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.h\"\n#include <fizz/protocol/DefaultCertificateVerifier.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/utils/URL.h>\n\n#include <folly/String.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GFlags.h>\n\n#include <cctype>\n#include <fstream>\n#include <iostream>\n#include <sstream>\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nDEFINE_bool(quic, false, \"Connect using QUIC/H3\");\nDEFINE_string(ca_file,\n              \"/etc/pki/tls/cert.pem\",\n              \"CA file for validating server certificates\");\nDEFINE_int32(timeout_ms, 3000, \"default connect/read timeout\");\nDEFINE_string(proxy, \"\", \"proxy address\");\nDEFINE_string(method, \"GET\", \"HTTP method\");\nDEFINE_string(post_body,\n              \"\",\n              \"path of file to send in the body of the POST request\");\nDEFINE_bool(tls_proxy, true, \"Use TLS to connect to proxy\");\nDEFINE_string(client_cert_path, \"\", \"client cert path\");\nDEFINE_string(client_key_path, \"\", \"client key path\");\nDEFINE_bool(fizz, true, \"proxy endpoint TLS use fizz\");\nDEFINE_bool(no_server_cert_verifier,\n            false,\n            \"Do not verify server identity presented (makes the client work \"\n            \"against self-signed certs)\");\n\nfolly::coro::Task<void> getViaProxy(folly::EventBase* evb,\n                                    const std::string& urlStr,\n                                    HTTPSourceReader reader);\n\nfolly::coro::Task<void> getWithCoro(folly::EventBase* evb,\n                                    const std::string& urlStr,\n                                    HTTPSourceReader reader);\n\nfolly::coro::Task<void> postWithCoro(folly::EventBase* evb,\n                                     const std::string& urlStr,\n                                     HTTPSourceReader reader);\n\nnamespace {\n// This is an insecure certificate verifier and is not meant to be\n// used in production. Using it in production would mean that this will\n// leave everyone insecure.\nclass InsecureVerifierDangerousDoNotUseInProduction\n    : public fizz::CertificateVerifier {\n public:\n  ~InsecureVerifierDangerousDoNotUseInProduction() override = default;\n\n  [[nodiscard]] std::shared_ptr<const folly::AsyncTransportCertificate> verify(\n      const std::vector<std::shared_ptr<const fizz::PeerCert>>& certs)\n      const override {\n    return certs.front();\n  }\n\n  [[nodiscard]] std::vector<fizz::Extension> getCertificateRequestExtensions()\n      const override {\n    return {};\n  }\n};\n\nconst HTTPClient::RequestHeaderMap& getDefaultHeaders() {\n  /*\n   * From RFC7231:\n   * A request without any Accept header field implies that the user agent will\n   * accept any media type in response.\n   *\n   * However this is explicitly sent due to misbehaving servers that do not\n   * conform to spec.\n   */\n  static HTTPClient::RequestHeaderMap headers{{\"accept\", \"*/*\"}};\n  return headers;\n}\n} // namespace\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  if (argc < 2) {\n    XLOG(ERR) << \"Usage: cocurl <url>\";\n    return 1;\n  }\n\n  folly::EventBase evb;\n  int ret = 0;\n  HTTPSourceReader reader;\n  reader\n      .onHeaders([](std::unique_ptr<HTTPMessage> response, bool, bool) {\n        std::cout << \"Received Headers:\" << std::endl;\n        response->describe(std::cout);\n        return HTTPSourceReader::Continue;\n      })\n      .onPushPromise(\n          [](std::unique_ptr<HTTPMessage> promise, HTTPSourceHolder, bool) {\n            std::cout << \"Received Push:\" << std::endl;\n            promise->describe(std::cout);\n            return HTTPSourceReader::Continue;\n          })\n      .onBody([](BufQueue body, bool) {\n        if (!body.empty()) {\n          std::cout << folly::StringPiece(body.move()->coalesce());\n        }\n        return HTTPSourceReader::Continue;\n      })\n      .onTrailers([](std::unique_ptr<HTTPHeaders> trailers) {\n        std::cout << \"Received Headers:\" << std::endl;\n        trailers->forEach([](const std::string& h, const std::string& v) {\n          std::cout << \" \" << stripCntrlChars(h) << \": \" << stripCntrlChars(v)\n                    << std::endl;\n        });\n      })\n      .onError([&ret](HTTPSourceReader::ErrorContext ec, HTTPError error) {\n        if (ec == HTTPSourceReader::ErrorContext::HEADERS) {\n          std::cerr << \"Error receiving response headers err=\"\n                    << error.describe() << std::endl;\n        } else {\n          std::cerr << \"Error receiving response body err=\" << error.describe()\n                    << std::endl;\n        }\n        ret = 1;\n      });\n\n  HTTPClient::setDefaultCAPaths({FLAGS_ca_file});\n\n  if (FLAGS_no_server_cert_verifier) {\n    auto verifier =\n        std::make_shared<InsecureVerifierDangerousDoNotUseInProduction>();\n    HTTPClient::setDefaultFizzCertVerifier(verifier);\n  }\n  std::string urlStr(argv[1]);\n\n  folly::SemiFuture<folly::Unit> getFuture;\n\n  std::string method = FLAGS_method;\n  folly::toLowerAscii(method);\n\n  if (!FLAGS_proxy.empty()) {\n    getFuture =\n        co_withExecutor(&evb, getViaProxy(&evb, urlStr, std::move(reader)))\n            .start();\n  } else if (method == \"post\") {\n    getFuture =\n        co_withExecutor(&evb, postWithCoro(&evb, urlStr, std::move(reader)))\n            .start();\n  } else {\n    getFuture =\n        co_withExecutor(&evb, getWithCoro(&evb, urlStr, std::move(reader)))\n            .start();\n  }\n\n  std::move(getFuture).via(&evb).thenTry([&ret](folly::Try<void> getResult) {\n    if (getResult.hasException()) {\n      std::cerr << \"Failed, err=\" << getResult.exception() << std::endl;\n      ret = 1;\n    }\n  });\n  evb.loop();\n  return ret;\n}\n\nfolly::coro::Task<void> getWithCoro(folly::EventBase* evb,\n                                    const std::string& urlStr,\n                                    HTTPSourceReader reader) {\n  proxygen::URL url(urlStr);\n  auto defaultTimeout = std::chrono::milliseconds(FLAGS_timeout_ms);\n  auto session =\n      co_await HTTPClient::getHTTPSession(evb,\n                                          url.getHost(),\n                                          /*port*/ url.getPort(),\n                                          /*isSecure*/ url.isSecure(),\n                                          /*useQuic*/ FLAGS_quic,\n                                          defaultTimeout,\n                                          defaultTimeout,\n                                          FLAGS_client_cert_path,\n                                          FLAGS_client_key_path);\n\n  XLOG(INFO) << \"Sending GET for \" << urlStr;\n  co_await HTTPClient::get(session,\n                           std::move(url),\n                           std::move(reader),\n                           defaultTimeout,\n                           getDefaultHeaders());\n\n  session->initiateDrain();\n}\n\nfolly::coro::Task<void> postWithCoro(folly::EventBase* evb,\n                                     const std::string& urlStr,\n                                     HTTPSourceReader reader) {\n  if (FLAGS_post_body.empty()) {\n    throw std::runtime_error(\"missing --post_body <file>\");\n  }\n\n  std::ifstream file(FLAGS_post_body);\n\n  if (!file) {\n    throw std::runtime_error(\"file could not be opened\");\n  }\n\n  std::ostringstream ss;\n  ss << file.rdbuf();\n\n  proxygen::URL url(urlStr);\n  auto defaultTimeout = std::chrono::milliseconds(FLAGS_timeout_ms);\n  auto session =\n      co_await HTTPClient::getHTTPSession(evb,\n                                          url.getHost(),\n                                          /*port*/ url.getPort(),\n                                          /*isSecure*/ url.isSecure(),\n                                          /*useQuic*/ FLAGS_quic,\n                                          defaultTimeout,\n                                          defaultTimeout,\n                                          FLAGS_client_cert_path,\n                                          FLAGS_client_key_path);\n\n  XLOG(INFO) << \"Sending POST for \" << urlStr;\n  co_await HTTPClient::post(session,\n                            std::move(url),\n                            ss.str(),\n                            std::move(reader),\n                            defaultTimeout,\n                            getDefaultHeaders());\n\n  session->initiateDrain();\n}\n\nfolly::coro::Task<void> getViaProxy(folly::EventBase* evb,\n                                    const std::string& urlStr,\n                                    HTTPSourceReader reader) {\n  folly::SocketAddress proxyAddr;\n  proxyAddr.setFromHostPort(FLAGS_proxy);\n  XLOG(INFO) << \"Establishing HTTP connection to proxy: \"\n             << proxyAddr.describe();\n  std::chrono::milliseconds timeout(FLAGS_timeout_ms);\n  auto proxySession =\n      co_await HTTPClient::getHTTPSession(evb,\n                                          proxyAddr.getAddressStr(),\n                                          proxyAddr.getPort(),\n                                          FLAGS_tls_proxy,\n                                          /*useQuic=*/false,\n                                          timeout,\n                                          timeout,\n                                          FLAGS_client_cert_path,\n                                          FLAGS_client_key_path);\n  XLOG(INFO) << \"Got HTTP connection to proxy: \" << proxyAddr.describe();\n\n  URL url(urlStr);\n  if (!url.isValid() || !url.hasHost()) {\n    throw std::runtime_error(folly::to<std::string>(\"Invalid URL: \", urlStr));\n  }\n  auto endpointSession = co_await HTTPClient::getHTTPSessionViaProxy(\n      proxySession,\n      url.getHost(),\n      url.getPort(),\n      /*connectUnique=*/true,\n      url.isSecure() ? (FLAGS_fizz ? HTTPClient::SecureTransportImpl::FIZZ\n                                   : HTTPClient::SecureTransportImpl::TLS)\n                     : HTTPClient::SecureTransportImpl::NONE,\n      timeout,\n      timeout,\n      /*clientCertPath=*/\"\",\n      /*clientKeyPath=*/\"\");\n\n  XLOG(INFO) << \"Sending GET for \" << urlStr;\n  co_await HTTPClient::get(endpointSession,\n                           std::move(url),\n                           std::move(reader),\n                           timeout,\n                           getDefaultHeaders());\n  endpointSession->initiateDrain();\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/CoroDNSResolverTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/CoroDNSResolver.h\"\n\n#include <folly/coro/BlockingWait.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GTest.h>\n\n#include \"proxygen/lib/dns/DNSResolver.h\"\n#include \"proxygen/lib/dns/test/MockDNSResolver.h\"\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\nusing namespace std::chrono;\n\nnamespace proxygen::coro::test {\n\nclass CoroDNSResolverTest : public Test {\n protected:\n  folly::EventBase* evb_ = folly::EventBaseManager::get()->getEventBase();\n\n  // Create a mock resolver object, inject it into CoroDNSResolver, and expect a\n  // call to its resolveHostname method with an action\n  void expectResolveHostname(\n      std::function<void(DNSResolver::ResolutionCallback*)> action) {\n    auto* mockResolver = new MockDNSResolver();\n\n    EXPECT_CALL(*mockResolver, resolveHostname(_, _, _, _, _))\n        .WillOnce(\n            ::testing::Invoke([&](DNSResolver::ResolutionCallback* cb,\n                                  const std::string&,\n                                  std::chrono::milliseconds,\n                                  sa_family_t,\n                                  const TraceEventContext&) { action(cb); }));\n\n    coro::CoroDNSResolver::resetDNSResolverInstance(\n        evb_, proxygen::DNSResolver::UniquePtr(mockResolver));\n  }\n};\n\nTEST_F(CoroDNSResolverTest, testSuccess) {\n  co_withExecutor(\n      evb_,\n      [this](folly::EventBase* evb) -> folly::coro::Task<void> {\n        expectResolveHostname([](DNSResolver::ResolutionCallback* cb) {\n          cb->resolutionSuccess({\n              DNSResolver::Answer(\n                  std::chrono::seconds(10),\n                  folly::SocketAddress(\n                      \"2a03:2880:f134:0183:face:b00c:0000:25de\", 0)),\n              DNSResolver::Answer(std::chrono::seconds(10),\n                                  folly::SocketAddress(\"31.13.93.35\", 0)),\n          });\n        });\n\n        auto addresses = co_await co_awaitTry(\n            CoroDNSResolver::resolveHost(evb, \"www.facebook.com\", seconds(1)));\n        EXPECT_FALSE(addresses.hasException());\n        EXPECT_NE(addresses->primary, folly::SocketAddress());\n        EXPECT_TRUE(addresses->fallback);\n        EXPECT_NE(addresses->fallback, folly::SocketAddress());\n        EXPECT_NE(addresses->primary.getFamily(),\n                  addresses->fallback->getFamily());\n        EXPECT_EQ(addresses->primary.getFamily(), AF_INET6);\n      }(evb_))\n      .start();\n}\n\nTEST_F(CoroDNSResolverTest, testNoNames) {\n  co_withExecutor(\n      evb_,\n      [this](folly::EventBase* evb) -> folly::coro::Task<void> {\n        expectResolveHostname([](DNSResolver::ResolutionCallback* cb) {\n          folly::exception_wrapper ew =\n              folly::make_exception_wrapper<DNSResolver::Exception>(\n                  DNSResolver::NODATA, \"err\");\n          cb->resolutionError(ew);\n        });\n\n        auto addresses = co_await co_awaitTry(CoroDNSResolver::resolveHost(\n            evb, \"ktufictvvthhuvlbh.com\", seconds(1)));\n        EXPECT_TRUE(addresses.hasException());\n        EXPECT_EQ(\n            addresses.tryGetExceptionObject<DNSResolver::Exception>()->status(),\n            DNSResolver::NODATA);\n      }(evb_))\n      .start();\n}\n\nTEST_F(CoroDNSResolverTest, testMultipleReturnedDomains) {\n  co_withExecutor(\n      evb_,\n      [this](folly::EventBase* evb) -> folly::coro::Task<void> {\n        expectResolveHostname([](DNSResolver::ResolutionCallback* cb) {\n          cb->resolutionSuccess({\n              DNSResolver::Answer(\n                  std::chrono::seconds(1),\n                  folly::SocketAddress(\n                      \"2a03:2880:f134:0183:face:b00c:0000:25de\", 0)),\n              DNSResolver::Answer(std::chrono::seconds(1),\n                                  folly::SocketAddress(\"31.13.93.35\", 0)),\n          });\n        });\n\n        auto addresses = co_await co_awaitTry(\n            CoroDNSResolver::resolveHost(evb, \"www.facebook.com\", seconds(1)));\n        EXPECT_FALSE(addresses.hasException());\n        auto address = addresses.value();\n        EXPECT_TRUE(address.fallback);\n      }(evb_))\n      .start();\n}\n\nTEST_F(CoroDNSResolverTest, testAllReturnedDomains) {\n  co_withExecutor(\n      evb_,\n      [this](folly::EventBase* evb) -> folly::coro::Task<void> {\n        expectResolveHostname([](DNSResolver::ResolutionCallback* cb) {\n          cb->resolutionSuccess({\n              DNSResolver::Answer(\n                  std::chrono::seconds(1),\n                  folly::SocketAddress(\n                      \"2a03:2880:f134:0183:face:b00c:0000:25de\", 0)),\n              DNSResolver::Answer(std::chrono::seconds(1),\n                                  folly::SocketAddress(\"31.13.93.35\", 0)),\n          });\n        });\n\n        auto addresses = co_await co_awaitTry(CoroDNSResolver::resolveHostAll(\n            evb, \"www.facebook.com\", seconds(1)));\n        EXPECT_FALSE(addresses.hasException());\n        EXPECT_GT(addresses.value().size(), 1);\n      }(evb_))\n      .start();\n}\n\nTEST_F(CoroDNSResolverTest, UserSuppliedDnsResolver) {\n  // user supplying custom DNSResolver should never call into the global\n  // DNSResolver\n  auto* globalDnsResolver = new MockDNSResolver();\n  coro::CoroDNSResolver::resetDNSResolverInstance(\n      evb_, DNSResolver::UniquePtr(globalDnsResolver));\n  EXPECT_CALL(*globalDnsResolver,\n              resolveHostname(_, \"www.facebook.com\", _, _, _))\n      .Times(0);\n\n  MockDNSResolver resolver;\n  EXPECT_CALL(resolver, resolveHostname(_, \"www.facebook.com\", _, _, _))\n      .WillOnce(WithArgs<0>(Invoke([](DNSResolver::ResolutionCallback* cb) {\n        cb->resolutionSuccess({DNSResolver::Answer{\n            std::chrono::seconds(0), folly::SocketAddress(\"0.0.0.0\", 0)}});\n      })));\n\n  folly::coro::blockingWait(\n      CoroDNSResolver::resolveHost(\n          evb_, \"www.facebook.com\", std::chrono::milliseconds(100), &resolver),\n      evb_);\n\n  coro::CoroDNSResolver::resetDNSResolverInstance(evb_, nullptr);\n}\n\nTEST(SingletonTest, DnsSingletonShutdown) {\n  // request global IOExecutor backed by singleton (order matters, this\n  // construction must occur first)\n  auto* evb = folly::getGlobalIOExecutor()->getEventBase();\n  auto keepalive = folly::Executor::getKeepAliveToken(evb);\n\n  folly::via(evb).then([evb, keepalive](auto&&) {\n    // coroutine to invoke CoroDNSResolver::resolveHost(...)\n    auto t =\n        folly::coro::co_invoke([evb, keepalive]() -> folly::coro::Task<void> {\n          // DNSModule singleton will be requested here\n          auto res =\n              co_await folly::coro::co_awaitTry(CoroDNSResolver::resolveHost(\n                  evb, \"www.facebook.com\", std::chrono::seconds(1)));\n          // should return an exception on shutdown\n          EXPECT_TRUE(res.hasException());\n          co_return;\n        });\n\n    /**\n     * run dns resolution after 100ms, gives enough time for shutdown to\n     * request destruction of CoroDNSResolver singleton prior to executor/evb\n     * (reverse construction order)\n     */\n    evb->runAfterDelay(\n        [t = std::move(t), evb, keepalive]() mutable {\n          co_withExecutor(evb, std::move(t)).start();\n        },\n        100);\n  });\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/HTTPClientTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/client/CertReloadSessionPool.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClientConnectionCache.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/test/HTTPTestSources.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <folly/logging/xlog.h>\n#include <quic/client/QuicClientTransport.h>\n\n#include <folly/SocketAddress.h>\n#include <folly/coro/AsyncScope.h>\n#include <folly/coro/Collect.h>\n#include <folly/coro/Timeout.h>\n#include <folly/coro/ViaIfAsync.h>\n#include <quic/api/test/Mocks.h>\n#include <quic/state/test/Mocks.h>\n#include <variant>\n#include <wangle/ssl/test/MockSSLStats.h>\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\nusing namespace std::chrono;\nusing folly::coro::blockingWait;\n\nnamespace {\nusing proxygen::coro::test::TransportType;\n\nHTTPClient::SecureTransportImpl transportImpl(TransportType transportType) {\n  switch (transportType) {\n    case TransportType::TCP:\n      return HTTPClient::SecureTransportImpl::NONE;\n    case TransportType::TLS:\n      return HTTPClient::SecureTransportImpl::TLS;\n    case TransportType::TLS_FIZZ:\n      return HTTPClient::SecureTransportImpl::FIZZ;\n    default:\n      XLOG(FATAL) << \"Don't call this for QUIC\";\n  }\n}\n\nusing ConnParams = std::variant<HTTPCoroConnector::ConnectionParams,\n                                HTTPCoroConnector::QuicConnectionParams>;\n\nConnParams getConnParams(TransportType transportType) {\n  if (transportType == TransportType::QUIC) {\n    return HTTPClient::getQuicConnParams();\n  } else {\n    return HTTPClient::getConnParams(transportImpl(transportType));\n  }\n}\n\nstd::shared_ptr<const HTTPCoroConnector::QuicConnectionParams>\ngetQuicConnParams(const ConnParams& connParams) {\n  return std::make_shared<const HTTPCoroConnector::QuicConnectionParams>(\n      std::get<HTTPCoroConnector::QuicConnectionParams>(connParams));\n}\n\nfolly::coro::Task<HTTPCoroSession*> HTTPCoroConnector_connect(\n    folly::EventBase* evb,\n    folly::SocketAddress addr,\n    std::chrono::milliseconds timeout,\n    ConnParams connParams,\n    HTTPCoroConnector::SessionParams sessParams =\n        HTTPClient::getSessionParams()) {\n  if (connParams.index() == 1) {\n    return HTTPCoroConnector::connect(\n        evb,\n        addr,\n        timeout,\n        std::get<HTTPCoroConnector::QuicConnectionParams>(connParams),\n        sessParams);\n  } else {\n    return HTTPCoroConnector::connect(\n        evb,\n        addr,\n        timeout,\n        std::get<HTTPCoroConnector::ConnectionParams>(connParams),\n        sessParams);\n  }\n}\n\nstd::string transportTypeToAlpn(TransportType ttype) {\n  switch (ttype) {\n    case TransportType::TLS:\n    case TransportType::TLS_FIZZ:\n      return \"h2\";\n    case TransportType::TCP:\n      return \"\";\n    case TransportType::QUIC:\n      return \"h3\";\n  }\n}\n\n/**\n * Unfortunately this class is needed for timeout test. This class never\n * registers the (WRITE) socket connect event which makes it appear as if\n * server syn-ack never arrives and subsequently connection timeout will fire.\n */\nclass EventBaseBackend : public folly::EventBaseBackendBase {\n public:\n  EventBaseBackend() {\n    evb_ = event_base_new();\n  }\n  ~EventBaseBackend() override {\n    event_base_free(evb_);\n  }\n\n  event_base* getEventBase() override {\n    return evb_;\n  }\n\n  int eb_event_base_loop(int flags) override {\n    return event_base_loop(evb_, flags);\n  }\n  int eb_event_base_loopbreak() override {\n    return event_base_loopbreak(evb_);\n  }\n\n  int eb_event_add(Event& event, const struct timeval* timeout) override {\n    // never register connect event\n    if (event.getEvent()->ev_events & (EV_WRITE)) {\n      return 0;\n    }\n    return event_add(event.getEvent(), timeout);\n  }\n  int eb_event_del(EventBaseBackendBase::Event& event) override {\n    return event_del(event.getEvent());\n  }\n\n  bool eb_event_active(Event& event, int res) override {\n    event_active(event.getEvent(), res, 1);\n    return true;\n  }\n\n private:\n  event_base* evb_;\n};\n\n} // namespace\n\nnamespace proxygen::coro::test {\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnect) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  sessParams.settings.emplace_back(SettingsId::HEADER_TABLE_SIZE, 8192);\n  sessParams.maxConcurrentOutgoingStreams = 10;\n  sessParams.connFlowControl = 128000;\n  sessParams.streamReadTimeout = std::chrono::seconds(5);\n  sessParams.connReadTimeout = std::chrono::seconds(3);\n  sessParams.writeTimeout = std::chrono::seconds(1);\n  auto sess =\n      co_await co_awaitTry(HTTPCoroConnector_connect(&evb_,\n                                                     serverAddress_,\n                                                     seconds(1),\n                                                     getConnParams(GetParam()),\n                                                     sessParams));\n  EXPECT_FALSE(sess.hasException());\n  const auto& tinfo = (*sess)->getSetupTransportInfo();\n  if (tinfo.appProtocol) {\n    EXPECT_EQ(*tinfo.appProtocol, transportTypeToAlpn(GetParam()));\n  }\n  (*sess)->dropConnection();\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectWithCustomTimeout) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  auto sessParams = HTTPClient::getSessionParams(std::chrono::seconds(3));\n  auto sess =\n      co_await co_awaitTry(HTTPCoroConnector_connect(&evb_,\n                                                     serverAddress_,\n                                                     seconds(1),\n                                                     getConnParams(GetParam()),\n                                                     sessParams));\n  EXPECT_FALSE(sess.hasException());\n  const auto& tinfo = (*sess)->getSetupTransportInfo();\n  if (tinfo.appProtocol) {\n    EXPECT_EQ(*tinfo.appProtocol, transportTypeToAlpn(GetParam()));\n  }\n  (*sess)->dropConnection();\n}\n\nTEST_P(HTTPClientTests, ConnectCancel) {\n  folly::CancellationSource source;\n  co_withExecutor(\n      &evb_,\n      folly::coro::co_withCancellation(\n          source.getToken(), ([this]() -> folly::coro::Task<void> {\n            auto res = co_await co_awaitTry(HTTPCoroConnector_connect(\n                &evb_, serverAddress_, seconds(1), getConnParams(GetParam())));\n            EXPECT_TRUE(res.hasException());\n            co_return;\n          })()))\n      .start();\n  evb_.loopOnce();\n  source.requestCancellation();\n  evb_.loop();\n}\n\nTEST_P(HTTPClientTests, CancelledConnection) {\n  HTTPCoroConnector::SessionParams sessParams;\n  auto observer = std::make_shared<LifecycleObserver>();\n  sessParams.lifecycleObserver = observer.get();\n  observer.reset();\n  folly::CancellationSource source;\n  co_withExecutor(\n      &evb_,\n      folly::coro::co_withCancellation(\n          source.getToken(),\n          ([this](auto sessionParams) -> folly::coro::Task<void> {\n            auto res = co_await co_awaitTry(\n                HTTPCoroConnector_connect(&evb_,\n                                          serverAddress_,\n                                          seconds(1),\n                                          getConnParams(GetParam()),\n                                          sessionParams));\n            EXPECT_TRUE(res.hasException());\n            if (GetParam() == TransportType::QUIC) {\n              std::string errMsg =\n                  \"quic::QuicInternalException: Connection cancelled\";\n              EXPECT_EQ(errMsg, res.exception().what().toStdString());\n            }\n            co_return;\n          })(sessParams)))\n      .start();\n  evb_.loopOnce();\n  source.requestCancellation();\n  evb_.loop();\n}\n\nCO_TEST_P_X(HTTPClientTests, MigrateSessionEvb) {\n  auto maybeSess = co_await co_awaitTry(\n      HTTPCoroConnector_connect(&evb_,\n                                serverAddress_,\n                                seconds(1),\n                                getConnParams(GetParam()),\n                                HTTPCoroConnector::SessionParams{}));\n\n  // round robin 100 requests on a single HTTPCoroSession between 10 evbs\n  std::array<folly::ScopedEventBaseThread, 10> scopedEvbs{};\n\n  // validate we've connected successfully\n  CHECK(!maybeSess.hasException());\n  auto* sess = maybeSess.value();\n\n  // we require a few evb loops until we reach the detachable suspension points\n  while (!sess->isDetachable()) {\n    co_await folly::coro::co_reschedule_on_current_executor;\n  }\n\n  auto ka = sess->acquireKeepAlive();\n  sess->detachEvb();\n  folly::EventBase* evb{nullptr};\n\n  for (uint8_t numRequests = 0; numRequests < 100; numRequests++) {\n    evb = scopedEvbs[numRequests % scopedEvbs.size()].getEventBase();\n\n    co_await co_withExecutor(\n        evb, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {\n          // attach evb must be invoked from current evb\n          sess->attachEvb(evb);\n\n          auto res = co_await co_awaitTry(\n              sess->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n          CHECK(!res.hasException());\n\n          HTTPSourceReader reader(std::move(res).value());\n          reader.onHeaders(\n              [](std::unique_ptr<HTTPMessage> headers, bool, bool eom) {\n                EXPECT_EQ(headers->getStatusCode(), 200);\n                EXPECT_EQ(headers->getHeaders().getSingleOrEmpty(\"x-method\"),\n                          \"GET\");\n                return HTTPSourceReader::Cancel;\n              });\n          auto readRes = co_await folly::coro::co_awaitTry(reader.read());\n          EXPECT_FALSE(readRes.hasException());\n\n          XCHECK(sess->isDetachable());\n          sess->detachEvb();\n        }));\n  }\n\n  sess->attachEvb(&evb_);\n  struct DestroyLifecycleObs : public LifecycleObserver {\n    DestroyLifecycleObs() = default;\n    ~DestroyLifecycleObs() override = default;\n    void onDestroy(const HTTPCoroSession&) override {\n      waitUntilDestroy.post();\n    }\n    folly::coro::Baton waitUntilDestroy;\n  } obs;\n\n  ka.reset();\n  sess->addLifecycleObserver(&obs);\n  sess->closeWhenIdle();\n  co_await obs.waitUntilDestroy;\n}\n\nusing HTTPClientTLSOnlyTests = HTTPClientTests;\nCO_TEST_P_X(HTTPClientTLSOnlyTests, sslSessionCallbacks) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  class SslSessionManager : public HTTPCoroConnector::SslSessionManagerIf {\n   public:\n    void onNewSslSession(SslSessionPtr) noexcept override {\n    }\n    SslSessionPtr getSslSession() noexcept override {\n      return (sslSessionRequested = true, nullptr);\n    }\n    bool sslSessionRequested{false};\n  } sslSessionManager;\n\n  connParams.sslContext = std::make_shared<folly::SSLContext>();\n  connParams.sslSessionManager = &sslSessionManager;\n  HTTPCoroConnector::SessionParams sessParams;\n  sessParams.settings.emplace_back(SettingsId::HEADER_TABLE_SIZE, 8192);\n  sessParams.maxConcurrentOutgoingStreams = 10;\n  sessParams.connFlowControl = 128000;\n  sessParams.streamReadTimeout = std::chrono::seconds(5);\n  sessParams.connReadTimeout = std::chrono::seconds(3);\n  sessParams.writeTimeout = std::chrono::seconds(1);\n  auto sess = co_await co_awaitTry(HTTPCoroConnector_connect(\n      &evb_, serverAddress_, seconds(1), connParams, sessParams));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->dropConnection();\n\n  EXPECT_EQ(sslSessionManager.sslSessionRequested, true);\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectError) {\n  auto sess = co_await co_awaitTry(\n      HTTPCoroConnector_connect(&evb_,\n                                folly::SocketAddress(\"169.254.1.1\", 443),\n                                milliseconds(1),\n                                getConnParams(GetParam())));\n  CHECK(sess.hasException());\n  if (useQuic()) {\n    auto ex = sess.tryGetExceptionObject<quic::QuicInternalException>();\n    EXPECT_TRUE(ex->errorCode() == quic::LocalErrorCode::CONNECT_FAILED ||\n                ex->errorCode() == quic::LocalErrorCode::CONNECTION_ABANDONED);\n  } else {\n    auto ex = sess.tryGetExceptionObject<AsyncSocketException>();\n    EXPECT_TRUE(ex->getType() == AsyncSocketException::NOT_OPEN ||\n                ex->getType() == AsyncSocketException::TIMED_OUT);\n  }\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectTimeout) {\n  /**\n   * a bit hacky but:\n   * - create UDP server socket that does not read anything (i.e. Quic will\n   *   timeout)\n   *\n   * - create TCP server socket that doesn't accept any connections (i.e. don't\n   *   process handshake for fizz/tls conns)\n   *\n   * - create evb backed by fake EventBaseBackend that drops connect write event\n   */\n  std::unique_ptr<folly::AsyncUDPServerSocket> serverUdpSocket{nullptr};\n  folly::AsyncServerSocket::UniquePtr serverTcpSocket{nullptr};\n  if (useQuic()) {\n    evb_.runImmediatelyOrRunInEventBaseThread([&]() {\n      serverUdpSocket = std::make_unique<folly::AsyncUDPServerSocket>(&evb_);\n      serverUdpSocket->bind(folly::SocketAddress(\"127.0.0.1\", 0));\n    });\n  } else {\n    evb_.runImmediatelyOrRunInEventBaseThread([&]() {\n      serverTcpSocket.reset(new folly::AsyncServerSocket(&evb_));\n      serverTcpSocket->bind(folly::SocketAddress(\"127.0.0.1\", 0));\n      serverTcpSocket->listen(0);\n      serverTcpSocket->setMaxAcceptAtOnce(0);\n    });\n  }\n\n  // fake EventBaseBackend to drop connect event\n  folly::ScopedEventBaseThread scopedEvb(\n      folly::EventBase::Options().setBackendFactory(\n          []() { return std::make_unique<EventBaseBackend>(); }),\n      nullptr,\n      \"\");\n  auto& connectEvb = *scopedEvb.getEventBase();\n\n  // attempt to connect to server; expect timeout\n  folly::SocketAddress serverAddr;\n  serverUdpSocket ? serverUdpSocket->getAddress(&serverAddr)\n                  : serverTcpSocket->getAddress(&serverAddr);\n\n  auto sess =\n      co_withExecutor(&connectEvb,\n                      HTTPCoroConnector_connect(&connectEvb,\n                                                serverAddr,\n                                                milliseconds(1),\n                                                getConnParams(GetParam())))\n          .start()\n          .getTry();\n\n  CHECK(sess.hasException());\n  expectException(GetParam(), sess, ExceptionType::TIMED_OUT);\n\n  co_return;\n}\n\nusing HTTPClientTLSTests = HTTPClientTests;\nCO_TEST_P_X(HTTPClientTLSTests, ConnectorConnectTLSError) {\n  HTTPClient::setDefaultCAPaths({\"/etc/pki/tls/cert.pem\"});\n  HTTPClient::setDefaultFizzCertVerifier(nullptr);\n  auto sess = co_await co_awaitTry(HTTPCoroConnector_connect(\n      &evb_, serverAddress_, seconds(1), getConnParams(GetParam())));\n  EXPECT_TRUE(sess.hasException());\n  expectException(GetParam(), sess, ExceptionType::SSL_ERROR);\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectHappyEyeballs) {\n  if (GetParam() == TransportType::QUIC) {\n    // Not supported yet\n    co_return;\n  }\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  // This addr will timeout\n  folly::SocketAddress badAddr(\"169.254.0.1\", serverAddress_.getPort());\n  auto sess = co_await co_awaitTry(HTTPCoroConnector::happyEyeballsConnect(\n      &evb_, badAddr, serverAddress_, seconds(1), connParams));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->dropConnection();\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectHappyEyeballsDoubleSuccess) {\n  if (GetParam() == TransportType::QUIC) {\n    // Not supported yet\n    co_return;\n  }\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  auto sessParams = HTTPCoroConnector::defaultSessionParams();\n  // Set primary and secondary to the same address and a very short delay\n  auto sess = co_await co_awaitTry(\n      HTTPCoroConnector::happyEyeballsConnect(&evb_,\n                                              serverAddress_,\n                                              serverAddress_,\n                                              seconds(1),\n                                              connParams,\n                                              sessParams,\n                                              std::chrono::milliseconds(0)));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->dropConnection();\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectHappyEyeballsFail) {\n  if (GetParam() == TransportType::QUIC) {\n    // Not supported yet\n    co_return;\n  }\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  folly::SocketAddress timeoutAddr(\"169.254.0.1\", serverAddress_.getPort());\n  folly::SocketAddress failAddr(\"127.0.0.1\", 0);\n  auto sess = co_await co_awaitTry(HTTPCoroConnector::happyEyeballsConnect(\n      &evb_, timeoutAddr, failAddr, seconds(1), connParams));\n  EXPECT_TRUE(sess.hasException());\n  auto asyncSockEx = sess.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_NE(asyncSockEx, nullptr);\n  // returns second exception\n  EXPECT_TRUE(asyncSockEx->getType() ==\n                  folly::AsyncSocketException::TIMED_OUT ||\n              asyncSockEx->getType() == folly::AsyncSocketException::NOT_OPEN ||\n              asyncSockEx->getErrno() == EHOSTUNREACH);\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectHappyEyeballsCancelFallback) {\n  if (GetParam() == TransportType::QUIC) {\n    // Not supported yet\n    co_return;\n  }\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  class SslSessionManager : public HTTPCoroConnector::SslSessionManagerIf {\n   public:\n    void onNewSslSession(SslSessionPtr) noexcept override {\n    }\n    SslSessionPtr getSslSession() noexcept override {\n      return (getSslSessionCount++, nullptr);\n    }\n    uint8_t getSslSessionCount{0};\n  } sslSessionManager;\n\n  connParams.sslSessionManager = &sslSessionManager;\n  auto sess = co_await co_awaitTry(HTTPCoroConnector::happyEyeballsConnect(\n      &evb_, serverAddress_, serverAddress_, seconds(1), connParams));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->dropConnection();\n  co_await folly::coro::sleep(HTTPCoroConnector::kHappyEyeballsDelay * 2);\n  // This only increments in the SSL test, it should attempt at most one conn\n  EXPECT_LE(sslSessionManager.getSslSessionCount, 1);\n}\n\nCO_TEST_P_X(HTTPClientTests, ConnectorConnectHappyEyeballsFastFail) {\n  if (GetParam() == TransportType::QUIC) {\n    // Not supported yet\n    co_return;\n  }\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  folly::SocketAddress badAddr(\"127.0.0.1\", 0);\n  auto sess = co_await co_awaitTry(folly::coro::timeout(\n      HTTPCoroConnector::happyEyeballsConnect(\n          &evb_, badAddr, serverAddress_, seconds(1), connParams),\n      std::chrono::milliseconds(HTTPCoroConnector::kHappyEyeballsDelay)));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->dropConnection();\n}\n\nCO_TEST_P_X(HTTPClientTests, Get) {\n  auto resp = co_await co_awaitTry(HTTPClient::get(\n      &evb_, getURL(\"/\"), seconds(1), useQuic(), {{\"custom\", \"header\"}}));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  auto& headers = resp->headers->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-method\"), \"GET\");\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-host\").rfind(\"127.0.0.1:\", 0), 0);\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-custom-header-count\"), \"1\");\n  EXPECT_EQ(resp->body.chainLength(), 0);\n}\n\nCO_TEST_P_X(HTTPClientTests, SetHostHeader) {\n  auto resp = co_await co_awaitTry(HTTPClient::get(\n      &evb_, getURL(\"/\"), seconds(1), useQuic(), {{\"host\", \"foo.bar\"}}));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  auto& headers = resp->headers->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-method\"), \"GET\");\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-host\"), \"foo.bar\");\n  EXPECT_EQ(resp->body.chainLength(), 0);\n}\n\nCO_TEST_P_X(HTTPClientTests, GetWithSessionAndReservation) {\n  std::unique_ptr<HTTPCoroSessionPool> pool;\n  if (GetParam() == TransportType::QUIC) {\n    auto qconnParams = getQuicConnParams(getConnParams(GetParam()));\n    pool =\n        std::make_unique<HTTPCoroSessionPool>(&evb_,\n                                              serverAddress_.getAddressStr(),\n                                              serverAddress_.getPort(),\n                                              HTTPCoroSessionPool::PoolParams(),\n                                              std::move(qconnParams));\n  } else {\n    auto connParams = std::get<HTTPCoroConnector::ConnectionParams>(\n        getConnParams(GetParam()));\n    pool =\n        std::make_unique<HTTPCoroSessionPool>(&evb_,\n                                              serverAddress_.getAddressStr(),\n                                              serverAddress_.getPort(),\n                                              HTTPCoroSessionPool::PoolParams(),\n                                              connParams);\n  }\n  auto res = co_await co_awaitTry(pool->getSessionWithReservation());\n  EXPECT_FALSE(res.hasException());\n  auto resp = co_await co_awaitTry(HTTPClient::get(res->session,\n                                                   std::move(res->reservation),\n                                                   proxygen::URL(getURL(\"/\")),\n                                                   seconds(1),\n                                                   {{\"custom\", \"header\"}}));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  auto& headers = resp->headers->getHeaders();\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-method\"), \"GET\");\n  EXPECT_EQ(headers.getSingleOrEmpty(\"x-custom-header-count\"), \"1\");\n  EXPECT_EQ(resp->body.chainLength(), 0);\n}\n\nCO_TEST_P_X(HTTPClientTests, SessionPoolCancellationTest) {\n  std::unique_ptr<HTTPCoroSessionPool> pool;\n  if (GetParam() == TransportType::QUIC) {\n    auto qconnParams = getQuicConnParams(getConnParams(GetParam()));\n    pool =\n        std::make_unique<HTTPCoroSessionPool>(&evb_,\n                                              serverAddress_.getAddressStr(),\n                                              serverAddress_.getPort(),\n                                              HTTPCoroSessionPool::PoolParams(),\n                                              std::move(qconnParams));\n  } else {\n    auto connParams = std::get<HTTPCoroConnector::ConnectionParams>(\n        getConnParams(GetParam()));\n    pool =\n        std::make_unique<HTTPCoroSessionPool>(&evb_,\n                                              serverAddress_.getAddressStr(),\n                                              serverAddress_.getPort(),\n                                              HTTPCoroSessionPool::PoolParams(),\n                                              connParams);\n  }\n  auto resTask =\n      co_withExecutor(&evb_, pool->getSessionWithReservation()).start();\n  co_await folly::coro::co_reschedule_on_current_executor;\n  pool.reset();\n  auto res = co_await folly::coro::co_awaitTry(std::move(resTask));\n  EXPECT_TRUE(res.hasException());\n}\n\nCO_TEST_P_X(HTTPClientTests, GetTrailers) {\n  HTTPSourceReader reader;\n  reader.onTrailers([](std::unique_ptr<HTTPHeaders> trailers) {\n    EXPECT_EQ(trailers->getSingleOrEmpty(\"Test\"), \"Success\");\n  });\n  auto resp = co_await co_awaitTry(HTTPClient::get(\n      &evb_, getURL(\"/trailers\"), seconds(1), std::move(reader), useQuic()));\n  EXPECT_FALSE(resp.hasException());\n}\n\nCO_TEST_P_X(HTTPClientTests, Post) {\n  auto resp = co_await co_awaitTry(HTTPClient::post(\n      &evb_, getURL(\"/\"), std::string(100, 'a'), seconds(1), useQuic()));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n}\n\nCO_TEST_P_X(HTTPClientTests, PostEarlyReturn) {\n  auto resp = co_await co_awaitTry(HTTPClient::post(&evb_,\n                                                    getURL(\"/earlyreturn\"),\n                                                    std::string(1000000, 'a'),\n                                                    seconds(1),\n                                                    useQuic()));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n}\n\nCO_TEST_P_X(HTTPClientTests, PostCancel) {\n  folly::coro::CancellableAsyncScope scope;\n  URL url(getURL(\"/echo\"));\n  auto sess = co_await co_awaitTry(HTTPClient::getHTTPSession(&evb_,\n                                                              url.getHost(),\n                                                              url.getPort(),\n                                                              url.isSecure(),\n                                                              useQuic(),\n                                                              seconds(1),\n                                                              seconds(1)));\n  // Cancel POST request before entire body has been sent\n  HTTPSourceReader reader;\n  reader.onError([](HTTPSourceReader::ErrorContext, const HTTPError&) {});\n  scope.add(co_withExecutor(&evb_,\n                            HTTPClient::post(*sess,\n                                             url,\n                                             std::string(1000000, 'a'),\n                                             std::move(reader),\n                                             seconds(1))));\n  co_await folly::coro::co_reschedule_on_current_executor;\n  co_await scope.cancelAndJoinAsync();\n}\n\nCO_TEST_P_X(HTTPClientTests, Connect) {\n  URL url(getURL(\"/\"));\n  auto timeout = std::chrono::milliseconds(3000);\n\n  auto sess = co_await co_awaitTry(HTTPClient::getHTTPSession(&evb_,\n                                                              url.getHost(),\n                                                              url.getPort(),\n                                                              url.isSecure(),\n                                                              useQuic(),\n                                                              timeout,\n                                                              timeout));\n  EXPECT_FALSE(sess.hasException());\n\n  auto sessViaProxy = co_await co_awaitTry(\n      HTTPClient::getHTTPSessionViaProxy(*sess,\n                                         \"example.com\",\n                                         443,\n                                         true,\n                                         transportImpl(TransportType::TCP),\n                                         timeout,\n                                         timeout));\n  EXPECT_FALSE(sessViaProxy.hasException());\n\n  (*sessViaProxy)->initiateDrain();\n  (*sess)->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientTests, PostSession) {\n  URL url(getURL(\"/\"));\n  auto sess =\n      co_await co_awaitTry(HTTPClient::getHTTPSession(&evb_,\n                                                      url.getHost(),\n                                                      url.getPort(),\n                                                      url.isSecure(),\n                                                      useQuic(),\n                                                      std::chrono::seconds(1),\n                                                      std::chrono::seconds(1)));\n  EXPECT_FALSE(sess.hasException());\n  size_t bodyExpected = 100;\n  HTTPSourceReader reader;\n  reader\n      .onHeaders([](std::unique_ptr<HTTPMessage> headers, bool, bool eom) {\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_EQ(headers->getHeaders().getSingleOrEmpty(\"x-method\"), \"POST\");\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&bodyExpected](BufQueue body, bool) {\n        if (!body.empty()) {\n          bodyExpected -= body.chainLength();\n        }\n        return HTTPSourceReader::Continue;\n      });\n  auto maybe = co_await co_awaitTry(HTTPClient::post(\n      *sess, std::move(url), std::string(100, 'a'), std::move(reader)));\n  EXPECT_FALSE(maybe.hasException());\n  EXPECT_EQ(bodyExpected, 0);\n  (*sess)->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientTests, PostCustomReader) {\n  size_t bytes = 0;\n  HTTPSourceReader reader;\n  reader\n      .onBody([&bytes](BufQueue body, bool) {\n        bytes += body.chainLength();\n        return HTTPSourceReader::Continue;\n      })\n      .onError([](HTTPSourceReader::ErrorContext, HTTPError) {\n        EXPECT_FALSE(true);\n      });\n  co_await co_awaitTry(HTTPClient::post(&evb_,\n                                        getURL(\"/\"),\n                                        std::string(100, 'a'),\n                                        seconds(1),\n                                        std::move(reader),\n                                        useQuic()));\n  EXPECT_EQ(bytes, 100);\n}\n\nCO_TEST_P_X(HTTPClientTests, Request) {\n  URL url(getURL(\"/\"));\n  auto sess =\n      co_await co_awaitTry(HTTPClient::getHTTPSession(&evb_,\n                                                      url.getHost(),\n                                                      url.getPort(),\n                                                      url.isSecure(),\n                                                      useQuic(),\n                                                      std::chrono::seconds(1),\n                                                      std::chrono::seconds(1)));\n  EXPECT_FALSE(sess.hasException());\n  HTTPSourceReader reader;\n  reader.onHeaders([](std::unique_ptr<HTTPMessage> headers, bool, bool eom) {\n    EXPECT_EQ(headers->getStatusCode(), 200);\n    EXPECT_EQ(headers->getHeaders().getSingleOrEmpty(\"x-method\"), \"OPTIONS\");\n    EXPECT_TRUE(eom);\n    return HTTPSourceReader::Continue;\n  });\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(HTTPMethod::OPTIONS);\n  request->setURL(url.makeRelativeURL());\n  request->getHeaders().set(HTTP_HEADER_HOST, url.getHostAndPort());\n  auto maybe = co_await co_awaitTry(\n      HTTPClient::request(*sess,\n                          std::move(*(*sess)->reserveRequest()),\n                          HTTPFixedSource::makeFixedSource(std::move(request)),\n                          std::move(reader)));\n  EXPECT_FALSE(maybe.hasException());\n  (*sess)->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientTests, getHTTPSessionAddressOverride) {\n  URL url(getURL(\"/\"));\n  auto sess = co_await co_awaitTry(\n      HTTPClient::getHTTPSession(&evb_,\n                                 \"shouldnotbeused.com\",\n                                 url.getPort(),\n                                 url.isSecure(),\n                                 useQuic(),\n                                 std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 \"\",\n                                 \"\",\n                                 std::string(\"169.254.1.1\")));\n  EXPECT_TRUE(sess.hasException());\n  sess = co_await co_awaitTry(\n      HTTPClient::getHTTPSession(&evb_,\n                                 \"shouldnotbeused.com\",\n                                 url.getPort(),\n                                 url.isSecure(),\n                                 useQuic(),\n                                 std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 \"\",\n                                 \"\",\n                                 serverAddress_.getIPAddress().str()));\n  EXPECT_FALSE(sess.hasException());\n  (*sess)->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientTests, RequestBuildHTTPSource) {\n  URL url(getURL(\"/\"));\n  auto sess =\n      co_await co_awaitTry(HTTPClient::getHTTPSession(&evb_,\n                                                      url.getHost(),\n                                                      url.getPort(),\n                                                      url.isSecure(),\n                                                      useQuic(),\n                                                      std::chrono::seconds(1),\n                                                      std::chrono::seconds(1)));\n  EXPECT_FALSE(sess.hasException());\n  HTTPSourceReader reader;\n  reader.onHeaders([](std::unique_ptr<HTTPMessage> headers, bool, bool) {\n    EXPECT_EQ(headers->getStatusCode(), 200);\n    EXPECT_EQ(headers->getHeaders().getSingleOrEmpty(\"x-method\"), \"POST\");\n    return HTTPSourceReader::Continue;\n  });\n  reader.onBody([](quic::BufQueue body, bool eom) {\n    EXPECT_EQ(eom, true);\n    EXPECT_EQ(body.move()->to<std::string>(), \"somebody\");\n    return HTTPSourceReader::Continue;\n  });\n  HTTPClient::RequestHeaderMap headers;\n  headers[\"host\"] = url.getHostAndPort();\n  std::optional<std::string> body = \"somebody\";\n\n  auto maybe = co_await co_awaitTry(\n      HTTPClient::request(*sess,\n                          std::move(*(*sess)->reserveRequest()),\n                          HTTPMethod::POST,\n                          url,\n                          headers,\n                          std::move(reader),\n                          body));\n  EXPECT_FALSE(maybe.hasException());\n  (*sess)->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientTests, GetBadURL) {\n  auto resp = co_await co_awaitTry(HTTPClient::get(&evb_, \"abcd\", seconds(1)));\n  EXPECT_TRUE(resp.hasException());\n}\n\nCO_TEST_P_X(HTTPClientTests, SourceReaderErrorHandling) {\n  HTTPClient::Response resp;\n  auto reader = HTTPClient::makeDefaultReader(resp);\n  YieldExceptionSource exceptionSource(\n      YieldExceptionSource::Stage::HeaderEvent,\n      YieldExceptionSource::MessageType::Request);\n  reader.setSource(&exceptionSource);\n  auto response = co_await co_awaitTry(reader.read());\n  EXPECT_TRUE(response.hasException());\n  auto error = response.exception().get_exception<HTTPError>();\n  EXPECT_NE(error, nullptr);\n  EXPECT_EQ(error->code, HTTPErrorCode::INTERNAL_ERROR);\n  // Check for appended message from HTTPClientmakeDefaultReader\n  EXPECT_TRUE(error->describe().find(\"Error receiving response headers\"));\n}\n\nCO_TEST_P_X(HTTPClientTests, GetError) {\n  auto resp = co_await co_awaitTry(HTTPClient::get(\n      &evb_, \"https://169.254.1.1:443/\", milliseconds(1), useQuic()));\n  EXPECT_TRUE(resp.hasException());\n}\n\nCO_TEST_P_X(HTTPClientTests, GetNon200) {\n  auto resp = co_await co_awaitTry(\n      HTTPClient::get(&evb_, getURL(\"/error\"), seconds(1), useQuic()));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 500);\n}\n\nCO_TEST_P_X(HTTPClientTests, GetErrorHeaders) {\n  auto resp = co_await co_awaitTry(\n      HTTPClient::get(&evb_, getURL(\"/abortHeaders\"), seconds(1), useQuic()));\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 500);\n}\n\nCO_TEST_P_X(HTTPClientTests, PostErrorBody) {\n  auto resp = co_await co_awaitTry(HTTPClient::post(&evb_,\n                                                    getURL(\"/abortBody\"),\n                                                    std::string(100, 'a'),\n                                                    seconds(1),\n                                                    useQuic()));\n  EXPECT_TRUE(resp.hasException());\n}\n\nCO_TEST_P_X(HTTPClientTests, QuicConnParamsTestMocks) {\n  // ccFactory & observers only set for quic connections\n  if (GetParam() == TransportType::QUIC) {\n    // setup mocked cc factory and observer\n    quic::MockObserver observer;\n    EXPECT_CALL(observer, attached).Times(1);\n    auto ccFactory =\n        std::make_shared<quic::test::MockCongestionControllerFactory>();\n    EXPECT_CALL(*ccFactory, makeCongestionController(_, _))\n        .Times(AtLeast(1))\n        .WillRepeatedly(Invoke([](quic::QuicConnectionStateBase& conn,\n                                  quic::CongestionControlType ccType) {\n          return quic::DefaultCongestionControllerFactory()\n              .makeCongestionController(conn, ccType);\n        }));\n\n    // connect\n    HTTPCoroConnector::SessionParams sessParams;\n    auto connParams = HTTPClient::getQuicConnParams();\n    connParams.ccFactory = std::move(ccFactory);\n    connParams.onTransportCreated =\n        [obs = &observer](quic::QuicClientTransport& client) {\n          client.addObserver(obs);\n        };\n\n    auto sess = co_await HTTPCoroConnector_connect(\n        &evb_, serverAddress_, seconds(10), std::move(connParams), sessParams);\n\n    // make reservation and send request\n    auto reservation = *(sess->reserveRequest());\n    auto respSource =\n        co_await co_withExecutor(\n            &evb_,\n            sess->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\"),\n                              std::move(reservation)))\n            .start();\n\n    HTTPSourceReader reader;\n    auto resp = co_await folly::coro::co_awaitTry(\n        reader.setSource(std::move(respSource)).read());\n    EXPECT_FALSE(resp.hasException());\n  }\n}\n\nCO_TEST_P_X(HTTPClientTests, ClientBodyError) {\n  auto params = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  sessParams.streamReadTimeout = seconds(10);\n  sessParams.connReadTimeout = seconds(10);\n  sessParams.writeTimeout = seconds(10);\n  auto sess = co_await HTTPCoroConnector_connect(&evb_,\n                                                 serverAddress_,\n                                                 seconds(10),\n                                                 getConnParams(GetParam()),\n                                                 sessParams);\n\n  auto reservation = *(sess->reserveRequest());\n  auto respSource =\n      co_await co_withExecutor(\n          &evb_,\n          sess->sendRequest(new ErrorSource(\"super super long text\", true, 11),\n                            std::move(reservation)))\n          .start();\n\n  HTTPSourceReader reader;\n  auto resp = co_await folly::coro::co_awaitTry(\n      reader.setSource(std::move(respSource)).read());\n\n  CO_ASSERT_TRUE(resp.hasException<HTTPError>());\n  EXPECT_NE(\n      dynamic_cast<const HTTPError*>(resp.exception().get_exception())->code,\n      HTTPErrorCode::READ_TIMEOUT);\n  sess->initiateDrain();\n  // Need to loop one more time to ensure writeLoop flushes the TCP reset\n  co_await folly::coro::co_reschedule_on_current_executor;\n}\n\nCO_TEST_P_X(HTTPClientTests, ServerBodyError) {\n  // use a custom reader to make sure that we get the original error\n  HTTPSourceReader reader;\n  auto resp = co_await co_awaitTry(HTTPClient::get(&evb_,\n                                                   getURL(\"/bodyError_11\"),\n                                                   seconds(10),\n                                                   std::move(reader),\n                                                   useQuic()));\n  CO_ASSERT_TRUE(resp.hasException<HTTPError>());\n  EXPECT_NE(\n      dynamic_cast<const HTTPError*>(resp.exception().get_exception())->code,\n      HTTPErrorCode::READ_TIMEOUT);\n}\n\nclass HTTPCoroSessionPoolTests : public HTTPClientTests {\n protected:\n  class CoroSessionPool : public HTTPCoroSessionPool {\n   public:\n    // protected => public visibility\n    using HTTPCoroSessionPool::HTTPCoroSessionPool;\n\n    using HTTPCoroSessionPool::detachIdleSession;\n    using HTTPCoroSessionPool::insertIdleSession;\n    using HTTPCoroSessionPool::setIdleSessionObserver;\n  };\n  std::unique_ptr<CoroSessionPool> pool_;\n  NiceMock<wangle::MockSSLStats> tlsStats_;\n\n public:\n  void SetUp() override {\n    HTTPClientTests::SetUp();\n    pool_ = makePool();\n  }\n\n  using PoolEx = HTTPCoroSessionPool::Exception;\n  using PoolParams = HTTPCoroSessionPool::PoolParams;\n  using SessParams = HTTPCoroConnector::SessionParams;\n  std::unique_ptr<CoroSessionPool> makePool(\n      PoolParams poolParams = PoolParams{},\n      SessParams sessParams = SessParams{}) {\n    if (GetParam() == TransportType::QUIC) {\n      auto qconnParams = std::get<HTTPCoroConnector::QuicConnectionParams>(\n          getConnParams(GetParam()));\n      qconnParams.tlsStats = &tlsStats_;\n      auto qconnParamsPtr = getQuicConnParams(qconnParams);\n      return std::make_unique<CoroSessionPool>(&evb_,\n                                               serverAddress_.getAddressStr(),\n                                               serverAddress_.getPort(),\n                                               poolParams,\n                                               std::move(qconnParamsPtr),\n                                               sessParams);\n    } else {\n      auto connParams = std::get<HTTPCoroConnector::ConnectionParams>(\n          getConnParams(GetParam()));\n      connParams.tlsStats = &tlsStats_;\n      return std::make_unique<CoroSessionPool>(&evb_,\n                                               serverAddress_.getAddressStr(),\n                                               serverAddress_.getPort(),\n                                               poolParams,\n                                               connParams,\n                                               sessParams);\n    }\n  }\n};\n\nfolly::coro::Task<uint16_t> get(HTTPCoroSessionPool& pool,\n                                bool expectError = true) {\n  auto res = co_await co_awaitTry(pool.getSessionWithReservation());\n  if (expectError) {\n    EXPECT_FALSE(res.hasException());\n  }\n  // Slightly jank not to pass the real URL here.  HTTPClient::get uses it to\n  // set the Host header\n  auto respSource = co_await res->session->sendRequest(\n      HTTPFixedSource::makeFixedRequest(URL(\"http://localhost/\")),\n      std::move(res->reservation));\n  HTTPSourceReader reader(std::move(respSource));\n  reader.onHeaders([](std::unique_ptr<HTTPMessage> resp, bool, bool) {\n    EXPECT_EQ(resp->getStatusCode(), 200);\n    return HTTPSourceReader::Continue;\n  });\n  auto maybe = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(maybe.hasException());\n  co_return res->session->getLocalAddress().getPort();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, Pool) {\n  // Send two requests via the same pool, ensure they use the same port\n  auto localPort1 = co_await get(*pool_);\n  auto localPort2 = co_await get(*pool_);\n  EXPECT_EQ(localPort1, localPort2);\n  pool_.reset();\n}\n\nusing HTTPCoroSessionPoolTLSTests = HTTPCoroSessionPoolTests;\nCO_TEST_P_X(HTTPCoroSessionPoolTLSTests, PoolSessionReuse) {\n  EXPECT_CALL(tlsStats_, recordSSLUpstreamConnection(true));\n  auto localPort1 = co_await get(*pool_);\n  pool_->flush();\n  EXPECT_CALL(tlsStats_, recordSSLUpstreamConnection(false));\n  auto localPort2 = co_await get(*pool_);\n  EXPECT_NE(localPort1, localPort2);\n  pool_.reset();\n}\n\nusing HTTPCoroSessionPoolSSLTests = HTTPCoroSessionPoolTests;\nCO_TEST_P_X(HTTPCoroSessionPoolSSLTests, PoolSessionReuseCustomStore) {\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  connParams.tlsStats = &tlsStats_;\n  class SslSessionManager : public HTTPCoroConnector::SslSessionManagerIf {\n   public:\n    void onNewSslSession(SslSessionPtr session) noexcept override {\n      sslSession = std::move(session);\n    }\n    SslSessionPtr getSslSession() noexcept override {\n      return std::move(sslSession);\n    }\n    std::shared_ptr<folly::ssl::SSLSession> sslSession{nullptr};\n  } sslSessionManager;\n\n  connParams.sslSessionManager = &sslSessionManager;\n  pool_->setConnParams(connParams);\n  EXPECT_CALL(tlsStats_, recordSSLUpstreamConnection(true));\n  auto localPort1 = co_await get(*pool_);\n  EXPECT_TRUE(sslSessionManager.sslSession);\n  pool_->flush();\n  EXPECT_CALL(tlsStats_, recordSSLUpstreamConnection(false));\n  auto localPort2 = co_await get(*pool_);\n  EXPECT_NE(localPort1, localPort2);\n  pool_.reset();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolLateBind) {\n  // Send two requests via the same pool at the same time, with max conns=1,\n  // ensure they use the same port\n  // Note: H1 causes the second connection to be reused after the get() finishes\n  // while H2 (tls) signals both gets() in parallel\n  // TODO: verify this behavior\n  pool_->setMaxConnections(1);\n  auto [localPort1, localPort2] =\n      co_await folly::coro::collectAll(get(*pool_), get(*pool_));\n  EXPECT_EQ(localPort1, localPort2);\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolLateBindTimeout) {\n  // Request a session from a pool that is already full, but don't return\n  // the session in time.\n  pool_->setMaxConnections(1);\n  auto params = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream\n  params.maxConcurrentOutgoingStreams = 1;\n  pool_->setSessionParams(params);\n  auto res = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res.hasException());\n  auto respSource = co_await co_awaitTry(res->session->sendRequest(\n      new TimeoutSource(std::make_unique<HTTPMessage>(getPostRequest(10))),\n      std::move(res->reservation)));\n  EXPECT_FALSE(respSource.hasException());\n  // Getting the second session fails\n  auto res2 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_TRUE(res2.hasException());\n  EXPECT_EQ(res2.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()->type,\n            HTTPCoroSessionPool::Exception::Type::Timeout);\n  respSource->stopReading();\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolHeadersTimeout) {\n  // Request a session from a pool that is already full with a session that\n  // hasn't sent it's request\n  pool_->setMaxConnections(2);\n  auto params = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream\n  params.maxConcurrentOutgoingStreams = 1;\n  pool_->setSessionParams(params);\n  auto res = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res.hasException());\n  folly::CancellationSource cancellationSource;\n  auto sendFut =\n      co_withExecutor(\n          &evb_,\n          folly::coro::co_withCancellation(\n              cancellationSource.getToken(),\n              res->session->sendRequest(\n                  new TimeoutSource(\n                      std::make_unique<HTTPMessage>(getGetRequest()), true),\n                  std::move(res->reservation))))\n          .start();\n  // Getting the second session succeeds, but with a different port\n  auto res2 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res2.hasException());\n  EXPECT_NE(res->session->getLocalAddress().getPort(),\n            res2->session->getLocalAddress().getPort());\n  cancellationSource.requestCancellation();\n  auto respSource = co_await folly::coro::co_awaitTry(std::move(sendFut));\n  EXPECT_TRUE(respSource.hasException());\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolLateBindConnectOnClose) {\n  // Return a closing session to a pool that has a waiter, verify it gets a\n  // new session\n  pool_->setMaxConnections(1);\n  auto params = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream\n  params.maxConcurrentOutgoingStreams = 1;\n  pool_->setSessionParams(params);\n  auto port1 = co_await get(*pool_);\n  EXPECT_NE(port1, 0);\n  auto res = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res.hasException());\n  EXPECT_EQ(port1, res->session->getLocalAddress().getPort());\n  auto respSource = co_await co_awaitTry(res->session->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\"), std::move(res->reservation)));\n  EXPECT_FALSE(respSource.hasException());\n  res->session->initiateDrain();\n  auto fut = co_withExecutor(&evb_, pool_->getSessionWithReservation()).start();\n  XLOG(DBG4) << \"waiting for headers\";\n  auto resp = co_await co_awaitTry(respSource->readHeaderEvent());\n  XLOG(DBG4) << \"got headers\";\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  EXPECT_TRUE(resp->eom);\n  auto res2 = co_await std::move(fut);\n  auto port2 = res2.session->getLocalAddress().getPort();\n  EXPECT_NE(port1, port2);\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, LateBindConnFailCancelWaiters) {\n  HTTPCoroSessionPool deadPool(&evb_, \"0.0.0.169\", 0);\n  // Send two requests via the same bad pool.  When the first one runs out\n  // of connection attempts, both are cancelled\n  deadPool.setMaxConnections(1);\n  auto [res1, res2] =\n      co_await folly::coro::collectAllTry(deadPool.getSessionWithReservation(),\n                                          deadPool.getSessionWithReservation());\n  auto ex = res1.tryGetExceptionObject<HTTPCoroSessionPool::Exception>();\n  EXPECT_EQ(ex->type, HTTPCoroSessionPool::Exception::Type::ConnectFailed);\n  auto asyncSockEx =\n      ex->connectException.get_exception<folly::AsyncSocketException>();\n  EXPECT_EQ(asyncSockEx->getType(), folly::AsyncSocketException::NOT_OPEN);\n  EXPECT_EQ(res2.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()->type,\n            HTTPCoroSessionPool::Exception::Type::ConnectFailed);\n  auto res3 = co_await co_awaitTry(deadPool.getSessionWithReservation());\n  // Another attemp will still fail, but not because it's been \"cancelled\"\n  EXPECT_EQ(res3.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()->type,\n            HTTPCoroSessionPool::Exception::Type::ConnectFailed);\n  deadPool.drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, DrainCancelWaiters) {\n  pool_->setMaxConnections(1);\n  // Start getting a session\n  auto fut = co_withExecutor(&evb_, pool_->getSessionWithReservation()).start();\n  // Context switch to ensure getSessionWithReservation() starts the new\n  // connection\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Drain the pool\n  pool_->drain();\n  // Should return null immediately\n  auto res = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_EQ(res.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()->type,\n            HTTPCoroSessionPool::Exception::Type::Draining);\n\n  // The connection will come back, and get drained without ever being\n  // made available to the caller\n  res = co_await folly::coro::co_awaitTry(std::move(fut));\n  EXPECT_EQ(res.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()->type,\n            HTTPCoroSessionPool::Exception::Type::Draining);\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, TwoWaitersOneConns) {\n  // Issue two requests to a pool with one currently full session that supports\n  // 1 txn. When it finishes it should signal both waiter.\n  if (GetParam() == TransportType::TCP) {\n    // TCP/H1 can't do this\n    co_return;\n  }\n  PoolParams poolParams;\n  poolParams.maxConnections = 1;\n  poolParams.maxWaiters = 2;\n  auto sessParams = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream, at first\n  sessParams.maxConcurrentOutgoingStreams = 1;\n  auto pool = makePool(poolParams);\n  pool->setSessionParams(sessParams);\n\n  auto res1 = co_await co_awaitTry(pool->getSessionWithReservation());\n  EXPECT_FALSE(res1.hasException());\n  auto res2Fut =\n      co_withExecutor(&evb_, pool->getSessionWithReservation()).start();\n  auto res3Fut =\n      co_withExecutor(&evb_, pool->getSessionWithReservation()).start();\n  auto res4Fut =\n      co_withExecutor(&evb_, pool->getSessionWithReservation()).start();\n  // Make sure these get in the waiter queue\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Technically, this API maybe should invoke an info callback by itself?\n  res1->session->setMaxConcurrentOutgoingStreams(2);\n  // Finish the request, this will signal both waiters\n  auto respSource = co_await res1->session->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\"), std::move(res1->reservation));\n  auto resp = co_await co_awaitTry(respSource.readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  EXPECT_TRUE(resp->eom);\n  auto res2 = co_await folly::coro::co_awaitTry(std::move(res2Fut));\n  EXPECT_EQ(res1->session, res2->session);\n  auto get2Fut = co_withExecutor(&evb_,\n                                 res2->session->sendRequest(\n                                     HTTPFixedSource::makeFixedRequest(\"/\"),\n                                     std::move(res2->reservation)))\n                     .start();\n  auto res3 = co_await folly::coro::co_awaitTry(std::move(res3Fut));\n  XCHECK(res3.hasValue());\n  EXPECT_EQ(res1->session, res3->session);\n\n  // maxWaiters=2 => res4Fut is cancelled\n  EXPECT_TRUE(res4Fut.isReady() && res4Fut.hasException() &&\n              res4Fut.result().exception().get_exception<PoolEx>()->type ==\n                  PoolEx::Type::MaxWaiters);\n\n  // meh abandon get2Fut\n  pool->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, TwoWaitersTwoConns) {\n  // Issue two requests to a pool with two currently full sessions that support\n  // 1 txn each. When they finish they should each signal one waiter.\n  pool_->setMaxConnections(2);\n  auto params = HTTPCoroConnector::defaultSessionParams();\n  // For H2, only allow one stream\n  params.maxConcurrentOutgoingStreams = 1;\n  pool_->setSessionParams(params);\n  auto res1 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res1.hasException());\n  auto get1Fut = co_withExecutor(&evb_,\n                                 res1->session->sendRequest(\n                                     HTTPFixedSource::makeFixedRequest(\"/\"),\n                                     std::move(res1->reservation)))\n                     .start();\n  auto res2 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res2.hasException());\n  auto get2Fut = co_withExecutor(&evb_,\n                                 res2->session->sendRequest(\n                                     HTTPFixedSource::makeFixedRequest(\"/\"),\n                                     std::move(res2->reservation)))\n                     .start();\n  auto res3Fut =\n      co_withExecutor(&evb_, pool_->getSessionWithReservation()).start();\n  auto res4Fut =\n      co_withExecutor(&evb_, pool_->getSessionWithReservation()).start();\n  // Make sure these get in the waiter queue\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Finish first request, will release only one session waiter\n  auto respSource = co_await folly::coro::co_awaitTry(std::move(get1Fut));\n  auto resp = co_await co_awaitTry(respSource->readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  EXPECT_TRUE(resp->eom);\n  auto res3 = co_await folly::coro::co_awaitTry(std::move(res3Fut));\n  EXPECT_EQ(res1->session, res3->session);\n  auto get3Fut = co_withExecutor(&evb_,\n                                 res3->session->sendRequest(\n                                     HTTPFixedSource::makeFixedRequest(\"/\"),\n                                     std::move(res3->reservation)))\n                     .start();\n  // Finish second request, releases second waiter\n  respSource = co_await folly::coro::co_awaitTry(std::move(get2Fut));\n  resp = co_await co_awaitTry(respSource->readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  EXPECT_TRUE(resp->eom);\n  auto res4 = co_await folly::coro::co_awaitTry(std::move(res4Fut));\n  EXPECT_EQ(res2->session, res4->session);\n  // meh abandon get3Fut\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolMaxAge) {\n  pool_->setMaxAge(std::chrono::seconds(1));\n  // Send two requests via the same pool, after the first one ages out\n  auto localPort1 = co_await get(*pool_);\n  co_await folly::coro::sleep(std::chrono::milliseconds(1100));\n  auto localPort2 = co_await get(*pool_);\n  EXPECT_NE(localPort1, localPort2);\n  pool_->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolConnect) {\n  pool_.reset();\n  std::shared_ptr<HTTPCoroSessionPool> serverPool = makePool();\n  // Using the default conn params: TCP/H1\n  HTTPCoroSessionPool connectPool(&evb_, \"example.com\", 443, serverPool);\n\n  // Send two requests via the same pool, after the first one ages out\n  auto res1 = co_await co_awaitTry(connectPool.getSessionWithReservation());\n  EXPECT_FALSE(res1.hasException());\n  auto localPort1 = res1->session->getLocalAddress().getPort();\n  auto res2 = co_await co_awaitTry(connectPool.getSessionWithReservation());\n  EXPECT_FALSE(res2.hasException());\n  auto localPort2 = res2->session->getLocalAddress().getPort();\n  // The connected session is H1, so full, expect a new top level session\n  EXPECT_NE(res1->session, res2->session);\n\n  if (GetParam() == TransportType::TCP) {\n    // The underlying session is H1, so a new underlying session is created\n    EXPECT_NE(localPort1, localPort2);\n  } else {\n    // The underlying session is multiplexed and re-used.\n    EXPECT_EQ(localPort1, localPort2);\n  }\n  connectPool.drain();\n  serverPool->drain();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, PoolingDisabled) {\n  // maxConnections = 0 can be set via constructor or setter\n  {\n    // test constructor path\n    auto pool = makePool(HTTPCoroSessionPool::PoolParams{.maxConnections = 0});\n    auto localPort1 = co_await get(*pool);\n    auto localPort2 = co_await get(*pool);\n    EXPECT_NE(localPort1, localPort2);\n  }\n\n  {\n    // test setter path\n    auto pool = makePool();\n    pool->setMaxConnections(0);\n    auto localPort1 = co_await get(*pool);\n    auto localPort2 = co_await get(*pool);\n    EXPECT_NE(localPort1, localPort2);\n  }\n\n  {\n    // test pooling disabled with existing maxConnections\n    auto pool = makePool(HTTPCoroSessionPool::PoolParams{.maxConnections = 2},\n                         SessParams{.maxConcurrentOutgoingStreams = 1});\n    pool->setPoolingEnabled(false);\n\n    {\n      auto [res1, res2] = co_await folly::coro::collectAll(\n          pool->getSessionWithReservation(), pool->getSessionWithReservation());\n      // two connections => pool is full\n      EXPECT_TRUE(pool->full());\n    }\n    // detaching both txns w/ pooling disabled => connections will drain\n    EXPECT_TRUE(pool->empty());\n  }\n}\n\nTEST_P(HTTPCoroSessionPoolTests, DrainViaDestructor) {\n  pool_.reset();\n  auto pool = makePool();\n  co_withExecutor(&evb_, get(*pool, /*expectError=*/false))\n      .start([](auto&& res) { EXPECT_TRUE(res.hasException()); });\n  evb_.loopOnce();\n  evb_.loopOnce();\n  pool.reset();\n  evb_.loop();\n}\n\nTEST_P(HTTPCoroSessionPoolTests, LastOutstandingConnectFailed) {\n  // just test http/1.1 for simplicity\n  if (GetParam() != TransportType::TCP) {\n    return;\n  }\n\n  /**\n   * Tests the following edgecase:\n   *\n   * If the last outstanding connection fails, we should not cancel waiters if a\n   * session already exists. To achieve this we need a special EventBaseBackend\n   * that fails AsyncSocket connect\n   */\n  struct FailConnectionEvb : public EventBaseBackend {\n    int eb_event_add(Event& event, const struct timeval* timeout) override {\n      bool fail = failWrite_ && (event.getEvent()->ev_events == EV_WRITE);\n      return fail ? -1 : event_add(event.getEvent(), timeout);\n    }\n    bool failWrite_{false};\n  };\n\n  auto evbBackend = new FailConnectionEvb();\n  folly::EventBase evb{\n      folly::EventBase::Options().setBackendFactory([evbBackend]() {\n        return std::unique_ptr<folly::EventBaseBackendBase>(evbBackend);\n      }),\n  };\n\n  // configure pool\n  auto connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  auto pool = std::make_unique<CoroSessionPool>(\n      &evb,\n      serverAddress_.getAddressStr(),\n      serverAddress_.getPort(),\n      PoolParams{.maxConnectionAttempts = 1},\n      connParams,\n      SessParams{.maxConcurrentOutgoingStreams = 1});\n\n  pool->setMaxConnections(1);\n  using PoolEx = HTTPCoroSessionPool::Exception;\n\n  {\n    // When the only single outstanding connection establishment fails with no\n    // other sessions, it should cancel the waiting coroutine with ConnectFailed\n    evbBackend->failWrite_ = true;\n    auto res =\n        blockingWait(co_awaitTry(pool->getSessionWithReservation()), &evb);\n    auto* ex = res.tryGetExceptionObject<PoolEx>();\n    EXPECT_TRUE(ex && ex->type == PoolEx::Type::ConnectFailed);\n    evbBackend->failWrite_ = false;\n  }\n\n  {\n    // connection establishment succeeds => one session in the pool\n    auto res =\n        blockingWait(co_awaitTry(pool->getSessionWithReservation()), &evb);\n    EXPECT_FALSE(res.hasException());\n\n    // pool is now full, second ::getSessionWithReservation will suspend without\n    // attempting to establish a connection\n    EXPECT_TRUE(pool->full());\n    auto res2 = co_withExecutor(&evb, pool->getSessionWithReservation())\n                    .startInlineUnsafe();\n    EXPECT_FALSE(res2.isReady()); // suspended\n\n    // allow room for one more connection => fail connection attempt => verify\n    // above coroutine is still waiting\n    pool->setMaxConnections(2);\n    EXPECT_FALSE(pool->full());\n\n    evbBackend->failWrite_ = true;\n    auto res3 = co_withExecutor(&evb, pool->getSessionWithReservation())\n                    .startInlineUnsafe();\n\n    // few loops\n    evb.loopOnce();\n    evb.loopOnce();\n\n    // validate both res2 & res3 are still suspended without exception\n    EXPECT_TRUE(!res2.isReady() && !res3.isReady());\n\n    // cancel res => res2.isReady()\n    res->reservation.cancel();\n    evb.loopOnce();\n    EXPECT_TRUE(res2.isReady() && res2.hasValue());\n\n    // cancel re2 => res3.isReady()\n    std::move(res2).value().reservation.cancel();\n    evb.loopOnce();\n    EXPECT_TRUE(res3.isReady() && res3.hasValue());\n  }\n\n  pool.reset();\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, IdleSessionsTest) {\n  /**\n   * pool settings to simplify test:\n   *   - a single reservation/request will cause the session to become full\n   *   - at most two sessions\n   */\n  pool_->setSessionParams({.maxConcurrentOutgoingStreams = 1});\n  pool_->setMaxConnections(2);\n\n  class MockIdleSessionObserver\n      : public HTTPCoroSessionPool::IdleSessionObserverIf {\n   public:\n    MOCK_METHOD(void,\n                onIdleSessionsChanged,\n                (const HTTPCoroSessionPool& pool),\n                (noexcept));\n  };\n  EXPECT_FALSE(pool_->detachIdleSession()); // no idle sessions\n\n  {\n    /**\n     * Two concurrent ::getSessionWithReservation will attempt to establish two\n     * connections; both should be initially inserted in available list (i.e. no\n     * idle sessions).\n     */\n    NiceMock<MockIdleSessionObserver> idleSessionObs;\n    pool_->setIdleSessionObserver(&idleSessionObs);\n    auto [res1, res2] = co_await folly::coro::collectAll(\n        pool_->getSessionWithReservation(), pool_->getSessionWithReservation());\n    EXPECT_TRUE(pool_->full());\n    EXPECT_FALSE(pool_->hasAvailableSessions());\n    EXPECT_FALSE(pool_->hasIdleSessions());\n\n    // cancelling reservations will move backing session back into idle list\n    EXPECT_CALL(idleSessionObs, onIdleSessionsChanged(_)).Times(2);\n    res1.reservation.cancel();\n    EXPECT_TRUE(pool_->hasAvailableSessions() && pool_->hasIdleSessions());\n    res2.reservation.cancel();\n    pool_->setIdleSessionObserver(nullptr);\n  }\n\n  {\n    // loop for session to become detachable\n    co_await folly::coro::co_reschedule_on_current_executor;\n    // there should be two idle sessions; expect detach to return session\n    auto idleSession = pool_->detachIdleSession();\n    XCHECK(idleSession);\n    EXPECT_EQ(idleSession->getEventBase(), nullptr);\n\n    // reservation will synchronously return reservation since we still have one\n    // idle session\n    EXPECT_TRUE(pool_->hasIdleSessions() && !pool_->full());\n    auto res1 = co_withExecutor(&evb_, pool_->getSessionWithReservation())\n                    .startInlineUnsafe();\n    EXPECT_TRUE(res1.isReady());\n\n    // no available sessions => will open new connection\n    EXPECT_FALSE(pool_->hasAvailableSessions());\n    auto res2 = co_await co_awaitTry(pool_->getSessionWithReservation());\n    XCHECK(!res2.hasException());\n\n    // acquiring another res will indefinitely suspend since pool is full\n    EXPECT_TRUE(pool_->full());\n    auto res3 = co_withExecutor(&evb_, pool_->getSessionWithReservation())\n                    .startInlineUnsafe();\n    EXPECT_FALSE(res3.isReady());\n\n    // inject idle session, will wake up awaiting ::getSessionWithReservation\n    // coro above after one evb loop\n    static_cast<HTTPCoroSession*>(idleSession.get())->attachEvb(&evb_);\n    pool_->insertIdleSession(std::move(idleSession));\n    co_await folly::coro::co_reschedule_on_current_executor;\n    EXPECT_TRUE(res3.isReady());\n  }\n  {\n    // verify IdleSessionObserver is invoked when an session is draining\n    pool_->flush();\n    NiceMock<MockIdleSessionObserver> idleSessionObs;\n    pool_->setIdleSessionObserver(&idleSessionObs);\n    HTTPCoroSession* session{nullptr};\n    {\n      auto res = co_await pool_->getSessionWithReservation();\n      // destructing reservation will move to idle\n      EXPECT_CALL(idleSessionObs, onIdleSessionsChanged(_)).Times(1);\n      session = res.session;\n    }\n\n    EXPECT_CALL(idleSessionObs, onIdleSessionsChanged(_)).Times(1);\n    // draining will invoke ::onIdleSessionsChanged\n    session->initiateDrain();\n  }\n}\n\nCO_TEST_P_X(HTTPCoroSessionPoolTests, FlushPool) {\n  /**\n   * pool settings to simplify test:\n   *   - a single reservation/request will cause the session to become full\n   *   - at most two sessions\n   */\n  pool_->setSessionParams({.maxConcurrentOutgoingStreams = 1});\n  pool_->setMaxConnections(2);\n\n  // helpers\n  auto session = [this]() -> auto {\n    return pool_->getSessionWithReservation();\n  };\n  using reschedule = folly::coro::co_reschedule_on_current_executor_t;\n\n  // two ::getSessionWithReservations will fill pool\n  auto [res1, res2] = co_await folly::coro::collectAll(session(), session());\n  EXPECT_TRUE(pool_->full());\n\n  // any additional ::getSessionWithReservation will be suspended\n  auto res3 = co_withExecutor(&evb_, session()).start();\n  auto res4 = co_withExecutor(&evb_, session()).start();\n  co_await reschedule{};\n  EXPECT_TRUE(!res3.isReady() && !res4.isReady());\n\n  // flushing pool should cancel any waiters\n  pool_->flush();\n  co_await reschedule{};\n  // both futs should be ready and contain exceptions\n  EXPECT_TRUE(res3.isReady() && res4.isReady());\n  EXPECT_TRUE(res3.hasException() && res4.hasException());\n}\n\nclass CertReloadSessionPoolTests : public HTTPClientTests {\n protected:\n  std::unique_ptr<CertReloadSessionPool> pool_;\n\n public:\n  void SetUp() override {\n    HTTPClientTests::SetUp();\n    pool_ = makePool();\n  }\n\n  using PoolParams = HTTPCoroSessionPool::PoolParams;\n  using SessParams = HTTPCoroConnector::SessionParams;\n\n  std::unique_ptr<CertReloadSessionPool> makePool(\n      PoolParams poolParams = PoolParams{},\n      SessParams sessParams = SessParams{}) {\n    std::unique_ptr<CertReloadSessionPool> pool;\n    if (GetParam() == TransportType::QUIC) {\n      auto qconnParams = std::get<HTTPCoroConnector::QuicConnectionParams>(\n          getConnParams(GetParam()));\n      auto qconnParamsPtr = getQuicConnParams(qconnParams);\n      pool = std::make_unique<CertReloadSessionPool>(\n          &evb_,\n          serverAddress_.getAddressStr(),\n          serverAddress_.getPort(),\n          poolParams,\n          std::move(qconnParamsPtr),\n          sessParams);\n    } else {\n      auto connParams = std::get<HTTPCoroConnector::ConnectionParams>(\n          getConnParams(GetParam()));\n      pool = std::make_unique<CertReloadSessionPool>(\n          &evb_,\n          serverAddress_.getAddressStr(),\n          serverAddress_.getPort(),\n          poolParams,\n          std::move(connParams),\n          sessParams);\n    }\n    return pool;\n  }\n};\n\n// Verifies that CertReloadSessionPool correctly invokes the user-provided\n// timer callback periodically, and that the pool continues to function\n// after the callback updates connection params. This simulates the real-world\n// scenario where long-running services need to refresh expiring certs.\nCO_TEST_P_X(CertReloadSessionPoolTests, TimerCallback) {\n  auto certPath =\n      getContainingDirectory(__FILE__).str() + \"/certs/test_cert1.pem\";\n  auto keyPath =\n      getContainingDirectory(__FILE__).str() + \"/certs/test_key1.pem\";\n  auto transportType = GetParam();\n\n  // Capture base connection params so we can preserve settings like the\n  // certificate verifier while updating the fizz context\n  auto baseConnParams = std::get<HTTPCoroConnector::ConnectionParams>(\n      getConnParams(TransportType::TLS_FIZZ));\n  auto baseQuicConnParams =\n      (transportType == TransportType::QUIC)\n          ? std::make_optional(\n                std::get<HTTPCoroConnector::QuicConnectionParams>(\n                    getConnParams(TransportType::QUIC)))\n          : std::nullopt;\n\n  pool_->setTimerCallback(\n      [certPath, keyPath, transportType, baseConnParams, baseQuicConnParams](\n          HTTPCoroSessionPool& p) {\n        // Build TLS context from cert paths\n        HTTPCoroConnector::TLSParams tlsParams;\n        tlsParams.clientCertPath = certPath;\n        tlsParams.clientKeyPath = keyPath;\n\n        if (transportType == TransportType::QUIC) {\n          tlsParams.nextProtocols = {\"h3\"};\n          auto quicParams =\n              std::make_shared<HTTPCoroConnector::QuicConnectionParams>(\n                  *baseQuicConnParams);\n          quicParams->fizzContextAndVerifier.fizzContext =\n              HTTPCoroConnector::makeFizzClientContext(tlsParams);\n          p.setQuicConnectionParams(std::move(quicParams));\n        } else {\n          auto connParams = baseConnParams;\n          connParams.fizzContextAndVerifier.fizzContext =\n              HTTPCoroConnector::makeFizzClientContext(tlsParams);\n          p.setConnParams(connParams);\n        }\n      },\n      std::chrono::milliseconds(50));\n\n  // Establish initial connection\n  auto res1 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res1.hasException());\n\n  // Wait for timer callback to fire at least once\n  co_await folly::coro::sleep(std::chrono::milliseconds(100));\n\n  // Verify pool still works after callback updated connection params.\n  // Flush existing sessions and establish a new connection with the\n  // reloaded certs.\n  pool_->flush();\n  auto res2 = co_await co_awaitTry(pool_->getSessionWithReservation());\n  EXPECT_FALSE(res2.hasException());\n\n  pool_->drain();\n}\n\nclass HTTPClientConnectionCacheTests : public HTTPClientTests {\n public:\n  void SetUp() override {\n    HTTPClientTests::SetUp();\n  }\n\n  proxygen::coro::HTTPCoroSessionPool& getPool(\n      const folly::SocketAddress& socket) {\n    return connCache_.getPool(\n        socket.getAddressStr(),\n        socket.getAddressStr(),\n        socket.getPort(),\n        (HTTPClientConnectionCacheTests::GetParam() != TransportType::TCP));\n  }\n\n  bool poolExists(const folly::SocketAddress& socket) {\n    return connCache_.poolExists(\n        socket.getAddressStr(),\n        socket.getPort(),\n        (HTTPClientConnectionCacheTests::GetParam() != TransportType::TCP));\n  }\n\n protected:\n  // wrapper class to expose protected members fns as public\n  class HTTPClientConnectionCacheWrapper : public HTTPClientConnectionCache {\n   public:\n    using HTTPClientConnectionCache::getPool;\n    using HTTPClientConnectionCache::HTTPClientConnectionCache;\n    using HTTPClientConnectionCache::poolExists;\n  };\n\n  HTTPClientConnectionCacheWrapper connCache_{evb_};\n};\n\nTEST_P(HTTPClientConnectionCacheTests, GetPool) {\n  auto& pool1 = connCache_.getPool(\"foo.com\", \"1.2.3.4\", 443, true);\n  EXPECT_FALSE(pool1.full());\n  auto& pool2 = connCache_.getPool(\"foo.com\", \"1.2.3.4\", 80, false);\n  EXPECT_EQ(connCache_.getNumPools(), 2);\n  auto& pool3 = connCache_.getPool(\"foo.com\", \"1.2.3.4\", 80, false);\n  EXPECT_EQ(connCache_.getNumPools(), 2);\n  EXPECT_EQ(&pool2, &pool3);\n\n  for (size_t i = 0; i < 20; i++) {\n    auto& pool = connCache_.getPool(\"foo.com\", \"1.2.3.4\", 9443 + i, true);\n    EXPECT_FALSE(pool.full());\n    EXPECT_LE(connCache_.getNumPools(), 10);\n  }\n  EXPECT_EQ(connCache_.getNumPools(), 10);\n}\n\nTEST_P(HTTPClientConnectionCacheTests, GetPoolWithConnParams) {\n  /**\n   * ::getPool() should always prefer connParams passed in via param rather than\n   * the connParams set via ::setConnParams(). Invoke ::setConnParams with a\n   * dummy value because why not?\n   */\n  HTTPCoroConnector::ConnectionParams other{};\n  other.fizzContextAndVerifier =\n      HTTPCoroConnector::makeFizzClientContextAndVerifier(\n          HTTPCoroConnector::defaultTLSParams());\n  connCache_.setConnParams(other);\n\n  auto& pool1 = connCache_.getPool(\n      \"foo.com\", \"1.2.3.4\", 443, true, /*connParams=*/nullptr);\n  auto& pool2 = connCache_.getPool(\n      \"foo.com\", \"1.2.3.4\", 443, true, /*connParams=*/nullptr);\n\n  // pools retrieved should be the same as (address, port, isSecure) tuples are\n  // equivalent\n  EXPECT_EQ(&pool1, &pool2);\n\n  // passing in the additional ConnectionParams* argument should retrieve a\n  // different pool\n  HTTPCoroConnector::ConnectionParams connParams{};\n  connParams.fizzContextAndVerifier =\n      HTTPCoroConnector::makeFizzClientContextAndVerifier(\n          HTTPCoroConnector::defaultTLSParams());\n  auto& pool3 =\n      connCache_.getPool(\"foo.com\", \"1.2.3.4\", 443, true, &connParams);\n  EXPECT_NE(&pool1, &pool3);\n\n  // since we're hashing the addresses of the members of fizzContextAndVerifier,\n  // creating a new one should yet again retrieve a different pool\n  connParams.fizzContextAndVerifier =\n      HTTPCoroConnector::makeFizzClientContextAndVerifier(\n          HTTPCoroConnector::defaultTLSParams());\n  auto& pool4 =\n      connCache_.getPool(\"foo.com\", \"1.2.3.4\", 443, true, &connParams);\n  EXPECT_NE(&pool3, &pool4);\n\n  // reusing the same connParams should retrieve the same pool\n  auto& pool5 =\n      connCache_.getPool(\"foo.com\", \"1.2.3.4\", 443, true, &connParams);\n  EXPECT_EQ(&pool4, &pool5);\n}\n\nTEST_P(HTTPClientConnectionCacheTests, PoolMaxSize) {\n  const auto maxConnectionPools = 232;\n  HTTPClientConnectionCache connCache{evb_, folly::none, maxConnectionPools};\n  EXPECT_EQ(connCache_.getMaxNumPools(), kDefaultMaxConnectionPools);\n  EXPECT_EQ(connCache.getMaxNumPools(), maxConnectionPools);\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, GetSession) {\n  auto res = co_await connCache_.getSessionWithReservation(\n      getURL(\"\"), std::chrono::seconds(1));\n  EXPECT_EQ(res.session->getPeerAddress().getPort(), serverAddress_.getPort());\n  res.reservation.cancel();\n  res = co_await connCache_.getSessionWithReservation(\n      serverAddress_.getAddressStr(),\n      serverAddress_.getPort(),\n      (GetParam() != TransportType::TCP),\n      std::chrono::seconds(1));\n  EXPECT_EQ(res.session->getPeerAddress().getPort(), serverAddress_.getPort());\n  res.reservation.cancel();\n\n  auto tryRes = co_await co_awaitTry(\n      connCache_.getSessionWithReservation(getURL(\"\"),\n                                           80,\n                                           false,\n                                           std::chrono::milliseconds(10),\n                                           std::string(\"169.254.1.1\")));\n  EXPECT_TRUE(tryRes.hasException());\n\n  res = co_await connCache_.getSessionWithReservation(\n      getURL(\"shouldnotbeused.com\"),\n      serverAddress_.getPort(),\n      (GetParam() != TransportType::TCP),\n      std::chrono::seconds(1),\n      serverAddress_.getIPAddress().str());\n  EXPECT_EQ(res.session->getPeerAddress().getAddressStr(),\n            serverAddress_.getAddressStr());\n\n  connCache_.drain();\n}\n\nTEST_P(HTTPClientConnectionCacheTests, EmptyPoolIsReaped) {\n  auto& pool = getPool(serverAddress_);\n  EXPECT_TRUE(pool.empty());\n  EXPECT_EQ(connCache_.getNumPools(), 1);\n  connCache_.reapAllEmptyPools();\n  EXPECT_EQ(connCache_.getNumPools(), 0);\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, OnlyEmptyPoolsAreReaped) {\n  // Using port 0 so that the kernel can choose a random port for us\n  std::unique_ptr<ScopedHTTPServer> serverA{constructServer(\"127.0.0.1\", 0)},\n      serverB{constructServer(\"127.0.0.1\", 0)},\n      serverC{constructServer(\"127.0.0.1\", 0)};\n\n  folly::SocketAddress socketA{*serverA->address()},\n      socketB{*serverB->address()}, socketC{*serverC->address()};\n\n  co_await connCache_.getSessionWithReservation(getURL(socketA, \"\"),\n                                                std::chrono::seconds(1));\n  EXPECT_EQ(connCache_.getNumPools(), 1);\n  EXPECT_TRUE(getPool(socketB).empty());\n  EXPECT_EQ(connCache_.getNumPools(), 2);\n\n  // Adding this pool will cause the empty pool to be reaped resulting in\n  // a total of 2 pools.\n  co_await connCache_.getSessionWithReservation(getURL(socketC, \"\"),\n                                                std::chrono::seconds(1));\n  EXPECT_EQ(connCache_.getNumPools(), 2);\n  EXPECT_TRUE(poolExists(socketA));\n  EXPECT_FALSE(poolExists(socketB));\n  EXPECT_TRUE(poolExists(socketC));\n  connCache_.drain();\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, TraverseAtMostMaxPoolsDuringReap) {\n  std::vector<std::unique_ptr<ScopedHTTPServer>> servers;\n  for (size_t i = 0; i < 6; ++i) {\n    auto server = constructServer(\"127.0.0.1\", 0);\n    auto socket = *server->address();\n    EXPECT_TRUE(getPool(socket).empty());\n    EXPECT_EQ(connCache_.getNumPools(), i + 1);\n    servers.push_back(std::move(server));\n  }\n\n  // When we add the 7th pool (below), we remove upto 5 pools from the\n  // back as defined by kMaxPoolsToTraverseDuringReap. In this case,\n  // we remove 5 pools from the back of the cache's LRU.\n  co_await connCache_.getSessionWithReservation(getURL(\"\"),\n                                                std::chrono::seconds(1));\n  EXPECT_EQ(connCache_.getNumPools(), 2);\n  EXPECT_TRUE(poolExists(serverAddress_));\n  // This is the empty pool that survived since it was most recently added.\n  EXPECT_TRUE(poolExists(*servers.back()->address()));\n  connCache_.drain();\n}\n\n// tests drain prior to establishing connection\nCO_TEST_P_X(HTTPClientConnectionCacheTests, GetSessionDuringDrain) {\n  connCache_.drain();\n  auto res = co_await co_awaitTry(connCache_.getSessionWithReservation(\n      getURL(\"\"), std::chrono::seconds(1)));\n  auto ex = res.tryGetExceptionObject<HTTPCoroSessionPool::Exception>();\n  CHECK(ex);\n  EXPECT_EQ(ex->type, HTTPCoroSessionPool::Exception::Type::Draining);\n  EXPECT_EQ(std::string(ex->what()), \"Pool is draining\");\n}\n\n// tests cancellation prior to establishing connection\nCO_TEST_P_X(HTTPClientConnectionCacheTests, GetSessionReqCancel) {\n  folly::CancellationSource cancelSource;\n  cancelSource.requestCancellation();\n\n  auto res = co_await co_awaitTry(folly::coro::co_withCancellation(\n      cancelSource.getToken(),\n      connCache_.getSessionWithReservation(getURL(\"\"),\n                                           std::chrono::seconds(1))));\n\n  auto ex = res.tryGetExceptionObject<HTTPError>();\n  CHECK(ex);\n  EXPECT_EQ(ex->code, HTTPErrorCode::CORO_CANCELLED);\n  EXPECT_EQ(ex->msg, \"Cancelled\");\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, PendingGetSessionDrain) {\n  HTTPClientConnectionCache connCache{evb_};\n  constexpr auto kTimeout = std::chrono::seconds(2);\n  connCache.setPoolParams({.maxConnections = 1});\n  connCache.setSessionParams({.maxConcurrentOutgoingStreams = 1,\n                              .streamReadTimeout = kTimeout,\n                              .connReadTimeout = kTimeout});\n\n  // reserve the only session (only one due to maxConnection=1), which will\n  // force the second ::getSessionWithReservation call to block waiting on the\n  // session to become available (until reservation is consumed/released)\n  auto sess =\n      co_await connCache.getSessionWithReservation(getURL(\"\"), kTimeout);\n\n  // drain ConnectionCache after 100ms (to give time for second\n  // ::getSessionWithReservation to execute)\n  evb_.runAfterDelay(\n      [&]() {\n        // resume waiter\n        sess.reservation.cancel();\n        // drain\n        connCache.drain();\n      },\n      100);\n  // reserving the same session should yield an exception due to drain\n  auto ex = co_await co_awaitTry(\n      connCache.getSessionWithReservation(getURL(\"\"), kTimeout));\n\n  CHECK(ex.hasException());\n  auto poolEx = ex.tryGetExceptionObject<HTTPCoroSessionPool::Exception>();\n  CHECK(poolEx);\n  EXPECT_EQ(poolEx->type, HTTPCoroSessionPool::Exception::Type::Draining);\n  EXPECT_EQ(std::string(poolEx->what()), \"Pool is draining\");\n}\n\n// same test as above, but instead of draining while the second\n// ::getSessionWithReservation call is awaiting a free session we cancel the\n// request\nCO_TEST_P_X(HTTPClientConnectionCacheTests, PendingGetSessionReqCancel) {\n  HTTPClientConnectionCache connCache{evb_};\n  constexpr auto kTimeout = std::chrono::seconds(2);\n  connCache.setPoolParams({.maxConnections = 1});\n  connCache.setSessionParams({.maxConcurrentOutgoingStreams = 1,\n                              .streamReadTimeout = kTimeout,\n                              .connReadTimeout = kTimeout});\n\n  // reserve the only session (only one due to maxConnection=1), which will\n  // force the second ::getSessionWithReservation call to block waiting on the\n  // session to become available (until reservation is consumed/released)\n  auto sess =\n      co_await connCache.getSessionWithReservation(getURL(\"\"), kTimeout);\n\n  // request cancellation after 100 (to give time for\n  // ::getSessionWithReservation to execute)\n  folly::CancellationSource cancelSource;\n  evb_.runAfterDelay(\n      [&]() {\n        // resume waiter\n        sess.reservation.cancel();\n        // request cancel\n        cancelSource.requestCancellation();\n      },\n      100);\n\n  auto ex = co_await co_awaitTry(co_withCancellation(\n      cancelSource.getToken(),\n      connCache.getSessionWithReservation(getURL(\"\"), kTimeout)));\n  CHECK(ex.hasException());\n  auto poolEx = ex.tryGetExceptionObject<HTTPCoroSessionPool::Exception>();\n  CHECK(poolEx);\n  EXPECT_EQ(poolEx->type, HTTPCoroSessionPool::Exception::Type::Cancelled);\n  EXPECT_EQ(std::string(poolEx->what()), \"Cancelled\");\n}\n\nusing HTTPClientConnectionCacheTLSTests = HTTPClientConnectionCacheTests;\nCO_TEST_P_X(HTTPClientConnectionCacheTLSTests, NonDefaultParams) {\n  HTTPCoroSessionPool::PoolParams poolParams;\n  poolParams.maxConnections = 1;\n  auto connParams(\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam())));\n  NiceMock<wangle::MockSSLStats> tlsStats_;\n  connParams.tlsStats = &tlsStats_;\n  HTTPCoroConnector::SessionParams sessParams;\n  sessParams.maxConcurrentOutgoingStreams = 1;\n  connCache_.setPoolParams(poolParams);\n  connCache_.setConnParams(connParams);\n  connCache_.setSessionParams(sessParams);\n  EXPECT_CALL(tlsStats_, recordSSLUpstreamConnection(true));\n  auto res = co_await connCache_.getSessionWithReservation(\n      getURL(\"\"), std::chrono::seconds(1));\n  EXPECT_EQ(res.session->numTransactionsAvailable(), 0);\n  auto& pool = connCache_.getPool(serverAddress_.getAddressStr(),\n                                  serverAddress_.getAddressStr(),\n                                  serverAddress_.getPort(),\n                                  true);\n  EXPECT_TRUE(pool.full());\n\n  auto respSource = co_await res.session->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\"), std::move(res.reservation));\n  co_await HTTPSourceReader{std::move(respSource)}.read();\n\n  pool.drain();\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, ProxyConnect) {\n  HTTPClientConnectionCache::ProxyParams proxyParams;\n  proxyParams.server = serverAddress_.getAddressStr();\n  proxyParams.port = serverAddress_.getPort();\n  proxyParams.useConnect = true;\n  proxyParams.connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  HTTPClientConnectionCache connCache{evb_, std::move(proxyParams)};\n  auto endpointConnParams = std::get<HTTPCoroConnector::ConnectionParams>(\n      getConnParams(TransportType::TLS));\n  // ConnCache conn params are a template, so need to include TLS context even\n  // when used for plaintext connections\n  connCache.setConnParams(endpointConnParams);\n  // Ask for an HTTP URL to avoid attempting a TLS handshaked on the CONNECT\n  // stream, but the endpoint expects port 443.\n  auto res = co_await connCache.getSessionWithReservation(\n      \"http://example.com:443/\", std::chrono::seconds(1));\n  EXPECT_EQ(res.session->getPeerAddress().getPort(), 443);\n  res.session->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPClientConnectionCacheTests, ProxyGet) {\n  HTTPClientConnectionCache::ProxyParams proxyParams;\n  proxyParams.server = serverAddress_.getAddressStr();\n  proxyParams.port = serverAddress_.getPort();\n  proxyParams.useConnect = false;\n  proxyParams.connParams =\n      std::get<HTTPCoroConnector::ConnectionParams>(getConnParams(GetParam()));\n  HTTPClientConnectionCache connCache{evb_, std::move(proxyParams)};\n  auto res = co_await connCache.getSessionWithReservation(\n      \"foo\", 12345, false, std::chrono::seconds(1));\n  EXPECT_EQ(res.session->getPeerAddress().getPort(), serverAddress_.getPort());\n  res.session->initiateDrain();\n}\n\nINSTANTIATE_TEST_SUITE_P(HTTPClientTests,\n                         HTTPClientTests,\n                         Values(TransportType::TCP,\n                                TransportType::TLS,\n                                TransportType::TLS_FIZZ,\n                                TransportType::QUIC),\n                         transportTypeToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPClientTLSTests,\n                         HTTPClientTLSTests,\n                         Values(TransportType::TLS,\n                                TransportType::TLS_FIZZ,\n                                TransportType::QUIC),\n                         transportTypeToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPClientTLSOnlyTests,\n                         HTTPClientTLSOnlyTests,\n                         Values(TransportType::TLS),\n                         transportTypeToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPCoroSessionPoolTests,\n                         HTTPCoroSessionPoolTests,\n                         Values(TransportType::TCP,\n                                TransportType::TLS,\n                                TransportType::TLS_FIZZ,\n                                TransportType::QUIC),\n                         transportTypeToTestName);\n\n// TODO: TransportType::QUIC - doesn't currently report handshake/resume\nINSTANTIATE_TEST_SUITE_P(HTTPCoroSessionPoolTLSTests,\n                         HTTPCoroSessionPoolTLSTests,\n                         Values(TransportType::TLS, TransportType::TLS_FIZZ),\n                         transportTypeToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPCoroSessionPoolSSLTests,\n                         HTTPCoroSessionPoolSSLTests,\n                         Values(TransportType::TLS),\n                         transportTypeToTestName);\n\nINSTANTIATE_TEST_SUITE_P(CertReloadSessionPoolTests,\n                         CertReloadSessionPoolTests,\n                         Values(TransportType::TLS,\n                                TransportType::TLS_FIZZ,\n                                TransportType::QUIC),\n                         transportTypeToTestName);\n\n// No QUIC, for now\nINSTANTIATE_TEST_SUITE_P(HTTPClientConnectionCacheTests,\n                         HTTPClientConnectionCacheTests,\n                         Values(TransportType::TCP,\n                                TransportType::TLS,\n                                TransportType::TLS_FIZZ),\n                         transportTypeToTestName);\n\n// TLS only\nINSTANTIATE_TEST_SUITE_P(HTTPClientConnectionCacheTLSTests,\n                         HTTPClientConnectionCacheTLSTests,\n                         Values(TransportType::TLS, TransportType::TLS_FIZZ),\n                         transportTypeToTestName);\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n\nusing folly::coro::co_nothrow;\n\nnamespace {\n\n// gets the sni sent by client\nusing namespace proxygen::coro;\nstd::string getSniOrEmpty(const HTTPSessionContextPtr& ctx) {\n  wangle::TransportInfo info;\n  ctx->getCurrentTransportInfo(&info, /*includeSetupFields=*/true);\n  return info.sslServerName ? *info.sslServerName : \"\";\n}\n\n} // namespace\n\nnamespace proxygen::coro::test {\n\n/*static*/ HTTPServer::Config HTTPClientTests::getServerConfig(\n    const std::string& ip,\n    const uint16_t port,\n    const TransportType transportType) {\n  auto tlsConfig = HTTPServer::getDefaultTLSConfig();\n  tlsConfig.isDefault = true;\n  tlsConfig.clientVerification =\n      folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n  tlsConfig.setNextProtocols({\"h2\", \"http/1.1\"});\n  try {\n    const std::string kTestDir = getContainingDirectory(XLOG_FILENAME).str();\n    tlsConfig.setCertificate(kTestDir + \"certs/test_cert1.pem\",\n                             kTestDir + \"certs/test_key1.pem\",\n                             \"\");\n  } catch (const std::exception& ex) {\n    XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n  }\n  HTTPServer::Config serverConfig;\n  serverConfig.socketConfig.bindAddress.setFromIpPort(ip, port);\n  if (transportType != TransportType::TCP) {\n    serverConfig.socketConfig.sslContextConfigs.emplace_back(\n        std::move(tlsConfig));\n  }\n  if (transportType == TransportType::QUIC) {\n    serverConfig.quicConfig = HTTPServer::QuicConfig();\n  }\n  return serverConfig;\n}\n\n/*static*/ std::unique_ptr<ScopedHTTPServer> HTTPClientTests::constructServer(\n    const std::string& ip,\n    const uint16_t port,\n    const TransportType transportType,\n    std::shared_ptr<TestHandler> testHandler) {\n  // Disable cert verification since the server is self-signed\n  HTTPClient::setDefaultCAPaths({});\n  HTTPClient::setDefaultFizzCertVerifier(\n      std::make_shared<InsecureVerifierDangerousDoNotUseInProduction>());\n  auto serverConfig = getServerConfig(ip, port, transportType);\n  return ScopedHTTPServer::start(std::move(serverConfig), testHandler);\n}\n\nstd::unique_ptr<ScopedHTTPServer> HTTPClientTests::constructServer(\n    const std::string& ip,\n    const uint16_t port,\n    std::shared_ptr<TestHandler> testHandler) {\n  return constructServer(ip, port, GetParam(), std::move(testHandler));\n}\n\nvoid HTTPClientTests::SetUp() {\n  testHandler_ = std::make_shared<TestHandler>();\n  server_ = constructServer(\"127.0.0.1\", 0, testHandler_);\n  serverAddress_ = *server_->address();\n}\n\nfolly::coro::Task<HTTPSourceHolder> TestHandler::handleRequest(\n    folly::EventBase* evb,\n    HTTPSessionContextPtr ctx,\n    HTTPSourceHolder requestSource) {\n\n  // all connections are made directly with ip; expect empty sni\n  auto sni = getSniOrEmpty(ctx);\n  EXPECT_TRUE(sni.empty()) << \"unexpected sni=\" << sni;\n\n  auto headerEvent = co_await co_nothrow(requestSource.readHeaderEvent());\n\n  auto request = headerEvent.headers.get();\n  EXPECT_EQ(request->isSecure(), ctx->getSetupTransportInfo().secure);\n\n  if (request->getMethod() == HTTPMethod::CONNECT) {\n    // Hack to silence the expect in connectHandler\n    request->getHeaders().add(\"Foo\", \"Bar\");\n    auto hybridSource = new HTTPHybridSource(std::move(headerEvent.headers),\n                                             requestSource.release());\n    hybridSource->setHeapAllocated();\n    auto source =\n        co_await connectHandler_.handleRequest(evb, ctx, hybridSource);\n\n    co_return source;\n  }\n\n  if (request->getPathAsStringPiece() == \"/error\") {\n    co_return HTTPFixedSource::makeFixedResponse(500, \"Error\");\n  }\n  if (request->getPathAsStringPiece() == \"/abortHeaders\") {\n    co_yield folly::coro::co_error(HTTPError(HTTPErrorCode::CANCEL, \"cancel\"));\n  }\n  if (request->getPathAsStringPiece() == \"/trailers\") {\n    XCHECK(headerEvent.eom);\n    auto resp = HTTPFixedSource::makeFixedResponse(200, \"Trailers\");\n    resp->trailers_ = std::make_unique<HTTPHeaders>();\n    resp->trailers_->add(\"Test\", \"Success\");\n    co_return resp;\n  }\n  if (request->getPathAsStringPiece() == \"/incompleteBody\") {\n    HTTPSourceReader reader;\n    auto hybridSource = new HTTPHybridSource(std::move(headerEvent.headers),\n                                             requestSource.release());\n    hybridSource->setHeapAllocated();\n    auto resp = co_await folly::coro::co_awaitTry(\n        reader.setSource(hybridSource).read());\n    if (resp.hasException<HTTPError>()) {\n      EXPECT_NE(dynamic_cast<const HTTPError*>(resp.exception().get_exception())\n                    ->code,\n                HTTPErrorCode::READ_TIMEOUT);\n    } else {\n      EXPECT_TRUE(false) << \"http exception is expected for this handler\";\n    }\n\n    co_return HTTPFixedSource::makeFixedResponse(500, \"failed\");\n  }\n  if (request->getPathAsStringPiece().starts_with(\"/bodyError\")) {\n    auto path = request->getPathAsStringPiece();\n    auto delimiter = std::find(path.begin(), path.end(), '_');\n    co_return new ErrorSource(\n        std::string(\"super long response that can't fit in one frame\"),\n        false,\n        delimiter == path.end()\n            ? 0\n            : folly::to<uint32_t>(delimiter + 1, path.end()));\n  }\n  if (request->getPathAsStringPiece().starts_with(\"/earlyreturn\")) {\n    auto hybridSource = new HTTPHybridSource(std::move(headerEvent.headers),\n                                             requestSource.release());\n    hybridSource->setHeapAllocated();\n    co_withExecutor(evb,\n                    [](HTTPHybridSource* source) -> folly::coro::Task<void> {\n                      HTTPSourceReader reader(source);\n                      co_await reader.read();\n                    }(hybridSource))\n        .start();\n    co_return HTTPFixedSource::makeFixedResponse(200);\n  }\n  // echo source\n  auto resp = new EchoBodySource(\n      std::move(requestSource),\n      200,\n      headerEvent.eom,\n      {{std::string(\"x-method\"), request->getMethodString()},\n       {std::string(\"x-host\"),\n        request->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST)},\n       {std::string(\"x-custom-header-count\"),\n        folly::to<std::string>(\n            request->getHeaders().getNumberOfValues(\"custom\"))},\n       {std::string(\"src-port\"),\n        folly::to<std::string>(ctx->getPeerAddress().getPort())}},\n      (request->getPathAsStringPiece() == \"/abortBody\"));\n  co_return resp;\n}\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPError.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n#include \"proxygen/lib/http/coro/server/ScopedHTTPServer.h\"\n#include \"proxygen/lib/http/coro/test/HTTPTestSources.h\"\n#include \"proxygen/lib/http/coro/transport/test/HTTPConnectTransportTest.h\"\n#include <folly/logging/xlog.h>\n\n#include <algorithm>\n#include <folly/coro/Baton.h>\n#include <folly/io/async/AsyncSocketException.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/test/TestUtils.h>\n#include <proxygen/lib/utils/TestUtils.h>\n\n#include <folly/portability/GTest.h>\n\nnamespace proxygen::coro::test {\nusing namespace testing;\nusing folly::AsyncSocketException;\n\nenum class TransportType { TCP, TLS, TLS_FIZZ, QUIC };\n\ninline std::string transportTypeToTestName(\n    const testing::TestParamInfo<TransportType>& info) {\n  switch (info.param) {\n    case TransportType::TLS:\n      return \"tls\";\n    case TransportType::TLS_FIZZ:\n      return \"fizz\";\n    case TransportType::TCP:\n      return \"tcp\";\n    case TransportType::QUIC:\n      return \"quic\";\n  }\n}\n\nclass TestHandler : public HTTPHandler {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override;\n\n  ConnectHandler connectHandler_;\n};\n\nclass HTTPClientTests : public TestWithParam<TransportType> {\n public:\n  void SetUp() override;\n\n  void TearDown() override {\n    testHandler_->connectHandler_.resetExceptionExpected();\n    server_.reset();\n  }\n\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n  std::string getURL(const std::string& hostname,\n                     uint16_t port,\n                     const std::string& path) {\n    return folly::to<std::string>(\"http\",\n                                  (GetParam() != TransportType::TCP ? \"s\" : \"\"),\n                                  \"://\",\n                                  hostname,\n                                  \":\",\n                                  port,\n                                  path);\n  }\n\n  std::string getURL(const folly::SocketAddress& serverAddress,\n                     const std::string& path) {\n    return getURL(serverAddress.getAddressStr(), serverAddress.getPort(), path);\n  }\n\n  std::string getURL(const std::string& path) {\n    return getURL(serverAddress_, path);\n  }\n\n  bool useQuic() const {\n    return GetParam() == TransportType::QUIC;\n  }\n\n  static std::unique_ptr<ScopedHTTPServer> constructServer(\n      const std::string& ip,\n      const uint16_t port,\n      const TransportType transportType,\n      std::shared_ptr<TestHandler> testHandler);\n\n  std::unique_ptr<ScopedHTTPServer> constructServer(\n      const std::string& ip,\n      const uint16_t port,\n      std::shared_ptr<TestHandler> testHandler =\n          std::make_shared<TestHandler>());\n\n protected:\n  static HTTPServer::Config getServerConfig(const std::string& ip,\n                                            const uint16_t port,\n                                            const TransportType transportType);\n\n  enum class ExceptionType {\n    TIMED_OUT,\n    SSL_ERROR,\n  };\n  static void expectException(TransportType ttype,\n                              folly::Try<HTTPCoroSession*>& sess,\n                              ExceptionType exType) {\n    if (ttype == TransportType::QUIC) {\n      switch (exType) {\n        case ExceptionType::TIMED_OUT: {\n          auto ex = sess.tryGetExceptionObject<quic::QuicInternalException>();\n          EXPECT_EQ(ex->errorCode(), quic::LocalErrorCode::CONNECT_FAILED);\n          break;\n        }\n        case ExceptionType::SSL_ERROR: {\n          auto ex = sess.tryGetExceptionObject<quic::QuicTransportException>();\n          bool isCryptoError = uint16_t(ex->errorCode()) &\n                               uint16_t(quic::TransportErrorCode::CRYPTO_ERROR);\n          EXPECT_TRUE(isCryptoError);\n          break;\n        }\n      }\n    } else {\n      auto ex = sess.tryGetExceptionObject<AsyncSocketException>();\n      folly::AsyncSocketException::AsyncSocketExceptionType aseType{};\n      switch (exType) {\n        case ExceptionType::TIMED_OUT:\n          aseType = AsyncSocketException::AsyncSocketExceptionType::TIMED_OUT;\n          break;\n        case ExceptionType::SSL_ERROR:\n          aseType = AsyncSocketException::AsyncSocketExceptionType::SSL_ERROR;\n          break;\n      }\n      EXPECT_EQ(ex->getType(), aseType);\n    }\n  }\n\n  folly::EventBase evb_;\n  std::shared_ptr<TestHandler> testHandler_;\n  std::unique_ptr<ScopedHTTPServer> server_;\n  folly::SocketAddress serverAddress_;\n};\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/HTTPConnectIntegrationTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/server/samples/fwdproxy/ConnectSource.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include \"proxygen/lib/http/session/HTTPSessionStats.h\"\n#include <proxygen/lib/http/session/test/MockHTTPSessionStats.h>\n\nusing folly::coro::co_awaitTry;\nusing folly::coro::co_error;\nusing folly::coro::co_result;\n\nnamespace {\n\nconstexpr std::chrono::milliseconds kConnectTimeout{100};\nconstexpr std::string_view kSuspendEgressMs = \"suspend_egress_ms\";\n\nauto parseQueryParamAsInt(const proxygen::HTTPMessage& message,\n                          std::string_view queryParamKey) {\n  const auto& queryParamVal = message.getQueryParam(std::string(queryParamKey));\n  return folly::tryTo<uint64_t>(queryParamVal);\n}\n\n} // namespace\n\nnamespace proxygen::coro::test {\n\nclass ConnectHandler : public HTTPHandler {\n public:\n  ConnectHandler(folly::SocketAddress serverAddress)\n      : serverAddress_(std::move(serverAddress)) {\n  }\n\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override {\n    XLOG(DBG6) << \"ConnectHandler connecting to upstream\";\n    auto transport =\n        co_await co_awaitTry(folly::coro::Transport::newConnectedSocket(\n            evb, serverAddress_, std::chrono::milliseconds(50)));\n    // loopback connect should never fail?\n    XCHECK(!transport.hasException()) << \"loopback connect failed!\";\n    auto connectSource = std::make_unique<ConnectSource>(\n        std::make_unique<folly::coro::Transport>(std::move(transport).value()),\n        std::move(requestSource));\n    co_withExecutor(evb, connectSource->readRequestSendUpstream()).start();\n    co_return connectSource.release();\n  }\n\n private:\n  folly::SocketAddress serverAddress_;\n};\n\nclass Handler : public TestHandler {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override {\n    auto headerEvent = co_await co_awaitTry(requestSource.readHeaderEvent());\n    XLOG(DBG6) << \" Handler::handleRequest; got headerEvent; ex=\"\n               << int(headerEvent.hasException());\n    if (headerEvent.hasException()) {\n      co_yield co_error(headerEvent.exception());\n    }\n\n    auto method = headerEvent->headers->getMethod();\n    XCHECK(method);\n    if (method == HTTPMethod::CONNECT) {\n      XLOG(DBG6) << \"Handler::handleRequest HTTPMethod::CONNECT\";\n      if (headerEvent->eom) {\n        XLOG(DBG4) << \"eom in header event for CONNECT request\";\n        co_yield co_error(\n            HTTPError{HTTPErrorCode::REFUSED_STREAM, \"eom in connect request\"});\n      }\n      auto connectRes = co_await co_awaitTry(\n          ConnectHandler(serverAddress_)\n              .handleRequest(evb, ctx, std::move(requestSource)));\n      co_yield co_result(std::move(connectRes));\n    }\n\n    XLOG(DBG6) << \"header event query string =\"\n               << headerEvent->headers->getQueryString();\n    uint64_t suspendEgressMs =\n        parseQueryParamAsInt(*headerEvent->headers, kSuspendEgressMs)\n            .value_or(0);\n    if (suspendEgressMs > 0) {\n      XLOG(DBG6) << folly::to<std::string>(\n          \"suspending ingress for \", suspendEgressMs, \"ms\");\n      co_await folly::coro::sleep(std::chrono::milliseconds(suspendEgressMs));\n    }\n\n    co_return HTTPFixedSource::makeFixedResponse(200);\n  }\n\n  folly::SocketAddress serverAddress_;\n};\n\n/**\n * Constructs a server; this server's handler is special in that it acts as a\n * fwdproxy in the case of CONNECT requests; it establishes a tunnel back to\n * itself, allowing us to test end-to-end client<->proxy<->server HTTP connect\n * tunnels (with HTTP being the tunneled protocol).\n *\n *\n * This effectively looks something like:\n *\n * +--------+   HTTP/TLS   +--------+   TCP    +--------+\n * | client | <----------> | proxy  | <------> | server |\n * +--------+              +--------+          +--------+\n *     ^                                           ^\n *     |        HTTP/TLS via proxy<->server        |\n *     |-------------------------------------------|\n */\nclass HTTPConnectIntegrationTest : public ::testing::Test {\n public:\n  void SetUp() override {\n    server_ = HTTPClientTests::constructServer(\n        \"127.0.0.1\", 0, TransportType::TLS_FIZZ, handler_);\n    handler_->serverAddress_ = *server_->address();\n  }\n\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  const folly::SocketAddress& getServAddr() {\n    return handler_->serverAddress_;\n  }\n\n  HTTPCoroConnector::ConnectionParams getConnParams() {\n    return HTTPClient::getConnParams(HTTPClient::SecureTransportImpl::TLS);\n  }\n\n  // ::getProxySess should never fail, sanity checks no exception is yielded\n  folly::coro::Task<HTTPCoroSession*> getProxySess() {\n    auto proxySess = co_await co_awaitTry(HTTPCoroConnector::connect(\n        &evb_, getServAddr(), kConnectTimeout, getConnParams()));\n    XCHECK(!proxySess.hasException())\n        << \"proxySess ex=\" << proxySess.exception().what();\n    co_return proxySess.value();\n  }\n\n  folly::coro::Task<HTTPCoroSession*> proxyConnect(\n      HTTPCoroSession* proxySess,\n      std::string authority,\n      HTTPCoroConnector::SessionParams sessParams =\n          HTTPCoroConnector::defaultSessionParams()) {\n    auto res = co_await co_awaitTry(\n        HTTPCoroConnector::proxyConnect(proxySess,\n                                        *proxySess->reserveRequest(),\n                                        /*authority=*/authority,\n                                        /*connectUnique=*/false,\n                                        /*timeout=*/kConnectTimeout,\n                                        getConnParams(),\n                                        sessParams));\n    co_return res;\n  }\n\n  folly::EventBase evb_;\n  std::shared_ptr<Handler> handler_{std::make_shared<Handler>()};\n  std::unique_ptr<ScopedHTTPServer> server_;\n};\n\nCO_TEST_F_X(HTTPConnectIntegrationTest, Simple) {\n  // connect to proxy\n  auto proxySess = co_await getProxySess();\n\n  // establish tunnel to server via proxy\n  auto authority = folly::to<std::string>(\n      \"https://localhost:\", getServAddr().getPort(), \"/\");\n  auto serverSess = co_await co_awaitTry(proxyConnect(proxySess, authority));\n  XCHECK(!serverSess.hasException())\n      << \"serverSess ex=\" << serverSess.exception().what();\n\n  // simple GET request on the tunneled session should yield 200\n  auto resp = co_await co_awaitTry(\n      HTTPClient::get(/*session=*/serverSess.value(),\n                      /*reservation=*/*serverSess.value()->reserveRequest(),\n                      /*url=*/URL{authority}));\n  XCHECK(!resp.hasException()) << \"resp ex=\" << resp.exception();\n  XCHECK_EQ(resp->headers->getStatusCode(), 200);\n\n  serverSess.value()->initiateDrain();\n  proxySess->initiateDrain();\n}\n\n/**\n * AsyncSocketException::TIMED_OUT ex yielded from HTTPConnectTransport::read()\n * should initiate drain, and continue reading successfully\n */\nCO_TEST_F_X(HTTPConnectIntegrationTest, TimeoutConnectTransportRead) {\n  // connect to proxy\n  auto proxySess = co_await getProxySess();\n\n  // establish tunnel to server via proxy\n  auto authority = folly::to<std::string>(\n      \"https://localhost:\", getServAddr().getPort(), \"/\");\n\n  // set a 200ms read timeout on the connect transport\n  HTTPCoroConnector::SessionParams sessParams{\n      HTTPCoroConnector::defaultSessionParams()};\n  sessParams.connReadTimeout = std::chrono::milliseconds(200);\n  auto serverSess =\n      co_await co_awaitTry(proxyConnect(proxySess, authority, sessParams));\n  XCHECK(!serverSess.hasException())\n      << \"serverSess ex=\" << serverSess.exception().what();\n\n  // send a request with a suspend_egress_ms query param which is read by the\n  // server to suspend writes for said amount, triggering the timeout\n  auto url = folly::to<std::string>(\"/?\", kSuspendEgressMs, \"=2000\");\n  auto reqSource = HTTPFixedSource::makeFixedRequest(std::move(url));\n\n  auto respSource =\n      co_await co_awaitTry(serverSess.value()->sendRequest(reqSource));\n  XCHECK(!respSource.hasException()) << respSource.exception().what();\n  HTTPSourceReader reader{std::move(respSource).value()};\n\n  auto readResult = co_await co_awaitTry(reader.read());\n  XCHECK(!readResult.hasException()) << readResult.exception();\n\n  serverSess.value()->initiateDrain();\n  proxySess->initiateDrain();\n}\n\nCO_TEST_F_X(HTTPConnectIntegrationTest, CancelAfterTunnelTlsConnect) {\n  /**\n   * This test establishes a connection to the proxy and establishes a tls\n   * connection via the HTTP CONNECT tunnel. The goal is to request cancellation\n   * of token (via destructing the HTTPCoroSessionPool) immediately after the\n   * TLS handshake via the CONNECT tunnel.\n   */\n  using PoolPtr = std::shared_ptr<HTTPCoroSessionPool>;\n\n  // LoopCallback to destruct the HTTPCoroSessionPool\n  class LoopCallback : public folly::EventBase::LoopCallback {\n   public:\n    LoopCallback(PoolPtr pool, PoolPtr proxyPool)\n        : pool_(std::move(pool)), proxyPool_(std::move(proxyPool)) {\n    }\n    void runLoopCallback() noexcept override {\n      proxyPool_.reset();\n      pool_.reset();\n      delete this;\n    }\n    PoolPtr pool_{nullptr}, proxyPool_{nullptr};\n  };\n\n  // heuristic to destruct the pools immediately after the TLS handshake via\n  // CONNECT tunnel\n  class WaitForBodyEvent : public proxygen::DummyHTTPSessionStats {\n   public:\n    ~WaitForBodyEvent() override = default;\n    void recordPendingBufferedReadBytes(int64_t) noexcept override {\n      if (++numEvents == 2) {\n        baton.post();\n        pool->getEventBase()->runBeforeLoop(\n            new LoopCallback(std::move(pool), std::move(proxyPool)));\n      }\n    }\n    folly::coro::Baton baton;\n    std::shared_ptr<HTTPCoroSessionPool> proxyPool;\n    std::shared_ptr<HTTPCoroSessionPool> pool;\n    uint8_t numEvents{0};\n  } waitForBodyEvent;\n\n  const auto& servAddr = getServAddr();\n  waitForBodyEvent.proxyPool = std::make_shared<HTTPCoroSessionPool>(\n      &evb_,\n      servAddr,\n      HTTPCoroSessionPool::defaultPoolParams(),\n      getConnParams());\n  waitForBodyEvent.pool = std::make_unique<HTTPCoroSessionPool>(\n      &evb_,\n      servAddr.getAddressStr(),\n      servAddr.getPort(),\n      waitForBodyEvent.proxyPool,\n      HTTPCoroSessionPool::defaultPoolParams(),\n      getConnParams());\n\n  auto proxySess =\n      co_await waitForBodyEvent.proxyPool->getSessionWithReservation();\n  proxySess.session->setSessionStats(&waitForBodyEvent);\n\n  co_withExecutor(&evb_, waitForBodyEvent.pool->getSessionWithReservation())\n      .start();\n  co_await waitForBodyEvent.baton;\n\n  proxySess.session->setSessionStats(nullptr);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/MockHTTPClient.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <gmock/gmock.h>\n\n#include \"proxygen/lib/http/coro/client/HTTPClient.h\"\n\nnamespace proxygen::coro {\n\nclass MockHTTPClient {\n public:\n  MOCK_METHOD5(get,\n               folly::coro::Task<HTTPClient::Response>(\n                   folly::EventBase* evb,\n                   std::string url,\n                   std::chrono::milliseconds timeout,\n                   bool useQuic,\n                   HTTPClient::RequestHeaderMap requestHeaders));\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/certs/test_cert1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc8CAQowDQYJKoZIhvcNAQEFBQAwQzELMAkGA1UEBhMCVVMxDTALBgNV\nBAoMBEFzb3gxJTAjBgNVBAMMHEFzb3ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw\nHhcNMTQwOTE5MjIxMzQ2WhcNNDIwMjA0MjIxMzQ2WjAwMQswCQYDVQQGEwJVUzEN\nMAsGA1UECgwEQXNveDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAprvPkBYr6LTFOldRjTQ9zKf86tBu9kG1a4CLu4c1\n2twWTf04b8lfpG5qUMt13IeI5CH9ygjkLz6gZjsGDICVokG5P5fd9k+3eIv6m0K5\nrgNUXeSYJJTbfIhEw99fdI4tpu5irtnWLGGsDApF1O2NDLYh6U0+eB1OWOGhqrSU\nAMPibief2jtLsETaRZrYSFknPfgrNjzcIfhnAv3rMnkEc55knV8l7UZCLgUaRPfS\n4ZcTe1VJghHPCbbfQ6AEcHZaXhOlX0voAXesB5RVuyPMuhQzBfasBstjITIpdbQI\nAlnFuF/vo8JRhqJKjOWek6DJyH7yjw9ZtvXsMTJCun9M7wIDAQABMA0GCSqGSIb3\nDQEBBQUAA4IBAQCdghgh4hK0HUgvr+Ue2xUgAkEhQK7nvBlxw42l64zWNIkVrg3C\nsGBx1/ZV7sVrrz5P8LkoZmKcgSoaZQRhiZ9P+nBj4hUz8oFYJ2xTl2Bo1UmEoz+r\nz63WerLLb48HQLrGJN/V1Uodjb/eVRwY16qw0JoaRg3BGbO2k19jeNIfpp00atic\nxvgxZsHuRrax4PkL6ObrASILj78AOzPmKOlMk2cbS+Ol4WJNzbqFDQaR3QXv4WSR\n6td3LlJtSyMWjMnkYOOidLYsSQ5bVnWbnP/bj/apRXxX9wi7ez739Gqc4bylJgW5\nYm+TCytFhaK6z05whWCDcD6CrXyFGer/Cqfv\n-----END CERTIFICATE-----\n\n"
  },
  {
    "path": "proxygen/lib/http/coro/client/test/certs/test_key1.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAprvPkBYr6LTFOldRjTQ9zKf86tBu9kG1a4CLu4c12twWTf04\nb8lfpG5qUMt13IeI5CH9ygjkLz6gZjsGDICVokG5P5fd9k+3eIv6m0K5rgNUXeSY\nJJTbfIhEw99fdI4tpu5irtnWLGGsDApF1O2NDLYh6U0+eB1OWOGhqrSUAMPibief\n2jtLsETaRZrYSFknPfgrNjzcIfhnAv3rMnkEc55knV8l7UZCLgUaRPfS4ZcTe1VJ\nghHPCbbfQ6AEcHZaXhOlX0voAXesB5RVuyPMuhQzBfasBstjITIpdbQIAlnFuF/v\no8JRhqJKjOWek6DJyH7yjw9ZtvXsMTJCun9M7wIDAQABAoIBAQCGJrJ4Yf5uO5Q8\nvqjVDeVzVu4+F/pPlMrddg33knCYaWBg246fEs0rRdOwsiNgjoRr2ZWTCthd0uvH\nlVHmmUbLyEm+iviCB9281hOK/ILdKbyl1xk6xbJbXmDFoGHzK7o7h65KtOaHywZc\noZ9SFNfaFGjwh7/tcNbq2I/1A1nZynTko6iLVpgV0kkQCpaweFYQMXWv/ELkFHeL\n7tFIA50XFXbNDqnAuaW6XrIrW33ZeOJfruF2OG+QVWyTBgczk0fodDZJFS5MbDXu\nUB+W1nDhakZFbugtDSXMd3BMnLZFdsa12FYTMNG050w2OTHOF5ILX+IFwzbnlboX\nfbralUKRAoGBANnjttZWcNtM4L8JkUJwXsDgTD/0d+iu5gCmSs0p9E4wGbWlu+In\ncNE6CV4Dy+GMk5GzXFR+GV/IlPvVzSOmbFsFdBi8L3c/1IrPbQv8dEosKm6BvV9O\n0zIBaPuzgU2yyBFxdpfsHynAZoLdY3rq6IJdNmJrmDcVgEfBVOExU7EVAoGBAMPl\nhqNmGi3VwHPQ2iiuM48ijPbuS0hK3dUjx2A4otOYAro86Q4egcdtyBOONhBwD89h\nI6BUo+vReV6ikI8LQfoplBaRos7qJ2e9SOxmRIJGAZPkGlFF0uljxKZ2Hdtmruae\nmJOZqKCa38sTnqWyXV/xCXE5X94EXuJP17L2Bt7zAoGAOG7gFheBV2tL8m657qlI\nAVCWryHURLG35IctbIHnQrD2l7N7PBHXCHmtn2oATkSom94GleOrEsHSxH8ViJw8\nCD8bWKS07n/bvrAGoEocnHFf9AsqTxsNXDA9TqOpY8RgSRRIEQUY9Sld45sPfvCE\nk+8sfMU9QVcSSINsRn8OHBkCgYEAgzBGD01EQOfB/42hW9b1fmjEAGYrElnY33Eb\nhyvGl29YfEJoTOVPQjAZ6ka1nCJ/5ACIrEmikT1yS1cQ+kquv4pyuv6DCpCzHP0d\nRfti699YFSOQIFdjXJtMybGWYyUMAjO5uDcSP6QYNVaJSyv87lBsY1/p/LPumx6f\nNCEhDtMCgYBpcK4f2E+JjaGHABX5OS5Soegtgj7JjZv/M3N2OLvX2xrlkxAEPlJ5\nnvaVjikBcOsj3/+LDrBMDoEbG2JFaopiue8pW+tZWfbhJw0pf02f+hgHjCaR+1Ny\nQqd+ERH7vFjwzc3UuZay1NbU9/wVMNsL7jWKnvsKKCk9PxG2OzP2iQ==\n-----END RSA PRIVATE KEY-----\n\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_filters_rate_limit_filter\n  SRCS\n    RateLimitFilter.cpp\n  DEPS\n    Folly::folly_coro_sleep\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_utils_time_util\n)\n\nproxygen_add_library(proxygen_http_coro_filters_compression_filter\n  SRCS\n    CompressionFilter.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_utils_compression_filter_utils\n)\n\nproxygen_add_library(proxygen_http_coro_filters_decompression_filter\n  SRCS\n    DecompressionFilter.cpp\n  DEPS\n    proxygen_utils_zstd_stream_decompressor\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_coro_filters_mutate_filter\n    proxygen_utils_compression_filter_utils\n)\n\nproxygen_add_library(proxygen_http_coro_filters_compression_filter_factory\n  EXPORTED_DEPS\n    proxygen_http_coro_filters_ServerFilterFactory\n    proxygen_http_coro_filters_compression_filter\n    proxygen_http_coro_filters_visitor_filter\n)\n\nproxygen_add_library(proxygen_http_coro_filters_decompression_filter_factory\n  SRCS\n    DecompressionFilterFactory.cpp\n  DEPS\n    proxygen_http_coro_filters_decompression_filter\n  EXPORTED_DEPS\n    proxygen_http_coro_filters_FilterFactory\n    proxygen_http_coro_filters_ServerFilterFactory\n)\n\nproxygen_add_library(proxygen_http_coro_filters_status_1xx_filter\n  SRCS\n    Status1xxFilter.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_filters_FilterFactory\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_filters_ServerFilterFactory\n  EXPORTED_DEPS\n    proxygen_http_coro_filters_FilterFactory\n)\n\nproxygen_add_library(proxygen_http_coro_filters_logger\n  SRCS\n    Logger.cpp\n  DEPS\n    proxygen_http_priority_functions\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_coro_source\n    proxygen_sampling_sampled\n    Folly::folly_coro_collect\n    Folly::folly_coro_promise\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_filters_redirect_handler\n  SRCS\n    HTTPRedirectHandler.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_coro_source\n    proxygen_http_coro_client_http_session_factory\n)\n\nproxygen_add_library(proxygen_http_coro_filters_visitor_filter\n  SRCS\n    VisitorFilter.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_filters_mutate_filter\n  SRCS\n    MutateFilter.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_filters_transform_filter\n  SRCS\n    TransformFilter.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_filters_request_context_filter_factory\n  SRCS\n    RequestContextFilterFactory.cpp\n  DEPS\n    Folly::folly_io_async_request_context\n  EXPORTED_DEPS\n    proxygen_http_coro_filters_ServerFilterFactory\n)\n\nproxygen_add_library(proxygen_http_coro_filters_StatsFilterUtil\n  SRCS\n    StatsFilterUtil.cpp\n  DEPS\n    proxygen_coro_source\n    proxygen_http_coro_filters_visitor_filter\n    proxygen_http_stats_HttpServerStats\n    Folly::folly_stop_watch\n)\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/CompressionFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/CompressionFilter.h\"\n\nnamespace {\nusing folly::coro::co_error;\n}\n\n#define CompressionFailedError \\\n  HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"CompressionFilter failed\")\n\nnamespace proxygen::coro {\nfolly::coro::Task<HTTPHeaderEvent> CompressionFilter::readHeaderEvent() {\n  // read from source\n  auto headerEvent =\n      co_await co_awaitTry(readHeaderEventImpl(/*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n\n  // check if compression should be skipped\n  skipCompression_ = headerEvent.hasException() || headerEvent->eom ||\n                     !params_->has_value() ||\n                     !CompressionFilterUtils::shouldCompress(\n                         *headerEvent->headers, params_->value());\n  if (skipCompression_) {\n    // passthru if skipping compression / has exception\n    co_return headerEvent;\n  }\n\n  // try to create compressor\n  compressor_ = params_->value().compressorFactory();\n  if (!compressor_ || compressor_->hasError()) {\n    co_yield co_error(CompressionFailedError);\n  }\n\n  // always set compressed req/res to chunked\n  headerEvent->headers->getHeaders().remove(HTTP_HEADER_CONTENT_LENGTH);\n  headerEvent->headers->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                         params_->value().headerEncoding);\n  headerEvent->headers->setIsChunked(true);\n  co_return headerEvent;\n}\n\nfolly::coro::Task<HTTPBodyEvent> CompressionFilter::readBodyEvent(\n    uint32_t max) {\n  // check if there's a pending event (i.e. trailer), deliver before reading\n  // from source\n  if (pendingBodyEvent_) {\n    // wrap in folly::Try<> for lifetime() guard\n    auto bodyEvent =\n        folly::Try<HTTPBodyEvent>(std::move(pendingBodyEvent_.value()));\n    auto guard = folly::makeGuard(lifetime(bodyEvent));\n    co_return std::move(bodyEvent);\n  }\n\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventImpl(max, /*deleteOnDone=*/false));\n  // note: guard is dismissed below if we need to buffer the trailers until next\n  // ::readBodyEvent(). Dismissing guard prevents source being deleted if eom\n  // was read in current bodyEvent\n  auto guard = folly::makeGuard(lifetime(bodyEvent));\n\n  // passthru on skipCompression flag or body exception\n  if (skipCompression_ || bodyEvent.hasException()) {\n    co_return bodyEvent;\n  }\n\n  // try to compress body, validate success\n  if (bodyEvent->eventType == HTTPBodyEvent::BODY) {\n    CHECK(compressor_);\n    folly::IOBuf emptyBuf{};\n    auto* pBuf = bodyEvent->event.body.empty() ? &emptyBuf\n                                               : bodyEvent->event.body.front();\n    auto compressed = compressor_->compress(pBuf, bodyEvent->eom);\n    if (compressor_->hasError()) {\n      co_yield co_error(CompressionFailedError);\n    }\n    // replace with compressed body\n    bodyEvent->event.body = std::move(compressed);\n    co_return bodyEvent;\n  }\n\n  // there may be remaining unflushed data in compressor, if so that data\n  // should be inserted before non-body eom event (note that trailer flag is\n  // identical to bodyEvent->eom)\n  if (bodyEvent->eom) {\n    // force flushing pending data in compressor and verify success\n    folly::IOBuf emptyBuf{};\n    auto compressed = compressor_->compress(&emptyBuf, /*trailer=*/true);\n    if (compressor_->hasError()) {\n      co_yield co_error(CompressionFailedError);\n    }\n    // if no remaining unflushed data, return bodyEvent as is\n    if (compressed == nullptr || compressed->empty()) {\n      co_return bodyEvent;\n    }\n\n    // need to buffer trailers, dismiss guard as mentioned above\n    CHECK(!pendingBodyEvent_);\n    pendingBodyEvent_.emplace(std::move(*bodyEvent));\n    guard.dismiss();\n    // eom=false; there's a pending event to be delivered via next\n    // ::readBodyEvent\n    co_return HTTPBodyEvent(std::move(compressed), /*trailer=*/false);\n  }\n\n  co_return bodyEvent;\n}\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/CompressionFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include <proxygen/lib/utils/CompressionFilterUtils.h>\n\nnamespace {\nusing FilterParams = proxygen::CompressionFilterUtils::FilterParams;\n}\n\nnamespace proxygen::coro {\nclass CompressionFilter : public HTTPSourceFilter {\n public:\n  CompressionFilter(HTTPSource* source,\n                    std::shared_ptr<folly::Optional<FilterParams>> params)\n      : HTTPSourceFilter(source), params_(std::move(params)) {\n    CHECK(params_);\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n private:\n  std::shared_ptr<folly::Optional<FilterParams>> params_;\n  std::unique_ptr<proxygen::StreamCompressor> compressor_{nullptr};\n  bool skipCompression_{false};\n  folly::Optional<proxygen::coro::HTTPBodyEvent> pendingBodyEvent_{folly::none};\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/CompressionFilterFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/filters/CompressionFilter.h\"\n#include \"proxygen/lib/http/coro/filters/ServerFilterFactory.h\"\n#include \"proxygen/lib/http/coro/filters/VisitorFilter.h\"\n\nnamespace {\nusing FilterParams = proxygen::CompressionFilterUtils::FilterParams;\nusing SharedCtx = folly::Optional<FilterParams>;\n} // namespace\n\nnamespace proxygen::coro {\n\nclass ServerCompressionFilterFactory : public ServerFilterFactory {\n public:\n  ServerCompressionFilterFactory(CompressionFilterUtils::FactoryOptions options)\n      : options_(std::move(options)) {\n  }\n\n  // no-op\n  void onServerStart(folly::EventBase* evb) noexcept override {\n  }\n\n  // no-op\n  void onServerStop() noexcept override {\n  }\n\n  /**\n   * The idea here is to pass a shared_ptr<folly::Optional<FilterParams>> as a\n   * way for the ingress filter to publish FilterParams (created only when\n   * request header is read) and the egress filter to consume the FilterParams\n   * for creating the compressor (created only when response header/body is\n   * sent). Since the FilterParams is only written to/read from after the\n   * ingress&egress filters are created here, the shared_ptr is passed as a copy\n   * to both filters allowing safe access since either filter may be deleted\n   * arbitrarily via ::stopReading or reading error from source.\n   */\n  std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override {\n    auto ctx = std::make_shared<SharedCtx>(folly::none);\n    return {makeIngressFilter(ctx), makeEgressFilter(ctx)};\n  }\n\n private:\n  HTTPSourceFilter* makeIngressFilter(std::shared_ptr<SharedCtx>& ctx) {\n    // simple visitor to set filterParams upon rx'ing request headers\n    auto visitor = std::make_unique<VisitorFilter>(\n        /*source=*/\n        nullptr,\n        [this, ctx](auto&& headerEvent) {\n          if (!headerEvent.hasException()) {\n            ctx->assign(CompressionFilterUtils::getFilterParams(\n                *headerEvent->headers, options_));\n          }\n        },\n        /*bodyHook=*/nullptr);\n    visitor->setHeapAllocated();\n    return visitor.release();\n  }\n\n  HTTPSourceFilter* makeEgressFilter(std::shared_ptr<SharedCtx>& ctx) {\n    auto compressionFilter = std::make_unique<CompressionFilter>(\n        /*source=*/nullptr, /*params=*/ctx);\n    compressionFilter->setHeapAllocated();\n    return compressionFilter.release();\n  }\n\n  CompressionFilterUtils::FactoryOptions options_;\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/DecompressionFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/DecompressionFilter.h\"\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\nnamespace {\n\n#define DecompressionFailedError \\\n  HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"DecompressionFilter failed\")\n\nusing CompressionType = proxygen::CompressionType;\nstruct SupportedCompression {\n  std::string_view name;\n  CompressionType type;\n};\n\n// sorted in descending order of preference\nconstexpr std::array kSupportedCompressionTypes = {\n    SupportedCompression{.name = \"gzip\", .type = CompressionType::GZIP},\n    SupportedCompression{.name = \"deflate\", .type = CompressionType::DEFLATE},\n    SupportedCompression{.name = \"zstd\", .type = CompressionType::ZSTD}};\n\nusing folly::coro::co_error;\n\nusing AcceptedEncodings =\n    std::array<std::string_view, kSupportedCompressionTypes.size()>;\nAcceptedEncodings getAcceptedEncodings() {\n  AcceptedEncodings acceptedEncodings{};\n  for (size_t idx = 0; idx < kSupportedCompressionTypes.size(); idx++) {\n    acceptedEncodings[idx] = kSupportedCompressionTypes[idx].name;\n  }\n  return acceptedEncodings;\n}\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\nvoid decompressionEgressHeaderEventHook(HTTPHeaderEvent& headerEvent) {\n  // append the supported compression types if the header doesn't exist\n  if (!headerEvent.headers->getHeaders().exists(HTTP_HEADER_ACCEPT_ENCODING)) {\n    std::string acceptedEncodings = folly::join(\", \", getAcceptedEncodings());\n    headerEvent.headers->getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING,\n                                          acceptedEncodings);\n  }\n}\n\n}; // namespace\n\nnamespace proxygen::coro {\n\nDecompressionEgressFilter::DecompressionEgressFilter(HTTPSource* source)\n    : MutateFilter(source,\n                   /*headerHook=*/\n                   decompressionEgressHeaderEventHook,\n                   /*bodyHook=*/nullptr) {\n}\n\nfolly::coro::Task<HTTPHeaderEvent>\nDecompressionIngressFilter::readHeaderEvent() {\n  auto headerEvent =\n      co_await co_awaitTry(readHeaderEventImpl(/*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n  if (headerEvent.hasException() || headerEvent->eom) {\n    co_return headerEvent;\n  }\n  initializeWithHTTPMessage(*headerEvent->headers);\n  co_return headerEvent;\n}\n\n/* static */ bool DecompressionIngressFilter::compressionSupported(\n    HTTPMessage& msg) {\n  const auto& contentEncoding =\n      msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_ENCODING);\n  return std::any_of(kSupportedCompressionTypes.begin(),\n                     kSupportedCompressionTypes.end(),\n                     [&contentEncoding](const auto& supportedCompression) {\n                       return supportedCompression.name == contentEncoding;\n                     });\n}\n\nvoid DecompressionIngressFilter::initializeWithHTTPMessage(HTTPMessage& msg) {\n  auto& headers = msg.getHeaders();\n  const auto& contentEncoding =\n      headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_ENCODING);\n\n  // instantiate decompressor_ if supported compression type\n  auto it = std::find_if(kSupportedCompressionTypes.begin(),\n                         kSupportedCompressionTypes.end(),\n                         [&contentEncoding](const auto& supportedCompression) {\n                           return supportedCompression.name == contentEncoding;\n                         });\n  if (it != kSupportedCompressionTypes.end()) {\n    headers.remove(HTTP_HEADER_CONTENT_LENGTH);\n    headers.remove(HTTP_HEADER_CONTENT_ENCODING);\n    headers.set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n    msg.setIsChunked(true);\n    if (statsCallback_) {\n      statsCallback_->onDecompressionAlgo(std::string(it->name));\n    }\n    if (it->type == CompressionType::ZSTD) {\n      decompressor_ = std::make_unique<ZstdStreamDecompressor>();\n    } else {\n      decompressor_ = std::make_unique<ZlibStreamDecompressor>(it->type);\n    }\n  } else if (statsCallback_) {\n    statsCallback_->onDecompressionAlgo(\"\");\n  }\n}\n\nfolly::coro::Task<HTTPBodyEvent> DecompressionIngressFilter::readBodyEvent(\n    uint32_t max) {\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventImpl(max, /*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(bodyEvent));\n  if (!decompressor_ || bodyEvent.hasException()) {\n    // if unsupported compression or error, return as is\n    co_return bodyEvent;\n  }\n\n  if (bodyEvent->eventType == HTTPBodyEvent::EventType::BODY) {\n    auto& body = bodyEvent->event.body;\n    folly::IOBuf empty{};\n\n    auto compressed = body.move();\n    auto decompressed =\n        decompressor_->decompress(compressed ? compressed.get() : &empty);\n    if (!decompressed || decompressor_->hasError()) {\n      if (statsCallback_) {\n        statsCallback_->onDecompressionError();\n      }\n      co_yield co_error(DecompressionFailedError);\n    }\n    body.append(std::move(decompressed));\n  }\n\n  /**\n   * At this point: if eom flag is true, we've attempted to decompress all the\n   * body events consumed. If decompressor has not finished then yield a\n   * decompression failed error.\n   */\n  if (bodyEvent->eom && !decompressor_->finished()) {\n    if (statsCallback_) {\n      statsCallback_->onDecompressionError();\n    }\n    co_yield co_error(DecompressionFailedError);\n  }\n\n  co_return bodyEvent;\n}\n\n}; // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/DecompressionFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/filters/MutateFilter.h\"\n#include <proxygen/lib/utils/CompressionFilterUtils.h>\n\nnamespace proxygen::coro {\n\n/**\n * Egress filter modifies the request headers to include the supported\n * compression types. More specifically, it adds an \"accept-encoding: gzip,\n * deflate\" request header; only if \"accept-encoding\" header isn't already\n * present.\n */\nclass DecompressionEgressFilter : public MutateFilter {\n public:\n  DecompressionEgressFilter(HTTPSource* source = nullptr);\n};\n\n/**\n * Ingress filter that instantiates the type of StreamDecompressor based on\n * \"content-encoding\" header, if supported. If the \"content-encoding\"\n * value isn't supported, we passthru the header & body events unmodified.\n */\nclass DecompressionIngressFilter : public HTTPSourceFilter {\n public:\n  class StatsCallback {\n   public:\n    virtual ~StatsCallback() = default;\n    // algo might be empty string, if it's unsupported compression\n    virtual void onDecompressionAlgo(const std::string& algo) = 0;\n    virtual void onDecompressionError() = 0;\n  };\n\n  DecompressionIngressFilter(HTTPSource* source = nullptr,\n                             std::shared_ptr<StatsCallback> statsCb = nullptr)\n      : HTTPSourceFilter(source), statsCallback_(std::move(statsCb)) {\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n  static bool compressionSupported(HTTPMessage& msg);\n\n protected:\n  void initializeWithHTTPMessage(HTTPMessage& msg);\n\n private:\n  std::unique_ptr<proxygen::StreamDecompressor> decompressor_;\n  std::shared_ptr<StatsCallback> statsCallback_;\n};\n\n}; // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/DecompressionFilterFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/DecompressionFilterFactory.h\"\n#include \"proxygen/lib/http/coro/filters/DecompressionFilter.h\"\n\nnamespace proxygen::coro {\n\nnamespace {\ntemplate <typename Filter>\nFilter* makeDecompressionFilter() {\n  auto filter = std::make_unique<Filter>(/*source=*/nullptr);\n  XCHECK(filter);\n  filter->setHeapAllocated();\n  return filter.release();\n}\n} // namespace\n\nstd::pair<HTTPSourceFilter*, HTTPSourceFilter*>\nClientDecompressionFilterFactory::makeFilters() {\n  return {\n      /* RequestFilter = */ makeDecompressionFilter<\n          DecompressionEgressFilter>(),\n      /* ResponseFilter = */\n      makeDecompressionFilter<DecompressionIngressFilter>(),\n  };\n}\n\nstd::pair<HTTPSourceFilter*, HTTPSourceFilter*>\nServerDecompressionFilterFactory::makeFilters() {\n  return {\n      /* RequestFilter = */ makeDecompressionFilter<\n          DecompressionIngressFilter>(),\n      /* ResponseFilter = */ nullptr,\n  };\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/DecompressionFilterFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/filters/FilterFactory.h\"\n#include \"proxygen/lib/http/coro/filters/ServerFilterFactory.h\"\n\nnamespace proxygen::coro {\n\nclass ClientDecompressionFilterFactory : public FilterFactory {\n public:\n  std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override;\n};\n\nclass ServerDecompressionFilterFactory : public ServerFilterFactory {\n public:\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/FilterFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n\nnamespace proxygen::coro {\n\nclass FilterFactory {\n public:\n  virtual ~FilterFactory() = default;\n\n  /**\n   * Returns a pair of filters representing the request and response filters\n   * respectively. The server installs these filters into the request and\n   * response paths in order. Any of the pair of filters can be nullptr and will\n   * omit installing that filter in the chain.\n   */\n  using HTTPSourceFilter = proxygen::coro::HTTPSourceFilter;\n  virtual std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() = 0;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/HTTPRedirectHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/HTTPRedirectHandler.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include <folly/logging/xlog.h>\n\nnamespace {\nbool isRedirectable(uint16_t code,\n                    folly::Optional<proxygen::HTTPMethod> maybeMethod) {\n  if (!maybeMethod) {\n    return false;\n  }\n  auto method = *maybeMethod;\n  // According to spec, 303 response code is a redirect from a POST that\n  // should include a Location header but redirect as a GET\n  // reference: http://tools.ietf.org/html/rfc2616#section-10.3.4\n  if (code == 303 && method == proxygen::HTTPMethod::POST) {\n    return true;\n  }\n\n  if (method != proxygen::HTTPMethod::GET &&\n      method != proxygen::HTTPMethod::HEAD) {\n    return false;\n  }\n\n  // RFC suggests 303 is to be used to redirect POST to GET\n  // http://tools.ietf.org/html/rfc2616#section-10.3.4\n  // but youtube is sending it from GET requests so lets support it\n  return code == 301 || code == 302 || code == 303 || code == 307 ||\n         code == 308;\n}\n} // namespace\n\nnamespace proxygen::coro {\n\nusing folly::coro::co_error;\n\nfolly::coro::Task<HTTPHeaderEvent>\nHTTPRedirectHandler::ResponseFilter::readHeaderEvent() {\n  size_t numRedirects = 0;\n\n  while (true) {\n    auto headerEvent = co_await readHeaderEventImpl();\n    auto statusCode = headerEvent.headers->getStatusCode();\n    if (!isRedirectable(statusCode, handler_.request_->getMethod())) {\n      co_return headerEvent;\n    }\n\n    headerEvent.headers->dumpMessage(4);\n    folly::Optional<URL> newUrl;\n    {\n      auto abortRequest =\n          folly::makeGuard([this] { HTTPSourceFilter::stopReading(); });\n      if (headerEvent.eom) {\n        abortRequest.dismiss();\n      }\n      auto const& location = headerEvent.headers->getHeaders().getSingleOrEmpty(\n          HTTP_HEADER_LOCATION);\n\n      if (location.empty()) {\n        co_yield co_error(Exception(Exception::Type::InvalidRedirect,\n                                    \"Missing location header for redirect\"));\n      } else if (++numRedirects > handler_.maxRedirects_) {\n        co_yield co_error(Exception(Exception::Type::MaxRedirects,\n                                    \"Exceeded maximum redirect depth\"));\n      } else if (!ParseURL::isSupportedScheme(location)) {\n        co_yield co_error(Exception(Exception::Type::UnsupportedScheme,\n                                    \"Unsupported Scheme\"));\n      }\n      newUrl = handler_.getRedirectDestination(location);\n      if (!newUrl) {\n        co_yield co_error(Exception(Exception::Type::InvalidRedirect,\n                                    \"Unparsable redirect location\"));\n      }\n\n      handler_.prepareRequest(newUrl->getUrl(), *newUrl, statusCode);\n    }\n\n    co_await folly::coro::co_withCancellation(\n        cancellationSource_.getToken(), handler_.redirect(std::move(*newUrl)));\n  }\n}\n\nvoid HTTPRedirectHandler::ResponseFilter::stopReading(\n    folly::Optional<const HTTPErrorCode> error) {\n  cancellationSource_.requestCancellation();\n  if (readable()) {\n    HTTPSourceFilter::stopReading(error);\n  }\n}\n\nfolly::Optional<URL> HTTPRedirectHandler::getRedirectDestination(\n    const std::string& location) const {\n  auto url = ParseURL::getRedirectDestination(\n      request_->getURL(),\n      request_->getScheme(),\n      location,\n      request_->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST));\n  if (!url) {\n    return folly::none;\n  }\n  return URL(*url);\n}\n\n/**\n * Strips various headers (Host which is set in another filter, Cookie\n * and Authorization headers) and sets a new url from the location which\n * may be absolute or relative.  The spec is unclear about what to do with\n * request headers during a redirect, so I followed some advice from this\n * discussion:\n *\n * https://code.google.com/p/go/issues/detail?id=4800&q=request%20header\n *\n * Since we are building a more restricted HTTP client, I started with a\n * basic allowlist, but even that grew large pretty quickly.  Seems\n * reasonable to just not forward on known private headers, even for same\n * origin requests.\n */\nvoid HTTPRedirectHandler::prepareRequest(const std::string& newUrlStr,\n                                         const URL& newUrl,\n                                         uint16_t statusCode) {\n  if (request_->getMethod() == HTTPMethod::POST && statusCode == 303) {\n    request_->setMethod(HTTPMethod::GET);\n    request_->getHeaders().remove(HTTP_HEADER_CONTENT_LENGTH);\n    requestBody_.move();\n  }\n\n  if (sessionFactory_->requiresAbsoluteURLs()) {\n    request_->setURL(newUrlStr);\n  } else {\n    request_->setURL(newUrl.makeRelativeURL());\n    request_->getHeaders().set(HTTP_HEADER_HOST,\n                               newUrl.getHostAndPortOmitDefault());\n  }\n\n  // Strip sensitive headers as described in function comment\n  // TODO(t4397642) determine if there are more headers we should strip\n  request_->getHeaders().remove(HTTP_HEADER_AUTHORIZATION);\n  request_->getHeaders().remove(HTTP_HEADER_COOKIE);\n}\n\nfolly::coro::Task<void> HTTPRedirectHandler::redirect(URL url) {\n  if (redirectCallback_) {\n    redirectCallback_(url.getUrl());\n  }\n\n  auto res = co_await sessionFactory_->getSessionWithReservation(\n      url.getHost(), url.getPort(), url.isSecure(), connectTimeout_);\n  auto reqSource = HTTPFixedSource::makeFixedSource(\n      std::make_unique<HTTPMessage>(*request_), requestBody_.clone());\n  // TODO: there isn't currently a way to install any filters on the redirected\n  // requests/responses.\n  auto respSource =\n      co_await res.session->sendRequest(reqSource, std::move(res.reservation));\n  if (readTimeout_) {\n    respSource.setReadTimeout(*readTimeout_);\n  }\n  respFilter_.setSource(respSource.release());\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/HTTPRedirectHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <utility>\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/client/HTTPSessionFactory.h\"\n\nusing RedirectCallback = std::function<void(const std::string&)>;\n\nnamespace proxygen::coro {\n\nclass HTTPRedirectHandler {\n public:\n  static constexpr std::chrono::seconds kDefaultConnectTimeout{2};\n\n  explicit HTTPRedirectHandler(\n      std::shared_ptr<HTTPSessionFactory> sessionFactory,\n      uint16_t maxRedirects = 1,\n      std::chrono::milliseconds connectTimeout = kDefaultConnectTimeout,\n      folly::Optional<std::chrono::milliseconds> readTimeout = folly::none)\n      : reqFilter_(*this),\n        respFilter_(*this),\n        sessionFactory_(std::move(sessionFactory)),\n        connectTimeout_(connectTimeout),\n        readTimeout_(std::move(readTimeout)),\n        maxRedirects_(maxRedirects) {\n  }\n\n  class Exception : public std::runtime_error {\n   public:\n    enum class Type { InvalidRedirect, MaxRedirects, UnsupportedScheme };\n    explicit Exception(Type t, const std::string& msg)\n        : std::runtime_error(msg), type(t) {\n    }\n\n    Type type;\n  };\n\n  HTTPSourceFilter* getRequestFilter(HTTPSourceHolder source) {\n    reqFilter_.setSource(source.release());\n    return &reqFilter_;\n  }\n\n  HTTPSourceFilter* getResponseFilter(HTTPSourceHolder source) {\n    respFilter_.setSource(source.release());\n    return &respFilter_;\n  }\n\n  // Set a callback to run each time we have identified a new URL to redirect\n  // to. URL is passed as const std::string&\n  void setRedirectCallback(RedirectCallback&& callback) {\n    redirectCallback_ = std::move(callback);\n  }\n\n private:\n  class RequestFilter : public HTTPSourceFilter {\n   public:\n    explicit RequestFilter(HTTPRedirectHandler& handler) : handler_(handler) {\n    }\n\n    folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n      auto headerEvent = co_await readHeaderEventImpl();\n      // Save a copy in case we need to redirecit\n      handler_.request_ = std::make_unique<HTTPMessage>(*headerEvent.headers);\n      co_return headerEvent;\n    }\n    folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n      auto bodyEvent = co_await readBodyEventImpl(max);\n      if (bodyEvent.eventType == HTTPBodyEvent::BODY) {\n        handler_.requestBody_.append(bodyEvent.event.body.clone());\n      }\n      co_return bodyEvent;\n    }\n\n   private:\n    HTTPRedirectHandler& handler_;\n  };\n\n  class ResponseFilter : public HTTPSourceFilter {\n   public:\n    explicit ResponseFilter(HTTPRedirectHandler& handler) : handler_(handler) {\n    }\n\n    folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n    // default readBodyEvent\n    void stopReading(folly::Optional<const HTTPErrorCode> error) override;\n\n   private:\n    HTTPRedirectHandler& handler_;\n    folly::CancellationSource cancellationSource_;\n  };\n\n  [[nodiscard]] folly::Optional<URL> getRedirectDestination(\n      const std::string& location) const;\n  void prepareRequest(const std::string& newUrlStr,\n                      const URL& newUrl,\n                      uint16_t statusCode);\n  folly::coro::Task<void> redirect(URL url);\n\n  RequestFilter reqFilter_;\n  ResponseFilter respFilter_;\n  std::unique_ptr<HTTPMessage> request_;\n  BufQueue requestBody_;\n  std::shared_ptr<HTTPSessionFactory> sessionFactory_;\n  std::chrono::milliseconds connectTimeout_;\n  folly::Optional<std::chrono::milliseconds> readTimeout_;\n  uint16_t maxRedirects_{1};\n\n  // Funciton pointer to run each time we find a new URL to redirect to.\n  RedirectCallback redirectCallback_ = nullptr;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/Logger.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/Logger.h\"\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n\nnamespace proxygen::coro {\n\nstd::ostream& operator<<(std::ostream& os, const Logger::Filter& filter) {\n  filter.describe(os);\n  return os;\n}\n// clang-format off\nvoid Logger::Filter::describe(std::ostream& os) const {\n  if (valid) {\n    os\n      << \"\\tHTTP Version:\\t\"\n      << folly::to<std::string>(httpVersion.first, \".\", httpVersion.second)\n      << \"\\n\\t\\t\"\n      << \"Priority:\\t\"\n      << httpPriorityToString(priority)\n      << \"\\n\";\n    if (direction_ == Direction::REQUEST) {\n      os\n        << \"\\t\\tMethod:\\t\\t\" <<  method << \"\\n\\t\\t\"\n        << \"Host:\\t\\t\" <<  host << \"\\n\\t\\t\"\n        << \"Path:\\t\\t\" << url << \"\\n\";\n    } else {\n      os << \"\\t\\tStatus Code:\\t\"\n         << (statusCode ?\n             folly::to<std::string>(*statusCode) :\n             std::string(\"None\"))<< \"\\n\";\n    }\n    if (headerSize) {\n      os << \"\\t\\tHeader Bytes:\\t\" << headerSize->uncompressed << \"\\n\\t\\t\"\n         << \"Compressed:\\t\" << headerSize->compressed << \"\\n\";\n    }\n    os << \"\\t\\tBody Bytes:\\t\" << bodyBytes << \"\\n\";\n  }\n  if (error) {\n    os << \"\\tError:\\t code=\" << uint32_t(error->code) << \" msg=\"\n       << error->msg << \"\\n\";\n  }\n}\n\nvoid Logger::logWithVlog(int level) const {\n  VLOG(level)\n    << \"HTTP request logger: \\n\\t\"\n    << \"Local Addr:\\t\" << localAddr << \"\\n\\t\"\n    << \"Peer Addr:\\t\" << peerAddr << \"\\n\\t\"\n    << \"Secure:\\t\\t\" << getSecurityType() << \"\\n\\t\"\n    << \"Protocol:\\t\" << getCodecProtocolString(protocol) << \"\\n\\t\"\n    << \"Session ID:\\t\" << std::hex << sessionID << \"\\n\\t\"\n    << \"Stream ID:\\t\" << std::dec << getStreamID() << \"\\n\\t\"\n    << \"Seq No:\\t\\t\" << HTTPCodec::streamIDToSeqNo(protocol, getStreamID()) << \"\\n\\t\"\n    << \"Start:\\t\\t\" << toTimeT(startTime) << \"\\n\\t\"\n    << \"TTFHB:\\t\\t\" << timeToFirstHeaderByte().count() << \"\\n\\t\"\n    << \"TTFB:\\t\\t\" << timeToFirstByte().count() << \"\\n\\t\"\n    << \"TTLB:\\t\\t\" << timeToLastByte().count() << \"\\n\\t\"\n    << \"RTT (us):\\t\" << transportInfo.rtt.count() << \"\\n\\t\"\n    << \"Request:\\n\\t\" << reqFilter << \"\\n\\t\"\n    << \"Response:\\n\\t\" << respFilter;\n}\n// clang-format on\n\nfolly::coro::Task<HTTPHeaderEvent> Logger::Filter::readHeaderEvent() {\n  streamID = getStreamID();\n  auto headerEvent = co_await co_awaitTry(readHeaderEventImpl());\n  if (headerEvent.hasException()) {\n    endTime = std::chrono::steady_clock::now();\n    error = getHTTPError(headerEvent);\n    done.first.setValue();\n    co_yield folly::coro::co_error(std::move(headerEvent.exception()));\n  }\n  if (headerEvent->isFinal()) {\n    finalHeaderTime = std::chrono::steady_clock::now();\n  }\n  valid = true;\n  auto& headers = *headerEvent->headers;\n  httpVersion = headers.getHTTPVersion();\n  if (headers.isRequest()) {\n    method = headers.getMethodString();\n    host = headers.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST);\n    url = headers.getURL();\n  } else {\n    statusCode = headers.getStatusCode();\n  }\n  auto size = headers.getIngressHeaderSize();\n  ingress = size.uncompressed > 0;\n  if (ingress) {\n    headerSize = size;\n  } else {\n    headerEvent->egressHeadersFn =\n        [this, egressHeadersFn = std::move(headerEvent->egressHeadersFn)](\n            HTTPHeaderSize size) mutable noexcept {\n          headerSize = size;\n          if (egressHeadersFn) {\n            egressHeadersFn(size);\n          }\n        };\n  }\n  priority = headers.getHTTPPriority().value_or(HTTPPriority());\n  if (headerEvent->eom) {\n    firstByteTime = std::chrono::steady_clock::now();\n    endTime = std::chrono::steady_clock::now();\n    done.first.setValue();\n  }\n  co_return std::move(*headerEvent);\n}\n\nfolly::coro::Task<HTTPBodyEvent> Logger::Filter::readBodyEvent(uint32_t max) {\n  auto bodyEvent = co_await co_awaitTry(readBodyEventImpl(max));\n  if (bodyEvent.hasException()) {\n    endTime = std::chrono::steady_clock::now();\n    error = getHTTPError(bodyEvent);\n    done.first.setValue();\n    co_yield folly::coro::co_error(std::move(bodyEvent.exception()));\n  }\n  if (bodyEvent->eventType == HTTPBodyEvent::BODY) {\n    bodyBytes += bodyEvent->event.body.chainLength();\n  }\n  if (!firstByteTime) {\n    firstByteTime = std::chrono::steady_clock::now();\n  }\n  if (bodyEvent->eom) {\n    endTime = std::chrono::steady_clock::now();\n    done.first.setValue();\n  }\n  co_return std::move(*bodyEvent);\n}\n\nvoid Logger::Filter::stopReading(folly::Optional<const HTTPErrorCode> err) {\n  endTime = std::chrono::steady_clock::now();\n  done.first.setValue();\n  HTTPSourceFilter::stopReading(err);\n}\n\n} // namespace proxygen::coro\n/**\n * fbcode Rev\n * tw task\n * window updates sent\n * window updates received\n * header queue time (requires byte events)\n * body queue time (requires byte events)\n */\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/Logger.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include <folly/coro/Collect.h>\n#include <folly/coro/Promise.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/sampling/Sampled.h>\n\nnamespace proxygen::coro {\n\nclass Logger {\n public:\n  class SampledLogger {\n   public:\n    virtual ~SampledLogger() = default;\n    /**\n     * @param error   Whether there was an ingress or egress error.\n     * @result        Weight of sample (will invoke ::log() if > 0). The\n     *                returned weight value will be subsequently passed into\n     *                ::log()\n     */\n    virtual uint32_t getLoggingWeight(bool error) {\n      // default implementation is to log on error or 1/10,000\n      if (error) {\n        return 1;\n      }\n      proxygen::Sampling sampling(0.0001);\n      return sampling.isLucky() ? sampling.getWeight() : 0;\n    }\n\n    virtual void log(const Logger& logger, uint32_t /*weight*/) {\n      logger.logWithVlog(2);\n    }\n  };\n  using SampledLoggerPtr = std::shared_ptr<SampledLogger>;\n\n  explicit Logger(HTTPSessionContextPtr sessionCtx,\n                  std::shared_ptr<SampledLogger> logImpl = nullptr,\n                  bool logOnDestroy = true) {\n    XCHECK(sessionCtx);\n    sessionCtx_ = std::move(sessionCtx);\n    localAddr = sessionCtx_->getLocalAddress();\n    peerAddr = sessionCtx_->getPeerAddress();\n    protocol = sessionCtx_->getCodecProtocol();\n    sessionID = sessionCtx_->getSessionID();\n    transportInfo = sessionCtx_->getSetupTransportInfo();\n    startTime = std::chrono::steady_clock::now();\n    if (logImpl) {\n      logImpl_ = std::move(logImpl);\n    } else {\n      logImpl_ = std::make_shared<SampledLogger>();\n    }\n    logOnDestroy_ = logOnDestroy;\n  }\n\n  ~Logger() {\n    if (logOnDestroy_) {\n      log();\n    }\n  }\n\n  static folly::coro::Task<void> logWhenDone(Logger& logger) {\n    co_await folly::coro::collectAll(std::move(logger.reqFilter.done.second),\n                                     std::move(logger.respFilter.done.second));\n    logger.log();\n  }\n\n  void log() {\n    logOnDestroy_ = false;\n    const uint32_t weight =\n        logImpl_->getLoggingWeight(reqFilter.error || respFilter.error);\n    if (weight > 0) {\n      if (sessionCtx_) {\n        sessionCtx_->getCurrentTransportInfo(&transportInfo, false);\n      }\n      logImpl_->log(*this, weight);\n    }\n    sessionCtx_.reset();\n  }\n\n  void logWithVlog(int level) const;\n\n  HTTPSourceFilter* getRequestFilter(HTTPSourceHolder source) {\n    reqFilter.setSource(source.release());\n    return &reqFilter;\n  }\n\n  HTTPSourceFilter* getResponseFilter(HTTPSourceHolder source) {\n    respFilter.setSource(source.release());\n    return &respFilter;\n  }\n\n  [[nodiscard]] HTTPCodec::StreamID getStreamID() const {\n    if (reqFilter.streamID) {\n      return *reqFilter.streamID;\n    } else if (respFilter.streamID) {\n      return *respFilter.streamID;\n    } else {\n      return HTTPCodec::MaxStreamID;\n    }\n  }\n\n  [[nodiscard]] std::chrono::milliseconds timeToFirstHeaderByte() const {\n    auto endTime = respFilter.finalHeaderTime ? *respFilter.finalHeaderTime\n                                              : respFilter.endTime;\n    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime -\n                                                                 startTime);\n  }\n\n  [[nodiscard]] std::chrono::milliseconds timeToFirstByte() const {\n    auto endTime = respFilter.firstByteTime ? *respFilter.firstByteTime\n                                            : respFilter.endTime;\n    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime -\n                                                                 startTime);\n  }\n\n  [[nodiscard]] std::chrono::milliseconds timeToLastByte() const {\n    return std::chrono::duration_cast<std::chrono::milliseconds>(\n        respFilter.endTime - startTime);\n  }\n\n  [[nodiscard]] std::string getSecurityType() const {\n    return transportInfo.secure ? transportInfo.securityType\n                                : std::string(\"plaintext\");\n  }\n\n  [[nodiscard]] std::string getAuthority() const {\n    if (!reqFilter.host.empty()) {\n      return reqFilter.host;\n    }\n    if (!reqFilter.url.empty()) {\n      auto parseUrl = ParseURL::parseURL(reqFilter.url);\n      if (parseUrl && parseUrl->hasHost()) {\n        return std::string(parseUrl->host());\n      }\n    }\n    return {};\n  }\n\n  [[nodiscard]] std::string getPath() const {\n    if (!reqFilter.url.empty()) {\n      auto parseUrl = ParseURL::parseURL(reqFilter.url);\n      if (parseUrl) {\n        return std::string(parseUrl->path());\n      }\n    }\n    return {};\n  }\n\n  class Filter : public HTTPSourceFilter {\n   public:\n    enum class Direction { REQUEST, RESPONSE };\n    explicit Filter(Direction dir) : direction_(dir) {\n      done = folly::coro::makePromiseContract<void>();\n    }\n    folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n    folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override;\n    void stopReading(folly::Optional<const HTTPErrorCode> error) override;\n\n    void describe(std::ostream& os) const;\n\n    folly::Optional<HTTPCodec::StreamID> streamID;\n    folly::Optional<std::chrono::steady_clock::time_point> finalHeaderTime;\n    folly::Optional<std::chrono::steady_clock::time_point> firstByteTime;\n    std::chrono::steady_clock::time_point endTime;\n    folly::Optional<HTTPError> error;\n    folly::Optional<HTTPHeaderSize> headerSize;\n    std::pair<uint8_t, uint8_t> httpVersion;\n    HTTPPriority priority;\n    std::string method;\n    std::string host;\n    std::string url;\n    folly::Optional<uint16_t> statusCode;\n    size_t bodyBytes{0};\n    std::pair<folly::coro::Promise<void>, folly::coro::Future<void>> done;\n    bool valid{false};\n    bool ingress{false};\n\n   private:\n    Direction direction_;\n  };\n\n  Filter reqFilter{Filter::Direction::REQUEST};\n  Filter respFilter{Filter::Direction::RESPONSE};\n\n  // Fields from session\n  folly::SocketAddress localAddr;\n  folly::SocketAddress peerAddr;\n  CodecProtocol protocol;\n  uint64_t sessionID{0};\n  std::chrono::steady_clock::time_point startTime;\n  wangle::TransportInfo transportInfo;\n\n private:\n  HTTPSessionContextPtr sessionCtx_;\n  std::shared_ptr<SampledLogger> logImpl_;\n  bool logOnDestroy_{true};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/MutateFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/MutateFilter.h\"\n\nnamespace proxygen::coro {\n\nMutateFilter::MutateFilter(HTTPSource* source,\n                           MutateFilter::HeaderMutateFn headerHook,\n                           MutateFilter::BodyMutateFn bodyHook)\n    : HTTPSourceFilter(source),\n      headerHook_(std::move(headerHook)),\n      bodyHook_(std::move(bodyHook)) {\n}\n\nusing HTTPHeaderEventTask = folly::coro::Task<HTTPHeaderEvent>;\nHTTPHeaderEventTask MutateFilter::readHeaderEvent() {\n  auto headerEvent =\n      co_await co_awaitTry(readHeaderEventImpl(/*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n  if (headerEvent.hasException()) {\n    co_yield folly::coro::co_error(std::move(headerEvent.exception()));\n  }\n  if (headerHook_) {\n    headerHook_(headerEvent.value());\n  }\n  co_return headerEvent;\n}\n\nusing HTTPBodyEventTask = folly::coro::Task<HTTPBodyEvent>;\nHTTPBodyEventTask MutateFilter::readBodyEvent(uint32_t max) {\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventImpl(max, /*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(bodyEvent));\n  if (bodyEvent.hasException()) {\n    co_yield folly::coro::co_error(std::move(bodyEvent.exception()));\n  }\n  if (bodyHook_) {\n    bodyHook_(bodyEvent.value());\n  }\n  co_return bodyEvent;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/MutateFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n\nnamespace proxygen::coro {\n\n/**\n * This class provides a simple abstraction for filters that just want to\n * execute simple transformations on events (HTTPHeaderEvent and HTTPBodyEvent).\n * This filter is essentially a passthru filter in two cases:\n *  1. on error since there is no event to mutate.\n *  2. if nullptr is passed for both functions.\n */\nclass MutateFilter : public HTTPSourceFilter {\n public:\n  using HeaderMutateFn = std::function<void(HTTPHeaderEvent&)>;\n  using BodyMutateFn = std::function<void(HTTPBodyEvent&)>;\n\n  explicit MutateFilter(HTTPSource* source,\n                        HeaderMutateFn headerHook = nullptr,\n                        BodyMutateFn bodyHook = nullptr);\n  ~MutateFilter() override = default;\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n private:\n  HeaderMutateFn headerHook_;\n  BodyMutateFn bodyHook_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/RateLimitFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/coro/filters/RateLimitFilter.h>\n\n#include <folly/coro/Sleep.h>\n\nusing namespace std::literals::chrono_literals;\nusing folly::coro::co_error;\n\nnamespace proxygen::coro {\n\nnamespace {\nconstexpr uint32_t kApproximateMTU = 1400;\nconstexpr auto kRateLimitMaxDelay = 10s;\nconstexpr uint32_t kDefaultChunkSize = 4096;\n} // namespace\n\nRateLimitFilter::RateLimitFilter(HTTPSource* source,\n                                 std::chrono::seconds maxDelay,\n                                 uint32_t chunkBytes)\n    : HTTPSourceFilter(source),\n      maxDelay_(maxDelay),\n      chunkBytes_(std::max(kApproximateMTU, chunkBytes)) {\n}\n\nRateLimitFilter::RateLimitFilter(HTTPSource* source)\n    : RateLimitFilter(source, kRateLimitMaxDelay, kDefaultChunkSize) {\n}\n\nfolly::coro::Task<HTTPBodyEvent> RateLimitFilter::readAndUpdateBytesCount(\n    uint32_t maxRead) noexcept {\n  auto readEvent =\n      co_await co_awaitTry(HTTPSourceFilter::readBodyEvent(maxRead));\n  if (readEvent.hasException() || readEvent->eom) {\n    // The filter itself might have been deleted already at this point if it's\n    // heap allocated. Either way, this is the end of body reads, so no need to\n    // update bytes count in this case.\n    co_return readEvent;\n  }\n  if (readEvent->eventType == HTTPBodyEvent::EventType::BODY) {\n    readBytesCount_ += readEvent->event.body.chainLength();\n  }\n  co_return readEvent;\n}\n\nfolly::coro::Task<HTTPBodyEvent> RateLimitFilter::readBodyEvent(uint32_t max) {\n  if (bytesPerMs_ == 0 || readBytesCount_ == 0) {\n    co_return co_await readAndUpdateBytesCount(max);\n  }\n\n  auto timeElapsedMs =\n      millisecondsBetween(getCurrentTime(), startTime_).count();\n  uint64_t bytesTarget = bytesPerMs_ * timeElapsedMs;\n  // At the very minimal, we should read this amount, otherwise the caller\n  // can make this into a tight loop with many small reads.\n  auto minReadAmount = std::min(max, chunkBytes_);\n  if (readBytesCount_ + minReadAmount <= bytesTarget) {\n    auto amountToRead =\n        std::min(max, folly::to<uint32_t>(bytesTarget - readBytesCount_));\n    co_return co_await readAndUpdateBytesCount(amountToRead);\n  }\n\n  auto sleepTime = std::chrono::milliseconds(\n      (readBytesCount_ + minReadAmount - bytesTarget) / bytesPerMs_);\n  sleepTime = std::min(\n      sleepTime,\n      std::chrono::duration_cast<std::chrono::milliseconds>(maxDelay_));\n  co_await folly::coro::sleepReturnEarlyOnCancel(sleepTime);\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  if (cancelToken.isCancellationRequested()) {\n    HTTPSourceFilter::stopReading();\n    co_yield co_error(\n        HTTPError(HTTPErrorCode::CORO_CANCELLED, \"Read body cancelled\"));\n  }\n  co_return co_await readAndUpdateBytesCount(minReadAmount);\n}\n\nvoid RateLimitFilter::setLimit(uint64_t bitsPerSecond) noexcept {\n  bytesPerMs_ = bitsPerSecond / 1000 / 8;\n  startTime_ = getCurrentTime();\n  readBytesCount_ = 0;\n}\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/RateLimitFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen::coro {\n\n/** A rate limit HTTPSourceFilter\n *\n * This filter limits body read to a preset rate limit. Header isn't limited.\n * Note this is a rate limiter, not a pacer.\n */\nclass RateLimitFilter : public HTTPSourceFilter {\n public:\n  explicit RateLimitFilter(HTTPSource* source);\n  explicit RateLimitFilter(HTTPSource* source,\n                           std::chrono::seconds maxDelay,\n                           uint32_t chunkBytes);\n\n  ~RateLimitFilter() override = default;\n\n  /**\n   * Read body byte from underlying HTTPSource, can potentially sleep if it hits\n   * the preset limit.\n   */\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override;\n\n  void setLimit(uint64_t bitsPerSecond) noexcept;\n\n private:\n  folly::coro::Task<HTTPBodyEvent> readAndUpdateBytesCount(\n      uint32_t bytes) noexcept;\n\n private:\n  std::chrono::seconds maxDelay_;\n  uint32_t chunkBytes_;\n  uint64_t bytesPerMs_{0};\n  TimePoint startTime_;\n  uint64_t readBytesCount_{0};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/RequestContextFilterFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/RequestContextFilterFactory.h\"\n#include \"folly/io/async/Request.h\"\n\nnamespace proxygen::coro {\n\nnamespace {\nclass RequestContextFilter : public HTTPSourceFilter {\n public:\n  explicit RequestContextFilter(\n      std::shared_ptr<folly::RequestContext> context) noexcept\n      : context_{std::move(context)} {\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    folly::RequestContextScopeGuard guard{context_};\n    co_return co_await folly::coro::co_nothrow(\n        HTTPSourceFilter::readHeaderEvent());\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    folly::RequestContextScopeGuard guard{context_};\n    co_return co_await folly::coro::co_nothrow(\n        HTTPSourceFilter::readBodyEvent());\n  }\n\n private:\n  const std::shared_ptr<folly::RequestContext> context_;\n};\n\n} // namespace\n\nstd::pair<HTTPSourceFilter*, HTTPSourceFilter*>\nRequestContextFilterFactory::makeFilters() {\n  folly::RequestContextScopeGuard guard;\n  auto context = folly::RequestContext::saveContext();\n  auto requestFilter =\n      std::make_unique<RequestContextFilter>(std::as_const(context));\n  requestFilter->setHeapAllocated();\n  auto responseFilter =\n      std::make_unique<RequestContextFilter>(std::move(context));\n  responseFilter->setHeapAllocated();\n  return {requestFilter.release(), responseFilter.release()};\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/RequestContextFilterFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/filters/ServerFilterFactory.h\"\n\nnamespace proxygen::coro {\n\n/**\n * Creates a new folly::RequestContext at the start of the request and executes\n * each method within the created context.\n */\nclass RequestContextFilterFactory : public ServerFilterFactory {\n public:\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n\n  void onServerStop() noexcept override {\n  }\n\n  std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/ServerFilterFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/filters/FilterFactory.h\"\n\nnamespace proxygen::coro {\n\nclass ServerFilterFactory : public FilterFactory {\n public:\n  ~ServerFilterFactory() override = default;\n  /**\n   * Invoked in each thread server is going to handle requests\n   * before we start handling requests. Can be used to setup\n   * thread-local setup for each thread (stats and such).\n   */\n  virtual void onServerStart(folly::EventBase* evb) noexcept = 0;\n\n  /**\n   * Invoked in each handler thread after all the connections are\n   * drained from that thread. Can be used to tear down thread-local setup.\n   */\n  virtual void onServerStop() noexcept = 0;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/StatsFilterUtil.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/StatsFilterUtil.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/filters/VisitorFilter.h\"\n#include \"proxygen/lib/http/stats/HttpServerStats.h\"\n#include <folly/stop_watch.h>\n\nnamespace {\nconstexpr std::chrono::milliseconds kZeroMs{0};\n\n// shared state between the proxygen::coro request and response stat filters\nstruct StatsState {\n  ~StatsState() {\n    // calculate latency and record request as complete\n    std::chrono::milliseconds latency =\n        stopwatch.has_value() ? stopwatch->elapsed() : kZeroMs;\n    stats->recordRequestComplete(\n        latency, error, requestBodyBytes, responseBodyBytes);\n  }\n\n  std::optional<folly::stop_watch<std::chrono::milliseconds>> stopwatch;\n  size_t requestBodyBytes{0};\n  size_t responseBodyBytes{0};\n  proxygen::HttpServerStatsIf* stats{nullptr};\n  // error observed in either req or res filters.\n  proxygen::ProxygenError error{proxygen::ProxygenError::kErrorNone};\n};\n\ntemplate <class T>\nbool maybeSetError(StatsState& state, const folly::Try<T>& event) {\n  if (event.hasException()) {\n    state.error = HTTPErrorCode2ProxygenError(getHTTPError(event).code);\n    return true;\n  }\n  return false;\n}\n} // namespace\n\nnamespace proxygen::coro {\n\n// Returns a pair of filters that share StatsState to enable collecting stats\nstd::pair<HTTPSourceFilter*, HTTPSourceFilter*> StatsFilterUtil::makeFilters(\n    HttpServerStatsIf* stats) {\n  // shared state between the two filters\n  auto state = std::make_shared<StatsState>();\n  state->stats = stats;\n\n  // Create req&res filters which is just a specialized visitor filter. Users\n  // should invoke .setSource() on the returned filters.\n  auto reqFilter = std::make_unique<VisitorFilter>(\n      /*source=*/\n      nullptr,\n      [state](const folly::Try<HTTPHeaderEvent>& headerEvent) {\n        state->stopwatch.emplace();\n        if (!maybeSetError(*state, headerEvent)) {\n          state->stats->recordRequest(*headerEvent->headers);\n        }\n      },\n      [state](const folly::Try<HTTPBodyEvent>& bodyEvent) {\n        if (!maybeSetError(*state, bodyEvent) &&\n            bodyEvent->eventType == HTTPBodyEvent::EventType::BODY) {\n          state->requestBodyBytes += bodyEvent->event.body.chainLength();\n        }\n      });\n\n  auto resFilter = std::make_unique<VisitorFilter>(\n      /*source=*/\n      nullptr,\n      [state](const folly::Try<HTTPHeaderEvent>& headerEvent) {\n        if (!maybeSetError(*state, headerEvent)) {\n          state->stats->recordResponse(*headerEvent->headers);\n        }\n      },\n      [state](const folly::Try<HTTPBodyEvent>& bodyEvent) {\n        if (!maybeSetError(*state, bodyEvent) &&\n            bodyEvent->eventType == HTTPBodyEvent::EventType::BODY) {\n          state->responseBodyBytes += bodyEvent->event.body.chainLength();\n        }\n      });\n\n  reqFilter->setHeapAllocated(), resFilter->setHeapAllocated();\n  return {reqFilter.release(), resFilter.release()};\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/StatsFilterUtil.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <utility>\n\nnamespace proxygen {\nclass HttpServerStatsIf;\n}\n\nnamespace proxygen::coro {\n\nclass HTTPSourceFilter;\n\nstruct StatsFilterUtil {\n  static std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters(\n      HttpServerStatsIf* stats);\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/Status1xxFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/Status1xxFilter.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nfolly::coro::Task<HTTPHeaderEvent> Status1xxFilter::readHeaderEvent() {\n  auto headerEvent = co_await HTTPSourceFilter::readHeaderEvent();\n  while (!headerEvent.isFinal()) {\n    // Ignore this event and wait for the next one.\n    XLOG(DBG4)\n        << fmt::format(\"A response with status code {} has been filtered out\",\n                       headerEvent.headers->getStatusCode());\n    headerEvent = co_await HTTPSourceFilter::readHeaderEvent();\n  }\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n  co_return headerEvent;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/Status1xxFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n\nnamespace proxygen::coro {\n\n/**\n * An HTTPSourceFilter that ignores 1xx responses\n */\nclass Status1xxFilter : public HTTPSourceFilter {\n public:\n  /**\n   * Read header event from underlying HTTPSource silently ignoring any 1xx\n   * responses\n   */\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n};\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/TransformFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/TransformFilter.h\"\n\nnamespace proxygen::coro {\n\nTransformFilter::TransformFilter(HTTPSource* source,\n                                 TransformFilter::HeaderTransformFn headerHook,\n                                 TransformFilter::BodyTransformFn bodyHook)\n    : HTTPSourceFilter(source),\n      headerHook_(std::move(headerHook)),\n      bodyHook_(std::move(bodyHook)) {\n}\n\nusing HTTPHeaderEventTask = folly::coro::Task<HTTPHeaderEvent>;\nHTTPHeaderEventTask TransformFilter::readHeaderEvent() {\n  auto headerEvent =\n      co_await co_awaitTry(readHeaderEventImpl(/*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n  if (headerHook_) {\n    co_return headerHook_(std::move(headerEvent));\n  }\n  co_return headerEvent;\n}\n\nusing HTTPBodyEventTask = folly::coro::Task<HTTPBodyEvent>;\nHTTPBodyEventTask TransformFilter::readBodyEvent(uint32_t max) {\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventImpl(max, /*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(bodyEvent));\n  if (bodyHook_) {\n    co_return bodyHook_(std::move(bodyEvent));\n  }\n  co_return bodyEvent;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/TransformFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n\nnamespace proxygen::coro {\n\n/**\n * This class provides a simple abstraction for filters that just want to\n * execute arbitrary transformations on events (HTTPHeaderEvent and\n * HTTPBodyEvent). Acts as a passthru filter if nullptr is passed in for both\n * hooks.\n */\nclass TransformFilter : public HTTPSourceFilter {\n public:\n  using HeaderTransformFn =\n      std::function<folly::Try<HTTPHeaderEvent>(folly::Try<HTTPHeaderEvent>&&)>;\n  using BodyTransformFn =\n      std::function<folly::Try<HTTPBodyEvent>(folly::Try<HTTPBodyEvent>&&)>;\n\n  explicit TransformFilter(HTTPSource* source,\n                           HeaderTransformFn headerHook = nullptr,\n                           BodyTransformFn bodyHook = nullptr);\n  ~TransformFilter() override = default;\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n private:\n  HeaderTransformFn headerHook_;\n  BodyTransformFn bodyHook_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/VisitorFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/VisitorFilter.h\"\n\nnamespace proxygen::coro {\n\nVisitorFilter::VisitorFilter(HTTPSource* source,\n                             VisitorFilter::HeaderHookFn headerHook,\n                             VisitorFilter::BodyHookFn bodyHook)\n    : HTTPSourceFilter(source),\n      headerHook_(std::move(headerHook)),\n      bodyHook_(std::move(bodyHook)) {\n}\n\nusing HTTPHeaderEventTask = folly::coro::Task<HTTPHeaderEvent>;\nHTTPHeaderEventTask VisitorFilter::readHeaderEvent() {\n  auto headerEvent =\n      co_await co_awaitTry(readHeaderEventImpl(/*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(headerEvent));\n  if (headerHook_) {\n    headerHook_(headerEvent);\n  }\n  if (headerEvent.hasException()) {\n    co_yield folly::coro::co_error(std::move(headerEvent.exception()));\n  }\n  co_return headerEvent;\n}\n\nusing HTTPBodyEventTask = folly::coro::Task<HTTPBodyEvent>;\nHTTPBodyEventTask VisitorFilter::readBodyEvent(uint32_t max) {\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventImpl(max, /*deleteOnDone=*/false));\n  auto guard = folly::makeGuard(lifetime(bodyEvent));\n  if (bodyHook_) {\n    bodyHook_(bodyEvent);\n  }\n  if (bodyEvent.hasException()) {\n    co_yield folly::coro::co_error(std::move(bodyEvent.exception()));\n  }\n  co_return bodyEvent;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/VisitorFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n\nnamespace proxygen::coro {\n\n/**\n * This class provides a simple abstraction for simple filters that just want to\n * observe events (HTTPHeaderEvent, HTTPBodyEvent, or Exception yielded) without\n * mutating via const ref. All events simply passthru this filter. Source can be\n * set in the constructor, or later via setSource.\n */\nclass VisitorFilter : public HTTPSourceFilter {\n public:\n  using HeaderHookFn = std::function<void(const folly::Try<HTTPHeaderEvent>&)>;\n  using BodyHookFn = std::function<void(const folly::Try<HTTPBodyEvent>&)>;\n\n  explicit VisitorFilter(HTTPSource* source,\n                         HeaderHookFn headerHook = nullptr,\n                         BodyHookFn bodyHook = nullptr);\n  ~VisitorFilter() override = default;\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n private:\n  HeaderHookFn headerHook_;\n  BodyHookFn bodyHook_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/CompressionFilterTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/CompressionFilter.h\"\n#include <folly/coro/GmockHelpers.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPHybridSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceReader.h>\n#include <proxygen/lib/http/coro/test/Mocks.h>\n#include <proxygen/lib/utils/ZlibStreamCompressor.h>\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nnamespace proxygen::coro::test {\nMATCHER_P(IOBufEquals, expected, \"\") {\n  return folly::IOBufEqualTo()(arg, expected);\n}\n\nstruct ZlibTest {\n  static std::unique_ptr<StreamDecompressor> makeDecompressor() {\n    return std::make_unique<ZlibStreamDecompressor>(CompressionType::GZIP);\n  }\n  static std::string getExpectedEncoding() {\n    return \"gzip\";\n  }\n  static int32_t getCompressionLevel() {\n    return 4 /* default */;\n  }\n};\n\nstruct ZstdTest {\n  static std::unique_ptr<StreamDecompressor> makeDecompressor() {\n    return std::make_unique<ZstdStreamDecompressor>();\n  }\n  static std::string getExpectedEncoding() {\n    return \"zstd\";\n  }\n  static int32_t getCompressionLevel() {\n    return 4 /* default */;\n  }\n};\n\ntemplate <typename T>\nclass CompressionFilterTest : public Test {\n public:\n  using CodecType = T;\n\n  void SetUp() override {\n    zd_ = T::makeDecompressor();\n    source_ = new HTTPStreamSource(&evb_);\n    source_->setHeapAllocated();\n  }\n\n  void TearDown() override {\n  }\n\n protected:\n  HTTPStreamSource* source_;\n  std::unique_ptr<StreamDecompressor> zd_;\n  folly::EventBase evb_;\n\n  void exercise_compression(\n      bool expectCompression,\n      const std::string& acceptedEncoding,\n      const std::string& expectedEncoding,\n      std::unique_ptr<HTTPMessage> respMsg,\n      std::unique_ptr<folly::IOBuf> maybeChainedRespBody,\n      const CompressionFilterUtils::FactoryOptions& compressionOpts) {\n    // single iobuf in chain implies non-chunked msg\n    bool isResponseChunked = maybeChainedRespBody->isChained();\n\n    // testing response-only path (TODO: request&response path)\n    if (!isResponseChunked) {\n      // send unchunked response (however, coro doesn't currently support\n      // unchunked compression – it will later override ContentLength header)\n      EXPECT_FALSE(respMsg->getIsChunked());\n      const auto respLen = maybeChainedRespBody->computeChainDataLength();\n      respMsg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH,\n                                folly::to<std::string>(respLen));\n    }\n    respMsg->setIsChunked(isResponseChunked);\n    // queue response into source (with trailers to exercise trailing chunk)\n    source_->headers(std::move(respMsg), /*eom=*/false);\n    source_->body(maybeChainedRespBody->clone(), /*padding=*/0, /*eom=*/false);\n    source_->trailers([]() {\n      auto msg = std::make_unique<HTTPHeaders>();\n      msg->add(\"foo\", \"bar\");\n      return msg;\n    }());\n    source_->eom();\n\n    // create the compression filter if the client has declared support for it\n    HTTPMessage req;\n    req.getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, acceptedEncoding);\n    auto filterParams =\n        std::make_shared<folly::Optional<CompressionFilterUtils::FilterParams>>(\n            CompressionFilterUtils::getFilterParams(req, compressionOpts));\n\n    HTTPSourceReader reader;\n    // create compression filter and set it to read from source_; set reader to\n    // read from compression filter\n    auto* compressionFilter =\n        new CompressionFilter(source_, std::move(filterParams));\n    compressionFilter->setHeapAllocated();\n    reader.setSource(compressionFilter);\n\n    reader.onHeaders(\n        [expectCompression, expectedEncoding](\n            std::unique_ptr<HTTPMessage> msg, bool /*final*/, bool /*eom*/) {\n          if (expectCompression) {\n            EXPECT_TRUE(msg->checkForHeaderToken(\n                HTTP_HEADER_CONTENT_ENCODING, expectedEncoding.c_str(), false));\n          }\n          // coro filter currently only supports chunked compression\n          EXPECT_EQ(expectCompression, msg->getIsChunked());\n          // \"Content-Length\" is not set on chunked messages\n          const auto& headers = msg->getHeaders();\n          EXPECT_EQ(msg->getIsChunked(), !headers.exists(\"Content-Length\"));\n          return HTTPSourceReader::Continue;\n        });\n\n    // Accumulate the body, decompressing it if it's compressed\n    std::unique_ptr<folly::IOBuf> decompressedResponseBody;\n    reader.onBody([&](BufQueue body, bool eof) -> bool {\n      // nothing to process\n      if (body.chainLength() == 0) {\n        return HTTPSourceReader::Continue;\n      }\n      auto processedBody =\n          expectCompression ? zd_->decompress(body.move().get()) : body.move();\n      CHECK(!zd_->hasError()) << \"Failed to decompress body!\";\n      if (decompressedResponseBody) {\n        decompressedResponseBody->prependChain(std::move(processedBody));\n      } else {\n        decompressedResponseBody = std::move(processedBody);\n      }\n      return HTTPSourceReader::Continue;\n    });\n\n    // read source\n    co_withExecutor(&evb_, reader.read()).start();\n    evb_.loop();\n\n    CHECK(decompressedResponseBody);\n    EXPECT_THAT(decompressedResponseBody.get(),\n                IOBufEquals(maybeChainedRespBody.get()));\n  }\n\n  // Helper method to convert a vector of strings to an IOBuf chain\n  // specifically create a chain because the chain pieces are chunks\n  std::unique_ptr<folly::IOBuf> createResponseChain(\n      const std::vector<std::string>& bodyStrings) {\n    std::unique_ptr<folly::IOBuf> responseBodyChain;\n    for (auto& s : bodyStrings) {\n      auto nextBody = folly::IOBuf::copyBuffer(s.c_str());\n      if (responseBodyChain) {\n        responseBodyChain->prependChain(std::move(nextBody));\n      } else {\n        responseBodyChain = std::move(nextBody);\n      }\n    }\n    return responseBodyChain;\n  }\n\n  // create compression object with provided options\n  CompressionFilterUtils::FactoryOptions createCompressionOpts(\n      int32_t compressionLevel = T::getCompressionLevel(),\n      uint32_t minimumCompressionSize = 1,\n      bool disableCompressionForThisEncoding = false) {\n    CompressionFilterUtils::FactoryOptions opts{};\n    opts.zlibCompressionLevel = compressionLevel;\n    opts.minimumCompressionSize = minimumCompressionSize;\n    opts.compressibleContentTypes = std::make_shared<std::set<std::string>>(\n        std::set<std::string>{\"text/html\"});\n    opts.enableZstd = true;\n    if (disableCompressionForThisEncoding) {\n      if (CodecType::getExpectedEncoding() == \"gzip\") {\n        opts.enableGzip = false;\n      }\n      if (CodecType::getExpectedEncoding() == \"zstd\") {\n        opts.enableZstd = false;\n      }\n    }\n    return opts;\n  }\n\n  // create http resp message object with provided headers\n  std::unique_ptr<HTTPMessage> createResponse(\n      std::string contentType, bool respAlreadyCompressed = false) {\n    // create response message & headers\n    auto msg = makeResponse(200);\n    msg->getHeaders().set(HTTP_HEADER_CONTENT_TYPE, contentType);\n    // response already compressed, signal via header to skip filter\n    if (respAlreadyCompressed) {\n      msg->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"gzip\");\n    }\n    return msg;\n  }\n};\n\nusing CompressionCodecs = ::testing::Types<ZlibTest, ZstdTest>;\n\nTYPED_TEST_SUITE(CompressionFilterTest, CompressionCodecs);\n\n// Basic smoke test\nTYPED_TEST(CompressionFilterTest, NonchunkedCompression) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, ChunkedCompression) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello\", \" World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, ParameterizedContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html; param1\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MixedcaseContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"Text/Html; param1\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\n// Client supports multiple possible compression encodings\nTYPED_TEST(CompressionFilterTest, MultipleAcceptedEncodings) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding() +\n            \", identity, deflate\",\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\n// Server skips compressing if the response is already compressed\nTYPED_TEST(CompressionFilterTest, ResponseAlreadyCompressedTest) {\n  using Codec = typename TestFixture::CodecType;\n  auto compressor =\n      std::make_unique<ZlibStreamCompressor>(CompressionType::GZIP, 4);\n  auto fakeCompressed = folly::IOBuf::copyBuffer(\"helloimsupposedlycompressed\");\n  auto compressionOpts =\n      this->createCompressionOpts(/*compressionLevel=*/4,\n                                  /*minimumCompressionSize=*/1);\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/\n        this->createResponse(\"text/html\", /*responseAlreadyCompressed=*/true),\n        /*maybeChainedRespBody=*/std::move(fakeCompressed),\n        /*compressionOpts=*/compressionOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MultipleAcceptedEncodingsQvalues) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding() + \"; q=.7;, identity\",\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, NoCompressibleAcceptedEncodings) {\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/\"identity; q=.7;\",\n        /*expectedEncoding=*/\"\",\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MissingAcceptedEncodings) {\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/\"\",\n        /*expectedEncoding=*/\"\",\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\n// Content is of an-uncompressible content-type\nTYPED_TEST(CompressionFilterTest, UncompressibleContenttype) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/\"\",\n        /*respMsg=*/this->createResponse(\"image/jpeg\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, UncompressibleContenttypeParam) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto defaultOpts = this->createCompressionOpts();\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/\"\",\n        /*respMsg=*/this->createResponse(\"application/jpeg; param1\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/defaultOpts);\n  });\n}\n\n// Content is under the minimum compression size\nTYPED_TEST(CompressionFilterTest, TooSmallToCompress) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto compressionOpts = this->createCompressionOpts(\n      /*compressionLevel=*/Codec::getCompressionLevel(),\n      /*minimumCompressionSize=*/1000);\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/\"\",\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/compressionOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, SmallChunksCompress) {\n  // Expect this to compress despite being small because can't tell the content\n  // length when we're chunked\n  using Codec = typename TestFixture::CodecType;\n  std::vector<std::string> chunks = {\"Hello\", \" World\"};\n  auto compressionOpts = this->createCompressionOpts(\n      /*compressionLevel=*/Codec::getCompressionLevel(),\n      /*minimumCompressionSize=*/1000);\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/compressionOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, MinimumCompressSizeEqualToRequestSize) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto compressionOpts = this->createCompressionOpts(\n      /*compressionLevel=*/Codec::getCompressionLevel(),\n      /*minimumCompressionSize=*/chunks[0].size());\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/true,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/compressionOpts);\n  });\n}\n\nTYPED_TEST(CompressionFilterTest, CompressionDisabledForEncoding) {\n  using Codec = typename TestFixture::CodecType;\n  const std::vector<std::string> chunks = {\"Hello World\"};\n  auto compressionOpts = this->createCompressionOpts(\n      /*compressionLevel=*/1,\n      /*minimumCompressionSize=*/1,\n      /*disableCompressionForThisEncoding=*/true);\n  ASSERT_NO_FATAL_FAILURE({\n    this->exercise_compression(\n        /*expectCompression=*/false,\n        /*acceptedEncoding=*/Codec::getExpectedEncoding(),\n        /*expectedEncoding=*/Codec::getExpectedEncoding(),\n        /*respMsg=*/this->createResponse(\"text/html\"),\n        /*maybeChainedRespBody=*/this->createResponseChain(chunks),\n        /*compressionOpts=*/compressionOpts);\n  });\n}\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/DecompressionFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceFilter.h>\n#include <proxygen/lib/http/coro/filters/DecompressionFilter.h>\n#include <proxygen/lib/http/coro/filters/DecompressionFilterFactory.h>\n#include <proxygen/lib/http/coro/test/HTTPTestSources.h>\n#include <proxygen/lib/http/coro/util/test/TestHelpers.h>\n\nusing namespace testing;\n\nnamespace {\n\nusing namespace proxygen::coro;\nusing folly::coro::co_error;\n\nHTTPSource* getEgressDecompressionFilter(HTTPSource* source) {\n  auto decompressionFilters = ClientDecompressionFilterFactory{}.makeFilters();\n  // unused, avoid memory leak\n  delete decompressionFilters.second;\n  decompressionFilters.first->setSource(source);\n  return decompressionFilters.first;\n}\n\nHTTPSource* getIngressDecompressionFilter(HTTPSource* source) {\n  auto decompressionFilters = ClientDecompressionFilterFactory{}.makeFilters();\n  // unused, avoid memory leak\n  delete decompressionFilters.first;\n  decompressionFilters.second->setSource(source);\n  return decompressionFilters.second;\n}\n\n}; // namespace\n\nnamespace proxygen::coro::test {\n\nclass DecompressionEgressFilterTest : public testing::Test {\n public:\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nCO_TEST_F_X(DecompressionEgressFilterTest, TestExceptionPassthru) {\n  // test yielding exception on header event\n  {\n    YieldExceptionSource headerEventExceptionSource{\n        YieldExceptionSource::Stage::HeaderEvent,\n        YieldExceptionSource::MessageType::Request};\n\n    // wrap reqSource with an egress decompression filter\n    auto* egressFilter =\n        getEgressDecompressionFilter(&headerEventExceptionSource);\n\n    auto headerEvent = co_await co_awaitTry(egressFilter->readHeaderEvent());\n    XCHECK(headerEvent.hasException());\n  }\n\n  // test yielding exception on body event\n  {\n    YieldExceptionSource bodyEventExceptionSource =\n        YieldExceptionSource{YieldExceptionSource::Stage::BodyEvent,\n                             YieldExceptionSource::MessageType::Request};\n    auto* egressFilter =\n        getEgressDecompressionFilter(&bodyEventExceptionSource);\n\n    auto headerEvent = co_await co_awaitTry(egressFilter->readHeaderEvent());\n    XCHECK(!headerEvent.hasException());\n    auto bodyEvent = co_await co_awaitTry(egressFilter->readBodyEvent());\n    XCHECK(bodyEvent.hasException());\n  }\n}\n\nCO_TEST_F_X(DecompressionEgressFilterTest, SkipExistingAcceptEncoding) {\n  // create req HTTPMessage with existing \"accept-encoding: foo\" header\n  auto getReq = makeGetRequest();\n  getReq->getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, \"foo\");\n  auto* reqSource =\n      HTTPFixedSource::makeFixedSource(std::move(getReq), nullptr);\n\n  // wrap reqSource with an egress decompression filter\n  auto* egressFilter = getEgressDecompressionFilter(reqSource);\n\n  // check filter doesn't modify header\n  auto headerEvent = co_await co_awaitTry(egressFilter->readHeaderEvent());\n  XCHECK(!headerEvent.hasException() && headerEvent->eom);\n  auto acceptEncoding = headerEvent->headers->getHeaders().getSingleOrEmpty(\n      HTTP_HEADER_ACCEPT_ENCODING);\n  EXPECT_EQ(acceptEncoding, \"foo\");\n}\n\nCO_TEST_F_X(DecompressionEgressFilterTest, AddAcceptEncodingIfMissing) {\n  // create req HTTPMessage without \"accept-encoding\" header\n  auto reqSource = HTTPFixedSource::makeFixedSource(makeGetRequest(), nullptr);\n\n  // wrap reqSource with an egress decompression filter\n  auto* egressFilter = getEgressDecompressionFilter(reqSource);\n\n  // check filter adds headers\n  auto headerEvent = co_await co_awaitTry(egressFilter->readHeaderEvent());\n  XCHECK(!headerEvent.hasException() && headerEvent->eom);\n  auto acceptEncoding = headerEvent->headers->getHeaders().getSingleOrEmpty(\n      HTTP_HEADER_ACCEPT_ENCODING);\n  EXPECT_EQ(acceptEncoding, \"gzip, deflate, zstd\");\n}\n\nclass DecompressionIngressFilterTest : public testing::Test {\n public:\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nCO_TEST_F_X(DecompressionIngressFilterTest, TestExceptionPassthru) {\n  // test yielding exception on header event\n  {\n    YieldExceptionSource headerEventExceptionSource{\n        YieldExceptionSource::Stage::HeaderEvent,\n        YieldExceptionSource::MessageType::Response};\n\n    // wrap reqSource with an egress decompression filter\n    auto* ingressFilter =\n        getIngressDecompressionFilter(&headerEventExceptionSource);\n\n    auto headerEvent = co_await co_awaitTry(ingressFilter->readHeaderEvent());\n    XCHECK(headerEvent.hasException());\n  }\n\n  // test yielding exception on body event\n  {\n    YieldExceptionSource bodyEventExceptionSource =\n        YieldExceptionSource{YieldExceptionSource::Stage::BodyEvent,\n                             YieldExceptionSource::MessageType::Response};\n\n    auto* ingressFilter =\n        getIngressDecompressionFilter(&bodyEventExceptionSource);\n\n    auto headerEvent = co_await co_awaitTry(ingressFilter->readHeaderEvent());\n    XCHECK(!headerEvent.hasException());\n    auto bodyEvent = co_await co_awaitTry(ingressFilter->readBodyEvent());\n    XCHECK(bodyEvent.hasException());\n  }\n}\n\nstruct DecompressionIngressFilterCompressionParam {\n  DecompressionIngressFilterCompressionParam(std::string_view encoding,\n                                             std::string_view body)\n      : encoding{encoding}, body{folly::unhexlify(body)} {\n  }\n\n  const std::string encoding;\n  const std::string body;\n};\n\nclass DecompressionIngressFilterCompressionTest\n    : public DecompressionIngressFilterTest\n    , public testing::WithParamInterface<\n          DecompressionIngressFilterCompressionParam> {};\n\nCO_TEST_P_X(DecompressionIngressFilterCompressionTest,\n            TestClientDecompression) {\n  HTTPFixedSource respSource(\n      makeResponse(200),\n      /*body=*/folly::IOBuf::wrapBuffer(folly::ByteRange{GetParam().body}));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                    GetParam().encoding);\n\n  // should we modify the headers if just eom (i.e. no body)?\n  auto* ingressFilter = getIngressDecompressionFilter(&respSource);\n  auto headerEvent = co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  XCHECK(!headerEvent.hasException());\n  const auto& respHeaders = headerEvent->headers->getHeaders();\n  // should have been stripped by the filter\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_ENCODING));\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_LENGTH));\n  const auto& transferEncoding =\n      respHeaders.getSingleOrEmpty(HTTP_HEADER_TRANSFER_ENCODING);\n  EXPECT_EQ(transferEncoding, \"chunked\");\n\n  auto bodyEvent = co_await co_awaitTry(ingressFilter->readBodyEvent());\n  XCHECK(!bodyEvent.hasException());\n  XCHECK(bodyEvent->eventType == HTTPBodyEvent::EventType::BODY &&\n         bodyEvent->eom);\n  EXPECT_EQ(bodyEvent->event.body.move()->toString(),\n            \"abcdefghijklmnopqrstuvwxyz\");\n}\n\nCO_TEST_P_X(DecompressionIngressFilterCompressionTest,\n            TestServerDecompression) {\n  HTTPFixedSource reqSource(\n      makePostRequest(GetParam().body.size()),\n      /*body=*/folly::IOBuf::wrapBuffer(folly::ByteRange{GetParam().body}));\n  reqSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                   GetParam().encoding);\n\n  auto&& [requestFilter, responseFilter] =\n      ServerDecompressionFilterFactory{}.makeFilters();\n  XCHECK(responseFilter == nullptr);\n  XCHECK(requestFilter != nullptr);\n  requestFilter->setSource(&reqSource);\n\n  const auto headerEvent =\n      co_await co_awaitTry(requestFilter->readHeaderEvent());\n  XCHECK(headerEvent.hasValue());\n  const auto& respHeaders = headerEvent->headers->getHeaders();\n  // should have been stripped by the filter\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_ENCODING));\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_LENGTH));\n  const auto& transferEncoding =\n      respHeaders.getSingleOrEmpty(HTTP_HEADER_TRANSFER_ENCODING);\n  EXPECT_EQ(transferEncoding, \"chunked\");\n\n  auto bodyEvent = co_await co_awaitTry(requestFilter->readBodyEvent());\n  XCHECK(bodyEvent.hasValue() &&\n         bodyEvent->eventType == HTTPBodyEvent::EventType::BODY &&\n         bodyEvent->eom);\n  EXPECT_EQ(bodyEvent->event.body.move()->toString(),\n            \"abcdefghijklmnopqrstuvwxyz\");\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    DecompressionIngressFilterCompressionTests,\n    DecompressionIngressFilterCompressionTest,\n    testing::Values(DecompressionIngressFilterCompressionParam(\n                        \"gzip\",\n                        // gzip of \"abcdefghijklmnopqrstuvwxyz\"\n                        \"1f8b080092a3326600ff011a00e5ff6162636465666768696a6b6c\"\n                        \"6d6e6f7071727374\"\n                        \"75767778797abd50274c1a000000\"),\n                    DecompressionIngressFilterCompressionParam(\n                        \"deflate\",\n                        // deflate of \"abcdefghijklmnopqrstuvwxyz\"\n                        \"7801011a00e5ff6162636465666768696a6b6c6d6e6f7071727374\"\n                        \"75767778797a90860b20\"),\n                    DecompressionIngressFilterCompressionParam(\n                        \"zstd\",\n                        // zstd of \"abcdefghijklmnopqrstuvwxyz\"\n                        \"28b52ffd0458d100006162636465666768696a6b6c6d6e6f707172\"\n                        \"737475767778797a5c8389fa\")),\n    [](const auto& info) { return info.param.encoding; });\n\nCO_TEST_F_X(DecompressionIngressFilterTest, TestUnsupportedCompression) {\n  HTTPFixedSource respSource(\n      makeResponse(200),\n      /*body=*/folly::IOBuf::copyBuffer(\"abcdefghijklmnopqrstuvwxyz\"));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                    \"foo-bar-baz\");\n\n  auto* ingressFilter = getIngressDecompressionFilter(&respSource);\n  auto headerEvent = co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  XCHECK(!headerEvent.hasException());\n  const auto& respHeaders = headerEvent->headers->getHeaders();\n  // should NOT have been stripped by the filter\n  const auto& contentEncoding =\n      respHeaders.getSingleOrEmpty(HTTP_HEADER_CONTENT_ENCODING);\n  EXPECT_EQ(contentEncoding, \"foo-bar-baz\");\n  EXPECT_TRUE(respHeaders.exists(HTTP_HEADER_CONTENT_LENGTH));\n\n  auto bodyEvent = co_await co_awaitTry(ingressFilter->readBodyEvent());\n  XCHECK(!bodyEvent.hasException());\n  XCHECK(bodyEvent->eventType == HTTPBodyEvent::EventType::BODY &&\n         bodyEvent->eom);\n  // should be identical to original body\n  EXPECT_EQ(bodyEvent->event.body.move()->to<std::string>(),\n            \"abcdefghijklmnopqrstuvwxyz\");\n}\n\nCO_TEST_F_X(DecompressionIngressFilterTest, TestMalformedCompression) {\n  // gzip of \"abcdefghijklmnopqrstuvwxyz\" prefixed with \"faceb00c\"\n  auto gzip = folly::unhexlify(\n      \"faceb00c\"\n      \"1f8b080092a3326600ff011a00e5ff6162636465666768696a6b6c6d6e6f707172737475\"\n      \"767778797abd50274c1a000000\");\n\n  HTTPFixedSource respSource(makeResponse(200),\n                             /*body=*/folly::IOBuf::copyBuffer(gzip));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"gzip\");\n\n  auto* ingressFilter = getIngressDecompressionFilter(&respSource);\n  auto headerEvent = co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  XCHECK(!headerEvent.hasException());\n  const auto& respHeaders = headerEvent->headers->getHeaders();\n  // should have been stripped by the filter\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_ENCODING));\n  EXPECT_FALSE(respHeaders.exists(HTTP_HEADER_CONTENT_LENGTH));\n  const auto& transferEncoding =\n      respHeaders.getSingleOrEmpty(HTTP_HEADER_TRANSFER_ENCODING);\n  EXPECT_EQ(transferEncoding, \"chunked\");\n\n  auto bodyEvent = co_await co_awaitTry(ingressFilter->readBodyEvent());\n  XCHECK(bodyEvent.hasException());\n}\n\nclass MockStatsCallback : public DecompressionIngressFilter::StatsCallback {\n public:\n  MOCK_METHOD(void, onDecompressionAlgo, (const std::string& algo), (override));\n  MOCK_METHOD(void, onDecompressionError, (), (override));\n};\n\nCO_TEST_P_X(DecompressionIngressFilterCompressionTest,\n            TestStatsCallback_Normal) {\n  HTTPFixedSource respSource(\n      makeResponse(200),\n      /*body=*/folly::IOBuf::wrapBuffer(folly::ByteRange{GetParam().body}));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                    GetParam().encoding);\n\n  auto cb = std::make_shared<MockStatsCallback>();\n  auto* ingressFilter = new DecompressionIngressFilter(&respSource, cb);\n  ingressFilter->setHeapAllocated();\n\n  EXPECT_CALL(*cb, onDecompressionAlgo(GetParam().encoding)).Times(1);\n  EXPECT_CALL(*cb, onDecompressionError()).Times(0);\n\n  co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  co_await co_awaitTry(ingressFilter->readBodyEvent());\n}\n\nCO_TEST_F_X(DecompressionIngressFilterTest, TestStatsCallback_Unsupported) {\n  HTTPFixedSource respSource(\n      makeResponse(200),\n      /*body=*/folly::IOBuf::copyBuffer(\"abcdefghijklmnopqrstuvwxyz\"));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING,\n                                    \"foo-bar-baz\");\n\n  auto cb = std::make_shared<MockStatsCallback>();\n  auto* ingressFilter = new DecompressionIngressFilter(&respSource, cb);\n  ingressFilter->setHeapAllocated();\n\n  EXPECT_CALL(*cb, onDecompressionAlgo(\"\")).Times(1);\n  EXPECT_CALL(*cb, onDecompressionError()).Times(0);\n  co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  co_await co_awaitTry(ingressFilter->readBodyEvent());\n}\n\nCO_TEST_F_X(DecompressionIngressFilterTest, TestStatsCallback_Failure) {\n  // gzip of \"abcdefghijklmnopqrstuvwxyz\" prefixed with \"faceb00c\"\n  auto gzip = folly::unhexlify(\n      \"faceb00c\"\n      \"1f8b080092a3326600ff011a00e5ff6162636465666768696a6b6c6d6e6f707172737475\"\n      \"767778797abd50274c1a000000\");\n\n  HTTPFixedSource respSource(makeResponse(200),\n                             /*body=*/folly::IOBuf::copyBuffer(gzip));\n  respSource.msg_->getHeaders().set(HTTP_HEADER_CONTENT_ENCODING, \"gzip\");\n\n  auto cb = std::make_shared<MockStatsCallback>();\n  auto* ingressFilter = new DecompressionIngressFilter(&respSource, cb);\n  ingressFilter->setHeapAllocated();\n\n  EXPECT_CALL(*cb, onDecompressionAlgo(\"gzip\")).Times(1);\n  EXPECT_CALL(*cb, onDecompressionError()).Times(1);\n\n  co_await co_awaitTry(ingressFilter->readHeaderEvent());\n  auto bodyEvent = co_await co_awaitTry(ingressFilter->readBodyEvent());\n  XCHECK(bodyEvent.hasException());\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/FakeServerStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/test/FakeServerStats.h\"\n\n#include <proxygen/lib/http/HTTPMessage.h>\n\nnamespace proxygen {\n\nvoid FakeHTTPServerStats::recordRequest(const HTTPMessage& msg) {\n  reqs++;\n}\n\nvoid FakeHTTPServerStats::recordResponse(const HTTPMessage& msg) {\n  const auto status = msg.getStatusCode();\n  responseCodes[status / 100]++;\n}\n\nvoid FakeHTTPServerStats::recordRequestComplete(\n    std::chrono::milliseconds latency,\n    ProxygenError err,\n    size_t requestBodyBytes,\n    size_t responseBodyBytes) {\n  reqBodyBytes += requestBodyBytes;\n  resBodyBytes += responseBodyBytes;\n  errors += err != ProxygenError::kErrorNone;\n  errorTypes[static_cast<uint8_t>(err)]++;\n  latencies.push_back(latency);\n}\n\nvoid FakeHTTPServerStats::recordAbort() {\n  aborts++;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/FakeServerStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <array>\n#include <vector>\n\n#include \"proxygen/lib/http/stats/HttpServerStats.h\"\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n\nnamespace proxygen {\n\nclass FakeHTTPServerStats : public HttpServerStatsIf {\n public:\n  explicit FakeHTTPServerStats() = default;\n  ~FakeHTTPServerStats() override = default;\n\n  void recordRequest(const HTTPMessage& msg) override;\n  void recordResponse(const HTTPMessage& msg) override;\n  void recordAbort() override;\n\n  void recordRequestComplete(std::chrono::milliseconds latency,\n                             ProxygenError err,\n                             size_t requestBodyBytes,\n                             size_t responseBodyBytes) override;\n\n  uint64_t reqs{0};\n  uint64_t reqBodyBytes{0};\n  uint64_t resBodyBytes{0};\n  uint64_t aborts{0};\n  uint64_t errors{0};\n  std::vector<std::chrono::milliseconds> latencies;\n\n  // 1xx -> 5xx (ignore index 0)\n  std::array<uint64_t, 6> responseCodes{};\n  // errors categorized by type\n  std::array<uint64_t, ProxygenError::kErrorMax> errorTypes{};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/HTTPRedirectHandlerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/HTTPRedirectHandler.h\"\n#include \"proxygen/lib/http/codec/test/TestUtils.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/test/HTTPTestSources.h\"\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n#include <folly/coro/Sleep.h>\n\nusing namespace testing;\n\nnamespace proxygen::coro::test {\n\nHTTPFixedSource* getRedirectResponse(\n    uint16_t code,\n    const std::string& location,\n    std::unique_ptr<folly::IOBuf> body = nullptr) {\n  auto resp = HTTPFixedSource::makeFixedResponse(code);\n  if (!location.empty()) {\n    resp->msg_->getHeaders().set(HTTP_HEADER_LOCATION, location);\n  }\n  resp->body_ = std::move(body);\n  return resp;\n}\n\nclass MockSessionFactory : public HTTPSessionFactory {\n public:\n  ~MockSessionFactory() override = default;\n  MOCK_METHOD(folly::coro::Task<GetSessionResult>,\n              getSessionWithReservation,\n              (std::string,\n               uint16_t,\n               bool,\n               std::chrono::milliseconds,\n               folly::Optional<std::string>));\n  MOCK_METHOD(bool, requiresAbsoluteURLs, (), (const));\n};\n\nclass HTTPRedirectHandlerTest : public testing::Test {\n public:\n  void SetUp() override {\n    ON_CALL(*mockSessionFactory_, requiresAbsoluteURLs)\n        .WillByDefault(ReturnPointee(&requiresAbsoluteURLs_));\n  }\n\n  folly::coro::Task<void> run(\n      HTTPSource* reqSource,\n      HTTPSource* respSource,\n      uint16_t expectedCode = 200,\n      std::function<void(const std::string&)> redirectCallback = nullptr) {\n    HTTPRedirectHandler redirectHandler(mockSessionFactory_);\n    // ok if nullptr\n    redirectHandler.setRedirectCallback(std::move(redirectCallback));\n\n    HTTPSourceReader reqReader;\n    reqReader.setSource(redirectHandler.getRequestFilter(reqSource));\n    co_await reqReader.read();\n    HTTPSourceReader respReader;\n    respReader.onHeaders(\n        [expectedCode](std::unique_ptr<HTTPMessage> resp, bool, bool) {\n          EXPECT_EQ(resp->getStatusCode(), expectedCode);\n          return HTTPSourceReader::Continue;\n        });\n    respReader.setSource(redirectHandler.getResponseFilter(respSource));\n    co_await respReader.read();\n  }\n\n protected:\n  void expectRedirect(\n      const std::string& host,\n      uint16_t port,\n      bool secure,\n      std::function<folly::coro::Task<HTTPSourceHolder>(\n          HTTPSourceHolder, HTTPCoroSession::RequestReservation)> sendFn) {\n    EXPECT_CALL(*mockSessionFactory_,\n                getSessionWithReservation(host, port, secure, _, _))\n        .WillOnce(folly::coro::gmock_helpers::CoReturnByMove(\n            HTTPSessionFactory::GetSessionResult(\n                HTTPCoroSession::RequestReservation(), &mockSession_)));\n    EXPECT_CALL(mockSession_, sendRequest(_, _))\n        .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n            [sendFn =\n                 std::move(sendFn)](HTTPSourceHolder request,\n                                    HTTPCoroSession::RequestReservation res)\n                -> folly::coro::Task<HTTPSourceHolder> {\n              return sendFn(std::move(request), std::move(res));\n            }));\n  }\n\n  std::shared_ptr<MockSessionFactory> mockSessionFactory_{\n      std::make_shared<MockSessionFactory>()};\n  MockHTTPCoroSession mockSession_;\n  bool requiresAbsoluteURLs_{false};\n};\n\nCO_TEST_F(HTTPRedirectHandlerTest, NoRedirect) {\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n      HTTPFixedSource::makeFixedResponse(200, \"success\"));\n}\n\nCO_TEST_F(HTTPRedirectHandlerTest, BasicRedirects) {\n  auto codes = {301, 302, 303, 307};\n  for (auto code : codes) {\n    expectRedirect(\"www.facebook-redirect.com\",\n                   443,\n                   true,\n                   [](HTTPSourceHolder, HTTPCoroSession::RequestReservation)\n                       -> folly::coro::Task<HTTPSourceHolder> {\n                     co_return HTTPFixedSource::makeFixedResponse(200,\n                                                                  \"success\");\n                   });\n    co_await run(\n        HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n        getRedirectResponse(code, \"https://www.facebook-redirect.com/\"));\n  }\n}\n\nCO_TEST_F(HTTPRedirectHandlerTest, AbsoluteRedirect) {\n  requiresAbsoluteURLs_ = true;\n  expectRedirect(\n      \"www.facebook-redirect.com\",\n      443,\n      true,\n      [](HTTPSourceHolder reqSource, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await reqSource.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getURL(),\n                  \"https://www.facebook-redirect.com/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n      getRedirectResponse(302, \"https://www.facebook-redirect.com/\"));\n}\n\n// 301 redirect on a POST is not handleable\nCO_TEST_F(HTTPRedirectHandlerTest, PostRedirect301) {\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(\n          URL(\"https://www.facebook.com/\"), HTTPMethod::POST, makeBuf(100)),\n      getRedirectResponse(301, \"https://www.facebook-redirect.com/\"),\n      301);\n}\n\n// 303 redirect on a POST discards the request body and converts to GET\nCO_TEST_F(HTTPRedirectHandlerTest, NoBodyOn303PostRedirect) {\n  expectRedirect(\n      \"www.facebook-redirect.com\",\n      443,\n      true,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getMethod(), HTTPMethod::GET);\n        EXPECT_TRUE(headerEvent.eom);\n        // validate content length header is cleared\n        EXPECT_FALSE(headerEvent.headers->getHeaders().exists(\n            HTTP_HEADER_CONTENT_LENGTH));\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(\n          URL(\"https://www.facebook.com/\"), HTTPMethod::POST, makeBuf(100)),\n      getRedirectResponse(303, \"https://www.facebook-redirect.com/\"));\n}\n\n// Non GET/HEAD/POST are unredirectable\nCO_TEST_F(HTTPRedirectHandlerTest, OptionsRedirect) {\n  co_await run(HTTPFixedSource::makeFixedRequest(\n                   URL(\"https://www.facebook.com/\"), HTTPMethod::OPTIONS),\n               getRedirectResponse(303, \"https://www.facebook-redirect.com/\"),\n               303);\n}\n\n// Can only redirect to http or https\nCO_TEST_F(HTTPRedirectHandlerTest, UnknownScheme) {\n  auto maybe = co_await co_awaitTry(\n      run(HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n          getRedirectResponse(302, \"masque://www.facebook-redirect.com/\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::UnsupportedScheme);\n}\n\n// Must have a location header\nCO_TEST_F(HTTPRedirectHandlerTest, NoLocation) {\n  auto maybe = co_await co_awaitTry(\n      run(HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n          getRedirectResponse(302, \"\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::InvalidRedirect);\n}\n\n// Exceeded MaxRedirects (default=1)\nCO_TEST_F(HTTPRedirectHandlerTest, MaxRedirects) {\n  expectRedirect(\"www.facebook-redirect.com\",\n                 443,\n                 true,\n                 [](HTTPSourceHolder, HTTPCoroSession::RequestReservation)\n                     -> folly::coro::Task<HTTPSourceHolder> {\n                   co_return getRedirectResponse(\n                       302, \"https://www.facebook-redirect.com/\");\n                 });\n  auto maybe = co_await co_awaitTry(\n      run(HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n          getRedirectResponse(302, \"https://www.facebook-redirect.com/\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::MaxRedirects);\n}\n\n// Location header unparsable\nCO_TEST_F(HTTPRedirectHandlerTest, InvalidLocation) {\n  auto maybe = co_await co_awaitTry(\n      run(HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n          getRedirectResponse(302, \":\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::InvalidRedirect);\n}\n\nCO_TEST_F(HTTPRedirectHandlerTest, AbortIncompleteSourceOnError) {\n  class StopFilter : public HTTPSourceFilter {\n   public:\n    void stopReading(\n        folly::Optional<const proxygen::coro::HTTPErrorCode> err) override {\n      stopped_ = true;\n      HTTPSourceFilter::stopReading(err);\n    }\n    bool stopped_{false};\n  };\n\n  HTTPRedirectHandler redirectHandler(mockSessionFactory_);\n  HTTPSourceReader reqReader;\n  reqReader.setSource(redirectHandler.getRequestFilter(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\"))));\n  co_await reqReader.read();\n  HTTPSourceReader respReader;\n  StopFilter stopFilter;\n  stopFilter.setSource(getRedirectResponse(302, \":\", makeBuf(10)));\n  respReader.setSource(redirectHandler.getResponseFilter(&stopFilter));\n  auto maybe = co_await co_awaitTry(respReader.read());\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::InvalidRedirect);\n  // source stopped before HTTPRedirectHandler goes out of scope\n  EXPECT_TRUE(stopFilter.stopped_);\n}\n\n// A relative redirect with an absolute original URL will use the URL\n// host/port/scheme\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectAbsoluteOriginal) {\n  expectRedirect(\n      \"www.facebook.com\",\n      443,\n      true,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getURL(), \"/redirect\");\n        EXPECT_EQ(headerEvent.headers->getHeaders().getSingleOrEmpty(\n                      HTTP_HEADER_HOST),\n                  \"www.facebook.com\");\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n\n  auto req = HTTPFixedSource::makeFixedRequest(\"/\");\n  req->msg_->setURL(\"https://www.facebook.com\");\n  req->msg_->setSecure(true);\n  co_await run(req, getRedirectResponse(302, \"/redirect\"));\n}\n\n// A relative redirect with an absolute original URL will use the URL\n// host/port/scheme, even through a proxy that requires absolute URLs\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectAbsoluteOriginalProxy) {\n  requiresAbsoluteURLs_ = true;\n  expectRedirect(\n      \"www.facebook.com\",\n      443,\n      true,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getURL(),\n                  \"https://www.facebook.com/redirect\");\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n\n  auto req = HTTPFixedSource::makeFixedRequest(\"/\");\n  req->msg_->setURL(\"https://www.facebook.com\");\n  req->msg_->setSecure(true);\n  co_await run(req, getRedirectResponse(302, \"/redirect\"));\n}\n\n// A relative redirect with an relative original URL will use the Host header\n// and original request scheme\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectHostHeader) {\n  expectRedirect(\n      \"www.facebook.com\",\n      80,\n      false,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getURL(), \"/redirect\");\n        EXPECT_EQ(headerEvent.headers->getHeaders().getSingleOrEmpty(\n                      HTTP_HEADER_HOST),\n                  \"www.facebook.com\");\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"http://www.facebook.com/\")),\n      getRedirectResponse(302, \"/redirect\"));\n}\n\n// Handles non-default port in the host header\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectHostHeaderNonDefaultPort) {\n  expectRedirect(\n      \"www.facebook.com\",\n      444,\n      true,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_EQ(headerEvent.headers->getURL(), \"/redirect\");\n        EXPECT_EQ(headerEvent.headers->getHeaders().getSingleOrEmpty(\n                      HTTP_HEADER_HOST),\n                  \"www.facebook.com:444\");\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com:444/\")),\n      getRedirectResponse(302, \"/redirect\"));\n}\n\n// If the original URL was relative and no Host header is present, a relative\n// redirect will fail.  This is a limitation of the filter architecture, since\n// there's no way to retrieve the original session details.\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectNoHostHeader) {\n  auto maybe = co_await co_awaitTry(run(HTTPFixedSource::makeFixedRequest(\"/\"),\n                                        getRedirectResponse(302, \"/redirect\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::InvalidRedirect);\n}\n\n// Relative redirect when the original Host header was garbage\nCO_TEST_F(HTTPRedirectHandlerTest, RelativeRedirectBadHostHeader) {\n  auto req = HTTPFixedSource::makeFixedRequest(\"/\");\n  req->msg_->getHeaders().set(HTTP_HEADER_HOST, \"abc:123456789\");\n  auto maybe =\n      co_await co_awaitTry(run(req, getRedirectResponse(302, \"/redirect\")));\n  EXPECT_TRUE(maybe.hasException());\n  EXPECT_EQ(maybe.tryGetExceptionObject<HTTPRedirectHandler::Exception>()->type,\n            HTTPRedirectHandler::Exception::Type::InvalidRedirect);\n}\n\n// The redirect response contains a body, but we discard it (will manifest\n// as stopReading, and the redirect source will delete itself, otherwise it\n// would be a leak.\nCO_TEST_F(HTTPRedirectHandlerTest, RedirectWithBody) {\n  expectRedirect(\n      \"www.facebook-redirect.com\",\n      443,\n      true,\n      [](HTTPSourceHolder request, HTTPCoroSession::RequestReservation)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await request.readHeaderEvent();\n        EXPECT_TRUE(headerEvent.eom);\n        co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n      });\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n      getRedirectResponse(\n          301, \"https://www.facebook-redirect.com/\", makeBuf(100)));\n}\n\n// stopReading before the first redirect comes back -- pass through to the\n// original response source.\nCO_TEST_F(HTTPRedirectHandlerTest, StopReading) {\n  HTTPRedirectHandler redirectHandler(mockSessionFactory_);\n  HTTPSourceReader reqReader;\n  reqReader.setSource(redirectHandler.getRequestFilter(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\"))));\n  co_await reqReader.read();\n  MockHTTPSource mockSource;\n  folly::CancellationSource cancellationSource;\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [&cancellationSource]() -> folly::coro::Task<HTTPHeaderEvent> {\n            co_await folly::coro::co_withCancellation(\n                cancellationSource.getToken(),\n                folly::coro::sleep(std::chrono::minutes(1)));\n            XLOG(FATAL) << \"Unreachable\";\n          }));\n  EXPECT_CALL(mockSource, stopReading(_))\n      .WillOnce(Invoke(\n          [&cancellationSource] { cancellationSource.requestCancellation(); }));\n  auto respSource = redirectHandler.getResponseFilter(&mockSource);\n\n  auto headerFut = co_withExecutor(co_await folly::coro::co_current_executor,\n                                   respSource->readHeaderEvent())\n                       .start()\n                       .via(co_await folly::coro::co_current_executor);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  respSource->stopReading();\n  EXPECT_THROW(co_await std::move(headerFut), folly::OperationCancelled);\n}\n\n// Stop reading while connecting.  Connect in progress is cancelled.\nCO_TEST_F(HTTPRedirectHandlerTest, StopReadingWhileConnecting) {\n  EXPECT_CALL(\n      *mockSessionFactory_,\n      getSessionWithReservation(\"www.facebook-redirect.com\", 443, true, _, _))\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [](std::string, uint16_t, bool, std::chrono::milliseconds, auto)\n              -> folly::coro::Task<HTTPSessionFactory::GetSessionResult> {\n            co_await folly::coro::sleep(std::chrono::minutes(1));\n            XLOG(FATAL) << \"Unreachable\";\n          }));\n  HTTPRedirectHandler redirectHandler(mockSessionFactory_);\n  HTTPSourceReader reqReader;\n  reqReader.setSource(redirectHandler.getRequestFilter(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\"))));\n  co_await reqReader.read();\n  auto respSource = redirectHandler.getResponseFilter(\n      getRedirectResponse(303, \"https://www.facebook-redirect.com/\"));\n\n  auto headerFut = co_withExecutor(co_await folly::coro::co_current_executor,\n                                   respSource->readHeaderEvent())\n                       .start()\n                       .via(co_await folly::coro::co_current_executor);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  respSource->stopReading();\n  EXPECT_THROW(co_await std::move(headerFut), folly::OperationCancelled);\n}\n\n// Stop reading while sending, send is cancelled.\nCO_TEST_F(HTTPRedirectHandlerTest, StopReadingWhileSending) {\n  expectRedirect(\"www.facebook-redirect.com\",\n                 443,\n                 true,\n                 [](HTTPSourceHolder, HTTPCoroSession::RequestReservation)\n                     -> folly::coro::Task<HTTPSourceHolder> {\n                   co_await folly::coro::sleep(std::chrono::minutes(1));\n                   XLOG(FATAL) << \"Unreachable\";\n                 });\n  HTTPRedirectHandler redirectHandler(mockSessionFactory_);\n  HTTPSourceReader reqReader;\n  reqReader.setSource(redirectHandler.getRequestFilter(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\"))));\n  co_await reqReader.read();\n  auto respSource = redirectHandler.getResponseFilter(\n      getRedirectResponse(303, \"https://www.facebook-redirect.com/\"));\n\n  auto headerFut = co_withExecutor(co_await folly::coro::co_current_executor,\n                                   respSource->readHeaderEvent())\n                       .start()\n                       .via(co_await folly::coro::co_current_executor);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  co_await folly::coro::co_reschedule_on_current_executor;\n  respSource->stopReading();\n  EXPECT_THROW(co_await std::move(headerFut), folly::OperationCancelled);\n}\n\nCO_TEST_F(HTTPRedirectHandlerTest, TestCallback) {\n  expectRedirect(\"www.facebook-redirect.com\",\n                 443,\n                 true,\n                 [](HTTPSourceHolder, HTTPCoroSession::RequestReservation)\n                     -> folly::coro::Task<HTTPSourceHolder> {\n                   co_return HTTPFixedSource::makeFixedResponse(200, \"success\");\n                 });\n  std::string redirectUrl;\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/\")),\n      getRedirectResponse(302, \"https://www.facebook-redirect.com/\"),\n      200,\n      [&redirectUrl](const std::string& url) { redirectUrl = url; });\n\n  EXPECT_EQ(redirectUrl, \"https://www.facebook-redirect.com/\");\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/LoggerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/Logger.h\"\n#include \"proxygen/lib/http/codec/test/TestUtils.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n\nusing namespace testing;\n\nnamespace {\nstd::chrono::steady_clock::time_point nullTime;\nusing proxygen::coro::Logger;\nusing TestLoggerCb = std::function<void(const Logger&, uint32_t weight)>;\n\nclass TestLogger : public Logger::SampledLogger {\n public:\n  TestLogger(TestLoggerCb&& logImpl, double rate)\n      : logImpl_(std::move(logImpl)), rate_(rate) {\n  }\n\n  uint32_t getLoggingWeight(bool error) override {\n    proxygen::Sampling sampling{rate_};\n    return sampling.isLucky() ? sampling.getWeight() : 0;\n  }\n\n  void log(const Logger& logger, uint32_t weight) override {\n    if (logImpl_) {\n      logImpl_(logger, weight);\n    }\n  }\n\n  TestLoggerCb logImpl_{nullptr};\n  double rate_{0.0};\n};\n\nstd::shared_ptr<Logger::SampledLogger> makeTestLogger(TestLoggerCb&& logImpl,\n                                                      double rate = 1.0) {\n  return std::make_shared<TestLogger>(std::move(logImpl), rate);\n}\n\n} // namespace\n\nnamespace proxygen::coro::test {\n\nclass LoggerTest : public testing::Test {\n public:\n  void SetUp() override {\n    EXPECT_CALL(mockSession_, getSessionID())\n        .WillRepeatedly(Return(0xfaceb00c));\n    EXPECT_CALL(mockSession_, getCodecProtocol())\n        .WillRepeatedly(Return(CodecProtocol::HTTP_3));\n    EXPECT_CALL(mockSession_, getLocalAddress())\n        .WillRepeatedly(ReturnRef(localAddr_));\n    EXPECT_CALL(mockSession_, getPeerAddress())\n        .WillRepeatedly(ReturnRef(peerAddr_));\n    tinfo_.secure = true;\n    EXPECT_CALL(mockSession_, getSetupTransportInfo())\n        .WillRepeatedly(ReturnRef(tinfo_));\n    EXPECT_CALL(mockSession_, getCurrentTransportInfo(_, _))\n        .WillRepeatedly(Invoke([](wangle::TransportInfo* tinfo, bool) {\n          tinfo->rtt = std::chrono::microseconds(123);\n          return true;\n        }));\n  }\n\n  folly::coro::Task<void> run(HTTPSource* reqSource,\n                              HTTPSource* respSource,\n                              TestLoggerCb&& logFn,\n                              double rate = 1.0) {\n    Logger logger(mockSession_.acquireKeepAlive(),\n                  makeTestLogger(std::move(logFn), rate),\n                  /*logOnDestroy=*/true);\n    HTTPSourceReader reqReader;\n    reqReader.setSource(logger.getRequestFilter(reqSource));\n    co_await reqReader.read();\n    HTTPSourceReader respReader;\n    respReader.setSource(logger.getResponseFilter(respSource));\n    co_await co_awaitTry(respReader.read());\n  }\n\n protected:\n  MockHTTPSessionContext mockSession_;\n  folly::SocketAddress localAddr_{\"0.0.0.0\", 1234};\n  folly::SocketAddress peerAddr_{\"0.0.0.0\", 5678};\n  wangle::TransportInfo tinfo_;\n};\n\nCO_TEST_F(LoggerTest, Basic) {\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(\n          URL(\"https://www.facebook.com/foo?param=value\")),\n      HTTPFixedSource::makeFixedResponse(200, \"success\"),\n      [this](const Logger& logger, uint32_t weight) {\n        EXPECT_EQ(weight, 1);\n        EXPECT_FALSE(logger.reqFilter.streamID.hasValue());\n        EXPECT_TRUE(logger.reqFilter.finalHeaderTime.hasValue());\n        EXPECT_TRUE(logger.reqFilter.firstByteTime.hasValue());\n        EXPECT_NE(logger.reqFilter.endTime, nullTime);\n        EXPECT_FALSE(logger.reqFilter.error.hasValue());\n        // header size not set with fixed req\n        EXPECT_FALSE(logger.reqFilter.headerSize.hasValue());\n        EXPECT_EQ(logger.reqFilter.httpVersion, HTTPMessage::kHTTPVersion11);\n        EXPECT_EQ(logger.reqFilter.priority.urgency, 3);\n        EXPECT_EQ(logger.reqFilter.method, \"GET\");\n        EXPECT_EQ(logger.reqFilter.host, \"www.facebook.com\");\n        EXPECT_EQ(logger.reqFilter.url, \"/foo?param=value\");\n        EXPECT_EQ(logger.getAuthority(), \"www.facebook.com\");\n        EXPECT_EQ(logger.getPath(), \"/foo\");\n        EXPECT_EQ(logger.reqFilter.statusCode, folly::none);\n        EXPECT_EQ(logger.reqFilter.bodyBytes, 0);\n\n        EXPECT_FALSE(logger.respFilter.streamID.hasValue());\n        EXPECT_TRUE(logger.respFilter.finalHeaderTime.hasValue());\n        EXPECT_TRUE(logger.respFilter.firstByteTime.hasValue());\n        EXPECT_NE(logger.respFilter.endTime, nullTime);\n        EXPECT_FALSE(logger.respFilter.error.hasValue());\n        // header size not set with fixed req\n        EXPECT_FALSE(logger.respFilter.headerSize.hasValue());\n        EXPECT_EQ(logger.respFilter.httpVersion, HTTPMessage::kHTTPVersion11);\n        EXPECT_EQ(logger.respFilter.priority.urgency, 3);\n        EXPECT_TRUE(logger.respFilter.method.empty());\n        EXPECT_TRUE(logger.respFilter.host.empty());\n        EXPECT_TRUE(logger.respFilter.url.empty());\n        EXPECT_EQ(logger.respFilter.statusCode, 200);\n        EXPECT_EQ(logger.respFilter.bodyBytes, 7);\n\n        EXPECT_EQ(logger.localAddr, localAddr_);\n        EXPECT_EQ(logger.peerAddr, peerAddr_);\n        EXPECT_EQ(logger.protocol, CodecProtocol::HTTP_3);\n        EXPECT_EQ(logger.sessionID, 0xfaceb00c);\n        EXPECT_GE(logger.timeToFirstHeaderByte().count(), 0);\n        EXPECT_GE(logger.timeToFirstByte().count(), 0);\n        EXPECT_GE(logger.timeToLastByte().count(), 0);\n        EXPECT_GE(logger.timeToLastByte().count(), 0);\n        EXPECT_TRUE(logger.transportInfo.secure);\n        EXPECT_EQ(logger.transportInfo.rtt.count(), 123);\n      });\n}\n\nCO_TEST_F(LoggerTest, StreamIDAndIngressHeaderSize) {\n  MockHTTPSource mockSource;\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [&]() -> folly::coro::Task<HTTPHeaderEvent> {\n            auto msg = std::make_unique<HTTPMessage>(getGetRequest(\"/\"));\n            HTTPHeaderSize size;\n            size.uncompressed = 100;\n            size.compressed = 50;\n            msg->setIngressHeaderSize(size);\n            co_return HTTPHeaderEvent(std::move(msg), true);\n          }));\n  EXPECT_CALL(mockSource, getStreamID()).WillOnce(Return(7));\n  co_await run(&mockSource,\n               HTTPFixedSource::makeFixedResponse(200, \"success\"),\n               [](const Logger& logger, uint32_t weight) {\n                 EXPECT_EQ(weight, 1);\n                 EXPECT_EQ(logger.getStreamID(), 7);\n                 EXPECT_EQ(logger.reqFilter.headerSize->uncompressed, 100);\n                 EXPECT_EQ(logger.reqFilter.headerSize->compressed, 50);\n               });\n}\n\nCO_TEST_F(LoggerTest, StopReading) {\n  Logger logger(mockSession_.acquireKeepAlive(),\n                makeTestLogger([](const Logger& logger, uint32_t weight) {\n                  EXPECT_EQ(weight, 1);\n                  EXPECT_EQ(logger.reqFilter.headerSize->uncompressed, 100);\n                  EXPECT_EQ(logger.reqFilter.headerSize->compressed, 50);\n                  EXPECT_NE(logger.reqFilter.endTime, nullTime);\n                }));\n  HTTPSourceHolder reqSource(\n      logger.getRequestFilter(HTTPFixedSource::makeFixedRequest(\n          \"http://www.facebook.com\", HTTPMethod::POST, makeBuf(10))));\n  auto headerEvent = co_await reqSource.readHeaderEvent();\n  EXPECT_NE(headerEvent.egressHeadersFn, nullptr);\n  HTTPHeaderSize size;\n  size.uncompressed = 100;\n  size.compressed = 50;\n  headerEvent.egressHeadersFn(size);\n}\n\nCO_TEST_F(LoggerTest, HeadersError) {\n  NiceMock<MockHTTPSource> mockSource;\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [&]() -> folly::coro::Task<HTTPHeaderEvent> {\n            co_yield folly::coro::co_error(std::runtime_error(\"err\"));\n          }));\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/foo\")),\n      &mockSource,\n      [](const Logger& logger, uint32_t weight) {\n        EXPECT_EQ(weight, 1);\n        EXPECT_TRUE(logger.respFilter.error.hasValue());\n      });\n}\n\nCO_TEST_F(LoggerTest, BodyError) {\n  NiceMock<MockHTTPSource> mockSource;\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [&]() -> folly::coro::Task<HTTPHeaderEvent> {\n            co_return HTTPHeaderEvent(makeResponse(200), false);\n          }));\n  EXPECT_CALL(mockSource, readBodyEvent(_))\n      .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n          [&](uint64_t) -> folly::coro::Task<HTTPBodyEvent> {\n            co_yield folly::coro::co_error(std::runtime_error(\"err\"));\n          }));\n  auto reqSource =\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/foo\"));\n  reqSource->msg_->getHeaders().remove(HTTP_HEADER_HOST);\n  reqSource->msg_->setURL(\"https://www.facebook.com/foo\");\n  co_await run(\n      reqSource, &mockSource, [](const Logger& logger, uint32_t weight) {\n        // Sneak in a test for getAuthority for absolute URLs\n        EXPECT_EQ(logger.getAuthority(), \"www.facebook.com\");\n        EXPECT_EQ(logger.respFilter.statusCode, 200);\n        EXPECT_TRUE(logger.respFilter.error.hasValue());\n        EXPECT_EQ(weight, 1);\n      });\n}\n\nCO_TEST_F(LoggerTest, DefaultLogger) {\n  int oldLevel = FLAGS_v;\n  FLAGS_v = 2;\n  gflags::SetCommandLineOption(\"minloglevel\", \"0\");\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(URL(\"https://www.facebook.com/foo\")),\n      HTTPFixedSource::makeFixedResponse(200, \"success\"),\n      nullptr);\n  FLAGS_v = oldLevel;\n  gflags::SetCommandLineOption(\"minloglevel\", \"0\");\n}\n\nCO_TEST_F(LoggerTest, Weight) {\n  // Set to 1%, weight = 100\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(\n          URL(\"https://www.facebook.com/foo?param=value\")),\n      HTTPFixedSource::makeFixedResponse(200, \"success\"),\n      [](const Logger&, uint32_t weight) { EXPECT_EQ(weight, 100); },\n      0.01);\n  // Set to 50%, weight = 2\n  co_await run(\n      HTTPFixedSource::makeFixedRequest(\n          URL(\"https://www.facebook.com/foo?param=value\")),\n      HTTPFixedSource::makeFixedResponse(200, \"success\"),\n      [](const Logger&, uint32_t weight) { EXPECT_EQ(weight, 2); },\n      0.5);\n  // Error, weight = 1\n  co_await run(HTTPFixedSource::makeFixedRequest(\n                   URL(\"https://www.facebook.com/foo?param=value\")),\n               new HTTPErrorSource(HTTPError(HTTPErrorCode::INTERNAL_ERROR)),\n               [](const Logger&, uint32_t weight) { EXPECT_EQ(weight, 1); });\n}\n// error reading headers\n// error reading body\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/MutateFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceReader.h>\n#include <proxygen/lib/http/coro/HTTPStreamSource.h>\n#include <proxygen/lib/http/coro/filters/MutateFilter.h>\n#include <proxygen/lib/http/coro/util/test/TestHelpers.h>\n\nusing namespace testing;\n\nnamespace proxygen::coro::test {\n\nclass MutateFilterTest : public testing::Test {\n public:\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nCO_TEST_F_X(MutateFilterTest, SimpleTest) {\n  auto msg = makeResponse(200);\n  // create response source\n  HTTPStreamSource respSource(&evb_);\n  respSource.headers(std::move(msg), /*eom=*/false);\n  respSource.body(\n      folly::IOBuf::copyBuffer(\"hello\"), /*padding=*/0, /*eom=*/false);\n\n  // create visitor\n  MutateFilter::HeaderMutateFn headerHook = [](HTTPHeaderEvent& headerEvent) {\n    // add two random header fields\n    auto& headers = headerEvent.headers->getHeaders();\n    headers.add(\"x-header-a\", \"x-value-a\");\n    headers.add(\"x-header-b\", \"x-value-b\");\n  };\n  MutateFilter::BodyMutateFn bodyHook = [](HTTPBodyEvent& bodyEvent) {\n    // replace \"hello\" in body with \"world\"\n    EXPECT_EQ(bodyEvent.eventType, HTTPBodyEvent::BODY);\n    CHECK(!bodyEvent.event.body.empty());\n    auto bodyStr = bodyEvent.event.body.move()->to<std::string>();\n    EXPECT_EQ(bodyStr, \"hello\");\n    bodyEvent.event.body.append(folly::IOBuf::copyBuffer(\"world\"));\n  };\n\n  auto* mutateSource =\n      new MutateFilter(&respSource, std::move(headerHook), std::move(bodyHook));\n  mutateSource->setHeapAllocated();\n\n  HTTPSourceReader reader(mutateSource);\n  reader.onHeaders(\n      [](std::unique_ptr<HTTPMessage> msg, bool /*final*/, bool eom) {\n        const auto& headers = msg->getHeaders();\n        EXPECT_TRUE(headers.exists(\"x-header-a\") &&\n                    headers.exists(\"x-header-b\"));\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-a\"), \"x-value-a\");\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-b\"), \"x-value-b\");\n        EXPECT_FALSE(eom);\n        return true;\n      });\n\n  reader.onBody([](BufQueue body, bool /*eom*/) {\n    CHECK(!body.empty());\n    auto bodyStr = body.move()->to<std::string>();\n    EXPECT_EQ(bodyStr, \"world\");\n    return true;\n  });\n\n  // read response\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(MutateFilterTest, PassThruOnError) {\n  auto* respSource =\n      new HTTPErrorSource(HTTPError(HTTPErrorCode::CANCEL, \"cancelled\"));\n\n  // create visitor\n  MutateFilter::HeaderMutateFn headerHook =\n      [](HTTPHeaderEvent& /*headerEvent*/) {\n        XLOG(FATAL) << \"header hook called\";\n      };\n  MutateFilter::BodyMutateFn bodyHook = [](HTTPBodyEvent& /*bodyEvent*/) {\n    XLOG(FATAL) << \"body hook called\";\n  };\n\n  auto* mutateSource =\n      new MutateFilter(respSource, std::move(headerHook), std::move(bodyHook));\n  mutateSource->setHeapAllocated();\n\n  HTTPSourceReader reader(mutateSource);\n  // read response\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_TRUE(res.hasException());\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/RateLimitFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/coro/filters/RateLimitFilter.h>\n\n#include <folly/coro/Collect.h>\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <proxygen/lib/http/coro/test/Mocks.h>\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\nusing namespace std::literals::chrono_literals;\n\nnamespace {\nconstexpr auto kLargeReadAmount = 1024 * 1024 * 200;\n}\n\nnamespace proxygen::coro::test {\nclass RateLimitFilterTest : public testing::Test {\n public:\n  void SetUp() override {\n    testFilter_ = new RateLimitFilter(&mockSource_);\n    testFilter_->setHeapAllocated();\n  }\n\n protected:\n  void verifyReadSize(uint64_t readSizeExpected, bool eom = false) {\n    auto buf = folly::IOBuf::create(readSizeExpected);\n    buf->append(readSizeExpected);\n    HTTPBodyEvent body(std::move(buf), eom);\n    EXPECT_CALL(mockSource_, readBodyEvent(Ge(readSizeExpected)))\n        .Times(1)\n        .WillOnce(folly::coro::gmock_helpers::CoReturnByMove(std::move(body)));\n  }\n\n  // Helper function to do a read on an eventbase, verify size on the underlying\n  // source, and also return the duration it takes to read.\n  std::chrono::seconds timedRead(uint64_t readAmount,\n                                 uint64_t verifyAmount,\n                                 bool eom = false) {\n    verifyReadSize(verifyAmount, eom);\n    auto currentTime = std::chrono::steady_clock::now();\n    folly::ScopedEventBaseThread sEvb;\n    auto task = co_withExecutor(sEvb.getEventBase(),\n                                testFilter_->readBodyEvent(readAmount));\n    folly::coro::blockingWait(std::move(task));\n    return std::chrono::duration_cast<std::chrono::seconds>(\n        std::chrono::steady_clock::now() - currentTime);\n  }\n\n  std::chrono::seconds timedRead(uint64_t readAmount) {\n    return timedRead(readAmount, readAmount);\n  }\n\n  folly::coro::Task<void> errorOutFilter() {\n    testFilter_->setLimit(1000);\n    EXPECT_CALL(mockSource_, readBodyEvent(1))\n        .Times(1)\n        .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n            [&](uint64_t) -> folly::coro::Task<HTTPBodyEvent> {\n              co_yield folly::coro::co_error(HTTPError(\n                  HTTPErrorCode::READ_TIMEOUT,\n                  \"Make sure no memory leak of a heap source filter\"));\n            }));\n    auto error = co_await co_awaitTry(testFilter_->readBodyEvent(1));\n    EXPECT_TRUE(error.hasException());\n  }\n\n  // This is heap allocated without a smart pointer by design, to verify\n  // lifetime in base class takes care of deallocating this filter.\n  RateLimitFilter* testFilter_;\n  MockHTTPSource mockSource_;\n};\n\nCO_TEST_F(RateLimitFilterTest, NoLimit) {\n  EXPECT_LT(timedRead(kLargeReadAmount), 1s);\n  co_await errorOutFilter();\n}\n\nTEST_F(RateLimitFilterTest, FirstReadAlwaysOK) {\n  testFilter_->setLimit(1000);\n  EXPECT_LT(timedRead(kLargeReadAmount), 1s);\n  testFilter_->stopReading();\n}\n\nTEST_F(RateLimitFilterTest, SecondReadBelowLimit) {\n  testFilter_->setLimit(70000 * 1000 * 8);\n  EXPECT_LT(timedRead(1), 1s);\n  EXPECT_LT(timedRead(1), 1s);\n  testFilter_->stopReading();\n}\n\nTEST_F(RateLimitFilterTest, SecondReadBeyondLimit) {\n  // Rate: 100 * 1000 per second\n  testFilter_->setLimit(100 * 1000 * 8);\n\n  // First read is always fine. Read 200 * 1000 bytes from source\n  EXPECT_LT(timedRead(200 * 1000), 1s);\n\n  // Read again, even 1 byte needs to wait 1s\n  auto secondsElapsed = timedRead(1);\n  EXPECT_GE(secondsElapsed, 1s);\n  testFilter_->stopReading();\n}\n\nTEST_F(RateLimitFilterTest, WontSleepTooLong) {\n  // Replace with a new filter with a low sleep limit.\n  delete testFilter_;\n  testFilter_ = new RateLimitFilter(&mockSource_, 2s, 4096);\n  testFilter_->setHeapAllocated();\n\n  // Rate: 100 * 1000 per second\n  testFilter_->setLimit(100 * 1000 * 8);\n\n  // First read is always fine. Read 2100 * 1000 bytes from source\n  EXPECT_LT(timedRead(2100 * 1000), 1s);\n\n  // Read again, it sleeps but up to a limit\n  EXPECT_LT(timedRead(1), 3s);\n  testFilter_->stopReading();\n}\n\nTEST_F(RateLimitFilterTest, SetLimitClearCounter) {\n  // Rate: 100 * 1000 per second\n  testFilter_->setLimit(100 * 1000 * 8);\n\n  // First read is always fine. Read 1100 * 1000 bytes from source\n  EXPECT_LT(timedRead(1100 * 1000), 1s);\n\n  // Set the limit again, we will start to count again\n  testFilter_->setLimit(100 * 1000 * 8);\n\n  // Then the next read will also go through for free\n  EXPECT_LT(timedRead(1100 * 1000), 1s);\n  testFilter_->stopReading();\n}\n\nCO_TEST_F(RateLimitFilterTest, CancelRead) {\n  // Rate: 100 * 1000 per second\n  testFilter_->setLimit(100 * 1000 * 8);\n\n  // First read is always fine. Read an amount that'd block later.\n  EXPECT_LT(timedRead(500 * 1000), 1s);\n\n  // Read again, we'd sleep. But let's cancel it before the sleep time is up\n  folly::CancellationSource cancelSource;\n  EXPECT_CALL(mockSource_, stopReading(_)).Times(1);\n  co_await folly::coro::collectAll(\n      [&]() -> folly::coro::Task<void> {\n        auto result = co_await co_awaitTry(folly::coro::co_withCancellation(\n            cancelSource.getToken(), testFilter_->readBodyEvent(1)));\n        EXPECT_TRUE(result.hasException());\n        EXPECT_EQ(HTTPErrorCode::CORO_CANCELLED, getHTTPError(result).code);\n      }(),\n      [&]() -> folly::coro::Task<void> {\n        co_await folly::coro::co_reschedule_on_current_executor;\n        cancelSource.requestCancellation();\n      }());\n}\n\nTEST_F(RateLimitFilterTest, MinReadAmount) {\n  testFilter_->setLimit(4096 * 8); // 4096 bytes per second\n  // First read goes through, takes 4096 from limit\n  EXPECT_LT(timedRead(4096), 1s);\n  // Without any delay, another 4096 read will sleep instead of returning a\n  // small amount of bytes.\n  EXPECT_GE(timedRead(4096), 1s);\n\n  // reset limit:\n  testFilter_->setLimit(4096 * 8);\n  EXPECT_LT(timedRead(4096), 1s);\n  // This time a smaller than chunk size read\n  EXPECT_LT(timedRead(400), 2s);\n\n  // reset limit:\n  testFilter_->setLimit(4096 * 8);\n  EXPECT_LT(timedRead(4096), 1s);\n  // This time a greater than chunk size read.\n  EXPECT_LT(timedRead(4096 * 2, 4096), 3s);\n\n  testFilter_->stopReading();\n}\n\nTEST_F(RateLimitFilterTest, ReadEOM) {\n  testFilter_->setLimit(100 * 1000 * 8);\n  // Underlying source gives a EOM body event:\n  EXPECT_CALL(mockSource_, readBodyEvent(2000))\n      .Times(1)\n      .WillOnce(folly::coro::gmock_helpers::CoReturnByMove(\n          HTTPBodyEvent(nullptr, true)));\n  folly::ScopedEventBaseThread sEvb;\n  auto task = co_awaitTry(\n      co_withExecutor(sEvb.getEventBase(), testFilter_->readBodyEvent(2000)));\n  auto body = folly::coro::blockingWait(std::move(task));\n  EXPECT_TRUE(body->eom);\n  // EOM body takes care of deleting filter. No need to error it out.\n}\n\nTEST_F(RateLimitFilterTest, SourceHasLessData) {\n  testFilter_->setLimit(100 * 1000 * 8);\n  // First read, source has no data returned:\n  EXPECT_LT(timedRead(1000, 0), 1s);\n  // Then next read won't have any delay:\n  EXPECT_LT(timedRead(1000, 900, true), 1s);\n  // The EOM read in last event also kills the filter\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/RequestContextFilterFactoryTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/RequestContextFilterFactory.h\"\n#include \"folly/coro/GtestHelpers.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/filters/VisitorFilter.h\"\n\nnamespace proxygen::coro::test {\n\nCO_TEST(RequestContextFilterFactoryTest, RequestContextInjection) {\n  const folly::RequestToken kRequestToken{\"proxygen::coro::test::token\"};\n  using RequestData = folly::ImmutableRequestData<int>;\n  const auto setRequestContextData = [&](auto&&) {\n    auto* context = folly::RequestContext::try_get();\n    EXPECT_NE(context, nullptr);\n    context->setContextData(kRequestToken, std::make_unique<RequestData>(42));\n  };\n  const auto ensureRequestContextData = [&](auto&&) {\n    auto* context = folly::RequestContext::try_get();\n    EXPECT_NE(context, nullptr);\n    auto* data = dynamic_cast<const RequestData*>(\n        context->getContextData(kRequestToken));\n    EXPECT_TRUE(data && data->value() == 42);\n  };\n\n  auto* requestSource = HTTPFixedSource::makeFixedRequest(\n      \"https://www.facebook.com\",\n      HTTPMethod::GET,\n      folly::IOBuf::wrapBuffer(folly::StringPiece{\"foo\"}));\n  auto* responseSource = HTTPFixedSource::makeFixedResponse(200, \"bar\");\n\n  auto requestVisitor = std::make_unique<VisitorFilter>(\n      requestSource, setRequestContextData, ensureRequestContextData);\n  auto responseVisitor = std::make_unique<VisitorFilter>(\n      responseSource, ensureRequestContextData, ensureRequestContextData);\n\n  auto [requestFilter, responseFilter] =\n      RequestContextFilterFactory{}.makeFilters();\n  CO_ASSERT_NE(requestFilter, nullptr);\n  CO_ASSERT_NE(responseFilter, nullptr);\n  requestFilter->setSource(requestVisitor.get());\n  responseFilter->setSource(responseVisitor.get());\n\n  const auto requestHeaderEvent =\n      co_await folly::coro::co_awaitTry(requestFilter->readHeaderEvent());\n  CO_ASSERT_FALSE(requestHeaderEvent.hasException());\n  const auto requestBodyEvent =\n      co_await folly::coro::co_awaitTry(requestFilter->readBodyEvent());\n  CO_ASSERT_FALSE(requestBodyEvent.hasException());\n  const auto responseHeaderEvent =\n      co_await folly::coro::co_awaitTry(responseFilter->readHeaderEvent());\n  CO_ASSERT_FALSE(responseHeaderEvent.hasException());\n  const auto responseBodyEvent =\n      co_await folly::coro::co_awaitTry(responseFilter->readBodyEvent());\n  CO_ASSERT_FALSE(responseBodyEvent.hasException());\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/Status1xxFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/filters/Status1xxFilter.h\"\n\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n#include <proxygen/lib/http/coro/test/Mocks.h>\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nnamespace proxygen::coro::test {\n\nCO_TEST(Status1xxFilterTest, Ignore1xxResponses) {\n  InSequence seq;\n\n  MockHTTPSource mockSource;\n\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .Times(3)\n      .WillRepeatedly(folly::coro::gmock_helpers::CoInvoke(\n          []() -> folly::coro::Task<HTTPHeaderEvent> {\n            auto headers1xx = std::make_unique<HTTPMessage>();\n            headers1xx->setStatusCode(100);\n            headers1xx->getHeaders().add(\"testName\", \"testValue\");\n            co_return HTTPHeaderEvent(std::move(headers1xx), false);\n          }));\n  EXPECT_CALL(mockSource, readHeaderEvent())\n      .Times(1)\n      .WillOnce([]() -> folly::coro::Task<HTTPHeaderEvent> {\n        auto headers200 = std::make_unique<HTTPMessage>();\n        headers200->setStatusCode(200);\n        headers200->getHeaders().add(\"co\", \"ro\");\n        co_return HTTPHeaderEvent(std::move(headers200), true);\n      });\n\n  // Consume first response directly from source\n  auto headerEvent = co_await mockSource.readHeaderEvent();\n  EXPECT_TRUE(headerEvent.headers->is1xxResponse());\n\n  // Consume remaining responses through filter\n  Status1xxFilter filter1xx;\n  filter1xx.setSource(&mockSource);\n\n  // 1xx responses should be skipped.\n  headerEvent = co_await filter1xx.readHeaderEvent();\n  EXPECT_FALSE(headerEvent.headers->is1xxResponse());\n  EXPECT_EQ(headerEvent.headers->getStatusCode(), 200);\n  EXPECT_TRUE(headerEvent.headers->getHeaders().exists(\"co\"));\n}\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/TransformFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceReader.h>\n#include <proxygen/lib/http/coro/HTTPStreamSource.h>\n#include <proxygen/lib/http/coro/filters/TransformFilter.h>\n#include <proxygen/lib/http/coro/util/test/TestHelpers.h>\n\nusing namespace testing;\n\nnamespace proxygen::coro::test {\n\nclass TransformFilterTest : public testing::Test {\n public:\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nCO_TEST_F_X(TransformFilterTest, BasicTest) {\n  auto msg = makeResponse(200);\n  // create response source\n  HTTPStreamSource respSource(&evb_);\n  respSource.headers(std::move(msg), /*eom=*/false);\n  respSource.body(\n      folly::IOBuf::copyBuffer(\"hello\"), /*padding=*/0, /*eom=*/false);\n\n  // header transform filter adds two header fields into HTTPHeaders\n  TransformFilter::HeaderTransformFn headerHook =\n      [](folly::Try<HTTPHeaderEvent>&& headerEvent) {\n        CHECK(!headerEvent.hasException());\n        // add two random header fields\n        auto& headers = headerEvent->headers->getHeaders();\n        headers.add(\"x-header-a\", \"x-value-a\");\n        headers.add(\"x-header-b\", \"x-value-b\");\n        return std::move(headerEvent);\n      };\n\n  // body transform filter (\"hello\", eom=false) -> (\"world\", eom=true)\n  TransformFilter::BodyTransformFn bodyHook =\n      [](folly::Try<HTTPBodyEvent>&& bodyEvent) {\n        CHECK(!bodyEvent.hasException());\n        // replace \"hello\" in body with \"world\"\n        EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n        CHECK(!bodyEvent->event.body.empty());\n        auto bodyStr = bodyEvent->event.body.move()->to<std::string>();\n        EXPECT_EQ(bodyStr, \"hello\");\n        bodyEvent->event.body.append(folly::IOBuf::copyBuffer(\"world\"));\n        // inject eom to terminate HTTPSourceReader\n        bodyEvent->eom = true;\n        return std::move(bodyEvent);\n      };\n\n  auto* transformSource = new TransformFilter(\n      &respSource, std::move(headerHook), std::move(bodyHook));\n  transformSource->setHeapAllocated();\n\n  HTTPSourceReader reader(transformSource);\n  reader.onHeaders(\n      [](std::unique_ptr<HTTPMessage> msg, bool /*final*/, bool eom) {\n        const auto& headers = msg->getHeaders();\n        EXPECT_TRUE(headers.exists(\"x-header-a\") &&\n                    headers.exists(\"x-header-b\"));\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-a\"), \"x-value-a\");\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-b\"), \"x-value-b\");\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      });\n\n  reader.onBody([](BufQueue body, bool /*eom*/) {\n    CHECK(!body.empty());\n    auto bodyStr = body.move()->to<std::string>();\n    EXPECT_EQ(bodyStr, \"world\");\n    return HTTPSourceReader::Continue;\n  });\n\n  // read response\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(TransformFilterTest, PassthruFilter) {\n  auto msg = makeResponse(200);\n  constexpr std::string_view kBodyStr = \"hello!\";\n  // save ptr to verify same underlying msg\n  auto* pMsg = msg.get();\n  auto body = folly::IOBuf::wrapBuffer(kBodyStr.begin(), kBodyStr.size());\n\n  // create response source, enqueue events\n  HTTPStreamSource respSource(&evb_);\n  respSource.headers(std::move(msg), /*eom=*/false);\n  respSource.body(std::move(body), /*padding=*/0, /*eom=*/true);\n\n  // create passthru transform filter\n  auto* transformSource = new TransformFilter(&respSource, nullptr, nullptr);\n  transformSource->setHeapAllocated();\n\n  HTTPSourceReader reader(transformSource);\n  // verify ptrs are equal\n  reader.onHeaders(\n      [=](std::unique_ptr<HTTPMessage> msg, bool /*final*/, bool /*eom*/) {\n        EXPECT_EQ(msg.get(), pMsg);\n        return HTTPSourceReader::Continue;\n      });\n  reader.onBody([&kBodyStr](BufQueue body, bool /*eom*/) {\n    EXPECT_EQ((void*)CHECK_NOTNULL(body.front())->data(), kBodyStr.begin());\n    return HTTPSourceReader::Continue;\n  });\n\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(TransformFilterTest, InvokeOnError) {\n  auto* respSource =\n      new HTTPErrorSource(HTTPError(HTTPErrorCode::CANCEL, \"cancelled\"));\n  bool headerCbInvoked = false;\n  TransformFilter::HeaderTransformFn headerHook =\n      [&headerCbInvoked](folly::Try<HTTPHeaderEvent>&& headerEvent) {\n        EXPECT_TRUE(headerEvent.hasException());\n        headerCbInvoked = true;\n        return std::move(headerEvent);\n      };\n  TransformFilter::BodyTransformFn bodyHook =\n      [](folly::Try<HTTPBodyEvent>&& bodyEvent) -> folly::Try<HTTPBodyEvent> {\n    LOG(FATAL) << \"unreachable\";\n  };\n\n  auto* transformSource = new TransformFilter(\n      respSource, std::move(headerHook), std::move(bodyHook));\n  transformSource->setHeapAllocated();\n\n  HTTPSourceReader reader(transformSource);\n  // read response\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_TRUE(res.hasException());\n  EXPECT_TRUE(headerCbInvoked);\n}\n\nCO_TEST_F_X(TransformFilterTest, TransformBodyToError) {\n  auto respSource = HTTPFixedSource::makeFixedResponse(200, \"~body~\");\n  // no-op header event\n  TransformFilter::HeaderTransformFn headerHook =\n      [](folly::Try<HTTPHeaderEvent>&& headerEvent) {\n        EXPECT_FALSE(headerEvent.hasException());\n        return std::move(headerEvent);\n      };\n  // transform body event into error arbitrarily just because\n  TransformFilter::BodyTransformFn bodyHook =\n      [](folly::Try<HTTPBodyEvent>&& bodyEvent) {\n        EXPECT_FALSE(bodyEvent.hasException());\n        return folly::Try<HTTPBodyEvent>(\n            HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"~~error~~\"));\n      };\n\n  auto* transformSource = new TransformFilter(\n      respSource, std::move(headerHook), std::move(bodyHook));\n  transformSource->setHeapAllocated();\n\n  HTTPSourceReader reader(transformSource);\n\n  reader.onBody([](BufQueue body, bool /*eom*/) -> bool {\n    LOG(FATAL) << \"shouldn't happen\";\n  });\n\n  // read response\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_TRUE(res.hasException());\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/filters/test/VisitorFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceReader.h>\n#include <proxygen/lib/http/coro/HTTPStreamSource.h>\n#include <proxygen/lib/http/coro/filters/VisitorFilter.h>\n#include <proxygen/lib/http/coro/util/test/TestHelpers.h>\n\nusing namespace testing;\n\nnamespace proxygen::coro::test {\n\nclass VisitorFilterTest : public testing::Test {\n public:\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nCO_TEST_F_X(VisitorFilterTest, SimpleTest) {\n  auto msg = makeResponse(200);\n  msg->getHeaders().set(\"x-header-a\", \"x-value-a\");\n  msg->getHeaders().set(\"x-header-b\", \"x-value-b\");\n\n  // create response source\n  auto* respSource = new HTTPStreamSource(&evb_);\n  respSource->setHeapAllocated();\n  respSource->headers(std::move(msg), /*eom=*/false);\n  respSource->body(\n      folly::IOBuf::copyBuffer(\"hello\"), /*padding=*/0, /*eom=*/false);\n\n  // create visitor\n  size_t numHeaderCallbacks{0}, numBodyCallbacks{0};\n  VisitorFilter::HeaderHookFn headerHook =\n      [&numHeaderCallbacks](const folly::Try<HTTPHeaderEvent>& headerEvent) {\n        CHECK(!headerEvent.hasException());\n        // validate message is the same\n        numHeaderCallbacks++;\n        const auto& headers = headerEvent->headers->getHeaders();\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-a\"), \"x-value-a\");\n        EXPECT_EQ(headers.getSingleOrEmpty(\"x-header-b\"), \"x-value-b\");\n      };\n  VisitorFilter::BodyHookFn bodyHook =\n      [&numBodyCallbacks](const folly::Try<HTTPBodyEvent>& bodyEvent) {\n        CHECK(!bodyEvent.hasException());\n        if (bodyEvent->eventType == HTTPBodyEvent::SUSPEND) {\n          return;\n        }\n        // validate we receive two sepearate body events\n        EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n        auto bodyStr = bodyEvent->event.body.clone()->to<std::string>();\n        EXPECT_EQ(bodyStr, numBodyCallbacks == 0 ? \"hello\" : \"world!\");\n        numBodyCallbacks++;\n      };\n\n  auto* visitorSource =\n      new VisitorFilter(respSource, std::move(headerHook), std::move(bodyHook));\n  visitorSource->setHeapAllocated();\n\n  // queue the second body event asynchronously because why not.\n  evb_.runAfterDelay(\n      [=]() {\n        respSource->body(\n            folly::IOBuf::copyBuffer(\"world!\"), /*padding=*/0, /*eom=*/true);\n      },\n      50);\n\n  HTTPSourceReader reader(visitorSource);\n  co_await reader.read();\n  EXPECT_EQ(numHeaderCallbacks, 1);\n  EXPECT_EQ(numBodyCallbacks, 2);\n}\n\nCO_TEST_F_X(VisitorFilterTest, VisitorInvokedOnError) {\n  auto msg = makeResponse(200);\n\n  // create error source\n  auto* respSource = new HTTPErrorSource(\n      HTTPError(HTTPErrorCode::CANCEL, \"cancelled\"), /*heapAllocated=*/true);\n\n  // create visitor that verifies headerEvent yields error and body hook is\n  // never invoked\n  bool expectHeaderHook{false};\n  VisitorFilter::HeaderHookFn headerHook =\n      [&expectHeaderHook](const folly::Try<HTTPHeaderEvent>& headerEvent) {\n        expectHeaderHook = true;\n        CHECK(headerEvent.hasException());\n      };\n  VisitorFilter::BodyHookFn bodyHook =\n      [](const folly::Try<HTTPBodyEvent>& /*bodyEvent*/) {\n        XLOG(FATAL) << \"body hook called\";\n      };\n\n  // create visitor source\n  auto* visitorSource =\n      new VisitorFilter(respSource, std::move(headerHook), std::move(bodyHook));\n  visitorSource->setHeapAllocated();\n\n  HTTPSourceReader reader(visitorSource);\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_TRUE(res.hasException());\n  EXPECT_TRUE(expectHeaderHook);\n}\n\nCO_TEST_F_X(VisitorFilterTest, PassThruObserver) {\n  auto msg = makeResponse(200);\n\n  // create response source\n  auto* respSource = new HTTPStreamSource(&evb_);\n  respSource->setHeapAllocated();\n  respSource->headers(std::move(msg), /*eom=*/false);\n  respSource->body(\n      folly::IOBuf::copyBuffer(\"hello\"), /*padding=*/0, /*eom=*/true);\n\n  // create dummy passthru observer filter, verify everything works as expected.\n  auto* visitorSource = new VisitorFilter(respSource, nullptr, nullptr);\n  visitorSource->setHeapAllocated();\n\n  // sanity check headers and body\n  HTTPSourceReader reader(visitorSource);\n  reader.onHeaders([](std::unique_ptr<HTTPMessage> msg, bool final, bool eom) {\n    CHECK(final && !eom);\n    EXPECT_EQ(msg->getStatusCode(), 200);\n    return true;\n  });\n  reader.onBody([](BufQueue body, bool eom) {\n    CHECK(!body.empty() && eom);\n    EXPECT_EQ(body.move()->to<std::string>(), \"hello\");\n    return true;\n  });\n  auto res = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(res.hasException());\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_server_coro_acceptor\n  SRCS\n    HTTPCoroAcceptor.cpp\n  DEPS\n    proxygen_coro\n    proxygen_http_session_http_session_codec_factory\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_common_events_folly_eventbase\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_codec_compress_header_codec\n    proxygen_services_acceptor_configuration\n    wangle::wangle_acceptor\n)\n\nproxygen_add_library(proxygen_http_coro_server_coro_httpserver\n  SRCS\n    HTTPServer.cpp\n  DEPS\n    proxygen_http_coro_filter_factory_handler\n    proxygen_utils_time_util\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_congestion_control_server_congestion_controller_factory\n    mvfst::mvfst_logging_file_qlogger\n    Folly::folly_fibers_batch_semaphore\n    Folly::folly_io_async_event_base_manager\n    Folly::folly_system_hardware_concurrency\n  EXPORTED_DEPS\n    proxygen_http_coro_filters_ServerFilterFactory\n    proxygen_http_coro_server_coro_acceptor\n    proxygen_sampling\n    mvfst::mvfst_server\n    mvfst::mvfst_server_quic_handshake_socket_holder\n    wangle::wangle_acceptor\n    Folly::folly_container_f14_hash\n    Folly::folly_executors_io_thread_pool_executor\n    Folly::folly_io_async_async_signal_handler\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_server_scope_coro_httpserver\n  EXPORTED_DEPS\n    proxygen_http_coro_server_coro_httpserver\n)\n\nadd_subdirectory(handlers)\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/HTTPCoroAcceptor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/server/HTTPCoroAcceptor.h\"\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include <folly/logging/xlog.h>\n\n#include <proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n\nnamespace proxygen::coro {\n\nHTTPCoroAcceptor::HTTPCoroAcceptor(\n    std::shared_ptr<const AcceptorConfiguration> accConfig,\n    std::shared_ptr<HTTPHandler> handler,\n    NewConnectionFilter* newConnFilter,\n    std::shared_ptr<HTTPCodecFactory> codecFactory,\n    std::shared_ptr<wangle::FizzLoggingCallback> loggingCallback)\n    : Acceptor(accConfig),\n      factory_(std::move(accConfig), std::move(codecFactory)),\n      handler_(std::move(handler)),\n      newConnectionFilter_(newConnFilter),\n      loggingCallback_(std::move(loggingCallback)) {\n  if (loggingCallback_ != nullptr) {\n    getFizzPeeker()->options().setLoggingCallback(loggingCallback_.get());\n  }\n}\n\nHTTPCoroDownstreamSessionFactory::HTTPCoroDownstreamSessionFactory(\n    std::shared_ptr<const AcceptorConfiguration> accConfig,\n    std::shared_ptr<HTTPCodecFactory> codecFactory,\n    Config config)\n    : accConfig_(std::move(accConfig)),\n      codecFactory_(std::move(codecFactory)),\n      config_(config) {\n  if (!codecFactory_) {\n    codecFactory_ =\n        std::make_shared<HTTPDefaultSessionCodecFactory>(accConfig_);\n  }\n}\n\nvoid HTTPCoroDownstreamSessionFactory::applySettingsToCodec(HTTPCodec& codec) {\n  auto settings = codec.getEgressSettings();\n  if (!settings) {\n    return;\n  }\n  if (accConfig_->maxConcurrentIncomingStreams) {\n    settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS,\n                         accConfig_->maxConcurrentIncomingStreams);\n  }\n  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                       std::max(accConfig_->initialReceiveWindow,\n                                accConfig_->receiveStreamWindowSize));\n  for (auto& setting : accConfig_->egressSettings) {\n    settings->setSetting(setting.id, setting.value);\n  }\n  if (config_.headerCodecStats) {\n    codec.setHeaderCodecStats(config_.headerCodecStats);\n  }\n}\n\nvoid HTTPCoroDownstreamSessionFactory::applySettingsToSession(\n    HTTPCoroSession& session) {\n  // PrioritiesEnabled\n\n  session.setConnectionFlowControl(accConfig_->receiveSessionWindowSize);\n  if (config_.sessionLifecycleCb) {\n    session.addLifecycleObserver(config_.sessionLifecycleCb);\n  }\n  session.setConnectionReadTimeout(accConfig_->connectionIdleTimeout);\n  session.setStreamReadTimeout(accConfig_->transactionIdleTimeout);\n  session.setWriteTimeout(accConfig_->transactionIdleTimeout);\n  // accConfig_->writeBufferLimit\n  //\n  if (config_.sessionStats) {\n    session.setSessionStats(config_.sessionStats);\n  }\n}\n\nHTTPCoroSession* HTTPCoroDownstreamSessionFactory::makeUniplexSession(\n    folly::AsyncTransport::UniquePtr transport,\n    const folly::SocketAddress* peerAddress,\n    const std::string& nextProtocol,\n    wangle::SecureTransportType transportType,\n    const wangle::TransportInfo& tinfo,\n    std::shared_ptr<HTTPHandler> handler) {\n  // we assume if security protocol isn't empty, then it's TLS\n  bool isTLS = !transport->getSecurityProtocol().empty();\n  std::string attemptNextProtocol =\n      isTLS ? nextProtocol : accConfig_->plaintextProtocol;\n  auto codec = codecFactory_->getCodec(\n      attemptNextProtocol, TransportDirection::DOWNSTREAM, isTLS);\n  if (!codec) {\n    XLOG(DBG2) << \"codecFactory_ failed to provide codec for proto=\"\n               << attemptNextProtocol;\n    return nullptr;\n  }\n\n  applySettingsToCodec(*codec);\n  XLOG(DBG4) << \"Created new \" << attemptNextProtocol << \" session for peer \"\n             << *peerAddress;\n  auto eventBase = transport->getEventBase();\n  auto coroTransport =\n      std::make_unique<folly::coro::Transport>(eventBase, std::move(transport));\n  HTTPCoroSession* session = HTTPCoroSession::makeDownstreamCoroSession(\n      std::move(coroTransport), std::move(handler), std::move(codec), tinfo);\n  applySettingsToSession(*session);\n  // readBufferLimit is only read for http/1.1 sessions\n  session->setReadBufferLimit(accConfig_->receiveStreamWindowSize);\n  return session;\n}\n\nHTTPCoroSession* HTTPCoroDownstreamSessionFactory::makeQuicSession(\n    std::shared_ptr<quic::QuicSocket> quicSocket,\n    wangle::TransportInfo tinfo,\n    std::shared_ptr<HTTPHandler> handler,\n    bool /*strictValidation*/) {\n  auto codec =\n      hq::HQMultiCodec::Factory::getCodec(TransportDirection::DOWNSTREAM,\n                                          codecFactory_->useStrictValidation(),\n                                          accConfig_->headerIndexingStrategy);\n  applySettingsToCodec(*codec);\n  XLOG(DBG4) << \"Created new \" << *tinfo.appProtocol << \" session for peer \"\n             << quicSocket->getPeerAddress();\n  HTTPCoroSession* session =\n      HTTPCoroSession::makeDownstreamCoroSession(std::move(quicSocket),\n                                                 std::move(handler),\n                                                 std::move(codec),\n                                                 std::move(tinfo));\n  applySettingsToSession(*session);\n  session->setReadBufferLimit(accConfig_->receiveStreamWindowSize);\n  return session;\n}\n\nvoid HTTPCoroAcceptor::onNewConnection(\n    folly::AsyncTransport::UniquePtr transport,\n    const folly::SocketAddress* peerAddress,\n    const std::string& nextProtocol,\n    wangle::SecureTransportType transportType,\n    const wangle::TransportInfo& tinfo) {\n  if (newConnectionFilter_ && *newConnectionFilter_) {\n    auto pass = folly::makeTryWith([&] {\n      return (*newConnectionFilter_)(peerAddress,\n                                     transport->getPeerCertificate(),\n                                     nextProtocol,\n                                     transportType,\n                                     tinfo);\n    });\n    if (pass.hasException() || !*pass) {\n      transport->closeWithReset();\n      return;\n    }\n  }\n\n  auto eventBase = transport->getEventBase();\n\n  if (zeroCopyEnableThreshold_ > 0) {\n    transport->setZeroCopy(true);\n    transport->setZeroCopyEnableThreshold(zeroCopyEnableThreshold_);\n  }\n\n  auto session = factory_.makeUniplexSession(std::move(transport),\n                                             peerAddress,\n                                             nextProtocol,\n                                             transportType,\n                                             tinfo,\n                                             handler_);\n  if (session) {\n    onSessionReady(eventBase, session);\n  } else {\n    onSessionCreationError(ProxygenError::kErrorUnsupportedScheme);\n  }\n}\n\nvoid HTTPCoroAcceptor::onSessionReady(folly::EventBase* eventBase,\n                                      HTTPCoroSession* session) {\n  Acceptor::addConnection(session);\n\n  // Start the coro\n  session->run().start();\n}\n\nvoid HTTPCoroAcceptor::onNewConnection(\n    std::shared_ptr<quic::QuicSocket> quicSocket, wangle::TransportInfo tinfo) {\n  if (newConnectionFilter_ && *newConnectionFilter_) {\n    auto nextProtocol = quicSocket->getAppProtocol().value_or(\"\");\n    auto peerAddr = quicSocket->getPeerAddress();\n    auto pass = folly::makeTryWith([&] {\n      return (*newConnectionFilter_)(&peerAddr,\n                                     quicSocket->getPeerCertificate().get(),\n                                     nextProtocol,\n                                     wangle::SecureTransportType::TLS,\n                                     tinfo);\n    });\n    if (pass.hasException() || !*pass) {\n      quicSocket->closeNow(\n          quic::QuicError(quic::TransportErrorCode::INTERNAL_ERROR,\n                          (pass.hasException() ? pass.exception().what().c_str()\n                                               : \"Connection Filtered\")));\n      return;\n    }\n  }\n  auto eventBase = quicSocket->getEventBase()\n                       ->getTypedEventBase<quic::FollyQuicEventBase>()\n                       ->getBackingEventBase();\n  auto session = factory_.makeQuicSession(std::move(quicSocket),\n                                          std::move(tinfo),\n                                          handler_,\n                                          /*strictValidation=*/true);\n  XCHECK(session);\n  onSessionReady(eventBase, session);\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/HTTPCoroAcceptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/ProxygenErrorEnum.h\"\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/services/AcceptorConfiguration.h>\n#include <wangle/acceptor/Acceptor.h>\n\nnamespace proxygen {\nclass HTTPCodecFactory;\nclass HTTPCodec;\nclass HTTPSessionStats;\n} // namespace proxygen\n\nnamespace quic {\nclass QuicSocket;\n}\n\nnamespace wangle {\nclass FizzLoggingCallback;\n} // namespace wangle\n\nnamespace proxygen::coro {\n\nclass HTTPHandler;\nclass LifecycleObserver;\nclass HTTPCoroSession;\n\nclass HTTPCoroDownstreamSessionFactory {\n public:\n  struct Config {\n    Config() {\n    }\n    LifecycleObserver* sessionLifecycleCb{nullptr};\n    HTTPSessionStats* sessionStats{nullptr};\n    HeaderCodec::Stats* headerCodecStats{nullptr};\n  };\n\n  explicit HTTPCoroDownstreamSessionFactory(\n      std::shared_ptr<const AcceptorConfiguration> accConfig,\n      std::shared_ptr<HTTPCodecFactory> codecFactory = nullptr,\n      Config config = Config());\n\n  void setConfig(const Config& config) {\n    config_ = config;\n  }\n\n  HTTPCoroSession* FOLLY_NULLABLE\n  makeUniplexSession(folly::AsyncTransport::UniquePtr sock,\n                     const folly::SocketAddress* address,\n                     const std::string& nextProtocol,\n                     wangle::SecureTransportType secureTransportType,\n                     const wangle::TransportInfo& tinfo,\n                     std::shared_ptr<HTTPHandler> handler);\n\n  HTTPCoroSession* makeQuicSession(std::shared_ptr<quic::QuicSocket> quicSocket,\n                                   wangle::TransportInfo tinfo,\n                                   std::shared_ptr<HTTPHandler> handler,\n                                   bool strictValidation);\n\n private:\n  void applySettingsToCodec(HTTPCodec& codec);\n  void applySettingsToSession(HTTPCoroSession& session);\n\n  std::shared_ptr<const AcceptorConfiguration> accConfig_;\n  std::shared_ptr<HTTPCodecFactory> codecFactory_;\n  Config config_;\n};\n\nclass HTTPCoroAcceptor : public wangle::Acceptor {\n\n public:\n  /**\n   * Type of function to run inside onNewConnection() of acceptors.\n   * If the function throws or returns false, the socket will be closed\n   * immediately. Useful for validating client cert before processing the\n   * request.\n   */\n  using NewConnectionFilter =\n      folly::Function<bool(const folly::SocketAddress* /* address */,\n                           const folly::AsyncTransportCertificate* /*peerCert*/,\n                           const std::string& /* nextProtocolName */,\n                           SecureTransportType /* secureTransportType */,\n                           const wangle::TransportInfo& /* tinfo */) const>;\n\n  explicit HTTPCoroAcceptor(\n      std::shared_ptr<const AcceptorConfiguration> accConfig,\n      std::shared_ptr<HTTPHandler> handler,\n      NewConnectionFilter* newConnectionFilter = nullptr,\n      std::shared_ptr<HTTPCodecFactory> codecFactory = nullptr,\n      std::shared_ptr<wangle::FizzLoggingCallback> loggingCallback = nullptr);\n  ~HTTPCoroAcceptor() override {\n  }\n\n  void init(folly::AsyncServerSocket* serverSocket,\n            folly::EventBase* eventBase,\n            wangle::SSLStats* stats = nullptr,\n            std::shared_ptr<const fizz::server::FizzServerContext> fizzContext =\n                nullptr) override {\n    if (eventBase) {\n      auto keepAlive = keepAlive_.wlock();\n      *keepAlive = eventBase;\n    }\n    Acceptor::init(serverSocket, eventBase, stats, fizzContext);\n  }\n\n  // TODO: custom error pages\n  // Priorities enabled?\n\n  void onNewConnection(std::shared_ptr<quic::QuicSocket> quicSocket,\n                       wangle::TransportInfo tinfo);\n\n  void setOnConnectionDrainedFn(std::function<void()> fn) {\n    onConnectionsDrainedFn_ = std::move(fn);\n  }\n\n  void setZeroCopyEnableThreshold(size_t threshold) {\n    zeroCopyEnableThreshold_ = threshold;\n  }\n\n  void stopAcceptingQuic() {\n    acceptStopped();\n  }\n\n  // Returns an EventBase KeepAlive for this Acceptor's EventBase if the\n  // Acceptor has not yet drained.  After drain, it returns an empty KeepAlive.\n  // The caller (from any thread) can use this to determine if this Acceptor\n  // needs to be drained or stopped.\n  folly::Executor::KeepAlive<folly::EventBase> getEventBaseKeepalive() {\n    return *keepAlive_.rlock();\n  }\n\n protected:\n  void onSessionReady(folly::EventBase* eventBase, HTTPCoroSession* session);\n  // HTTPSessionStats* downstreamSessionStats_{nullptr};\n\n  // Acceptor methods\n  void onNewConnection(folly::AsyncTransport::UniquePtr sock,\n                       const folly::SocketAddress* address,\n                       const std::string& nextProtocol,\n                       wangle::SecureTransportType secureTransportType,\n                       const wangle::TransportInfo& tinfo) override;\n\n  virtual void onSessionCreationError(ProxygenError /*error*/) {\n  }\n\n  void onConnectionsDrained() override {\n    {\n      auto keepAlive = keepAlive_.wlock();\n      keepAlive->reset();\n    }\n    if (onConnectionsDrainedFn_) {\n      onConnectionsDrainedFn_();\n    }\n  }\n\n private:\n  HTTPCoroAcceptor(const HTTPCoroAcceptor&) = delete;\n  HTTPCoroAcceptor& operator=(const HTTPCoroAcceptor&) = delete;\n\n  HTTPCoroDownstreamSessionFactory factory_;\n  std::shared_ptr<HTTPHandler> handler_;\n  NewConnectionFilter* newConnectionFilter_{nullptr};\n  std::function<void()> onConnectionsDrainedFn_;\n  folly::Synchronized<folly::Executor::KeepAlive<folly::EventBase>> keepAlive_;\n  std::shared_ptr<wangle::FizzLoggingCallback> loggingCallback_;\n  size_t zeroCopyEnableThreshold_{0};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/HTTPServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/server/HTTPServer.h\"\n#include \"proxygen/lib/http/coro/HTTPFilterFactoryHandler.h\"\n#include <proxygen/lib/utils/Time.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/congestion_control/ServerCongestionControllerFactory.h>\n#include <quic/logging/FileQLogger.h>\n#include <quic/server/QuicSharedUDPSocketFactory.h>\n\n#include <folly/fibers/BatchSemaphore.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/logging/xlog.h>\n#include <folly/system/HardwareConcurrency.h>\n\nnamespace proxygen::coro {\n\nnamespace {\n\nclass QuicAcceptorTransportFactory : public quic::QuicServerTransportFactory {\n public:\n  explicit QuicAcceptorTransportFactory(HTTPServer& server) : server_(server) {\n  }\n\n  quic::QuicServerTransport::Ptr make(\n      folly::EventBase* evb,\n      std::unique_ptr<quic::FollyAsyncUDPSocketAlias> socket,\n      const folly::SocketAddress& /* peerAddr */,\n      quic::QuicVersion,\n      std::shared_ptr<const fizz::server::FizzServerContext> ctx) noexcept\n      override {\n    auto transport = quic::QuicHandshakeSocketHolder::makeServerTransport(\n        evb, std::move(socket), std::move(ctx), &server_);\n    const auto& quicConfig = server_.getConfig().quicConfig;\n    if (quicConfig->qlogger && quicConfig->qloggerSampling.isLucky()) {\n      transport->setQLogger(quicConfig->qlogger);\n    }\n    for (auto* quicObserver : quicConfig->observers) {\n      transport->addObserver(quicObserver);\n    }\n    return transport;\n  }\n\n private:\n  proxygen::coro::HTTPServer& server_;\n};\n\nuint64_t getHTTPSettingValueOrDefault(const SettingsList& settings,\n                                      const SettingsId id,\n                                      const SettingsValue defaultValue) {\n  for (const auto& setting : settings) {\n    if (setting.id == id) {\n      return setting.value;\n    }\n  }\n  return defaultValue;\n}\n\n/**\n * HTTPServerHandler is a wrapper around the HTTPHandler that the user provides\n * via the HTTPServer constructor (i.e. HTTPServer::handler_). It's designed to\n * install a list of filters on the request (before executing user's\n * handleRequest) and response path (after executing user's handleRequest).\n *\n * See HTTPFilterFactoryHandler.h for more details on filter installation.\n */\nclass HTTPServerHandler : public proxygen::coro::HTTPFilterFactoryHandler {\n public:\n  HTTPServerHandler(std::shared_ptr<HTTPHandler> userHandler,\n                    HTTPServer::ServerFilterFactoryList filterFactories) {\n    // user supplied handler must exist\n    CHECK(userHandler);\n    setNextHandler(std::move(userHandler));\n    for (auto& factory : filterFactories) {\n      addFilterFactory(std::move(factory));\n    }\n  }\n\n  ~HTTPServerHandler() override = default;\n};\n\n} // namespace\n\nHTTPServer::~HTTPServer() {\n  XCHECK(!eventBase_.isRunning());\n}\n\nvoid HTTPServer::start(\n    std::function<void()> onSuccess,\n    std::function<void(std::exception_ptr)> onError,\n    std::shared_ptr<folly::ThreadPoolExecutor::Observer> observer) {\n  folly::EventBaseManager::get()->setEventBase(&eventBase_, false);\n  if (config_.numIOThreads == 0) {\n    config_.numIOThreads = folly::available_concurrency();\n  }\n  folly::IOThreadPoolExecutor::Options options;\n  options.setWaitForAll(true);\n  folly::IOThreadPoolExecutor threadPool(\n      config_.numIOThreads,\n      config_.numIOThreads,\n      std::make_shared<folly::NamedThreadFactory>(\"HTTPServerIO\"),\n      folly::EventBaseManager::get(),\n      options);\n  auto threadObserver = std::make_shared<InternalThreadObserver>(this);\n  threadPool.addObserver(threadObserver);\n  if (observer) {\n    threadPool.addObserver(observer);\n  }\n\n  // if filterFactories not empty, wrap user supplied handler with server\n  // handler that installs the filterFactories\n  if (!config_.filterFactories.empty()) {\n    handler_ = std::make_shared<HTTPServerHandler>(std::move(handler_),\n                                                   config_.filterFactories);\n  }\n\n  try {\n    // Ensures all evb threads are running\n    auto evbs = threadPool.getAllEventBases();\n    if (config_.quicConfig) {\n      startQuic(evbs);\n    } else {\n      startTcp(evbs);\n    }\n  } catch (const std::exception& ex) {\n    XLOG(ERR) << \"Initialization encountered an error=\" << ex.what();\n    if (onError) {\n      onError(std::current_exception());\n    } else {\n      // Rather than swallowing the exception silently, raise it accordingly.\n      throw;\n    }\n    return;\n  }\n  run(std::move(onSuccess));\n  // Blocks until all IO Threads have terminated and joined\n  threadPool.stop();\n  state_ = State::STOPPED;\n}\n\nvoid HTTPServer::startQuic(const KeepAliveEventBaseVec& keepAliveEvbs) {\n  std::vector<folly::EventBase*> evbs{keepAliveEvbs.size(), nullptr};\n  std::transform(keepAliveEvbs.begin(),\n                 keepAliveEvbs.end(),\n                 evbs.begin(),\n                 [](const folly::Executor::KeepAlive<folly::EventBase>& evb) {\n                   return evb.get();\n                 });\n  createQuicServer(evbs);\n  quicServer_->initialize(config_.socketConfig.bindAddress, evbs, true);\n  quicServer_->waitUntilInitialized();\n  quicServer_->start();\n\n  // Wait for start to install read callback for all listening sockets\n  std::atomic<size_t> started = evbs.size();\n  folly::Baton baton;\n  for (auto evb : evbs) {\n    evb->runInEventBaseThread([&] {\n      if (--started == 0) {\n        baton.post();\n      }\n    });\n  }\n  baton.wait();\n}\n\nHTTPCoroAcceptor* HTTPServer::createAcceptor(\n    folly::EventBase* evb,\n    std::shared_ptr<const AcceptorConfiguration> acceptorConfig) {\n  auto [it, _] = acceptors_.try_emplace(evb, std::list<HTTPCoroAcceptor>());\n  auto* acceptor = &(*it->second.emplace(it->second.end(),\n                                         std::move(acceptorConfig),\n                                         handler_,\n                                         &config_.newConnectionFilter,\n                                         /* codecFactory = */ nullptr,\n                                         config_.fizzLoggingCallback));\n  if (config_.zeroCopyEnableThreshold > 0) {\n    acceptor->setZeroCopyEnableThreshold(config_.zeroCopyEnableThreshold);\n  }\n  return acceptor;\n}\n\nHTTPCoroAcceptor* FOLLY_NULLABLE\nHTTPServer::getQuicAcceptor(folly::EventBase* evb) {\n  auto it = acceptors_.find(evb);\n  if (it == acceptors_.end()) {\n    return nullptr;\n  }\n  // For now, the quic implementation can still only support one acceptor.\n  XCHECK_EQ(it->second.size(), 1UL);\n  return &it->second.front();\n}\n\nvoid HTTPServer::startTcp(const KeepAliveEventBaseVec& keepAliveEvbs) {\n  std::vector<SocketAcceptorConfig> socketAcceptorConfigs;\n  if (socketAcceptorConfigFactoryFn_) {\n    XLOG(DBG4) << \"Using custom socket acceptor config factory\";\n    socketAcceptorConfigs = socketAcceptorConfigFactoryFn_(eventBase_, config_);\n    for (auto& socketAcceptorConfig : socketAcceptorConfigs) {\n      socketAcceptorConfig.socket->startAccepting();\n    }\n  } else {\n    auto serverSocket = folly::AsyncServerSocket::UniquePtr(\n        new folly::AsyncServerSocket(&eventBase_));\n    try {\n      serverSocket->setReusePortEnabled(setReusePortSocketOption_);\n      if (config_.socketConfig.useZeroCopy) {\n        serverSocket->setZeroCopy(true);\n      }\n      if (config_.preboundSocket.has_value()) {\n        serverSocket->useExistingSocket(\n            folly::NetworkSocket::fromFd(config_.preboundSocket.value()));\n      } else {\n        serverSocket->bind(config_.socketConfig.bindAddress);\n      }\n      serverSocket->listen(config_.socketConfig.acceptBacklog);\n      serverSocket->startAccepting();\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Failed to setup server socket ex=\" << ex.what();\n      throw;\n    }\n    socketAcceptorConfigs.push_back({\n        .socket = std::move(serverSocket),\n        .acceptorConfig = toAcceptorConfig(config_),\n    });\n  }\n  for (auto& socketAcceptorConfig : socketAcceptorConfigs) {\n    for (auto& evb : keepAliveEvbs) {\n      createAcceptor(evb.get(), socketAcceptorConfig.acceptorConfig)\n          ->init(socketAcceptorConfig.socket.get(), evb.get());\n    }\n    serverSockets_.emplace_back(std::move(socketAcceptorConfig.socket));\n  }\n}\n\nvoid HTTPServer::run(std::function<void()> onSuccess) {\n  XLOG(DBG4) << __func__;\n  XCHECK_EQ(state_, State::UNINIT);\n  state_ = State::RUNNING;\n  for (auto sig : config_.shutdownOnSignals) {\n    signalHandler_.registerSignalHandler(sig);\n  }\n  if (onSuccess) {\n    eventBase_.runInLoop([onSuccess = std::move(onSuccess)] { onSuccess(); });\n  }\n  eventBase_.loop();\n  XLOG(DBG4) << __func__ << \" exit\";\n}\n\nvoid HTTPServer::createQuicServer(const std::vector<folly::EventBase*>& evbs) {\n  std::shared_ptr<fizz::server::FizzServerContext> fizzCtx;\n  auto acceptorConfig = toAcceptorConfig(config_);\n  for (auto evb : evbs) {\n    auto acceptor = createAcceptor(evb, acceptorConfig);\n    acceptor->init(nullptr, evb);\n    nRunningAcceptors_++;\n    acceptor->setOnConnectionDrainedFn([this] {\n      if (--nRunningAcceptors_ == 0) {\n        quicServer_->shutdown();\n      }\n    });\n    if (!fizzCtx) {\n      fizzCtx = acceptor->recreateFizzContext();\n    }\n  }\n  CHECK(config_.quicConfig) << \"QuicConfig must be set\";\n  quicServer_ =\n      quic::QuicServer::createQuicServer(config_.quicConfig->transportSettings);\n  quicServer_->setBindV6Only(false);\n  quicServer_->setCongestionControllerFactory(\n      std::make_shared<quic::ServerCongestionControllerFactory>());\n  quicServer_->setQuicServerTransportFactory(\n      std::make_unique<QuicAcceptorTransportFactory>(*this));\n  quicServer_->setQuicUDPSocketFactory(\n      std::make_unique<quic::QuicSharedUDPSocketFactory>());\n  quicServer_->setHealthCheckToken(\"health\");\n  if (!config_.quicConfig->quicVersions.empty()) {\n    quicServer_->setSupportedVersion(config_.quicConfig->quicVersions);\n  }\n  configureFizzServerContext(fizzCtx);\n  quicServer_->setFizzContext(fizzCtx);\n  if (config_.quicConfig->rateLimitPerThread) {\n    quicServer_->setRateLimit(\n        [rateLimitPerThread =\n             config_.quicConfig->rateLimitPerThread.value()]() {\n          return rateLimitPerThread;\n        },\n        std::chrono::seconds(1));\n  }\n  if (config_.quicConfig->statsFactory) {\n    quicServer_->setTransportStatsCallbackFactory(\n        std::move(config_.quicConfig->statsFactory));\n  }\n  if (config_.quicConfig->ccFactory) {\n    quicServer_->setCongestionControllerFactory(\n        std::move(config_.quicConfig->ccFactory));\n  }\n  quicServer_->setConnectionIdVersion(quic::ConnectionIdVersion::V2);\n  quicServer_->setHostId(hostId_);\n}\n\nvoid HTTPServer::configureFizzServerContext(\n    std::shared_ptr<fizz::server::FizzServerContext> serverCtx) {\n  serverCtx->setSupportedAlpns(config_.quicConfig->supportedAlpns);\n  serverCtx->setAlpnMode(fizz::server::AlpnMode::Required);\n  serverCtx->setSendNewSessionTicket(false);\n  serverCtx->setEarlyDataFbOnly(false);\n  serverCtx->setVersionFallbackEnabled(false);\n\n  fizz::server::ClockSkewTolerance tolerance;\n  tolerance.before = std::chrono::minutes(-5);\n  tolerance.after = std::chrono::minutes(5);\n\n  std::shared_ptr<fizz::server::ReplayCache> replayCache =\n      std::make_shared<fizz::server::AllowAllReplayReplayCache>();\n\n  serverCtx->setEarlyDataSettings(true, tolerance, std::move(replayCache));\n}\n\nvoid HTTPServer::onQuicTransportReady(\n    std::shared_ptr<quic::QuicSocket> quicSocket) {\n  wangle::TransportInfo tinfo;\n  tinfo.acceptTime = getCurrentTime();\n  tinfo.appProtocol =\n      std::make_shared<std::string>(quicSocket->getAppProtocol().value_or(\"\"));\n  tinfo.secure = true;\n  // TODO: fill in other tinfo\n  auto acceptor =\n      getQuicAcceptor(quicSocket->getEventBase()\n                          ->getTypedEventBase<quic::FollyQuicEventBase>()\n                          ->getBackingEventBase());\n  XCHECK(acceptor) << \"QuicSocket in foreign EventBase\";\n  acceptor->onNewConnection(std::move(quicSocket), std::move(tinfo));\n}\n\nvoid HTTPServer::drain() {\n  XLOG(DBG4) << __func__;\n  if (state_ == State::RUNNING) {\n    state_ = State::DRAINING;\n    eventBase_.runImmediatelyOrRunInEventBaseThread(\n        [this] { globalDrainImpl(); });\n    for (auto& it : acceptors_) {\n      for (auto& acceptor : it.second) {\n        if (auto evb = acceptor.getEventBaseKeepalive()) {\n          evb->runImmediatelyOrRunInEventBaseThread(\n              [this, &acceptor] { drainImpl(acceptor); });\n        } // else the acceptor already drained, maybe called after\n          // drain/forceStop\n      }\n    }\n    eventBase_.runImmediatelyOrRunInEventBaseThread(\n        [this] { unregisterSignalHandlers(); });\n  }\n}\n\nvoid HTTPServer::globalDrainImpl() {\n  XLOG(DBG4) << __func__;\n  for (const auto& serverSocket : serverSockets_) {\n    XCHECK(serverSocket);\n    serverSocket->stopAccepting();\n  }\n  if (quicServer_) {\n    quicServer_->rejectNewConnections([]() { return true; });\n  }\n}\n\nvoid HTTPServer::unregisterSignalHandlers() {\n  XLOG(DBG4) << __func__;\n  for (auto sig : config_.shutdownOnSignals) {\n    signalHandler_.unregisterSignalHandler(sig);\n  }\n}\n\nvoid HTTPServer::drainImpl(HTTPCoroAcceptor& acceptor) {\n  XLOG(DBG4) << __func__;\n  if (quicServer_) {\n    acceptor.stopAcceptingQuic();\n  }\n}\n\nvoid HTTPServer::forceStop() {\n  XLOG(DBG4) << __func__;\n  auto state = state_.load();\n  if (state == State::RUNNING) {\n    state_ = state = State::DRAINING;\n    folly::ExecutorKeepAlive keepAlive(&eventBase_);\n    eventBase_.runImmediatelyOrRunInEventBaseThread([this] {\n      for (const auto& serverSocket : serverSockets_) {\n        XCHECK(serverSocket);\n        serverSocket->stopAccepting();\n      }\n    });\n    eventBase_.runImmediatelyOrRunInEventBaseThread(\n        [this] { unregisterSignalHandlers(); });\n  }\n  if (state == State::DRAINING) {\n    if (quicServer_) {\n      quicServer_->shutdown();\n    }\n    for (auto& it : acceptors_) {\n      for (auto& acceptor : it.second) {\n        if (acceptor.getEventBaseKeepalive()) {\n          acceptor.forceStop();\n        } // else, the acceptor already drained\n      }\n    }\n  }\n}\n\nstd::shared_ptr<const AcceptorConfiguration> HTTPServer::toAcceptorConfig(\n    const Config& config) {\n  auto accConfig = std::make_shared<AcceptorConfiguration>();\n  wangle::ServerSocketConfig* serverSocketConfig = accConfig.get();\n  *serverSocketConfig = config.socketConfig;\n  // TODO: write timeout\n  accConfig->egressSettings = config.sessionConfig.settings;\n  accConfig->transactionIdleTimeout = config.sessionConfig.streamReadTimeout;\n  accConfig->receiveSessionWindowSize = config.sessionConfig.connFlowControl;\n  accConfig->receiveStreamWindowSize =\n      getHTTPSettingValueOrDefault(config.sessionConfig.settings,\n                                   SettingsId::INITIAL_WINDOW_SIZE,\n                                   config.sessionConfig.streamFlowControl);\n  accConfig->plaintextProtocol = config.plaintextProtocol;\n  accConfig->forceHTTP1_0_to_1_1 = true;\n  accConfig->connectionIdleTimeout = config.sessionConfig.connIdleTimeout;\n  return accConfig;\n}\n\nvoid HTTPServer::setHostId(uint32_t hostId) {\n  hostId_ = hostId;\n  if (quicServer_) {\n    // quicServer_ has been initialized. need to update hostId in\n    // quicServer_\n    eventBase_.runImmediatelyOrRunInEventBaseThreadAndWait(\n        [this] { quicServer_->setHostId(hostId_); });\n  }\n}\n\nvoid HTTPServer::updateTlsCredentials() noexcept {\n  eventBase_.checkIsInEventBaseThread();\n  folly::fibers::BatchSemaphore sem{/*tokenCount=*/0};\n  const size_t numAcceptors = acceptors_.size();\n\n  // reload all ssl contexts in all acceptors\n  for (auto& [evb, acceptors] : acceptors_) {\n    evb->runInEventBaseThread([&]() {\n      for (auto& acceptor : acceptors) {\n        if (acceptor.isSSL()) {\n          acceptor.reloadSSLContextConfigs();\n        }\n      }\n      sem.signal(1);\n    });\n  }\n  sem.wait(numAcceptors); // wait until all async work is complete\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/HTTPServer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/filters/ServerFilterFactory.h\"\n#include \"proxygen/lib/http/coro/server/HTTPCoroAcceptor.h\"\n#include <folly/container/F14Map.h>\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/io/async/AsyncSignalHandler.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/sampling/Sampling.h>\n#include <quic/server/QuicHandshakeSocketHolder.h>\n#include <quic/server/QuicServer.h>\n#include <signal.h>\n#include <wangle/acceptor/FizzAcceptorHandshakeHelper.h>\n#include <wangle/acceptor/ServerSocketConfig.h>\n\nnamespace proxygen::coro {\n\nusing folly::IOThreadPoolExecutor;\nusing folly::ThreadPoolExecutor;\n\nclass HTTPServer : public quic::QuicHandshakeSocketHolder::Callback {\n public:\n  struct QuicConfig {\n    std::vector<quic::QuicVersion> quicVersions;\n    std::vector<std::string> supportedAlpns{\"h3\", \"h3-fb-05\"};\n    quic::TransportSettings transportSettings;\n    folly::Optional<int64_t> rateLimitPerThread;\n\n    std::shared_ptr<quic::QLogger> qlogger{nullptr};\n    Sampling qloggerSampling{1.0};\n    std::unique_ptr<quic::QuicTransportStatsCallbackFactory> statsFactory{\n        nullptr};\n    std::shared_ptr<quic::CongestionControllerFactory> ccFactory{nullptr};\n    std::vector<quic::QuicSocket::Observer*> observers;\n  };\n\n  struct SessionConfig {\n    SettingsList settings{{SettingsId::MAX_HEADER_LIST_SIZE, 32 * 1024},\n                          {SettingsId::HEADER_TABLE_SIZE, 4096},\n                          {SettingsId::MAX_FRAME_SIZE, 16384},\n                          {SettingsId::MAX_CONCURRENT_STREAMS, 100}};\n    uint32_t maxConcurrentOutgoingStreams{100};\n    // streamFlowControl sets the stream fc window in h2 and stream read buffer\n    // limit in h1&h3 – SettingsId::INITIAL_WINDOW_SIZE in SettingsList above\n    // overrides this value.\n    size_t streamFlowControl{256 * 1024};\n    size_t connFlowControl{20 * 256 * 1024};\n    std::chrono::milliseconds streamReadTimeout{std::chrono::seconds(10)};\n    std::chrono::milliseconds connIdleTimeout{std::chrono::seconds(10)};\n    std::chrono::milliseconds writeTimeout{std::chrono::seconds(5)};\n  };\n\n  using NewConnectionFilter = HTTPCoroAcceptor::NewConnectionFilter;\n  using ServerFilterFactoryList =\n      std::vector<std::shared_ptr<ServerFilterFactory>>;\n  struct Config {\n    wangle::ServerSocketConfig socketConfig;\n    std::optional<QuicConfig> quicConfig;\n    // Allow taking in an existing socket. If provided together with\n    // socketConfig.bindAddress, this is preferred\n    // TODO(T198199559): Right now supported for TCP server only\n    std::optional<int> preboundSocket;\n    std::string plaintextProtocol;\n    SessionConfig sessionConfig;\n    size_t numIOThreads{1};\n    std::vector<int> shutdownOnSignals{SIGINT};\n    NewConnectionFilter newConnectionFilter;\n    ServerFilterFactoryList filterFactories;\n    std::shared_ptr<wangle::FizzLoggingCallback> fizzLoggingCallback;\n\n    /**\n     * Zero copy enable threshold. When set to a non-zero value (along with\n     * socketConfig.useZeroCopy = true), each accepted TCP connection will\n     * have zero copy enabled and MSG_ZEROCOPY will be used for writes\n     * whose total length meets or exceeds this threshold.\n     */\n    size_t zeroCopyEnableThreshold{0};\n  };\n\n  static wangle::SSLContextConfig getDefaultTLSConfig() {\n    wangle::SSLContextConfig defaultConfig;\n    defaultConfig.clientVerification =\n        folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n    defaultConfig.sslVersion = folly::SSLContext::TLSv1;\n    defaultConfig.isDefault = true;\n    defaultConfig.setNextProtocols({\"h2\", \"http/1.1\"});\n    return defaultConfig;\n  }\n\n  struct SocketAcceptorConfig {\n    folly::AsyncServerSocket::UniquePtr socket;\n    std::shared_ptr<const AcceptorConfiguration> acceptorConfig;\n  };\n  using SocketAcceptorConfigFactoryFn =\n      std::function<std::vector<SocketAcceptorConfig>(\n          folly::EventBase&, const HTTPServer::Config&)>;\n\n  HTTPServer(Config config, std::shared_ptr<HTTPHandler> handler)\n      : HTTPServer(std::move(config), std::move(handler), nullptr) {\n  }\n\n  HTTPServer(Config config,\n             std::shared_ptr<HTTPHandler> handler,\n             SocketAcceptorConfigFactoryFn fn)\n      : config_(std::move(config)),\n        handler_(std::move(handler)),\n        socketAcceptorConfigFactoryFn_(std::move(fn)) {\n  }\n\n  ~HTTPServer() override;\n\n  const Config& getConfig() const {\n    return config_;\n  }\n\n  /**\n   * Start HTTPServer.\n   *\n   * Note this is a blocking call and the current thread will be used to listen\n   * for incoming connections. Throws exception if something goes wrong (say\n   * somebody else is already listening on that socket).\n   *\n   * `onSuccess` callback will be invoked from the event loop which shows that\n   * all the setup was successfully done.\n   *\n   * `onError` callback will be invoked if some errors occurs while starting the\n   * server instead of throwing exception.\n   *\n   * `observer` will be added as a thread pool observer.\n   */\n  void start(\n      std::function<void()> onSuccess = nullptr,\n      std::function<void(std::exception_ptr)> onError = nullptr,\n      std::shared_ptr<folly::ThreadPoolExecutor::Observer> observer = nullptr);\n\n  /**\n   * Get the address the server is listening on. Empty if sockets are not\n   * bound yet. Almost every use cases of HttpServer only support one socket,\n   * so we keep this function for backwards compatibility as we migrate to\n   * support implementations that require multiple sockets.\n   */\n  std::optional<folly::SocketAddress> address() const {\n    if (!serverSockets_.empty()) {\n      XCHECK_EQ(serverSockets_.size(), 1UL)\n          << \"Attempt to use address() on an implementation that supports \"\n             \"multiple addresses\";\n      folly::SocketAddress address;\n      serverSockets_.front()->getAddress(&address);\n      return address;\n    } else if (quicServer_) {\n      return quicServer_->getAddress();\n    } else {\n      return std::nullopt;\n    }\n  }\n\n  /**\n   * Returns all addresses the server is listening on - useful for\n   * implementations that support multiple sockets, but can still be\n   * called by single socket implementations too.\n   */\n  std::vector<folly::SocketAddress> addresses() const {\n    std::vector<folly::SocketAddress> addresses;\n    if (quicServer_) {\n      addresses.emplace_back(quicServer_->getAddress());\n    } else {\n      for (auto& socket : serverSockets_) {\n        addresses.emplace_back(socket->getAddress());\n      }\n    }\n    return addresses;\n  }\n\n  /**\n   * Stop listening on bound ports. (Stop accepting new work).\n   * It does not wait for pending work to complete.\n   * This can be called from any thread, and it is idempotent.\n   * However, it may only be called **after** start() has called onSuccess.\n   */\n  void drain();\n\n  /**\n   * Stop HTTPServer.\n   *\n   * Can be called from any thread, but only after start() has called\n   * onSuccess.  Server will stop listening for new connections and drop all\n   * connections immediately. It's preferable to call drain() before\n   * forceStop().\n   */\n  void forceStop();\n\n  void enableSoReusePort() {\n    setReusePortSocketOption_ = true;\n  }\n\n  void updateTlsCredentials() noexcept;\n\n  void setHostId(uint32_t hostId);\n\n  /**\n   * Interface for receiving callback when server threads are started or\n   * stopped. The server does not guarantee thread-safety for the\n   * callbacks.\n   */\n  class Observer {\n   public:\n    virtual ~Observer() = default;\n    virtual void onThreadStart(folly::EventBase*) {\n    }\n    virtual void onThreadStop(folly::EventBase*) {\n    }\n  };\n\n  void addObserver(Observer* observer) {\n    CHECK_NOTNULL(observer);\n    observers_.insert(observer);\n  }\n\n  void removeObserver(Observer* observer) {\n    observers_.erase(observer);\n  }\n\n  folly::EventBase* evb() noexcept {\n    return &eventBase_;\n  }\n\n private:\n  using KeepAliveEventBaseVec =\n      std::vector<folly::Executor::KeepAlive<folly::EventBase>>;\n\n  std::shared_ptr<const AcceptorConfiguration> toAcceptorConfig(\n      const Config& config);\n  HTTPCoroAcceptor* createAcceptor(\n      folly::EventBase* evb,\n      std::shared_ptr<const AcceptorConfiguration> acceptorConfig);\n  HTTPCoroAcceptor* FOLLY_NULLABLE getQuicAcceptor(folly::EventBase* evb);\n  void startTcp(const KeepAliveEventBaseVec& evbs);\n  void startQuic(const KeepAliveEventBaseVec& vbs);\n  void createQuicServer(const std::vector<folly::EventBase*>& evbs);\n  void configureFizzServerContext(\n      std::shared_ptr<fizz::server::FizzServerContext> fizzCtx);\n  void onQuicTransportReady(\n      std::shared_ptr<quic::QuicSocket> quicSocket) override;\n  void onConnectionSetupError(std::shared_ptr<quic::QuicSocket>,\n                              quic::QuicError error) override {\n    XLOG(ERR) << \"Failed to accept QUIC connection: \" << error.message;\n  }\n  void run(std::function<void()> onSuccess);\n  void globalDrainImpl();\n  void unregisterSignalHandlers();\n  void drainImpl(HTTPCoroAcceptor& acceptor);\n\n  Config config_;\n  std::shared_ptr<HTTPHandler> handler_;\n  folly::F14FastSet<Observer*> observers_;\n  folly::EventBase eventBase_;\n  std::vector<folly::AsyncServerSocket::UniquePtr> serverSockets_;\n  bool setReusePortSocketOption_{false};\n  folly::F14NodeMap<folly::EventBase*, std::list<HTTPCoroAcceptor>> acceptors_;\n  std::shared_ptr<quic::QuicServer> quicServer_;\n  SocketAcceptorConfigFactoryFn socketAcceptorConfigFactoryFn_{nullptr};\n  enum class State : uint8_t {\n    UNINIT = 0,\n    RUNNING = 1,\n    DRAINING = 2,\n    STOPPED = 3\n  };\n  std::atomic<State> state_{State::UNINIT};\n  std::atomic<size_t> nRunningAcceptors_{0};\n  uint32_t hostId_{0};\n  friend std::ostream& operator<<(std::ostream& os,\n                                  const HTTPServer::State& state);\n\n  template <typename T, typename... Args>\n  void deliverObserverEvent(T callbackFn, Args&&... args) {\n    auto obsClone = observers_;\n\n    for (auto& obs : obsClone) {\n      if (std::find(obsClone.begin(), obsClone.end(), obs) != obsClone.end()) {\n        (obs->*callbackFn)(std::forward<Args>(args)...);\n      }\n    }\n  }\n\n  class InternalThreadObserver\n      : public folly::IOThreadPoolExecutorBase::IOObserver {\n   public:\n    explicit InternalThreadObserver(HTTPServer* server) : server_(server) {\n    }\n\n    void registerEventBase(folly::EventBase& evb) noexcept override {\n      evb.runInEventBaseThread([this, &evb]() {\n        server_->deliverObserverEvent(&HTTPServer::Observer::onThreadStart,\n                                      &evb);\n        for (auto& filterFactory : server_->config_.filterFactories) {\n          filterFactory->onServerStart(&evb);\n        }\n      });\n    }\n\n    void unregisterEventBase(folly::EventBase& evb) noexcept override {\n      evb.runInEventBaseThread([this, &evb]() {\n        server_->deliverObserverEvent(&HTTPServer::Observer::onThreadStop,\n                                      &evb);\n        for (auto& filterFactory : server_->config_.filterFactories) {\n          filterFactory->onServerStop();\n        }\n      });\n    }\n\n   private:\n    HTTPServer* server_;\n  };\n  friend class ThreadObserver;\n\n  class SignalHandler : public folly::AsyncSignalHandler {\n   public:\n    explicit SignalHandler(HTTPServer& server)\n        : folly::AsyncSignalHandler(&server.eventBase_), server_(server) {\n    }\n\n    void signalReceived(int signum) noexcept override {\n      XLOG(DBG2) << \"Received signal \" << signum << \", initiating drain\";\n      server_.drain();\n    }\n\n   private:\n    HTTPServer& server_;\n  };\n  SignalHandler signalHandler_{*this};\n};\n\ninline std::ostream& operator<<(std::ostream& os,\n                                const HTTPServer::State& state) {\n  switch (state) {\n    case HTTPServer::State::UNINIT:\n      os << \"UNINIT\";\n      break;\n    case HTTPServer::State::RUNNING:\n      os << \"RUNNING\";\n      break;\n    case HTTPServer::State::DRAINING:\n      os << \"DRAINING\";\n      break;\n    case HTTPServer::State::STOPPED:\n      os << \"STOPPED\";\n      break;\n  }\n  return os;\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/ScopedHTTPServer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/server/HTTPServer.h\"\n\nnamespace proxygen::coro {\n\nclass ScopedHTTPServer {\n public:\n  static std::unique_ptr<ScopedHTTPServer> start(\n      HTTPServer::Config config,\n      std::shared_ptr<HTTPHandler> handler,\n      HTTPServer::Observer* observer = nullptr,\n      HTTPServer::SocketAcceptorConfigFactoryFn socketAcceptorConfigFactoryFn_ =\n          nullptr) {\n    std::unique_ptr<ScopedHTTPServer> server(\n        new ScopedHTTPServer(std::move(config),\n                             std::move(handler),\n                             std::move(socketAcceptorConfigFactoryFn_)));\n    if (observer) {\n      server->getServer().addObserver(observer);\n    }\n    server->start();\n    return server;\n  }\n\n  ~ScopedHTTPServer() {\n    server_.drain();\n    server_.forceStop();\n    thread_.join();\n  }\n\n  std::optional<folly::SocketAddress> address() {\n    return server_.address();\n  }\n\n  std::vector<folly::SocketAddress> addresses() {\n    return server_.addresses();\n  }\n\n  void start() {\n    std::exception_ptr eptr;\n    folly::Baton baton;\n\n    thread_ = std::thread([&]() {\n      server_.start([&]() { baton.post(); },\n                    [&](std::exception_ptr ex) {\n                      eptr = ex;\n                      baton.post();\n                    });\n    });\n\n    // Wait for server to start\n    baton.wait();\n    if (eptr) {\n      thread_.join();\n\n      std::rethrow_exception(eptr);\n    }\n  }\n\n  HTTPServer& getServer() {\n    return server_;\n  }\n\n protected:\n  ScopedHTTPServer(\n      HTTPServer::Config config,\n      std::shared_ptr<HTTPHandler> handler,\n      HTTPServer::SocketAcceptorConfigFactoryFn socketAcceptorConfigFactoryFn_)\n      : server_(std::move(config),\n                std::move(handler),\n                std::move(socketAcceptorConfigFactoryFn_)) {\n  }\n\n private:\n  std::thread thread_;\n  HTTPServer server_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/handlers/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_server_handlers_expect_continue_wrapper_handler\n  SRCS\n    ExpectContinueWrapperHandler.cpp\n  DEPS\n    proxygen_http_http_headers\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_coro_source\n)\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/handlers/ExpectContinueWrapperHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/server/handlers/ExpectContinueWrapperHandler.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n\n#include <folly/logging/xlog.h>\n#include <string_view>\n\nnamespace proxygen::coro {\n\nconstexpr folly::StringPiece k100ContinueExpectation = \"100-continue\";\nconstexpr std::string_view kContinueMessage = \"Continue\";\nconstexpr std::string_view kExpectationFailedMessage = \"Expectation Failed\";\nconstexpr uint16_t kContinueStatusCode = 100;\nconstexpr uint16_t kExpectationFailedStatusCode = 417;\n\nfolly::coro::Task<HTTPHeaderEvent>\nExpectContinueWrapperResponse::readHeaderEvent() {\n  if (!response100Written_) {\n    XLOG(DBG8)\n        << \"ExpectContinueWrapper sending 100-continue to fulfill expectation\";\n    response100Written_ = true;\n    auto msg = std::make_unique<HTTPMessage>();\n    msg->setStatusCode(kContinueStatusCode);\n    msg->setStatusMessage(kContinueMessage);\n    msg->setHTTPVersion(1, 1);\n    HTTPHeaderEvent event(std::move(msg), false);\n    co_return event;\n  } else {\n    co_await ensureNextHandlerInvoked();\n    auto event = co_await wrappedResponseHolder_.readHeaderEvent();\n    auto guard = folly::makeGuard(lifetime(event));\n    co_return event;\n  }\n}\n\nfolly::coro::Task<HTTPBodyEvent> ExpectContinueWrapperResponse::readBodyEvent(\n    uint32_t max) {\n  co_await ensureNextHandlerInvoked();\n  auto event = co_await wrappedResponseHolder_.readBodyEvent(max);\n  auto guard = folly::makeGuard(lifetime(event));\n  co_return event;\n}\n\nvoid ExpectContinueWrapperResponse::stopReading(\n    folly::Optional<const HTTPErrorCode>) {\n  if (heapAllocated_) {\n    delete this;\n  }\n}\n\nfolly::coro::Task<void>\nExpectContinueWrapperResponse::ensureNextHandlerInvoked() {\n  if (!bool(wrappedResponseHolder_)) {\n    XLOG(DBG8) << \"ExpectContinueWrapper invoking wrapped handler\";\n    wrappedResponseHolder_ = co_await nextHandler_->handleRequest(\n        evb_, ctx_, std::move(requestForNextHandler_));\n  }\n}\n\nfolly::coro::Task<HTTPSourceHolder> ExpectContinueWrapperHandler::handleRequest(\n    folly::EventBase* evb,\n    HTTPSessionContextPtr ctx,\n    HTTPSourceHolder requestSource) {\n  auto headerEvent = co_await requestSource.readHeaderEvent();\n  XCHECK(headerEvent.headers);\n  auto headers = headerEvent.headers->getHeaders();\n  if (headers.exists(HTTP_HEADER_EXPECT)) {\n    auto& expectVal = headers.getSingleOrEmpty(HTTP_HEADER_EXPECT);\n    if (k100ContinueExpectation.equals(expectVal,\n                                       folly::AsciiCaseInsensitive{})) {\n      // Request has an expectation with the value 100-continue\n      // We need to wrap the next handler in an ExpectContinueWrapperResponse\n      // to inject the 100 Continue response\n      XLOG(DBG8) << \"ExpectContinueWrapper found 100-continue expectation\";\n      headerEvent.headers->getHeaders().remove(HTTP_HEADER_EXPECT);\n      auto expectWrapperResponse =\n          new ExpectContinueWrapperResponse(evb,\n                                            ctx,\n                                            std::move(headerEvent.headers),\n                                            std::move(requestSource),\n                                            nextHandler_);\n      expectWrapperResponse->setHeapAllocated();\n      co_return expectWrapperResponse;\n    } else {\n      // Invalid expectation. Send a response failing this request.\n      XLOG(DBG8)\n          << \"ExpectContinueWrapper returning 417 for invalid expectation: \"\n          << expectVal;\n      co_return HTTPFixedSource::makeFixedResponse(\n          kExpectationFailedStatusCode, std::string(kExpectationFailedMessage));\n    }\n  } else {\n    // This request does not have an expectation. Bypass the wrapper\n    // and handle this request directly using the next handler.\n\n    // Combine already consumed headers with the requestSource in a new\n    // HybridSource\n    auto hybridSource = new HTTPHybridSource(std::move(headerEvent.headers),\n                                             std::move(requestSource));\n    hybridSource->setHeapAllocated();\n    co_return co_await nextHandler_->handleRequest(evb, ctx, hybridSource);\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/handlers/ExpectContinueWrapperHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <utility>\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n\nnamespace proxygen::coro {\n\nclass ExpectContinueWrapperResponse : public HTTPSource {\n public:\n  explicit ExpectContinueWrapperResponse(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      std::unique_ptr<HTTPMessage> consumedRequestHeader,\n      HTTPSourceHolder bodyOnlySource,\n      std::shared_ptr<HTTPHandler> nextHandler)\n      : evb_(evb),\n        ctx_(std::move(ctx)),\n        requestForNextHandler_(),\n        nextHandler_(std::move(nextHandler)) {\n    auto hybridSource = new HTTPHybridSource(std::move(consumedRequestHeader),\n                                             std::move(bodyOnlySource));\n    hybridSource->setHeapAllocated();\n    requestForNextHandler_.setSource(hybridSource);\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override;\n\n  void stopReading(folly::Optional<const HTTPErrorCode> = folly::none) override;\n\n private:\n  folly::coro::Task<void> ensureNextHandlerInvoked();\n  folly::EventBase* evb_;\n  HTTPSessionContextPtr ctx_;\n  bool response100Written_{false};\n  HTTPSourceHolder requestForNextHandler_;\n  std::shared_ptr<HTTPHandler> nextHandler_;\n  HTTPSourceHolder wrappedResponseHolder_;\n};\n\nclass ExpectContinueWrapperHandler : public HTTPHandler {\n public:\n  explicit ExpectContinueWrapperHandler(\n      std::shared_ptr<HTTPHandler> nextHandler)\n      : nextHandler_(std::move(nextHandler)) {\n  }\n\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override;\n\n private:\n  std::shared_ptr<HTTPHandler> nextHandler_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/handlers/test/ExpectContinueWrapperHandlerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/server/handlers/ExpectContinueWrapperHandler.h\"\n\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/test/Mocks.h>\n\nusing namespace testing;\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nnamespace proxygen::coro::test {\n\nclass ExpectContinueWrapperHandlerTest : public Test {\n public:\n  ExpectContinueWrapperHandlerTest()\n      : mockHandler_(std::make_shared<MockHTTPHandler>()),\n        expectContinueWrapper_(\n            std::make_unique<ExpectContinueWrapperHandler>(mockHandler_)) {\n  }\n\n protected:\n  folly::EventBase evb_;\n  MockHTTPSessionContext httpSessionContext_;\n  std::shared_ptr<MockHTTPHandler> mockHandler_;\n  std::unique_ptr<ExpectContinueWrapperHandler> expectContinueWrapper_;\n};\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest, SendContinueResponse) {\n  InSequence seq;\n\n  // Create a request with Expect: 100-continue header\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"POST\");\n  request->setURL(\"https://test.facebook.com/continue\");\n  request->getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  request->getHeaders().add(HTTP_HEADER_HOST, \"test.facebook.com\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder(requestSource.get());\n\n  // The first header event should be handled by the wrapper\n  // returning the 100 Continue response without triggering the\n  // wrapped mockHandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _)).Times(0);\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 100);\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The second header event should be returned from the mockhandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _))\n      .Times(1)\n      .WillRepeatedly(folly::coro::gmock_helpers::CoInvoke(\n          [](folly::EventBase *evb,\n             HTTPSessionContextPtr ctx,\n             HTTPSourceHolder requestSourceHolder)\n              -> folly::coro::Task<HTTPSourceHolder> {\n            // Make sure mock handler receives original request but with expect\n            // header removed\n            auto propagatedHeader =\n                co_await requestSourceHolder.readHeaderEvent();\n            EXPECT_EQ(propagatedHeader.headers->getHeaders().getSingleOrEmpty(\n                          HTTP_HEADER_EXPECT),\n                      \"\");\n            EXPECT_EQ(propagatedHeader.headers->getHeaders().getSingleOrEmpty(\n                          HTTP_HEADER_HOST),\n                      \"test.facebook.com\");\n            EXPECT_TRUE(propagatedHeader.eom);\n\n            co_return HTTPFixedSource::makeFixedResponse(200, \"MOCK BODY\");\n          }));\n  auto headerEvent2 = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent2.headers->getStatusCode(), 200);\n  CO_ASSERT_FALSE(headerEvent2.eom);\n\n  // The response body event should be come from the mockHandler\n  auto bodyEvent = co_await responseHolder.readBodyEvent();\n  auto bodyStr = bodyEvent.event.body.move()->moveToFbString();\n  CO_ASSERT_EQ(bodyStr, \"MOCK BODY\");\n  CO_ASSERT_TRUE(bodyEvent.eom);\n}\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest, NoExpectation) {\n  InSequence seq;\n\n  // Create a request without an expectation\n  auto requestSource = HTTPFixedSource::makeFixedRequest(\n      \"https://test.facebook.com\", HTTPMethod::POST);\n  HTTPSourceHolder requestSourceHolder(requestSource);\n\n  // The header event should be returned from the mockhandler directly.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _))\n      .Times(1)\n      .WillRepeatedly(folly::coro::gmock_helpers::CoInvoke(\n          [](folly::EventBase *evb,\n             HTTPSessionContextPtr ctx,\n             HTTPSourceHolder source) -> folly::coro::Task<HTTPSourceHolder> {\n            co_return HTTPFixedSource::makeFixedResponse(200, \"MOCK BODY\");\n          }));\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 200);\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The body event should be returned from the mockhandler directly.\n  auto bodyEvent = co_await responseHolder.readBodyEvent();\n  auto bodyStr = bodyEvent.event.body.move()->moveToFbString();\n  CO_ASSERT_EQ(bodyStr, \"MOCK BODY\");\n  CO_ASSERT_TRUE(bodyEvent.eom);\n}\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest, InvalidExpectation) {\n  InSequence seq;\n\n  // Create a request with an invalid expectation\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"POST\");\n  request->setURL(\"https://test.facebook.com/continue\");\n  request->getHeaders().add(HTTP_HEADER_EXPECT, \"blah\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder(requestSource.get());\n\n  // The mockHandler should never be called.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _)).Times(0);\n\n  // The wrapper should respond directly with 417.\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 417);\n  CO_ASSERT_EQ(headerEvent.headers->getStatusMessage(), \"Expectation Failed\");\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The body event should be returned from the mockhandler directly.\n  auto bodyEvent = co_await responseHolder.readBodyEvent();\n  auto bodyStr = bodyEvent.event.body.move()->moveToFbString();\n  CO_ASSERT_EQ(bodyStr, \"Expectation Failed\");\n  CO_ASSERT_TRUE(bodyEvent.eom);\n}\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest,\n          StopReadingBeforeInvokeRequestWithoutBody) {\n  InSequence seq;\n\n  // Create a request with Expect: 100-continue header\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"POST\");\n  request->setURL(\"https://test.facebook.com/continue\");\n  request->getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder(requestSource.get());\n\n  // The first header event should be handled by the wrapper\n  // returning the 100 Continue response without triggering the\n  // wrapped mockHandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _)).Times(0);\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 100);\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The wrapped mock handler has not been invoked yet.\n  // Stop reading and make sure this is handled gracefully.\n  EXPECT_NO_THROW(responseHolder.stopReading());\n}\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest,\n          StopReadingBeforeInvokeRequestWithBody) {\n  InSequence seq;\n\n  // Create a request with Expect: 100-continue header and a body\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"POST\");\n  request->setURL(\"https://test.facebook.com/continue\");\n  request->getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  auto body = folly::IOBuf::copyBuffer(\"REQUEST BODY\");\n  auto requestSource =\n      std::make_unique<HTTPFixedSource>(std::move(request), std::move(body));\n  HTTPSourceHolder requestSourceHolder(requestSource.get());\n\n  // The first header event should be handled by the wrapper\n  // returning the 100 Continue response without triggering the\n  // wrapped mockHandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _)).Times(0);\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 100);\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The wrapped mock handler has not been invoked yet.\n  // Stop reading and make sure this is handled gracefully.\n  EXPECT_NO_THROW(responseHolder.stopReading());\n}\n\nCO_TEST_F(ExpectContinueWrapperHandlerTest, StopReadingAfterInvoke) {\n  InSequence seq;\n\n  // Create a request with Expect: 100-continue header\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"POST\");\n  request->setURL(\"https://test.facebook.com/continue\");\n  request->getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder(requestSource.get());\n\n  // The first header event should be handled by the wrapper\n  // returning the 100 Continue response without triggering the\n  // wrapped mockHandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _)).Times(0);\n  auto responseHolder = co_await expectContinueWrapper_->handleRequest(\n      &evb_,\n      httpSessionContext_.acquireKeepAlive(),\n      std::move(requestSourceHolder));\n  auto headerEvent = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent.headers->getStatusCode(), 100);\n  CO_ASSERT_FALSE(headerEvent.eom);\n\n  // The second header event should be returned from the mockhandler.\n  EXPECT_CALL(*mockHandler_, handleRequest(_, _, _))\n      .Times(1)\n      .WillRepeatedly(folly::coro::gmock_helpers::CoInvoke(\n          [](folly::EventBase *evb,\n             HTTPSessionContextPtr ctx,\n             HTTPSourceHolder source) -> folly::coro::Task<HTTPSourceHolder> {\n            co_return HTTPFixedSource::makeFixedResponse(200, \"MOCK BODY\");\n          }));\n  auto headerEvent2 = co_await responseHolder.readHeaderEvent();\n  CO_ASSERT_EQ(headerEvent2.headers->getStatusCode(), 200);\n  CO_ASSERT_FALSE(headerEvent2.eom);\n\n  // The wrapped mock handler has been invoked but message is not over yet.\n  // Stop reading and make sure this is handled gracefully.\n  EXPECT_NO_THROW(responseHolder.stopReading());\n}\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/samples/echo/EchoServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/server/HTTPServer.h\"\n#include <folly/logging/xlog.h>\n// TODO: move handler to it's own file?\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include <quic/QuicConstants.h>\n\n#include <folly/init/Init.h>\n#include <folly/portability/GFlags.h>\n\nDEFINE_int32(port, 80, \"What port to listen on\");\nDEFINE_string(cert, \"\", \"Certificate file\");\nDEFINE_string(key, \"\", \"Key file\");\nDEFINE_string(plaintext_proto, \"\", \"Plaintext protocol\");\nDEFINE_bool(quic, false, \"Enable QUIC (requires cert/key)\");\nDEFINE_uint32(quic_batching_mode,\n              static_cast<uint32_t>(quic::QuicBatchingMode::BATCHING_MODE_NONE),\n              \"QUIC batching mode\");\nDEFINE_uint32(quic_batch_size,\n              quic::kDefaultQuicMaxBatchSize,\n              \"Maximum number of packets that can be batched in Quic\");\nDEFINE_int32(threads, 1, \"Number of HTTPServer IO threads\");\n\nusing namespace proxygen::coro;\nusing namespace proxygen;\n\nnamespace {\n\nclass EchoResponse : public HTTPSource {\n public:\n  explicit EchoResponse(HTTPSourceHolder request)\n      : request_(std::move(request)) {\n    setHeapAllocated(); // EchoResponse is always heap allocated\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    auto headerEvent = co_await co_awaitTry(request_.readHeaderEvent());\n    auto guard = folly::makeGuard(lifetime(headerEvent));\n    if (headerEvent.hasException()) {\n      co_yield folly::coro::co_error(\n          HTTPError(HTTPErrorCode::CANCEL, \"input error\"));\n    }\n    auto path = headerEvent->headers->getPathAsStringPiece();\n    if (path == \"/push\") {\n      if (headerEvent->eom) {\n        XLOG(ERR) << \"Can only push in reply to POST\";\n      }\n      pendingPush_ = true;\n    }\n    auto msg = std::make_unique<HTTPMessage>();\n    msg->setStatusCode(200);\n    headerEvent->headers->getHeaders().forEach(\n        [&msg](const std::string& name, const std::string& value) {\n          msg->getHeaders().set(folly::to<std::string>(\"x-echo-\", name), value);\n        });\n    co_return HTTPHeaderEvent(std::move(msg), headerEvent->eom);\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    if (pendingPush_) {\n      pendingPush_ = false;\n      auto promise = std::make_unique<HTTPMessage>();\n      promise->setURL(\"/\");\n      promise->getHeaders().set(HTTP_HEADER_HOST, \"foo.com\");\n      co_return HTTPBodyEvent(\n          std::move(promise),\n          HTTPFixedSource::makeFixedResponse(\n              200, folly::IOBuf::copyBuffer(\"push it real good\")),\n          false);\n    }\n    auto bodyEvent = co_await co_awaitTry(request_.readBodyEvent(max));\n    auto guard = folly::makeGuard(lifetime(bodyEvent));\n    if (bodyEvent.hasException()) {\n      co_yield folly::coro::co_error(\n          HTTPError(HTTPErrorCode::CANCEL, \"input error\"));\n    }\n    co_return std::move(*bodyEvent);\n  }\n\n  void stopReading(\n      folly::Optional<const proxygen::coro::HTTPErrorCode>) override {\n    // This could run into trouble if there is an active guard on the stack?\n    delete this;\n  }\n\n private:\n  HTTPSourceHolder request_;\n  bool pendingPush_{false};\n};\n\nclass EchoHandler\n    : public HTTPHandler\n    , public HTTPServer::Observer {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* /*evb*/,\n      HTTPSessionContextPtr /*ctx*/,\n      HTTPSourceHolder requestSource) override {\n    co_return new EchoResponse(std::move(requestSource));\n  }\n  void onThreadStart(folly::EventBase* /*evb*/) override {\n    XLOG(DBG2) << \"Thread started\";\n  }\n  void onThreadStop(folly::EventBase* /*evb*/) override {\n    XLOG(DBG2) << \"Thread stopped\";\n  }\n};\n\n} // namespace\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  HTTPServer::Config httpServerConfig;\n  httpServerConfig.socketConfig.bindAddress.setFromLocalPort(FLAGS_port);\n  httpServerConfig.plaintextProtocol = FLAGS_plaintext_proto;\n  httpServerConfig.numIOThreads = FLAGS_threads;\n\n  if (!FLAGS_cert.empty()) {\n    auto tlsConfig = HTTPServer::getDefaultTLSConfig();\n    try {\n      tlsConfig.setCertificate(FLAGS_cert, FLAGS_key, \"\");\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n    }\n    httpServerConfig.socketConfig.sslContextConfigs.emplace_back(\n        std::move(tlsConfig));\n    if (FLAGS_quic) {\n      httpServerConfig.quicConfig = HTTPServer::QuicConfig();\n      httpServerConfig.quicConfig->transportSettings.batchingMode =\n          quic::getQuicBatchingMode(FLAGS_quic_batching_mode);\n      httpServerConfig.quicConfig->transportSettings.maxBatchSize =\n          FLAGS_quic_batch_size;\n    }\n  } else if (FLAGS_quic) {\n    XLOG(ERR) << \"QUIC requires a cert and key\";\n    return 1;\n  }\n  auto handler = std::make_shared<EchoHandler>();\n  HTTPServer server(std::move(httpServerConfig), handler);\n  server.addObserver(handler.get());\n  server.start();\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/samples/fwdproxy/ConnectSource.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include <proxygen/lib/utils/ConditionalGate.h>\n\nnamespace proxygen::coro {\n\nclass ConnectSource : public HTTPSource {\n public:\n  ConnectSource(std::unique_ptr<folly::coro::Transport> transport,\n                HTTPSourceHolder requestSource)\n      : transport_(std::move(transport)), reqSource_(std::move(requestSource)) {\n    setHeapAllocated();\n    gate_.then([this] {\n      transport_->close();\n      delete this;\n    });\n  }\n\n  // Read from the HTTP request and write on the upstream transport\n  folly::coro::Task<void> readRequestSendUpstream() {\n    auto guard =\n        folly::makeGuard([this] { gate_.set(Event::ReadRequestComplete); });\n    bool eom = false;\n    do {\n      auto bodyEvent = co_await co_awaitTry(reqSource_.readBodyEvent(65535));\n      if (bodyEvent.hasException()) {\n        XLOG(ERR) << \"read exception:\" << bodyEvent.exception().what();\n        // This will break the server read loop\n        transport_->closeWithReset();\n        co_return;\n      }\n      if (bodyEvent->eventType == HTTPBodyEvent::EventType::BODY &&\n          !bodyEvent->event.body.empty()) {\n        folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n        writeBuf.append(bodyEvent->event.body.move());\n        XLOG(DBG2) << \"Got \" << writeBuf.chainLength() << \" client bytes\";\n        try {\n          co_await transport_->write(writeBuf,\n                                     std::chrono::milliseconds(writeTimeout_));\n        } catch (const std::exception& ex) {\n          XLOG(ERR) << \"Upstream write error: \" << ex.what();\n          transport_->closeWithReset();\n          co_return;\n        }\n      } // else ignore any other events\n      eom = bodyEvent->eom;\n    } while (!eom);\n    XLOG(DBG2) << \"Client read finished\";\n    transport_->shutdownWrite();\n  }\n\n  // HTTPSource overrides\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    // Send 200 Connected\n    auto resp = std::make_unique<HTTPMessage>();\n    resp->setHTTPVersion(1, 1);\n    resp->setStatusCode(200);\n    resp->setStatusMessage(\"Connected\");\n    co_return HTTPHeaderEvent(std::move(resp), false);\n  }\n\n  // Read from the upstream socket and return as HTTPBodyEvents\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    folly::IOBufQueue readBuf(folly::IOBufQueue::cacheChainLength());\n    inRead_ = true;\n    auto nread = co_await co_awaitTry(folly::coro::co_withCancellation(\n        cancellationSource_.getToken(),\n        transport_->read(readBuf, 1500, 4096, readTimeout_)));\n    inRead_ = false;\n    if (nread.hasException()) {\n      XLOG(ERR) << \"read error: \" << nread.exception().what();\n      gate_.set(Event::WriteResponseComplete);\n      co_yield folly::coro::co_error(std::move(nread.exception()));\n    }\n    XLOG(DBG2) << \"Got \" << *nread << \" server bytes\";\n    if (*nread == 0) {\n      gate_.set(Event::WriteResponseComplete);\n    }\n    co_return HTTPBodyEvent(readBuf.move(), *nread == 0);\n  }\n\n  void stopReading(folly::Optional<const HTTPErrorCode>) override {\n    if (inRead_) {\n      cancellationSource_.requestCancellation();\n    } else {\n      gate_.set(Event::WriteResponseComplete);\n    }\n  }\n\n  void setReadTimeout(std::chrono::milliseconds timeout) override {\n    readTimeout_ = timeout;\n  }\n  void setWriteTimeout(std::chrono::milliseconds timeout) {\n    writeTimeout_ = timeout;\n  }\n\n private:\n  std::unique_ptr<folly::coro::Transport> transport_;\n  HTTPSourceHolder reqSource_;\n  enum class Event { ReadRequestComplete, WriteResponseComplete };\n  ConditionalGate<Event, 2> gate_;\n  static constexpr auto kDefaultTimeout = std::chrono::milliseconds{5'000};\n  std::chrono::milliseconds readTimeout_{kDefaultTimeout};\n  std::chrono::milliseconds writeTimeout_{kDefaultTimeout};\n  folly::CancellationSource cancellationSource_;\n  bool inRead_{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/samples/fwdproxy/FwdProxyServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n#include \"proxygen/lib/http/coro/client/CoroDNSResolver.h\"\n#include \"proxygen/lib/http/coro/client/HTTPClientConnectionCache.h\"\n#include \"proxygen/lib/http/coro/server/HTTPServer.h\"\n#include \"proxygen/lib/http/coro/server/samples/fwdproxy/ConnectSource.h\"\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBaseLocal.h>\n#include <folly/logging/xlog.h>\n#include <folly/portability/GFlags.h>\n#include <memory>\n\nDEFINE_int32(port, 8082, \"What port to listen on\");\nDEFINE_string(cert, \"\", \"Certificate file\");\nDEFINE_string(key, \"\", \"Key file\");\nDEFINE_string(plaintext_proto, \"\", \"Plaintext protocol\");\nDEFINE_bool(quic, false, \"Enable QUIC (requires cert/key)\");\nDEFINE_int32(timeout, 5000, \"Uber timeout\");\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nnamespace {\n\nclass FwdProxyHandler : public proxygen::coro::HTTPHandler {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr /*ctx*/,\n      HTTPSourceHolder requestSource) override {\n    auto headerEvent = co_await co_awaitTry(requestSource.readHeaderEvent());\n    if (headerEvent.hasException()) {\n      co_return HTTPFixedSource::makeFixedResponse(400);\n    }\n    auto& request = headerEvent->headers;\n    auto parseURL = ParseURL::parseURL(request->getURL());\n    if (!parseURL.has_value() || !parseURL->hasHost()) {\n      XLOG(ERR) << \"Invalid url=\" << request->getURL();\n      co_return HTTPFixedSource::makeFixedResponse(400);\n    }\n\n    // Handle connect separately\n    if (request->getMethod() == HTTPMethod::CONNECT) {\n      if (parseURL->port() == 0) {\n        XLOG(ERR) << \"Invalid url=\" << request->getURL();\n        co_return HTTPFixedSource::makeFixedResponse(400);\n      }\n      co_return co_await handleConnect(evb,\n                                       std::string(parseURL->host()),\n                                       parseURL->port(),\n                                       std::move(*headerEvent),\n                                       std::move(requestSource));\n    }\n\n    if (parseURL->scheme().empty()) {\n      XLOG(ERR) << \"Invalid url=\" << request->getURL();\n      co_return HTTPFixedSource::makeFixedResponse(400);\n    }\n\n    XLOG(DBG4) << \"Sending request upstream\";\n    // any exceptions are propagated\n    auto& connCache = connCache_.try_emplace(*evb, *evb);\n    auto res = co_await co_awaitTry(connCache.getSessionWithReservation(\n        request->getURL(), std::chrono::milliseconds(FLAGS_timeout)));\n    if (res.hasException()) {\n      XLOG(ERR) << \"Failed to connect err=\" << res.exception().what();\n      co_return HTTPFixedSource::makeFixedResponse(503);\n    }\n    URL reqURL(std::move(*parseURL));\n    request->setURL(reqURL.makeRelativeURL());\n    request->getHeaders().set(HTTP_HEADER_HOST,\n                              reqURL.getHostAndPortOmitDefault());\n    auto newSource = new HTTPHybridSource(std::move(headerEvent->headers),\n                                          std::move(requestSource));\n    newSource->setHeapAllocated();\n    co_return co_await res->session->sendRequest(newSource,\n                                                 std::move(res->reservation));\n  }\n\n  folly::coro::Task<HTTPSourceHolder> handleConnect(\n      folly::EventBase* evb,\n      std::string host,\n      uint16_t port,\n      HTTPHeaderEvent headerEvent,\n      HTTPSourceHolder requestSource) {\n    auto serverAddresses = co_await co_awaitTry(CoroDNSResolver::resolveHost(\n        evb, host, std::chrono::milliseconds(FLAGS_timeout)));\n    if (serverAddresses.hasException()) {\n      XLOG(ERR) << \"DNS error: \" << serverAddresses.exception().what();\n      co_return HTTPFixedSource::makeFixedResponse(503);\n    }\n    serverAddresses->primary.setPort(port);\n    auto transport =\n        co_await co_awaitTry(folly::coro::Transport::newConnectedSocket(\n            evb,\n            serverAddresses->primary,\n            std::chrono::milliseconds(FLAGS_timeout)));\n    if (transport.hasException()) {\n      XLOG(ERR) << \"Connect error: \" << transport.exception().what();\n      co_return HTTPFixedSource::makeFixedResponse(503);\n    }\n    auto connectSource = new ConnectSource(\n        std::make_unique<folly::coro::Transport>(std::move(*transport)),\n        std::move(requestSource));\n    connectSource->setWriteTimeout(std::chrono::milliseconds(FLAGS_timeout));\n    if (!headerEvent.eom) {\n      co_withExecutor(evb, connectSource->readRequestSendUpstream()).start();\n    }\n    co_return connectSource;\n  }\n\n private:\n  folly::EventBaseLocal<HTTPClientConnectionCache> connCache_;\n};\n\n} // namespace\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  HTTPServer::Config httpServerConfig;\n  httpServerConfig.socketConfig.bindAddress.setFromLocalPort(FLAGS_port);\n  httpServerConfig.plaintextProtocol = FLAGS_plaintext_proto;\n\n  if (!FLAGS_cert.empty()) {\n    auto tlsConfig = HTTPServer::getDefaultTLSConfig();\n    try {\n      tlsConfig.setCertificate(FLAGS_cert, FLAGS_key, \"\");\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n    }\n    httpServerConfig.socketConfig.sslContextConfigs.emplace_back(\n        std::move(tlsConfig));\n    if (FLAGS_quic) {\n      httpServerConfig.quicConfig = HTTPServer::QuicConfig();\n    }\n  } else if (FLAGS_quic) {\n    XLOG(ERR) << \"QUIC requires a cert and key\";\n    return 1;\n  }\n  HTTPServer server(std::move(httpServerConfig),\n                    std::make_shared<FwdProxyHandler>());\n  server.start();\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/samples/proxy/ProxyServer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include \"proxygen/lib/http/coro/server/HTTPServer.h\"\n#include <folly/init/Init.h>\n#include <folly/logging/xlog.h>\n#include <folly/portability/GFlags.h>\n#include <memory>\n\nDEFINE_int32(port, 8082, \"What port to listen on\");\nDEFINE_string(cert, \"\", \"Certificate file\");\nDEFINE_string(key, \"\", \"Key file\");\nDEFINE_string(plaintext_proto, \"\", \"Plaintext protocol\");\nDEFINE_bool(quic, false, \"Enable QUIC (requires cert/key)\");\n\nDEFINE_string(backend_server, \"::1\", \"Backend server address\");\nDEFINE_int32(backend_port, 80, \"Backend server port\");\nDEFINE_bool(backend_tls, false, \"Backend server TLS (y/n)\");\nDEFINE_bool(fbssl, false, \"Add FBSSL: ON\");\n// TODO: backend QUIC?\n\nusing namespace proxygen;\nusing namespace proxygen::coro;\n\nnamespace {\nstatic HTTPCoroSessionPool& getPool(folly::EventBase* evb) {\n  if (FLAGS_backend_tls) {\n    static auto connParams = HTTPCoroConnector::defaultConnectionParams();\n    connParams.fizzContextAndVerifier =\n        HTTPCoroConnector::makeFizzClientContextAndVerifier(\n            HTTPCoroConnector::defaultTLSParams());\n    static HTTPCoroSessionPool pool(evb,\n                                    FLAGS_backend_server,\n                                    FLAGS_backend_port,\n                                    HTTPCoroSessionPool::PoolParams(),\n                                    connParams);\n    return pool;\n  } else {\n    static HTTPCoroSessionPool pool(\n        evb, FLAGS_backend_server, FLAGS_backend_port);\n    return pool;\n  }\n}\n\nclass ProxyHandler : public proxygen::coro::HTTPHandler {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr /*ctx*/,\n      HTTPSourceHolder requestSource) override {\n    auto headerEvent = co_await co_awaitTry(requestSource.readHeaderEvent());\n    if (headerEvent.hasException()) {\n      co_return HTTPFixedSource::makeFixedResponse(400);\n    }\n\n    auto& pool = getPool(evb);\n    XLOG(DBG4) << \"Sending request upstream\";\n    if (FLAGS_fbssl && !FLAGS_backend_tls) {\n      headerEvent->headers->getHeaders().add(\"FBSSL\", \"ON\");\n    }\n    // any exceptions are propagated\n    auto res = co_await co_awaitTry(pool.getSessionWithReservation());\n    if (res.hasException()) {\n      XLOG(ERR)\n          << \"Failed to connect err=\"\n          << uint64_t(\n                 res.tryGetExceptionObject<HTTPCoroSessionPool::Exception>()\n                     ->type);\n      co_return HTTPFixedSource::makeFixedResponse(503);\n    }\n    co_return co_await res->session->sendRequest(\n        new HTTPHybridSource(std::move(headerEvent->headers),\n                             std::move(requestSource)),\n        std::move(res->reservation));\n  }\n};\n\n} // namespace\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  HTTPServer::Config httpServerConfig;\n  httpServerConfig.socketConfig.bindAddress.setFromLocalPort(FLAGS_port);\n  httpServerConfig.plaintextProtocol = FLAGS_plaintext_proto;\n\n  if (!FLAGS_cert.empty()) {\n    auto tlsConfig = HTTPServer::getDefaultTLSConfig();\n    try {\n      tlsConfig.setCertificate(FLAGS_cert, FLAGS_key, \"\");\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n    }\n    httpServerConfig.socketConfig.sslContextConfigs.emplace_back(\n        std::move(tlsConfig));\n    if (FLAGS_quic) {\n      httpServerConfig.quicConfig = HTTPServer::QuicConfig();\n    }\n  } else if (FLAGS_quic) {\n    XLOG(ERR) << \"QUIC requires a cert and key\";\n    return 1;\n  }\n  HTTPServer server(std::move(httpServerConfig),\n                    std::make_shared<ProxyHandler>());\n  server.start();\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/test/HTTPServerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <folly/system/HardwareConcurrency.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/coro/HTTPCoroSession.h>\n#include <proxygen/lib/http/coro/HTTPFixedSource.h>\n#include <proxygen/lib/http/coro/HTTPSourceReader.h>\n#include <proxygen/lib/http/coro/client/HTTPClient.h>\n#include <proxygen/lib/http/coro/filters/ServerFilterFactory.h>\n#include <proxygen/lib/http/coro/filters/StatsFilterUtil.h>\n#include <proxygen/lib/http/coro/filters/test/FakeServerStats.h>\n#include <proxygen/lib/http/coro/server/HTTPServer.h>\n#include <proxygen/lib/http/coro/server/ScopedHTTPServer.h>\n#include <proxygen/lib/http/coro/test/HTTPTestSources.h>\n\n#include <chrono>\n#include <folly/coro/GtestHelpers.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <folly/portability/Sockets.h>\n#include <folly/testing/TestUtil.h>\n#include <proxygen/lib/http/coro/test/TestUtils.h>\n#include <quic/api/test/Mocks.h>\n#include <quic/logging/test/Mocks.h>\n#include <quic/state/test/MockQuicStats.h>\n#include <quic/state/test/Mocks.h>\n#include <wangle/acceptor/FizzAcceptorHandshakeHelper.h>\n\nusing namespace proxygen::coro;\nusing folly::coro::blockingWait;\n\nnamespace {\n\nstd::string_view getTestDir() {\n  static const std::string kTestDir =\n      getContainingDirectory(XLOG_FILENAME).str();\n  return kTestDir;\n}\n\nstruct StatsFactory : public ServerFilterFactory {\n  std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override {\n    return StatsFilterUtil::makeFilters(&stats_);\n  }\n  void onServerStart(folly::EventBase*) noexcept override {\n  }\n  void onServerStop() noexcept override {\n  }\n  proxygen::FakeHTTPServerStats stats_;\n};\n\nclass MockFizzLoggingCallback : public wangle::FizzLoggingCallback {\n public:\n  MOCK_METHOD(void,\n              logFizzHandshakeSuccess,\n              (const fizz::server::AsyncFizzServer&,\n               const wangle::TransportInfo&),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              logFallbackHandshakeSuccess,\n              (const folly::AsyncSSLSocket&, const wangle::TransportInfo&),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              logFizzHandshakeFallback,\n              (const fizz::server::AsyncFizzServer&,\n               const wangle::TransportInfo&),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              logFizzHandshakeError,\n              (const fizz::server::AsyncFizzServer&,\n               const folly::exception_wrapper&),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              logFallbackHandshakeError,\n              (const folly::AsyncSSLSocket&,\n               const folly::AsyncSocketException&,\n               const fizz::server::HandshakeLogging*),\n              (noexcept, override));\n};\n\n} // namespace\n\nnamespace proxygen::coro::test {\n\nusing namespace folly;\nusing namespace testing;\n\nenum class TransportType { QUIC, TLS };\nstd::string transportTypeToString(testing::TestParamInfo<TransportType> type) {\n  switch (type.param) {\n    case TransportType::QUIC:\n      return \"quic\";\n    case TransportType::TLS:\n      return \"tls\";\n    default:\n      return \"undefined\";\n  }\n}\n\nclass TestHandler : public HTTPHandler {\n protected:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override {\n    HTTPSourceReader reader(std::move(requestSource));\n    co_await reader.read();\n    co_return HTTPFixedSource::makeFixedResponse(200, \"OK\");\n  }\n};\n\nclass MockServerObserver : public HTTPServer::Observer {\n public:\n  MOCK_METHOD((void), onThreadStart, (folly::EventBase*));\n  MOCK_METHOD((void), onThreadStop, (folly::EventBase*));\n};\n\nclass HTTPServerTests : public TestWithParam<TransportType> {\n protected:\n  void startServer(MockServerObserver* mockObserver = nullptr,\n                   bool expectRequest = false) {\n    serverConfig_.socketConfig.bindAddress.setFromIpPort(listenAddress_,\n                                                         listenPort_);\n    serverConfig_.socketConfig.sslContextConfigs.emplace_back(getTLSConfig());\n\n    if (GetParam() == TransportType::QUIC) {\n      serverConfig_.quicConfig = HTTPServer::QuicConfig();\n\n      // create mock qlogger and adjust sampling rate based on whether we expect\n      // a request\n      auto mockQLogger =\n          std::make_shared<quic::test::MockQLogger>(quic::VantagePoint::Server);\n      serverConfig_.quicConfig->qloggerSampling.updateRate(int(expectRequest));\n      EXPECT_CALL(*mockQLogger,\n                  addPacket(Matcher<const quic::RegularQuicPacket&>(_), _))\n          .Times(expectRequest ? AtLeast(1) : Exactly(0));\n      serverConfig_.quicConfig->qlogger = std::move(mockQLogger);\n\n      if (expectRequest) {\n        // add observer to verify attach callback & onPacketReceived\n        EXPECT_CALL(quicSocketObserver_, attached(_)).Times(1);\n        // quicSocketObserver_\n        EXPECT_CALL(quicSocketObserver_, packetsReceived(_, _))\n            .Times(AtLeast(1));\n        serverConfig_.quicConfig->observers.push_back(&quicSocketObserver_);\n\n        // expect invocations on QuicStats CongestionControllerFactory mocks\n        auto testFactory = std::make_unique<quic::MockQuicStatsFactory>();\n        auto ccFactory =\n            std::make_shared<quic::test::MockCongestionControllerFactory>();\n        EXPECT_CALL(*ccFactory, makeCongestionController(_, _))\n            .Times(AtLeast(1))\n            .WillRepeatedly(Invoke([](quic::QuicConnectionStateBase& conn,\n                                      quic::CongestionControlType ccType) {\n              return quic::DefaultCongestionControllerFactory()\n                  .makeCongestionController(conn, ccType);\n            }));\n        ON_CALL(*testFactory, make()).WillByDefault(testing::Invoke([]() {\n          auto mockQuicStats = std::make_unique<quic::MockQuicStats>();\n          EXPECT_CALL(*mockQuicStats, onPacketReceived()).Times(AtLeast(1));\n          EXPECT_CALL(*mockQuicStats, onNewConnection()).Times(AtLeast(1));\n          EXPECT_CALL(*mockQuicStats, onNewQuicStream()).Times(AtLeast(1));\n          return mockQuicStats;\n        }));\n        serverConfig_.quicConfig->statsFactory = std::move(testFactory);\n        serverConfig_.quicConfig->ccFactory = std::move(ccFactory);\n      }\n    }\n\n    CHECK(handler_);\n    server_ = ScopedHTTPServer::start(\n        std::move(serverConfig_), handler_, mockObserver);\n  }\n\n  void stopServer() {\n    server_.reset();\n  }\n\n  wangle::SSLContextConfig getTLSConfig() {\n    auto tlsConfig = HTTPServer::HTTPServer::getDefaultTLSConfig();\n    tlsConfig.isDefault = true;\n    tlsConfig.clientVerification =\n        folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n    tlsConfig.setNextProtocols({\"h2\", \"http/1.1\"});\n    try {\n      const std::string kTestDir{getTestDir()};\n      tlsConfig.setCertificate(kTestDir + \"certs/test_cert1.pem\",\n                               kTestDir + \"certs/test_key1.pem\",\n                               \"\");\n    } catch (const std::exception& ex) {\n      XLOG(ERR) << \"Invalid certificate file or key file: %s\" << ex.what();\n    }\n    return tlsConfig;\n  }\n\n  void initClient() {\n    HTTPClient::setDefaultCAPaths({});\n    HTTPClient::setDefaultFizzCertVerifier(\n        std::make_shared<InsecureVerifierDangerousDoNotUseInProduction>());\n  }\n\n  std::string listenAddress_{\"127.0.0.1\"};\n  uint16_t listenPort_{0};\n  std::shared_ptr<HTTPHandler> handler_{std::make_shared<TestHandler>()};\n\n  std::unique_ptr<ScopedHTTPServer> server_;\n  HTTPServer::Config serverConfig_;\n  quic::MockObserver quicSocketObserver_{\n      quic::ManagedObserver::EventSetBuilder()\n          .enable(quic::ManagedObserver::Events::packetsReceivedEvents)\n          .build()};\n};\n\nTEST_P(HTTPServerTests, TestBasic) {\n  // `expectRequest=true` will use the default sampling of one. We then send a\n  // request to validate `.addPacket()` is invoked on mocked qlogger\n  startServer(nullptr, /*expectRequest=*/true);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestExistingSocket) {\n  AsyncServerSocket::UniquePtr serverSocket(new folly::AsyncServerSocket());\n  serverSocket->bind(0);\n  serverConfig_.preboundSocket = serverSocket->getNetworkSocket().toFd();\n\n  auto useQuic = GetParam() == TransportType::QUIC;\n  // still call startServer for TransportType::QUIC, preboundSocket\n  // should have no effect\n  if (useQuic) {\n    startServer(nullptr, /*expectRequest=*/true);\n  } else {\n    serverConfig_.socketConfig.sslContextConfigs.emplace_back(getTLSConfig());\n    server_ =\n        ScopedHTTPServer::start(std::move(serverConfig_), handler_, nullptr);\n  }\n\n  initClient();\n\n  auto address = server_->address();\n  CHECK(address.has_value());\n  auto url = fmt::format(\"https://{}/test\", address->describe());\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(\n          &evb, url, std::chrono::milliseconds(500), useQuic /* useQuic */),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestStopMultipleTimes) {\n  MockServerObserver mockObserver;\n  serverConfig_.numIOThreads = 4;\n  EXPECT_CALL(mockObserver, onThreadStart(_))\n      .Times(folly::to<int>(serverConfig_.numIOThreads));\n  EXPECT_CALL(mockObserver, onThreadStop(_))\n      .Times(folly::to<int>(serverConfig_.numIOThreads));\n\n  startServer(&mockObserver);\n  server_->getServer().drain();\n  server_->getServer().forceStop();\n  // Draining or stopping an already stopped server should be safe\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestZeroThreadsMeansNumCPUs) {\n  MockServerObserver mockObserver;\n  serverConfig_.numIOThreads = 0;\n  auto cpuCount = folly::available_concurrency();\n  EXPECT_CALL(mockObserver, onThreadStart(_)).Times(folly::to<int>(cpuCount));\n  EXPECT_CALL(mockObserver, onThreadStop(_)).Times(folly::to<int>(cpuCount));\n\n  startServer(&mockObserver);\n  EXPECT_EQ(server_->getServer().getConfig().numIOThreads, cpuCount);\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestSampling) {\n  // `expectRequest=false` will set the sampling rate to zero. We then send a\n  // request to validate `.addPacket()` is never invoked on mocked qlogger.\n  startServer(nullptr, /*expectRequest=*/false);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestFilterPass) {\n  serverConfig_.newConnectionFilter =\n      [](const folly::SocketAddress* /* address */,\n         const folly::AsyncTransportCertificate* /*peerCert*/,\n         const std::string& /* nextProtocolName */,\n         SecureTransportType /* secureTransportType */,\n         const wangle::TransportInfo& /* tinfo */) { return true; };\n  startServer(nullptr, /*expectRequest=*/true);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestFilterFail) {\n  serverConfig_.newConnectionFilter =\n      [](const folly::SocketAddress* /* address */,\n         const folly::AsyncTransportCertificate* /*peerCert*/,\n         const std::string& /* nextProtocolName */,\n         SecureTransportType /* secureTransportType */,\n         const wangle::TransportInfo& /* tinfo */) { return false; };\n  startServer(nullptr, /*expectRequest=*/false);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response =\n      blockingWait(co_awaitTry(HTTPClient::get(\n                       &evb, url, std::chrono::milliseconds(500), useQuic)),\n                   &evb);\n  EXPECT_TRUE(response.hasException());\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestFilterFailException) {\n  serverConfig_.newConnectionFilter =\n      [](const folly::SocketAddress* /* address */,\n         const folly::AsyncTransportCertificate* /*peerCert*/,\n         const std::string& /* nextProtocolName */,\n         SecureTransportType /* secureTransportType */,\n         const wangle::TransportInfo& /* tinfo */) -> bool {\n    throw std::runtime_error(\"filter failed this connection\");\n  };\n  startServer(nullptr, /*expectRequest=*/false);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response =\n      blockingWait(co_awaitTry(HTTPClient::get(\n                       &evb, url, std::chrono::milliseconds(500), useQuic)),\n                   &evb);\n  EXPECT_TRUE(response.hasException());\n  stopServer();\n}\n\nTEST_P(HTTPServerTests, TestFizzLoggingCallbackInvoked) {\n  auto mockFizzLoggingCallback = std::make_shared<MockFizzLoggingCallback>();\n  if (GetParam() == TransportType::TLS) {\n    EXPECT_CALL(*mockFizzLoggingCallback, logFizzHandshakeSuccess(_, _))\n        .Times(1);\n  }\n\n  serverConfig_.fizzLoggingCallback = std::move(mockFizzLoggingCallback);\n  startServer(nullptr, /*expectRequest=*/true);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n\n  stopServer();\n}\n\n/**\n * Unit test extrapolated from proxygen/httpserver/tests/HTTPServerTest.cpp\n */\nTEST_P(HTTPServerTests, TestUpdateTLSCredentials) {\n  // Set up a temporary file with credentials that we will update\n  folly::test::TemporaryFile credFile;\n  auto copyCreds = [path = credFile.path()](const std::string& certFile,\n                                            const std::string& keyFile) {\n    std::string certData, keyData;\n    folly::readFile(certFile.c_str(), certData);\n    folly::writeFile(certData, path.c_str(), O_WRONLY | O_CREAT | O_TRUNC);\n    folly::writeFile(std::string(\"\\n\"), path.c_str(), O_WRONLY | O_APPEND);\n    folly::readFile(keyFile.c_str(), keyData);\n    folly::writeFile(keyData, path.c_str(), O_WRONLY | O_APPEND);\n  };\n\n  auto getCertDigest = [&](const X509* x) -> std::string {\n    unsigned int n;\n    unsigned char md[EVP_MAX_MD_SIZE];\n    const EVP_MD* dig = EVP_sha256();\n\n    if (!X509_digest(x, dig, md, &n)) {\n      throw std::runtime_error(\"Cannot calculate digest\");\n    }\n    return std::string((const char*)md, n);\n  };\n\n  // init tmp file w/ cert1.pem & cert1.key\n  const std::string testDir{getTestDir()};\n  const std::string credFilePath{credFile.path().string()};\n\n  copyCreds(testDir + \"certs/test_cert1.pem\", testDir + \"certs/test_key1.pem\");\n  // set tlsConfig certificate to tmp file\n  auto tlsConfig = getTLSConfig();\n  tlsConfig.setCertificate(credFilePath, credFilePath, \"\");\n\n  // start server with custom tlsConfig constructed above\n  serverConfig_.socketConfig.bindAddress.setFromIpPort(listenAddress_,\n                                                       listenPort_);\n  serverConfig_.socketConfig.sslContextConfigs.emplace_back(\n      std::move(tlsConfig));\n  server_ =\n      ScopedHTTPServer::start(std::move(serverConfig_), handler_, nullptr);\n\n  struct TlsConnectCb : public folly::AsyncSocket::ConnectCallback {\n    TlsConnectCb(folly::AsyncSSLSocket& sock) : sock(sock) {\n    }\n    void connectSuccess() noexcept override {\n      if (auto cert = sock.getPeerCertificate()) {\n        peerCert = folly::OpenSSLTransportCertificate::tryExtractX509(cert);\n      }\n      baton.post();\n    }\n    void connectErr(\n        const folly::AsyncSocketException& socketEx) noexcept override {\n      ex = folly::make_exception_wrapper<folly::AsyncSocketException>(socketEx);\n      baton.post();\n    }\n\n    folly::AsyncSocket& sock;\n    folly::ssl::X509UniquePtr peerCert{nullptr};\n    folly::exception_wrapper ex;\n    folly::coro::Baton baton;\n  };\n\n  folly::EventBase evb;\n\n  // Connect and store digest of server cert\n  auto doTlsConnection = [&]() -> auto {\n    return folly::coro::co_invoke([&]() -> folly::coro::Task<std::string> {\n      folly::AsyncSSLSocket::UniquePtr sock(\n          new folly::AsyncSSLSocket(std::make_shared<SSLContext>(), &evb));\n      TlsConnectCb cb{*sock};\n      sock->connect(&cb, server_->address().value(), 100);\n      co_await cb.baton;\n      CHECK(!cb.ex && cb.peerCert);\n      co_return getCertDigest(cb.peerCert.get());\n    });\n  };\n\n  /**\n   * 1. do tls connection and get first certificate\n   * 2. update temp credFile & invoke HTTPServer::updateTlsCredentials\n   * 3. do new tls connection and get second certificate\n   * 4. verify first cert != second cert\n   */\n  auto cert1 = blockingWait(doTlsConnection(), &evb);\n\n  // update credFile to a different cert/key\n  copyCreds(testDir + \"certs/test_cert2.pem\", testDir + \"certs/test_key2.pem\");\n  auto& httpServer = server_->getServer();\n  httpServer.evb()->runImmediatelyOrRunInEventBaseThreadAndWait(\n      [&]() { httpServer.updateTlsCredentials(); });\n\n  // second tls connection post updating server cert file\n  auto cert2 = blockingWait(doTlsConnection(), &evb);\n\n  // verify certificates yielded from first and second tls conn attempts are\n  // different\n  EXPECT_EQ(cert1.length(), SHA256_DIGEST_LENGTH);\n  EXPECT_EQ(cert2.length(), SHA256_DIGEST_LENGTH);\n  EXPECT_NE(cert1, cert2);\n}\n\nINSTANTIATE_TEST_SUITE_P(HTTPServerStartStop,\n                         HTTPServerTests,\n                         testing::Values(TransportType::QUIC,\n                                         TransportType::TLS),\n                         transportTypeToString);\n\nclass HTTPStatsFilterTests : public HTTPServerTests {\n  void SetUp() override {\n    // makes this test easier with only one server evb\n    serverConfig_.numIOThreads = 1;\n\n    // store ref to server evb\n    EXPECT_CALL(mockObserver_, onThreadStart(_))\n        .Times(1)\n        .WillOnce([this](folly::EventBase* evb) { this->serverEvb_ = evb; });\n\n    statsFilterFactory_ = std::make_shared<StatsFactory>();\n    serverConfig_.filterFactories.push_back(statsFilterFactory_);\n  }\n\n protected:\n  class SourceFactoryTestHandler : public TestHandler {\n   public:\n    SourceFactoryTestHandler() = default;\n\n    folly::coro::Task<HTTPSourceHolder> handleRequest(\n        folly::EventBase* evb,\n        HTTPSessionContextPtr ctx,\n        HTTPSourceHolder requestSource) override {\n      auto result = co_await TestHandler::handleRequest(\n          evb, ctx, std::move(requestSource));\n      co_return sourceFactory ? sourceFactory() : std::move(result);\n    }\n\n    std::function<HTTPSource*(void)> sourceFactory{nullptr};\n  };\n\n  // used to get a ref to the server evb since server stats is thread local\n  MockServerObserver mockObserver_;\n  folly::EventBase* serverEvb_{nullptr};\n  std::shared_ptr<StatsFactory> statsFilterFactory_{nullptr};\n};\n\nTEST_P(HTTPStatsFilterTests, TestSimplePostStatsFilter) {\n  startServer(&mockObserver_, /*expectRequest=*/true);\n  initClient();\n\n  // send post request with random body\n  constexpr size_t bodyLen = 100'000;\n  auto body = makeBuf(bodyLen)->to<std::string>();\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::post(\n          &evb, url, std::move(body), std::chrono::milliseconds(500), useQuic),\n      &evb);\n  CHECK(response.headers.get());\n  auto statusCode = response.headers->getStatusCode();\n  EXPECT_EQ(statusCode, 200);\n\n  // stats is thread local and needs to run in server evb\n  CHECK_NOTNULL(serverEvb_)\n      ->runImmediatelyOrRunInEventBaseThreadAndWait(\n          [this, bodyLen, statusCode]() {\n            auto& fakeStats = statsFilterFactory_->stats_;\n            EXPECT_EQ(fakeStats.reqs, 1);\n            EXPECT_EQ(fakeStats.errors, 0);\n            EXPECT_EQ(fakeStats.reqBodyBytes, bodyLen);\n            EXPECT_EQ(fakeStats.responseCodes[statusCode / 100], 1);\n            // test handler returns OK -> 2 bytes\n            EXPECT_EQ(fakeStats.resBodyBytes, 2);\n          });\n\n  stopServer();\n}\n\nTEST_P(HTTPStatsFilterTests, TestServerErrorHandler) {\n  auto handler = std::make_shared<SourceFactoryTestHandler>();\n  handler->sourceFactory = []() {\n    return new ErrorSource(\n        /*body=*/makeBuf(200)->to<std::string>(),\n        /*client=*/false,\n        /*bytesTillTheError=*/0);\n  };\n  handler_ = std::move(handler);\n\n  startServer(&mockObserver_, /*expectRequest=*/true);\n  initClient();\n\n  // send post request with random body\n  constexpr uint16_t bodyLen = 300;\n  auto body = makeBuf(bodyLen)->to<std::string>();\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n\n  EventBase evb;\n  auto response = blockingWait(\n      co_awaitTry(HTTPClient::post(\n          &evb, url, std::move(body), std::chrono::milliseconds(500), useQuic)),\n      &evb);\n  EXPECT_TRUE(response.hasException());\n\n  // stats is thread local and needs to run in server evb\n  CHECK_NOTNULL(serverEvb_)\n      ->runImmediatelyOrRunInEventBaseThreadAndWait([this, bodyLen]() {\n        auto& fakeStats = statsFilterFactory_->stats_;\n        EXPECT_EQ(fakeStats.reqs, 1);\n        EXPECT_EQ(fakeStats.errors, 1);\n        EXPECT_EQ(fakeStats.reqBodyBytes, bodyLen);\n        // status code 200 at index 200/100 = 2\n        EXPECT_EQ(fakeStats.responseCodes[2], 1);\n        // in HTTPErrorCode2ProxygenError, internal error gets mapped to\n        // kErrorStreamAbort.\n        constexpr uint8_t expectedError = ProxygenError::kErrorStreamAbort;\n        EXPECT_EQ(fakeStats.errorTypes[expectedError], 1);\n        // error source bytesTillTheError = 0\n        EXPECT_EQ(fakeStats.resBodyBytes, 0);\n      });\n\n  stopServer();\n}\n\nINSTANTIATE_TEST_SUITE_P(HTTPStatsFilter,\n                         HTTPStatsFilterTests,\n                         testing::Values(TransportType::QUIC,\n                                         TransportType::TLS),\n                         transportTypeToString);\n\nclass MultiAcceptorHttpServerTest : public HTTPServerTests {\n protected:\n  void startServer(std::vector<folly::SocketAddress>&& addresses,\n                   MockServerObserver* mockObserver = nullptr) {\n    CHECK(handler_);\n    HTTPServer::SocketAcceptorConfigFactoryFn socketAcceptorConfigFactoryFn =\n        [this, addresses](folly::EventBase& evb,\n                          [[maybe_unused]] const HTTPServer::Config& config) {\n          auto tlsConfig = this->getTLSConfig();\n          std::vector<HTTPServer::SocketAcceptorConfig> configs;\n          auto accConfig = std::make_shared<proxygen::AcceptorConfiguration>();\n          accConfig->sslContextConfigs.push_back(tlsConfig);\n          for (const auto& address : addresses) {\n            folly::AsyncServerSocket::UniquePtr serverSocket(\n                new folly::AsyncServerSocket(&evb));\n            serverSocket->bind(address);\n            serverSocket->listen(1024);\n            configs.emplace_back(std::move(serverSocket), accConfig);\n          }\n          return configs;\n        };\n\n    server_ = ScopedHTTPServer::start(std::move(serverConfig_),\n                                      handler_,\n                                      mockObserver,\n                                      socketAcceptorConfigFactoryFn);\n  }\n};\n\nTEST_F(MultiAcceptorHttpServerTest, TestMultiplePorts) {\n  initClient();\n  std::vector<std::string> ips = {\"127.0.0.1\", \"::1\"};\n  std::vector<folly::SocketAddress> addresses;\n  addresses.reserve(ips.size());\n  for (const auto& ip : ips) {\n    addresses.emplace_back(ip, 0);\n  }\n  startServer(std::move(addresses));\n\n  auto boundAddresses = server_->addresses();\n  EXPECT_EQ(boundAddresses.size(), 2);\n  EXPECT_NE(boundAddresses[0].getPort(), boundAddresses[1].getPort());\n\n  EventBase evb;\n  for (const auto& address : boundAddresses) {\n    auto url = fmt::format(\"https://{}/test\", address.describe());\n    auto response = blockingWait(\n        HTTPClient::get(&evb, url, std::chrono::milliseconds(500), false),\n        &evb);\n    CHECK(response.headers.get());\n    EXPECT_EQ(response.headers->getStatusCode(), 200);\n  }\n\n  // Hit a bad port and confirm that we get an error.\n  auto badAddress = folly::SocketAddress(listenAddress_, 9999);\n  auto url = fmt::format(\"https://{}/test\", badAddress.describe());\n  auto result =\n      blockingWait(co_awaitTry(HTTPClient::get(\n                       &evb, url, std::chrono::milliseconds(500), false)),\n                   &evb);\n  EXPECT_TRUE(result.hasException());\n\n  stopServer();\n}\n\nTEST_F(MultiAcceptorHttpServerTest, TestShutdown) {\n  initClient();\n  std::vector<std::string> ips = {\"127.0.0.1\", \"::1\"};\n  std::vector<folly::SocketAddress> addresses;\n  addresses.reserve(ips.size());\n  for (const auto& ip : ips) {\n    addresses.emplace_back(ip, 0);\n  }\n  startServer(std::move(addresses));\n\n  auto boundAddresses = server_->addresses();\n  EXPECT_EQ(boundAddresses.size(), 2);\n\n  stopServer();\n\n  EventBase evb;\n  for (const auto& address : boundAddresses) {\n    auto url = fmt::format(\"https://{}/test\", address.describe());\n    auto result =\n        blockingWait(co_awaitTry(HTTPClient::get(\n                         &evb, url, std::chrono::milliseconds(500), false)),\n                     &evb);\n    EXPECT_TRUE(result.hasException());\n  }\n}\n\nTEST(StatsFilterFactory, LatencyOnDestruction) {\n  FakeHTTPServerStats stats;\n  auto filters = StatsFilterUtil::makeFilters(&stats);\n  delete filters.first;\n  delete filters.second;\n  EXPECT_EQ(stats.latencies.at(0).count(), 0);\n}\n\nclass ZeroCopyHTTPServerTest : public HTTPServerTests {};\n\n// Verify that configuring zero copy on the server does not break normal\n// request handling. This exercises the full path: Config -> startTcp() ->\n// createAcceptor() -> setZeroCopyEnableThreshold -> onNewConnection() ->\n// transport zero copy setup.\nTEST_P(ZeroCopyHTTPServerTest, TestZeroCopyConfigBasicRequest) {\n  serverConfig_.socketConfig.useZeroCopy = true;\n  serverConfig_.zeroCopyEnableThreshold = 32768;\n\n  startServer(nullptr, /*expectRequest=*/true);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\n// Verify that only socketConfig.useZeroCopy (without zeroCopyEnableThreshold)\n// also works. The server socket gets SO_ZEROCOPY but accepted connections\n// won't have the per-write threshold.\nTEST_P(ZeroCopyHTTPServerTest, TestZeroCopySocketOnlyBasicRequest) {\n  serverConfig_.socketConfig.useZeroCopy = true;\n  // No zeroCopyEnableThreshold set\n\n  startServer(nullptr, /*expectRequest=*/true);\n  initClient();\n\n  auto url = fmt::format(\"https://{}/test\", server_->address()->describe());\n  auto useQuic = GetParam() == TransportType::QUIC;\n  EventBase evb;\n  auto response = blockingWait(\n      HTTPClient::get(&evb, url, std::chrono::milliseconds(500), useQuic),\n      &evb);\n  EXPECT_NE(response.headers.get(), nullptr);\n  EXPECT_EQ(response.headers->getStatusCode(), 200);\n  stopServer();\n}\n\nINSTANTIATE_TEST_SUITE_P(ZeroCopy,\n                         ZeroCopyHTTPServerTest,\n                         testing::Values(TransportType::QUIC,\n                                         TransportType::TLS),\n                         transportTypeToString);\n\nnamespace {\n\n// Creates a pair of connected TCP sockets (server-side, client-side).\nstd::pair<folly::NetworkSocket, folly::NetworkSocket> createTcpSocketPair() {\n  int listenFd = ::socket(AF_INET, SOCK_STREAM, 0);\n  CHECK_GE(listenFd, 0);\n\n  struct sockaddr_in addr{};\n  addr.sin_family = AF_INET;\n  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n  addr.sin_port = 0;\n  CHECK_EQ(::bind(listenFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)),\n           0);\n  CHECK_EQ(::listen(listenFd, 1), 0);\n\n  socklen_t addrLen = sizeof(addr);\n  CHECK_EQ(\n      ::getsockname(listenFd, reinterpret_cast<sockaddr*>(&addr), &addrLen), 0);\n\n  int clientFd = ::socket(AF_INET, SOCK_STREAM, 0);\n  CHECK_GE(clientFd, 0);\n  CHECK_EQ(\n      ::connect(clientFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0);\n\n  int serverFd = ::accept(listenFd, nullptr, nullptr);\n  CHECK_GE(serverFd, 0);\n  ::close(listenFd);\n\n  return {folly::NetworkSocket::fromFd(serverFd),\n          folly::NetworkSocket::fromFd(clientFd)};\n}\n\n// Exposes the protected TCP onNewConnection for direct unit testing.\nclass TestableHTTPCoroAcceptor : public HTTPCoroAcceptor {\n public:\n  using HTTPCoroAcceptor::HTTPCoroAcceptor;\n  using HTTPCoroAcceptor::onNewConnection;\n};\n\n} // namespace\n\n// Verify that zero copy is applied to accepted TCP connections when the\n// zeroCopyEnableThreshold is set on the acceptor.\nTEST(ZeroCopyAcceptor, ZeroCopyAppliedToAcceptedTransport) {\n  auto [serverFd, clientFd] = createTcpSocketPair();\n\n  auto accConfig = std::make_shared<AcceptorConfiguration>();\n  accConfig->plaintextProtocol = \"http/1.1\";\n  auto handler = std::make_shared<TestHandler>();\n\n  folly::ScopedEventBaseThread evbThread;\n  auto* evb = evbThread.getEventBase();\n\n  auto acceptor =\n      std::make_unique<TestableHTTPCoroAcceptor>(accConfig, handler);\n  evb->runInEventBaseThreadAndWait([&]() { acceptor->init(nullptr, evb); });\n  acceptor->setZeroCopyEnableThreshold(32768);\n\n  std::atomic<bool> zeroCopyApplied{false};\n\n  evb->runInEventBaseThreadAndWait([&]() {\n    auto socket = folly::AsyncSocket::newSocket(evb, serverFd);\n    auto* rawSocket = socket.get();\n\n    folly::SocketAddress addr;\n    wangle::TransportInfo tinfo;\n    acceptor->onNewConnection(\n        folly::AsyncTransport::UniquePtr(socket.release()),\n        &addr,\n        \"http/1.1\",\n        wangle::SecureTransportType::NONE,\n        tinfo);\n\n    zeroCopyApplied = rawSocket->getZeroCopy();\n  });\n\n  EXPECT_TRUE(zeroCopyApplied.load());\n\n  ::close(clientFd.toFd());\n  // forceStop defers dropAllConnections via runInLoop, so we must let the\n  // EventBase process that callback before destroying the acceptor.\n  evb->runInEventBaseThreadAndWait([&]() { acceptor->forceStop(); });\n  evb->runInEventBaseThreadAndWait([&]() { acceptor.reset(); });\n}\n\n// Verify that zero copy is NOT applied when zeroCopyEnableThreshold is not set.\nTEST(ZeroCopyAcceptor, ZeroCopyNotAppliedWithoutThreshold) {\n  auto [serverFd, clientFd] = createTcpSocketPair();\n\n  auto accConfig = std::make_shared<AcceptorConfiguration>();\n  accConfig->plaintextProtocol = \"http/1.1\";\n  auto handler = std::make_shared<TestHandler>();\n\n  folly::ScopedEventBaseThread evbThread;\n  auto* evb = evbThread.getEventBase();\n\n  auto acceptor =\n      std::make_unique<TestableHTTPCoroAcceptor>(accConfig, handler);\n  evb->runInEventBaseThreadAndWait([&]() { acceptor->init(nullptr, evb); });\n  // Deliberately NOT setting zeroCopyEnableThreshold\n\n  std::atomic<bool> zeroCopyApplied{false};\n\n  evb->runInEventBaseThreadAndWait([&]() {\n    auto socket = folly::AsyncSocket::newSocket(evb, serverFd);\n    auto* rawSocket = socket.get();\n\n    folly::SocketAddress addr;\n    wangle::TransportInfo tinfo;\n    acceptor->onNewConnection(\n        folly::AsyncTransport::UniquePtr(socket.release()),\n        &addr,\n        \"http/1.1\",\n        wangle::SecureTransportType::NONE,\n        tinfo);\n\n    zeroCopyApplied = rawSocket->getZeroCopy();\n  });\n\n  // Without zeroCopyEnableThreshold, zero copy should remain disabled.\n  EXPECT_FALSE(zeroCopyApplied.load());\n\n  ::close(clientFd.toFd());\n  // forceStop defers dropAllConnections via runInLoop, so we must let the\n  // EventBase process that callback before destroying the acceptor.\n  evb->runInEventBaseThreadAndWait([&]() { acceptor->forceStop(); });\n  evb->runInEventBaseThreadAndWait([&]() { acceptor.reset(); });\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/test/certs/test_cert1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc8CAQowDQYJKoZIhvcNAQEFBQAwQzELMAkGA1UEBhMCVVMxDTALBgNV\nBAoMBEFzb3gxJTAjBgNVBAMMHEFzb3ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw\nHhcNMTQwOTE5MjIxMzQ2WhcNNDIwMjA0MjIxMzQ2WjAwMQswCQYDVQQGEwJVUzEN\nMAsGA1UECgwEQXNveDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAprvPkBYr6LTFOldRjTQ9zKf86tBu9kG1a4CLu4c1\n2twWTf04b8lfpG5qUMt13IeI5CH9ygjkLz6gZjsGDICVokG5P5fd9k+3eIv6m0K5\nrgNUXeSYJJTbfIhEw99fdI4tpu5irtnWLGGsDApF1O2NDLYh6U0+eB1OWOGhqrSU\nAMPibief2jtLsETaRZrYSFknPfgrNjzcIfhnAv3rMnkEc55knV8l7UZCLgUaRPfS\n4ZcTe1VJghHPCbbfQ6AEcHZaXhOlX0voAXesB5RVuyPMuhQzBfasBstjITIpdbQI\nAlnFuF/vo8JRhqJKjOWek6DJyH7yjw9ZtvXsMTJCun9M7wIDAQABMA0GCSqGSIb3\nDQEBBQUAA4IBAQCdghgh4hK0HUgvr+Ue2xUgAkEhQK7nvBlxw42l64zWNIkVrg3C\nsGBx1/ZV7sVrrz5P8LkoZmKcgSoaZQRhiZ9P+nBj4hUz8oFYJ2xTl2Bo1UmEoz+r\nz63WerLLb48HQLrGJN/V1Uodjb/eVRwY16qw0JoaRg3BGbO2k19jeNIfpp00atic\nxvgxZsHuRrax4PkL6ObrASILj78AOzPmKOlMk2cbS+Ol4WJNzbqFDQaR3QXv4WSR\n6td3LlJtSyMWjMnkYOOidLYsSQ5bVnWbnP/bj/apRXxX9wi7ez739Gqc4bylJgW5\nYm+TCytFhaK6z05whWCDcD6CrXyFGer/Cqfv\n-----END CERTIFICATE-----\n\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/test/certs/test_cert2.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxBc294\nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MCAXDTI0MDYwNzIxMzAyNFoYDzIxMjQw\nNTE0MjEzMDI0WjAVMRMwEQYDVQQDDAp0ZXN0X2NlcnQyMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAqT7JKAm5ToEjPoRI/BRvZ65iQL5jOj9aBWARG1m5\nmxbRZ4yWBL07tX/uFMYvoq6it5tsnqsIO+YnnRFJxwZsh8rLe5KXZLWSfBI+4fzF\nM9NXVa634hnJQWQtErN6RAcLKYZHQctHvij1IhMyacfLTmH/iZCtWhpplSISmTDm\nl7t0+FsE0wTy6rUwgl6JK2oZa8/pWp4n2KYojWPXmGeA2W5ov+iFoCCNk7XUikPb\nvvC38smJErJV9ooWCZKPjEOntJuHjd0/PiqcZrrFwBU51EFe9JYJQA2szAfJ1MFT\nzE+vLjrurE5PC5t67nNT9qBATSFpb6R+T9tm18RRl5a1OwIDAQABo4GmMIGjMAwG\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLEoOdkozRsNg+3DKtJdRGWnsgynMB8GA1Ud\nIwQYMBaAFGW5mAAuvah4WzqcNFwW/jA9+6epMA4GA1UdDwEB/wQEAwID6DAgBgNV\nHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIQYDVR0RBBowGIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAEuyRrGGJ+3veqAYx\nR6SG5gwgfMDX+Aik2TWtRUPOlrwvAO8ud3X5nihNRvwM8cOrggrh7LfYaLVKSqm0\nsfIT+QgDezQIGjWmFevNU7oAlCViQ/B/Ik2HgBvuOzcrg9lOsh7pIO7ZRhX9Zdwl\nCE/foDU4LUIfw64u6vxR5ErYxumifRRlhx2rxZIUrtAEEHHA9iWXC2GD+TPOC86F\nePmpXaKH56d80OsvZDDfGW0FJ3vRkZR2Fhu5vmpIDr+mT5dLTAbD+mnc9YMzlcW6\nJen4pE/Mjsd+QNUjCkeNVN0PFrTiKnJfB4Mv2AdP7jPidfyWgU8v0DyOMi5BJLBO\nNgAwOg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/test/certs/test_key1.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAprvPkBYr6LTFOldRjTQ9zKf86tBu9kG1a4CLu4c12twWTf04\nb8lfpG5qUMt13IeI5CH9ygjkLz6gZjsGDICVokG5P5fd9k+3eIv6m0K5rgNUXeSY\nJJTbfIhEw99fdI4tpu5irtnWLGGsDApF1O2NDLYh6U0+eB1OWOGhqrSUAMPibief\n2jtLsETaRZrYSFknPfgrNjzcIfhnAv3rMnkEc55knV8l7UZCLgUaRPfS4ZcTe1VJ\nghHPCbbfQ6AEcHZaXhOlX0voAXesB5RVuyPMuhQzBfasBstjITIpdbQIAlnFuF/v\no8JRhqJKjOWek6DJyH7yjw9ZtvXsMTJCun9M7wIDAQABAoIBAQCGJrJ4Yf5uO5Q8\nvqjVDeVzVu4+F/pPlMrddg33knCYaWBg246fEs0rRdOwsiNgjoRr2ZWTCthd0uvH\nlVHmmUbLyEm+iviCB9281hOK/ILdKbyl1xk6xbJbXmDFoGHzK7o7h65KtOaHywZc\noZ9SFNfaFGjwh7/tcNbq2I/1A1nZynTko6iLVpgV0kkQCpaweFYQMXWv/ELkFHeL\n7tFIA50XFXbNDqnAuaW6XrIrW33ZeOJfruF2OG+QVWyTBgczk0fodDZJFS5MbDXu\nUB+W1nDhakZFbugtDSXMd3BMnLZFdsa12FYTMNG050w2OTHOF5ILX+IFwzbnlboX\nfbralUKRAoGBANnjttZWcNtM4L8JkUJwXsDgTD/0d+iu5gCmSs0p9E4wGbWlu+In\ncNE6CV4Dy+GMk5GzXFR+GV/IlPvVzSOmbFsFdBi8L3c/1IrPbQv8dEosKm6BvV9O\n0zIBaPuzgU2yyBFxdpfsHynAZoLdY3rq6IJdNmJrmDcVgEfBVOExU7EVAoGBAMPl\nhqNmGi3VwHPQ2iiuM48ijPbuS0hK3dUjx2A4otOYAro86Q4egcdtyBOONhBwD89h\nI6BUo+vReV6ikI8LQfoplBaRos7qJ2e9SOxmRIJGAZPkGlFF0uljxKZ2Hdtmruae\nmJOZqKCa38sTnqWyXV/xCXE5X94EXuJP17L2Bt7zAoGAOG7gFheBV2tL8m657qlI\nAVCWryHURLG35IctbIHnQrD2l7N7PBHXCHmtn2oATkSom94GleOrEsHSxH8ViJw8\nCD8bWKS07n/bvrAGoEocnHFf9AsqTxsNXDA9TqOpY8RgSRRIEQUY9Sld45sPfvCE\nk+8sfMU9QVcSSINsRn8OHBkCgYEAgzBGD01EQOfB/42hW9b1fmjEAGYrElnY33Eb\nhyvGl29YfEJoTOVPQjAZ6ka1nCJ/5ACIrEmikT1yS1cQ+kquv4pyuv6DCpCzHP0d\nRfti699YFSOQIFdjXJtMybGWYyUMAjO5uDcSP6QYNVaJSyv87lBsY1/p/LPumx6f\nNCEhDtMCgYBpcK4f2E+JjaGHABX5OS5Soegtgj7JjZv/M3N2OLvX2xrlkxAEPlJ5\nnvaVjikBcOsj3/+LDrBMDoEbG2JFaopiue8pW+tZWfbhJw0pf02f+hgHjCaR+1Ny\nQqd+ERH7vFjwzc3UuZay1NbU9/wVMNsL7jWKnvsKKCk9PxG2OzP2iQ==\n-----END RSA PRIVATE KEY-----\n\n"
  },
  {
    "path": "proxygen/lib/http/coro/server/test/certs/test_key2.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpPskoCblOgSM+\nhEj8FG9nrmJAvmM6P1oFYBEbWbmbFtFnjJYEvTu1f+4Uxi+irqK3m2yeqwg75ied\nEUnHBmyHyst7kpdktZJ8Ej7h/MUz01dVrrfiGclBZC0Ss3pEBwsphkdBy0e+KPUi\nEzJpx8tOYf+JkK1aGmmVIhKZMOaXu3T4WwTTBPLqtTCCXokrahlrz+lanifYpiiN\nY9eYZ4DZbmi/6IWgII2TtdSKQ9u+8LfyyYkSslX2ihYJko+MQ6e0m4eN3T8+Kpxm\nusXAFTnUQV70lglADazMB8nUwVPMT68uOu6sTk8Lm3ruc1P2oEBNIWlvpH5P22bX\nxFGXlrU7AgMBAAECggEAEtxDqnSQSVFWziFhHhu2NoWrvq9OJjA9JfAzYoA4kNyV\ncBwDSSVAFtUvgPJ9CHy8TAhura09o6ndrEFASYT2ae44Mt3T2o3pdG4hbSmwCDsl\ntmSxapRDu0fLRhQ+LH/kceIxkAFz+JsJMPJBYfcOjv5Dbd8ePQudzILEM9IhQF3y\n0ouqWu+jTLn78ZPSfIeXFYclGOt4fVEtU2NC1/Rlv8yb/VSxjcMa2lqLPPT55YB/\nToTxp6GSp5Y60nyc8WSiQnaZRGBPsOjEdINrxHHX8/NCEla8Wcrqt7sZ7UC7EYRf\nZ5Ud7dnZ29ZHpC/IOz7xX+z8Ovnm5nj2l3m4GVBWwQKBgQC5ceDOw0TMmQt64QRs\nPsflx6duvI9su9FSeCGz5YpERVVSdpi7xf+chsUtkxaOn73ZuvG2r7sKJMw4itd8\nIpEpBVmFuTSvzqcGUTDzZpyyDQ81Ukt+xZNZjoRw9zZl2OkgDBJGXOAtEoi/rv86\nhOW9MYUsnNjViW6GW2a1Q9/ScQKBgQDpoxYjyi7uPuBkKQllhHR1x/lGwsXLsyvo\nUFwR91wpDx6h+/w9NJxx7CbtgjYC1y/lMWhxOWu7AwqLtEOXF9pToAPVX5MVGztU\nj6pjJPPUwrapB2CrgLI0TSEkXiOPZNOiLiDklwnYlX7x78+kTz4T0iCvDJStjW9L\nE6QsrwXAawKBgQCD0V/keUcZTByt7u8O5p1/RylL/LrSprsHLR9/2cUr/EDHCkhN\nCVRF9kKIv8pD/WadM1aH7mg8sKV996tusL+Qch4NgPXjljiBtArgqWru4XuTAnlp\nlpXEDhs0lXVUdhhYUFxZKcGsKEWOQ51nAnqvvliUurUjLLqkxKnAZYve8QKBgQDl\n1U80SfK84BGxtkTOHuzJ6LyqBXS6nDk3QcYwzltU8NC7nL1YIGc+EoeA4bTsOm+d\nUWti5o+52pYHNH/BJO/bj+/1eR2hh7ZnyyRcf791r04tHVrVm7ayiKVvt0PYDeG7\nCxHEjWhcLURCEBz9kA6LRQxt5zxjNl0jR+EbK9nGnQKBgAo5CiVJBHjTW5KFvynt\nqLc39k2OzssQTqpCl8XY5phlxNrSzRgD1qNlHG7Su/C17x00/2eTGUDNpBf4HECs\nqYJAb1TsQqM+87+84Pl48wYxMLnKksFC+R4MRwNw/l6gjGdLJSuel/ZAEbQJ6SzF\nofMMxWJr/Hhi531EDP2ggh9l\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPCoroSessionTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/test/HTTPCoroSessionTests.h\"\n#include <folly/logging/xlog.h>\n\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n\nusing namespace testing;\nusing namespace proxygen;\n\nnamespace {\nvoid initSelfCodec(HTTPCodec &) {\n}\n} // namespace\n\nnamespace proxygen::coro::test {\n\nstd::string paramsToTestName(const testing::TestParamInfo<TestParams> &info) {\n  switch (info.param.codecProtocol) {\n    case CodecProtocol::HTTP_1_1:\n      return \"h1\";\n    case CodecProtocol::HTTP_2:\n      return \"h2\";\n    case CodecProtocol::HQ:\n      return \"h3\";\n    default:\n      XLOG(FATAL);\n      return \"\";\n  }\n}\n\nHTTPCoroSessionTest::HTTPCoroSessionTest(TransportDirection direction)\n    : direction_(direction) {\n  initSelfCodec_ = initSelfCodec;\n  initPeerCodec();\n}\n\nvoid HTTPCoroSessionTest::initPeerCodec() {\n  if (isHQ()) {\n    auto codec = std::make_unique<hq::HQMultiCodec>(oppositeDirection());\n    multiCodec_ = codec.get();\n    multiCodec_->setQPACKEncoderMaxDataFn(\n        []() -> uint64_t { return std::numeric_limits<uint64_t>::max(); });\n    auto headerTableSize = (GetParam().useDynamicTable) ? 4096 : 0;\n    auto maxBlocking = 100;\n    multiCodec_->getEgressSettings()->setSetting(\n        proxygen::SettingsId::HEADER_TABLE_SIZE, headerTableSize);\n    multiCodec_->getEgressSettings()->setSetting(\n        proxygen::SettingsId::_HQ_QPACK_BLOCKED_STREAMS, maxBlocking);\n    // Apply the egress settings to the simulated codec\n    multiCodec_->getQPACKCodec().setDecoderHeaderTableMaxSize(headerTableSize);\n    multiCodec_->getQPACKCodec().setMaxBlocking(maxBlocking);\n    peerCodec_ = std::move(codec);\n  } else {\n    peerCodec_ = HTTPCodecFactory::getCodec(GetParam().codecProtocol,\n                                            oppositeDirection());\n  }\n  auto settings = peerCodec_->getEgressSettings();\n  if (direction_ == TransportDirection::DOWNSTREAM) {\n    setTestCodecSetting(settings, SettingsId::ENABLE_PUSH, 1);\n  }\n  setTestCodecSetting(settings, SettingsId::INITIAL_WINDOW_SIZE, 65535);\n  setTestCodecSetting(settings, SettingsId::MAX_CONCURRENT_STREAMS, 10);\n  peerCodec_->setCallback(&callbacks_);\n}\n\nvoid HTTPCoroSessionTest::setUp(std::shared_ptr<HTTPHandler> handler) {\n  wangle::TransportInfo tinfo;\n  tinfo.appProtocol = std::make_shared<std::string>(\"blarf\");\n\n  if (isHQ()) {\n    muxTransport_ =\n        std::make_unique<TestCoroMultiplexTransport>(&evb_, direction_);\n    transport_ = muxTransport_.get();\n    // Other MQSD server setup\n    auto codec = std::make_unique<hq::HQMultiCodec>(direction_);\n    auto headerTableSize = (GetParam().useDynamicTable) ? 4096 : 0;\n    codec->getEgressSettings()->setSetting(SettingsId::HEADER_TABLE_SIZE,\n                                           headerTableSize);\n    // Apply the SETTINGS to the simulated codec before the first request\n    multiCodec_->getQPACKCodec().setEncoderHeaderTableSize(\n        codec->getEgressSettings()\n            ->getSetting(SettingsId::HEADER_TABLE_SIZE)\n            ->value);\n    multiCodec_->getQPACKCodec().setMaxVulnerable(\n        codec->getEgressSettings()\n            ->getSetting(SettingsId::_HQ_QPACK_BLOCKED_STREAMS)\n            ->value);\n    codec_ = codec.get();\n    initSelfCodec_(*codec_);\n    if (handler) {\n      session_ =\n          HTTPCoroSession::makeDownstreamCoroSession(muxTransport_->getSocket(),\n                                                     handler,\n                                                     std::move(codec),\n                                                     std::move(tinfo));\n    } else {\n      session_ = HTTPCoroSession::makeUpstreamCoroSession(\n          muxTransport_->getSocket(), std::move(codec), std::move(tinfo));\n    }\n  } else {\n    auto transport =\n        std::make_unique<TestUniplexTransport>(&evb_, &transportState_);\n    transport_ = transport.get();\n    auto codec =\n        HTTPCodecFactory::getCodec(GetParam().codecProtocol, direction_);\n    codec_ = codec.get();\n    initSelfCodec_(*codec_);\n    if (handler) {\n      session_ = HTTPCoroSession::makeDownstreamCoroSession(\n          std::move(transport), handler, std::move(codec), std::move(tinfo));\n    } else {\n      session_ = HTTPCoroSession::makeUpstreamCoroSession(\n          std::move(transport), std::move(codec), std::move(tinfo));\n    }\n  }\n  peerCodec_->generateConnectionPreface(writeBuf_);\n  peerCodec_->generateSettings(writeBuf_);\n\n  transport_->addReadEvent(writeBuf_.move(), false);\n\n  session_->setConnectionReadTimeout(std::chrono::seconds(1));\n  session_->setStreamReadTimeout(std::chrono::seconds(1));\n  session_->setWriteTimeout(std::chrono::seconds(1));\n  session_->setSessionStats(&fakeSessionStats_);\n  session_->addLifecycleObserver(&lifecycleObs_);\n  // mimic EventBase::add() when ::inRunningEventBase == false\n  evb_.runInEventBaseThreadAlwaysEnqueue([this]() {\n    folly::coro::co_withCancellation(cancellationSource_.getToken(),\n                                     session_->run())\n        .startInlineUnsafe();\n  });\n}\n\nvoid HTTPCoroSessionTest::resetStream(HTTPCodec::StreamID id,\n                                      ErrorCode code,\n                                      bool stopSending) {\n  if (isHQ()) {\n    auto h3Code =\n        HTTPErrorCode2HTTP3ErrorCode(ErrorCode2HTTPErrorCode(code), false);\n    muxTransport_->socketDriver_.addReadError(id, h3Code);\n    auto sock = muxTransport_->socketDriver_.getSocket();\n    // If it's a downstream test, then the framework is a client\n    bool isClient = direction_ == TransportDirection::DOWNSTREAM;\n    if (sock->isBidirectionalStream(id) ||\n        sock->isClientStream(id) == isClient) {\n      muxTransport_->socketDriver_.addReadError(id, h3Code);\n    }\n    if ((stopSending && sock->isBidirectionalStream(id)) ||\n        sock->isClientStream(id) != isClient) {\n      muxTransport_->socketDriver_.addStopSending(id, h3Code);\n    }\n  } else {\n    peerCodec_->generateRstStream(writeBuf_, id, code);\n    transport_->addReadEvent(id, writeBuf_.move(), false);\n  }\n}\n\nvoid HTTPCoroSessionTest::generateGoaway(HTTPCodec::StreamID id,\n                                         ErrorCode code) {\n  if (isHQ()) {\n    if (direction_ == TransportDirection::DOWNSTREAM) {\n      // Downstream test, peer codec is upstream\n      // push ID, min unseen + 1\n      id += 1;\n    } else {\n      // stream ID, min unseen + 4\n      id += 4;\n    }\n  }\n  peerCodec_->generateGoaway(writeBuf_, id, code);\n  transport_->addReadEvent(writeBuf_.move(), false);\n}\n\nvoid HTTPCoroSessionTest::generateGoaway() {\n  peerCodec_->generateGoaway(writeBuf_);\n  transport_->addReadEvent(writeBuf_.move(), false);\n}\n\nvoid HTTPCoroSessionTest::windowUpdate(HTTPCodec::StreamID id, uint32_t delta) {\n  if (isHQ()) {\n    auto fc =\n        muxTransport_->socketDriver_.getSocket()->getStreamFlowControl(id);\n    muxTransport_->socketDriver_.setStreamFlowControlWindow(\n        id, fc->sendWindowAvailable + delta);\n  } else {\n    peerCodec_->generateWindowUpdate(writeBuf_, id, delta);\n    transport_->addReadEvent(id, writeBuf_.move(), false);\n  }\n}\n\nvoid HTTPCoroSessionTest::windowUpdate(uint32_t delta) {\n  if (isHQ()) {\n    auto fc =\n        muxTransport_->socketDriver_.getSocket()->getConnectionFlowControl();\n    muxTransport_->socketDriver_.setConnectionFlowControlWindow(\n        fc->sendWindowAvailable + delta);\n  } else {\n    peerCodec_->generateWindowUpdate(writeBuf_, 0, delta);\n    transport_->addReadEvent(writeBuf_.move(), false);\n  }\n}\n\nvoid HTTPCoroSessionTest::expectStreamAbort(HTTPCodec::StreamID id,\n                                            ErrorCode code) {\n  if (isHQ()) {\n    EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].writeState,\n              quic::MockQuicSocketDriver::ERROR);\n    EXPECT_EQ(\n        muxTransport_->socketDriver_.streams_[id].error,\n        HTTPErrorCode2HTTP3ErrorCode(ErrorCode2HTTPErrorCode(code), false));\n  } else {\n    EXPECT_CALL(callbacks_, onAbort(id, code));\n  }\n}\n\nvoid HTTPCoroSessionTest::expectGoaway(HTTPCodec::StreamID id, ErrorCode code) {\n  if (isHQ()) {\n    id = ((id / 2) + 1) * 4;\n    EXPECT_CALL(callbacks_, onGoaway(id, _, _));\n  } else {\n    EXPECT_CALL(callbacks_, onGoaway(id, code, _));\n  }\n}\n\n} // namespace proxygen::coro::test\n// Tests to write\n// Response before request\n// messageBegin gets dup stream\n// onHeadersComplete for missing stream\n// receiving body conn flow control error\n// trailers stream not found\n// onError: stream 0 or no codec status\n// onAbort stream not found\n// flow control errors\n// responseSource provided for egress complete stream\n// handler returned nullptr but not egress complete\n// error reading response headers\n// response headers only\n// response body error\n// response body egress complete\n// resetStream with non-empty buffer queue\n// read error\n// read timeout\n// bytesParsed == 0\n// timeout waiting for write event\n// write cancelled\n// write timeoout\n// write error\n// write error cleanup\n// writable stream is missing\n// egress trailers\n// read/ingress first, egress last (normal server path)\n// read/egress first, ingress last (normal client path)\n// ingress/egress first, read last (server receive reset stream)\n// Info callbacks for settings ack, ingress error and flow controlw window full\n\n// TODO: push promise with push disabled, still decode push promise?\n// HTTP/1.0 response with no content-length - have to send a FIN to terminate\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPCoroSessionTests.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n\n#include \"proxygen/lib/http/coro/transport/test/TestCoroTransport.h\"\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n\n#include \"proxygen/lib/http/session/test/MockHTTPSessionStats.h\"\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n\n#include <gmock/gmock.h>\n#include <gtest/gtest.h>\n\nnamespace proxygen::coro::test {\n\nstruct TestParams {\n  CodecProtocol codecProtocol;\n  bool useDynamicTable{true};\n  bool enableDatagrams{false};\n};\n\nstd::string paramsToTestName(const testing::TestParamInfo<TestParams> &info);\n\nclass TestHTTPTransport {\n public:\n  virtual ~TestHTTPTransport() = default;\n\n  // Add a read event for the given stream\n  virtual void addReadEvent(HTTPCodec::StreamID id,\n                            std::unique_ptr<folly::IOBuf> data,\n                            bool eom = false) = 0;\n\n  // Add a read event for the control stream\n  virtual void addReadEvent(std::unique_ptr<folly::IOBuf> data,\n                            bool eom = false) = 0;\n\n  virtual void pauseWrites(HTTPCodec::StreamID id) = 0;\n};\n\nclass TestUniplexTransport\n    : public TestHTTPTransport\n    , public TestCoroTransport {\n public:\n  TestUniplexTransport(folly::EventBase *evb, TestCoroTransport::State *state)\n      : TestCoroTransport(evb, state) {\n  }\n\n  void addReadEvent(std::unique_ptr<folly::IOBuf> data,\n                    bool eom = false) override {\n    TestCoroTransport::addReadEvent(std::move(data), eom);\n  }\n\n  void addReadEvent(HTTPCodec::StreamID /*id*/,\n                    std::unique_ptr<folly::IOBuf> ev,\n                    bool /*endStream*/) override {\n    // This transport is uniplexed, ignore stream ID\n    addReadEvent(std::move(ev), false);\n  }\n\n  void pauseWrites(HTTPCodec::StreamID /*id*/) override {\n    TestCoroTransport::pauseWrites();\n  }\n};\n\nclass TestCoroMultiplexTransport : public TestHTTPTransport {\n public:\n  class DummyConnectionCallback\n      : public quic::QuicSocket::ConnectionSetupCallback\n      , public quic::QuicSocket::ConnectionCallback {\n   public:\n    void onNewBidirectionalStream(quic::StreamId /*id*/) noexcept override {\n      XLOG(FATAL) << __func__ << \" on dummy conn cb\";\n    }\n    void onNewUnidirectionalStream(quic::StreamId /*id*/) noexcept override {\n      XLOG(FATAL) << __func__ << \" on dummy conn cb\";\n    }\n    void onStopSending(quic::StreamId /*id*/,\n                       quic::ApplicationErrorCode /*error*/) noexcept override {\n      XLOG(FATAL) << __func__ << \" on dummy conn cb\";\n    }\n    void onConnectionEnd() noexcept override {\n      XLOG(FATAL) << __func__ << \" on dummy conn cb\";\n    }\n    void onConnectionSetupError(quic::QuicError code) noexcept override {\n      onConnectionError(std::move(code));\n    }\n    void onConnectionError(quic::QuicError /*code*/) noexcept override {\n      XLOG(FATAL) << __func__ << \" on dummy conn cb\";\n    }\n  };\n\n  TestCoroMultiplexTransport(folly::EventBase *evb, TransportDirection dir)\n      : socketDriver_(evb,\n                      &dummyConnCb_,\n                      &dummyConnCb_,\n                      dir == TransportDirection::DOWNSTREAM\n                          ? quic::MockQuicSocketDriver::TransportEnum::SERVER\n                          : quic::MockQuicSocketDriver::TransportEnum::CLIENT) {\n    if (dir == TransportDirection::DOWNSTREAM) {\n      nextBidirectionalStreamId_ = 0;\n      nextUnidirectionalStreamId_ = 2;\n      socketDriver_.setMaxBidiStreams(0);\n      socketDriver_.setMaxUniStreams(103);\n    } else {\n      nextBidirectionalStreamId_ = 1;\n      nextUnidirectionalStreamId_ = 3;\n      socketDriver_.setMaxBidiStreams(10);\n      socketDriver_.setMaxUniStreams(3);\n    }\n    createControlStream(hq::UnidirectionalStreamType::CONTROL,\n                        connControlStreamId_);\n    createControlStream(hq::UnidirectionalStreamType::QPACK_ENCODER,\n                        qpackEncoderStreamId_);\n    createControlStream(hq::UnidirectionalStreamType::QPACK_DECODER,\n                        qpackDecoderStreamId_);\n  }\n\n  void createControlStream(hq::UnidirectionalStreamType streamType,\n                           quic::StreamId &id) {\n    id = nextUnidirectionalStreamId_;\n    nextUnidirectionalStreamId_ += 4;\n    folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n    hq::writeStreamPreface(writeBuf, uint64_t(streamType));\n    addReadEvent(id, writeBuf.move(), false);\n  }\n\n  std::shared_ptr<quic::MockQuicSocket> getSocket() {\n    return socketDriver_.getSocket();\n  }\n\n  void addReadEvent(HTTPCodec::StreamID id,\n                    std::unique_ptr<folly::IOBuf> ev,\n                    bool endStream) override {\n    socketDriver_.addReadEvent(id, std::move(ev), endStream);\n  }\n\n  void addReadEvent(std::unique_ptr<folly::IOBuf> ev, bool eof) override {\n    if (ev) {\n      addReadEvent(connControlStreamId_, std::move(ev), false);\n    }\n    if (eof) {\n      socketDriver_.addOnConnectionEndEvent(0);\n    }\n  }\n\n  void pauseWrites(HTTPCodec::StreamID id) override {\n    socketDriver_.pauseWrites(id);\n  }\n\n  DummyConnectionCallback dummyConnCb_;\n  quic::MockQuicSocketDriver socketDriver_;\n  quic::StreamId connControlStreamId_;\n  quic::StreamId qpackEncoderStreamId_;\n  quic::StreamId qpackDecoderStreamId_;\n  quic::StreamId nextUnidirectionalStreamId_;\n  quic::StreamId nextBidirectionalStreamId_;\n};\n\nclass MockLifecycleObserver : public LifecycleObserver {\n public:\n  MOCK_METHOD(void, onAttached, (HTTPCoroSession &));\n  // MOCK_METHOD(void, onTransportReady, (const HTTPCoroSession&));\n  // MOCK_METHOD(void, onConnectionError, (const HTTPCoroSession&));\n  // MOCK_METHOD(void, onFullHandshakeCompletion, (const HTTPCoroSession&));\n  MOCK_METHOD(void, onIngressError, (const HTTPCoroSession &, ProxygenError));\n  MOCK_METHOD(void, onIngressEOF, (const HTTPCoroSession &));\n  MOCK_METHOD(void,\n              onRead,\n              (const HTTPCoroSession &,\n               size_t,\n               folly::Optional<HTTPCodec::StreamID>));\n  MOCK_METHOD(void, onWrite, (const HTTPCoroSession &, size_t));\n  MOCK_METHOD(void, onRequestBegin, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onRequestEnd, (const HTTPCoroSession &, uint32_t));\n  MOCK_METHOD(void, onActivateConnection, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onDeactivateConnection, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onDrainStarted, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onDestroy, (const HTTPCoroSession &));\n  MOCK_METHOD(void,\n              onIngressMessage,\n              (const HTTPCoroSession &, const HTTPMessage &));\n  // MOCK_METHOD(void, onIngressLimitExceeded, (const HTTPCoroSession&));\n  // MOCK_METHOD(void, onIngressPaused, (const HTTPCoroSession&));\n  MOCK_METHOD(void, onTransactionAttached, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onTransactionDetached, (const HTTPCoroSession &));\n  MOCK_METHOD(void, onPingReplySent, (int64_t));\n  MOCK_METHOD(void, onPingReplyReceived, ());\n  MOCK_METHOD(void, onSettingsOutgoingStreamsFull, (const HTTPCoroSession &));\n  MOCK_METHOD(void,\n              onSettingsOutgoingStreamsNotFull,\n              (const HTTPCoroSession &));\n  MOCK_METHOD(void, onFlowControlWindowClosed, (const HTTPCoroSession &));\n  // MOCK_METHOD(void, onEgressBuffered, (const HTTPCoroSession&));\n  // MOCK_METHOD(void, onEgressBufferCleared, (const HTTPCoroSession&));\n  MOCK_METHOD(void,\n              onSettings,\n              (const HTTPCoroSession &, const SettingsList &));\n  MOCK_METHOD(void, onSettingsAck, (const HTTPCoroSession &));\n  MOCK_METHOD(void,\n              onGoaway,\n              (const HTTPCoroSession &, const uint64_t, const ErrorCode));\n};\n\nclass HTTPCoroSessionTest : public testing::TestWithParam<TestParams> {\n protected:\n  HTTPCoroSessionTest(TransportDirection direction);\n\n  folly::DrivableExecutor *getExecutor() {\n    return &evb_;\n  }\n\n  void initPeerCodec();\n\n  void run() {\n    evb_.loop();\n  }\n\n  void loopN(size_t n) {\n    for (size_t i = 0; i < n; i++) {\n      evb_.loopOnce();\n    }\n  }\n\n  folly::coro::Task<void> rescheduleN(size_t n) {\n    for (size_t i = 0; i < n; i++) {\n      co_await folly::coro::co_reschedule_on_current_executor;\n    }\n  }\n\n  void setUp(std::shared_ptr<HTTPHandler> handler = nullptr);\n\n  void TearDown() override {\n    evb_.loop();\n    for (auto &fn : tearDownFns_) {\n      fn();\n    }\n    if (isHQ()) {\n      static const auto httpNoError =\n          quic::ApplicationErrorCode(HTTP3::ErrorCode::HTTP_NO_ERROR);\n      EXPECT_EQ(muxTransport_->socketDriver_.getConnErrorCode().value_or(\n                    httpNoError) != httpNoError,\n                bool(expectedError_) || bool(notExpectedError_));\n      if (!expectedError_ && !notExpectedError_) {\n        for (auto &stream : muxTransport_->socketDriver_.streams_) {\n          XLOG(DBG2) << \"Verifying stream=\" << stream.first;\n          EXPECT_TRUE(stream.second.unsentBuf.empty());\n          EXPECT_TRUE(stream.second.pendingWriteBuf.empty());\n        }\n      }\n    } else {\n      if (notExpectedError_) {\n        EXPECT_NE(transportState_.readError, notExpectedError_);\n      } else {\n        EXPECT_EQ(transportState_.readError, expectedError_);\n      }\n    }\n  }\n\n  void onTearDown(std::function<void()> fn) {\n    tearDownFns_.emplace_back(std::move(fn));\n  }\n\n  void setTestCodecSetting(HTTPSettings *settings,\n                           SettingsId id,\n                           uint32_t value) {\n    if (settings) {\n      settings->setSetting(id, value);\n    }\n  }\n\n  bool IS_H1() const {\n    return GetParam().codecProtocol == CodecProtocol::HTTP_1_1;\n  }\n\n  bool isHQ() const {\n    return GetParam().codecProtocol == CodecProtocol::HQ;\n  }\n\n  TransportDirection oppositeDirection() {\n    return (direction_ == TransportDirection::UPSTREAM)\n               ? TransportDirection::DOWNSTREAM\n               : TransportDirection::UPSTREAM;\n  }\n\n  void flushQPACKEncoder() {\n    if (isHQ()) {\n      transport_->addReadEvent(muxTransport_->qpackEncoderStreamId_,\n                               multiCodec_->getQPACKEncoderWriteBuf().move(),\n                               false);\n    }\n  }\n\n  void parseOutputUniplex() {\n    XCHECK(!isHQ());\n    folly::IOBufQueue parseOutputStream{folly::IOBufQueue::cacheChainLength()};\n    for (auto &event : transportState_.writeEvents) {\n      parseOutputStream.append(event.move());\n      size_t consumed = 0;\n      do {\n        consumed = peerCodec_->onIngress(*parseOutputStream.front());\n        parseOutputStream.trimStart(consumed);\n      } while (consumed > 0 && !parseOutputStream.empty());\n    }\n    transportState_.writeEvents.clear();\n    if (parseOutputStream.chainLength() > 0) {\n      // replace unparsed output at the head of writeEvents\n      transportState_.writeEvents.emplace_back(std::move(parseOutputStream));\n    }\n    if (transportState_.writesClosed) {\n      peerCodec_->onIngressEOF();\n    }\n  }\n\n  // stopSending controls whether H3 bidirectional streams get RST+SS or\n  // just RST\n  void resetStream(HTTPCodec::StreamID id,\n                   ErrorCode code,\n                   bool stopSending = true);\n  void generateGoaway(HTTPCodec::StreamID id, ErrorCode code);\n  void generateGoaway();\n  void windowUpdate(HTTPCodec::StreamID id, uint32_t delta);\n  void windowUpdate(uint32_t delta);\n  void expectStreamAbort(HTTPCodec::StreamID id, ErrorCode code);\n  void expectGoaway(HTTPCodec::StreamID id, ErrorCode code);\n\n  TransportDirection direction_;\n  TestHTTPTransport *transport_{nullptr};\n  TestCoroTransport::State transportState_;\n  std::unique_ptr<TestCoroMultiplexTransport> muxTransport_;\n  FakeSessionStats fakeSessionStats_;\n  HTTPCoroSession *session_{nullptr};\n  testing::NiceMock<MockLifecycleObserver> lifecycleObs_;\n  std::unique_ptr<HTTPCodec> peerCodec_;\n  HTTPCodec *codec_{nullptr}; // self codec\n  std::function<void(HTTPCodec &)> initSelfCodec_;\n  hq::HQMultiCodec *multiCodec_{nullptr};\n  testing::NiceMock<MockHTTPCodecCallback> callbacks_;\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::CancellationSource cancellationSource_;\n  folly::Optional<folly::coro::TransportIf::ErrorCode> expectedError_;\n  folly::Optional<folly::coro::TransportIf::ErrorCode> notExpectedError_;\n  std::list<std::function<void()>> tearDownFns_;\n  folly::EventBase evb_;\n};\n\ninline folly::coro::Task<HTTPBodyEvent> readBodyEventNoSuspend(\n    HTTPSource &source, uint32_t max = std::numeric_limits<uint32_t>::max()) {\n  while (true) {\n    auto event = co_await co_awaitTry(source.readBodyEvent(max));\n    if (event.hasException()) {\n      co_yield folly::coro::co_error(std::move(event.exception()));\n    }\n    if (event->eventType == HTTPBodyEvent::EventType::SUSPEND) {\n      co_await std::move(event->event.resume);\n      continue;\n    }\n    co_return event;\n  }\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPDownstreamCoroSessionTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSourceHolder.h\"\n#include \"proxygen/lib/http/coro/test/HTTPCoroSessionTests.h\"\n#include \"proxygen/lib/http/coro/test/HTTPTestSources.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <proxygen/lib/http/session/test/MockHTTPSessionStats.h>\n\n#include \"folly/coro/GmockHelpers.h\"\n#include <folly/coro/Sleep.h>\n#include <folly/logging/xlog.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nusing namespace proxygen;\nusing namespace testing;\nusing TransportErrorCode = folly::coro::TransportIf::ErrorCode;\n\nnamespace {\nconst quic::StreamId kQPACKEncoderEgressStreamId = 7;\nconst quic::StreamId kQPACKDecoderEgressStreamId = 11;\n} // namespace\n\nnamespace proxygen::coro::test {\n\nclass HTTPDownstreamSessionTest : public HTTPCoroSessionTest {\n public:\n  HTTPDownstreamSessionTest()\n      : HTTPCoroSessionTest(TransportDirection::DOWNSTREAM),\n        handler_(std::make_shared<proxygen::coro::test::MockHTTPHandler>()),\n        clientCodec_(peerCodec_.get()) {\n  }\n\n  void SetUp() override {\n    SettingsId datagramSetting =\n        *hq::hqToHttpSettingsId(hq::SettingId::H3_DATAGRAM);\n    bool enableDatagrams = isHQ() && GetParam().enableDatagrams;\n    setTestCodecSetting(\n        clientCodec_->getEgressSettings(), datagramSetting, enableDatagrams);\n    HTTPCoroSessionTest::setUp(handler_);\n    session_->setSetting(datagramSetting, enableDatagrams);\n  }\n\n  using HandlerFn = std::function<folly::coro::Task<HTTPSourceHolder>(\n      folly::EventBase *, HTTPSessionContextPtr, HTTPSourceHolder)>;\n\n  std::shared_ptr<HandlerFn> addSimpleStrictHandler(HandlerFn handlerFn) {\n    // If the function captures any local variables, it needs to outlive\n    // handleRequest(), because it's run in a coroutine.\n    auto sharedHandlerFn = std::make_shared<HandlerFn>(std::move(handlerFn));\n    EXPECT_CALL(*handler_, handleRequest(_, _, _))\n        .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n            [sharedHandlerFn](folly::EventBase *evb,\n                              HTTPSessionContextPtr ctx,\n                              HTTPSourceHolder source)\n                -> folly::coro::Task<HTTPSourceHolder> {\n              CHECK_EQ(ctx->getEventBase(), evb);\n              return (*sharedHandlerFn)(evb, ctx, std::move(source));\n            }))\n        .RetiresOnSaturation();\n\n    return sharedHandlerFn;\n  }\n\n  void parseOutput() {\n    isHQ() ? parseOutputHQ() : parseOutputUniplex();\n  }\n\n  void parseOneStream(quic::MockQuicSocketDriver::StreamStatePair &stream,\n                      bool skipPreface) {\n    if ((stream.first & 0x3) == 2) {\n      // Don't parse client-initiated uni\n      return;\n    }\n    hq::HQUnidirectionalCodec *controlCodec{nullptr};\n    class Callback : public hq::HQUnidirectionalCodec::Callback {\n     public:\n      void onError(HTTPCodec::StreamID, const HTTPException &, bool) override {\n        EXPECT_TRUE(false);\n      }\n    } cb;\n    hq::QPACKEncoderCodec qpackEncoderCodec(multiCodec_->getQPACKCodec(), cb);\n    hq::QPACKDecoderCodec qpackDecoderCodec(multiCodec_->getQPACKCodec(), cb);\n    if (!skipPreface && (stream.first & 0x3) == 3 &&\n        !stream.second.writeBuf.empty()) {\n      folly::io::Cursor cursor(stream.second.writeBuf.front());\n      auto preface = quic::follyutils::decodeQuicInteger(cursor);\n      XCHECK(preface);\n      size_t toTrim = preface->second;\n      auto g = folly::makeGuard(\n          [&toTrim, &stream] { stream.second.writeBuf.trimStart(toTrim); });\n      switch ((hq::UnidirectionalStreamType)preface->first) {\n        case hq::UnidirectionalStreamType::CONTROL:\n          controlCodec = multiCodec_;\n          break;\n        case hq::UnidirectionalStreamType::QPACK_ENCODER:\n          controlCodec = &qpackEncoderCodec;\n          break;\n        case hq::UnidirectionalStreamType::QPACK_DECODER:\n          controlCodec = &qpackDecoderCodec;\n          break;\n        case hq::UnidirectionalStreamType::PUSH: {\n          auto pushID = quic::follyutils::decodeQuicInteger(cursor);\n          multiCodec_->addCodec(stream.first);\n          XCHECK(pushID);\n          pushMap_.emplace(pushID->first, stream.first);\n          toTrim += pushID->second;\n          // Parse the rest of the push later\n          return;\n        }\n        default:\n          XLOG(FATAL) << \"Bad uni stream type\";\n      }\n    }\n    if (!stream.second.writeBuf.empty()) {\n      XLOG(DBG4) << \"Decoding stream id=\" << stream.first;\n      if (controlCodec) {\n        if (!stream.second.writeBuf.empty()) {\n          controlCodec->onUnidirectionalIngress(stream.second.writeBuf.move());\n        }\n      } else {\n        multiCodec_->setCurrentStream(stream.first);\n        auto consumed =\n            clientCodec_->onIngress(*stream.second.writeBuf.front());\n        stream.second.writeBuf.trimStart(consumed);\n        EXPECT_EQ(stream.second.writeBuf.chainLength(), 0);\n      }\n      if (stream.second.writeEOF) {\n        if (controlCodec) {\n          controlCodec->onUnidirectionalIngressEOF();\n        } else {\n          multiCodec_->setCurrentStream(stream.first);\n          clientCodec_->onIngressEOF();\n        }\n      }\n    }\n  }\n\n  void parseOutputHQ() {\n    // Parse all uni control streams and pushIDs\n    for (auto &stream : muxTransport_->socketDriver_.streams_) {\n      if ((stream.first & 0x3) == 3 && !stream.second.writeBuf.empty()) {\n        parseOneStream(stream, false);\n      }\n    }\n    // Parse all data streams and pushes\n    for (auto &stream : muxTransport_->socketDriver_.streams_) {\n      parseOneStream(stream, true);\n    }\n  }\n\n  // TODO: this only expects a GET request\n  folly::coro::Task<HTTPCodec::StreamID> expectRequest(\n      HTTPSourceHolder &requestSource,\n      HTTPMethod method,\n      folly::StringPiece path,\n      bool eom = true,\n      HTTPHeaders *headers = nullptr) {\n    auto streamID = *requestSource.getStreamID();\n    auto req = co_await co_awaitTry(requestSource.readHeaderEvent());\n    EXPECT_FALSE(req.hasException());\n    EXPECT_EQ(req->headers->getSeqNo(),\n              HTTPCodec::streamIDToSeqNo(GetParam().codecProtocol, streamID));\n    EXPECT_EQ(req->headers->getMethod(), method);\n    EXPECT_EQ(req->headers->getPathAsStringPiece(), path);\n    EXPECT_TRUE(req->isFinal());\n    EXPECT_EQ(req->eom, eom);\n    if (headers) {\n      headers->forEach([reqHeaders = req->headers->getHeaders()](\n                           const std::string &name, const std::string &value) {\n        EXPECT_EQ(reqHeaders.getSingleOrEmpty(name), value);\n      });\n    }\n    co_return streamID;\n  }\n\n  static folly::coro::Task<HTTPError> expectHeaderError(\n      HTTPSourceHolder &requestSource,\n      std::optional<HTTPErrorCode> expectedCode = std::nullopt) {\n    auto headerEvent = co_await co_awaitTry(requestSource.readHeaderEvent());\n    EXPECT_TRUE(headerEvent.hasException());\n    auto err = getHTTPError(headerEvent);\n    if (expectedCode) {\n      EXPECT_EQ(err.code, *expectedCode);\n    }\n    co_return err;\n  }\n\n  static folly::coro::Task<HTTPErrorCode> expectBodyError(\n      HTTPSourceHolder &requestSource,\n      std::optional<HTTPErrorCode> expectedCode = std::nullopt) {\n    auto bodyEvent =\n        co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n    EXPECT_TRUE(bodyEvent.hasException());\n    auto code = getHTTPError(bodyEvent).code;\n    if (expectedCode) {\n      EXPECT_EQ(code, *expectedCode);\n    }\n    co_return code;\n  }\n\n  void expectResponseHeaders(HTTPCodec::StreamID id, uint16_t statusCode) {\n    expectResponseHeaders(\n        id,\n        [statusCode](HTTPCodec::StreamID, std::shared_ptr<HTTPMessage> resp) {\n          EXPECT_EQ(resp->getStatusCode(), statusCode);\n        });\n  }\n\n  void expectResponseHeaders(\n      HTTPCodec::StreamID id,\n      std::function<void(HTTPCodec::StreamID, std::shared_ptr<HTTPMessage>)>\n          callback = nullptr) {\n    if (callback) {\n      EXPECT_CALL(callbacks_, onHeadersComplete(id, _))\n          .WillOnce(Invoke(std::move(callback)))\n          .RetiresOnSaturation();\n    } else {\n      EXPECT_CALL(callbacks_, onHeadersComplete(id, _));\n    }\n  }\n\n  void expectResponseBody(HTTPCodec::StreamID id) {\n    EXPECT_CALL(callbacks_, onBody(id, _, _));\n  }\n\n  void expectResponseEOM(HTTPCodec::StreamID id) {\n    EXPECT_CALL(callbacks_, onMessageComplete(id, _));\n  }\n\n  void expectResponse(HTTPCodec::StreamID id,\n                      uint16_t statusCode,\n                      HTTPHeaders *headers = nullptr,\n                      bool expectBody = true) {\n    expectResponseHeaders(\n        id,\n        [statusCode, headers](HTTPCodec::StreamID,\n                              std::shared_ptr<HTTPMessage> resp) {\n          EXPECT_EQ(resp->getStatusCode(), statusCode);\n          if (headers) {\n            headers->forEach(\n                [respHeaders = resp->getHeaders()](const std::string &name,\n                                                   const std::string &value) {\n                  EXPECT_EQ(respHeaders.getSingleOrEmpty(name), value);\n                });\n          }\n        });\n    if (expectBody) {\n      expectResponseBody(id);\n    }\n    expectResponseEOM(id);\n  }\n\n  HTTPCodec::StreamID sendRequest(folly::StringPiece path,\n                                  std::unique_ptr<folly::IOBuf> body = nullptr,\n                                  bool eom = true,\n                                  bool eof = false) {\n    HTTPMessage req;\n    if (body) {\n      req.setMethod(HTTPMethod::POST);\n      // TODO: should be optional?\n      req.setIsChunked(true);\n    } else {\n      req.setMethod(HTTPMethod::GET);\n    }\n    req.setURL(path);\n    // TODO: some HTTP/1.0 tests?  Don't say 0.9\n    req.setHTTPVersion(1, 1);\n    return sendRequest(req, std::move(body), eom, eof);\n  }\n\n  HTTPCodec::StreamID sendRequest(HTTPMessage &req,\n                                  std::unique_ptr<folly::IOBuf> body = nullptr,\n                                  bool eom = true,\n                                  bool eof = false) {\n\n    if (eof) {\n      req.setWantsKeepalive(false);\n    }\n    if (body) {\n      req.getHeaders().set(\n          HTTP_HEADER_CONTENT_LENGTH,\n          folly::to<std::string>(body->computeChainDataLength()));\n    }\n    HTTPCodec::StreamID id = sendRequestHeader(std::move(req), eom && !body);\n    if (body) {\n      sendBody(id, std::move(body), eom);\n    }\n    transport_->addReadEvent(id, writeBuf_.move(), eom);\n    if (eof) {\n      generateGoaway();\n      transport_->addReadEvent(nullptr, IS_H1());\n    }\n    return id;\n  }\n\n  HTTPCodec::StreamID createStreamID() {\n    HTTPCodec::StreamID id;\n    if (isHQ()) {\n      id = muxTransport_->nextBidirectionalStreamId_;\n      muxTransport_->nextBidirectionalStreamId_ += 4;\n      multiCodec_->addCodec(id);\n    } else {\n      id = clientCodec_->createStream();\n    }\n    return id;\n  }\n\n  HTTPCodec::StreamID sendRequestHeader(HTTPMessage req,\n                                        bool eom = false,\n                                        bool flushQPACK = true) {\n    auto id = createStreamID();\n    sendRequestHeader(id, std::move(req), eom, flushQPACK);\n    return id;\n  }\n\n  void sendRequestHeader(HTTPCodec::StreamID id,\n                         HTTPMessage req,\n                         bool eom = false,\n                         bool flushQPACK = true) {\n    clientCodec_->generateHeader(writeBuf_, id, req, eom);\n    if (flushQPACK) {\n      flushQPACKEncoder();\n    }\n  }\n\n  void sendBody(HTTPCodec::StreamID id,\n                std::unique_ptr<folly::IOBuf> body = nullptr,\n                bool eom = true,\n                bool flush = false) {\n    clientCodec_->generateBody(\n        writeBuf_, id, std::move(body), HTTPCodec::NoPadding, eom);\n    if (flush) {\n      transport_->addReadEvent(id, writeBuf_.move(), false);\n    }\n  }\n\n  void sendPadding(HTTPCodec::StreamID id,\n                   uint16_t amount,\n                   bool flush = false) {\n    clientCodec_->generatePadding(writeBuf_, id, amount);\n    if (flush) {\n      transport_->addReadEvent(id, writeBuf_.move(), false);\n    }\n  }\n\n  HTTPSource *makeResponseWithPush(\n      std::function<OnEOMSource::CallbackReturn()> eomCallback) {\n    auto resp = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n    auto promise = std::make_unique<HTTPMessage>(getGetRequest(\"/push\"));\n    auto pushRespSource = HTTPFixedSource::makeFixedResponse(200, makeBuf(75));\n    auto onEOMSource = new OnEOMSource(pushRespSource, eomCallback);\n    resp->pushes_.emplace_back(std::move(promise), onEOMSource, false);\n    return resp;\n  }\n\n  void expectPush(HTTPCodec::StreamID id,\n                  bool response,\n                  std::optional<ErrorCode> error) {\n    EXPECT_CALL(callbacks_, onPushMessageBegin(_, id, _))\n        .WillOnce(\n            Invoke([this, response, error](HTTPCodec::StreamID pushID,\n                                           HTTPCodec::StreamID assocStreamID,\n                                           HTTPMessage * /*promise*/) {\n              auto expectedPushStreamID =\n                  isHQ() ? pushMap_.find(pushID)->second : pushID;\n              if (response) {\n                // expect a whole response\n                expectResponse(expectedPushStreamID, 200);\n              } else {\n                if (!error || !isHQ()) {\n                  // The stopSending jumps the headers in the write queue in\n                  // MockQuicSocketDriver\n                  expectResponseHeaders(expectedPushStreamID);\n                }\n                if (error) {\n                  expectStreamAbort(expectedPushStreamID, *error);\n                }\n              }\n              // Now expect the promise to be complete, %@$# gmock\n              auto expectedPushId = (isHQ()) ? assocStreamID : pushID;\n              EXPECT_CALL(callbacks_, onHeadersComplete(expectedPushId, _))\n                  .WillOnce(Invoke([](HTTPCodec::StreamID /*id*/,\n                                      std::shared_ptr<HTTPMessage> promise) {\n                    EXPECT_EQ(promise->getPathAsStringPiece(), \"/push\");\n                  }))\n                  .RetiresOnSaturation();\n            }));\n  }\n\n  std::shared_ptr<HandlerFn> addHandlerWithByteEvents(\n      MockByteEventCallback &mockByteEventCallback,\n      uint8_t headerEvents,\n      uint8_t bodyEvents,\n      const std::function<void(HTTPFixedSource &,\n                               TestHTTPTransport &,\n                               HTTPCodec &,\n                               folly::IOBufQueue &)> &setupFn,\n      uint32_t maxBytesPerRead = std::numeric_limits<uint32_t>::max()) {\n    auto handler =\n        addSimpleStrictHandler([this,\n                                &mockByteEventCallback,\n                                headerEvents,\n                                bodyEvents,\n                                setupFn,\n                                maxBytesPerRead](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                   -> folly::coro::Task<HTTPSourceHolder> {\n          co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n          auto filter =\n              new ByteEventFilter(headerEvents,\n                                  bodyEvents,\n                                  mockByteEventCallback.getWeakRefCountedPtr(),\n                                  maxBytesPerRead);\n          auto resp = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n          if (setupFn) {\n            setupFn(*resp, *transport_, *clientCodec_, writeBuf_);\n          }\n          filter->setSource(resp);\n          generateGoaway(0, ErrorCode::NO_ERROR);\n          transport_->addReadEvent(nullptr, IS_H1());\n          co_return filter;\n        });\n    return handler;\n  }\n\n  std::shared_ptr<MockHTTPHandler> handler_;\n  HTTPCodec *clientCodec_{nullptr};\n  folly::F14FastMap<uint64_t, HTTPCodec::StreamID> pushMap_;\n};\n\n// Use this test class for h1 only tests\nusing H1DownstreamSessionTest = HTTPDownstreamSessionTest;\n// Use this test class for h2 only tests\nusing H2DownstreamSessionTest = HTTPDownstreamSessionTest;\n// Use this test class for h3 only tests\nusing HQDownstreamSessionTest = HTTPDownstreamSessionTest;\n// Use this test class for h3 datagram only tests\nusing HQDownstreamSessionDatagramTest = HTTPDownstreamSessionTest;\n// Use this test class for h3 only tests, with static-only QPACK\nusing HQStaticQPACKDownstreamSessionTest = HTTPDownstreamSessionTest;\n// Use this test class for h1/h2 only tests\nusing H12DownstreamSessionTest = HTTPDownstreamSessionTest;\n// Use this test class for h2/h3 only tests\nusing H2QDownstreamSessionTest = HTTPDownstreamSessionTest;\n\nTEST_P(HTTPDownstreamSessionTest, TestEOF) {\n  transport_->addReadEvent(nullptr, true);\n  evb_.loopOnce();\n}\n\nTEST_P(HTTPDownstreamSessionTest, Simple) {\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        EXPECT_EQ(session_->numIncomingStreams(), 1);\n        EXPECT_EQ(session_->numOutgoingStreams(), 0);\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(nullptr, IS_H1());\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, PaddedRequest) {\n  const auto id = sendRequestHeader(getPostRequest(10));\n  sendPadding(id, 20);\n  sendBody(id, makeBuf(5), /*eom=*/false);\n  sendPadding(id, 30);\n  sendBody(id, makeBuf(5), /*eom=*/false);\n  sendPadding(id, 40);\n  sendBody(id, makeBuf(0), /*eom=*/true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n\n  auto handler =\n      addSimpleStrictHandler([this, id](folly::EventBase *evb,\n                                        HTTPSessionContextPtr /*ctx*/,\n                                        HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        const auto id2 =\n            co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        EXPECT_EQ(id, id2);\n        EXPECT_EQ(session_->numIncomingStreams(), 1);\n        EXPECT_EQ(session_->numOutgoingStreams(), 0);\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_EQ(HTTPBodyEvent::EventType::BODY, bodyEvent->eventType);\n        EXPECT_EQ(5 + 5 + 0, bodyEvent->event.body.chainLength());\n        EXPECT_TRUE(bodyEvent->eom);\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(nullptr, IS_H1());\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, HeadResponse) {\n  auto req = getGetRequest();\n  req.setMethod(HTTPMethod::HEAD);\n  auto id = sendRequest(req);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::HEAD, \"/\");\n        EXPECT_EQ(session_->numIncomingStreams(), 1);\n        EXPECT_EQ(session_->numOutgoingStreams(), 0);\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(nullptr, IS_H1());\n        auto resp = HTTPFixedSource::makeFixedResponse(200);\n        resp->msg_->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n        co_return resp;\n      });\n\n  evb_.loop();\n  HTTPHeaders expected;\n  expected.set(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  expectResponse(id, 200, &expected, false);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, SimpleByteEvents) {\n  auto id = sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler =\n      addHandlerWithByteEvents(mockByteEventCallback,\n                               uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE) |\n                                   uint8_t(HTTPByteEvent::Type::KERNEL_WRITE) |\n                                   uint8_t(HTTPByteEvent::Type::NIC_TX) |\n                                   uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK),\n                               uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE) |\n                                   uint8_t(HTTPByteEvent::Type::KERNEL_WRITE),\n                               nullptr);\n\n  auto expectedEvents = 6;\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_))\n      .Times(expectedEvents)\n      .WillRepeatedly(Invoke([this, id](HTTPByteEvent event) {\n        // Validate streamOffset field\n        EXPECT_GT(event.streamOffset, event.bodyOffset);\n        if (isHQ()) {\n          // For HTTP/3, streamOffset should equal transportOffset\n          EXPECT_EQ(event.streamOffset, event.transportOffset)\n              << \"HTTP/3 streamOffset should equal transportOffset\";\n        } else {\n          EXPECT_LE(event.streamOffset, event.transportOffset);\n        }\n\n        if (event.type == HTTPByteEvent::Type::TRANSPORT_WRITE &&\n            event.fieldSectionInfo) {\n          EXPECT_EQ(event.fieldSectionInfo->type,\n                    HTTPByteEvent::FieldSectionInfo::Type::HEADERS);\n          EXPECT_TRUE(event.fieldSectionInfo->finalHeaders);\n          EXPECT_EQ(event.bodyOffset, 0);\n          EXPECT_GE(event.transportOffset,\n                    event.fieldSectionInfo->size.compressed);\n        } else if (event.type == HTTPByteEvent::Type::KERNEL_WRITE &&\n                   event.eom) {\n          EXPECT_EQ(event.bodyOffset, 100);\n          if (isHQ()) {\n            EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].nextWriteOffset,\n                      event.transportOffset);\n          } else {\n            EXPECT_EQ(transportState_.writeOffset, event.transportOffset);\n          }\n        }\n      }));\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, TrailerByteEvents) {\n  auto id = sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler = addHandlerWithByteEvents(\n      mockByteEventCallback,\n      0,\n      uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE),\n      [](HTTPFixedSource &resp,\n         TestHTTPTransport &,\n         HTTPCodec &,\n         folly::IOBufQueue &) {\n        resp.trailers_ = std::make_unique<HTTPHeaders>();\n        resp.trailers_->add(\"x-trailer-1\", \"foo\");\n      });\n\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_))\n      .Times(2)\n      .WillRepeatedly(Invoke([](HTTPByteEvent event) {\n        if (event.type == HTTPByteEvent::Type::TRANSPORT_WRITE &&\n            event.fieldSectionInfo) {\n          EXPECT_TRUE(event.eom);\n          EXPECT_EQ(event.fieldSectionInfo->type,\n                    HTTPByteEvent::FieldSectionInfo::Type::TRAILERS);\n          // finalHeaders=true is questionable for trailers?\n          EXPECT_TRUE(event.fieldSectionInfo->finalHeaders);\n          EXPECT_EQ(event.bodyOffset, 100);\n          EXPECT_GE(event.transportOffset,\n                    event.fieldSectionInfo->size.compressed + 100);\n        } else {\n          EXPECT_EQ(event.bodyOffset, 100);\n          EXPECT_FALSE(event.eom);\n        }\n      }));\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, ByteEventErrors) {\n  auto id = sendRequest(\"/\");\n\n  auto mockByteEventCallback = std::make_shared<MockByteEventCallback>();\n  auto handler = addHandlerWithByteEvents(\n      *mockByteEventCallback,\n      0, /* no events */\n      uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE),\n      [&mockByteEventCallback](HTTPFixedSource &,\n                               TestHTTPTransport &transport,\n                               HTTPCodec &,\n                               folly::IOBufQueue &) {\n        mockByteEventCallback.reset();\n      });\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\n// Close the socket before a response can be written, byte events are registered\n// and then cancelled\nTEST_P(HTTPDownstreamSessionTest, ByteEventsCancelOnWriteError) {\n  sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler =\n      addHandlerWithByteEvents(mockByteEventCallback,\n                               uint8_t(HTTPByteEvent::Type::KERNEL_WRITE),\n                               uint8_t(HTTPByteEvent::Type::TRANSPORT_WRITE) |\n                                   uint8_t(HTTPByteEvent::Type::KERNEL_WRITE),\n                               nullptr);\n\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_))\n      .WillOnce(Invoke([this](const HTTPByteEvent &event) {\n        EXPECT_EQ(event.type, HTTPByteEvent::Type::TRANSPORT_WRITE);\n        if (isHQ()) {\n          muxTransport_->socketDriver_.setStrictErrorCheck(false);\n          muxTransport_->socketDriver_.closeConnection();\n        } else {\n          static_cast<TestUniplexTransport *>(transport_)->shutdownWrite();\n        }\n      }));\n  EXPECT_CALL(mockByteEventCallback, onByteEventCanceled(_, _))\n      .Times(2)\n      .WillRepeatedly(\n          Invoke([](const HTTPByteEvent &event, const HTTPError &err) {\n            XLOG(DBG4) << \"onByteEventCanceled t=\" << uint8_t(event.type)\n                       << \" off=\" << event.transportOffset;\n            EXPECT_EQ(event.type, HTTPByteEvent::Type::KERNEL_WRITE);\n            EXPECT_EQ(err.code, HTTPErrorCode::TRANSPORT_WRITE_ERROR);\n          }));\n  evb_.loop();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\n// Test that ACK events on zero-byte EOM (HTTP/1 with Content-Length: 1) fire\n// This reproduces the crash at HTTPCoroSession.cpp:3096 where\n// txAckEvent->sessionByteOffset == sessionBytesScheduled_.\nTEST_P(H1DownstreamSessionTest, ZeroByteEOMAckEventImmediate) {\n  auto id = sendRequest(\"/\");\n  (void)id; // Unused in simplified test\n\n  // Enable fast ACK events to fire immediately\n  static_cast<TestUniplexTransport *>(transport_)->setFastTxAckEvents(true);\n\n  bool ackCallbackFired = false;\n  MockByteEventCallback mockByteEventCallback;\n  HTTPStreamSourceHolder::Ptr streamSource;\n\n  auto handler =\n      addSimpleStrictHandler([this, &mockByteEventCallback, &streamSource](\n                                 folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n\n        // Create HTTPStreamSource and save it for test to control\n        streamSource = HTTPStreamSourceHolder::make(evb);\n        streamSource->start();\n\n        // Wrap with ByteEventFilter to register for ACK events\n        auto *filter = new ByteEventFilter(\n            0,                                            // no header events\n            uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK), // body events\n            mockByteEventCallback.getWeakRefCountedPtr());\n        filter->setSource(streamSource->get());\n\n        co_return filter;\n      });\n\n  // Expect ACK events - one for body, one for EOM\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_))\n      .Times(AtLeast(1))\n      .WillRepeatedly([&ackCallbackFired](const HTTPByteEvent &event) {\n        EXPECT_EQ(event.type, HTTPByteEvent::Type::CUMULATIVE_ACK);\n        if (event.eom) {\n          ackCallbackFired = true;\n        }\n      });\n\n  evb_.loopOnce(); // Let handler run and return source\n\n  // Now enqueue headers and body\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setStatusCode(200);\n  msg->setStatusMessage(\"OK\");\n  msg->setHTTPVersion(1, 1);\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"1\");\n  streamSource->get()->headers(std::move(msg), false);\n\n  auto bodyBuf = folly::IOBuf::copyBuffer(\"x\");\n  streamSource->get()->body(std::move(bodyBuf), false);\n\n  // Process headers+body write\n  evb_.loopOnce();\n  evb_.loopOnce();\n\n  // Now writeBuf_ should be empty, enqueue zero-byte EOM\n  streamSource->get()->eom();\n\n  // Send a second request to trigger more writes (to hit XCHECK without the\n  // fix)\n  sendRequest(\"/second\");\n  addSimpleStrictHandler([this](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                             -> folly::coro::Task<HTTPSourceHolder> {\n    co_await expectRequest(requestSource, HTTPMethod::GET, \"/second\");\n    co_return HTTPFixedSource::makeFixedResponse(200);\n  });\n\n  evb_.loop();\n\n  // Verify EOM ACK callback fired\n  EXPECT_TRUE(ackCallbackFired);\n}\n\n// Test that ACK events on zero-byte EOM work correctly when ACK arrives\n// after the EOM is processed (delayed ACK scenario).\n// With the fix, events fire immediately even without fastTxAck.\nTEST_P(H1DownstreamSessionTest, ZeroByteEOMAckEventDelayed) {\n  auto id = sendRequest(\"/\");\n  (void)id; // Unused in simplified test\n\n  // Leave fastTxAck as false (default) to test non-fast path\n  bool ackCallbackFired = false;\n  MockByteEventCallback mockByteEventCallback;\n  HTTPStreamSourceHolder::Ptr streamSource;\n\n  auto handler =\n      addSimpleStrictHandler([this, &mockByteEventCallback, &streamSource](\n                                 folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n\n        // Create HTTPStreamSource and save it for test to control\n        streamSource = HTTPStreamSourceHolder::make(evb);\n        streamSource->start();\n\n        // Wrap with ByteEventFilter to register for ACK events\n        auto *filter = new ByteEventFilter(\n            0,                                            // no header events\n            uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK), // body events\n            mockByteEventCallback.getWeakRefCountedPtr());\n        filter->setSource(streamSource->get());\n\n        co_return filter;\n      });\n\n  // Expect ACK events - one for body, one for EOM (after delay since\n  // fastTxAck=false)\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_))\n      .Times(AtLeast(1))\n      .WillRepeatedly([&ackCallbackFired](const HTTPByteEvent &event) {\n        EXPECT_EQ(event.type, HTTPByteEvent::Type::CUMULATIVE_ACK);\n        if (event.eom) {\n          ackCallbackFired = true;\n        }\n      });\n\n  evb_.loopOnce(); // Let handler run and return source\n\n  // Now enqueue headers and body\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setStatusCode(200);\n  msg->setStatusMessage(\"OK\");\n  msg->setHTTPVersion(1, 1);\n  msg->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"1\");\n  streamSource->get()->headers(std::move(msg), false);\n\n  auto bodyBuf = folly::IOBuf::copyBuffer(\"x\");\n  streamSource->get()->body(std::move(bodyBuf), false);\n\n  // Process headers+body write\n  evb_.loopOnce();\n  evb_.loopOnce();\n\n  // Now writeBuf_ should be empty, enqueue zero-byte EOM\n  streamSource->get()->eom();\n\n  // Send a second request to trigger more writes (to hit XCHECK without the\n  // fix)\n  sendRequest(\"/second\");\n  addSimpleStrictHandler([this](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                             -> folly::coro::Task<HTTPSourceHolder> {\n    co_await expectRequest(requestSource, HTTPMethod::GET, \"/second\");\n    co_return HTTPFixedSource::makeFixedResponse(200);\n  });\n\n  // Run event loop - with the fix, ACK should fire immediately\n  evb_.loop();\n\n  // Verify EOM ACK callback fired\n  EXPECT_TRUE(ackCallbackFired);\n}\n\nTEST_P(H1DownstreamSessionTest, ReadDataAfterCancel) {\n  EXPECT_CALL(lifecycleObs_, onWrite(_, _)).WillOnce([&]() {\n    // send second request after response for prior request is written\n    sendRequest(\"/\");\n\n    // subsequently trigger h1 codec to become not reusable\n    evb_.runInEventBaseThreadAlwaysEnqueue(\n        [&]() { session_->closeWhenIdle(); });\n  });\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto id = sendRequest(\"/\");\n  evb_.loop();\n\n  expectResponse(id, 200);\n  parseOutput();\n  // expect graceful shutdown (i.e. eof & not rst)\n  XCHECK(!transportState_.closedWithReset && transportState_.writesClosed);\n}\n\nTEST_P(H2DownstreamSessionTest, ByteEventsCancelOnProtocolError) {\n  sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler = addHandlerWithByteEvents(\n      mockByteEventCallback,\n      uint8_t(HTTPByteEvent::Type::KERNEL_WRITE),\n      uint8_t(HTTPByteEvent::Type::KERNEL_WRITE),\n      [](HTTPFixedSource &,\n         TestHTTPTransport &transport,\n         HTTPCodec &clientCodec,\n         folly::IOBufQueue &writeBuf) {\n        clientCodec.generateWindowUpdate(writeBuf, 0, 0x7fffffff);\n        transport.addReadEvent(writeBuf.move(), false);\n      });\n\n  // Header and Body events are cancelled from different code paths.  The header\n  // event is cancelled from HTTPCoroSession::connectionError -> cancelEvents,\n  // the queued body event is cancelled from HTTPBodyEventQueue::clear.\n  EXPECT_CALL(mockByteEventCallback, onByteEventCanceled(_, _))\n      .Times(2)\n      .WillRepeatedly(\n          Invoke([](const HTTPByteEvent &event, const HTTPError &err) {\n            EXPECT_EQ(event.type, HTTPByteEvent::Type::KERNEL_WRITE);\n            EXPECT_EQ(err.code, HTTPErrorCode::FLOW_CONTROL_ERROR);\n          }));\n  evb_.loop();\n}\n\nTEST_P(H12DownstreamSessionTest, ByteEventTimeout) {\n  session_->setByteEventTimeout(std::chrono::seconds(2));\n  sendRequest(\"/\");\n\n  static_cast<TestUniplexTransport *>(transport_)->setByteEventsEnabled(false);\n  MockByteEventCallback mockByteEventCallback;\n  auto handler = addHandlerWithByteEvents(\n      mockByteEventCallback, uint8_t(HTTPByteEvent::Type::NIC_TX), 0, nullptr);\n\n  EXPECT_CALL(mockByteEventCallback, onByteEventCanceled(_, _))\n      .WillOnce(Invoke([](const HTTPByteEvent &event, const HTTPError &err) {\n        EXPECT_EQ(event.type, HTTPByteEvent::Type::NIC_TX);\n        EXPECT_EQ(err.code, HTTPErrorCode::READ_TIMEOUT);\n      }));\n  evb_.loop();\n}\n\nTEST_P(H12DownstreamSessionTest, TooManyByteEvents) {\n  sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler =\n      addHandlerWithByteEvents(mockByteEventCallback,\n                               0,\n                               uint8_t(HTTPByteEvent::Type::NIC_TX) |\n                                   uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK),\n                               nullptr,\n                               1 /*byte at a time*/);\n\n  EXPECT_CALL(mockByteEventCallback, onByteEventCanceled(_, _)).Times(104);\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_)).Times(96);\n\n  evb_.loop();\n}\n\nTEST_P(H12DownstreamSessionTest, CancelPendingByteEvents) {\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler =\n      addHandlerWithByteEvents(mockByteEventCallback,\n                               0,\n                               uint8_t(HTTPByteEvent::Type::KERNEL_WRITE) |\n                                   uint8_t(HTTPByteEvent::Type::NIC_TX) |\n                                   uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK),\n                               nullptr,\n                               50 /* two body byte events */);\n  // Write timeout\n  transport_->pauseWrites(id);\n\n  EXPECT_CALL(mockByteEventCallback, onByteEventCanceled(_, _)).Times(6);\n  evb_.loop();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(H12DownstreamSessionTest, TxAckBeforeScheduleByteEvents) {\n  static_cast<TestUniplexTransport *>(transport_)->setFastTxAckEvents(true);\n  sendRequest(\"/\");\n\n  MockByteEventCallback mockByteEventCallback;\n  auto handler =\n      addHandlerWithByteEvents(mockByteEventCallback,\n                               0,\n                               uint8_t(HTTPByteEvent::Type::NIC_TX) |\n                                   uint8_t(HTTPByteEvent::Type::CUMULATIVE_ACK),\n                               nullptr);\n\n  EXPECT_CALL(mockByteEventCallback, onByteEvent(_)).Times(2);\n  evb_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, IngressBackpressureUnderLimit) {\n  // Send two 20,000 byte body post requests (which does not exceed the default\n  // buffer limit).\n  auto id1 =\n      sendRequest(\"/\", folly::IOBuf::copyBuffer(std::string(20000, 'a')));\n  auto id2 =\n      sendRequest(\"/\", folly::IOBuf::copyBuffer(std::string(20000, 'b')));\n\n  // Since we're below the default buffered threshold, we shouldn't be calling\n  // pauseRead.\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, pauseRead(id1)).Times(0);\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, pauseRead(id2)).Times(0);\n\n  /**\n   * Created to maintain a reference to the HTTPSourceHolder so no implicit\n   * invocation to stopReading() occurs when the HTTPSourceHolder goes out\n   * of scope in the handler. This effectively prevents the buffered data\n   * from draining.\n   */\n  HTTPSourceHolder reqSource1{nullptr};\n  HTTPSourceHolder reqSource2{nullptr};\n\n  auto handler1 =\n      addSimpleStrictHandler([this, &reqSource1](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        reqSource1 = std::move(requestSource);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto handler2 =\n      addSimpleStrictHandler([this, &reqSource2](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        reqSource2 = std::move(requestSource);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  loopN(3);\n  reqSource1.stopReading();\n  reqSource2.stopReading();\n  evb_.loop();\n\n  expectResponse(id1, 200);\n  expectResponse(id2, 200);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, IngressBackpressureLimitExceeded) {\n  NiceMock<MockLifecycleObserver> lifecycleCb;\n  session_->addLifecycleObserver(&lifecycleCb);\n  folly::coro::Baton baton;\n\n  auto handler =\n      addSimpleStrictHandler([this, &baton](folly::EventBase *evb,\n                                            HTTPSessionContextPtr /*ctx*/,\n                                            HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        // simulate lack of consuming source, applying backpressure when\n        // exceeding limit\n        co_await baton;\n\n        // read source to completion and return 200\n        auto eomSeen = false;\n        do {\n          auto bodyEvent =\n              co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n          EXPECT_FALSE(bodyEvent.hasException());\n          EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n          eomSeen = bodyEvent->eom;\n        } while (!eomSeen);\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto id = sendRequestHeader(getPostRequest(72'000));\n  // send first 35k, expect LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(35'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // send next 35k, expect LifecycleObserver::onRead() – limit is exceeded at\n  // this point\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(35'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // read loop is blocked, sending next 2k should not invoke\n  // LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(0);\n  sendBody(id, makeBuf(2'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // retire expectation and post baton to consume source\n  session_->removeLifecycleObserver(&lifecycleCb);\n  baton.post();\n  transport_->addReadEvent(nullptr, true);\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, IngressBackpressureSetReadBufferLimit) {\n  NiceMock<MockLifecycleObserver> lifecycleCb;\n  session_->addLifecycleObserver(&lifecycleCb);\n  folly::coro::Baton baton;\n  session_->setReadBufferLimit(500);\n\n  auto handler =\n      addSimpleStrictHandler([this, &baton](folly::EventBase *evb,\n                                            HTTPSessionContextPtr /*ctx*/,\n                                            HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        // simulate lack of consuming source, applying backpressure when\n        // exceeding limit\n        co_await baton;\n\n        // read source to completion and return 200\n        auto eomSeen = false;\n        do {\n          auto bodyEvent =\n              co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n          EXPECT_FALSE(bodyEvent.hasException());\n          EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n          eomSeen = bodyEvent->eom;\n        } while (!eomSeen);\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto id = sendRequestHeader(getPostRequest(850));\n  // send first 300, expect LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(300), /*eom=*/false);\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // send next 300, expect LifecycleObserver::onRead() – limit is exceeded at\n  // this point\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(300), /*eom=*/false);\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // read loop is blocked, sending next 250 should not invoke\n  // LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(0);\n  sendBody(id, makeBuf(250), /*eom=*/false);\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // retire expectation and post baton to consume source\n  session_->removeLifecycleObserver(&lifecycleCb);\n  baton.post();\n  transport_->addReadEvent(nullptr, true);\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, IngressBackpressureLimitExceededTimeout) {\n  // similar to above test, but handles the timeout case\n  folly::coro::Baton baton;\n  // set the conn read timeout for sake of test\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(200));\n  // set the readBufferLimit to 0 (no-op)\n  session_->setReadBufferLimit(0);\n  // post baton after read timeout ms has passed\n  evb_.runAfterDelay([&]() { baton.post(); }, 250);\n\n  auto handler =\n      addSimpleStrictHandler([this, &baton](folly::EventBase *evb,\n                                            HTTPSessionContextPtr /*ctx*/,\n                                            HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        // block on baton\n        co_await baton;\n\n        // connectionError should have queued error in stream source\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_TRUE(bodyEvent.hasException());\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto id = sendRequestHeader(getPostRequest(72'000));\n  // send first 35k, expect LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleObs_, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(35'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // send next 35k, expect LifecycleObserver::onRead() – limit is exceeded at\n  // this point\n  EXPECT_CALL(lifecycleObs_, onRead(_, _, _)).Times(AtLeast(1));\n  sendBody(id, makeBuf(35'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // read loop is blocked, sending next 2k should not invoke\n  // LifecycleObserver::onRead()\n  EXPECT_CALL(lifecycleObs_, onRead(_, _, _)).Times(0);\n  sendBody(id, makeBuf(2'000));\n  transport_->addReadEvent(id, writeBuf_.move());\n  loopN(1);\n\n  // retire expectation and send eof\n  transport_->addReadEvent(nullptr, true);\n\n  evb_.loop();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(HQDownstreamSessionTest, IngressBackpressureOverLimit) {\n  /**\n   * Created to maintain a reference to the HTTPSourceHolder so no implicit\n   * invocation to stopReading() occurs when the HTTPSourceHolder goes out\n   * of scope in the handler. This effectively prevents the buffered data\n   * from draining.\n   */\n  HTTPSourceHolder reqSource1{nullptr};\n  HTTPSourceHolder reqSource2{nullptr};\n\n  /**\n   * Send two 70,000+ byte POST requests (which exceeds the default buffer limit\n   * and should call pauseRead()) and one 20,000 bytes POST request (which\n   * doesn't exceed and should proceed to process regardless of first two\n   * requests exceeding).\n   */\n  auto id1 =\n      sendRequest(\"/\", folly::IOBuf::copyBuffer(std::string(70000, 'a')));\n  auto id2 =\n      sendRequest(\"/\", folly::IOBuf::copyBuffer(std::string(70000, 'b')));\n  auto id3 =\n      sendRequest(\"/\", folly::IOBuf::copyBuffer(std::string(20000, 'c')));\n\n  /**\n   * The test is as follows:\n   *\n   * 1. We send two requests with 70,000+ bytes in the body.\n   *\n   * 2. The first two handlers `std::move` the requestSource outside of the\n   *    lambda scope to prevent an implicit .stopReading(), allowing the buffer\n   *    to fill up. Once we buffer more than the threshold, we expect\n   *    .pauseRead() on id1 and id2. The third handler should be able to read\n   *    the 20,000 byte POST request to completion since the ingress\n   *    backpressure is per-stream (first two requests exceeding should not\n   *    affect third request).\n   */\n\n  // Flag to be set once the third handler has completed execution. Expected to\n  // be true to verify that the third request was not blocked on the first two\n  // requests being limited by backpressure.\n  bool handler3End = false;\n\n  // handler for id3\n  auto handler3 = addSimpleStrictHandler(\n      [this, &handler3End](folly::EventBase *evb,\n                           HTTPSessionContextPtr /*ctx*/,\n                           HTTPSourceHolder requestSource)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        // Handler for id3 should be able to read the post request to\n        // completion\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        bool eomSeen = false;\n        while (!eomSeen) {\n          auto bodyEvent =\n              co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n          EXPECT_FALSE(bodyEvent.hasException());\n          EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n          eomSeen = bodyEvent->eom;\n        }\n\n        handler3End = true;\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  // handler for id2\n  auto handler2 =\n      addSimpleStrictHandler([this, &reqSource2](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        reqSource2 = std::move(requestSource);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  // handler for id1\n  auto handler1 =\n      addSimpleStrictHandler([this, &reqSource1](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        reqSource1 = std::move(requestSource);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  // before any requests have been processed, handler flag should be false\n  EXPECT_FALSE(handler3End);\n\n  // First two requests should have been paused for reading by the socket.\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, pauseRead(id1)).Times(1);\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, pauseRead(id2)).Times(1);\n\n  auto fn = folly::coro::co_invoke([&]() -> folly::coro::Task<void> {\n    // Third request should have been read to completion (handler3End = true).\n    EXPECT_TRUE(handler3End);\n\n    // First two requests should then be resumed after consuming some of the\n    // data.\n    EXPECT_CALL(*muxTransport_->socketDriver_.sock_, resumeRead(id1)).Times(1);\n    EXPECT_CALL(*muxTransport_->socketDriver_.sock_, resumeRead(id2)).Times(1);\n\n    auto bodyEvent1 = co_await co_awaitTry(readBodyEventNoSuspend(reqSource1));\n    auto bodyEvent2 = co_await co_awaitTry(readBodyEventNoSuspend(reqSource2));\n\n    EXPECT_FALSE(bodyEvent1.hasException());\n    EXPECT_FALSE(bodyEvent2.hasException());\n\n    co_return;\n  });\n\n  loopN(4);\n  co_withExecutor(&evb_, std::move(fn)).start();\n  evb_.loop();\n\n  expectResponse(id1, 200);\n  expectResponse(id2, 200);\n  expectResponse(id3, 200);\n\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, IngressBackpressureOverLimitDelayedQPACK) {\n  /**\n   * This test is very similar to the one above. It sends a large POST request\n   * (70KB) without consuming the source, causing backpressure logic to kick in\n   * and pause the stream. However, we will delay flushing QPACK instructions to\n   * validate that when the QPACK instructions are received, we don't\n   * prematurely resume reading from an ingress limited stream.\n   */\n  auto req = getPostRequest(70000);\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/false, /*flushQPACK=*/false);\n  sendBody(id, folly::IOBuf::copyBuffer(std::string(70000, 'a')));\n  transport_->addReadEvent(id, writeBuf_.move());\n\n  /**\n   * Created to maintain a reference to the HTTPSourceHolder so no implicit\n   * invocation to stopReading() occurs when the HTTPSourceHolder goes out\n   * of scope in the handler. This effectively prevents the buffered data\n   * from draining.\n   */\n  HTTPSourceHolder reqSource1{nullptr};\n\n  /**\n   * `pauseRead()` should be invoked on the quic socket twice here, once due\n   * to QPACK delay, and another due to ingress limit being exceeded.\n   * `resumeRead()` should never be called yet since we will not be consuming\n   * the buffer or flushing QPACK instructions.\n   */\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, pauseRead(id)).Times(2);\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, resumeRead(id)).Times(0);\n\n  auto handler =\n      addSimpleStrictHandler([this, &reqSource1](folly::EventBase *evb,\n                                                 HTTPSessionContextPtr /*ctx*/,\n                                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        reqSource1 = std::move(requestSource);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  loopN(2);\n\n  EXPECT_GT(multiCodec_->getQPACKEncoderWriteBuf().chainLength(), 0);\n\n  // flushing QPACK should not call resumeRead()\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, resumeRead(id)).Times(0);\n  flushQPACKEncoder();\n\n  // send eom for the post req\n  transport_->addReadEvent(id, nullptr, true);\n  loopN(2);\n\n  // consuming some buffered data should resume reading\n  auto fn = folly::coro::co_invoke([&]() -> folly::coro::Task<void> {\n    EXPECT_CALL(*muxTransport_->socketDriver_.sock_, resumeRead(id)).Times(1);\n\n    auto bodyEvent = co_await co_awaitTry(readBodyEventNoSuspend(reqSource1));\n\n    EXPECT_FALSE(bodyEvent.hasException());\n\n    co_return;\n  });\n\n  co_withExecutor(&evb_, std::move(fn)).start();\n  loopN(1);\n\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, HQEgressStreamLimitExceeded) {\n  auto &socketDriver = muxTransport_->socketDriver_;\n  auto quicSocket = socketDriver.getSocket();\n\n  auto id = sendRequest(\"/\");\n  auto handler = addSimpleStrictHandler(\n      [this, quicSocket, id](folly::EventBase *evb,\n                             HTTPSessionContextPtr /*ctx*/,\n                             HTTPSourceHolder requestSource)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n\n        // simulate lack of stream flow credits, set flow control wndw to 0.\n        quicSocket->setStreamFlowControlWindow(id, 0);\n\n        // response will have to be returned in batches as we incrementally gain\n        // more flow control credit\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(20000));\n      });\n  loopN(4);\n\n  // Since we're blocked on egress limit, we can validate that looping one more\n  // time won't write any new data\n  auto numWriteChainCalls = socketDriver.getNumWriteChainInvocations(id);\n\n  // looping again without receiving additional FC credit won't resume write\n  // loop (and invoke writeChain())\n  loopN(1);\n  EXPECT_EQ(socketDriver.getNumWriteChainInvocations(id), numWriteChainCalls);\n\n  // receiving additional stream FC will resume write loop.\n  quicSocket->setStreamFlowControlWindow(id, 25000);\n  evb_.loop();\n  EXPECT_GT(socketDriver.getNumWriteChainInvocations(id), numWriteChainCalls);\n\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, HQEgressIndependentStreamLimitExceeded) {\n  // Send two requests, only one of which is flow control blocked. The other\n  // stream should complete successfully.\n  auto &socketDriver = muxTransport_->socketDriver_;\n  auto quicSocket = socketDriver.getSocket();\n\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n\n  auto id1 = sendRequest(\"/\");\n  auto id2 = sendRequest(\"/\");\n  auto handler2 = addSimpleStrictHandler(\n      [this, quicSocket, id2](folly::EventBase *evb,\n                              HTTPSessionContextPtr /*ctx*/,\n                              HTTPSourceHolder requestSource)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n\n        // simulate lack of stream flow credits.\n        quicSocket->setStreamFlowControlWindow(id2, 2000);\n\n        // response will have to be returned in batches as we incrementally gain\n        // more flow control credit\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(20000));\n      });\n\n  auto handler1 =\n      addSimpleStrictHandler([this, quicSocket](folly::EventBase *evb,\n                                                HTTPSessionContextPtr /*ctx*/,\n                                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(20000));\n      });\n\n  evb_.loop();\n\n  // we expect the first response to be fully completed\n  expectResponse(id1, 200);\n\n  // we expect the second response to be partially completed and timed out\n  expectResponseHeaders(id2);\n  EXPECT_CALL(callbacks_, onBody(id2, _, _)).Times(AtLeast(1));\n  expectStreamAbort(id2, ErrorCode::FLOW_CONTROL_ERROR);\n  parseOutput();\n}\n\nTEST_P(H12DownstreamSessionTest, BodyError) {\n  sendRequest(\"/bodyError_11\");\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/bodyError_11\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        co_return new ErrorSource(\n            std::string(\"error error error error\"), false, 11);\n      });\n\n  evb_.loop();\n  parseOutput();\n  notExpectedError_ = TransportErrorCode::TIMED_OUT;\n}\n\nTEST_P(HQStaticQPACKDownstreamSessionTest, StaticOnlyQPACK) {\n  // Add some non-static headers to the request and response\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rbjjvdgukrcbrivgehgcvtbrvnjvbgdj\");\n  auto id = sendRequest(req);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        auto resp = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n        resp->msg_->getHeaders().add(\"X-FB-Debug\",\n                                     \"dtklfefbhjgnjudrerfnggurghgigkbi\");\n        co_return resp;\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n  EXPECT_EQ(multiCodec_->getQPACKCodec().getCompressionInfo().egress.inserts_,\n            0);\n  EXPECT_EQ(multiCodec_->getQPACKCodec().getCompressionInfo().ingress.inserts_,\n            0);\n}\n\nTEST_P(HTTPDownstreamSessionTest, TwoRequests) {\n  MockHTTPSessionStats sessionStats;\n  session_->setSessionStats(&sessionStats);\n\n  // Spin the loop once so the settings are processed before the requests\n  // arrive\n  evb_.loopOnce();\n  auto req = getGetRequest(\"/\");\n  // Exercise dynamic table\n  req.getHeaders().add(\"X-FB-Debug\", \"cdhhhnlkjkehtdjrtbintgdcdiinngdh\");\n  auto id1 = sendRequest(req);\n  auto id2 = sendRequest(req);\n\n  auto handler1 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  auto handler2 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        clientCodec_->generateGoaway(writeBuf_, 0, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(writeBuf_.move(), IS_H1());\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  EXPECT_CALL(sessionStats, _recordTransactionsServed(2));\n  EXPECT_CALL(sessionStats, _recordTransactionOpened()).Times(2);\n  EXPECT_CALL(sessionStats, _recordTransactionClosed()).Times(2);\n  EXPECT_CALL(sessionStats, _recordSessionReused());\n  evb_.loop();\n  expectResponse(id1, 200);\n  expectResponse(id2, 200);\n  if (isHQ()) {\n    // Expect QPACK encoder stream has the dynamic header in it\n    EXPECT_EQ(\n        multiCodec_->getQPACKCodec().getCompressionInfo().ingress.inserts_, 0);\n  }\n\n  parseOutput();\n}\n\nCO_TEST_P(H2DownstreamSessionTest, ResetRateLimit) {\n  // send first request (not to be reset below) to verify that the session will\n  // abort any active streams when the rate limit is exceeded\n  auto msg = getPostRequest(/*contentLength=*/200);\n  sendRequest(msg, /*body=*/nullptr, /*eom=*/false, /*eof=*/false);\n  HTTPSourceHolder keepaliveReqSource{};\n\n  // handler extracts out requestSource to prevent destructor invoking\n  // ::stopReading()\n  auto handler = addSimpleStrictHandler(\n      [this, &keepaliveReqSource](folly::EventBase *evb,\n                                  HTTPSessionContextPtr /*ctx*/,\n                                  HTTPSourceHolder requestSource)\n          -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(\n            requestSource, HTTPMethod::POST, /*path=*/\"/\", /*eom=*/false);\n        keepaliveReqSource.setSource(requestSource.release());\n        co_return HTTPFixedSource::makeFixedResponse(200);\n      });\n  loopN(4);\n\n  // saturate reset rate limit, ::onIngressError will be invoked\n  EXPECT_CALL(lifecycleObs_, onIngressError(_, kErrorMessage));\n  constexpr uint16_t kResetRateLimit = 1'000;\n  for (uint32_t i = 0; i <= kResetRateLimit; i++) {\n    auto req = getGetRequest(\"/\");\n    auto id = sendRequest(req);\n    resetStream(id, ErrorCode::STREAM_CLOSED);\n  }\n  loopN(2);\n\n  // verify first reqSource was aborted by expecting error on reading body event\n  folly::coro::co_withExecutor(\n      &evb_,\n      [](HTTPSourceHolder reqSource) -> folly::coro::Task<void> {\n        auto bodyEvent = co_await co_awaitTry(reqSource.readBodyEvent());\n        CHECK(bodyEvent.hasException());\n        auto *ex = bodyEvent.tryGetExceptionObject<HTTPError>();\n        EXPECT_TRUE(ex && ex->code == HTTPErrorCode::CANCEL);\n      }(std::move(keepaliveReqSource)))\n      .start();\n\n  evb_.loop();\n  co_return;\n}\n\nTEST_P(HTTPDownstreamSessionTest, StreamIdSeqNo) {\n  // Spin the loop once so the settings are processed before the requests\n  // arrive\n  evb_.loopOnce();\n  auto req = getGetRequest(\"/\");\n  // Exercise dynamic table\n  req.getHeaders().add(\"X-FB-Debug\", \"cdhhhnlkjkehtdjrtbintgdcdiinngdh\");\n  auto id1 = sendRequest(req);\n  auto id2 = sendRequest(req);\n\n  auto handler1 =\n      addSimpleStrictHandler([this, id1](folly::EventBase *evb,\n                                         HTTPSessionContextPtr /*ctx*/,\n                                         HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        EXPECT_EQ(session_->getSequenceNumberFromStreamId(id1), 0);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  auto handler2 =\n      addSimpleStrictHandler([this, id2](folly::EventBase *evb,\n                                         HTTPSessionContextPtr /*ctx*/,\n                                         HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        clientCodec_->generateGoaway(writeBuf_, 0, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(writeBuf_.move(), IS_H1());\n        EXPECT_EQ(session_->getSequenceNumberFromStreamId(id2), 1);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id1, 200);\n  expectResponse(id2, 200);\n  if (isHQ()) {\n    // Expect QPACK encoder stream has the dynamic header in it\n    EXPECT_EQ(\n        multiCodec_->getQPACKCodec().getCompressionInfo().ingress.inserts_, 0);\n  }\n\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, InvalidPeerCertificate) {\n  auto req = getGetRequest(\"/\");\n  auto id1 = sendRequest(req);\n\n  MockAsyncTransportCertificate *mockCert = nullptr;\n  // set up expectations\n  if (isHQ()) {\n    mockCert = muxTransport_->socketDriver_.mockCertificate.get();\n  } else {\n    auto *uniplexTransport = static_cast<TestUniplexTransport *>(transport_);\n    mockCert = &uniplexTransport->mockCertificate;\n  }\n\n  EXPECT_CALL(*mockCert, getIdentity()).WillOnce(Return(\"bad_actor\"));\n\n  auto handler1 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr ctx,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        CHECK(ctx);\n        auto peerIdentity = ctx->getPeerCertificate()->getIdentity();\n        co_return peerIdentity == \"good_actor\"\n            ? HTTPFixedSource::makeFixedResponse(200, \"ok!\")\n            : HTTPFixedSource::makeFixedResponse(407, \"not ok!\");\n      });\n\n  evb_.loop();\n  expectResponse(id1, 407);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, LifecycleObserver) {\n  StrictMock<MockLifecycleObserver> lifecycleCb;\n  EXPECT_CALL(lifecycleCb, onAttached(_));\n  session_->addLifecycleObserver(&lifecycleCb);\n\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onSettings(_, _));\n    if (!isHQ()) {\n      EXPECT_CALL(lifecycleCb, onSettingsAck(_));\n    }\n  }\n  EXPECT_CALL(lifecycleCb, onTransactionAttached(_));\n  EXPECT_CALL(lifecycleCb, onRequestBegin(_));\n  EXPECT_CALL(lifecycleCb, onActivateConnection(_));\n  EXPECT_CALL(lifecycleCb, onIngressMessage(_, _));\n  if (IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onIngressEOF(_));\n  }\n  EXPECT_CALL(lifecycleCb, onWrite(_, _)).Times(AtLeast(1));\n  EXPECT_CALL(lifecycleCb, onRequestEnd(_, _));\n  EXPECT_CALL(lifecycleCb, onDrainStarted(_));\n  EXPECT_CALL(lifecycleCb, onTransactionDetached(_));\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onGoaway(_, _, _));\n  }\n  EXPECT_CALL(lifecycleCb, onDeactivateConnection(_));\n  EXPECT_CALL(lifecycleCb, onDestroy(_));\n\n  clientCodec_->generateSettingsAck(writeBuf_);\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr ctx,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto peerIP = ctx->getPeerAddress();\n        auto localIP = ctx->getLocalAddress();\n        EXPECT_GT(peerIP.getAddressStr().size(), 0);\n        EXPECT_GT(localIP.getAddressStr().size(), 0);\n        const auto &setupTinfo = ctx->getSetupTransportInfo();\n        EXPECT_EQ(*setupTinfo.appProtocol, \"blarf\");\n        wangle::TransportInfo curInfo;\n        bool gotSocketInfo =\n            ctx->getCurrentTransportInfo(&curInfo, /*includeSetupFields=*/true);\n        EXPECT_TRUE(gotSocketInfo);\n        EXPECT_EQ(*curInfo.appProtocol, \"blarf\");\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, LifecycleObserverRemoveCallback) {\n  StrictMock<MockLifecycleObserver> lifecycleCb, otherLifecycleCb;\n  EXPECT_CALL(lifecycleCb, onAttached(_));\n  session_->addLifecycleObserver(&lifecycleCb);\n  EXPECT_CALL(otherLifecycleCb, onAttached(_));\n  session_->addLifecycleObserver(&otherLifecycleCb);\n\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  EXPECT_CALL(otherLifecycleCb, onRead(_, _, _))\n      .Times(AtLeast(1))\n      .WillOnce([&, otherLifecycleCbPtr = &otherLifecycleCb]() {\n        // remove otherLifecycleCb observer from session\n        session_->removeLifecycleObserver(otherLifecycleCbPtr);\n      });\n\n  // since we removed the observer, we should never expect a call\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onSettings(_, _));\n    EXPECT_CALL(otherLifecycleCb, onSettings(_, _)).Times(0);\n    if (!isHQ()) {\n      EXPECT_CALL(lifecycleCb, onSettingsAck(_));\n      EXPECT_CALL(otherLifecycleCb, onSettingsAck(_)).Times(0);\n    }\n  }\n  EXPECT_CALL(lifecycleCb, onTransactionAttached(_));\n  EXPECT_CALL(otherLifecycleCb, onTransactionAttached(_)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onRequestBegin(_));\n  EXPECT_CALL(otherLifecycleCb, onRequestBegin(_)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onActivateConnection(_));\n  EXPECT_CALL(otherLifecycleCb, onActivateConnection(_)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onIngressMessage(_, _));\n  EXPECT_CALL(otherLifecycleCb, onIngressMessage(_, _)).Times(0);\n\n  if (IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onIngressEOF(_));\n    EXPECT_CALL(otherLifecycleCb, onIngressEOF(_)).Times(0);\n  }\n  EXPECT_CALL(lifecycleCb, onWrite(_, _)).Times(AtLeast(1));\n  EXPECT_CALL(otherLifecycleCb, onWrite(_, _)).Times(AnyNumber());\n\n  EXPECT_CALL(lifecycleCb, onRequestEnd(_, _));\n  EXPECT_CALL(otherLifecycleCb, onRequestEnd(_, _)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onDrainStarted(_));\n  EXPECT_CALL(otherLifecycleCb, onDrainStarted(_)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onTransactionDetached(_));\n  EXPECT_CALL(otherLifecycleCb, onTransactionDetached(_)).Times(0);\n\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onGoaway(_, _, _));\n    EXPECT_CALL(otherLifecycleCb, onGoaway(_, _, _)).Times(0);\n  }\n\n  EXPECT_CALL(lifecycleCb, onDeactivateConnection(_));\n  EXPECT_CALL(otherLifecycleCb, onDeactivateConnection(_)).Times(0);\n\n  EXPECT_CALL(lifecycleCb, onDestroy(_));\n  EXPECT_CALL(otherLifecycleCb, onDestroy(_)).Times(0);\n\n  clientCodec_->generateSettingsAck(writeBuf_);\n  // two loops neede for h3 to pass\n  evb_.loopOnce();\n  evb_.loopOnce();\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr ctx,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto peerIP = ctx->getPeerAddress();\n        auto localIP = ctx->getLocalAddress();\n        EXPECT_GT(peerIP.getAddressStr().size(), 0);\n        EXPECT_GT(localIP.getAddressStr().size(), 0);\n        const auto &setupTinfo = ctx->getSetupTransportInfo();\n        EXPECT_EQ(*setupTinfo.appProtocol, \"blarf\");\n        wangle::TransportInfo curInfo;\n        bool gotSocketInfo =\n            ctx->getCurrentTransportInfo(&curInfo, /*includeSetupFields=*/true);\n        EXPECT_TRUE(gotSocketInfo);\n        EXPECT_EQ(*curInfo.appProtocol, \"blarf\");\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, MultiLifecycleObserver) {\n  StrictMock<MockLifecycleObserver> lifecycleCb, otherLifecycleCb;\n  EXPECT_CALL(lifecycleCb, onAttached(_));\n  session_->addLifecycleObserver(&lifecycleCb);\n  EXPECT_CALL(otherLifecycleCb, onAttached(_));\n  session_->addLifecycleObserver(&otherLifecycleCb);\n\n  EXPECT_CALL(lifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  EXPECT_CALL(otherLifecycleCb, onRead(_, _, _)).Times(AtLeast(1));\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onSettings(_, _));\n    EXPECT_CALL(otherLifecycleCb, onSettings(_, _));\n    if (!isHQ()) {\n      EXPECT_CALL(lifecycleCb, onSettingsAck(_));\n      EXPECT_CALL(otherLifecycleCb, onSettingsAck(_));\n    }\n  }\n  EXPECT_CALL(lifecycleCb, onTransactionAttached(_));\n  EXPECT_CALL(otherLifecycleCb, onTransactionAttached(_));\n\n  EXPECT_CALL(lifecycleCb, onRequestBegin(_));\n  EXPECT_CALL(otherLifecycleCb, onRequestBegin(_));\n\n  EXPECT_CALL(lifecycleCb, onActivateConnection(_));\n  EXPECT_CALL(otherLifecycleCb, onActivateConnection(_));\n\n  EXPECT_CALL(lifecycleCb, onIngressMessage(_, _));\n  EXPECT_CALL(otherLifecycleCb, onIngressMessage(_, _));\n  if (IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onIngressEOF(_));\n    EXPECT_CALL(otherLifecycleCb, onIngressEOF(_));\n  }\n  EXPECT_CALL(lifecycleCb, onWrite(_, _)).Times(AtLeast(1));\n  EXPECT_CALL(otherLifecycleCb, onWrite(_, _)).Times(AtLeast(1));\n\n  EXPECT_CALL(lifecycleCb, onRequestEnd(_, _));\n  EXPECT_CALL(otherLifecycleCb, onRequestEnd(_, _));\n\n  EXPECT_CALL(lifecycleCb, onDrainStarted(_));\n  EXPECT_CALL(otherLifecycleCb, onDrainStarted(_));\n\n  EXPECT_CALL(lifecycleCb, onTransactionDetached(_));\n  EXPECT_CALL(otherLifecycleCb, onTransactionDetached(_));\n\n  if (!IS_H1()) {\n    EXPECT_CALL(lifecycleCb, onGoaway(_, _, _));\n    EXPECT_CALL(otherLifecycleCb, onGoaway(_, _, _));\n  }\n\n  EXPECT_CALL(lifecycleCb, onDeactivateConnection(_));\n  EXPECT_CALL(otherLifecycleCb, onDeactivateConnection(_));\n\n  EXPECT_CALL(lifecycleCb, onDestroy(_));\n  EXPECT_CALL(otherLifecycleCb, onDestroy(_));\n\n  clientCodec_->generateSettingsAck(writeBuf_);\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr ctx,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto peerIP = ctx->getPeerAddress();\n        auto localIP = ctx->getLocalAddress();\n        EXPECT_GT(peerIP.getAddressStr().size(), 0);\n        EXPECT_GT(localIP.getAddressStr().size(), 0);\n        const auto &setupTinfo = ctx->getSetupTransportInfo();\n        EXPECT_EQ(*setupTinfo.appProtocol, \"blarf\");\n        wangle::TransportInfo curInfo;\n        bool gotSocketInfo =\n            ctx->getCurrentTransportInfo(&curInfo, /*includeSetupFields=*/true);\n        EXPECT_TRUE(gotSocketInfo);\n        EXPECT_EQ(*curInfo.appProtocol, \"blarf\");\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, NonKeepAliveResponse) {\n  auto req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  req.setWantsKeepalive(false);\n  auto id = sendRequestHeader(req, true);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler1 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await requestSource.readHeaderEvent();\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, AntiPipeline) {\n  // Send two requests in one flight.  The parse loop will pause until\n  // the first request detaches.\n  InSequence enforceOrder;\n  auto id1 = sendRequest(\"/\");\n  auto id2 = sendRequest(\"/\", nullptr, true, true);\n  bool handler1complete = false;\n  auto handler1 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        EXPECT_EQ(*requestSource.getStreamID(), id1);\n        auto onEOMSource = new OnEOMSource(\n            HTTPFixedSource::makeFixedResponse(200, makeBuf(100)),\n            [&handler1complete]() -> OnEOMSource::CallbackReturn {\n              handler1complete = true;\n              co_return folly::none;\n            });\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return onEOMSource;\n      });\n  auto handler2 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        EXPECT_EQ(*requestSource.getStreamID(), id2);\n        EXPECT_TRUE(handler1complete);\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(206, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id1, 200);\n  expectResponse(id2, 206);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, AntiPipelineCancel) {\n  // Send two requests in one flight.  Cancel the readLoop while waiting for\n  // the antiPipelineBaton_\n  InSequence enforceOrder;\n  auto id1 = sendRequest(\"/\");\n  sendRequest(\"/\", nullptr, true, true);\n  auto handler1 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        EXPECT_EQ(*requestSource.getStreamID(), id1);\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        // cancel the session in the next loop\n        evb->runInLoop([this] { cancellationSource_.requestCancellation(); });\n        co_await folly::coro::sleepReturnEarlyOnCancel(\n            std::chrono::seconds(60));\n        auto cancelToken = co_await folly::coro::co_current_cancellation_token;\n        EXPECT_TRUE(cancelToken.isCancellationRequested());\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  // request2 is never parsed,\n  auto handler2 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource);\n        co_return nullptr;\n      });\n  evb_.loop();\n  // request1 response is\n  EXPECT_TRUE(transportState_.writeEvents.empty());\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\n// H2 only, H1 needs to test with transport read error\nTEST_P(H2QDownstreamSessionTest, ResetStream) {\n  sendRequest(\"/\", nullptr, false, false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto id = *requestSource.getStreamID();\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", false);\n        resetStream(id, ErrorCode::CANCEL);\n        transport_->addReadEvent(writeBuf_.move(), true);\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_TRUE(bodyEvent.hasException());\n        auto err = getHTTPError(bodyEvent);\n        EXPECT_TRUE(isCancelled(err.code));\n        EXPECT_TRUE(err.msg.ends_with(\"details=received RST_STREAM from peer\"));\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  parseOutput();\n}\n\nTEST_P(H2QDownstreamSessionTest, ResetEgressIngressOpen) {\n  sendRequest(\"/\", nullptr, false, false);\n\n  auto expectCoroCancelled =\n      [](HTTPSourceHolder requestSource) -> folly::coro::Task<void> {\n    auto err = co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n    EXPECT_TRUE(err.hasException());\n    auto httpErr = getHTTPError(err);\n    EXPECT_EQ(httpErr.code, HTTPErrorCode::CORO_CANCELLED);\n    EXPECT_EQ(httpErr.msg,\n              \"HTTP source aborted err=CORO_CANCELLED, details=Sent reset\");\n  };\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", false);\n        co_withExecutor(evb, expectCoroCancelled(std::move(requestSource)))\n            .start();\n        co_return new HTTPErrorSource(HTTPError(HTTPErrorCode::CANCEL));\n      });\n\n  evb_.loop();\n}\n\nTEST_P(H2QDownstreamSessionTest, ResetStreamAwaitingHeaders) {\n  sendRequest(\"/\", nullptr, false, false);\n\n  class SleepAndErrorSource : public HTTPSource {\n   public:\n    folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n      co_await folly::coro::sleepReturnEarlyOnCancel(std::chrono::seconds(60));\n      EXPECT_TRUE((co_await folly::coro::co_current_cancellation_token)\n                      .isCancellationRequested());\n      delete this;\n      co_yield folly::coro::co_error(\n          HTTPError(HTTPErrorCode::CANCEL, \"cancelled by session\"));\n    }\n\n    folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t) override {\n      XCHECK(false);\n    }\n\n    void stopReading(\n        folly::Optional<const proxygen::coro::HTTPErrorCode>) override {\n      XCHECK(false);\n    }\n  };\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto id = requestSource.getStreamID();\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", false);\n        // Queue a reset stream\n        resetStream(*id, ErrorCode::CANCEL);\n        transport_->addReadEvent(writeBuf_.move(), true);\n        co_return new SleepAndErrorSource();\n      });\n\n  evb_.loop();\n}\n\nTEST_P(HTTPDownstreamSessionTest, HoldContext) {\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  HTTPSessionContextPtr ctxHolder;\n  auto handler =\n      addSimpleStrictHandler([this, &ctxHolder](folly::EventBase *evb,\n                                                HTTPSessionContextPtr ctx,\n                                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        EXPECT_EQ(session_->numIncomingStreams(), 1);\n        EXPECT_EQ(session_->numOutgoingStreams(), 0);\n        ctxHolder = std::move(ctx);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  loopN(2); // takes 2 loops to process\n  if (isHQ()) {\n    // MockQuicSocketDriver takes extra loops to egress\n    loopN(2);\n  }\n  expectResponse(id, 200);\n  parseOutput();\n  evb_.loopOnce();\n  EXPECT_EQ(session_->numIncomingStreams(), 0);\n  ctxHolder.reset();\n  evb_.loopOnce();\n  if (isHQ()) {\n    // H3 has to wait for all control stream data to be ack'd before closing\n    evb_.loop();\n  }\n}\n\nTEST_P(H2DownstreamSessionTest, ResetStreamNoError) {\n  // Client sends RST_STREAM with NO_ERROR while server has not finished\n  // consuming ingress.\n  sendRequest(\"/\", nullptr, /*eom=*/false, /*eof=*/false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase * /*evb*/,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto id = *requestSource.getStreamID();\n        co_await expectRequest(\n            requestSource, HTTPMethod::GET, \"/\", /*eom=*/false);\n\n        // Inbound RST_STREAM with NO_ERROR from the peer.\n        resetStream(id, ErrorCode::NO_ERROR);\n        transport_->addReadEvent(writeBuf_.move(), /*eom=*/true);\n\n        // Expect the body read to surface a CANCEL error (normalized).\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_TRUE(bodyEvent.hasException());\n\n        auto err = getHTTPError(bodyEvent);\n        EXPECT_TRUE(isCancelled(err.code));\n        co_return nullptr;\n      });\n\n  evb_.loop();\n}\n\n// H2 only, H1 needs to test with transport write error\nTEST_P(H2QDownstreamSessionTest, StopReadingDefault500Source) {\n  // Stop reading as soon as headers are received without returning source. This\n  // defaults to 500 source internally.\n  auto id = sendRequest(\"/\", nullptr, false, false);\n\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        requestSource.stopReading();\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectResponse(id, 500, nullptr, false);\n  if (isHQ()) {\n    EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].error,\n              HTTP3::ErrorCode::HTTP_NO_ERROR);\n  } else {\n    expectStreamAbort(id, ErrorCode::NO_ERROR);\n  }\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, ContentLengthMismatch) {\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n\n        // body event should have an exception due to content length mismatch\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_TRUE(bodyEvent.hasException());\n        auto httpError = getHTTPError(bodyEvent);\n        EXPECT_EQ(httpError.code, HTTPErrorCode::CONTENT_LENGTH_MISMATCH);\n        EXPECT_EQ(httpError.msg.rfind(\n                      \"Content-Length/body mismatch on ingress: expected= \", 0),\n                  0);\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  // create msg with content length = 200\n  auto msg = getPostRequest(/*contentLength=*/200);\n  msg.setIsChunked(true);\n  msg.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  auto id = sendRequestHeader(std::move(msg));\n  transport_->addReadEvent(id, writeBuf_.move(), /*eom=*/false);\n  loopN(1);\n\n  if (IS_H1()) {\n    sendBody(id, makeBuf(300));\n    transport_->addReadEvent(id, writeBuf_.move());\n  } else {\n    sendBody(id, makeBuf(100), /*eom=*/true);\n    transport_->addReadEvent(id, writeBuf_.move(), /*eof(h1)/eom(hq)*/ isHQ());\n  }\n\n  evb_.loop();\n  if (IS_H1()) {\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  } else {\n    expectStreamAbort(id, ErrorCode::INTERNAL_ERROR);\n  }\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, HandlerThrowsResetStream) {\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headers = co_await co_awaitTry(requestSource.readHeaderEvent());\n        EXPECT_FALSE(headers.hasException());\n        EXPECT_TRUE(headers->eom);\n        if (!isHQ()) {\n          transport_->addReadEvent(nullptr, true);\n        }\n        throw std::runtime_error(\"Panic at the disco!\");\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectResponse(id, 500, nullptr, true);\n  parseOutput();\n}\n\n// H2 only.  H1 cannot serialize body after trailers.  It's trailers or EOM\nTEST_P(H2QDownstreamSessionTest, StateMachineError) {\n  // Send request, send trailers (no EOM), send body\n  auto req = getGetRequest();\n  req.getHeaders().removeAll();\n  req.getHeaders().add(\"cache-control\", \"max-age=0\"); // static header\n  auto id = sendRequest(req, nullptr, false, false);\n  HTTPHeaders trailers;\n  // Use QPACK static table only here\n  trailers.set(\"x-forwarded-for\", \"\");\n  clientCodec_->generateTrailers(writeBuf_, id, trailers);\n  if (isHQ()) {\n    EXPECT_TRUE(multiCodec_->getQPACKEncoderWriteBuf().empty());\n  } else {\n    // Our H2 codec always marks EOM on trailers.  Clear the flag.\n    auto frameHeader = writeBuf_.split(9);\n    frameHeader->writableData()[4] &= ~0x01;\n    frameHeader->prependChain(writeBuf_.move());\n    writeBuf_.append(std::move(frameHeader));\n  }\n  clientCodec_->generateBody(\n      writeBuf_, id, makeBuf(10), HTTPCodec::NoPadding, false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto err = co_await expectHeaderError(\n            requestSource, HTTPErrorCode::INVALID_STATE_TRANSITION);\n        XCHECK(err.httpMessage);\n        EXPECT_EQ(\n            err.httpMessage->getHeaders().getSingleOrEmpty(\"cache-control\"),\n            \"max-age=0\");\n        transport_->addReadEvent(nullptr, true);\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectStreamAbort(id, ErrorCode::PROTOCOL_ERROR);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, SimplePost) {\n  auto id = sendRequest(\"/\", makeBuf(100), true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        auto bodyEvent =\n            co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_FALSE(bodyEvent.hasException());\n        EXPECT_EQ(bodyEvent->eventType, HTTPBodyEvent::BODY);\n        EXPECT_EQ(bodyEvent->event.body.chainLength(), 100);\n        EXPECT_TRUE(bodyEvent->eom);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest,\n       HandlerGracefullyTerminateIngressFixedResponse) {\n  auto id = sendRequest(\"/\", makeBuf(2000), false);\n\n  /**\n   * We allow returning a source without reading request to completion, however\n   * some caveats worth noting:\n   *\n   *\n   * - in http/2 & http/3 case, we issue a RST_STREAM (h2) / STOP_SENDING (h3)\n   *   frame after the response is sent with reason = NO_ERROR as outlined by\n   *   the RFC\n   *\n   * - in http/1.1, in the event of returning a source gracefully (i.e. not\n   * - yielding or throwing an exception), we keep the connection alive\n   *\n   *\n   */\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        co_return HTTPFixedSource::makeFixedResponse(401, \"unauthorized!\");\n      });\n\n  loopN(2);\n  EXPECT_FALSE(transportState_.closedWithReset);\n  EXPECT_FALSE(transportState_.writesClosed);\n  expectResponse(id, 401, nullptr, true);\n  evb_.loop();\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, HandlerGracefullyTerminateIngressYieldError) {\n  auto id = sendRequest(\"/\", makeBuf(2000), false);\n  // Yielding an error from the handler defaults to 500 status code. Almost\n  // identical to the previous test, except http/1.1 behavior differs: this\n  // aborts the connection as this is not considered a \"graceful\" close\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        // returning source without reading request to completion should work\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        requestSource.stopReading();\n        co_yield folly::coro::co_error(\n            HTTPError(HTTPErrorCode::REFUSED_STREAM, \"not interested!\"));\n      });\n\n  loopN(2);\n  // h1 closes writes\n  EXPECT_EQ(IS_H1(), transportState_.writesClosed);\n  expectResponse(id, 500, nullptr, true);\n  evb_.loop();\n  parseOutput();\n}\n\n// Push tests -- H2/Q only, H1 doesn't support push\nTEST_P(H2QDownstreamSessionTest, Push) {\n  // Receive a request and send a complete response including a complete push\n  auto id = sendRequest(\"/\", nullptr, true, false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return makeResponseWithPush([this]() -> OnEOMSource::CallbackReturn {\n          EXPECT_EQ(session_->numOutgoingStreams(), 1);\n          auto pushStream = isHQ() ? 15 : 2; // hard-coded\n          clientCodec_->generateGoaway(\n              writeBuf_, pushStream + 4, ErrorCode::NO_ERROR);\n          transport_->addReadEvent(writeBuf_.move(), false);\n          co_return folly::none;\n        });\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  expectPush(id, /*response=*/true, /*error=*/std::nullopt);\n  parseOutput();\n}\n\nTEST_P(H2QDownstreamSessionTest, PushEgressRstStream) {\n  // Receive a request and send a complete response including a push, but\n  // reset the push before EOM\n  auto id = sendRequest(\"/\", nullptr, true, false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return makeResponseWithPush([this]() -> OnEOMSource::CallbackReturn {\n          EXPECT_EQ(session_->numOutgoingStreams(), 1);\n          clientCodec_->generateGoaway(writeBuf_, 19, ErrorCode::NO_ERROR);\n          transport_->addReadEvent(writeBuf_.move(), false);\n          co_return HTTPError(HTTPErrorCode::CANCEL, \"\");\n        });\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  expectPush(id, /*response=*/false, ErrorCode::CANCEL);\n  parseOutput();\n}\n\nTEST_P(H2QDownstreamSessionTest, PushIngressRstStream) {\n  // Receive a request and send a complete response including a push, but\n  // receive a RST_STREAM on the push before finishing it.\n  auto id = sendRequest(\"/\");\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return makeResponseWithPush([this]() -> OnEOMSource::CallbackReturn {\n          EXPECT_EQ(session_->numOutgoingStreams(), 1);\n          if (isHQ()) {\n            // Wait a couple loops for the headers to get flushed\n            co_await rescheduleN(2);\n          }\n          auto pushStream = isHQ() ? 15 : 2; // hard-coded\n          resetStream(pushStream, ErrorCode::CANCEL);\n          clientCodec_->generateGoaway(\n              writeBuf_, pushStream + 4, ErrorCode::NO_ERROR);\n          transport_->addReadEvent(writeBuf_.move(), false);\n          // stall this coroutine, it will get cancelled.\n          TimedBaton baton(&evb_);\n          co_await baton.wait();\n          EXPECT_EQ(baton.getStatus(), TimedBaton::Status::cancelled);\n          co_return folly::none;\n        });\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  expectPush(id, /*response=*/false, /*error=*/std::nullopt);\n  parseOutput();\n}\n\n// TODO: Test HQ MAX_PUSH_ID exceeded too\nTEST_P(H2QDownstreamSessionTest, EgressPushStreamLimitExceeded) {\n  if (isHQ()) {\n    muxTransport_->socketDriver_.setMaxUniStreams(3);\n  } else {\n    setTestCodecSetting(clientCodec_->getEgressSettings(),\n                        SettingsId::MAX_CONCURRENT_STREAMS,\n                        0);\n    clientCodec_->generateSettings(writeBuf_);\n    transport_->addReadEvent(writeBuf_.move(), false);\n  }\n  evb_.loopOnce();\n\n  // Receive a request and try to push, but it will fail because the peer's\n  // limit is 0\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return makeResponseWithPush([]() -> OnEOMSource::CallbackReturn {\n          XLOG(FATAL) << \"EOM is never read, because session calls stopSending\";\n          co_return folly::none;\n        });\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\n// TODO: HQ push\nTEST_P(H2DownstreamSessionTest, EgressPushNotSupported) {\n  setTestCodecSetting(\n      clientCodec_->getEgressSettings(), SettingsId::ENABLE_PUSH, 0);\n  clientCodec_->generateSettings(writeBuf_);\n  transport_->addReadEvent(writeBuf_.move(), false);\n  evb_.loopOnce();\n\n  // Receive a request and try to push, but it will fail because the peer\n  // disabled push\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return makeResponseWithPush([]() -> OnEOMSource::CallbackReturn {\n          XLOG(FATAL) << \"EOM is never read, because session calls stopSending\";\n          co_return folly::none;\n        });\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\n// H2 only, H1 doesn't support PING\n// H3 supports ping differently\nTEST_P(H2DownstreamSessionTest, Ping) {\n  session_->sendPing();\n  clientCodec_->generatePingRequest(writeBuf_, 12345);\n  // TODO: technically, we should be generating a ping reply with the value\n  // generated in sendPing, but it's not known until later.\n  clientCodec_->generatePingReply(writeBuf_, 54321);\n  EXPECT_CALL(lifecycleObs_, onPingReplySent(_));\n  EXPECT_CALL(lifecycleObs_, onPingReplyReceived());\n  transport_->addReadEvent(writeBuf_.move(), true);\n  evb_.loop();\n  EXPECT_CALL(callbacks_, onPingRequest(_));\n  EXPECT_CALL(callbacks_, onPingReply(12345));\n  parseOutput();\n}\n\n// H2 only, H1 doesn't have settings\n// HQ doesn't invoke the onSettingsOutgoingStreamsFull callback, it can\n// only become full by creating streams\nTEST_P(H2DownstreamSessionTest, StreamsFull) {\n  // It's full again when the conn shuts down\n  EXPECT_CALL(lifecycleObs_, onSettingsOutgoingStreamsFull(_));\n  EXPECT_CALL(lifecycleObs_, onSettingsOutgoingStreamsNotFull(_));\n  setTestCodecSetting(\n      clientCodec_->getEgressSettings(), SettingsId::MAX_CONCURRENT_STREAMS, 0);\n  clientCodec_->generateSettings(writeBuf_);\n  setTestCodecSetting(clientCodec_->getEgressSettings(),\n                      SettingsId::MAX_CONCURRENT_STREAMS,\n                      10);\n  clientCodec_->generateSettings(writeBuf_);\n  transport_->addReadEvent(writeBuf_.move(), true);\n  evb_.loop();\n}\n\n// Flow control tests -- H2 only, H1 doesn't support flow control\n// H3 does f/c in the transport\nTEST_P(H2DownstreamSessionTest, EgressStreamFlowControl) {\n  NiceMock<MockHTTPSessionStats> sessionStats;\n  session_->setSessionStats(&sessionStats);\n  EXPECT_CALL(sessionStats, _recordTransactionStalled()).Times(2);\n  auto id = sendRequest(\"/\", nullptr, false, false);\n  setTestCodecSetting(\n      clientCodec_->getEgressSettings(), SettingsId::INITIAL_WINDOW_SIZE, 0);\n  clientCodec_->generateSettings(writeBuf_);\n  transport_->addReadEvent(writeBuf_.move(), false);\n  clientCodec_->generateEOM(writeBuf_, id);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n\n  auto handler1 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  auto id2 = sendRequest(\"/\");\n  auto handler2 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  loopN(2); // takes 2 loops to process\n  {\n    expectResponseHeaders(id);\n    expectResponseHeaders(id2);\n    parseOutput();\n  }\n  windowUpdate(id, 100);\n  windowUpdate(id2, 100);\n  transport_->addReadEvent(nullptr, true);\n  evb_.loop();\n  expectResponseBody(id);\n  expectResponseEOM(id);\n  expectResponseBody(id2);\n  expectResponseEOM(id2);\n  parseOutput();\n}\n\nTEST_P(H2DownstreamSessionTest, IngressStreamFlowControlError) {\n  session_->setSetting(SettingsId::INITIAL_WINDOW_SIZE, 10);\n  constexpr uint16_t kRequestDataLen = 50'000;\n  auto id = sendRequest(\"/\", makeBuf(kRequestDataLen), true, true);\n\n  // peer violating stream fc should trigger a rst_stream and a connection-level\n  // window_update\n\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource,\n                                   HTTPErrorCode::FLOW_CONTROL_ERROR);\n        co_return nullptr;\n      });\n\n  // amount should be half the window capacity, but better not to hardcode the\n  // value\n  EXPECT_CALL(callbacks_, onWindowUpdate(/*id=*/0, /*amount=*/_));\n  evb_.loop();\n  expectStreamAbort(id, ErrorCode::FLOW_CONTROL_ERROR);\n  parseOutput();\n}\n\n// H2 only, H1 doesn't send RST_STREAM\n// For HQ, a stream over the limit is a protocol error from the transport\nTEST_P(H2DownstreamSessionTest, IngressStreamLimitExceeded) {\n  session_->setSetting(SettingsId::MAX_CONCURRENT_STREAMS, 0);\n\n  // Refuse a request over the stream limit\n  auto id = sendRequest(\"/\", nullptr, true, true);\n\n  evb_.loop();\n  expectStreamAbort(id, ErrorCode::REFUSED_STREAM);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, EgressErrorRst) {\n  // Source returns an error instead of EOM, connection closed with reset\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", true);\n        // session's codec should auto add connection: close\n        co_return new OnEOMSource(\n            HTTPFixedSource::makeFixedResponse(200, makeBuf(100)),\n            []() -> OnEOMSource::CallbackReturn {\n              co_return HTTPError(HTTPErrorCode::CANCEL, \"\");\n            });\n      });\n\n  evb_.loop();\n  expectResponseHeaders(id);\n  parseOutput();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(H1DownstreamSessionTest, EgressErrorRstPipeline) {\n  auto id = sendRequest(\"/\");\n  sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", true);\n        // session's codec should auto add connection: close\n        co_return new OnEOMSource(\n            HTTPFixedSource::makeFixedResponse(200, makeBuf(100)),\n            []() -> OnEOMSource::CallbackReturn {\n              co_return HTTPError(HTTPErrorCode::CANCEL, \"\");\n            });\n      });\n\n  evb_.loop();\n  expectResponseHeaders(id);\n  parseOutput();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\n// H1 Goaway tests\nTEST_P(H1DownstreamSessionTest, ReceiveGoaway) {\n  InSequence enforceOrder;\n  // Send one request, expect response\n  auto id1 = sendRequest(\"/\");\n\n  auto handler1 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", true);\n        // session's codec should auto add connection: close\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  // Client sends request with Connection: close, server terminates connection\n  // after stream completes\n  clientCodec_->generateGoaway(writeBuf_, 0, ErrorCode::NO_ERROR);\n  auto id2 = sendRequest(\"/\");\n  HTTPHeaders expectedHeaders;\n  expectedHeaders.set(HTTP_HEADER_CONNECTION, \"close\");\n\n  auto handler2 =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(\n            requestSource, HTTPMethod::GET, \"/\", true, &expectedHeaders);\n        // session's codec should auto add connection: close\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  evb_.loop();\n  expectResponse(id1, 200);\n  expectResponse(id2, 200, &expectedHeaders);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, SendConnectionClose) {\n  auto id = sendRequest(\"/\");\n  session_->initiateDrain();\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\", true);\n        // session's codec should auto add connection: close\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  HTTPHeaders expectedHeaders;\n  expectedHeaders.set(HTTP_HEADER_CONNECTION, \"close\");\n  expectResponse(id, 200, &expectedHeaders);\n  parseOutput();\n}\n\n// H2 Goaway tests\nTEST_P(H2QDownstreamSessionTest, ReceiveGoaway) {\n  // Server receives GOAWAY, no EOF\n  generateGoaway(0, ErrorCode::NO_ERROR);\n  evb_.loop();\n}\n\nTEST_P(HTTPDownstreamSessionTest, SendGoaway) {\n  // Server sends GOAWAY\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->initiateDrain();\n  evb_.loop();\n}\n\nTEST_P(HTTPDownstreamSessionTest, CloseWhenIdleNoStreams) {\n  // Should shutdown immediately\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->closeWhenIdle();\n  evb_.loopOnce();\n  if (isHQ()) {\n    loopN(3); // Sad\n  }\n}\n\nTEST_P(HTTPDownstreamSessionTest, CloseWhenIdleWithStream) {\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        session_->closeWhenIdle();\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, EOFWithStream) {\n  auto id = sendRequestHeader(getPostRequest(20), false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        EXPECT_TRUE(requestSource);              // haven't read eom yet\n        transport_->addReadEvent(nullptr, true); // and EOF\n        // TODO: local/no-error = TRANSPORT_EOF\n        auto expectedError = (isHQ()) ? HTTPErrorCode::TRANSPORT_READ_ERROR\n                                      : HTTPErrorCode::TRANSPORT_EOF;\n        co_await expectBodyError(requestSource, expectedError);\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  if (IS_H1()) {\n    // H1 egresses a 400 (body error), with no body, and EOM (should it be\n    // rst?)\n    expectResponse(id, 400, nullptr, false);\n  } else if (isHQ()) {\n    // H3 can't send anything after CONNECTION_CLOSE, and can't close the\n    // socket in error, because it's gone.\n  } else {\n    // H2 egresses a 500 (handleRequest error)\n    expectResponse(id, 500, nullptr, false);\n  }\n  parseOutput();\n}\n\nTEST_P(H2QDownstreamSessionTest, ReceiveGoawayWithOpenStream) {\n  // Client sends request and GOAWAY, server terminates connection\n  // after stream completes and final GOAWAY is sent.\n  auto id = sendRequest(\"/\");\n  generateGoaway(0, ErrorCode::NO_ERROR);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        // Force a context switch out of this coro to ensure the GOAWAY\n        // is processed\n        co_await folly::coro::sleepReturnEarlyOnCancel(\n            std::chrono::milliseconds(100));\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  evb_.loop();\n  expectResponse(id, 200);\n  expectGoaway(1, ErrorCode::NO_ERROR);\n  parseOutput();\n}\n\n// HQ doesn't have GOAWAY errors\nTEST_P(H2DownstreamSessionTest, ReceiveGoawayErrorWithOpenStream) {\n  // Client sends unterminated POST request and GOAWAY with error and EOF.\n\n  sendRequest(\"/\", makeBuf(20), false);\n  generateGoaway(0, ErrorCode::PROTOCOL_ERROR);\n  // handleRequest is never called because the error cancels it first\n  evb_.loop();\n  expectGoaway(1, ErrorCode::NO_ERROR);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, IdleTimeoutNoStreams) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  // Just run the loop, the session wtill timeout, drain and close\n  session_->setConnectionReadTimeout(connIdleTimeout);\n  auto start = std::chrono::steady_clock::now();\n  evb_.loop();\n  auto end = std::chrono::steady_clock::now();\n  EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start)\n                .count(),\n            connIdleTimeout.count());\n}\n\nTEST_P(HQDownstreamSessionTest, QuicIdleTimeoutWithStream) {\n  // Send an unterminated POST request, the deliver an idle timeout from the\n  // transport\n  auto id = sendRequest(\"/\", makeBuf(20), false);\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        co_await readBodyEventNoSuspend(requestSource);\n        muxTransport_->socketDriver_.deliverConnectionError(quic::QuicError(\n            quic::LocalErrorCode::IDLE_TIMEOUT, \"idle timeout\"));\n        muxTransport_->socketDriver_.closeConnection();\n        auto res = co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        EXPECT_TRUE(res.hasException());\n        EXPECT_EQ(getHTTPError(res).code, HTTPErrorCode::TRANSPORT_EOF);\n        co_return nullptr;\n      });\n  evb_.loop();\n  EXPECT_CALL(callbacks_, onMessageBegin(id, _)).Times(0);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, IdleTimeoutResetWithPing) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  session_->setConnectionReadTimeout(connIdleTimeout);\n\n  for (int i = 1; i <= 4; i++) {\n    muxTransport_->socketDriver_.addPingReceivedReadEvent(\n        std::chrono::milliseconds(100));\n  }\n  // Just run the loop, the session will timeout, drain and close\n  auto start = std::chrono::steady_clock::now();\n  evb_.loop();\n  auto end = std::chrono::steady_clock::now();\n  EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start)\n                .count(),\n            connIdleTimeout.count() * 2);\n}\n\nTEST_P(H12DownstreamSessionTest, WebSocketUpgrade) {\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setURL(\"/\");\n  req.getHeaders().set(HTTP_HEADER_HOST, \"foo.com\");\n  req.setEgressWebsocketUpgrade();\n  req.setMethod(HTTPMethod::GET);\n  sendRequest(req, nullptr, false, false);\n  HTTPStreamSource respSource(&evb_);\n\n  auto readBlob =\n      [&](HTTPSourceHolder requestSource) -> folly::coro::Task<void> {\n    auto bodyEvent = co_await readBodyEventNoSuspend(requestSource);\n    EXPECT_EQ(bodyEvent.eventType, HTTPBodyEvent::BODY);\n    EXPECT_EQ(bodyEvent.event.body.chainLength(), 100);\n    respSource.body(makeBuf(100), 0, true);\n    transport_->addReadEvent(nullptr, IS_H1());\n    if (!bodyEvent.eom) {\n      bodyEvent = co_await readBodyEventNoSuspend(requestSource);\n    }\n    EXPECT_TRUE(bodyEvent.eom);\n  };\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent = co_await requestSource.readHeaderEvent();\n        if (IS_H1()) {\n          EXPECT_EQ(headerEvent.headers->getMethod(), HTTPMethod::GET);\n        } else {\n          EXPECT_EQ(headerEvent.headers->getMethod(), HTTPMethod::CONNECT);\n        }\n        EXPECT_TRUE(headerEvent.headers->isIngressWebsocketUpgrade());\n        EXPECT_FALSE(headerEvent.eom);\n        auto resp = makeResponse(200);\n        resp->setEgressWebsocketUpgrade();\n        respSource.headers(std::move(resp));\n        sendBody(*requestSource.getStreamID(), makeBuf(100), true, true);\n        session_->closeWhenIdle();\n        co_withExecutor(evb, readBlob(std::move(requestSource))).start();\n        co_return &respSource;\n      });\n  evb_.loop();\n}\n\n// HQ write timeouts\nTEST_P(H12DownstreamSessionTest, WriteTimeout) {\n  // Pause writes after receiving the request so write will timeout\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto id = co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        transport_->pauseWrites(id);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  evb_.loop();\n  // Socket is closed with RST for H1 and H2\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nclass HTTPContinueSource : public HTTPSource {\n public:\n  HTTPContinueSource() {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    if (firstTime_) {\n      firstTime_ = false;\n      auto msg = std::make_unique<HTTPMessage>();\n      msg->setHTTPVersion(1, 1);\n      msg->setStatusCode(100);\n      co_return HTTPHeaderEvent(std::move(msg), false);\n    } else {\n      co_await folly::coro::sleepReturnEarlyOnCancel(std::chrono::seconds(60));\n      delete this;\n      co_yield folly::coro::co_error(HTTPError(HTTPErrorCode::PROTOCOL_ERROR));\n    }\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t) override {\n    co_yield folly::coro::co_error(HTTPError(HTTPErrorCode::PROTOCOL_ERROR));\n  }\n\n  void stopReading(\n      folly::Optional<const proxygen::coro::HTTPErrorCode>) override {\n    delete this;\n  }\n\n private:\n  bool firstTime_{true};\n};\n\nTEST_P(HTTPDownstreamSessionTest, ReadTimeout) {\n  // Send an incomplete POST request.  The handler will read a timeout error,\n  // and the session will send a 408 Client Timeout response automatically.\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequestHeader(getPostRequest(100));\n  sendBody(id, makeBuf(10), false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  generateGoaway(0, ErrorCode::NO_ERROR);\n\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headerEvent =\n            co_await co_awaitTry(requestSource.readHeaderEvent());\n        EXPECT_FALSE(headerEvent.hasException());\n        EXPECT_FALSE(headerEvent->eom);\n        // Start a coro to read the POST body, which never comes\n        co_withExecutor(\n            evb,\n            [](HTTPSourceHolder requestSource) -> folly::coro::Task<void> {\n              auto bodyEvent =\n                  co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n              EXPECT_FALSE(bodyEvent.hasException());\n              EXPECT_FALSE(bodyEvent->eom);\n              co_await expectBodyError(requestSource,\n                                       HTTPErrorCode::READ_TIMEOUT);\n            }(std::move(requestSource)))\n            .start();\n        // return a 100 continue, to show that we can still 408 after\n        // non-final\n        co_return new HTTPContinueSource();\n      });\n  evb_.loop();\n  InSequence enforceOrder;\n  expectResponseHeaders(id, 100);\n  HTTPHeaders expectedHeaders;\n  if (IS_H1()) {\n    expectedHeaders.add(HTTP_HEADER_CONNECTION, \"close\");\n  }\n  expectResponse(id, 408, &expectedHeaders, false);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, HeadersReadTimeout) {\n  // Send an incomplete GET request.  The handler will read a timeout error,\n  // and the session will send a 408 Client Timeout response automatically.\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  std::string incompleteHeaders(\"GET / HTTP/1.1\");\n  HTTPCodec::StreamID id = 1;\n  transport_->addReadEvent(\n      id, folly::IOBuf::copyBuffer(incompleteHeaders), false);\n\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource, HTTPErrorCode::READ_TIMEOUT);\n        // This \"400\" never gets read, the session generates it's own 408\n        co_return HTTPFixedSource::makeFixedResponse(400);\n      });\n  evb_.loop();\n  HTTPHeaders expectedHeaders;\n  expectedHeaders.add(HTTP_HEADER_CONNECTION, \"close\");\n  expectResponse(1, 408, &expectedHeaders, false);\n  parseOutput();\n}\n\nTEST_P(H2DownstreamSessionTest, HeadersReadTimeout) {\n  // Send an incomplete GET request.  H2 delays onMessageBegin until the\n  // headers are complete, so the *connection* timeout will fire\n  session_->setStreamReadTimeout(std::chrono::milliseconds(100));\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequestHeader(getGetRequest(), true);\n  auto buf = writeBuf_.split(writeBuf_.chainLength() - 1);\n  writeBuf_.move();\n  writeBuf_.append(std::move(buf));\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  // No handler is created\n  evb_.loop();\n  // request was not received\n  expectGoaway(0, ErrorCode::NO_ERROR);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, BodyReadTimeout) {\n  // incomplete GET req => session detects ingress timeout => sends 408\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  constexpr std::string_view req{\"GET / HTTP/1.1\"};\n  HTTPCodec::StreamID id = 1;\n  transport_->addReadEvent(id, folly::IOBuf::copyBuffer(req), false);\n\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource, HTTPErrorCode::READ_TIMEOUT);\n        // return exception\n        co_yield folly::coro::co_error(std::runtime_error(\"ex\"));\n      });\n  evb_.loop();\n  HTTPHeaders expectedHeaders;\n  expectedHeaders.add(HTTP_HEADER_CONNECTION, \"close\");\n  expectResponse(1, 408, &expectedHeaders, false);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, HeadersReadTimeout) {\n  // Send an incomplete GET request.\n  session_->setStreamReadTimeout(std::chrono::milliseconds(100));\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequestHeader(getGetRequest(), true);\n  auto buf = writeBuf_.split(writeBuf_.chainLength() - 1);\n  writeBuf_.move();\n  writeBuf_.append(std::move(buf));\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource, HTTPErrorCode::READ_TIMEOUT);\n        // This \"400\" never gets read, the session generates it's own 408\n        co_return HTTPFixedSource::makeFixedResponse(400);\n      });\n  evb_.loop();\n  // request was not received\n  expectResponse(id, 408, /*headers=*/nullptr, /*expectBody=*/false);\n  parseOutput();\n}\n\nTEST_P(H2QDownstreamSessionTest, StreamFlowControlTimeout) {\n  // Open connection flow control window, but starve the request for stream\n  // flow control\n  windowUpdate(4000);\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequest(\"/\");\n  generateGoaway();\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(70000));\n      });\n  evb_.loop();\n  expectResponseHeaders(id);\n  EXPECT_CALL(callbacks_, onBody(id, _, _)).Times(AtLeast(1));\n  expectStreamAbort(id, ErrorCode::FLOW_CONTROL_ERROR);\n  parseOutput();\n}\n\nTEST_P(H2DownstreamSessionTest, ConnFlowControlTimeout) {\n  // Open stream flow control window, but starve the connection of flow\n  // control\n  NiceMock<MockHTTPSessionStats> sessionStats;\n  session_->setSessionStats(&sessionStats);\n  EXPECT_CALL(sessionStats, _recordSessionStalled());\n  windowUpdate(1, 4000);\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(70000));\n      });\n  evb_.loop();\n  expectResponseHeaders(id);\n  EXPECT_CALL(callbacks_, onBody(id, _, _)).Times(AtLeast(1));\n  expectGoaway(id, ErrorCode::FLOW_CONTROL_ERROR);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, ConnFlowControlTimeout) {\n  // Open stream flow control window, but starve the connection of flow\n  // control\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  muxTransport_->socketDriver_.setConnectionFlowControlWindow(6000);\n  auto id = sendRequest(\"/\");\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(70000));\n      });\n\n  evb_.loop();\n  expectResponseHeaders(id);\n  EXPECT_CALL(callbacks_, onBody(id, _, _)).Times(AtLeast(1));\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  parseOutput();\n}\n\n// HQ doesn't have any backpressure now\nTEST_P(H2DownstreamSessionTest, TransactionBufferTimeout) {\n  // Open 2 streams.  Stream 1 keeps getting all the conn flow control,\n  // eventually stream 2 will timeout.\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  auto id1 = sendRequest(\"/\");\n  auto id2 = sendRequest(\"/\");\n  generateGoaway();\n\n  auto handler1 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(\n            200, makeBuf(http2::kInitialWindow * 2));\n      });\n  auto handler2 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(\n            200, makeBuf(http2::kInitialWindow * 2));\n      });\n  auto grantFlowControlFn = [this] {\n    windowUpdate(http2::kInitialWindow);\n    windowUpdate(1, http2::kInitialWindow);\n  };\n  // Will reset the conn flow control timer but not the stream's buf space\n  // timer\n  evb_.runAfterDelay(grantFlowControlFn, 150);\n  evb_.loop();\n  expectResponseHeaders(id1);\n  EXPECT_CALL(callbacks_, onBody(id1, _, _)).Times(AtLeast(1));\n  expectResponseHeaders(id2);\n  expectStreamAbort(id2, ErrorCode::INTERNAL_ERROR);\n  parseOutput();\n}\n\n// H2 only, HQ needs a different way to generate a session error\nTEST_P(H2DownstreamSessionTest, CodecError) {\n  // Send a stream with ID=0\n  if (isHQ()) {\n    multiCodec_->addCodec(1);\n  }\n  clientCodec_->generateHeader(writeBuf_, 1, getGetRequest(), true);\n  auto writeBuf = writeBuf_.move();\n  writeBuf->writableData()[8] = 0;\n  transport_->addReadEvent(1, std::move(writeBuf), false);\n\n  evb_.loop();\n  expectGoaway(0, ErrorCode::PROTOCOL_ERROR);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, CodecHTTPError) {\n  std::string badRequest(\"BLARF\");\n  transport_->addReadEvent(1, folly::IOBuf::copyBuffer(badRequest), false);\n  evb_.loop();\n  expectResponse(1, 400, nullptr, false);\n  parseOutput();\n}\n\nTEST_P(H2DownstreamSessionTest, CodecHTTPError) {\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"200\");\n  auto id = sendRequestHeader(req, true);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  generateGoaway();\n\n  evb_.loop();\n  expectResponse(id, 400, nullptr, false);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, CodecHTTPError) {\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"200\");\n  auto id = sendRequestHeader(req, true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource,\n                                   HTTPErrorCode::HEADER_PARSE_ERROR);\n        // This \"408\" never gets read, the session generates it's own 400\n        co_return HTTPFixedSource::makeFixedResponse(408);\n      });\n  generateGoaway();\n\n  evb_.loop();\n  expectResponse(id, 400, nullptr, false);\n  parseOutput();\n}\n\nTEST_P(H1DownstreamSessionTest, CodecHTTPErrorStreamEgressComplete) {\n  std::string malformedChunkHeader(\"z\\r\\n\");\n  auto req = getPostRequest(100);\n  req.setIsChunked(true);\n  req.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  auto id = sendRequestHeader(req);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto event = co_await requestSource.readHeaderEvent();\n        // Async wait for ingress error but return the response early.\n        co_withExecutor(\n            &evb_,\n            [](HTTPSourceHolder requestSource) -> folly::coro::Task<void> {\n              co_await expectBodyError(requestSource);\n            }(std::move(requestSource)))\n            .start();\n        co_return HTTPFixedSource::makeFixedResponse(301);\n      });\n  loopN(2); // Takes 2 loops to process\n  // Bad chunk header\n  transport_->addReadEvent(\n      id, folly::IOBuf::copyBuffer(malformedChunkHeader), false);\n\n  evb_.loop();\n  expectResponse(id, 301, nullptr, false);\n  parseOutput();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(H1DownstreamSessionTest, CodecHTTPErrorStreamReadInProgress) {\n  std::string malformedChunkHeader(\"z\\r\\n\");\n  auto req = getPostRequest(100);\n  req.setIsChunked(true);\n  req.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  auto id = sendRequestHeader(req);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  transport_->addReadEvent(\n      id, folly::IOBuf::copyBuffer(malformedChunkHeader), false);\n  // handleRequest is never called because the error cancels it first\n  evb_.loop();\n  expectResponse(id, 400, nullptr, false);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, HandlerNoResponse) {\n  // Handler returns nullptr instead of a response source\n  auto id = sendRequest(\"/\", nullptr, true, true);\n  addSimpleStrictHandler([](folly::EventBase *evb,\n                            HTTPSessionContextPtr /*ctx*/,\n                            HTTPSourceHolder requestSource)\n                             -> folly::coro::Task<HTTPSourceHolder> {\n    auto event = co_await co_awaitTry(requestSource.readHeaderEvent());\n    EXPECT_TRUE(event->eom);\n    co_return nullptr;\n  });\n  evb_.loop();\n  expectResponse(id, 500, nullptr, false);\n  parseOutput();\n}\n\nTEST_P(HTTPDownstreamSessionTest, ResponseHeadersError) {\n  // Return a response that immediately errors in readHeaderEvent\n  auto id = sendRequest(\"/\", nullptr, true, true);\n  addSimpleStrictHandler([](folly::EventBase *evb,\n                            HTTPSessionContextPtr /*ctx*/,\n                            HTTPSourceHolder requestSource)\n                             -> folly::coro::Task<HTTPSourceHolder> {\n    auto event = co_await co_awaitTry(requestSource.readHeaderEvent());\n    EXPECT_TRUE(event->eom);\n    co_return new HTTPErrorSource(HTTPError(HTTPErrorCode::ENHANCE_YOUR_CALM));\n  });\n  evb_.loop();\n  if (IS_H1()) {\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  } else {\n    expectStreamAbort(id, ErrorCode::ENHANCE_YOUR_CALM);\n    parseOutput();\n  }\n}\n\nCO_TEST_P_X(HTTPDownstreamSessionTest, IngressErrorCustomResponse) {\n  /**\n   * On specific ingress errors (e.g. timeout), we allow a handler to egress a\n   * custom http response as long as it contains the expected status code (e.g.\n   * 408 in timeout case).\n   *\n   * We send headers but no body event, causing the ::readBodyEvent invocation\n   * to timeout – since we're generating the expected status code (408), the\n   * session won't override the response\n   */\n  session_->setStreamReadTimeout(std::chrono::milliseconds(50));\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headers = co_await co_awaitTry(\n            expectRequest(requestSource, HTTPMethod::POST, \"/\", false));\n        CHECK(headers.hasValue());\n\n        auto res = co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        auto err = res.tryGetExceptionObject<HTTPError>();\n        EXPECT_TRUE(err && err->code == HTTPErrorCode::READ_TIMEOUT);\n        co_return HTTPFixedSource::makeFixedResponse(\n            408, \"custom response generated\");\n      });\n\n  // send msg headers but no body triggering ::readBodyEvent to t/o\n  auto msg = getChunkedPostRequest();\n  auto id = sendRequestHeader(std::move(msg));\n  transport_->addReadEvent(id, writeBuf_.move(), /*eom=*/false);\n\n  folly::coro::Baton waitForEgressEom;\n  EXPECT_CALL(lifecycleObs_, onDrainStarted(_)).WillOnce([&]() {\n    waitForEgressEom.post();\n  });\n  co_await waitForEgressEom;\n  expectResponseHeaders(id, 408);\n  EXPECT_CALL(callbacks_, onBody(id, _, _)).WillOnce([](auto, auto body, auto) {\n    EXPECT_EQ(body->template to<std::string>(), \"custom response generated\");\n  });\n  expectResponseEOM(id);\n  parseOutput();\n}\n\nCO_TEST_P_X(HTTPDownstreamSessionTest, IngressErrorCustomResponseMismatch) {\n  /**\n   * Similar to the unit test above – but if the handler does not yield the\n   * expected response status code, we will generate a default http response on\n   * behalf the app/handler\n   */\n  session_->setStreamReadTimeout(std::chrono::milliseconds(50));\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        auto headers = co_await co_awaitTry(\n            expectRequest(requestSource, HTTPMethod::POST, \"/\", false));\n        CHECK(headers.hasValue());\n\n        auto res = co_await co_awaitTry(readBodyEventNoSuspend(requestSource));\n        auto err = res.tryGetExceptionObject<HTTPError>();\n        EXPECT_TRUE(err && err->code == HTTPErrorCode::READ_TIMEOUT);\n        co_return HTTPFixedSource::makeFixedResponse(\n            500, \"custom response generated\");\n      });\n\n  // send msg headers but no body triggering ::readBodyEvent to t/o\n  auto msg = getChunkedPostRequest();\n  auto id = sendRequestHeader(std::move(msg));\n  transport_->addReadEvent(id, writeBuf_.move(), /*eom=*/false);\n\n  folly::coro::Baton waitForEgressEom;\n  EXPECT_CALL(lifecycleObs_, onDrainStarted(_)).WillOnce([&]() {\n    waitForEgressEom.post();\n  });\n  co_await waitForEgressEom;\n  expectResponse(id, 408, /*headers=*/nullptr, /*expectBody=*/false);\n  parseOutput();\n}\n\n// H3 specific tests\nTEST_P(HQDownstreamSessionTest, CreateControlStreamFail) {\n  // Kill the default session\n  session_->closeWhenIdle();\n\n  // Make a new muxTransport with no uni credit\n  TestCoroMultiplexTransport muxTransport(&evb_, direction_);\n  transport_ = &muxTransport;\n  muxTransport.socketDriver_.setMaxUniStreams(0);\n  auto codec = std::make_unique<hq::HQMultiCodec>(direction_);\n  wangle::TransportInfo tinfo;\n  auto session = HTTPCoroSession::makeDownstreamCoroSession(\n      muxTransport.getSocket(), handler_, std::move(codec), std::move(tinfo));\n  NiceMock<MockLifecycleObserver> lifecycleCb;\n  session->addLifecycleObserver(&lifecycleCb);\n  folly::coro::co_withCancellation(cancellationSource_.getToken(),\n                                   session->run())\n      .start();\n  EXPECT_CALL(lifecycleCb, onDestroy(_));\n  loopN(4);\n  EXPECT_EQ(HTTP3::ErrorCode(*muxTransport.socketDriver_.getConnErrorCode()),\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n}\n\nTEST_P(HQDownstreamSessionTest, StopSending) {\n  auto id = sendRequestHeader(getPostRequest(20), false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n\n  auto handler =\n      addSimpleStrictHandler([this, id](folly::EventBase *evb,\n                                        HTTPSessionContextPtr /*ctx*/,\n                                        HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::POST, \"/\", false);\n        muxTransport_->socketDriver_.addStopSending(\n            id, HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD);\n        co_await expectBodyError(requestSource, HTTPErrorCode::CORO_CANCELLED);\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectStreamAbort(id, ErrorCode::CANCEL);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, UniDispatchEdgeCases) {\n  loopN(2); // to finish dispatching control stream\n\n  // === EOF only\n  auto id = muxTransport_->nextUnidirectionalStreamId_;\n  muxTransport_->nextUnidirectionalStreamId_ += 4;\n  transport_->addReadEvent(id, nullptr, true);\n  loopN(2); // Sadly takes 2 looks after first read event until the peek fires\n  // Now the peek cb is gone\n  EXPECT_TRUE(muxTransport_->socketDriver_.streams_[id].peekCB == nullptr);\n\n  // == Stream without enough data to dispatch\n  std::array<uint8_t, 2> twoByteGreaseId{0x40, 0x33};\n  id = muxTransport_->nextUnidirectionalStreamId_;\n  muxTransport_->nextUnidirectionalStreamId_ += 4;\n  transport_->addReadEvent(\n      id, folly::IOBuf::wrapBuffer(twoByteGreaseId.data(), 1), false);\n  loopN(2);\n  // Still has a peek cb installed\n  EXPECT_TRUE(muxTransport_->socketDriver_.streams_[id].peekCB != nullptr);\n  transport_->addReadEvent(id, nullptr, true);\n  evb_.loopOnce();\n  // Now the peek cb is gone\n  EXPECT_TRUE(muxTransport_->socketDriver_.streams_[id].peekCB == nullptr);\n\n  // === Grease stream, EOF\n  id = muxTransport_->nextUnidirectionalStreamId_;\n  muxTransport_->nextUnidirectionalStreamId_ += 4;\n  transport_->addReadEvent(\n      id, folly::IOBuf::wrapBuffer(twoByteGreaseId.data(), 2), true);\n  loopN(2);\n  // Peek cb is gone\n  EXPECT_TRUE(muxTransport_->socketDriver_.streams_[id].peekCB == nullptr);\n  // Stream complete - stopSending called\n  EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].error,\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  // Buf not consumed\n  EXPECT_FALSE(muxTransport_->socketDriver_.streams_[id].readBuf.empty());\n\n  // === Grease stream, no EOF\n  id = muxTransport_->nextUnidirectionalStreamId_;\n  muxTransport_->nextUnidirectionalStreamId_ += 4;\n  transport_->addReadEvent(\n      id, folly::IOBuf::wrapBuffer(twoByteGreaseId.data(), 2), false);\n  loopN(2);\n  // Now the peek cb is gone\n  EXPECT_TRUE(muxTransport_->socketDriver_.streams_[id].peekCB == nullptr);\n  // It told us to stop sending\n  EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].error,\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  // Send some data on the grease stream\n  transport_->addReadEvent(id, makeBuf(10));\n  evb_.loopOnce();\n  // App didn't read the data or consume the preface\n  EXPECT_EQ(muxTransport_->socketDriver_.streams_[id].readBuf.chainLength(),\n            12);\n  // Now close the stream\n  transport_->addReadEvent(id, nullptr, true);\n  evb_.loopOnce();\n\n  session_->initiateDrain();\n  evb_.loop();\n}\n\nTEST_P(H2QDownstreamSessionTest, StreamAfterGoaway) {\n  // 1st req is accepted\n  auto id1 = sendRequest(\"/\");\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  loopN(1);\n\n  // Send GOAWAY\n  session_->initiateDrain();\n  auto id2 = sendRequest(\"/\");\n  // no handler is created for id2\n  evb_.loop();\n  expectResponse(id1, 200, nullptr, true);\n  expectGoaway(id1, ErrorCode::NO_ERROR);\n  if (isHQ()) {\n    expectStreamAbort(id2, ErrorCode::REFUSED_STREAM);\n  }\n  parseOutput();\n}\n\nTEST_P(H2DownstreamSessionTest, BasicRFC9218PriorityUrgency) {\n  /* Headers are processed in the order that they're sent (req: u=7, then req2:\n   * u=1), but after the stream priorities are set from reading the headers,\n   * addStreamBodyDataToWriteBuf() takes the stream with the highest priority\n   * and generates the body for that stream first.\n   */\n  InSequence enforceOrder;\n  auto req = getGetRequest(\"/\");\n  req.getHeaders().add(\"priority\",\n                       \"u=7\"); // urgency is in descending order of priority, so\n                               // this is actually lower in priority than u=1\n  auto req2 = getGetRequest(\"/\");\n  req2.getHeaders().add(\"priority\", \"u=1\");\n\n  auto id = sendRequestHeader(req, true, true);\n  auto id2 = sendRequestHeader(req2, true, true);\n\n  auto handler =\n      addSimpleStrictHandler([&](folly::EventBase *evb,\n                                 HTTPSessionContextPtr /*ctx*/,\n                                 HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  auto handler2 =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  transport_->addReadEvent(writeBuf_.move());\n  loopN(2);\n  EXPECT_CALL(callbacks_,\n              onHeadersComplete(id, _)); // onHeadersComplete() in the\n                                         // order the reqs are sent\n  EXPECT_CALL(callbacks_, onHeadersComplete(id2, _));\n  EXPECT_CALL(callbacks_,\n              onBody(id2, _, _)); // id2 gets body written first since it has\n                                  // the higher priority\n  EXPECT_CALL(callbacks_, onMessageComplete(id2, _));\n  EXPECT_CALL(callbacks_, onBody(id, _, _));\n  EXPECT_CALL(callbacks_, onMessageComplete(id, _));\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, onPriorityHeaderUrgency) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"priority\", \"u=1\");\n\n  auto id = sendRequestHeader(req, true, true);\n  muxTransport_->socketDriver_.expectSetPriority(\n      id, quic::HTTPPriorityQueue::Priority(1, false));\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutputHQ();\n}\n\nTEST_P(HQDownstreamSessionTest, onPriorityHeaderUrgencyIncremental) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"priority\", \"u=2, i\");\n\n  auto id = sendRequestHeader(req, true, true);\n  muxTransport_->socketDriver_.expectSetPriority(\n      id, quic::HTTPPriorityQueue::Priority(2, true));\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutputHQ();\n}\n\nTEST_P(HQDownstreamSessionTest, onPriorityUpdateFrame) {\n  loopN(2);\n  auto req = getGetRequest();\n  /**\n   * pri header should be ignored since we're sending the PriorityUpdate frame\n   * first (simulating delay).\n   */\n  req.getHeaders().add(\"priority\", \"u=2\");\n\n  auto id = createStreamID();\n  muxTransport_->socketDriver_.expectSetPriority(\n      id, quic::HTTPPriorityQueue::Priority(3, true));\n\n  /**\n   * Generate PriorityUpdate frame, expected to be buffered by session since\n   * we're receiving an pri update for a stream id we haven't yet received.\n   */\n  clientCodec_->generatePriority(writeBuf_, id, HTTPPriority(3, true));\n  transport_->addReadEvent(writeBuf_.move());\n  loopN(2);\n\n  // Send request with pri header (should be ignored)\n  sendRequestHeader(id, req, true, true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n  loopN(1);\n\n  session_->initiateDrain();\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutputHQ();\n}\n\nTEST_P(HQDownstreamSessionTest, onServerPriorityHeader) {\n  auto req = getGetRequest();\n\n  auto id = sendRequestHeader(req, true, true);\n  muxTransport_->socketDriver_.expectSetPriority(\n      id, quic::HTTPPriorityQueue::Priority(1, false));\n\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        auto resp = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n        resp->msg_->getHeaders().add(\"priority\", \"u=1\");\n        co_return resp;\n      });\n\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutputHQ();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACK) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/true, /*flushQPACK=*/false);\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        generateGoaway(0, ErrorCode::NO_ERROR);\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n      });\n\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  EXPECT_GT(multiCodec_->getQPACKEncoderWriteBuf().chainLength(), 0);\n  loopN(2);\n  flushQPACKEncoder();\n  evb_.loop();\n  expectResponse(id, 200);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, cancelQPACK) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req);\n  // discard part of request, header won't get qpack-ack'd\n  writeBuf_.trimEnd(writeBuf_.chainLength() - 3);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource,\n                                   HTTPErrorCode::REQUEST_CANCELLED);\n        co_return nullptr;\n      });\n  loopN(2);\n  resetStream(id, ErrorCode::CANCEL, /*stopSending=*/false);\n  session_->initiateDrain();\n  evb_.loop();\n  // this will evict all headers, which is only legal if the cancellation is\n  // emitted and processed.\n  parseOutput();\n  multiCodec_->getQPACKCodec().setEncoderHeaderTableSize(0,\n                                                         /*updateMax=*/false);\n  EXPECT_EQ(*muxTransport_->socketDriver_.streams_[id].error,\n            HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKCanceled) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/true, /*flushQPACK=*/false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        // Maybe REQUEST_CANCELLED someday\n        co_await expectHeaderError(requestSource,\n                                   HTTPErrorCode::CORO_CANCELLED);\n        co_return nullptr;\n      });\n\n  // receive header block with unsatisfied dep\n  loopN(1);\n\n  // cancel this request\n  muxTransport_->socketDriver_.addStopSending(\n      id, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  loopN(1);\n\n  // Now send the dependency\n  session_->initiateDrain();\n  flushQPACKEncoder();\n  evb_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKTimeout) {\n  auto req = getPostRequest(10);\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/true, /*flushQPACK=*/false);\n  folly::IOBufQueue reqTail(folly::IOBufQueue::cacheChainLength());\n  reqTail.append(writeBuf_.move());\n  writeBuf_.append(reqTail.split(reqTail.chainLength() / 2));\n  // reqTail now has the second half of request\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  writeBuf_.append(reqTail.move());\n\n  auto handler =\n      addSimpleStrictHandler([this, id](folly::EventBase *evb,\n                                        HTTPSessionContextPtr /*ctx*/,\n                                        HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource, HTTPErrorCode::READ_TIMEOUT);\n        // Also flushes reqTail from writeBuf_\n        sendBody(id, makeBuf(10), true, true);\n        session_->initiateDrain();\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectResponse(id, 408, /*headers=*/nullptr, /*expectBody=*/false);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKTimeoutLoopOnceUAF) {\n  auto req = getPostRequest(10);\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/true, /*flushQPACK=*/false);\n  folly::IOBufQueue reqTail(folly::IOBufQueue::cacheChainLength());\n  reqTail.append(writeBuf_.move());\n  writeBuf_.append(reqTail.split(reqTail.chainLength() / 2));\n  // reqTail now has the second half of request\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  writeBuf_.append(reqTail.move());\n\n  auto handler =\n      addSimpleStrictHandler([this, id](folly::EventBase *evb,\n                                        HTTPSessionContextPtr /*ctx*/,\n                                        HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource, HTTPErrorCode::READ_TIMEOUT);\n        // Also flushes reqTail from writeBuf_\n        sendBody(id, makeBuf(10), true, true);\n        co_await folly::coro::co_reschedule_on_current_executor;\n        session_->initiateDrain();\n        co_return nullptr;\n      });\n\n  evb_.loop();\n  expectResponse(id, 408, /*headers=*/nullptr, /*expectBody=*/false);\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKICI) {\n  loopN(3);\n  auto req = getPostRequest(10);\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  sendRequestHeader(req);\n  // Don't add the actual request, just the QPACK data\n  loopN(3);\n  // Should get an ICI\n  EXPECT_GE(muxTransport_->socketDriver_.streams_[kQPACKDecoderEgressStreamId]\n                .writeBuf.chainLength(),\n            2);\n  session_->initiateDrain();\n  evb_.loop();\n  parseOutput();\n}\n\nTEST_P(HQDownstreamSessionTest, QPACKEncoderLimited) {\n  muxTransport_->socketDriver_.getSocket()->setStreamFlowControlWindow(\n      kQPACKEncoderEgressStreamId, 10);\n  EXPECT_CALL(lifecycleObs_, onDeactivateConnection(_))\n      // Free the encoder flow control after the request completes\n      .WillOnce(Invoke([this](const HTTPCoroSession &) {\n        muxTransport_->socketDriver_.setStreamFlowControlWindow(\n            kQPACKEncoderEgressStreamId, 100);\n      }));\n  auto req = getGetRequest();\n  auto id = sendRequest(req);\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(requestSource, HTTPMethod::GET, \"/\");\n        auto resp = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n        resp->msg_->getHeaders().add(\"X-FB-Debug\",\n                                     \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n        session_->initiateDrain();\n        co_return resp;\n      });\n  evb_.loop();\n\n  // QPACK will attempt to index the header, but cannot reference it because\n  // it runs out of stream flow control\n  EXPECT_GT(muxTransport_->socketDriver_.streams_[id].writeBuf.chainLength(),\n            30);\n\n  expectResponse(id, 200);\n  parseOutput();\n  // The insert happens after f/c is released\n  EXPECT_EQ(multiCodec_->getQPACKCodec().getCompressionInfo().ingress.inserts_,\n            1);\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKStopSendingReset) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequestHeader(req, /*eom=*/true, /*flushQPACK=*/false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        session_->initiateDrain();\n        co_await expectHeaderError(requestSource,\n                                   HTTPErrorCode::REQUEST_CANCELLED);\n        co_return nullptr;\n      });\n  // receive header block with unsatisfied dep\n  loopN(2);\n\n  // cancel this request\n  muxTransport_->socketDriver_.addStopSending(\n      id, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  muxTransport_->socketDriver_.addReadError(\n      id,\n      HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n      std::chrono::milliseconds(0));\n  loopN(2);\n\n  // Now send the dependency\n  flushQPACKEncoder();\n  evb_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, DatagramNotSupportedTransport) {\n  // destroy previous session\n  session_->closeWhenIdle();\n  evb_.loop();\n\n  // create new downstream session to rx settings without transport support\n  TestCoroMultiplexTransport muxTransport(&evb_, direction_);\n  transport_ = &muxTransport;\n  auto codec = std::make_unique<hq::HQMultiCodec>(direction_);\n  wangle::TransportInfo tinfo;\n  auto session = HTTPCoroSession::makeDownstreamCoroSession(\n      muxTransport.getSocket(), handler_, std::move(codec), std::move(tinfo));\n  folly::coro::co_withCancellation(cancellationSource_.getToken(),\n                                   session->run())\n      .start();\n  loopN(4);\n\n  // simulate lack of support for datagrams at transport level\n  EXPECT_CALL(*muxTransport.getSocket(), getDatagramSizeLimit())\n      .WillRepeatedly(Return(0));\n\n  // rx datagram settings\n  auto peerCodec = std::make_unique<hq::HQMultiCodec>(oppositeDirection());\n  setTestCodecSetting(\n      peerCodec->getEgressSettings(), SettingsId::_HQ_DATAGRAM, 1);\n  peerCodec->generateSettings(writeBuf_);\n  muxTransport.addReadEvent(writeBuf_.move(), false);\n  loopN(4);\n\n  // expect conn err\n  EXPECT_EQ(muxTransport.socketDriver_.getConnErrorCode(),\n            static_cast<quic::ApplicationErrorCode>(\n                HTTP3::ErrorCode::HTTP_SETTINGS_ERROR));\n}\n\nTEST_P(HQDownstreamSessionDatagramTest, RxDatagramInvalidStreamId) {\n  auto &socketDriver = muxTransport_->socketDriver_;\n  EXPECT_GT(session_->getDatagramSizeLimit(), 0);\n  // add datagram callback as settings are modified after ::start()\n  socketDriver.datagramCB_ = static_cast<HTTPQuicCoroSession *>(session_);\n\n  // initiate client bidi request\n  auto id = sendRequestHeader(\n      getPostRequest(100), /*eom=*/false, /*flushQPACK=*/false);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n\n  // waits for header event (blocked on QPACK)\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource);\n        co_return nullptr;\n      });\n  loopN(3);\n\n  // empty datagram fails to parse stream id and result in conn error\n  socketDriver.addDatagram(folly::IOBuf::copyBuffer(\"\"));\n  socketDriver.addDatagramsAvailableReadEvent();\n  evb_.loop();\n\n  // expect conn err\n  EXPECT_EQ(socketDriver.getConnErrorCode(),\n            static_cast<quic::ApplicationErrorCode>(\n                HTTPErrorCode::GENERAL_PROTOCOL_ERROR));\n  expectedError_ = folly::coro::TransportIf::ErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(HQDownstreamSessionDatagramTest, RxDatagramStreamIdLimitExceeded) {\n  auto &socketDriver = muxTransport_->socketDriver_;\n  EXPECT_GT(session_->getDatagramSizeLimit(), 0);\n  // add datagram callback as settings are modified after ::start()\n  socketDriver.datagramCB_ = static_cast<HTTPQuicCoroSession *>(session_);\n\n  // initiate client bidi request\n  auto id = sendRequestHeader(\n      getPostRequest(100), /*eom=*/false, /*flushQPACK=*/false);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n\n  // waits for header event (blocked on QPACK)\n  auto handler =\n      addSimpleStrictHandler([](folly::EventBase *evb,\n                                HTTPSessionContextPtr /*ctx*/,\n                                HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectHeaderError(requestSource);\n        co_return nullptr;\n      });\n  loopN(3);\n\n  // stringify std::numeric_limits<uint64_t>::max()\n  std::string strStreamId(8, '\\xff');\n  socketDriver.addDatagram(folly::IOBuf::copyBuffer(strStreamId));\n  socketDriver.addDatagramsAvailableReadEvent();\n  evb_.loop();\n\n  // expect conn err\n  EXPECT_EQ(socketDriver.getConnErrorCode(),\n            static_cast<quic::ApplicationErrorCode>(\n                HTTPErrorCode::GENERAL_PROTOCOL_ERROR));\n  expectedError_ = folly::coro::TransportIf::ErrorCode::NETWORK_ERROR;\n}\n\nTEST_P(HQDownstreamSessionDatagramTest, RxDatagramsPriorToStream) {\n  auto &socketDriver = muxTransport_->socketDriver_;\n  EXPECT_GT(session_->getDatagramSizeLimit(), 0);\n  // add datagram callback as settings are modified after ::start()\n  socketDriver.datagramCB_ = static_cast<HTTPQuicCoroSession *>(session_);\n\n  auto streamID = createStreamID();\n  auto flowID = std::byte(streamID >> 2);\n\n  // \"hello\" datagram for first bidi stream\n  std::vector<char> helloMsg = {(char)(flowID), 'h', 'e', 'l', 'l', 'o'};\n  auto helloBuf = folly::IOBuf::copyBuffer(helloMsg);\n\n  // send 3x \"hello\" datagrams\n  const int numDatagramsToSend = 3;\n  for (int idx = 0; idx < numDatagramsToSend; idx++) {\n    socketDriver.addDatagram(helloBuf->clone());\n  }\n  socketDriver.addDatagramsAvailableReadEvent();\n  // loop so session can buffer datagrams\n  loopN(2);\n\n  // send request with eom\n  sendRequestHeader(streamID, getGetRequest(\"/\"), /*eom=*/true);\n  transport_->addReadEvent(streamID, writeBuf_.move(), /*eom=*/true);\n\n  // verify datagrams were sent to source\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(\n            requestSource, HTTPMethod::GET, \"/\", /*eom=*/false);\n\n        // expect three datagrams and then an empty body with eom\n        for (int idx = 0; idx < numDatagramsToSend; idx++) {\n          auto bodyEvent = co_await readBodyEventNoSuspend(requestSource);\n          EXPECT_EQ(bodyEvent.eventType, HTTPBodyEvent::EventType::DATAGRAM);\n          EXPECT_EQ(bodyEvent.event.datagram->to<std::string>(), \"hello\");\n        }\n        auto eomBodyEvent = co_await readBodyEventNoSuspend(requestSource);\n        EXPECT_EQ(eomBodyEvent.eventType, HTTPBodyEvent::EventType::BODY);\n        EXPECT_EQ(eomBodyEvent.event.body.chainLength(), 0);\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(40));\n      });\n\n  // expect 200 response\n  evb_.loop();\n  expectResponse(streamID, 200);\n  parseOutputHQ();\n}\n\nTEST_P(HQDownstreamSessionDatagramTest, RxStreamPriorToDatagrams) {\n  auto &socketDriver = muxTransport_->socketDriver_;\n  EXPECT_GT(session_->getDatagramSizeLimit(), 0);\n  // add datagram callback as settings are modified after ::start()\n  socketDriver.datagramCB_ = static_cast<HTTPQuicCoroSession *>(session_);\n\n  const int numDatagramsToSend = 3;\n\n  // handler verifies datagrams were sent to source\n  auto handler =\n      addSimpleStrictHandler([this](folly::EventBase *evb,\n                                    HTTPSessionContextPtr /*ctx*/,\n                                    HTTPSourceHolder requestSource)\n                                 -> folly::coro::Task<HTTPSourceHolder> {\n        co_await expectRequest(\n            requestSource, HTTPMethod::GET, \"/\", /*eom=*/false);\n\n        // expect three datagrams and then an empty body with eom\n        for (int idx = 0; idx < numDatagramsToSend; idx++) {\n          auto bodyEvent = co_await readBodyEventNoSuspend(requestSource);\n          EXPECT_EQ(bodyEvent.eventType, HTTPBodyEvent::EventType::DATAGRAM);\n          EXPECT_EQ(bodyEvent.event.datagram->to<std::string>(), \"hello\");\n        }\n        auto eomBodyEvent = co_await readBodyEventNoSuspend(requestSource);\n        EXPECT_EQ(eomBodyEvent.eventType, HTTPBodyEvent::EventType::BODY);\n        EXPECT_EQ(eomBodyEvent.event.body.chainLength(), 0);\n\n        co_return HTTPFixedSource::makeFixedResponse(200, makeBuf(40));\n      });\n\n  // send request without eom\n  auto streamID = sendRequest(\"/\", nullptr, /*eom=*/false);\n  auto flowID = std::byte(streamID >> 2);\n  loopN(2);\n\n  // \"hello\" datagram for first client bidi stream id = 0\n  std::vector<char> helloMsg = {(char)flowID, 'h', 'e', 'l', 'l', 'o'};\n  auto helloBuf = folly::IOBuf::copyBuffer(helloMsg);\n\n  // send 3x \"hello\" datagrams\n  for (int idx = 0; idx < numDatagramsToSend; idx++) {\n    socketDriver.addDatagram(helloBuf->clone());\n  }\n  // loop so session can deliver datagrams to source\n  socketDriver.addDatagramsAvailableReadEvent();\n  loopN(2);\n\n  // serialize eom\n  transport_->addReadEvent(streamID, nullptr, true);\n\n  // expect 200 response\n  evb_.loop();\n  expectResponse(streamID, 200);\n  parseOutputHQ();\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    HTTPDownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1}),\n           TestParams({.codecProtocol = CodecProtocol::HTTP_2}),\n           TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    H1DownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    H2DownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    HQDownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPDownstreamSessionTest,\n                         HQDownstreamSessionDatagramTest,\n                         Values(TestParams({.codecProtocol = CodecProtocol::HQ,\n                                            .enableDatagrams = true})),\n                         paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HTTPDownstreamSessionTest,\n                         HQStaticQPACKDownstreamSessionTest,\n                         Values(TestParams({.codecProtocol = CodecProtocol::HQ,\n                                            .useDynamicTable = false})),\n                         paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    H12DownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1}),\n           TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    H2QDownstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2}),\n           TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPFilterFactoryHandlerTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/coro/GtestHelpers.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <string>\n\n#include \"proxygen/lib/http/coro/HTTPEvents.h\"\n#include \"proxygen/lib/http/coro/HTTPFilterFactoryHandler.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/filters/FilterFactory.h\"\n#include \"proxygen/lib/http/coro/filters/MutateFilter.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n\nnamespace proxygen::coro::test {\n\nclass HTTPFilterFactoryHandlerTests : public ::testing::Test {\n protected:\n  void SetUp() override {\n    EXPECT_CALL(mockSource_, readHeaderEvent())\n        .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n            [&]() -> folly::coro::Task<HTTPHeaderEvent> {\n              co_return HTTPHeaderEvent(std::make_unique<HTTPMessage>(), true);\n            }));\n  }\n  void TearDown() override {\n  }\n\n  MockHTTPSessionContext mockSession_;\n  MockHTTPSource mockSource_;\n\n  /**\n   * FilterFactory that simply appends the name of the filter (e.g.\n   * \"reqFilterA\", \"resFilterA\") to the \"req-filter\" or \"res-filter\" header\n   * field value\n   */\n  enum class Direction { Request, Response, RequestResponse };\n  class AppendFilterNameFilterFactory : public FilterFactory {\n   public:\n    AppendFilterNameFilterFactory(std::string filterName, Direction direction)\n        : filterName_(std::move(filterName)), direction_(direction) {\n    }\n\n    std::pair<HTTPSourceFilter*, HTTPSourceFilter*> makeFilters() override {\n      HTTPSourceFilter *reqFilter{nullptr}, *resFilter{nullptr};\n\n      if (direction_ != Direction::Response) {\n        reqFilter = new MutateFilter(\n            /*source*/\n            nullptr,\n            [this](HTTPHeaderEvent& event) {\n              auto& headers = event.headers->getHeaders();\n              const auto& value = headers.getSingleOrEmpty(\"req-filter\");\n              auto newValue =\n                  folly::to<std::string>(value, \"req\", filterName_, \",\");\n              headers.set(\"req-filter\", newValue);\n            },\n            nullptr);\n        reqFilter->setHeapAllocated();\n      }\n\n      if (direction_ != Direction::Request) {\n        resFilter = new MutateFilter(\n            /*source*/\n            nullptr,\n            [this](HTTPHeaderEvent& event) {\n              auto& headers = event.headers->getHeaders();\n              const auto& value = headers.getSingleOrEmpty(\"res-filter\");\n              auto newValue =\n                  folly::to<std::string>(value, \"res\", filterName_, \",\");\n              headers.set(\"res-filter\", newValue);\n            },\n            nullptr);\n        resFilter->setHeapAllocated();\n      }\n\n      return {reqFilter, resFilter};\n    }\n\n    std::string filterName_;\n    Direction direction_;\n  };\n\n  /**\n   * Handler that reads the request to completion expects the \"req-filter\"\n   * header contains the expected value. This is done to validate the order in\n   * which req are installed by HTTPServer.\n   */\n  class ReqFilterNameHandler : public HTTPHandler {\n   public:\n    ReqFilterNameHandler(std::string expectedReqFilterValue = \"\")\n        : expectedReqFilterValue_(std::move(expectedReqFilterValue)) {\n    }\n\n    folly::coro::Task<HTTPSourceHolder> handleRequest(\n        folly::EventBase* /*evb*/,\n        HTTPSessionContextPtr /*ctx*/,\n        HTTPSourceHolder requestSource) override {\n      // copy over \"req-filter\" header over to the response\n      std::string reqFilterHeaderValue;\n      HTTPSourceReader reader(std::move(requestSource));\n      co_await reader\n          .onHeaders([this](std::unique_ptr<HTTPMessage> msg,\n                            bool /*final*/,\n                            bool /*eom*/) {\n            CHECK(msg);\n            const auto& headers = msg->getHeaders();\n            EXPECT_EQ(headers.getSingleOrEmpty(\"req-filter\"),\n                      expectedReqFilterValue_);\n            return HTTPSourceReader::Continue;\n          })\n          .read();\n\n      co_return sourceFactory ? sourceFactory()\n                              : HTTPFixedSource::makeFixedResponse(200, \"OK\");\n    }\n\n    std::string expectedReqFilterValue_;\n    std::function<HTTPSource*(void)> sourceFactory{nullptr};\n  };\n};\n\nCO_TEST_F(HTTPFilterFactoryHandlerTests, TestEmptyFilters) {\n  auto handler = std::make_shared<ReqFilterNameHandler>();\n  auto filterFactoryHandler = std::make_shared<HTTPFilterFactoryHandler>();\n  filterFactoryHandler->setNextHandler(handler);\n\n  auto result = co_await filterFactoryHandler->handleRequest(\n      folly::EventBaseManager::get()->getEventBase(),\n      mockSession_.acquireKeepAlive(),\n      HTTPSourceHolder(&mockSource_));\n\n  auto headerEvent =\n      co_await folly::coro::co_awaitTry(result.readHeaderEvent());\n  EXPECT_EQ(headerEvent->headers->getHeaders().getSingleOrEmpty(\"res-filter\"),\n            \"\");\n}\n\n// Validate order of filter execution – on the request path, filterA get's\n// executed first then filterB, and on the response path filterC gets executed\n// first then filterA.\nCO_TEST_F(HTTPFilterFactoryHandlerTests, TestFilterFactoriesSimple) {\n  std::string expectedReqFilterValue = \"reqFilterA,reqFilterB,\";\n  std::string expectedRespFilterValue = \"resFilterC,resFilterA,\";\n\n  auto handler = std::make_shared<ReqFilterNameHandler>(expectedReqFilterValue);\n  auto filterFactoryHandler = std::make_shared<HTTPFilterFactoryHandler>();\n  filterFactoryHandler->setNextHandler(handler);\n\n  // create req&res \"filterA\", req \"filterB\" and res \"filterC\" and insert into\n  // filterFactories list\n  auto reqResFilterA = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterA\", Direction::RequestResponse);\n  auto reqFilterB = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterB\", Direction::Request);\n  auto resFilterC = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterC\", Direction::Response);\n  filterFactoryHandler->addFilterFactory(std::move(reqResFilterA))\n      .addFilterFactory(std::move(reqFilterB))\n      .addFilterFactory(std::move(resFilterC));\n\n  auto result = co_await filterFactoryHandler->handleRequest(\n      folly::EventBaseManager::get()->getEventBase(),\n      mockSession_.acquireKeepAlive(),\n      HTTPSourceHolder(&mockSource_));\n\n  auto headerEvent =\n      co_await folly::coro::co_awaitTry(result.readHeaderEvent());\n  EXPECT_EQ(headerEvent->headers->getHeaders().getSingleOrEmpty(\"res-filter\"),\n            expectedRespFilterValue);\n}\n\n// Similar to the test above, but here the handler returns nullptr. Validate\n// order of filter execution – on the request path, filterA get's executed\n// first then filterB, and on the response path filterC gets executed first\n// then filterA (even when nullptr source is returned by the handler).\nCO_TEST_F(HTTPFilterFactoryHandlerTests, TestHandlerNullptrSource) {\n  std::string expectedReqFilterValue = \"reqFilterA,reqFilterB,\";\n  std::string expectedRespFilterValue = \"resFilterC,resFilterA,\";\n\n  auto handler = std::make_shared<ReqFilterNameHandler>(expectedReqFilterValue);\n  handler->sourceFactory = []() { return nullptr; };\n  auto filterFactoryHandler = std::make_shared<HTTPFilterFactoryHandler>();\n  filterFactoryHandler->setNextHandler(handler);\n\n  // create req&res \"filterA\", req \"filterB\" and res \"filterC\" and insert into\n  // filterFactories list\n  auto reqResFilterA = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterA\", Direction::RequestResponse);\n  auto reqFilterB = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterB\", Direction::Request);\n  auto resFilterC = std::make_shared<AppendFilterNameFilterFactory>(\n      \"FilterC\", Direction::Response);\n  filterFactoryHandler->addFilterFactory(std::move(reqResFilterA))\n      .addFilterFactory(std::move(reqFilterB))\n      .addFilterFactory(std::move(resFilterC));\n\n  auto result = co_await filterFactoryHandler->handleRequest(\n      folly::EventBaseManager::get()->getEventBase(),\n      mockSession_.acquireKeepAlive(),\n      HTTPSourceHolder(&mockSource_));\n\n  auto headerEvent =\n      co_await folly::coro::co_awaitTry(result.readHeaderEvent());\n  EXPECT_EQ(headerEvent->headers->getHeaders().getSingleOrEmpty(\"res-filter\"),\n            expectedRespFilterValue);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPHandlerChainTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/coro/HTTPHandlerChain.h>\n\nnamespace proxygen::coro::test {\n\nclass TestHTTPChainHandler : public HTTPChainHandler {\n public:\n  TestHTTPChainHandler() = default;\n  ~TestHTTPChainHandler() override = default;\n\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* evb,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override {\n    co_return HTTPSourceHolder();\n  }\n};\n\nTEST(HTTPHandlerChainTests, getNextHandlerEmpty) {\n  TestHTTPChainHandler handler;\n  EXPECT_FALSE(handler.getNextHandler());\n}\n\nTEST(HTTPHandlerChainTests, setNextHandler) {\n  TestHTTPChainHandler handlerA;\n  auto handlerB = std::make_shared<TestHTTPChainHandler>();\n\n  handlerA.setNextHandler(handlerB);\n  EXPECT_EQ(handlerA.getNextHandler(), handlerB);\n\n  handlerA.setNextHandler(nullptr);\n  EXPECT_FALSE(handlerA.getNextHandler());\n}\n\nTEST(HTTPHandlerChainTests, getFrontEmpty) {\n  HTTPHandlerChain chain;\n  EXPECT_FALSE(chain.getFront());\n}\n\nTEST(HTTPHandlerChainTests, getBackEmpty) {\n  HTTPHandlerChain chain;\n  EXPECT_FALSE(chain.getBack());\n}\n\nTEST(HTTPHandlerChainTests, getFrontNonEmpty) {\n  HTTPHandlerChain chainFront;\n  chainFront.insertFront(std::make_shared<TestHTTPChainHandler>());\n  EXPECT_TRUE(chainFront.getFront());\n\n  HTTPHandlerChain chainBack;\n  chainBack.insertBack(std::make_shared<TestHTTPChainHandler>());\n  EXPECT_TRUE(chainBack.getFront());\n}\n\nTEST(HTTPHandlerChainTests, getBackNonEmpty) {\n  HTTPHandlerChain chainFront;\n  chainFront.insertFront(std::make_shared<TestHTTPChainHandler>());\n  EXPECT_TRUE(chainFront.getFront());\n\n  HTTPHandlerChain chainBack;\n  chainBack.insertBack(std::make_shared<TestHTTPChainHandler>());\n  EXPECT_TRUE(chainBack.getFront());\n}\n\nTEST(HTTPHandlerChainTests, insertFrontMultiple) {\n  HTTPHandlerChain chain;\n\n  auto chainHandlerA = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerB = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerC = std::make_shared<TestHTTPChainHandler>();\n\n  chain.insertFront(chainHandlerA);\n  chain.insertFront(chainHandlerB);\n  chain.insertFront(chainHandlerC);\n\n  // Check for expected ends\n  EXPECT_EQ(chain.getFront(), chainHandlerC);\n  EXPECT_EQ(chain.getBack(), chainHandlerA);\n\n  // Check for expected chain order\n  EXPECT_EQ(chainHandlerC->getNextHandler(), chainHandlerB);\n  EXPECT_EQ(chainHandlerB->getNextHandler(), chainHandlerA);\n  EXPECT_FALSE(chainHandlerA->getNextHandler());\n}\n\nTEST(HTTPHandlerChainTests, insertBackMultiple) {\n  HTTPHandlerChain chain;\n\n  auto chainHandlerA = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerB = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerC = std::make_shared<TestHTTPChainHandler>();\n\n  chain.insertBack(chainHandlerA);\n  chain.insertBack(chainHandlerB);\n  chain.insertBack(chainHandlerC);\n\n  // Check for expected ends\n  EXPECT_EQ(chain.getFront(), chainHandlerA);\n  EXPECT_EQ(chain.getBack(), chainHandlerC);\n\n  // Check for expected chain order\n  EXPECT_EQ(chainHandlerA->getNextHandler(), chainHandlerB);\n  EXPECT_EQ(chainHandlerB->getNextHandler(), chainHandlerC);\n  EXPECT_FALSE(chainHandlerC->getNextHandler());\n}\n\nTEST(HTTPHandlerChainTests, insertMultiple) {\n  HTTPHandlerChain chain;\n\n  auto chainHandlerA = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerB = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerC = std::make_shared<TestHTTPChainHandler>();\n  auto chainHandlerD = std::make_shared<TestHTTPChainHandler>();\n\n  chain.insertFront(chainHandlerA);\n  chain.insertBack(chainHandlerB);\n  chain.insertFront(chainHandlerC);\n  chain.insertBack(chainHandlerD);\n\n  // Check for expected ends\n  EXPECT_EQ(chain.getFront(), chainHandlerC);\n  EXPECT_EQ(chain.getBack(), chainHandlerD);\n\n  // Check for expected chain order\n  EXPECT_EQ(chainHandlerC->getNextHandler(), chainHandlerA);\n  EXPECT_EQ(chainHandlerA->getNextHandler(), chainHandlerB);\n  EXPECT_EQ(chainHandlerB->getNextHandler(), chainHandlerD);\n  EXPECT_FALSE(chainHandlerD->getNextHandler());\n}\n\nTEST(HTTPHandlerChainTests, insertFrontNull) {\n  HTTPHandlerChain chain;\n\n  auto chainHandler = std::make_shared<TestHTTPChainHandler>();\n  chain.insertFront(chainHandler);\n  chain.insertFront(nullptr);\n\n  // Check for expected ends, nullptr should not have been added\n  EXPECT_EQ(chain.getFront(), chainHandler);\n  EXPECT_EQ(chain.getBack(), chainHandler);\n}\n\nTEST(HTTPHandlerChainTests, insertBackNull) {\n  HTTPHandlerChain chain;\n\n  auto chainHandler = std::make_shared<TestHTTPChainHandler>();\n  chain.insertBack(chainHandler);\n  chain.insertBack(nullptr);\n\n  // Check for expected ends, nullptr should not have been added\n  EXPECT_EQ(chain.getFront(), chainHandler);\n  EXPECT_EQ(chain.getBack(), chainHandler);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPSourceTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"folly/coro/GmockHelpers.h\"\n#include \"proxygen/lib/http/coro/HTTPBodyEventQueue.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPHybridSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSourceHolder.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include \"proxygen/lib/http/coro/util/ExecutorSourceFilter.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Sleep.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <folly/logging/xlog.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/HTTP3ErrorCode.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\nusing namespace proxygen::coro;\nusing namespace testing;\nusing namespace proxygen;\n\nnamespace proxygen::coro::test {\n\nclass MockStreamSourceCallback : public HTTPStreamSource::Callback {\n public:\n  MockStreamSourceCallback() = default;\n\n  MOCK_METHOD(void, bytesProcessed, (HTTPCodec::StreamID, size_t, size_t));\n  MOCK_METHOD(void, windowOpen, (HTTPCodec::StreamID));\n  MOCK_METHOD(void,\n              sourceComplete,\n              (HTTPCodec::StreamID, folly::Optional<HTTPError>));\n};\n\nclass MockHTTPBodyEventQueueCallback : public HTTPBodyEventQueue::Callback {\n public:\n  MockHTTPBodyEventQueueCallback() = default;\n\n  MOCK_METHOD(void, onEgressBytesBuffered, (int64_t), (noexcept));\n};\n\nclass HTTPStreamSourceTest : public testing::Test {\n public:\n  void SetUp() override {\n    EXPECT_CALL(callback_, sourceComplete(_, _))\n        .WillOnce(InvokeWithoutArgs([this] {\n          if (terminateLoopOnComplete_) {\n            evb_.terminateLoopSoon();\n          }\n        }));\n    XCHECK(!stream_.sourceComplete());\n    XCHECK_EQ(stream_.window().getSize(), 65'535);\n  }\n\n protected:\n  folly::coro::Task<void> drainSource(\n      HTTPStreamSource* stream = nullptr,\n      uint32_t maxRead = std::numeric_limits<uint32_t>::max(),\n      bool* stopReading = nullptr) {\n    if (!stream) {\n      stream = &stream_;\n    }\n    bool final = false;\n    bool eom = false;\n    do {\n      if (stopReading && *stopReading) {\n        stream->stopReading(folly::none);\n        co_return;\n      }\n      auto headerEvent = co_await co_awaitTry(stream->readHeaderEvent());\n      if (headerEvent.hasException()) {\n        error_ = getHTTPError(headerEvent);\n        co_return;\n      }\n      final = headerEvent->isFinal();\n      eom = headerEvent->eom;\n      headerEvents_.emplace_back(std::move(*headerEvent));\n      // eom must be final\n      EXPECT_TRUE(final || !eom);\n    } while (!final && !eom);\n\n    while (!eom) {\n      if (stopReading && *stopReading) {\n        stream->stopReading(folly::none);\n        co_return;\n      }\n      auto bodyEvent = co_await co_awaitTry(stream->readBodyEvent(maxRead));\n      if (bodyEvent.hasException()) {\n        error_ = getHTTPError(bodyEvent);\n        co_return;\n      }\n      eom = bodyEvent->eom;\n      if (bodyEvent->eventType != HTTPBodyEvent::EventType::SUSPEND) {\n        bodyEvents_.emplace_back(std::move(*bodyEvent));\n      }\n    }\n  }\n\n  void run() {\n    evb_.loop();\n  }\n\n  void reset() {\n    headerEvents_.clear();\n    bodyEvents_.clear();\n    error_.reset();\n  }\n\n  folly::EventBase evb_;\n  NiceMock<MockStreamSourceCallback> callback_;\n  HTTPStreamSource stream_{\n      &evb_, 0, callback_, 65535, std::chrono::milliseconds(250)};\n  folly::Optional<HTTPError> error_;\n  std::vector<HTTPHeaderEvent> headerEvents_;\n  std::vector<HTTPBodyEvent> bodyEvents_;\n  bool terminateLoopOnComplete_{true};\n};\n\nCO_TEST(FixedSourceTest, BasicCopyTest) {\n  InSequence seq;\n\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"GET\");\n  request->setURL(\"https://test.facebook.com/\");\n\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  auto requestCopy = std::make_unique<HTTPFixedSource>(*requestSource);\n\n  auto headers = co_await requestSource->readHeaderEvent();\n  auto headersCopy = co_await requestCopy->readHeaderEvent();\n\n  EXPECT_NE(headers.headers, headersCopy.headers);\n  EXPECT_EQ(headers.headers->getMethodString(),\n            headersCopy.headers->getMethodString());\n  EXPECT_EQ(headers.headers->getURL(), headersCopy.headers->getURL());\n  EXPECT_EQ(headers.eom, headersCopy.eom);\n}\n\nCO_TEST(FixedSourceTest, BasicMoveTest) {\n  InSequence seq;\n\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"GET\");\n  request->setURL(\"https://test.facebook.com/\");\n\n  auto requestSource = HTTPFixedSource(std::move(request));\n  auto requestMove =\n      std::make_unique<HTTPFixedSource>(std::move(requestSource));\n\n  auto headers = co_await requestMove->readHeaderEvent();\n\n  EXPECT_FALSE(requestSource.msg_);\n  EXPECT_TRUE(requestSource.body_.empty());\n\n  EXPECT_EQ(\"GET\", headers.headers->getMethodString());\n  EXPECT_EQ(\"https://test.facebook.com/\", headers.headers->getURL());\n}\n\nTEST_F(HTTPStreamSourceTest, Basic) {\n  auto req = std::make_unique<HTTPMessage>();\n  req->setURL(\"/\");\n  stream_.headers(std::move(req), true);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n  EXPECT_EQ(headerEvents_[0].headers->getPathAsStringPiece(), \"/\");\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, true);\n}\n\nTEST_F(HTTPStreamSourceTest, Body) {\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream_.headers(std::move(resp));\n\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n\n  auto promiseSource = HTTPFixedSource::makeFixedResponse(\n      200, folly::IOBuf::copyBuffer(\"push body\"));\n\n  auto promise = std::make_unique<HTTPMessage>();\n  promise->setURL(\"/push\");\n  stream_.pushPromise(std::move(promise), promiseSource);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource(&stream_, 1)).start();\n  evb_.loopOnce();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 5);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 200);\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, false);\n  EXPECT_EQ(bodyEvents_[0].eventType, HTTPBodyEvent::BODY);\n  for (auto i = 1; i < 4; i++) {\n    EXPECT_EQ(bodyEvents_[i].eventType, HTTPBodyEvent::BODY);\n    EXPECT_EQ(bodyEvents_[i].eom, false);\n    bodyEvents_[0].event.body.append(bodyEvents_[i].event.body.move());\n  }\n  auto buf = bodyEvents_[0].event.body.move();\n  buf->coalesce();\n  EXPECT_EQ(buf->moveToFbString(), std::string(\"body\"));\n  EXPECT_EQ(bodyEvents_[0].eom, false);\n  EXPECT_EQ(bodyEvents_[4].eventType, HTTPBodyEvent::PUSH_PROMISE);\n  EXPECT_EQ(bodyEvents_[4].event.push.promise->getPathAsStringPiece(), \"/push\");\n  EXPECT_EQ(bodyEvents_[4].eom, true);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLength0) {\n  auto req = makePostRequest(0);\n  stream_.headers(std::move(req), false);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthShorterBody) {\n  auto req = makePostRequest(2);\n  stream_.headers(std::move(req), false);\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::CONTENT_LENGTH_MISMATCH);\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthLongerBody) {\n  auto req = makePostRequest(10);\n  stream_.headers(std::move(req), false);\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::CONTENT_LENGTH_MISMATCH);\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthMalformed) {\n  auto req = std::make_unique<HTTPMessage>();\n  req->getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"xxx\");\n  req->setURL(\"/\");\n  stream_.headers(std::move(req), false);\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 1);\n  EXPECT_EQ(headerEvents_[0].headers->getPathAsStringPiece(), \"/\");\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, false);\n  EXPECT_EQ(bodyEvents_[0].eom, true);\n\n  auto buf = bodyEvents_[0].event.body.move();\n  buf->coalesce();\n  EXPECT_EQ(buf->moveToFbString(), std::string(\"body\"));\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthNotOkForEmptyGetResponse) {\n  auto resp = makeResponse(200, 10);\n  stream_.headers(std::move(std::get<0>(resp)), true);\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::CONTENT_LENGTH_MISMATCH);\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthOkForEmptyHeadResponse) {\n  auto resp = makeResponse(200, 10);\n  stream_.skipContentLengthValidation();\n  stream_.headers(std::move(std::get<0>(resp)), true);\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 200);\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, true);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthIsNotIgnoredInChunkedBody) {\n  // Confirm the intent of an RFC violation in this test:\n  // According to https://datatracker.ietf.org/doc/html/rfc2616#section-4.4,\n  //    Messages must not include both a Content-Length header field\n  //      and a non-identity transfer-coding.\n  //    If the message does include a non-identity transfer-coding,\n  //      the Content-Length must be ignored.\n  // We don't validate either of the two parts, opting for comparing declared\n  //  Content-Length against what's actually received as in the non-chunked\n  //  case.\n  auto req = makePostRequest(40);\n  req->setIsChunked(true);\n  req->getHeaders().add(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  stream_.headers(std::move(req), false);\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::CONTENT_LENGTH_MISMATCH);\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, ContentLengthOkFor304ResponsesWithEmptyBody) {\n  auto resp = makeResponse(304, 10);\n  stream_.headers(std::move(std::get<0>(resp)), true);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 304);\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, true);\n}\n\nTEST_F(HTTPStreamSourceTest, FinalHeaders) {\n  EXPECT_TRUE(stream_.headersAllowed());\n  stream_.headers(makeResponse(100), false);\n  EXPECT_TRUE(stream_.headersAllowed());\n  stream_.headers(makeResponse(200), false);\n  EXPECT_FALSE(stream_.headersAllowed());\n  stream_.headers(makeResponse(100), false);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::INVALID_STATE_TRANSITION);\n}\n\nTEST_F(HTTPStreamSourceTest, BodyBeforeFinalHeaders) {\n  EXPECT_TRUE(stream_.headersAllowed());\n  stream_.headers(makeResponse(100), false);\n  EXPECT_TRUE(stream_.headersAllowed());\n  stream_.body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::INVALID_STATE_TRANSITION);\n}\n\nTEST_F(HTTPStreamSourceTest, DatagramPriorToHeader) {\n  // rx/tx-ing datagrams before headers is a SM violation\n  auto resp = makeResponse(200, 10);\n  stream_.datagram(makeBuf(100));\n  stream_.headers(std::move(std::get<0>(resp)), true);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, BodyQueueDatagramEOM) {\n  // if we rx/tx eom after datagram, we push an empty HTTPBodyEvent::BODY with\n  // eom to the end of the bodyQueue\n  auto resp = makeResponse(200, 0);\n  stream_.headers(std::move(std::get<0>(resp)), false);\n  stream_.datagram(makeBuf(10));\n  stream_.datagram(makeBuf(10));\n  stream_.eom();\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  // 2 datagrams plus empty HTTPBodyEvent::BODY w/ eom\n  EXPECT_EQ(bodyEvents_.size(), 3);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 200);\n  EXPECT_EQ(bodyEvents_[2].eom, true);\n}\n\nTEST(HTTPBodyStreamSourceTest, BodyOnlyStreamSource) {\n  folly::EventBase evb;\n  struct HTTPBodyStreamSource : public HTTPStreamSource {\n    using HTTPStreamSource::HTTPStreamSource;\n    using HTTPStreamSource::validateHeadersAndSkip;\n  } bodySource{&evb};\n\n  constexpr uint8_t kContentLength = 100;\n  auto req = getPostRequest(kContentLength);\n  // ingress SM transition to body; consumer expected to invoke only\n  // ::readBodyEvent\n  bodySource.validateHeadersAndSkip(req, /*eom=*/false);\n  bodySource.body(makeBuf(kContentLength), /*padding=*/0, /*eom=*/true);\n  auto res = folly::coro::blockingWait(bodySource.readBodyEvent(), &evb);\n  EXPECT_EQ(res.eventType, HTTPBodyEvent::BODY);\n  EXPECT_EQ(res.event.body.chainLength(), 100);\n  EXPECT_TRUE(res.eom);\n}\n\nclass HTTPStreamSourceTestPerCode\n    : public HTTPStreamSourceTest\n    , public ::testing::WithParamInterface<uint16_t> {};\n\nTEST_P(HTTPStreamSourceTestPerCode, ContentLengthIgnoredFor1xx) {\n  uint16_t responseCode = GetParam();\n  // Add a content length where it should be ignored\n  auto resp = makeResponse(responseCode, 10);\n  stream_.headers(std::move(std::get<0>(resp)), false);\n  stream_.headers(std::move(std::get<0>(makeResponse(200, 0))), true);\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 2);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\n// Note: 1xx shouldn't have a Content-Length at all, and having one on a\n// 101 Switching Protocols is especially bad.  TODO: validate this T138957911\nINSTANTIATE_TEST_SUITE_P(HTTPStreamSourceTests,\n                         HTTPStreamSourceTestPerCode,\n                         ::testing::Values(100, 102, 103));\n\nTEST_F(HTTPStreamSourceTest, BodyBackpressure) {\n  auto stream = new HTTPStreamSource(&evb_, folly::none, &callback_, 4000);\n  stream->setHeapAllocated();\n\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream->headers(std::move(resp));\n\n  EXPECT_EQ(stream->body(BufQueue(makeBuf(100)), 0),\n            HTTPStreamSource::FlowControlState::OPEN);\n  EXPECT_EQ(stream->body(BufQueue(makeBuf(3899)), 0),\n            HTTPStreamSource::FlowControlState::OPEN);\n  EXPECT_EQ(stream->body(BufQueue(makeBuf(1)), 0),\n            HTTPStreamSource::FlowControlState::CLOSED);\n  EXPECT_EQ(stream->body(BufQueue(makeBuf(10)), 0),\n            HTTPStreamSource::FlowControlState::CLOSED);\n  EXPECT_CALL(callback_, windowOpen(HTTPCodec::MaxStreamID))\n      .WillOnce(InvokeWithoutArgs([stream] { stream->eom(); }));\n\n  co_withExecutor(&evb_, drainSource(stream, 100)).start();\n  evb_.loopOnce();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 41);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 200);\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, false);\n}\n\nTEST_F(HTTPStreamSourceTest, KMinThreshold) {\n  /** Verifies that when kMinThreshold amount of bytes are read, we expect to\n   * also process kMinThreshold bytes in bytesProcessed() because of the\n   * absolute min threshold in WindowContainer.processed().\n   */\n  constexpr uint32_t kCapacity = 1024 * 1024;\n  constexpr uint32_t kMinThreshold = 128 * 1024;\n  auto stream = new HTTPStreamSource(&evb_, folly::none, &callback_, kCapacity);\n  stream->setHeapAllocated();\n\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream->headers(std::move(resp));\n\n  EXPECT_EQ(stream->body(BufQueue(makeBuf(kMinThreshold)), 0),\n            HTTPStreamSource::FlowControlState::OPEN);\n  EXPECT_CALL(\n      callback_,\n      bytesProcessed(HTTPCodec::MaxStreamID, kMinThreshold, kMinThreshold))\n      .WillOnce(InvokeWithoutArgs([stream] { stream->eom(); }));\n\n  co_withExecutor(&evb_, drainSource(stream, kMinThreshold)).start();\n  evb_.loopOnce();\n}\n\nTEST_F(HTTPStreamSourceTest, Trailers) {\n  auto req = std::make_unique<HTTPMessage>();\n  req->setURL(\"/\");\n  stream_.headers(std::move(req));\n  auto trailers = std::make_unique<HTTPHeaders>();\n  trailers->add(\"foo\", \"bar\");\n  stream_.trailers(std::move(trailers));\n\n  co_withExecutor(&evb_, drainSource()).start();\n  run();\n\n  EXPECT_FALSE(error_.hasValue());\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 1);\n  EXPECT_EQ(headerEvents_[0].eom, false);\n  EXPECT_EQ(bodyEvents_[0].eventType, HTTPBodyEvent::TRAILERS);\n  EXPECT_EQ(bodyEvents_[0].event.trailers->size(), 1);\n  EXPECT_EQ(bodyEvents_[0].eom, true);\n}\n\nTEST_F(HTTPStreamSourceTest, StateMachineError) {\n  NiceMock<MockStreamSourceCallback> callback;\n  auto validateError =\n      [this](HTTPStreamSource* stream, size_t headerEvents, size_t bodyEvents) {\n        co_withExecutor(&evb_, drainSource(stream)).start();\n        evb_.loopOnce();\n\n        EXPECT_TRUE(error_.hasValue());\n        EXPECT_EQ(error_->code, HTTPErrorCode::INVALID_STATE_TRANSITION);\n        EXPECT_EQ(headerEvents_.size(), headerEvents);\n        EXPECT_EQ(bodyEvents_.size(), bodyEvents);\n      };\n\n  // body error\n  auto stream = std::make_unique<HTTPStreamSource>(&evb_, 0, callback);\n  stream->body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  // Stick in an event to test drain after error\n  stream->eom();\n  validateError(stream.get(), 0, 0);\n\n  // headers error\n  reset();\n  stream = std::make_unique<HTTPStreamSource>(&evb_, 0, callback);\n  stream->headers(std::make_unique<HTTPMessage>());\n  stream->body(BufQueue(folly::IOBuf::copyBuffer(\"body\")), 0);\n  stream->headers(std::make_unique<HTTPMessage>());\n  validateError(stream.get(), 0, 0);\n\n  // pushPromise error\n  reset();\n  stream = std::make_unique<HTTPStreamSource>(&evb_, 0, callback);\n  stream->headers(std::make_unique<HTTPMessage>(), true);\n  stream->pushPromise(std::make_unique<HTTPMessage>(),\n                      HTTPFixedSource::makeFixedResponse(\n                          200, folly::IOBuf::copyBuffer(\"push body\")),\n                      false);\n  validateError(stream.get(), 0, 0);\n\n  // trailers error\n  reset();\n  // Use real cb at the end\n  stream = std::make_unique<HTTPStreamSource>(&evb_, 0, callback_);\n  stream->trailers(nullptr);\n  validateError(stream.get(), 0, 0);\n}\n\nTEST_F(HTTPStreamSourceTest, APIErrorConsumerLateHeaders) {\n  // Calling readHeaderEvent after readBodyEvent\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  co_withExecutor(\n      &evb_,\n      [](HTTPStreamSource* stream) -> folly::coro::Task<void> {\n        auto headerEvent = co_await co_awaitTry(stream->readHeaderEvent());\n        EXPECT_FALSE(headerEvent.hasException());\n\n        auto bodyEvent = co_await co_awaitTry(stream->readBodyEvent());\n        EXPECT_FALSE(bodyEvent.hasException());\n\n        headerEvent = co_await co_awaitTry(stream->readHeaderEvent());\n        EXPECT_TRUE(headerEvent.hasException());\n        EXPECT_EQ(getHTTPError(headerEvent).code,\n                  HTTPErrorCode::INVALID_STATE_TRANSITION);\n      }(&stream_))\n      .start();\n  evb_.loopOnce();\n\n  stream_.headers(std::move(resp));\n  stream_.body(makeBuf(100), 0);\n  evb_.loop();\n}\n\nTEST_F(HTTPStreamSourceTest, APIErrorConsumerEarlyBody) {\n  // Calling readBodyEvent before readHeaderEvent\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  co_withExecutor(&evb_,\n                  [](HTTPStreamSource* stream) -> folly::coro::Task<void> {\n                    auto bodyEvent =\n                        co_await co_awaitTry(stream->readBodyEvent());\n                    EXPECT_TRUE(bodyEvent.hasException());\n                    EXPECT_EQ(getHTTPError(bodyEvent).code,\n                              HTTPErrorCode::INVALID_STATE_TRANSITION);\n                  }(&stream_))\n      .start();\n  evb_.loopOnce();\n\n  stream_.headers(std::move(resp));\n  evb_.loop();\n}\n\nTEST_F(HTTPStreamSourceTest, StateMachineErrorHeaderWait) {\n  co_withExecutor(&evb_,\n                  [](HTTPStreamSource* stream) -> folly::coro::Task<void> {\n                    auto bodyEvent =\n                        co_await co_awaitTry(stream->readHeaderEvent());\n                    EXPECT_TRUE(bodyEvent.hasException());\n                    EXPECT_EQ(getHTTPError(bodyEvent).code,\n                              HTTPErrorCode::INVALID_STATE_TRANSITION);\n                  }(&stream_))\n      .start();\n  evb_.loopOnce();\n\n  stream_.eom();\n  evb_.loop();\n}\n\nTEST_F(HTTPStreamSourceTest, StopReadingWhileWaiting) {\n  // Heap allocated stream to verify stopReading doesn't delete while there's a\n  // coro waiting\n  auto stream = new HTTPStreamSource(&evb_, folly::none, &callback_, 4000);\n  stream->setHeapAllocated();\n\n  terminateLoopOnComplete_ = false;\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  co_withExecutor(\n      &evb_,\n      [](HTTPStreamSource* stream) -> folly::coro::Task<void> {\n        auto bodyEvent = co_await co_awaitTry(stream->readHeaderEvent());\n        EXPECT_TRUE(bodyEvent.hasException());\n        EXPECT_EQ(getHTTPError(bodyEvent).code, HTTPErrorCode::NO_ERROR);\n      }(stream))\n      .start();\n  evb_.loopOnce();\n\n  stream->stopReading(folly::none);\n  evb_.loop();\n}\n\nTEST_F(HTTPStreamSourceTest, BodyError) {\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream_.headers(std::move(resp));\n\n  co_withExecutor(&evb_, drainSource()).start();\n  evb_.loopOnce();\n\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n  EXPECT_EQ(headerEvents_[0].headers->getStatusCode(), 200);\n  EXPECT_EQ(headerEvents_[0].isFinal(), true);\n  EXPECT_EQ(headerEvents_[0].eom, false);\n\n  stream_.abort(HTTPErrorCode::TRANSPORT_READ_ERROR);\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::TRANSPORT_READ_ERROR);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, stopReading) {\n  bool stopReading = false;\n  co_withExecutor(&evb_, drainSource(&stream_, 100, &stopReading)).start();\n  evb_.loopOnce();\n  stopReading = true;\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream_.headers(std::move(resp));\n  run();\n\n  EXPECT_EQ(headerEvents_.size(), 1);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nTEST_F(HTTPStreamSourceTest, cancel) {\n  folly::CancellationSource cancellationSource;\n  co_withExecutor(&evb_,\n                  folly::coro::co_withCancellation(\n                      cancellationSource.getToken(), drainSource()))\n      .start();\n  evb_.loopOnce();\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  stream_.headers(std::move(resp));\n  cancellationSource.requestCancellation();\n  run();\n\n  EXPECT_TRUE(error_.hasValue());\n  EXPECT_EQ(error_->code, HTTPErrorCode::CORO_CANCELLED);\n  EXPECT_EQ(headerEvents_.size(), 0);\n  EXPECT_EQ(bodyEvents_.size(), 0);\n}\n\nclass BodyEventQueueTest : public testing::Test {\n public:\n  BodyEventQueueTest() : queue_(&evb_, 1, queueCb, 100) {\n  }\n\n  void SetUp() override {\n    queue_.setSource(&source_);\n  }\n\n  folly::DrivableExecutor* getExecutor() {\n    return &evb_;\n  }\n\n  void testCancelCoro(std::function<folly::coro::Task<void>()> coro) {\n    folly::CancellationSource cancellationSource;\n    co_withExecutor(\n        &evb_,\n        folly::coro::co_withCancellation(cancellationSource.getToken(), coro()))\n        .start();\n    evb_.loopOnce();\n    cancellationSource.requestCancellation();\n    evb_.loop();\n  }\n\n  folly::EventBase evb_;\n  StrictMock<MockHTTPSource> source_;\n  MockHTTPBodyEventQueueCallback queueCb;\n  MockHTTPBodyEventQueue queue_;\n};\n\nTEST_F(BodyEventQueueTest, TestCancelReadHeaders) {\n  // yield an HTTPHeaderEvent from ::readHeaderEvent\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponse(200)), false))));\n  EXPECT_CALL(source_, stopReading(_));\n\n  // despite cancellation, the header event will be yielded\n  folly::CancellationSource cs;\n  auto res = folly::coro::blockingWait(\n      co_withCancellation(cs.getToken(), co_awaitTry(queue_.readHeaderEvent())),\n      &evb_);\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(BodyEventQueueTest, TestMatchingContentLength) {\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponse(200, 50)), false))));\n\n  EXPECT_CALL(source_, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent(makeBuf(25), false))))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent(makeBuf(25), true))));\n\n  EXPECT_CALL(queue_, contentLengthMismatch()).Times(0);\n\n  co_await co_awaitTry(queue_.readHeaderEvent());\n  auto res = co_await co_awaitTry(queue_.readBodyEvent());\n  EXPECT_FALSE(res.hasException());\n  res = co_await co_awaitTry(queue_.readBodyEvent());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(BodyEventQueueTest, TestMismatchedContentLengthOverExpected) {\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponse(200, 50)), false))));\n\n  EXPECT_CALL(source_, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent(makeBuf(75), true))));\n\n  EXPECT_CALL(queue_, contentLengthMismatch()).Times(1);\n\n  co_await co_awaitTry(queue_.readHeaderEvent());\n  auto res = co_await co_awaitTry(queue_.readBodyEvent());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(BodyEventQueueTest, TestMismatchedContentLengthUnderExpected) {\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponse(200, 50)), false))));\n\n  EXPECT_CALL(source_, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent(makeBuf(25), true))));\n\n  EXPECT_CALL(queue_, contentLengthMismatch()).Times(1);\n\n  co_await co_awaitTry(queue_.readHeaderEvent());\n  auto res = co_await co_awaitTry(queue_.readBodyEvent());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(BodyEventQueueTest, TestSkipValidationOn304EmptyBody) {\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponse(304, 10)), true))));\n\n  EXPECT_CALL(queue_, contentLengthMismatch()).Times(0);\n\n  auto res = co_await co_awaitTry(queue_.readHeaderEvent());\n  EXPECT_FALSE(res.hasException());\n}\n\nCO_TEST_F_X(BodyEventQueueTest, TestInvalidBodyLength) {\n  EXPECT_CALL(source_, readHeaderEvent())\n      .WillOnce(Return(folly::coro::makeTask<HTTPHeaderEvent>(HTTPHeaderEvent(\n          std::make_unique<HTTPMessage>(getResponseWithInvalidBodyLength()),\n          false))));\n\n  EXPECT_CALL(source_, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent(makeBuf(25), true))));\n\n  EXPECT_CALL(queue_, contentLengthMismatch()).Times(0);\n\n  co_await co_awaitTry(queue_.readHeaderEvent());\n  auto res = co_await co_awaitTry(queue_.readBodyEvent());\n  EXPECT_FALSE(res.hasException());\n}\n\nTEST_F(BodyEventQueueTest, TestCancelReadHeadersWithError) {\n  auto coro = [this]() -> folly::coro::Task<void> {\n    EXPECT_CALL(source_, readHeaderEvent())\n        .WillOnce(Return(folly::coro::makeErrorTask<HTTPHeaderEvent>(\n            folly::make_exception_wrapper<HTTPError>(\n                HTTPErrorCode::CORO_CANCELLED, \"\"))));\n\n    auto res = co_await co_awaitTry(queue_.readHeaderEvent());\n    EXPECT_TRUE(res.hasException());\n    EXPECT_EQ(getHTTPError(res).code, HTTPErrorCode::CORO_CANCELLED);\n  };\n  testCancelCoro(coro);\n}\n\nTEST_F(BodyEventQueueTest, TestCancelReadBody) {\n  auto coro = [this]() -> folly::coro::Task<void> {\n    EXPECT_CALL(source_, readBodyEvent(_))\n        .WillOnce(folly::coro::gmock_helpers::CoInvoke(\n            [](uint32_t) -> folly::coro::Task<HTTPBodyEvent> {\n              co_await folly::coro::sleepReturnEarlyOnCancel(\n                  std::chrono::minutes(1));\n              co_return HTTPBodyEvent(std::unique_ptr<folly::IOBuf>(nullptr),\n                                      false);\n            }));\n    EXPECT_CALL(source_, stopReading(_));\n    auto res = co_await co_awaitTry(queue_.readBodyEvent());\n    EXPECT_TRUE(res.hasException());\n    EXPECT_EQ(getHTTPError(res).code, HTTPErrorCode::CORO_CANCELLED);\n  };\n  testCancelCoro(coro);\n}\n\nTEST_F(BodyEventQueueTest, TestCancelReadBodyWithError) {\n  auto coro = [this]() -> folly::coro::Task<void> {\n    EXPECT_CALL(source_, readBodyEvent(_))\n        .WillOnce(Return(folly::coro::makeErrorTask<HTTPBodyEvent>(\n            folly::make_exception_wrapper<HTTPError>(\n                HTTPErrorCode::CORO_CANCELLED, \"\"))));\n    auto res = co_await co_awaitTry(queue_.readBodyEvent());\n    EXPECT_TRUE(res.hasException());\n    EXPECT_EQ(getHTTPError(res).code, HTTPErrorCode::CORO_CANCELLED);\n  };\n  testCancelCoro(coro);\n}\n\nTEST_F(BodyEventQueueTest, TestCancelReadBodyAwaitingBuffer) {\n  auto coro = [this]() -> folly::coro::Task<void> {\n    EXPECT_CALL(source_, readBodyEvent(_))\n        .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n            HTTPBodyEvent(makeBuf(100), false))));\n    EXPECT_CALL(source_, stopReading(_));\n    auto res = co_await co_awaitTry(queue_.readBodyEvent(100));\n    EXPECT_FALSE(res.hasException());\n    EXPECT_FALSE(res->eom);\n    res = co_await co_awaitTry(queue_.readBodyEvent());\n    EXPECT_TRUE(res.hasException());\n    EXPECT_EQ(getHTTPError(res).code, HTTPErrorCode::CORO_CANCELLED);\n  };\n  testCancelCoro(coro);\n}\n\nTEST(HTTPSourceFilterTest, test) {\n  HTTPSourceHolder holder(HTTPFixedSource::makeFixedRequest(\"/\"));\n}\n\nTEST(HTTPSourceReader, test) {\n  folly::EventBase evb;\n  size_t events = 0;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n  // Add a push\n  source->pushes_.emplace_back(\n      std::make_unique<HTTPMessage>(getGetRequest(\"/push\")),\n      HTTPFixedSource::makeFixedResponse(200, makeBuf(50)),\n      /*eom=*/false);\n  // Add trailers\n  source->trailers_ = std::make_unique<HTTPHeaders>();\n  source->trailers_->add(\"x-trailer1\", \"trailer1\");\n\n  HTTPSourceReader reader(source);\n  reader\n      .preRead([&events]() {\n        events++;\n        return HTTPSourceReader::Continue;\n      })\n      .onHeaders([&events](std::unique_ptr<HTTPMessage> headers,\n                           bool final,\n                           bool eom) {\n        events++;\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(final);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onPushPromise([&events](std::unique_ptr<HTTPMessage> promise,\n                               HTTPSourceHolder,\n                               bool eom) {\n        events++;\n        EXPECT_EQ(promise->getPathAsStringPiece(), \"/push\");\n        EXPECT_FALSE(eom);\n        // response gets a stopReading on dtor\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&events](BufQueue body, bool eom) {\n        events++;\n        EXPECT_EQ(body.chainLength(), 100);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onTrailers([&events](std::unique_ptr<HTTPHeaders> trailers) {\n        events++;\n        EXPECT_EQ(trailers->size(), 1);\n        EXPECT_EQ(trailers->getSingleOrEmpty(\"x-trailer1\"), \"trailer1\");\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  evb.loop();\n  EXPECT_EQ(events, 4 * 2);\n}\n\nTEST(HTTPSourceReader, TestMissingTrailersFn) {\n  folly::EventBase evb;\n  size_t events = 0;\n  size_t bodyEvents = 0;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n  // add trailers\n  source->trailers_ = std::make_unique<HTTPHeaders>();\n  source->trailers_->add(\"x-trailer\", \"fb-header\");\n\n  HTTPSourceReader reader(source);\n  reader\n      .preRead([&events]() {\n        events++;\n        return HTTPSourceReader::Continue;\n      })\n      .onHeaders([&events](std::unique_ptr<HTTPMessage> headers,\n                           bool final,\n                           bool eom) {\n        events++;\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(final);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&events, &bodyEvents](BufQueue body, bool eom) {\n        events++;\n        // first body event should contain body\n        if (bodyEvents == 0) {\n          EXPECT_EQ(body.chainLength(), 100);\n          EXPECT_FALSE(eom);\n        } else {\n          // second body event should invoked when we rx trailer without having\n          // set onTrailer(...)\n          EXPECT_EQ(body.chainLength(), 0);\n          EXPECT_TRUE(eom);\n        }\n        bodyEvents++;\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  evb.loop();\n  // 2 * 3 because onBody(...) gets invoked twice\n  EXPECT_EQ(events, 2 * 3);\n  EXPECT_EQ(bodyEvents, 2);\n}\n\nTEST(HTTPSourceReader, stop) {\n  folly::EventBase evb;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n\n  HTTPSourceReader reader(source);\n  reader\n      .onHeaders(\n          [](std::unique_ptr<HTTPMessage> headers, bool final, bool eom) {\n            EXPECT_EQ(headers->getStatusCode(), 200);\n            EXPECT_TRUE(final);\n            EXPECT_FALSE(eom);\n            return HTTPSourceReader::Cancel; // Stop reading\n          })\n      .onBody([](BufQueue, bool) {\n        XLOG(FATAL) << \"onBody called after stop\";\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  evb.loop();\n}\n\nTEST(HTTPSourceReader, cancelHeaders) {\n  folly::EventBase evb;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n\n  folly::CancellationSource cancellationSource;\n  HTTPSourceReader reader(source);\n  reader\n      .onHeadersAsync([](std::unique_ptr<HTTPMessage> headers,\n                         bool final,\n                         bool eom) -> folly::coro::Task<bool> {\n        co_await folly::coro::sleepReturnEarlyOnCancel(\n            std::chrono::seconds(10));\n        co_return HTTPSourceReader::Continue;\n      })\n      .onBody([](BufQueue, bool) {\n        XLOG(FATAL) << \"onBody called after cancel\";\n        return HTTPSourceReader::Continue;\n      })\n      .onError([](const HTTPSourceReader::ErrorContext&, const HTTPError&) {\n        XLOG(FATAL) << \"onError called after cancel\";\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader,\n                     folly::CancellationSource& cancellationSource)\n                      -> folly::coro::Task<void> {\n                    co_await folly::coro::co_withCancellation(\n                        cancellationSource.getToken(), reader.read());\n                  }(reader, cancellationSource))\n      .start();\n  evb.loopOnce();\n  cancellationSource.requestCancellation();\n  evb.loop();\n}\n\nTEST(HTTPSourceReader, cancelPreRead) {\n  folly::EventBase evb;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n\n  folly::CancellationSource cancellationSource;\n  HTTPSourceReader reader(source);\n  reader\n      .preReadAsync([]() -> folly::coro::Task<bool> {\n        co_await folly::coro::sleepReturnEarlyOnCancel(\n            std::chrono::seconds(10));\n        co_return HTTPSourceReader::Continue;\n      })\n      .onHeaders(\n          [](std::unique_ptr<HTTPMessage> headers, bool final, bool eom) {\n            XLOG(FATAL) << \"onHeaders called after cancel\";\n            return HTTPSourceReader::Continue;\n          })\n      .onError([](const HTTPSourceReader::ErrorContext&, const HTTPError&) {\n        XLOG(FATAL) << \"onError called after cancel\";\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader,\n                     folly::CancellationSource& cancellationSource)\n                      -> folly::coro::Task<void> {\n                    co_await folly::coro::co_withCancellation(\n                        cancellationSource.getToken(), reader.read());\n                  }(reader, cancellationSource))\n      .start();\n  evb.loopOnce();\n  cancellationSource.requestCancellation();\n  evb.loop();\n}\n\nTEST(HTTPSourceReader, cancelYieldException) {\n  folly::EventBase evb;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n\n  folly::CancellationSource cancellationSource;\n  HTTPSourceReader reader(source);\n  reader\n      .onHeadersAsync([](std::unique_ptr<HTTPMessage> headers,\n                         bool final,\n                         bool eom) -> folly::coro::Task<bool> {\n        co_await folly::coro::sleep(std::chrono::seconds(10));\n        co_return HTTPSourceReader::Continue;\n      })\n      .onError([](const HTTPSourceReader::ErrorContext&, const HTTPError& err) {\n        EXPECT_EQ(err.code, HTTPErrorCode::INTERNAL_ERROR);\n        EXPECT_EQ(err.msg, \"Operation cancelled\");\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader,\n                     folly::CancellationSource& cancellationSource)\n                      -> folly::coro::Task<void> {\n                    co_await folly::coro::co_withCancellation(\n                        cancellationSource.getToken(), reader.read());\n                  }(reader, cancellationSource))\n      .start();\n  evb.loopOnce();\n  cancellationSource.requestCancellation();\n  evb.loop();\n}\n\nTEST(HTTPSourceReader, datagrams) {\n  folly::EventBase evb;\n  auto* source = new HTTPStreamSource(&evb);\n  source->setHeapAllocated();\n  // queue headers, 3x datagrams, then EOM\n  source->headers(makeResponse(200));\n  source->datagram(makeBuf(10));\n  source->datagram(makeBuf(10));\n  source->datagram(makeBuf(10));\n  source->eom();\n\n  size_t numDatagrams = 0, numBytesDatagrams = 0;\n\n  HTTPSourceReader reader(source);\n  reader\n      .onHeaders(\n          [](std::unique_ptr<HTTPMessage> headers, bool final, bool eom) {\n            EXPECT_EQ(headers->getStatusCode(), 200);\n            EXPECT_TRUE(final);\n            EXPECT_FALSE(eom);\n            return HTTPSourceReader::Continue; // Stop reading\n          })\n      .onDatagram([&](BufQueue buf) {\n        numDatagrams++;\n        numBytesDatagrams += buf.chainLength();\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  evb.loop();\n\n  EXPECT_EQ(numDatagrams, 3);\n  EXPECT_EQ(numBytesDatagrams, 30);\n}\n\nTEST(HTTPSourceReader, error) {\n  folly::EventBase evb;\n  size_t events = 0;\n  auto source =\n      new HTTPErrorSource(HTTPError(HTTPErrorCode::PROTOCOL_ERROR, \"oops\"));\n\n  HTTPSourceReader reader(source);\n  reader.onError([&events](HTTPSourceReader::ErrorContext ec, HTTPError error) {\n    events++;\n    EXPECT_EQ(error.code, HTTPErrorCode::PROTOCOL_ERROR);\n    EXPECT_EQ(ec, HTTPSourceReader::ErrorContext::HEADERS);\n  });\n  co_withExecutor(&evb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  evb.loop();\n  EXPECT_EQ(events, 1);\n}\n\nTEST(HTTPSourceReader, MaxReadSize) {\n  folly::EventBase evb;\n  MockHTTPSource mockSource;\n  HTTPSourceReader reader{&mockSource};\n  auto headerEv = HTTPHeaderEvent{makeResponse(200), /*inEOM=*/false};\n\n  {\n    InSequence s;\n    EXPECT_CALL(mockSource, readHeaderEvent())\n        .WillOnce(Return(folly::coro::makeTask(std::move(headerEv))));\n    EXPECT_CALL(mockSource, readBodyEvent(100))\n        .WillOnce(Return(folly::coro::makeTask(\n            HTTPBodyEvent{makeBuf(100), /*inEOM=*/false})))\n        .RetiresOnSaturation();\n    EXPECT_CALL(mockSource, readBodyEvent(100))\n        .WillOnce(Return(\n            folly::coro::makeTask(HTTPBodyEvent{nullptr, /*inEOM=*/true})));\n  }\n\n  co_withExecutor(&evb, reader.read(/*maxBodySize=*/100)).start();\n  evb.loop();\n}\n\nTEST(HTTPSourceReader, filter) {\n  folly::EventBase evb;\n  size_t events = 0;\n  auto source = HTTPFixedSource::makeFixedResponse(200, makeBuf(100));\n\n  HTTPSourceReader reader;\n  class ByteCountFilter : public HTTPSourceFilter {\n   public:\n    folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n      XLOG(INFO) << __func__;\n      auto bodyEvent = co_await readBodyEventImpl(max);\n      bytes += (bodyEvent.eventType == HTTPBodyEvent::BODY)\n                   ? bodyEvent.event.body.chainLength()\n                   : 0;\n      co_return bodyEvent;\n    }\n    size_t bytes = 0;\n  };\n  ByteCountFilter filter;\n  reader.insertFilter(&filter);\n  reader.setSource(source);\n  reader\n      .onHeaders([&events](std::unique_ptr<HTTPMessage> headers,\n                           bool final,\n                           bool eom) {\n        events++;\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(final);\n        EXPECT_FALSE(eom);\n        return false;\n      })\n      .onBody([&events](BufQueue body, bool eom) {\n        events++;\n        EXPECT_EQ(body.chainLength(), 100);\n        EXPECT_TRUE(eom);\n        return false;\n      });\n  folly::coro::blockingWait(reader.read(), &evb);\n  EXPECT_EQ(filter.bytes, 100);\n  EXPECT_EQ(events, 2);\n}\n\nTEST(HTTPErrorTests, ErrorStrings) {\n  EXPECT_EQ(getErrorString(HTTPErrorCode::PROTOCOL_ERROR), \"PROTOCOL_ERROR\");\n  EXPECT_EQ(getErrorString(HTTPErrorCode::QPACK_DECOMPRESSION_FAILED),\n            \"QPACK_DECOMPRESSION_FAILED\");\n  EXPECT_EQ(getErrorString(HTTPErrorCode::INVALID_STATE_TRANSITION),\n            \"INVALID_STATE_TRANSITION\");\n  EXPECT_EQ(getErrorString(HTTPErrorCode(ErrorCode::NO_ERROR)), \"NO_ERROR\");\n  EXPECT_EQ(getErrorString(HTTPErrorCode(HTTP3::ErrorCode::HTTP_NO_ERROR)),\n            \"_H3_NO_ERROR\");\n  EXPECT_EQ(getErrorString(HTTPErrorCode(100000)), \"UNKNOWN_ERROR\");\n}\n\nTEST(HTTPEventTest, DefaultTrailers) {\n  HTTPBodyEvent trailerEvent1(std::make_unique<HTTPHeaders>());\n  HTTPBodyEvent bodyEvent(nullptr, true);\n  trailerEvent1 = std::move(bodyEvent);\n}\n\nCO_TEST(HybridSourceTest, BasicTest) {\n  // Verify that a source holder holds a HybridSource instead of\n  // taking over its source\n  InSequence seq;\n\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"GET\");\n  request->setURL(\"https://test.facebook.com/\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder;\n  requestSourceHolder.setSource(requestSource.get());\n\n  auto headers = co_await requestSourceHolder.readHeaderEvent();\n\n  HTTPHybridSource hybridSource(std::move(headers.headers),\n                                std::move(requestSourceHolder));\n  HTTPSourceHolder combinedSourceHolder(&hybridSource);\n\n  EXPECT_TRUE(bool(combinedSourceHolder));\n}\n\nCO_TEST(HybridSourceTest, StopReading) {\n  auto request = std::make_unique<HTTPMessage>();\n  request->setMethod(\"GET\");\n  request->setURL(\"https://test.facebook.com/\");\n  auto requestSource = std::make_unique<HTTPFixedSource>(std::move(request));\n  HTTPSourceHolder requestSourceHolder;\n  requestSourceHolder.setSource(requestSource.get());\n\n  auto headers = co_await requestSourceHolder.readHeaderEvent();\n\n  auto hybridSource = new HTTPHybridSource(std::move(headers.headers), nullptr);\n  hybridSource->setHeapAllocated();\n  HTTPSourceHolder combinedSourceHolder(hybridSource);\n\n  EXPECT_TRUE(bool(combinedSourceHolder));\n\n  combinedSourceHolder.stopReading();\n}\n\nclass NameFilter : public HTTPSourceFilter {\n public:\n  NameFilter(std::string inName, std::vector<std::string>& inNames)\n      : name(std::move(inName)), names(inNames) {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    auto headerEvent = co_await readHeaderEventImpl();\n    auto g = folly::makeGuard(lifetime(headerEvent));\n    names.push_back(name);\n    co_return headerEvent;\n  }\n  std::string name;\n  std::vector<std::string>& names;\n};\n\nCO_TEST(FilterChainTest, SetBeforeInsert) {\n  FilterChain chain;\n  EXPECT_EQ(chain.head(), nullptr);\n  auto reqSource = HTTPFixedSource::makeFixedRequest(\"/filterchain\");\n  chain.setSource(reqSource);\n  EXPECT_NE(chain.head(), nullptr);\n  std::vector<std::string> names;\n  auto nameFilter = new NameFilter(\"filter1\", names);\n  chain.insertFront(nameFilter);\n  nameFilter = new NameFilter(\"filter2\", names);\n  chain.insertEnd(nameFilter);\n  auto headerEvent = co_await chain.head()->readHeaderEvent();\n  // head_ -> filter1 -> filter2 -> source\n  EXPECT_EQ(names.size(), 2);\n  EXPECT_EQ(names[0], \"filter2\"); // vector is backwards\n  EXPECT_EQ(names[1], \"filter1\");\n  EXPECT_EQ(headerEvent.headers->getPathAsStringPiece(), \"/filterchain\");\n}\n\nCO_TEST(FilterChainTest, ReleaseFilterChain) {\n  auto readChainTask = [](HTTPSourceHolder holder) -> folly::coro::Task<void> {\n    auto headerEvent = co_await co_awaitTry(holder.readHeaderEvent());\n    CHECK(!headerEvent.hasException());\n    EXPECT_EQ(headerEvent->headers->getPathAsStringPiece(), \"/filterchain\");\n    co_return;\n  };\n  FilterChain chain;\n  EXPECT_EQ(chain.head(), nullptr);\n  auto reqSource = HTTPFixedSource::makeFixedRequest(\"/filterchain\");\n  chain.setSource(reqSource);\n  EXPECT_NE(chain.head(), nullptr);\n  std::vector<std::string> names;\n  auto nameFilter = new NameFilter(\"filter1\", names);\n  chain.insertFront(nameFilter);\n  nameFilter = new NameFilter(\"filter2\", names);\n  chain.insertEnd(nameFilter);\n  // release ownership\n  co_await readChainTask(chain.release());\n  // head_ -> filter1 -> filter2 -> source\n  EXPECT_EQ(names.size(), 2);\n  EXPECT_EQ(names[0], \"filter2\"); // vector is backwards\n  EXPECT_EQ(names[1], \"filter1\");\n}\n\nCO_TEST(FilterChainTest, InsertBeforeSet) {\n  FilterChain chain;\n  EXPECT_EQ(chain.head(), nullptr);\n  std::vector<std::string> names;\n  auto nameFilter = new NameFilter(\"filter1\", names);\n  chain.insertEnd(nameFilter);\n  EXPECT_EQ(chain.head(), nullptr);\n\n  nameFilter = new NameFilter(\"filter2\", names);\n  chain.insertFront(nameFilter);\n  EXPECT_EQ(chain.head(), nullptr);\n\n  auto reqSource = HTTPFixedSource::makeFixedRequest(\"/filterchain\");\n  chain.setSource(reqSource);\n  EXPECT_NE(chain.head(), nullptr);\n  auto headerEvent = co_await chain.head()->readHeaderEvent();\n  // head_ -> filter2 -> filter1 -> source\n  EXPECT_EQ(names.size(), 2);\n  EXPECT_EQ(names[0], \"filter1\"); // vector is backwards\n  EXPECT_EQ(names[1], \"filter2\");\n  EXPECT_EQ(headerEvent.headers->getPathAsStringPiece(), \"/filterchain\");\n}\n\nCO_TEST(FilterChainTest, StopReading) {\n  FilterChain chain;\n  EXPECT_EQ(chain.head(), nullptr);\n  std::vector<std::string> names;\n  auto nameFilter = new NameFilter(\"filter1\", names);\n  chain.insertEnd(nameFilter);\n  EXPECT_EQ(chain.head(), nullptr);\n\n  nameFilter = new NameFilter(\"filter2\", names);\n  chain.insertFront(nameFilter);\n  EXPECT_EQ(chain.head(), nullptr);\n\n  auto reqSource = HTTPFixedSource::makeFixedResponse(200, \"hello world\");\n  chain.setSource(reqSource);\n  EXPECT_NE(chain.head(), nullptr);\n  auto headerEvent = co_await chain.head()->readHeaderEvent();\n  // head_ -> filter2 -> filter1 -> source\n  EXPECT_EQ(names.size(), 2);\n  EXPECT_EQ(names[0], \"filter1\"); // vector is backwards\n  EXPECT_EQ(names[1], \"filter2\");\n  EXPECT_EQ(headerEvent.headers->getStatusCode(), 200);\n  EXPECT_NE(chain.head(), nullptr); // not done yet\n  // didn't read body, implicit stopReading from ~FilterChain\n}\n\nCO_TEST(ConsumerProducer, TestConcurrentAccess) {\n  folly::ScopedEventBaseThread producerEvb{};\n  folly::EventBase consumerEvb;\n\n  size_t events = 0;\n  auto source = new HTTPStreamSource(producerEvb.getEventBase());\n  source->setHeapAllocated();\n\n  // space out producer work to introduce randomness in execution\n  producerEvb.add(\n      [source]() { source->headers(makeResponse(200), /*eom=*/false); });\n  producerEvb.add(\n      [source]() { source->body(makeBuf(100), /*padding=*/0, /*eom=*/false); });\n  producerEvb.add([source]() {\n    // Add a push\n    source->pushPromise(std::make_unique<HTTPMessage>(getGetRequest(\"/push\")),\n                        HTTPFixedSource::makeFixedResponse(200, makeBuf(50)),\n                        /*eom=*/false);\n  });\n  producerEvb.add([source]() {\n    // Add trailers\n    auto trailers = std::make_unique<HTTPHeaders>();\n    trailers->add(\"x-trailer1\", \"trailer1\");\n    source->trailers(std::move(trailers));\n  });\n  producerEvb.add([source]() { source->eom(); });\n\n  auto execFilter = ExecutorSourceFilter::make(producerEvb.getEventBase());\n  execFilter->setSource(source);\n  HTTPSourceReader reader(execFilter.release());\n  reader\n      .preRead([&events]() {\n        events++;\n        return HTTPSourceReader::Continue;\n      })\n      .onHeaders([&events](std::unique_ptr<HTTPMessage> headers,\n                           bool final,\n                           bool eom) {\n        events++;\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(final);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onPushPromise([&events](std::unique_ptr<HTTPMessage> promise,\n                               HTTPSourceHolder,\n                               bool eom) {\n        events++;\n        EXPECT_EQ(promise->getPathAsStringPiece(), \"/push\");\n        EXPECT_FALSE(eom);\n        // response gets a stopReading on dtor\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([&events](BufQueue body, bool eom) {\n        events++;\n        EXPECT_EQ(body.chainLength(), 100);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onTrailers([&events](std::unique_ptr<HTTPHeaders> trailers) {\n        events++;\n        EXPECT_EQ(trailers->size(), 1);\n        EXPECT_EQ(trailers->getSingleOrEmpty(\"x-trailer1\"), \"trailer1\");\n      });\n  co_withExecutor(&consumerEvb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  consumerEvb.loop();\n  co_return;\n}\n\nCO_TEST(ConsumerProducer, TestConcurrentAccessWithAbort) {\n  folly::ScopedEventBaseThread producerEvb{};\n  folly::EventBase consumerEvb;\n\n  size_t events = 0;\n  auto source = new HTTPStreamSource(producerEvb.getEventBase());\n  source->setHeapAllocated();\n\n  // space out producer work to introduce randomness in execution\n  producerEvb.add(\n      [source]() { source->headers(makeResponse(200), /*eom=*/false); });\n  producerEvb.add(\n      [source]() { source->body(makeBuf(100), /*padding=*/0, /*eom=*/false); });\n  producerEvb.add([source]() { source->abort(HTTPErrorCode::CANCEL); });\n\n  auto execFilter = ExecutorSourceFilter::make(producerEvb.getEventBase());\n  execFilter->setSource(source);\n  HTTPSourceReader reader(execFilter.release());\n  reader\n      .preRead([&events]() {\n        events++;\n        return HTTPSourceReader::Continue;\n      })\n      .onHeaders([&events](std::unique_ptr<HTTPMessage> headers,\n                           bool final,\n                           bool eom) {\n        events++;\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(final);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([](BufQueue body, bool eom) {\n        EXPECT_EQ(body.chainLength(), 100);\n        EXPECT_FALSE(eom);\n        return HTTPSourceReader::Continue;\n      });\n  co_withExecutor(&consumerEvb,\n                  [](HTTPSourceReader& reader) -> folly::coro::Task<void> {\n                    co_await reader.read();\n                  }(reader))\n      .start();\n  consumerEvb.loop();\n  co_return;\n}\n\nCO_TEST(ConsumerProducer, TestHTTPStreamSourceHolderDestructor) {\n  // HTTPStreamSourceHolder (wrapper around HTTPStreamSource) is not thread\n  // safe, consumer and producer should be running within the same thread.\n  // However, this tests that the destructor of HTTPStreamSourceHolder can be\n  // invoked just fine outside of the consumerAndProducerEvb.\n  folly::ScopedEventBaseThread consumerAndProducerEvb{};\n\n  auto streamSourceHolder =\n      HTTPStreamSourceHolder::make(consumerAndProducerEvb.getEventBase());\n  CHECK(streamSourceHolder->get());\n\n  // space out producer work to introduce randomness in execution\n  consumerAndProducerEvb.add([streamSourceHolder]() {\n    // source should still exist\n    auto source = streamSourceHolder->get();\n    CHECK(streamSourceHolder->get());\n    source->headers(makeResponse(200), /*eom=*/false);\n  });\n\n  consumerAndProducerEvb.add([streamSourceHolder]() {\n    // source should still exist\n    auto source = streamSourceHolder->get();\n    CHECK(streamSourceHolder->get());\n    source->body(makeBuf(100), /*padding=*/0, /*eom=*/false);\n  });\n\n  consumerAndProducerEvb.add([streamSourceHolder]() {\n    // source should still exist\n    auto source = streamSourceHolder->get();\n    CHECK(streamSourceHolder->get());\n    source->datagram(makeBuf(100));\n  });\n\n  consumerAndProducerEvb.add([streamSourceHolder]() {\n    // source should still exist\n    auto source = streamSourceHolder->get();\n    CHECK(streamSourceHolder->get());\n    source->eom();\n  });\n\n  auto consumerTask = co_withExecutor(\n      consumerAndProducerEvb.getEventBase(),\n      folly::coro::co_invoke([streamSourceHolder]() -> folly::coro::Task<void> {\n        auto maybeHeaders =\n            co_await co_awaitTry(streamSourceHolder->get()->readHeaderEvent());\n        CHECK(!maybeHeaders.hasException());\n\n        // source should still exist\n        CHECK(streamSourceHolder->get());\n        auto maybeBody =\n            co_await co_awaitTry(streamSourceHolder->get()->readBodyEvent());\n        CHECK(!maybeBody.hasException());\n        CHECK(maybeBody->eventType == HTTPBodyEvent::EventType::BODY);\n        CHECK(maybeBody->event.body.chainLength() == 100 &&\n              maybeBody->eom == false);\n\n        // source should still exist\n        CHECK(streamSourceHolder->get());\n        auto maybeDatagram =\n            co_await co_awaitTry(streamSourceHolder->get()->readBodyEvent());\n        // this last event should be an abort so expect exception\n        CHECK(!maybeDatagram.hasException() &&\n              maybeDatagram->eventType == HTTPBodyEvent::EventType::DATAGRAM);\n\n        // source should still exist\n        CHECK(streamSourceHolder->get());\n        auto maybeEom =\n            co_await co_awaitTry(streamSourceHolder->get()->readBodyEvent());\n        // this last event should be an abort so expect exception\n        CHECK(!maybeEom.hasException() && maybeEom->eom);\n\n        // source should no longer exist due to ::sourceComplete callback –\n        // which should run destructor of the internal HTTPStreamSource within\n        // the producer evb\n        CHECK(streamSourceHolder->get() == nullptr);\n      }));\n\n  folly::coro::blockingWait(std::move(consumerTask));\n\n  // destructor for HTTPStreamSourceHolder invoked here outside of\n  // consumerAndProducerEvb, should be okay\n  co_return;\n}\n\nTEST(HTTPStreamSourceHolder, Simple) {\n  folly::EventBase evb;\n  auto source = HTTPStreamSourceHolder::make(&evb);\n  XCHECK(source->get());\n\n  HTTPSourceReader reader{source->get()};\n  co_withExecutor(&evb, reader.read()).start();\n\n  source->get()->headers(makePostRequest(1'000));\n  evb.loopOnce();\n\n  for (uint8_t idx = 0; idx < 10; idx++) {\n    source->get()->body(makeBuf(100), /*padding=*/0, /*eom=*/false);\n    evb.loopOnce();\n  }\n  source->get()->eom();\n  evb.loopOnce();\n\n  evb.loop();\n}\n\nTEST(HTTPStreamSourceHolder, DestructorTest) {\n  folly::EventBase evb;\n  auto source = HTTPStreamSourceHolder::make(&evb);\n  XCHECK(source->get());\n  source->get()->headers(makePostRequest(1'000));\n}\n\nTEST(HTTPStreamSourceHolder, EgressBackpressure) {\n  using folly::coro::blockingWait;\n  using FlowControlState = HTTPStreamSource::FlowControlState;\n\n  folly::EventBase evb;\n  auto source = HTTPStreamSourceHolder::make(\n      &evb, /*id=*/folly::none, /*egressBufferSize=*/2);\n  auto* pSource = source->get();\n  pSource->headers(makePostRequest(4));\n\n  // enqueue one byte => fc available => ::awaitEgressBuffer should not suspend\n  auto fc = pSource->body(makeBuf(1), /*padding=*/0, /*eom=*/false);\n  auto fut =\n      co_withExecutor(&evb, source->awaitEgressBuffer()).startInlineUnsafe();\n  EXPECT_EQ(fc, FlowControlState::OPEN);\n  EXPECT_TRUE(fut.isReady() && fut.hasValue());\n\n  // enqueue second byte => not enough fc credit => ::awaitEgressBuffer should\n  // suspend\n  fc = pSource->body(makeBuf(1), /*padding=*/0, /*eom=*/false);\n  fut = co_withExecutor(&evb, source->awaitEgressBuffer()).startInlineUnsafe();\n  EXPECT_EQ(fc, FlowControlState::CLOSED);\n  EXPECT_TRUE(!fut.isReady());\n\n  // consume body => unblocks fc\n  blockingWait(pSource->readHeaderEvent(), &evb);\n  blockingWait(pSource->readBodyEvent(), &evb);\n\n  // fut should now be available\n  EXPECT_TRUE(fut.isReady() && fut.hasValue());\n\n  // enqueuing another two bytes => fc blocked again\n  fc = pSource->body(makeBuf(2), /*padding=*/0, /*eom=*/false);\n  fut = co_withExecutor(&evb, source->awaitEgressBuffer()).startInlineUnsafe();\n  EXPECT_EQ(fc, FlowControlState::CLOSED);\n  EXPECT_TRUE(!fut.isReady());\n\n  blockingWait(pSource->readBodyEvent(), &evb);\n}\n\n} // namespace proxygen::coro::test\n// Test cases to write:\n//\n// body flow control error\n// body in sink mode\n// pushPromise sink mode\n// onEOF\n// readBodyEvent with no body events\n// error from waitForEvent(body)\n// enable sink mode with pending body\n// event timeout\n// release flow control\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPStreamSourceSinkTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPStreamSourceSink.h\"\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include \"proxygen/lib/http/session/test/HTTPSessionMocks.h\"\n\n#include <folly/MoveWrapper.h>\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Sleep.h>\n\nusing namespace proxygen::coro;\nusing namespace testing;\nusing namespace proxygen;\n\nnamespace proxygen::coro::test {\n\nCO_TEST_P_X(HTTPClientTests, BasicUpstream) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  MockHTTPHandler handler;\n  HTTPStreamSourceUpstreamSink serverSink(\n      &evb_, sess->acquireKeepAlive(), &handler);\n\n  handler.expectHeaders();\n  handler.expectEOM();\n  handler.expectDetachTransaction();\n  auto req = getGetRequest();\n  serverSink.sendHeadersWithEOM(req);\n  co_await serverSink.transact(sess, *sess->reserveRequest());\n}\n\nCO_TEST_P_X(HTTPClientTests, UpstreamPauseBeforeEOM) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  MockHTTPHandler handler;\n  HTTPStreamSourceUpstreamSink serverSink(\n      &evb_, sess->acquireKeepAlive(), &handler);\n\n  handler.expectHeaders([&serverSink] { serverSink.pauseIngress(); });\n  auto req = getGetRequest();\n  serverSink.sendHeadersWithEOM(req);\n  co_withExecutor(\n      co_await folly::coro::co_current_executor,\n      folly::coro::co_invoke(\n          [&serverSink, &handler]() -> folly::coro::Task<void> {\n            co_await folly::coro::sleep(std::chrono::milliseconds(100));\n            handler.expectEOM();\n            handler.expectDetachTransaction();\n            serverSink.resumeIngress();\n          }))\n      .start();\n  co_await serverSink.transact(sess, *sess->reserveRequest());\n}\n\nCO_TEST_P_X(HTTPClientTests, FailSendUpstreamReq) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  // get request before drain\n  auto reservation = sess->reserveRequest();\n  XCHECK(reservation.hasValue());\n  // initiateDrain will cause next HTTPCoroSession::sendRequest to yield an\n  // exception\n  sess->initiateDrain();\n\n  // enqueue headers to be sent to upstream\n  auto* handler = new MockHTTPHandler();\n  HTTPStreamSourceUpstreamSink serverSink(\n      &evb_, sess->acquireKeepAlive(), handler);\n  auto req = getGetRequest();\n  serverSink.sendHeaders(req);\n\n  EXPECT_CALL(*handler, _onError(_)).Times(1);\n  EXPECT_CALL(*handler, _detachTransaction()).WillOnce([handler]() {\n    delete handler;\n  });\n\n  co_await serverSink.transact(sess, std::move(*reservation));\n}\n\nCO_TEST_P_X(HTTPClientTests, PostWithPause) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  MockHTTPHandler handler;\n  HTTPStreamSourceUpstreamSink serverSink(\n      &evb_, sess->acquireKeepAlive(), &handler);\n\n  auto length = 65535;\n  auto req = getPostRequest(length + 1);\n  // will pause\n  serverSink.sendHeaders(req);\n  serverSink.sendPadding(2);\n  handler.expectEgressPaused();\n  serverSink.sendBody(makeBuf(length));\n  handler.expectEgressResumed([&] {\n    serverSink.sendPadding(10101);\n    serverSink.sendBody(makeBuf(1));\n    serverSink.sendPadding(12345);\n    serverSink.sendEOM();\n  });\n\n  serverSink.pauseIngress();\n  evb_.runAfterDelay(\n      [&] {\n        serverSink.resumeIngress();\n        handler.expectHeaders();\n        EXPECT_CALL(handler, _onBody(_));\n        handler.expectEOM();\n        handler.expectDetachTransaction();\n      },\n      50);\n  co_await serverSink.transact(sess, *sess->reserveRequest());\n}\n\nCO_TEST_P_X(HTTPClientTests, SendAbortDuringPause) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  MockHTTPHandler handler;\n  HTTPStreamSourceUpstreamSink serverSink(\n      &evb_, sess->acquireKeepAlive(), &handler);\n  // send abort after 50ms\n  evb_.runAfterDelay([&] { serverSink.sendAbort(); }, 50);\n\n  handler.expectHeaders([&]() {\n    // pause ingress after rx'ing headers\n    serverSink.pauseIngress();\n  });\n\n  folly::coro::Baton waitForDetach;\n  handler.expectDetachTransaction([&]() { waitForDetach.post(); });\n\n  constexpr auto length = 65535;\n  auto req = getPostRequest(length);\n  serverSink.sendHeaders(req);\n  serverSink.sendBody(makeBuf(length));\n  co_await co_awaitTry(serverSink.transact(sess, *sess->reserveRequest()));\n  co_await waitForDetach;\n}\n\nCO_TEST_P_X(HTTPClientTests, DetachAndAbortIfIncomplete) {\n  HTTPCoroConnector::ConnectionParams connParams;\n  HTTPCoroConnector::SessionParams sessParams;\n  auto sess = co_await HTTPCoroConnector::connect(\n      &evb_, serverAddress_, std::chrono::seconds(1), connParams, sessParams);\n\n  StrictMock<MockHTTPHandler> handler;\n  auto serverSink = std::make_unique<HTTPStreamSourceUpstreamSink>(\n      &evb_, sess->acquireKeepAlive(), &handler);\n\n  folly::coro::Baton waitUntilHeaders;\n  handler.expectHeaders([&]() {\n    // pause ingress after rx'ing headers\n    serverSink->pauseIngress();\n\n    // detachAndAbort; no more callbacks expected\n    auto sinkPtr = serverSink.get();\n    sinkPtr->detachAndAbortIfIncomplete(std::move(serverSink));\n\n    waitUntilHeaders.post();\n  });\n\n  constexpr auto length = 65535;\n  auto req = getPostRequest(length);\n  serverSink->sendHeaders(req);\n\n  EXPECT_CALL(handler, _onEgressPaused());\n  serverSink->sendBody(makeBuf(length));\n\n  EXPECT_CALL(handler, _onEgressResumed());\n  co_await co_awaitTry(serverSink->transact(sess, *sess->reserveRequest()));\n\n  co_await waitUntilHeaders;\n}\n\nINSTANTIATE_TEST_SUITE_P(HTTPClientTests,\n                         HTTPClientTests,\n                         Values(TransportType::TCP),\n                         transportTypeToTestName);\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPTestSources.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/HTTPMessage.h\"\n#include \"proxygen/lib/http/codec/test/TestUtils.h\"\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceHolder.h\"\n#include <folly/coro/Sleep.h>\n#include <folly/io/IOBufQueue.h>\n\nnamespace proxygen::coro::test {\n\n// ErrorSource is a source that throws an exception in the middle of the\n// 'readBodyEvent' routine\nstruct ErrorSource : public HTTPSourceFilter {\n  explicit ErrorSource(std::string body,\n                       bool client,\n                       uint32_t bytesTillTheError,\n                       uint32_t limit = 10)\n      : HTTPSourceFilter(\n            HTTPFixedSource::makeFixedResponse(200, std::move(body))),\n        client_(client),\n        bytesTillTheError_(bytesTillTheError),\n        limit_(limit) {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    std::unique_ptr<HTTPHeaderEvent> eventPtr;\n    if (client_) {\n      auto req = getPostRequest(bytesTillTheError_ + 1);\n      req.setURL<std::string>(folly::to<std::string>(\"/incompleteBody\"));\n      eventPtr = std::make_unique<HTTPHeaderEvent>(\n          std::make_unique<HTTPMessage>(std::move(req)), false);\n    } else {\n      eventPtr =\n          std::make_unique<HTTPHeaderEvent>(co_await readHeaderEventImpl(true));\n    }\n\n    auto event = std::move(*eventPtr);\n    auto guard = folly::makeGuard(lifetime(event));\n    co_return event;\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    auto bodyEvent = co_await readBodyEventImpl(\n        std::min(static_cast<uint32_t>(limit_), max));\n    if (bodyEvent.eventType == HTTPBodyEvent::EventType::BODY) {\n      bytesTillTheError_ -= bodyEvent.event.body.chainLength();\n    }\n\n    if (bodyEvent.eom || bytesTillTheError_ <= 0) {\n      stopReading();\n      co_yield folly::coro::co_error(proxygen::coro::HTTPError(\n          HTTPErrorCode::INTERNAL_ERROR, \"Failed to read body\"));\n    }\n\n    auto guard = folly::makeGuard(lifetime(bodyEvent));\n    co_return bodyEvent;\n  }\n\n  bool client_;\n  int64_t bytesTillTheError_;\n  uint32_t limit_;\n};\n\n/* OnEOMSource is a source filter that runs a user callback before EOM */\nclass OnEOMSource : public HTTPSourceFilter {\n public:\n  using CallbackReturn = folly::coro::Task<folly::Optional<HTTPError>>;\n\n  OnEOMSource(HTTPSource *source, std::function<CallbackReturn()> eomCallback)\n      : HTTPSourceFilter(source), eomCallback_(eomCallback) {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    auto bodyEvent = co_await co_awaitTry(readBodyEventImpl(max));\n    auto guard = folly::makeGuard(lifetime(bodyEvent));\n    if (bodyEvent.hasException()) {\n      co_yield folly::coro::co_error(getHTTPError(bodyEvent));\n    }\n    if (bodyEvent->eom) {\n      error_ = co_await eomCallback_();\n      if (error_) {\n        // doesn't affect lifetime\n        co_yield folly::coro::co_error(*error_);\n      }\n    }\n\n    co_return bodyEvent;\n  }\n\n  folly::Optional<HTTPError> error_;\n  std::function<CallbackReturn()> eomCallback_;\n};\n\n// An HTTP source that will hang in readBodyEvent\nclass TimeoutSource : public HTTPSource {\n public:\n  explicit TimeoutSource(std::unique_ptr<HTTPMessage> msg,\n                         bool timeoutHeaders = false,\n                         bool errorOnCancel = true,\n                         bool heapAllocated = true)\n      : msg_(std::move(msg)),\n        timeoutHeaders_(timeoutHeaders),\n        errorOnCancel_(errorOnCancel) {\n    if (heapAllocated) {\n      setHeapAllocated();\n    }\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    if (timeoutHeaders_) {\n      co_await folly::coro::sleepReturnEarlyOnCancel(std::chrono::seconds(60));\n      if (errorOnCancel_) {\n        if (heapAllocated_) {\n          delete this;\n        }\n        co_yield folly::coro::co_error(\n            HTTPError(HTTPErrorCode::READ_TIMEOUT, \"\"));\n      }\n    }\n    co_return HTTPHeaderEvent(std::move(msg_), false);\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    SCOPE_EXIT {\n      if (heapAllocated_) {\n        delete this;\n      }\n    };\n    if (!timeoutHeaders_) {\n      co_await folly::coro::sleepReturnEarlyOnCancel(std::chrono::seconds(60));\n      if (errorOnCancel_) {\n        co_yield folly::coro::co_error(\n            HTTPError(HTTPErrorCode::READ_TIMEOUT, \"\"));\n      }\n    }\n\n    co_return HTTPBodyEvent(std::unique_ptr<folly::IOBuf>(nullptr), true);\n  }\n\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> = folly::none) override {\n    if (heapAllocated_) {\n      delete this;\n    }\n  }\n\n  std::unique_ptr<HTTPMessage> msg_;\n  bool timeoutHeaders_{false};\n  bool errorOnCancel_{true};\n};\n\nclass EchoBodySource : public HTTPSourceFilter {\n public:\n  explicit EchoBodySource(HTTPSourceHolder requestSource,\n                          uint16_t statusCode,\n                          bool eom,\n                          std::map<std::string, std::string> headers,\n                          bool abortBody)\n      : HTTPSourceFilter(requestSource.release()),\n        eom_(eom),\n        abortBody_(abortBody) {\n    setHeapAllocated();\n    response_ = std::make_unique<HTTPMessage>();\n    response_->setHTTPVersion(1, 1);\n    response_->setStatusCode(statusCode);\n    for (auto nv : headers) {\n      response_->getHeaders().add(nv.first, nv.second);\n    }\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    HTTPHeaderEvent event(std::move(response_), eom_);\n    if (eom_) {\n      delete this;\n    }\n    co_return event;\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    if (abortBody_) {\n      delete this;\n      co_yield folly::coro::co_error(\n          HTTPError(HTTPErrorCode::CANCEL, \"cancel\"));\n    }\n    auto bodyEvent = co_await co_awaitTry(readBodyEventImpl(max));\n    if (!*this) {\n      delete this;\n    }\n    co_return bodyEvent;\n  }\n\n  void setReadTimeout(std::chrono::milliseconds) override {\n  }\n\n  std::unique_ptr<HTTPMessage> response_;\n  bool eom_{false};\n  bool abortBody_{false};\n};\n\nclass ByteEventFilter : public HTTPSourceFilter {\n public:\n  ByteEventFilter(\n      uint8_t headerEvents,\n      uint8_t bodyEvents,\n      HTTPByteEventCallbackPtr callback,\n      uint32_t maxBytesPerRead = std::numeric_limits<uint32_t>::max())\n      : headerEvents_(headerEvents),\n        bodyEvents_(bodyEvents),\n        callback_(std::move(callback)),\n        max_(maxBytesPerRead) {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    auto ev = co_await HTTPSourceFilter::readHeaderEventImpl();\n    for (auto eventType : HTTPByteEvent::kByteEventTypes) {\n      if (headerEvents_ & folly::to_underlying(eventType)) {\n        HTTPByteEventRegistration reg;\n        reg.events = folly::to_underlying(eventType);\n        reg.callback = callback_;\n        ev.byteEventRegistrations.emplace_back(std::move(reg));\n      }\n    }\n    lifetime(ev)();\n    co_return ev;\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override {\n    auto ev = co_await HTTPSourceFilter::readBodyEventImpl(std::min(max_, max));\n    for (auto eventType : HTTPByteEvent::kByteEventTypes) {\n      if (bodyEvents_ & folly::to_underlying(eventType)) {\n        XLOG(DBG4) << \"registering for event t=\"\n                   << folly::to_underlying(eventType);\n        HTTPByteEventRegistration reg;\n        reg.events = folly::to_underlying(eventType);\n\n        reg.callback = callback_;\n        ev.byteEventRegistrations.emplace_back(std::move(reg));\n      }\n    }\n    lifetime(ev)();\n    co_return ev;\n  }\n\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> = folly::none) override {\n    if (heapAllocated_) {\n      delete this;\n    }\n  }\n\n private:\n  uint8_t headerEvents_;\n  uint8_t bodyEvents_;\n  HTTPByteEventCallbackPtr callback_;\n  uint32_t max_;\n};\n\nclass YieldExceptionSource : public HTTPSource {\n public:\n  enum Stage : uint8_t { HeaderEvent, BodyEvent };\n  enum MessageType : uint8_t { Request, Response };\n  using co_error = folly::coro::co_error;\n\n  YieldExceptionSource(Stage stage, MessageType type)\n      : stage_(stage), type_(type) {\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override {\n    if (stage_ == HeaderEvent) {\n      co_yield co_error(HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"exception\"));\n    }\n    co_return HTTPHeaderEvent(type_ == Request ? proxygen::makeGetRequest()\n                                               : proxygen::makeResponse(200),\n                              /*inEOM=*/false);\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    XCHECK(stage_ == BodyEvent);\n    [[maybe_unused]] auto guard = folly::makeGuard([this] {\n      if (heapAllocated_) {\n        delete this;\n      }\n    });\n    co_yield co_error(HTTPError(HTTPErrorCode::INTERNAL_ERROR, \"exception\"));\n  }\n\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> = folly::none) override {\n  }\n\n private:\n  const Stage stage_;\n  const MessageType type_;\n};\n\n/**\n * Propagates a body in a fixed size chunks. Useful if you want to test\n * aborting reading the body or any behavior requiring multiple readBodyEvent.\n */\nclass ChunkedBodySource : public HTTPSourceFilter {\n public:\n  ChunkedBodySource(std::string body, uint32_t chunkSize)\n      : HTTPSourceFilter(\n            HTTPFixedSource::makeFixedResponse(200, std::move(body))),\n        chunkSize_(chunkSize) {\n    setHeapAllocated();\n  }\n\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(\n      uint32_t max = std::numeric_limits<uint32_t>::max()) override {\n    auto bodyEvent = co_await readBodyEventImpl(chunkSize_);\n    auto guard = folly::makeGuard(lifetime(bodyEvent));\n    co_return bodyEvent;\n  }\n\n private:\n  uint32_t chunkSize_;\n};\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPTransactionAdaptorSourceTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/coro/DetachOnCancel.h>\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPSourceReader.h\"\n#include \"proxygen/lib/http/coro/HTTPTransactionAdaptorSource.h\"\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n\nusing namespace proxygen::coro;\nusing namespace testing;\nusing namespace proxygen;\n\nnamespace proxygen::coro::test {\n\nnamespace {\nstatic std::chrono::milliseconds kTestDefaultTimeout =\n    std::chrono::milliseconds(1000);\nstatic folly::SocketAddress kTestAddress(\"127.0.0.1\", 80);\nstatic wangle::TransportInfo kTestTransportInfo;\n\n} // namespace\n\nclass HTTPTransactionAdaptorSourceTests : public HTTPClientTests {\n protected:\n  void SetUp() override {\n    HTTPClientTests::SetUp();\n    mockTxn_ = createMockHTTPTransaction();\n    adaptor_ = HTTPTransactionAdaptorSource::create(&evb_);\n    getHandler()->setTransaction(mockTxn_.get());\n\n    ON_CALL(ctx_, getPeerAddress()).WillByDefault(ReturnRef(kTestAddress));\n    ON_CALL(ctx_, getSetupTransportInfo())\n        .WillByDefault(ReturnRef(kTestTransportInfo));\n  }\n\n  std::unique_ptr<::testing::StrictMock<MockHTTPTransaction>>\n  createMockHTTPTransaction(\n      TransportDirection direction = TransportDirection::DOWNSTREAM) {\n    auto& egressQueue = direction == TransportDirection::DOWNSTREAM\n                            ? downstreamEgressQueue_\n                            : upstreamEgressQueue_;\n\n    auto mock = std::make_unique<::testing::StrictMock<MockHTTPTransaction>>(\n        direction,\n        /*streamId=*/1,\n        /*seqNo=*/0,\n        egressQueue,\n        /*timer=*/wheelTimer_.get(),\n        /*transactionTimeout=*/std::chrono::milliseconds(60000),\n        /*stats=*/nullptr,\n        /*useFlowControl=*/false,\n        /*receiveInitialWindowSize=*/0,\n        /*sendInitialWindowSize=*/0,\n        http2::DefaultPriority,\n        folly::none);\n\n    ON_CALL(*mock, pauseIngress())\n        .WillByDefault(::testing::Invoke([mockPtr = mock.get()] {\n          mockPtr->HTTPTransaction::pauseIngress();\n        }));\n    ON_CALL(*mock, resumeIngress())\n        .WillByDefault(::testing::Invoke([mockPtr = mock.get()] {\n          mockPtr->HTTPTransaction::resumeIngress();\n        }));\n    EXPECT_CALL(*mock, resumeIngress()).Times(AnyNumber());\n\n    return mock;\n  }\n\n  HTTPTransactionHandler* getHandler() {\n    return static_cast<HTTPTransactionHandler*>(adaptor_);\n  }\n\n  struct SourceExpectations {\n    std::optional<std::string> maybeMethod;\n    std::optional<std::string> maybeBody;\n    bool eom{true};\n    bool error{false};\n  };\n\n  folly::coro::Task<void> consumeAndValidateSource(\n\n      HTTPSourceHolder&& source, const SourceExpectations& expectations) {\n    bool headersComplete = false;\n    bool bodyReceived = false;\n    bool eomComplete = false;\n    bool error = false;\n\n    HTTPSourceReader reader(std::move(source));\n    reader\n        .onHeaders([&](std::unique_ptr<HTTPMessage> msg, auto, bool eom) {\n          headersComplete = true;\n          if (msg && expectations.maybeMethod.has_value()) {\n            EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"x-method\"),\n                      expectations.maybeMethod.value());\n          }\n          eomComplete = eom;\n          return HTTPSourceReader::Continue;\n        })\n        .onBody([&](BufQueue body, bool eom) {\n          if (expectations.maybeBody.has_value()) {\n            EXPECT_EQ(body.move()->moveToFbString().toStdString(),\n                      expectations.maybeBody.value());\n          }\n          bodyReceived = true;\n          eomComplete = eom;\n          return HTTPSourceReader::Continue;\n        })\n        .onError([&](auto, auto) { error = true; });\n\n    co_await reader.read();\n\n    if (expectations.maybeMethod.has_value()) {\n      CO_ASSERT_TRUE(headersComplete);\n    }\n    if (expectations.maybeBody.has_value()) {\n      CO_ASSERT_TRUE(bodyReceived);\n    }\n    CO_ASSERT_EQ(eomComplete, expectations.eom);\n  }\n\n  void setTransactionExpectations(const SourceExpectations& expectations) {\n    if (expectations.maybeMethod.has_value()) {\n      EXPECT_CALL(*mockTxn_, sendHeadersWithOptionalEOM)\n          .Times(1)\n          .WillRepeatedly([expectations](const HTTPMessage& msg, auto) {\n            EXPECT_EQ(msg.getHeaders().getSingleOrEmpty(\"x-method\"),\n                      expectations.maybeMethod.value());\n          });\n    } else {\n      EXPECT_CALL(*mockTxn_, sendHeaders).Times(0);\n    }\n\n    if (expectations.maybeBody.has_value()) {\n      EXPECT_CALL(*mockTxn_, sendBody)\n          .Times(1)\n          .WillOnce([expectations](auto body) {\n            EXPECT_EQ(body->moveToFbString().toStdString(),\n                      expectations.maybeBody.value());\n          });\n    } else {\n      EXPECT_CALL(*mockTxn_, sendBody).Times(0);\n    }\n\n    if (expectations.eom) {\n      EXPECT_CALL(*mockTxn_, sendEOM).Times(1);\n    } else {\n      EXPECT_CALL(*mockTxn_, sendEOM).Times(0);\n    }\n\n    if (expectations.error) {\n      EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(1);\n    } else {\n      EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(0);\n    }\n  }\n\n  folly::HHWheelTimer::UniquePtr wheelTimer_;\n  HTTP2PriorityQueue downstreamEgressQueue_;\n  HTTP2PriorityQueue upstreamEgressQueue_;\n  std::unique_ptr<::testing::StrictMock<MockHTTPTransaction>> mockTxn_;\n  MockHTTPSessionContext ctx_;\n  HTTPTransactionAdaptorSource* adaptor_;\n};\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressFlow) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n  co_await consumeAndValidateSource(std::move(response),\n                                    SourceExpectations{\n                                        .maybeMethod = \"GET\",\n                                        .maybeBody = \"hello world\",\n                                    });\n  getHandler()->detachTransaction();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressBackPressure) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n\n  EXPECT_CALL(*mockTxn_, pauseIngress()).Times(1);\n  std::string body(70000, 'a');\n  getHandler()->onBody(folly::IOBuf::copyBuffer(body));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n  EXPECT_CALL(*mockTxn_, resumeIngress()).Times(1);\n  co_await consumeAndValidateSource(std::move(response),\n                                    SourceExpectations{\n                                        .maybeMethod = \"GET\",\n                                        .maybeBody = std::move(body),\n                                    });\n  getHandler()->detachTransaction();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressError) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(1);\n  getHandler()->onError(\n      HTTPException(HTTPException::Direction::INGRESS, \"oops\"));\n\n  auto response = co_await co_awaitTry(folly::coro::detachOnCancel(\n      handler->handleRequest(&evb_, ctx_.acquireKeepAlive(), ingressSource)));\n  EXPECT_TRUE(response.hasException());\n  getHandler()->detachTransaction();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressEarlyTermination) {\n  auto handler = std::make_shared<TestHandler>();\n  auto ingressSource = adaptor_->getIngressSource();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto readCoro =\n      folly::coro::timeout(folly::coro::detachOnCancel(handler->handleRequest(\n                               &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n                           kTestDefaultTimeout);\n\n  getHandler()->detachTransaction();\n  co_await std::move(readCoro);\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, EgressFlow) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n\n  setTransactionExpectations(SourceExpectations{\n      .maybeMethod = \"GET\",\n      .maybeBody = \"hello world\",\n  });\n\n  ON_CALL(*mockTxn_, sendEOM).WillByDefault([&]() {\n    getHandler()->detachTransaction();\n  });\n  adaptor_->setEgressSource(std::move(response));\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, EgressBackPressure) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n\n  getHandler()->onEgressPaused();\n\n  setTransactionExpectations(SourceExpectations{.eom = false});\n  adaptor_->setEgressSource(std::move(response));\n\n  setTransactionExpectations(SourceExpectations{\n      .maybeMethod = \"GET\",\n      .maybeBody = \"hello world\",\n  });\n\n  ON_CALL(*mockTxn_, sendEOM).WillByDefault([&]() {\n    getHandler()->detachTransaction();\n  });\n  getHandler()->onEgressResumed();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, UpstreamEgressErrors) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"test/bodyError_11\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n\n  setTransactionExpectations(\n      SourceExpectations{.maybeMethod = \"\" /* response */,\n                         .maybeBody = \"super long\",\n                         .eom = false,\n                         .error = true});\n  adaptor_->setEgressSource(std::move(response));\n\n  ON_CALL(*mockTxn_, sendAbort).WillByDefault([&]() {\n    getHandler()->detachTransaction();\n  });\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests,\n            DownstreamEarlyEgressTermination) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n\n  adaptor_->setEgressSource(std::move(response));\n\n  // Terminate before egress loop is complete.\n  getHandler()->detachTransaction();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, DownstreamErrors) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  getHandler()->onEOM();\n\n  auto response = co_await folly::coro::timeout(\n      folly::coro::detachOnCancel(handler->handleRequest(\n          &evb_, ctx_.acquireKeepAlive(), ingressSource)),\n      kTestDefaultTimeout);\n\n  EXPECT_CALL(*mockTxn_, sendHeadersWithOptionalEOM)\n      .Times(1)\n      .WillRepeatedly([&](const HTTPMessage& /* msg */, auto) {\n        getHandler()->onError(\n            HTTPException(HTTPException::Direction::EGRESS, \"oops\"));\n        // Delay the detach to the next loop.\n        evb_.runInEventBaseThreadAlwaysEnqueue(\n            [&]() { getHandler()->detachTransaction(); });\n      });\n\n  EXPECT_CALL(*mockTxn_, sendBody).Times(0);\n  EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(1);\n\n  adaptor_->setEgressSource(std::move(response));\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressNoErrorAbortNoCrash) {\n  auto ingressSource = adaptor_->getIngressSource();\n  auto handler = std::make_shared<TestHandler>();\n\n  auto msg = std::make_unique<HTTPMessage>();\n  msg->setMethod(HTTPMethod::GET);\n  msg->setURL(\"https://www.facebook.com/\");\n\n  getHandler()->onHeadersComplete(std::move(msg));\n  getHandler()->onBody(folly::IOBuf::copyBuffer(\"hello world\"));\n  // Do not send EOM to simulate incomplete ingress\n\n  // Simulate an inbound RST_STREAM(NO_ERROR) via codec status on the exception\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   \"rst_stream_no_error\");\n  ex.setCodecStatusCode(ErrorCode::NO_ERROR);\n  EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(1);\n  getHandler()->onError(ex);\n\n  // The adaptor will abort the ingress source; consuming should surface an\n  // error\n  auto responseTry = co_await co_awaitTry(folly::coro::detachOnCancel(\n      handler->handleRequest(&evb_, ctx_.acquireKeepAlive(), ingressSource)));\n  EXPECT_TRUE(responseTry.hasException());\n\n  getHandler()->detachTransaction();\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests,\n            WebSocketPausesIngressAndResumesOnEgressHeaders) {\n  auto ingressSource = adaptor_->getIngressSource();\n\n  auto req = std::make_unique<HTTPMessage>();\n  req->setURL(\"https://www.facebook.com/\");\n  req->setIngressWebsocketUpgrade();\n\n  // pauseIngress() should be called on websocket upgrade request.\n  EXPECT_CALL(*mockTxn_, pauseIngress()).Times(1);\n  getHandler()->onHeadersComplete(std::move(req));\n\n  auto headerEvent =\n      co_await co_nothrow(HTTPSourceHolder(ingressSource).readHeaderEvent());\n  EXPECT_FALSE(headerEvent.eom);\n  EXPECT_TRUE(headerEvent.headers->isIngressWebsocketUpgrade());\n\n  auto resp = std::make_unique<HTTPMessage>();\n  auto egressSource = HTTPFixedSource::makeFixedSource(std::move(resp));\n\n  // When the response comes back, resumeIngress() should be called.\n  EXPECT_CALL(*mockTxn_, sendHeadersWithOptionalEOM).Times(1);\n  EXPECT_CALL(*mockTxn_, resumeIngress()).Times(1);\n\n  ON_CALL(*mockTxn_, resumeIngress()).WillByDefault([&] {\n    mockTxn_->HTTPTransaction::resumeIngress();\n    getHandler()->detachTransaction();\n  });\n  adaptor_->setEgressSource(egressSource);\n}\n\nCO_TEST_P_X(HTTPTransactionAdaptorSourceTests,\n            WebSocketWithBodyDoesNotResumeIngressUntilWindowOpens) {\n  auto ingressSource = adaptor_->getIngressSource();\n\n  auto req = std::make_unique<HTTPMessage>();\n  req->setURL(\"https://www.facebook.com/\");\n  req->setIngressWebsocketUpgrade();\n\n  // pauseIngress() should be called twice:\n  // 1. On websocket upgrade request\n  // 2. When body fills the flow control window\n  EXPECT_CALL(*mockTxn_, pauseIngress()).Times(2);\n  getHandler()->onHeadersComplete(std::move(req));\n\n  // Send a large body to fill the buffer which will close the window.\n  std::string body(70000, 'a');\n  getHandler()->onBody(folly::IOBuf::copyBuffer(body));\n  getHandler()->onEOM();\n\n  HTTPSourceHolder sourceHolder(ingressSource);\n  auto headerEvent = co_await co_nothrow(sourceHolder.readHeaderEvent());\n  EXPECT_FALSE(headerEvent.eom);\n  EXPECT_TRUE(headerEvent.headers->isIngressWebsocketUpgrade());\n\n  // resumeIngress() should only be called once, when the window opens again.\n  EXPECT_CALL(*mockTxn_, resumeIngress()).Times(1);\n  auto resp = std::make_unique<HTTPMessage>();\n  auto egressSource = HTTPFixedSource::makeFixedSource(std::move(resp));\n\n  // Used to track where resumeIngress() is called.\n  bool resumeIngressCalled = false;\n  folly::coro::Baton headersSentBaton;\n\n  ON_CALL(*mockTxn_, resumeIngress()).WillByDefault([&] {\n    resumeIngressCalled = true;\n    mockTxn_->HTTPTransaction::resumeIngress();\n  });\n  EXPECT_CALL(*mockTxn_, sendHeadersWithOptionalEOM).Times(1).WillOnce([&] {\n    headersSentBaton.post();\n  });\n\n  adaptor_->setEgressSource(egressSource);\n\n  // Wait for egress headers to be sent.\n  co_await headersSentBaton;\n\n  EXPECT_FALSE(resumeIngressCalled)\n      << \"resumeIngress should not be called on egress headers when window is \"\n         \"closed\";\n\n  // Now read the body from ingress source. This drains the buffer and\n  // triggers windowOpen callback, which should call resumeIngress().\n  auto bodyEvent = co_await co_nothrow(sourceHolder.readBodyEvent());\n\n  // Verify resumeIngress WAS called after body was read (window opened)\n  EXPECT_TRUE(resumeIngressCalled)\n      << \"resumeIngress should be called when window opens after body drain\";\n\n  getHandler()->detachTransaction();\n}\n\nINSTANTIATE_TEST_SUITE_P(HTTPTransactionAdaptorSourceTests,\n                         HTTPTransactionAdaptorSourceTests,\n                         Values(TransportType::TCP),\n                         transportTypeToTestName);\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HTTPUpstreamCoroSessionTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/test/HTTPCoroSessionTests.h\"\n#include \"proxygen/lib/http/coro/test/HTTPTestSources.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include <folly/logging/xlog.h>\n\n#include <folly/ExceptionWrapper.h>\n#include <folly/coro/Timeout.h>\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n\nusing namespace proxygen;\nusing namespace testing;\nusing TransportErrorCode = folly::coro::TransportIf::ErrorCode;\n\nnamespace proxygen::coro::test {\n\nclass HTTPUpstreamSessionTest : public HTTPCoroSessionTest {\n public:\n  HTTPUpstreamSessionTest()\n      : HTTPCoroSessionTest(TransportDirection::UPSTREAM),\n        serverCodec_(peerCodec_.get()) {\n  }\n\n  void SetUp() override {\n    HTTPCoroSessionTest::setUp();\n  }\n\n  // TearDown defined in parent\n\n  folly::coro::Task<std::unique_ptr<HTTPFixedSource>> expectResponse(\n      HTTPSourceHolder responseSource,\n      uint16_t statusCode,\n      uint32_t contentLength = 0,\n      bool eom = true) {\n    auto res = co_await co_awaitTry(responseSource.readHeaderEvent());\n    EXPECT_FALSE(res.hasException());\n    if (res.hasException()) {\n      co_return nullptr;\n    }\n    auto streamID = *responseSource.getStreamID();\n    EXPECT_EQ(res->headers->getSeqNo(),\n              HTTPCodec::streamIDToSeqNo(GetParam().codecProtocol, streamID));\n    EXPECT_EQ(res->headers->getStatusCode(), statusCode);\n    EXPECT_TRUE(res->isFinal());\n    auto response = std::make_unique<HTTPFixedSource>(std::move(res->headers));\n    if (res->eom) {\n      EXPECT_EQ(contentLength, 0);\n      co_return response;\n    }\n    bool readEom = false;\n    do {\n      auto bodyEvent =\n          co_await co_awaitTry(readBodyEventNoSuspend(responseSource));\n      EXPECT_FALSE(bodyEvent.hasException());\n      if (bodyEvent.hasException()) {\n        co_return nullptr;\n      }\n      switch (bodyEvent->eventType) {\n        case HTTPBodyEvent::PUSH_PROMISE:\n          co_withExecutor(&evb_,\n                          onPushPromise(std::move(bodyEvent->event.push)))\n              .start();\n          break;\n        case HTTPBodyEvent::BODY: {\n          if (!bodyEvent->event.body.empty()) {\n            EXPECT_GT(contentLength, 0);\n            auto length = bodyEvent->event.body.chainLength();\n            XCHECK_GE(contentLength, length);\n            contentLength -= length;\n            response->body_.append(bodyEvent->event.body.move());\n          }\n          break;\n        }\n        default:\n          // unhandled for now\n          break;\n      }\n      readEom = bodyEvent->eom;\n    } while (!readEom);\n    EXPECT_EQ(contentLength, 0);\n    co_return response;\n  }\n\n  void serializeResponse(HTTPCodec::StreamID id,\n                         uint16_t statusCode,\n                         std::unique_ptr<folly::IOBuf> body = nullptr,\n                         bool eom = true,\n                         bool eof = false) {\n    serializeResponse(\n        id, makeResponse(statusCode, eom), std::move(body), eom, eof);\n  }\n\n  void serializeResponse(HTTPCodec::StreamID id,\n                         HTTPMessage response,\n                         std::unique_ptr<folly::IOBuf> body = nullptr,\n                         bool eom = true,\n                         bool eof = false) {\n    serializeResponseHeader(id, std::move(response), eom && !body);\n    if (body) {\n      serverCodec_->generateBody(\n          writeBuf_, id, std::move(body), HTTPCodec::NoPadding, eom);\n    }\n    transport_->addReadEvent(id, writeBuf_.move(), (eom && isHQ()) || eof);\n  }\n\n  HTTPMessage makeResponse(uint16_t statusCode, bool eom = false) {\n    HTTPMessage resp;\n    resp.setHTTPVersion(1, 1);\n    resp.setStatusCode(statusCode);\n    if (GetParam().codecProtocol == CodecProtocol::HTTP_1_1) {\n      // Gross, but H1 can't serialize a response without receiving a request\n      auto fake =\n          folly::IOBuf::copyBuffer(std::string(\"GET / HTTP/1.1\\r\\n\\r\\n\"));\n      NiceMock<MockHTTPCodecCallback> callbacks;\n      serverCodec_->setCallback(&callbacks);\n      serverCodec_->onIngress(*fake);\n      if (!eom) {\n        resp.setIsChunked(true);\n        resp.getHeaders().set(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n      }\n    }\n    return resp;\n  }\n\n  void serializeResponseHeader(HTTPCodec::StreamID id,\n                               uint16_t statusCode,\n                               bool eom = true) {\n    serializeResponseHeader(id, makeResponse(statusCode, eom), eom);\n  }\n\n  void serializeResponseHeader(HTTPCodec::StreamID id,\n                               HTTPMessage resp,\n                               bool eom = true,\n                               bool flushQPACK = true) {\n    if (isHQ()) {\n      multiCodec_->addCodec(id);\n    }\n    serverCodec_->generateHeader(writeBuf_, id, resp, eom);\n    if (flushQPACK) {\n      flushQPACKEncoder();\n    }\n  }\n\n  folly::coro::Task<void> onPushPromise(HTTPPushEvent pushEvent) {\n    if (pushPromiseHandler_) {\n      co_await pushPromiseHandler_(std::move(pushEvent));\n    } else {\n      auto &it = pushes_.emplace_back(std::move(pushEvent), nullptr);\n      it.second =\n          co_await expectResponse(it.first.movePushSource(), 200, 100, true);\n    }\n  }\n\n  /* sends a new push promise for \"GET /push\" on given streamID, optionally\n   * adding streamID's EOM. Returns a tuple of [PushStreamId, PushId].\n   */\n  std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID> serializePushPromise(\n      HTTPCodec::StreamID streamID,\n      bool eom,\n      bool flush = true,\n      folly::IOBufQueue *pushStreamBuf = nullptr) {\n    HTTPCodec::StreamID pushID;\n    HTTPCodec::StreamID pushStreamID;\n    if (isHQ()) {\n      pushStreamID = muxTransport_->nextUnidirectionalStreamId_;\n      muxTransport_->nextUnidirectionalStreamId_ += 4;\n      pushID = multiCodec_->nextPushID();\n      auto streamType = uint64_t(hq::UnidirectionalStreamType::PUSH);\n      folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n      bool flushPushStream = false;\n      if (!pushStreamBuf) {\n        pushStreamBuf = &writeBuf;\n        flushPushStream = true;\n      }\n      hq::writeStreamPreface(*pushStreamBuf, streamType);\n      hq::writeStreamPreface(*pushStreamBuf, pushID);\n      if (flushPushStream) {\n        transport_->addReadEvent(pushStreamID, pushStreamBuf->move(), false);\n      }\n    } else {\n      pushID = pushStreamID = serverCodec_->createStream();\n    }\n    HTTPHeaderSize size;\n    HTTPMessage promise = getGetRequest(\"/push\");\n    serverCodec_->generatePushPromise(\n        writeBuf_, pushID, promise, streamID, false, &size);\n    flushQPACKEncoder();\n    if (eom) {\n      serverCodec_->generateEOM(writeBuf_, streamID);\n    }\n    if (flush) {\n      transport_->addReadEvent(streamID, writeBuf_.move(), isHQ() && eom);\n    }\n    return {pushStreamID, pushID};\n  }\n\n  /* Simple push test */\n  folly::coro::Task<HTTPCodec::StreamID> testPush(\n      bool expectIncomingStream = true, bool pushEOM = true, bool eof = true) {\n    // send a request for GET /\n    auto responseSource = co_await co_awaitTry(\n        session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n    XCHECK(!responseSource.hasException());\n    auto streamID = *responseSource->getStreamID();\n\n    // serialize a response with no EOM\n    serializeResponse(streamID, 200, makeBuf(100), false);\n\n    // serialize a PUSH_PROMISE and stream EOM\n    auto pushID = serializePushPromise(streamID, true).first;\n\n    // Expect the request's response\n    co_await expectResponse(std::move(*responseSource), 200, 100, true);\n    if (isHQ()) {\n      // TODO: HQ (properly) doesn't count the stream until the actual stream is\n      // opened.\n      co_await rescheduleN(1);\n    }\n    EXPECT_EQ(session_->numIncomingStreams(), expectIncomingStream ? 1 : 0);\n\n    // Now serialize the push response, with optional EOM\n    serializeResponse(pushID, 200, makeBuf(100), pushEOM);\n\n    // Optionally add EOF\n    if (eof) {\n      serverCodec_->generateGoaway(writeBuf_, streamID, ErrorCode::NO_ERROR);\n      transport_->addReadEvent(writeBuf_.move(), false);\n    }\n    co_return pushID;\n  }\n\n  void expectH1ConnectionReset() {\n    if (IS_H1()) {\n      EXPECT_TRUE(transportState_.closedWithReset);\n      expectedError_ = TransportErrorCode::NETWORK_ERROR;\n    }\n  }\n\n  HTTPCodec *serverCodec_{nullptr};\n  std::list<std::pair<HTTPPushEvent, std::unique_ptr<HTTPFixedSource>>> pushes_;\n  std::function<folly::coro::Task<void>(HTTPPushEvent)> pushPromiseHandler_;\n};\n\n// Use this test class for h1 only tests\nusing H1UpstreamSessionTest = HTTPUpstreamSessionTest;\n// Use this test class for h2 only tests\nusing H2UpstreamSessionTest = HTTPUpstreamSessionTest;\n// Use this test class for hq only tests\nusing HQUpstreamSessionTest = HTTPUpstreamSessionTest;\n// Use this test class for h2/hq only tests\nusing H2QUpstreamSessionTest = HTTPUpstreamSessionTest;\n// Use this test class for h1/h2 only tests\nusing H12UpstreamSessionTest = HTTPUpstreamSessionTest;\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, Simple) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  EXPECT_EQ(session_->numIncomingStreams(), 0);\n  EXPECT_EQ(session_->numOutgoingStreams(), 1);\n  if (!IS_H1()) {\n    EXPECT_EQ(session_->numTransactionsAvailable(), 9);\n  }\n  XCHECK(!responseSource.hasException());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  transport_->addReadEvent(nullptr, true);\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, SimplePost) {\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\", HTTPMethod::POST, makeBuf(100))));\n  XCHECK(!responseSource.hasException());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  transport_->addReadEvent(nullptr, true);\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, LargePost) {\n  co_await rescheduleN(1);\n  HTTPCodec::StreamID id;\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      new OnEOMSource(HTTPFixedSource::makeFixedRequest(\n                          \"/\", HTTPMethod::POST, makeBuf(70000)),\n                      [this, &id]() -> OnEOMSource::CallbackReturn {\n                        // Serialize the response when the EOM goes out\n                        // The response is large enough to trigger a\n                        // WINDOW_UPDATE\n                        serializeResponse(id, 200, makeBuf(40000), true);\n                        co_return folly::none;\n                      })));\n  XCHECK(!responseSource.hasException());\n  id = *responseSource->getStreamID();\n  evb_.runInLoop([this, id] {\n    // The POST will run out of flow control, give it some in the loop\n    windowUpdate(10000);\n    windowUpdate(id, 10000);\n  });\n\n  co_await expectResponse(std::move(*responseSource), 200, 40000, true);\n  co_await rescheduleN(1);\n\n  transport_->addReadEvent(nullptr, true);\n  if (isHQ()) {\n    EXPECT_GT(muxTransport_->socketDriver_.streams_[id].writeBuf.chainLength(),\n              70000);\n  }\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, Padding) {\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\", HTTPMethod::POST, makeBuf(100))));\n  XCHECK(!responseSource.hasException());\n  // @lint-ignore CLANGTIDY bugprone-unchecked-optional-access\n  const auto id = *responseSource->getStreamID();\n  HTTPMessage msg = makeResponse(200, /*eom=*/false);\n  serializeResponseHeader(id, std::move(msg), false);\n  serverCodec_->generatePadding(writeBuf_, id, 10);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(90), HTTPCodec::NoPadding, false);\n  serverCodec_->generatePadding(writeBuf_, id, 5);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(10), HTTPCodec::NoPadding, true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  transport_->addReadEvent(nullptr, true);\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, ReceiveFullResponsePriorToRequestEOM) {\n  /**\n   * This test basically creates a large post request and checks that we can\n   * receive a response before the egress request is completed.\n   *\n   * From RFC7540:\n   * A server can send a complete response prior to the client sending an entire\n   * request if the response does not depend on any portion of the request that\n   * has not been sent and received.\n   */\n\n  /**\n   * Create HTTPSource that will only produce a header and *no* body to simulate\n   * that req egress isn't \"complete\" before we receive resp. When the final\n   * header is read from the req source, sendRequest() returns and we serialize\n   * the 200 response to the client.\n   */\n  auto reqHeaders = std::make_unique<HTTPMessage>(getPostRequest(1000));\n  auto *reqStreamSource = new HTTPStreamSource(&evb_);\n  reqStreamSource->setHeapAllocated();\n  reqStreamSource->headers(std::move(reqHeaders));\n\n  // send request & get id\n  auto responseSource =\n      co_await co_awaitTry(session_->sendRequest(reqStreamSource));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  serializeResponse(id, 200, makeBuf(1500));\n\n  // egress stop_sending or rst_stream w/ no_error to peer after\n  // message complete\n  if (isHQ()) {\n    muxTransport_->socketDriver_.addStopSending(id, HTTP3::HTTP_NO_ERROR);\n  } else {\n    serverCodec_->generateRstStream(writeBuf_, id, ErrorCode::NO_ERROR);\n    transport_->addReadEvent(writeBuf_.move());\n  }\n\n  // recv-ing full response & stop_sending or rst_stream w/ no_error should\n  // detach transaction\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  co_await expectResponse(std::move(*responseSource), 200, 1500, true);\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, StopReadingOnAbort) {\n  /** Verifies that when receiving a RST_STREAM / STOP_SENDING frame from the\n   * peer while body egress is still in progress, we invoke ::stopReading() w/\n   * a HTTPErrorCode */\n  HTTPCodec::StreamID id;\n\n  auto reqHeaders = std::make_unique<HTTPMessage>(getPostRequest(1000));\n  MockHTTPSource reqSource;\n  EXPECT_CALL(reqSource, readHeaderEvent).WillOnce([]() {\n    return folly::coro::makeTask(HTTPHeaderEvent(\n        std::make_unique<HTTPMessage>(getPostRequest(1000)), false));\n  });\n\n  EXPECT_CALL(reqSource, readBodyEvent(_))\n      .WillOnce([&]() -> folly::coro::Task<HTTPBodyEvent> {\n        // StreamID available after one evb loop\n        co_await folly::coro::co_reschedule_on_current_executor;\n        if (isHQ()) {\n          muxTransport_->socketDriver_.addStopSending(id, HTTP3::HTTP_NO_ERROR);\n        } else {\n          serverCodec_->generateRstStream(writeBuf_, id, ErrorCode::NO_ERROR);\n          transport_->addReadEvent(writeBuf_.move());\n        }\n        co_await folly::coro::co_reschedule_on_current_executor;\n        co_return HTTPBodyEvent(makeBuf(1000), false);\n      });\n\n  EXPECT_CALL(reqSource,\n              stopReading(A<folly::Optional<const HTTPErrorCode>>()));\n\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(&reqSource));\n  XCHECK(!responseSource.hasException());\n  id = *responseSource->getStreamID();\n  auto resSourceHeaderEvent =\n      co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(resSourceHeaderEvent.hasException());\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, RstStreamAfterEOM) {\n  HTTPCodec::StreamID id;\n  auto reqHeaders = std::make_unique<HTTPMessage>(getGetRequest());\n  auto *reqStreamSource = new HTTPStreamSource(&evb_);\n  reqStreamSource->setHeapAllocated();\n  reqStreamSource->headers(std::move(reqHeaders), false);\n\n  // send request & get id\n  auto responseSource =\n      co_await co_awaitTry(session_->sendRequest(reqStreamSource));\n  XCHECK(!responseSource.hasException());\n  id = *responseSource->getStreamID();\n\n  // send response with eom and follow with RST_STREAM\n  serializeResponse(id, 200, makeBuf(1500));\n  serverCodec_->generateRstStream(writeBuf_, id, ErrorCode::NO_ERROR);\n  transport_->addReadEvent(writeBuf_.move());\n  // add connection end\n  transport_->addReadEvent(nullptr, true);\n\n  co_await expectResponse(std::move(*responseSource), 200, 1500, true);\n  co_await rescheduleN(2);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, ReceiveStopSendingStreamPriorToResponseEOM) {\n  // receiving a STOP_SENDING prior to response eom (due to possible packet\n  // re-ordering) should generate just a RST_STREAM and not STOP_SENDING\n  HTTPCodec::StreamID id;\n\n  /**\n   * Create HTTPSource that will only produce a header and *no* body to simulate\n   * that req egress isn't \"complete\" before we receive resp. When the final\n   * header is read from the req source, sendRequest() returns and we serialize\n   * the 200 response to the client without EOM then send a RST_STREAM.\n   */\n  auto reqHeaders = std::make_unique<HTTPMessage>(getPostRequest(1000));\n  auto *reqStreamSource = new HTTPStreamSource(&evb_);\n  reqStreamSource->setHeapAllocated();\n  reqStreamSource->headers(std::move(reqHeaders));\n\n  // send request & get id\n  auto responseSource =\n      co_await co_awaitTry(session_->sendRequest(reqStreamSource));\n  XCHECK(!responseSource.hasException());\n  id = *responseSource->getStreamID();\n\n  // serialize partial response\n  serializeResponse(id, 200, makeBuf(1500), /*eom=*/false);\n\n  // Deliver stopSending callback to session. This should result in the session\n  // calling resetStream but not stopSending on the socket.\n  EXPECT_CALL(*muxTransport_->getSocket(),\n              resetStream(id,\n                          quic::ApplicationErrorCode(\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED)))\n      .Times(1);\n  EXPECT_CALL(*muxTransport_->getSocket(), stopSending(id, _)).Times(0);\n  muxTransport_->socketDriver_.addStopSending(id,\n                                              HTTP3::ErrorCode::HTTP_NO_ERROR);\n  co_await rescheduleN(1);\n\n  // deliver rest of body\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(500), HTTPCodec::NoPadding, /*eom=*/true);\n  transport_->addReadEvent(id, writeBuf_.move(), /*eom=*/true);\n\n  // recv-ing stop_sending w/ NO_ERROR prior to full response  should\n  // detach transaction successfully without error\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  co_await expectResponse(std::move(*responseSource), 200, 2000, true);\n  co_await rescheduleN(2);\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, ReserveRequest) {\n  session_->setMaxConcurrentOutgoingStreams(1);\n  EXPECT_EQ(session_->getNextStreamSeqNum(), 0);\n\n  // Reserve a request\n  EXPECT_CALL(lifecycleObs_, onTransactionAttached(_));\n  auto res = session_->reserveRequest();\n  EXPECT_FALSE(res.hasException());\n  EXPECT_EQ(session_->getNextStreamSeqNum(), 0); // still not bumped\n\n  // Try to reserve another - fails\n  auto res2 = session_->reserveRequest();\n  EXPECT_TRUE(res2.hasException());\n  EXPECT_EQ(getHTTPError(res2).code, HTTPErrorCode::REFUSED_STREAM);\n\n  // cancel first reservation without sending, can get another\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  res->cancel();\n  EXPECT_TRUE(session_->supportsMoreTransactions());\n  EXPECT_CALL(lifecycleObs_, onTransactionAttached(_));\n  res2 = session_->reserveRequest();\n  EXPECT_FALSE(res.hasException());\n\n  // move assign\n  res = std::move(res2);\n  // res2 is empty.  cancel is now a no-op\n  // @lint - ignore\n  res2->cancel();\n  EXPECT_FALSE(session_->supportsMoreTransactions());\n\n  if (!IS_H1()) {\n    session_->setMaxConcurrentOutgoingStreams(2);\n    {\n      EXPECT_CALL(lifecycleObs_, onTransactionAttached(_));\n      auto res3 = session_->reserveRequest();\n      EXPECT_FALSE(res3.hasException());\n      EXPECT_FALSE(session_->supportsMoreTransactions());\n      // let res3 destruct without sending\n      EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n    }\n    EXPECT_TRUE(session_->supportsMoreTransactions());\n  }\n\n#if NDEBUG\n  // res2 is no longer a valid reservation, will get an error.  This is a\n  // DFATAL so only try in opt builds\n  auto respSource = co_await co_awaitTry(session_->sendRequest(\n      (HTTPFixedSource::makeFixedRequest(\"/\")), std::move(*res2)));\n  EXPECT_EQ(getHTTPError(respSource).code, HTTPErrorCode::INTERNAL_ERROR);\n#endif\n\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  // Valid request\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\"), std::move(*res)));\n  XCHECK(!responseSource.hasException());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  EXPECT_EQ(session_->getNextStreamSeqNum(), 1);\n  transport_->addReadEvent(nullptr, true);\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, IngressResetStream) {\n  // Send request, response with a reset stream\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  resetStream(id, ErrorCode::CANCEL);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  session_->initiateDrain();\n}\n\n// Could be H1 if we check upstream pipeline limit\nCO_TEST_P_X(H2QUpstreamSessionTest, EgressStreamLimitExceeded) {\n  if (isHQ()) {\n    muxTransport_->socketDriver_.setMaxBidiStreams(0);\n  } else {\n    setTestCodecSetting(serverCodec_->getEgressSettings(),\n                        SettingsId::MAX_CONCURRENT_STREAMS,\n                        0);\n    serverCodec_->generateSettings(writeBuf_);\n    transport_->addReadEvent(writeBuf_.move(), false);\n  }\n  co_await folly::coro::co_reschedule_on_current_executor;\n\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  EXPECT_TRUE(responseSource.hasException());\n  EXPECT_EQ(getHTTPError(responseSource).code, HTTPErrorCode::REFUSED_STREAM);\n  transport_->addReadEvent(nullptr, true);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, StreamsFull) {\n  muxTransport_->socketDriver_.setMaxBidiStreams(1);\n\n  EXPECT_TRUE(session_->supportsMoreTransactions());\n  EXPECT_EQ(session_->numTransactionsAvailable(), 1);\n  auto res = session_->reserveRequest();\n  EXPECT_FALSE(res.hasException());\n  EXPECT_FALSE(session_->supportsMoreTransactions());\n  EXPECT_EQ(session_->numTransactionsAvailable(), 0);\n  res->cancel();\n  EXPECT_TRUE(session_->supportsMoreTransactions());\n  EXPECT_EQ(session_->numTransactionsAvailable(), 1);\n\n  EXPECT_CALL(lifecycleObs_, onSettingsOutgoingStreamsNotFull(_));\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  EXPECT_FALSE(session_->supportsMoreTransactions());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  muxTransport_->socketDriver_.setMaxBidiStreams(2);\n}\n\n// Push tests -- H2 only\n// TODO: HQ push id limits\nCO_TEST_P_X(H2UpstreamSessionTest, IngressStreamLimitExceeded) {\n  session_->setSetting(SettingsId::MAX_CONCURRENT_STREAMS, 0);\n  // Set the setting to 0, but don't send to the peer\n\n  pushPromiseHandler_ = [](HTTPPushEvent pushEvent) -> folly::coro::Task<void> {\n    auto pushSource = pushEvent.movePushSource();\n    auto event = co_await co_awaitTry(pushSource->readHeaderEvent());\n    EXPECT_TRUE(event.hasException());\n  };\n  // send a request and receive a response that includes a push promise and\n  // complete push response.\n  co_await testPush();\n  onTearDown([this] { EXPECT_EQ(pushes_.size(), 0); });\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, Push) {\n  // send a request and receive a response that includes a push promise and\n  // complete push response.\n  co_await testPush();\n  onTearDown([this] {\n    EXPECT_EQ(pushes_.size(), 1);\n    auto &push = pushes_.front();\n    XCHECK(push.second);\n    EXPECT_EQ(push.first.promise->getURL(), \"/push\");\n    EXPECT_EQ(push.second->msg_->getStatusCode(), 200);\n    EXPECT_EQ(push.second->body_.chainLength(), 100);\n  });\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, PushEgressRstStream) {\n  // When we receive a push promise, stop reading from the source.\n  pushPromiseHandler_ =\n      [this](HTTPPushEvent pushEvent) -> folly::coro::Task<void> {\n    // The pushEvent will automatically call stopReading on destruction,\n    // triggering STOP_SENDING/RST_STREAM (w/ ErrorCode::CANCEL) since we\n    // haven't seen EOM yet.\n    co_await rescheduleN(2);\n    XLOG(DBG4) << \"Abandoning push\";\n    co_return;\n  };\n\n  auto pushID = co_await testPush(\n      /*expectIncomingStream=*/true, /*pushEOM=*/false, /*eof=*/false);\n\n  onTearDown([this, pushID] {\n    EXPECT_EQ(pushes_.size(), 0);\n    // upstream .stopReading() = cancel error code\n    if (isHQ()) {\n      EXPECT_EQ(muxTransport_->socketDriver_.streams_[pushID].error,\n                HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n    } else {\n      expectStreamAbort(pushID, ErrorCode::CANCEL);\n      parseOutputUniplex();\n    }\n  });\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, PushIngressRstStream) {\n  // Instead of serializing the push response EOM, serialize a RST_STREAM\n  // for the push stream.  This will result in the push handler receiving an\n  // error.\n  pushPromiseHandler_ =\n      [this](HTTPPushEvent pushEvent) -> folly::coro::Task<void> {\n    auto &it = pushes_.emplace_back(std::move(pushEvent), nullptr);\n    auto req =\n        co_await co_awaitTry(it.first.movePushSource()->readHeaderEvent());\n    EXPECT_TRUE(req.hasException());\n    co_return;\n  };\n  auto pushID = co_await testPush(\n      /*expectIncomingStream=*/true, /*pushEOM=*/false, /*eof=*/false);\n  resetStream(pushID, ErrorCode::CANCEL);\n  serverCodec_->generateGoaway(writeBuf_, pushID + 4, ErrorCode::NO_ERROR);\n  transport_->addReadEvent(writeBuf_.move(), false);\n\n  onTearDown([this] { EXPECT_EQ(pushes_.size(), 1); });\n}\n\n// Test when the parent stream goes away between onPushMessageBegin and\n// onHeadersComplete.\n// TODO: H3 requires a QPACK dynamic table stall for this case\nCO_TEST_P_X(H2UpstreamSessionTest, PushParentReset) {\n  // send a request, then reset it.  The peer sends a response that\n  // includes a push promise and complete push response.\n  //\n  // Note, we need the PUSH_PROMISE to have a continuation, so give the server\n  // codec a ridiculously small MAX_FRAME_SIZE.\n  auto *settings = (HTTPSettings *)serverCodec_->getIngressSettings();\n  settings->setSetting(SettingsId::MAX_FRAME_SIZE, 5);\n\n  // send a request for GET /\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto streamID = *responseSource->getStreamID();\n\n  // serialize a response with no body/EOM\n  serializeResponse(streamID, 200, nullptr, false);\n\n  // serialize a PUSH_PROMISE and stream EOM\n  serializePushPromise(streamID, false, false);\n  auto toSend = writeBuf_.split(writeBuf_.chainLength() - 1);\n  auto toHold = writeBuf_.move();\n  writeBuf_.append(std::move(toSend));\n  transport_->addReadEvent(streamID, writeBuf_.move(), false);\n\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(headerEvent.hasException());\n  // Reset the parent stream, and flush the CONTINUATION byte\n  responseSource->stopReading();\n  transport_->addReadEvent(streamID, std::move(toHold), false);\n  transport_->addReadEvent(nullptr, true);\n  onTearDown([this, settings] {\n    EXPECT_EQ(pushes_.size(), 0);\n    // Reset the MAX_FRAME_SIZE\n    settings->setSetting(SettingsId::MAX_FRAME_SIZE,\n                         http2::kMaxFramePayloadLengthMin);\n  });\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, PromiseBeforePush) {\n  // send a request for GET /\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto streamID = *responseSource->getStreamID();\n\n  // serialize a response with no EOM\n  serializeResponse(streamID, 200, makeBuf(100), false);\n\n  // serialize a PUSH_PROMISE, eom, flush, capture pushStream preface\n  multiCodec_->addCodec(streamID);\n  folly::IOBufQueue pushStreamBuf{folly::IOBufQueue::cacheChainLength()};\n  auto pushID =\n      serializePushPromise(streamID, true, true, &pushStreamBuf).first;\n\n  // Wait for PUSH_PROMISE to get parsed\n  co_await folly::coro::co_reschedule_on_current_executor;\n\n  // Expect the request's response\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n\n  // Now serialize the push response\n  transport_->addReadEvent(pushID, pushStreamBuf.move(), false);\n  serializeResponse(pushID, 200, makeBuf(100), true);\n\n  serverCodec_->generateGoaway(writeBuf_, streamID, ErrorCode::NO_ERROR);\n  transport_->addReadEvent(writeBuf_.move(), false);\n  onTearDown([this] {\n    EXPECT_EQ(pushes_.size(), 1);\n    auto &push = pushes_.front();\n    ASSERT_NE(push.second, nullptr);\n    EXPECT_EQ(push.first.promise->getURL(), \"/push\");\n    EXPECT_EQ(push.second->msg_->getStatusCode(), 200);\n    EXPECT_EQ(push.second->body_.chainLength(), 100);\n  });\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, SendPushPriority) {\n  // send a request for GET /\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto streamID = *responseSource->getStreamID();\n\n  // serialize a response with no EOM\n  serializeResponse(streamID, 200, makeBuf(100), false);\n\n  // Send stream priority update frame. This should also set local priority\n  // (even tho the request has been written).\n  auto *session = static_cast<HTTPQuicCoroSession *>(session_);\n  muxTransport_->socketDriver_.expectSetPriority(\n      streamID, quic::HTTPPriorityQueue::Priority(3, false));\n  EXPECT_GT(session->sendPriority(streamID, HTTPPriority(3, false)), 0);\n\n  // serialize a PUSH_PROMISE, eom, flush, capture pushStream preface\n  multiCodec_->addCodec(streamID);\n  folly::IOBufQueue pushStreamBuf{folly::IOBufQueue::cacheChainLength()};\n  auto pushRes = serializePushPromise(streamID, true, true, &pushStreamBuf);\n\n  // Wait for PUSH_PROMISE to get parsed\n  co_await folly::coro::co_reschedule_on_current_executor;\n\n  // Expect the request's response\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n\n  // Send push priority\n  EXPECT_GT(session->sendPushPriority(pushRes.second, HTTPPriority(4, true)),\n            0);\n\n  // Now serialize the push response\n  transport_->addReadEvent(pushRes.first, pushStreamBuf.move(), false);\n  serializeResponse(pushRes.first, 200, makeBuf(100), true);\n\n  serverCodec_->generateGoaway(writeBuf_, streamID, ErrorCode::NO_ERROR);\n  transport_->addReadEvent(writeBuf_.move(), false);\n  onTearDown([this] {\n    EXPECT_EQ(pushes_.size(), 1);\n    auto &push = pushes_.front();\n    ASSERT_NE(push.second, nullptr);\n    EXPECT_EQ(push.first.promise->getURL(), \"/push\");\n    EXPECT_EQ(push.second->msg_->getStatusCode(), 200);\n    EXPECT_EQ(push.second->body_.chainLength(), 100);\n  });\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, DrainAfterCodecNotReusable) {\n  // send first request, verify no exception\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n\n  // rx response with Connection: Close header (i.e. no keep-alive)\n  auto id = *responseSource->getStreamID();\n  auto respMsg = makeResponse(200, true);\n  respMsg.getHeaders().set(HTTPHeaderCode::HTTP_HEADER_CONNECTION, \"close\");\n  respMsg.getHeaders().set(HTTPHeaderCode::HTTP_HEADER_CONTENT_LENGTH, \"0\");\n  serializeResponseHeader(id, std::move(respMsg), true);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n\n  // read full response, sanity check status code\n  HTTPSourceReader reader(std::move(*responseSource));\n  reader.onHeaders(\n      [](std::unique_ptr<HTTPMessage> headers, bool /*final*/, bool eom) {\n        EXPECT_EQ(headers->getStatusCode(), 200);\n        EXPECT_TRUE(eom);\n        return HTTPSourceReader::Continue;\n      });\n  auto resp = co_await co_awaitTry(reader.read());\n  EXPECT_FALSE(resp.hasException());\n\n  // verify session is draining and attempting to send another request will\n  // yield exception\n  auto drainedException = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  CHECK(drainedException.hasException());\n  auto ex = drainedException.exception().get_exception<HTTPError>();\n\n  // reservation fails with \"Exceeded stream limit\" since\n  // supportsMoreTransactions() == false\n  EXPECT_EQ(ex->code, HTTPErrorCode::REFUSED_STREAM);\n  EXPECT_EQ(ex->msg, \"Exceeded stream limit\");\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, ReceiveGoaway) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  generateGoaway(id, ErrorCode::NO_ERROR);\n  serializeResponseHeader(id, 200, false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(100), HTTPCodec::NoPadding, true);\n  // no EOF, sess will close when stream detaches\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto event = co_await co_awaitTry(readBodyEventNoSuspend(*responseSource));\n  EXPECT_FALSE(event.hasException());\n  EXPECT_EQ(event->eventType, HTTPBodyEvent::BODY);\n  EXPECT_EQ(event->event.body.chainLength(), 100);\n  EXPECT_TRUE(event->eom);\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, SendGoaway) {\n  // session_->initiateDrain() can't add Connection: close to the\n  // next request, because sending a request after drain will fail.\n  // Have to add Connection: close manually\n  auto req = HTTPFixedSource::makeFixedRequest(\"/\");\n  req->msg_->getHeaders().set(HTTP_HEADER_CONNECTION, \"close\");\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(req));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  serializeResponseHeader(id, 200, false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(100), HTTPCodec::NoPadding, true);\n  // no EOF, sess will close when stream detaches\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto event = co_await co_awaitTry(readBodyEventNoSuspend(*responseSource));\n  EXPECT_FALSE(event.hasException());\n  EXPECT_EQ(event->eventType, HTTPBodyEvent::BODY);\n  EXPECT_EQ(event->event.body.chainLength(), 100);\n  EXPECT_TRUE(event->eom);\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, DrainAfterFailedUpgrade) {\n  auto req = HTTPFixedSource::makeFixedRequest(\"/websocket\");\n  req->msg_->setEgressWebsocketUpgrade();\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(req));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  serializeResponseHeader(id, 500, /*eom*/ true);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  {\n    EXPECT_CALL(lifecycleObs_, onDrainStarted(_)).Times(1);\n    auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n    EXPECT_FALSE(resp.hasException());\n    EXPECT_EQ(resp->headers->getStatusCode(), 500);\n    EXPECT_CALL(lifecycleObs_, onDrainStarted(_)).Times(0);\n  }\n}\n\nTEST_P(H2QUpstreamSessionTest, ReceiveGoaway) {\n  // Client receives GOAWAY, no EOF.  Even though the peer is allowing for\n  // more streams, this session doesn't have any in-flight, so it egresses\n  // it's own GOAWAY and closed the connection.\n  generateGoaway(1, ErrorCode::NO_ERROR);\n}\n\nTEST_P(HTTPUpstreamSessionTest, SendGoaway) {\n  // Client sends a GOAWAY, codec becomes unusable and session exits\n  session_->initiateDrain();\n}\n\nTEST_P(HTTPUpstreamSessionTest, LifecycleObserverTestNullptr) {\n  EXPECT_DEATH(session_->addLifecycleObserver(nullptr), \"Check failed:\");\n}\n\nTEST_P(HTTPUpstreamSessionTest, LifecycleObserverTests) {\n  auto obs1 = std::make_unique<StrictMock<MockLifecycleObserver>>();\n  auto obs2 = std::make_unique<StrictMock<MockLifecycleObserver>>();\n\n  // helper function to clear both observers\n  auto clearObservers = [&]() {\n    session_->removeLifecycleObserver(obs1.get());\n    session_->removeLifecycleObserver(obs2.get());\n  };\n\n  /**\n   * test #1: adding obs2 during a ::deliverLifecycleCallback invocation\n   * will not call back into obs2 for that event / during that iteration\n   */\n  {\n    EXPECT_CALL(*obs1, onAttached(_));\n    session_->addLifecycleObserver(obs1.get());\n\n    EXPECT_CALL(*obs1, onActivateConnection(_)).Times(1);\n    EXPECT_CALL(*obs1, onTransactionAttached(_)).WillOnce([&]() {\n      EXPECT_CALL(*obs2, onAttached(_));\n      session_->addLifecycleObserver(obs2.get());\n    });\n    EXPECT_CALL(*obs2, onTransactionAttached(_)).Times(0);\n\n    // both should expect onTransactionDetached & onDeactivateConnection\n    // callbacks\n    EXPECT_CALL(*obs1, onTransactionDetached(_));\n    EXPECT_CALL(*obs2, onTransactionDetached(_));\n    EXPECT_CALL(*obs1, onDeactivateConnection(_));\n    EXPECT_CALL(*obs2, onDeactivateConnection(_));\n\n    session_->reserveRequest();\n    clearObservers();\n  }\n\n  {\n    /**\n     * test #2: deleting obs2 prior to its subsequent\n     * ::deliverLifecycleCallback invocation should work as expected\n     */\n    EXPECT_CALL(*obs1, onAttached(_));\n    EXPECT_CALL(*obs2, onAttached(_));\n    session_->addLifecycleObserver(obs1.get());\n    session_->addLifecycleObserver(obs2.get());\n\n    // both should receive the first ::onActivateConnection &\n    // ::onTransactionAttached callback\n    EXPECT_CALL(*obs1, onActivateConnection(_)).Times(1);\n    EXPECT_CALL(*obs2, onActivateConnection(_)).Times(1);\n    EXPECT_CALL(*obs1, onTransactionAttached(_))\n        .WillOnce([&]() { session_->removeLifecycleObserver(obs2.get()); })\n        .RetiresOnSaturation();\n    EXPECT_CALL(*obs2, onTransactionAttached(_)).Times(1);\n\n    // only obs1 should receive the detach/deactivate callback\n    EXPECT_CALL(*obs1, onTransactionDetached(_));\n    EXPECT_CALL(*obs1, onDeactivateConnection(_));\n    EXPECT_CALL(*obs2, onTransactionDetached(_)).Times(0);\n\n    session_->reserveRequest();\n    clearObservers();\n  }\n\n  {\n    /**\n     * test #3: single obs1 removes itself from within ::onAttached callback;\n     * this should never deliver any more\n     */\n    EXPECT_CALL(*obs1, onAttached(_)).WillOnce([&]() {\n      session_->removeLifecycleObserver(obs1.get());\n    });\n    session_->addLifecycleObserver(obs1.get());\n  }\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, BodyError) {\n  auto reservation = *(session_->reserveRequest());\n  auto respSource = co_await co_withExecutor(\n                        &evb_,\n                        session_->sendRequest(\n                            new ErrorSource(\"super super long text\", true, 11),\n                            std::move(reservation)))\n                        .start();\n\n  folly::exception_wrapper ew;\n  while (true) {\n    auto event = co_await co_awaitTry(readBodyEventNoSuspend(respSource));\n    if (event.hasException()) {\n      ew = std::move(event).exception();\n      break;\n    } else if (event.value().eom) {\n      break;\n    }\n  }\n\n  CO_ASSERT_TRUE(bool(ew));\n  CO_ASSERT_TRUE(ew.get_exception<HTTPError>() != nullptr);\n  EXPECT_NE(ew.get_exception<HTTPError>()->code, HTTPErrorCode::READ_TIMEOUT);\n  if (IS_H1()) {\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  }\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, ReceiveGoawayWithOpenStream) {\n  auto responseSource1 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource1.hasException());\n  auto responseSource2 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto id = *responseSource1->getStreamID();\n  generateGoaway(id, ErrorCode::NO_ERROR);\n  serializeResponseHeader(id, 200, false);\n  transport_->addReadEvent(id, writeBuf_.move(), false);\n  auto resp = co_await co_awaitTry(responseSource1->readHeaderEvent());\n  EXPECT_FALSE(resp.hasException());\n  EXPECT_EQ(resp->headers->getStatusCode(), 200);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(100), HTTPCodec::NoPadding, true);\n  // no EOF, sess will close when stream detaches\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  auto event = co_await co_awaitTry(readBodyEventNoSuspend(*responseSource1));\n  EXPECT_FALSE(event.hasException());\n  EXPECT_EQ(event->eventType, HTTPBodyEvent::BODY);\n  EXPECT_EQ(event->event.body.chainLength(), 100);\n  EXPECT_TRUE(event->eom);\n  resp = co_await co_awaitTry(responseSource2->readHeaderEvent());\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::REFUSED_STREAM);\n}\n\n// H3 GOAWAY does not have an error!\nCO_TEST_P_X(H2UpstreamSessionTest, ReceiveGoawayWithError) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto id = *responseSource->getStreamID();\n  generateGoaway(id, ErrorCode::PROTOCOL_ERROR);\n  auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::PROTOCOL_ERROR);\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, ReceiveGoawayBeforeRequestEgress) {\n  // Send a GOAWAY with last stream ID 1 and an error code\n  serverCodec_->generateGoaway(writeBuf_, 1, ErrorCode::PROTOCOL_ERROR);\n  transport_->addReadEvent(writeBuf_.move(), false);\n  co_await folly::coro::co_reschedule_on_current_executor;\n\n  // Attempt to send 3 POST requests with 25000 bytes each\n  auto responseSource1 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\n          \"/\", HTTPMethod::POST, makeBuf(25000))));\n  auto responseSource2 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\n          \"/\", HTTPMethod::POST, makeBuf(25000))));\n  auto responseSource3 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\n          \"/\", HTTPMethod::POST, makeBuf(25000))));\n\n  co_await folly::coro::co_reschedule_on_current_executor;\n  co_await folly::coro::co_reschedule_on_current_executor;\n  co_await folly::coro::co_reschedule_on_current_executor;\n}\n\nTEST_P(HTTPUpstreamSessionTest, IdleTimeoutNoStreams) {\n  // Just run the loop.  The session should idle timeout, drain and close.\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n}\n\n// Need to change the test to get let the mock socket create the stream first\nCO_TEST_P_X(H12UpstreamSessionTest, WriteTimeout) {\n  // Pause writes, so the session sees a write timeout while sending a request.\n  // The response header event is an error.\n  session_->setWriteTimeout(std::chrono::milliseconds(250));\n  // TODO: not quite right but I don't know the id before sendRequest\n  transport_->pauseWrites(0);\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  // Socket is closed with RST for H1 and H2\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, WriteErrorWithCompleteStreams) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  session_->initiateDrain();\n  // Flush request\n  co_await folly::coro::co_reschedule_on_current_executor;\n\n  // Ack the first SETTINGS sent from the test codec so it can generate more\n  HTTP2Codec fakeUpstream(TransportDirection::UPSTREAM);\n  fakeUpstream.generateConnectionPreface(writeBuf_);\n  fakeUpstream.generateSettingsAck(writeBuf_);\n  serverCodec_->onIngress(*writeBuf_.front());\n  writeBuf_.move();\n\n  // Send the real response\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100));\n\n  // Send another SETTINGS frame, this will trigger the write of a SETTINGS\n  // ACK\n  serverCodec_->generateSettings(writeBuf_);\n  transport_->addReadEvent(writeBuf_.move(), false);\n\n  // Force the write end of the transport closed, triggers a write error\n  static_cast<TestUniplexTransport *>(transport_)->shutdownWrite();\n  // Wait two loops, one to parse the SETTINGS, one to flush the ACK and hit\n  // the write error\n  co_await rescheduleN(2);\n\n  // Now read out the buffered response, it's still fine\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  // Socket is closed with RST for H1 and H2\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, PostBodyReadTimeout) {\n  // Send a request that hangs in readBodyEvent\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  auto req = std::make_unique<HTTPMessage>(getPostRequest(100));\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(new TimeoutSource(std::move(req))));\n  XCHECK(!responseSource.hasException());\n  auto resp = co_await co_awaitTry(folly::coro::timeoutNoDiscard(\n      responseSource->readHeaderEvent(), std::chrono::milliseconds(500)));\n  // Wait for a header event for 2x the read timeout -- the read timer isn't\n  // running.  timeoutNoDiscard will cancel the read\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::CORO_CANCELLED);\n  session_->closeWhenIdle();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, PostReadTimeout) {\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\", HTTPMethod::POST, makeBuf(100))));\n  XCHECK(!responseSource.hasException());\n  auto resp = co_await co_awaitTry(folly::coro::timeoutNoDiscard(\n      responseSource->readHeaderEvent(), std::chrono::milliseconds(1500)));\n  // Timer is running, read timeout before timeoutNoDiscard\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::READ_TIMEOUT);\n  session_->closeWhenIdle();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, PostReadCustomTimeout) {\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->setStreamReadTimeout(std::chrono::seconds(20));\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(\n      HTTPFixedSource::makeFixedRequest(\"/\", HTTPMethod::POST, makeBuf(100))));\n  XCHECK(!responseSource.hasException());\n  responseSource->setReadTimeout(std::chrono::milliseconds(250));\n  auto resp = co_await co_awaitTry(folly::coro::timeoutNoDiscard(\n      responseSource->readHeaderEvent(), std::chrono::milliseconds(1500)));\n  // Timer is running, read timeout before timeoutNoDiscard\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::READ_TIMEOUT);\n  session_->closeWhenIdle();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, GetReadTimeout) {\n  session_->setConnectionReadTimeout(std::chrono::milliseconds(250));\n  session_->setStreamReadTimeout(std::chrono::milliseconds(250));\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto resp = co_await co_awaitTry(folly::coro::timeoutNoDiscard(\n      responseSource->readHeaderEvent(), std::chrono::milliseconds(1500)));\n  // Timer is running, read timeout before timeoutNoDiscard\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::READ_TIMEOUT);\n  session_->closeWhenIdle();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\n// TODO: hq flow control tests\nCO_TEST_P_X(H2UpstreamSessionTest, ConnFlowControlOnBodyError) {\n  // Send a request, response is larger than both the conn and stream flow\n  // control window, but conn will trip first.\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID = *responseSource->getStreamID();\n  serializeResponse(streamID, 200, makeBuf(70000), false);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(headerEvent.hasException());\n  // Get more data to trigger before reading body events to trigger flow\n  // control error.\n  co_await folly::coro::co_reschedule_on_current_executor;\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventNoSuspend(*responseSource));\n  EXPECT_TRUE(bodyEvent.hasException());\n  EXPECT_EQ(getHTTPError(bodyEvent).code, HTTPErrorCode::FLOW_CONTROL_ERROR);\n}\n\n// TODO: hq flow control tests\nCO_TEST_P_X(H2UpstreamSessionTest, StreamFlowControlOnBodyError) {\n  // Send a request, response is larger than stream flow control window.\n  // Stream is reset, but connection is open.\n  session_->setConnectionFlowControl(70000);\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID = *responseSource->getStreamID();\n  serializeResponse(streamID, 200, makeBuf(70000), false);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(headerEvent.hasException());\n  // Get more data before reading body events\n  co_await folly::coro::co_reschedule_on_current_executor;\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventNoSuspend(*responseSource));\n  EXPECT_TRUE(bodyEvent.hasException());\n  EXPECT_EQ(getHTTPError(bodyEvent).code, HTTPErrorCode::FLOW_CONTROL_ERROR);\n  transport_->addReadEvent(nullptr, true);\n  // TODO: verify graceful vs error shutdowns\n}\n\n// H2 only\nTEST_P(H2UpstreamSessionTest, ConnFlowControlOnWindowUpdateError) {\n  // Add a window update that overflows the conn window\n  windowUpdate(std::numeric_limits<int32_t>::max());\n  // expect an error?\n}\n\n// H2 only\nCO_TEST_P_X(H2UpstreamSessionTest, StreamFlowControlOnWindowUpdateError) {\n  // Add a window update that overflows the stream window.  The connection is\n  // closed with error\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID = *responseSource->getStreamID();\n  windowUpdate(streamID, std::numeric_limits<int32_t>::max());\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  EXPECT_EQ(getHTTPError(headerEvent).code, HTTPErrorCode::FLOW_CONTROL_ERROR);\n}\n\nTEST_P(H1UpstreamSessionTest, CodecHTTPError) {\n  std::string badRequest(\"BLARF\");\n  HTTPCodec::StreamID id = 1;\n  transport_->addReadEvent(id, folly::IOBuf::copyBuffer(badRequest), false);\n\n  evb_.loop();\n  expectedError_ = TransportErrorCode::NETWORK_ERROR;\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, CodecHTTPError) {\n  auto responseSource =\n      co_await session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\"));\n  auto streamID = *responseSource.getStreamID();\n  HTTPMessage req = getResponse(200);\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"200\");\n  if (isHQ()) {\n    multiCodec_->addCodec(streamID);\n  }\n  serverCodec_->generateHeader(writeBuf_, streamID, req, true);\n  transport_->addReadEvent(streamID, writeBuf_.move(), false);\n  generateGoaway();\n\n  auto headerEvent = co_await co_awaitTry(responseSource.readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n}\n\nCO_TEST_P_X(H2QUpstreamSessionTest, OnDeactivateConnectionLifecycle) {\n  EXPECT_CALL(lifecycleObs_, onActivateConnection(_)).Times(1);\n  EXPECT_CALL(lifecycleObs_, onTransactionAttached(_)).Times(1);\n  // reserving a request should invoke both activated & attached cbs\n  auto res = session_->reserveRequest().value();\n\n  EXPECT_CALL(lifecycleObs_, onTransactionAttached(_)).Times(1);\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n\n  // detaching a stream with a pending reservation should not\n  // ::onDeactivateConnection\n\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_)).Times(1);\n  EXPECT_CALL(lifecycleObs_, onDeactivateConnection(_)).Times(0);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n\n  // cancelling last pending stream (i.e. reservation) should invoke\n  // ::onDeactivateConnection\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_)).Times(1);\n  EXPECT_CALL(lifecycleObs_, onDeactivateConnection(_)).Times(1);\n  res.cancel();\n  transport_->addReadEvent(nullptr, true);\n}\n\n// TODO: hq push\nCO_TEST_P_X(H2UpstreamSessionTest, PushPromiseParseError) {\n  // send a request.  The peer sends a response that\n  // includes a push promise with a parse error and complete push response.\n  // send a request for GET /\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto streamID = *responseSource->getStreamID();\n\n  // serialize a response with no body/EOM\n  serializeResponse(streamID, 200, makeBuf(100), false);\n\n  // Serialize a push promise, with a parsing error\n  auto pushID = serverCodec_->createStream();\n  HTTPHeaderSize size;\n  HTTPMessage promise = getGetRequest(\"/push\");\n  promise.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"100\");\n  promise.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"200\");\n  serverCodec_->generatePushPromise(\n      writeBuf_, pushID, promise, streamID, false, &size);\n\n  // Now serialize the push response, with EOM\n  serializeResponse(pushID, 200, makeBuf(100), true);\n\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  EXPECT_EQ(getHTTPError(headerEvent).httpMessage->getStatusCode(), 200);\n  session_->initiateDrain();\n  // TODO: verify we egress a reset stream for push stream too\n  onTearDown([this] { EXPECT_EQ(pushes_.size(), 0); });\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, ResetReleaseConnFlowControl) {\n  // When a stream is reset it releases connection flow control.  Send two\n  // POST requests.  The second one is blocked on connection flow control,\n  // which is released when the first stream is reset.\n  auto responseSource1 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID1 = *responseSource1->getStreamID();\n  auto responseSource2 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID2 = *responseSource2->getStreamID();\n  // Serialize first response, then reset it.  The second response would\n  // generate a flow control error if the reset didn't release flow control\n  serializeResponse(streamID1, 200, makeBuf(http2::kInitialWindow), false);\n  resetStream(streamID1, ErrorCode::CANCEL);\n  serializeResponse(streamID2, 200, makeBuf(100), true);\n  // Because we only read 64k per loop, we see the headers and almost all the\n  // body before the RST_STREAM is read.\n  auto headerEvent = co_await co_awaitTry(responseSource1->readHeaderEvent());\n  EXPECT_FALSE(headerEvent.hasException());\n  auto bodyEvent =\n      co_await co_awaitTry(readBodyEventNoSuspend(*responseSource1));\n  EXPECT_FALSE(bodyEvent.hasException());\n  bodyEvent = co_await co_awaitTry(readBodyEventNoSuspend(*responseSource1));\n  EXPECT_TRUE(bodyEvent.hasException());\n  session_->initiateDrain();\n  co_await expectResponse(std::move(*responseSource2), 200, 100, true);\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, FreeConnFlowControlAfterRst) {\n  // After a stream is reset and state is gone, connection flow control is\n  // still released.\n\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  // Send response headers\n  auto streamID = *responseSource->getStreamID();\n  serializeResponse(streamID, 200, nullptr, false);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_FALSE(headerEvent.hasException());\n\n  // Abandon and wait for stream to abort and detach state\n  responseSource->stopReading();\n  co_await rescheduleN(1);\n\n  // Send some body\n  serverCodec_->generateBody(writeBuf_,\n                             streamID,\n                             makeBuf(http2::kInitialWindow / 2),\n                             HTTPCodec::NoPadding,\n                             true);\n  transport_->addReadEvent(streamID, writeBuf_.move(), true);\n  co_await rescheduleN(2);\n\n  // The session should release this flow control immediately\n  EXPECT_CALL(callbacks_, onWindowUpdate(_, http2::kInitialWindow / 2));\n  parseOutputUniplex();\n}\n\nCO_TEST_P_X(H2UpstreamSessionTest, EgressWhileWritesBlocked) {\n  co_await folly::coro::co_reschedule_on_current_executor;\n  auto uniplexTransport = static_cast<TestUniplexTransport *>(transport_);\n  // Preface/SETTINGS\n  EXPECT_EQ(transportState_.writeEvents.size(), 1);\n  auto responseSource1 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource1.hasException());\n  // Pause writes and let the egress go to the writeBuf\n  transport_->pauseWrites(0);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Headers are present for first req\n  EXPECT_EQ(transportState_.writeEvents.size(), 2);\n  auto responseSource2 = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Headers for second req are in writeBuf_ but not in transport\n  EXPECT_EQ(transportState_.writeEvents.size(), 2);\n  uniplexTransport->resumeWrites();\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Headers for second req are in transport\n  EXPECT_EQ(transportState_.writeEvents.size(), 3);\n  session_->initiateDrain();\n}\n\nTEST_P(HTTPUpstreamSessionTest, SessionCancelledNoStreams) {\n  cancellationSource_.requestCancellation();\n  evb_.loop();\n  if (isHQ()) {\n    // Questionnable that this results in a connection error\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  }\n  expectH1ConnectionReset();\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, SessionCancelledWithStream) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  cancellationSource_.requestCancellation();\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  if (isHQ()) {\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  }\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nTEST_P(HTTPUpstreamSessionTest, SessionDropNoStreams) {\n  session_->dropConnection();\n  evb_.loopOnce();\n  if (isHQ()) {\n    loopN(3);\n    // Questionnable that this results in a connection error\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  }\n  expectH1ConnectionReset();\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, SessionDropWithStream) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  session_->dropConnection();\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  co_await folly::coro::co_reschedule_on_current_executor;\n  if (isHQ()) {\n    co_await rescheduleN(3);\n    expectedError_ = TransportErrorCode::NETWORK_ERROR;\n  }\n  expectH1ConnectionReset();\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, StreamIngressCompleteNoRST) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  auto streamID = *responseSource->getStreamID();\n  serializeResponse(streamID, 200, makeBuf(100), true);\n  session_->initiateDrain();\n  // Suspend this coro so the session reads the response\n  co_await folly::coro::co_reschedule_on_current_executor;\n  responseSource->stopReading();\n  // TODO: verify no RST_STREAM is sent\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, CancelBeforeSendingHeaders) {\n  folly::CancellationSource cancelSource;\n  auto coro = [this]() -> folly::coro::Task<void> {\n    auto req = std::make_unique<HTTPMessage>(getPostRequest(10));\n    auto responseSource = co_await co_awaitTry(\n        session_->sendRequest(new TimeoutSource(std::move(req),\n                                                /*timeoutHeaders=*/true,\n                                                /*errorOnCancel=*/false)));\n    EXPECT_TRUE(responseSource.hasException());\n    EXPECT_EQ(getHTTPError(responseSource).code, HTTPErrorCode::CORO_CANCELLED);\n  };\n  co_withExecutor(\n      &evb_, folly::coro::co_withCancellation(cancelSource.getToken(), coro()))\n      .start();\n  co_await folly::coro::co_reschedule_on_current_executor;\n  cancelSource.requestCancellation();\n  session_->initiateDrain();\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, CancelDuringPostBody) {\n  folly::CancellationSource cancelSource;\n  auto coro = [this]() -> folly::coro::Task<void> {\n    auto req = std::make_unique<HTTPMessage>(getPostRequest(10));\n    auto responseSource = co_await co_awaitTry(session_->sendRequest(\n        new TimeoutSource(std::move(req), /*timeoutHeaders=*/false)));\n    EXPECT_FALSE(responseSource.hasException());\n    auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n    EXPECT_TRUE(headerEvent.hasException());\n    auto err = getHTTPError(headerEvent);\n    // the read coro was cancelled\n    EXPECT_EQ(err.code, HTTPErrorCode::CORO_CANCELLED);\n    EXPECT_TRUE(err.msg.ends_with(\"Read cancelled\"));\n  };\n  co_withExecutor(\n      &evb_, folly::coro::co_withCancellation(cancelSource.getToken(), coro()))\n      .start();\n  co_await folly::coro::co_reschedule_on_current_executor;\n  cancelSource.requestCancellation();\n  session_->initiateDrain();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, SupportsMoreTransactions) {\n  EXPECT_TRUE(session_->supportsMoreTransactions());\n  session_->setMaxConcurrentOutgoingStreams(1);\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  EXPECT_FALSE(responseSource.hasException());\n  EXPECT_FALSE(session_->supportsMoreTransactions());\n  serializeResponse(*responseSource->getStreamID(), 200, makeBuf(100), true);\n  co_await expectResponse(std::move(*responseSource), 200, 100, true);\n  EXPECT_TRUE(session_->supportsMoreTransactions());\n  session_->initiateDrain();\n  EXPECT_FALSE(session_->supportsMoreTransactions());\n}\n\nCO_TEST_P_X(HTTPUpstreamSessionTest, SendRequestHeadersAvailable) {\n  NiceMock<MockHTTPSource> reqSource;\n  EXPECT_CALL(reqSource, readHeaderEvent).Times(0);\n  EXPECT_CALL(reqSource, readBodyEvent(_)).WillOnce([&]() {\n    return folly::coro::makeTask(HTTPBodyEvent{makeBuf(1500), /*inEOM=*/true});\n  });\n  EXPECT_CALL(reqSource, stopReading(_)).Times(0);\n\n  HTTPHeaderEvent headerEv{\n      std::make_unique<HTTPMessage>(getPostRequest(/*contentLength=*/1500)),\n      /*inEOM=*/false};\n  auto reservation = session_->reserveRequest().value();\n  auto res = session_->sendRequest(\n      std::move(reservation), *headerEv.headers, &reqSource);\n  XCHECK(!res.hasError());\n\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  serializeResponse(res->getStreamID().value(), 200, makeBuf(100), true);\n  co_await HTTPSourceReader{std::move(*res)}.read();\n\n  EXPECT_CALL(lifecycleObs_, onTransactionDetached(_));\n  reservation = session_->reserveRequest().value();\n  // expect error if ::sendRequest during drain\n  session_->initiateDrain();\n  res =\n      session_->sendRequest(std::move(reservation), *headerEv.headers, nullptr);\n  EXPECT_TRUE(res.hasError());\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, DrainStartOnReset) {\n  // When h1 resets a stream, make sure it invokes the drain started callback\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  EXPECT_CALL(lifecycleObs_, onDrainStarted);\n  responseSource->stopReading();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\nCO_TEST_P_X(H12UpstreamSessionTest, CloseOnResponseWithoutRequest) {\n  serializeResponse(1, 200, makeBuf(100), true);\n  {\n    EXPECT_CALL(lifecycleObs_, onDrainStarted);\n    co_await folly::coro::co_reschedule_on_current_executor;\n  }\n  if (IS_H1()) {\n    onTearDown([this] { expectH1ConnectionReset(); });\n  } else {\n    co_await folly::coro::co_reschedule_on_current_executor;\n    EXPECT_CALL(callbacks_, onGoaway(_, ErrorCode::PROTOCOL_ERROR, _));\n    parseOutputUniplex();\n  }\n}\n\nCO_TEST_P_X(H1UpstreamSessionTest, TrailingGarbage) {\n  // Send an incomplete request by holding the EOM until the response\n  // headers come back.  There's a second response on the wire, which triggers\n  // a connection error, but the body and EOM have been received so they are\n  // delivered.\n  folly::coro::Baton baton;\n  auto onEomRequestSource = new OnEOMSource(\n      HTTPFixedSource::makeFixedRequest(\"/\", HTTPMethod::POST, makeBuf(10)),\n      [&baton]() -> OnEOMSource::CallbackReturn {\n        co_await baton;\n        co_return folly::none;\n      });\n  auto responseSource =\n      co_await co_awaitTry(session_->sendRequest(onEomRequestSource));\n  XCHECK(!responseSource.hasException());\n  auto resp = makeResponse(200, true); // eom=true prevents te: chunked\n  resp.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"10\");\n  serializeResponseHeader(\n      *responseSource->getStreamID(), std::move(resp), false);\n  writeBuf_.append(makeBuf(10));\n\n  serializeResponseHeader(\n      *responseSource->getStreamID() + 1, makeResponse(200), false);\n  transport_->addReadEvent(\n      *responseSource->getStreamID(), writeBuf_.move(), false);\n\n  HTTPSourceReader reader(std::move(*responseSource));\n  reader\n      .onHeaders([&baton](std::unique_ptr<HTTPMessage>, bool, bool) {\n        baton.post();\n        return HTTPSourceReader::Continue;\n      })\n      .onBody([](BufQueue body, bool eom) {\n        EXPECT_EQ(body.chainLength(), 10);\n        EXPECT_TRUE(eom);\n        return HTTPSourceReader::Continue;\n      });\n  co_await reader.read();\n  onTearDown([this] { expectH1ConnectionReset(); });\n}\n\n// H3 specific tests\nCO_TEST_P_X(HQUpstreamSessionTest, CreateBidiStreamFailure) {\n  // reserve request successfully\n  auto reservation = session_->reserveRequest();\n  XCHECK(reservation.hasValue());\n\n  // make next call to QuicSocket::createBidirectionalStream fail\n  EXPECT_CALL(*muxTransport_->socketDriver_.sock_, createBidirectionalStream(_))\n      .WillOnce(Return(\n          quic::make_unexpected(quic::LocalErrorCode::STREAM_LIMIT_EXCEEDED)));\n\n  // ::sendRequest should fail accordingly\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n\n  // verify exception yielded\n  EXPECT_TRUE(responseSource.hasException());\n  auto ex = CHECK_NOTNULL(responseSource.tryGetExceptionObject<HTTPError>());\n  EXPECT_EQ(ex->code, HTTPErrorCode::REFUSED_STREAM);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, CreateControlStreamFail) {\n  // Kill the default session\n  session_->closeWhenIdle();\n\n  // Make a new muxTransport with no uni credit\n  TestCoroMultiplexTransport muxTransport(&evb_, direction_);\n  transport_ = &muxTransport;\n  muxTransport.socketDriver_.setMaxUniStreams(0);\n  auto codec = std::make_unique<hq::HQMultiCodec>(direction_);\n  wangle::TransportInfo tinfo;\n  auto session = HTTPCoroSession::makeUpstreamCoroSession(\n      muxTransport.getSocket(), std::move(codec), std::move(tinfo));\n  NiceMock<MockLifecycleObserver> infoCb;\n  session->addLifecycleObserver(&infoCb);\n  folly::coro::co_withCancellation(cancellationSource_.getToken(),\n                                   session->run())\n      .start();\n  EXPECT_CALL(infoCb, onDestroy(_));\n\n  auto responseSource = co_await co_awaitTry(\n      session->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  // This request never goes through\n  EXPECT_TRUE(responseSource.hasException());\n  co_await rescheduleN(4);\n  EXPECT_EQ(HTTP3::ErrorCode(*muxTransport.socketDriver_.getConnErrorCode()),\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, StopSending) {\n  auto req = std::make_unique<HTTPMessage>(getPostRequest(100));\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(new TimeoutSource(std::move(req))));\n  XCHECK(!responseSource.hasException());\n  // Server asks client to stop sending.  Cancels the egress coro\n  auto id = *responseSource->getStreamID();\n  muxTransport_->socketDriver_.addStopSending(\n      id, HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD);\n  // Now close the ingress\n  resetStream(id, ErrorCode::PROTOCOL_ERROR);\n  auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::GENERAL_PROTOCOL_ERROR);\n  session_->initiateDrain();\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, StopSendingEgressComplete) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\n          \"/\", HTTPMethod::POST, makeBuf(70000))));\n  XCHECK(!responseSource.hasException());\n  // Server asks client to stop sending.  Egress coro is already complete\n  auto id = *responseSource->getStreamID();\n  muxTransport_->socketDriver_.addStopSending(\n      id, HTTP3::ErrorCode::HTTP_EXCESSIVE_LOAD);\n  // Now close the ingress\n  resetStream(id, ErrorCode::REFUSED_STREAM);\n  auto resp = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(resp.hasException());\n  EXPECT_EQ(getHTTPError(resp).code, HTTPErrorCode::REQUEST_REJECTED);\n  session_->initiateDrain();\n  // Grant enough conn FCW for the GOAWAY and QPACK stream\n  muxTransport_->socketDriver_.setConnectionFlowControlWindow(5);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, CancelWithStopSending) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  co_await folly::coro::co_reschedule_on_current_executor;\n  responseSource->stopReading();\n  serializeResponse(id, 200, makeBuf(100), true);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  session_->initiateDrain();\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, QPACKQueuedOnClose) {\n  // It takes 2 loops for the encoder stream to get established\n  co_await rescheduleN(2);\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  auto resp = makeResponse(200);\n  resp.getHeaders().add(\"Dynamic\", \"Header\");\n  // flush the header but not the control data\n  serializeResponseHeader(id, std::move(resp), false, false);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(100), HTTPCodec::NoPadding, true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  // Add connection end\n  transport_->addReadEvent(nullptr, true);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  EXPECT_EQ(getHTTPError(headerEvent).code,\n            HTTPErrorCode::QPACK_DECOMPRESSION_FAILED);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, DrainSessionOnConnectionError) {\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  // ::onConnectionError callback should invoke ::onDrainStarted\n  EXPECT_CALL(lifecycleObs_, onDrainStarted(_));\n  muxTransport_->socketDriver_.closeImpl(std::nullopt);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, QPACKQueuedOnCloseNoEncoderStream) {\n  // Don't wait for the encoder stream to be established\n  auto responseSource = co_await co_awaitTry(\n      session_->sendRequest(HTTPFixedSource::makeFixedRequest(\"/\")));\n  XCHECK(!responseSource.hasException());\n  auto id = *responseSource->getStreamID();\n  auto resp = makeResponse(200);\n  resp.getHeaders().add(\"Dynamic\", \"Header\");\n  // flush the header but not the control data\n  serializeResponseHeader(id, std::move(resp), false, false);\n  serverCodec_->generateBody(\n      writeBuf_, id, makeBuf(100), HTTPCodec::NoPadding, true);\n  transport_->addReadEvent(id, writeBuf_.move(), true);\n  // Add connection end\n  transport_->addReadEvent(nullptr, true);\n  auto headerEvent = co_await co_awaitTry(responseSource->readHeaderEvent());\n  EXPECT_TRUE(headerEvent.hasException());\n  EXPECT_EQ(getHTTPError(headerEvent).code,\n            HTTPErrorCode::QPACK_DECOMPRESSION_FAILED);\n}\n\nCO_TEST_P_X(HQUpstreamSessionTest, LargeRequest) {\n  if (folly::kIsDebug) { // skip test in debug modes; will take too long to run\n    co_return;\n  }\n  // reserve request successfully\n  auto reservation = session_->reserveRequest();\n  XCHECK(reservation.hasValue());\n\n  struct SourceCallback : public HTTPStreamSource::Callback {\n    void bytesProcessed(HTTPCodec::StreamID, size_t, size_t) override {\n      b.post();\n      b.reset();\n    }\n    void sourceComplete(HTTPCodec::StreamID,\n                        folly::Optional<HTTPError>) override {\n      done = true;\n    }\n    folly::coro::Baton b;\n    bool done{false};\n  } sourceCallback;\n  HTTPStreamSource reqSource{&evb_, folly::none, &sourceCallback};\n\n  constexpr uint32_t kReqSize =\n      (1ull << 16) + std::numeric_limits<int32_t>::max();\n  reqSource.headers(makePostRequest(kReqSize));\n\n  auto responseSource = co_await co_awaitTry(session_->sendRequest(&reqSource));\n  XCHECK(responseSource.hasValue());\n\n  uint32_t egressed = 0;\n  constexpr auto kChunkSize = std::numeric_limits<uint16_t>::max();\n  while (!sourceCallback.done) {\n    uint32_t chunkSize = std::min<uint32_t>(kReqSize - egressed, kChunkSize);\n    egressed += chunkSize;\n    reqSource.body(\n        makeBuf(chunkSize), /*padding=*/0, /*eom=*/egressed == kReqSize);\n    // simulate window updates\n    windowUpdate(chunkSize);\n    windowUpdate(0, chunkSize);\n    co_await sourceCallback.b;\n  }\n  while (!sourceCallback.done) {\n    co_await folly::coro::co_reschedule_on_current_executor;\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    HTTPUpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1}),\n           TestParams({.codecProtocol = CodecProtocol::HTTP_2}),\n           TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    H1UpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    H2UpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    HQUpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    H2QUpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2}),\n           TestParams({.codecProtocol = CodecProtocol::HQ})),\n    paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPUpstreamSessionTest,\n    H12UpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_1_1}),\n           TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/HttpWtUpstreamTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n#include \"proxygen/lib/http/coro/client/test/HTTPClientTestsCommon.h\"\n#include \"proxygen/lib/http/coro/test/HTTPCoroSessionTests.h\"\n#include \"proxygen/lib/http/coro/test/Mocks.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n#include <proxygen/lib/http/coro/util/CoroWtSession.h>\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n\nusing namespace proxygen;\nusing namespace proxygen::test;\nusing namespace testing;\nusing namespace proxygen::detail;\n\nusing folly::coro::blockingWait;\nusing folly::coro::co_awaitTry;\nusing folly::coro::co_reschedule_on_current_executor;\n\nnamespace proxygen::coro::test {\n\nconstexpr auto kWtSettings = {SettingsId::ENABLE_CONNECT_PROTOCOL,\n                              SettingsId::WT_MAX_SESSIONS};\n\nstruct WtCapsuleCodecCallback : public WebTransportCapsuleCodec::Callback {\n  ~WtCapsuleCodecCallback() override = default;\n  void onWTResetStreamCapsule(WTResetStreamCapsule capsule) noexcept override {\n    rst.emplace(capsule);\n    baton.post();\n  }\n  void onWTStopSendingCapsule(WTStopSendingCapsule capsule) noexcept override {\n    ss.emplace(capsule);\n    baton.post();\n  }\n  void onWTStreamCapsule(WTStreamCapsule capsule) noexcept override {\n    XLOG(DBG4)\n        << __func__ << \"; id=\" << capsule.streamId << \"; len=\"\n        << (capsule.streamData ? capsule.streamData->computeChainDataLength()\n                               : 0);\n    stream.emplace(std::move(capsule));\n    baton.post();\n  }\n  void onWTMaxDataCapsule(WTMaxDataCapsule capsule) noexcept override {\n    md.emplace(capsule);\n    baton.post();\n  }\n  void onWTMaxStreamDataCapsule(\n      WTMaxStreamDataCapsule capsule) noexcept override {\n    msd.emplace(capsule);\n    baton.post();\n  }\n  void onWTMaxStreamsBidiCapsule(\n      WTMaxStreamsCapsule capsule) noexcept override {\n    bidiMaxStreams.emplace(capsule);\n    baton.post();\n  }\n  void onWTMaxStreamsUniCapsule(WTMaxStreamsCapsule capsule) noexcept override {\n    uniMaxStreams.emplace(capsule);\n    baton.post();\n  }\n\n  void onWTDataBlockedCapsule(WTDataBlockedCapsule) noexcept override {\n  }\n  void onWTStreamDataBlockedCapsule(\n      WTStreamDataBlockedCapsule) noexcept override {\n  }\n  void onPaddingCapsule(PaddingCapsule) noexcept override {\n  }\n  void onWTStreamsBlockedBidiCapsule(\n      WTStreamsBlockedCapsule) noexcept override {\n  }\n  void onWTStreamsBlockedUniCapsule(WTStreamsBlockedCapsule) noexcept override {\n  }\n  void onDatagramCapsule(DatagramCapsule) noexcept override {\n  }\n  void onCloseWTSessionCapsule(\n      CloseWebTransportSessionCapsule) noexcept override {\n  }\n  void onDrainWTSessionCapsule(\n      DrainWebTransportSessionCapsule) noexcept override {\n  }\n  void onConnectionError(\n      WebTransportCapsuleCodec::ErrorCode) noexcept override {\n    XLOG(FATAL) << \"conn error\";\n  }\n  void onCapsule(uint64_t capsuleType,\n                 uint64_t capsuleLength) noexcept override {\n    XLOG(DBG4) << __func__ << \"; capsuleType=\" << capsuleType\n               << \"; capsuleLength=\" << capsuleLength;\n  }\n\n  folly::coro::Task<void> waitForEvent() {\n    co_await baton;\n    baton.reset();\n  }\n\n  std::optional<CloseWebTransportSessionCapsule> close;\n  std::optional<DrainWebTransportSessionCapsule> drain;\n  std::optional<WTResetStreamCapsule> rst;\n  std::optional<WTStopSendingCapsule> ss;\n  std::optional<WTStreamCapsule> stream;\n  std::optional<WTMaxDataCapsule> md;\n  std::optional<WTMaxStreamDataCapsule> msd;\n  std::optional<WTMaxStreamsCapsule> bidiMaxStreams;\n  std::optional<WTMaxStreamsCapsule> uniMaxStreams;\n\n private:\n  folly::coro::Baton baton;\n};\n\nclass HttpWtUpstreamSessionTest : public HTTPCoroSessionTest {\n public:\n  HttpWtUpstreamSessionTest()\n      : HTTPCoroSessionTest(TransportDirection::UPSTREAM),\n        serverCodec_(peerCodec_.get()) {\n  }\n\n  void SetUp() override {\n    // need to set wt settings before HTTPCoroSession is constructed\n    initSelfCodec_ = [this](HTTPCodec&) { setWtSupport(true); };\n    HTTPCoroSessionTest::setUp();\n  }\n\n protected:\n  // hacks to enable/disable wt\n  std::array<HTTPSettings*, 2> getHttpSettings() {\n    return {const_cast<HTTPSettings*>(codec_->getIngressSettings()),\n            codec_->getEgressSettings()};\n  }\n\n  void setWtSupport(bool enabled) {\n    for (auto httpSettings : getHttpSettings()) {\n      for (const auto wtSetting : kWtSettings) {\n        httpSettings->setSetting(wtSetting, int(enabled));\n      }\n    }\n  }\n\n  // TODO(@damlaj): derive from HTTPUpstreamSessionTest to reduce code\n  // duplication\n  HTTPMessage makeResponse(uint16_t statusCode) {\n    HTTPMessage resp;\n    resp.setHTTPVersion(1, 1);\n    resp.setStatusCode(statusCode);\n    return resp;\n  }\n\n  void deliverRespHeaders(HTTPCodec::StreamID id,\n                          const HTTPMessage& resp,\n                          bool eom = true) {\n    serverCodec_->generateHeader(writeBuf_, id, resp, eom);\n    // @lint-ignore CLANGTIDY\n    transport_->addReadEvent(id, writeBuf_.move(), /*eof=*/false);\n  }\n\n  void deliverRstStream(HTTPCodec::StreamID id) {\n    serverCodec_->generateRstStream(writeBuf_, id, ErrorCode::CANCEL);\n    // @lint-ignore CLANGTIDY\n    transport_->addReadEvent(id, writeBuf_.move(), /*eof=*/false);\n  }\n\n  HTTPCodec* serverCodec_{nullptr};\n};\n\nusing H2WtUpstreamSessionTest = HttpWtUpstreamSessionTest;\n\nCO_TEST_P_X(H2WtUpstreamSessionTest, Simple) {\n  // valid wt req\n  HTTPMessage msg;\n  msg.setMethod(HTTPMethod::CONNECT);\n  msg.setUpgradeProtocol(\"webtransport\");\n\n  auto reservation = session_->reserveRequest();\n  auto fut =\n      co_withExecutor(&evb_,\n                      session_->sendWtReq(\n                          std::move(*reservation), msg, DummyWtHandler::make()))\n          .start();\n\n  MockLifecycleObserver obs;\n  session_->addLifecycleObserver(&obs);\n  folly::coro::Baton waitForHeaders;\n  EXPECT_CALL(obs, onIngressMessage(_, _)).WillOnce([&]() {\n    waitForHeaders.post();\n    session_->removeLifecycleObserver(&obs);\n  });\n\n  // serialize non-final 1xx continue\n  deliverRespHeaders(/*id=*/1, makeResponse(100), /*eom=*/false);\n  co_await waitForHeaders;     // wait for session to parse 1xx resp headers\n  co_await rescheduleN(2);     // for good measure\n  EXPECT_FALSE(fut.isReady()); // fut only resolves once final headers are rx'd\n\n  // serialize final 2xx\n  deliverRespHeaders(/*id=*/1, makeResponse(200), /*eom=*/true);\n  auto res = co_await co_awaitTry(std::move(fut));\n\n  EXPECT_TRUE(res->resp->is2xxResponse());\n  EXPECT_NE(res->wt, nullptr);\n  res->wt->closeSession(/*error=*/folly::none);\n}\n\nCO_TEST_P_X(H2WtUpstreamSessionTest, Non2xxResp) {\n  // valid wt req\n  HTTPMessage msg;\n  msg.setMethod(HTTPMethod::CONNECT);\n  msg.setUpgradeProtocol(\"webtransport\");\n\n  auto reservation = session_->reserveRequest();\n  auto fut =\n      co_withExecutor(&evb_,\n                      session_->sendWtReq(\n                          std::move(*reservation), msg, DummyWtHandler::make()))\n          .start();\n\n  // server rejecting CONNECT (i.e. sending 5xx) => res->wt == nullptr\n  deliverRespHeaders(/*id=*/1, makeResponse(500), /*eom=*/false);\n  auto res = co_await co_awaitTry(std::move(fut));\n  EXPECT_TRUE(res->resp->is5xxResponse());\n  EXPECT_EQ(res->wt, nullptr);\n}\n\nCO_TEST_P_X(H2WtUpstreamSessionTest, WtUpgradeReqRstErr) {\n  // receiving a rst_stream when waiting for 2xx should propagate the\n  // ::readHeaderEvent error\n  HTTPMessage msg;\n  msg.setMethod(HTTPMethod::CONNECT);\n  msg.setUpgradeProtocol(\"webtransport\");\n\n  auto reservation = session_->reserveRequest();\n  evb_.runAfterDelay([this]() { deliverRstStream(/*id=*/1); },\n                     /*milliseconds=*/50);\n  auto res = co_await co_awaitTry(session_->sendWtReq(\n      std::move(*reservation), msg, DummyWtHandler::make()));\n  auto* ex = res.tryGetExceptionObject<HTTPError>();\n  EXPECT_TRUE(ex && ex->code == HTTPErrorCode::CANCEL);\n}\n\nCO_TEST_P_X(H2WtUpstreamSessionTest, SendInvalidWtReq) {\n  HTTPMessage msg;\n  msg.setMethod(HTTPMethod::GET);\n\n  {\n    // invalid reservation\n    HTTPCoroSession::RequestReservation reservation;\n    auto res = co_await co_awaitTry(session_->sendWtReq(\n        std::move(reservation), msg, DummyWtHandler::make()));\n    auto* ex = res.tryGetExceptionObject<HTTPError>();\n    EXPECT_TRUE(ex && ex->code == HTTPErrorCode::INTERNAL_ERROR);\n  }\n\n  {\n    // invalid msg\n    auto reservation = session_->reserveRequest();\n    auto res = co_await co_awaitTry(session_->sendWtReq(\n        std::move(*reservation), msg, DummyWtHandler::make()));\n    auto* ex = res.tryGetExceptionObject<HTTPError>();\n    EXPECT_TRUE(ex && ex->code == HTTPErrorCode::INTERNAL_ERROR);\n  }\n\n  {\n    setWtSupport(false);\n    // unsupported webtransport (settings not set)\n    msg.setMethod(HTTPMethod::CONNECT);\n    msg.setUpgradeProtocol(\"webtransport\");\n    auto reservation = session_->reserveRequest();\n    auto res = co_await co_awaitTry(session_->sendWtReq(\n        std::move(*reservation), msg, DummyWtHandler::make()));\n    auto* ex = res.tryGetExceptionObject<HTTPError>();\n    EXPECT_TRUE(ex && ex->code == HTTPErrorCode::INTERNAL_ERROR);\n  }\n}\n\n// only http/2 WebTransport tests for now\nINSTANTIATE_TEST_SUITE_P(\n    HttpWtUpstreamSessionTest,\n    H2WtUpstreamSessionTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\n// unit tests where a wt session is already successfully established\nclass WtTest : public H2WtUpstreamSessionTest {\n public:\n  WtTest() = default;\n\n  void SetUp() override {\n    H2WtUpstreamSessionTest::SetUp();\n    loopN(2);\n    establishWtSession();\n\n    /**\n     * Every HTTPCoroSession upstream http/2 transport write is piped into the\n     * peer http/2 codec, which subsequently pipes every http/2 body chunk into\n     * WebTransportCapsuleCodec.\n     */\n    EXPECT_CALL(lifecycleObs_, onWrite(_, _)).WillRepeatedly([this]() {\n      peerCodec_->onIngress(*coalesceWrites());\n    });\n    EXPECT_CALL(callbacks_, onBody(1, _, _))\n        .WillRepeatedly([this](auto id, auto buf, auto padding) {\n          XLOG(DBG5) << \"::onBody len=\" << buf->computeChainDataLength();\n          wtCodec.onIngress(buf->clone(), false);\n        });\n    EXPECT_CALL(callbacks_, onMessageComplete(1, _)).WillRepeatedly([this]() {\n      XLOG(DBG5) << \"::onMessageComplete\";\n      wtCodec.onIngress(nullptr, true);\n    });\n    coroTp = CHECK_NOTNULL(dynamic_cast<TestCoroTransport*>(transport_));\n  }\n\n  void TearDown() override {\n    wt->closeSession(folly::none);\n  }\n\n  void establishWtSession() {\n    HTTPMessage msg;\n    msg.setMethod(HTTPMethod::CONNECT);\n    msg.setUpgradeProtocol(\"webtransport\");\n\n    auto handler = DummyWtHandler::make();\n    wtHandlerCtx = handler->ctx;\n\n    // send/serialize CONNECT request\n    auto reservation = session_->reserveRequest();\n    auto fut =\n        co_withExecutor(&evb_,\n                        session_->sendWtReq(\n                            std::move(*reservation), msg, std::move(handler)))\n            .start();\n    evb_.loopOnce(); // serialize request\n\n    // release 1mb of http/2 conn- and stream-level fc\n    constexpr uint32_t kInitWindowSize = 1ul << 20; // 1MiB\n    session_->onWindowUpdate(0, kInitWindowSize);\n    session_->onWindowUpdate(1, kInitWindowSize);\n\n    // serialize final 2xx\n    deliverRespHeaders(/*id=*/1, makeResponse(200), /*eom=*/false);\n    auto res = folly::coro::blockingWait(std::move(fut), &evb_);\n    wt = std::move(res.wt);\n    // EXPECT_TRUE(wtHandler->wtSession);\n  }\n\n  std::unique_ptr<folly::IOBuf> coalesceWrites() {\n    folly::IOBuf buf;\n    auto writeEvents = std::move(coroTp->state_->writeEvents);\n    for (auto& ev : writeEvents) {\n      buf.appendToChain(ev.move());\n    }\n    return buf.pop();\n  }\n\n  void deliverWtData(std::unique_ptr<folly::IOBuf> wtData, bool eom = false) {\n    peerCodec_->generateBody(writeBuf_,\n                             1,\n                             std::move(wtData),\n                             /*padding=*/folly::none,\n                             /*eom=*/eom);\n    // @lint-ignore CLANGTIDY\n    transport_->addReadEvent(1, writeBuf_.move(), /*eof=*/false);\n  }\n\n  void grantMaxData(uint64_t streamId, uint64_t offset) {\n    if (streamId == kInvalidVarint) {\n      writeWTMaxData(wtBuf, {offset});\n    } else {\n      writeWTMaxStreamData(wtBuf,\n                           {.streamId = streamId, .maximumStreamData = offset});\n    }\n    deliverWtData(wtBuf.move());\n  }\n\n  std::shared_ptr<WebTransport> wt;\n  // wtCodec & wtCodecCb are used for parsing capsules; simulating a peer\n  // receiving data from client\n  WtCapsuleCodecCallback wtCodecCb;\n  WebTransportCapsuleCodec wtCodec{&wtCodecCb, CodecVersion::H2};\n  TestCoroTransport* coroTp{nullptr};\n  std::shared_ptr<const DummyWtHandler::Ctx> wtHandlerCtx;\n  // wtBuf contains the serialize wt capsules; simulating a peer sending data\n  // to client\n  folly::IOBufQueue wtBuf{folly::IOBufQueue::cacheChainLength()};\n};\n\n/**\n * Tests the following egress related behaviours:\n *  - defaults to zero wt_initial_max_streams_uni/bidi; cannot create uni/bidi\n *    streams until we receive a max_streams capsule\n *\n *  - dequeues are blocked until connection- and stream-level flow\n *    control credit is received from peer\n *\n *  - writing only fin=true even if blocked on flow control should still go thru\n *\n *  - backpressure signal is irrelevant of peer fc credit and is resolved after\n *    the http/2 loop has dequeued from WtStreamManager\n */\nCO_TEST_P_X(WtTest, SimpleUniEgress) {\n  XCHECK(wt);\n\n  // no available uni/bidi streams\n  auto createStream = wt->createUniStream();\n  EXPECT_TRUE(createStream.hasError());\n\n  // asynchronously advertise max_streams\n  evb_.runInLoop([&]() {\n    writeWTMaxStreams(wtBuf, /*capsule=*/{1}, /*isBidi=*/false);\n    deliverWtData(wtBuf.move());\n  });\n\n  co_await wt->awaitUniStreamCredit();\n\n  // next awaitUniStreamCredit should be synchronously available\n  wt->awaitUniStreamCredit().isReady();\n\n  // peer advertised uni credit => ::createUniStream now yields handle\n  createStream = wt->createUniStream();\n  CHECK(createStream.hasValue());\n  auto* wh = createStream.value();\n  auto id = wh->getID();\n\n  // fill up egress buffer => writes blocked\n  constexpr uint16_t kBufLen = 65'535;\n  auto writeRes =\n      wt->writeStreamData(id, makeBuf(kBufLen), /*fin=*/false, nullptr);\n  EXPECT_EQ(writeRes.value(), WebTransport::FCState::BLOCKED);\n\n  // awaitWritable is only resolved after the data has been dequeued from\n  // WtStreamManager\n  auto awaitWritable = wt->awaitWritable(id);\n  EXPECT_FALSE(awaitWritable->isReady());\n\n  // asynchronously advertise max_data (default peer value when not present in\n  // http settings is assumed to be 0)\n  evb_.runInLoop([&]() {\n    // grant conn- and stream-level fc\n    grantMaxData(kInvalidVarint, kBufLen);\n    grantMaxData(id, kBufLen);\n  });\n\n  // wait for wt capsule codec callback to fire\n  co_await wtCodecCb.waitForEvent();\n\n  // validate we've rx'd a wt_stream capsule with expected values\n  auto streamEvent = std::exchange(wtCodecCb.stream, {});\n  XCHECK(streamEvent.has_value());\n  EXPECT_EQ(streamEvent->streamId, id);\n  EXPECT_EQ(streamEvent->streamData->computeChainDataLength(), kBufLen);\n\n  // when WtStreamManager dequeued the buffered data, awaitWritable is\n  // resolved\n  EXPECT_TRUE(awaitWritable->isReady() && awaitWritable->value() == kBufLen);\n\n  // blocked on both connection- and stream-level fc; buffered data will not be\n  // dequeued from WtStreamManager\n  wt->writeStreamData(\n      id, makeBuf(1), /*fin=*/false, /*deliveryCallback=*/nullptr);\n\n  co_await rescheduleN(4);\n  EXPECT_FALSE(wtCodecCb.stream.has_value()); // no data has been written\n\n  // release 1 byte of stream-level wt fc; buffered data will still not be\n  // dequeued from WtStreamManager as we're still blocked on connection-level fc\n  grantMaxData(id, kBufLen + 1);\n  co_await rescheduleN(5);\n  EXPECT_FALSE(wtCodecCb.stream.has_value()); // no data has been written\n\n  // release 1 byte of connection-level wt fc; buffered data will be dequeued\n  // from WtStreamManager\n  grantMaxData(kInvalidVarint, kBufLen + 1);\n  co_await wtCodecCb.waitForEvent(); // ouch, this needs 5 loops otherwise\n                                     // (i.e. co_await rescheduleN(5))\n  streamEvent = std::exchange(wtCodecCb.stream, {});\n  XCHECK(streamEvent.has_value());\n  EXPECT_EQ(streamEvent->streamId, id);\n  EXPECT_EQ(streamEvent->streamData->computeChainDataLength(), 1);\n\n  // blocked on both connection- and stream-level fc; however if just fin=true,\n  // this should be dequeued regardless of peer fc credit\n  wt->writeStreamData(id, nullptr, /*fin=*/true, /*deliveryCallback=*/nullptr);\n\n  co_await wtCodecCb.waitForEvent();\n  streamEvent = std::exchange(wtCodecCb.stream, {});\n  XCHECK(streamEvent.has_value());\n  EXPECT_EQ(streamEvent->streamId, id);\n  EXPECT_EQ(streamEvent->streamData->computeChainDataLength(), 0);\n  EXPECT_EQ(streamEvent->fin, true);\n\n  co_return;\n}\n\n/**\n * Tests the following ingress related behaviours:\n *  - Receiving data on a new stream id implicitly creates the data\n *\n *  - When bytes read have exceeded half the conn- and stream-level flow control\n *    advertised to the peer, the endpoint sends a max_data & max_stream_data\n *    frame respectively\n */\nCO_TEST_P_X(WtTest, SimpleUniIngress) {\n  XCHECK(wt);\n  constexpr uint16_t kBufLen = 65'535;\n  constexpr uint16_t kIngressId = 3;\n\n  // send kBufLen / 2 bytes of data, ensure client is able to read\n  writeWTStream(wtBuf,\n                WTStreamCapsule{.streamId = kIngressId,\n                                .streamData = makeBuf(kBufLen / 2 + 1),\n                                .fin = false});\n  deliverWtData(wtBuf.move());\n\n  // wait until we get a peer stream\n  while (wtHandlerCtx->peerStreams.empty()) {\n    co_await folly::coro::co_reschedule_on_current_executor;\n  }\n  // ingress only => writeHandle == nullptr\n  auto handle = wtHandlerCtx->peerStreams.at(0);\n  EXPECT_FALSE(handle.writeHandle);\n  EXPECT_TRUE(handle.readHandle);\n\n  auto read = wt->readStreamData(kIngressId);\n  EXPECT_TRUE(read->isReady());\n  EXPECT_EQ(read->value().fin, false);\n  EXPECT_EQ(read->value().data->computeChainDataLength(), kBufLen / 2 + 1);\n\n  // consuming half of advertised rwnd issues MaxData & MaxStreamData to peer\n  co_await wtCodecCb.waitForEvent();\n  EXPECT_TRUE(wtCodecCb.md.has_value() && wtCodecCb.msd.has_value());\n\n  // when receiving a rst_stream, read should resolve an exc\n  read = wt->readStreamData(kIngressId);\n  EXPECT_TRUE(read.hasValue());\n\n  // asynchronously deliver reset stream\n  evb_.runInLoop([&]() {\n    writeWTResetStream(wtBuf,\n                       {/*streamId=*/.streamId = kIngressId,\n                        .appProtocolErrorCode = 0,\n                        .reliableSize = 0});\n    deliverWtData(wtBuf.move());\n  });\n\n  // expect read resolves with exception due to rst above\n  auto readRes = co_await co_awaitTry(std::move(read).value());\n  EXPECT_TRUE(readRes.hasException());\n\n  co_return;\n}\n\nCO_TEST_P_X(WtTest, SimpleBidiEcho) {\n  XCHECK(wt);\n  constexpr uint16_t kBufLen = 65'535;\n\n  // no available bidi streams\n  auto createStream = wt->createBidiStream();\n  EXPECT_TRUE(createStream.hasError());\n\n  // asynchronously advertise max_streams\n  evb_.runInLoop([&]() {\n    writeWTMaxStreams(wtBuf, /*capsule=*/{1}, /*isBidi=*/true);\n    deliverWtData(wtBuf.move());\n  });\n\n  co_await wt->awaitBidiStreamCredit();\n\n  // next awaitBidiStreamCredit should be synchronously available\n  EXPECT_TRUE(wt->awaitBidiStreamCredit().isReady());\n\n  // peer advertised bidi credit => ::createBidiStream now yields handle\n  createStream = wt->createBidiStream();\n  CHECK(createStream.hasValue());\n  auto handle = createStream.value();\n  auto id = handle.readHandle->getID();\n\n  // asynchronously advertise MaxData & MaxStreamData\n  evb_.runInLoop([&]() {\n    grantMaxData(kInvalidVarint, kBufLen);\n    grantMaxData(id, kBufLen);\n  });\n\n  // asynchronously deliver varius wt frames to exercise codepaths\n  evb_.runInLoop([&]() {\n    writeWTDataBlocked(wtBuf, {kBufLen});\n    writeWTStreamDataBlocked(\n        wtBuf, {/*streamId=*/.streamId = 0, .maximumStreamData = kBufLen});\n    writeWTStreamsBlocked(wtBuf, {10}, /*isBidi=*/false);\n    writeWTStreamsBlocked(wtBuf, {10}, /*isBidi=*/true);\n    writePadding(wtBuf, {10});\n    writeDrainWebTransportSession(wtBuf);\n    deliverWtData(wtBuf.move());\n  });\n\n  /**\n   * in a loop – write one byte, wait for peer codec to parse byte, send the\n   * byte back, and finally read the byte.\n   */\n  for (uint8_t idx = 0; idx < std::numeric_limits<uint8_t>::max(); idx++) {\n    // write idx to stream\n    auto buf = folly::IOBuf::copyBuffer(&idx, sizeof(idx));\n    wt->writeStreamData(\n        id, buf->clone(), /*fin=*/false, /*deliveryCallback=*/nullptr);\n\n    // wait for peer codec to receive event\n    co_await wtCodecCb.waitForEvent();\n\n    // validate we've rx'd a wt_stream capsule with val idx\n    auto streamEvent = std::exchange(wtCodecCb.stream, {});\n    XCHECK(streamEvent.has_value());\n    EXPECT_EQ(streamEvent->streamId, id);\n    EXPECT_EQ(streamEvent->streamData->length(), 1);\n    EXPECT_EQ(*streamEvent->streamData->data(), idx);\n\n    // send the same byte back to client\n    writeWTStream(wtBuf,\n                  WTStreamCapsule{.streamId = id,\n                                  .streamData = buf->clone(),\n                                  .fin = false});\n    deliverWtData(wtBuf.move());\n\n    // expect to client to rx same byte\n    auto read = co_await wt->readStreamData(id).value();\n    EXPECT_EQ(*read.data->data(), idx);\n  }\n\n  // deliver both stop_sending & rst_stream, which should bidirectionally reset\n  // the stream (tbd – should we wait for app to specifically invoke\n  // ::resetStream before reaping state)\n  evb_.runInLoop([&]() {\n    writeWTStopSending(wtBuf, {.streamId = id});\n    writeWTResetStream(\n        wtBuf, {.streamId = id, .appProtocolErrorCode = 0, .reliableSize = 0});\n    writeCloseWebTransportSession(\n        wtBuf,\n        {.applicationErrorCode = 0, .applicationErrorMessage = \"close wt\"});\n    deliverWtData(wtBuf.move());\n  });\n\n  // stream is reset, ::read will return an exception\n  auto read = co_await co_awaitTry(wt->readStreamData(id).value());\n  EXPECT_TRUE(read.hasException());\n\n  wt->closeSession();\n}\n\nTEST_P(WtTest, TestErrConditions) {\n  XCHECK(wt);\n\n  // default no uni credit\n  auto uniRes = wt->createUniStream();\n  EXPECT_TRUE(uniRes.hasError());\n  // default no bidi credit\n  auto bidiRes = wt->createBidiStream();\n  EXPECT_TRUE(bidiRes.hasError());\n\n  // advertise one uni&bidi stream credit to client\n  writeWTMaxStreams(wtBuf, /*capsule=*/{1}, /*isBidi=*/false);\n  writeWTMaxStreams(wtBuf, /*capsule=*/{1}, /*isBidi=*/true);\n  deliverWtData(wtBuf.move());\n  loopN(2);\n\n  /**\n   * stream 0 doesn't exist; all ops expected to fail (e.g. write, read, reset,\n   * stop_sending)\n   */\n  constexpr uint64_t streamId = 0;\n  auto read = wt->readStreamData(streamId);\n  auto write = wt->writeStreamData(\n      streamId, /*data=*/nullptr, /*fin=*/false, /*deliveryCallback=*/nullptr);\n  auto reset = wt->resetStream(streamId, /*error=*/0);\n  auto ss = wt->stopSending(streamId, /*error=*/0);\n  auto await = wt->awaitWritable(streamId);\n\n  EXPECT_TRUE(read.hasError() && write.hasError() && reset.hasError() &&\n              ss.hasError() && await.hasError());\n\n  // quic transport info is defaulted\n  std::ignore = wt->getTransportInfo();\n\n  // local & peer addr sanity checks\n  const auto& localAddr = wt->getLocalAddress();\n  const auto& peerAddr = wt->getPeerAddress();\n  EXPECT_EQ(localAddr.getIPAddress(), peerAddr.getIPAddress());\n}\n\nCO_TEST_P_X(WtTest, PeerBidiAndTransportEom) {\n  // Deliver a peer-initiated bidi stream, followed by a transport eom (http/2\n  // stream eom). This should trigger shutdown of WebTransport\n  XCHECK(wt);\n\n  constexpr uint16_t kBufLen = 65'535;\n  constexpr uint16_t kIngressId = 1;\n\n  // deliver a peer-initiated bidi stream of len=kBufLen & eom=false\n  writeWTStream(wtBuf,\n                WTStreamCapsule{.streamId = kIngressId,\n                                .streamData = makeBuf(kBufLen),\n                                .fin = false});\n  deliverWtData(wtBuf.move());\n\n  // wait until client receives stream\n  while (wtHandlerCtx->peerStreams.empty()) {\n    co_await rescheduleN(1);\n  }\n\n  // ensure it's recognized as bidi stream\n  auto& handle = wtHandlerCtx->peerStreams.at(0);\n  EXPECT_TRUE(handle.readHandle && handle.writeHandle);\n\n  // data should be available synchronously\n  auto read = wt->readStreamData(kIngressId);\n  EXPECT_TRUE(read->isReady() && read->value().data &&\n              read->value().fin == false);\n\n  // next read will resolve with an error after http/2 stream eom is parsed\n  read = wt->readStreamData(kIngressId);\n\n  // deliver http/2 stream eom\n  deliverWtData(/*wtData=*/nullptr, /*eom=*/true);\n\n  // wait until WebTransportHandler::onSessionEnd is invoked\n  while (!wtHandlerCtx->err.has_value()) {\n    co_await rescheduleN(1);\n  }\n\n  // read should have an exception now\n  EXPECT_TRUE(read->hasException());\n}\n\nCO_TEST_P_X(WtTest, MaxStreamsBidiUni) {\n  // open 5 of each peer-initiated uni streams and peer-initiated bidi streams;\n  // close them to verify that advertising MaxStreamsBidi/MaxStreamsUni is\n  // working as expected (when 50% of the limit is reached)\n  XCHECK(wt);\n  uint64_t nextServerBidi = 0x01, nextServerUni = 0x03;\n\n  // send 1 byte of data on the first five server bidi and server uni streams\n  for (uint8_t idx = 0; idx < 5; idx++) {\n    writeWTStream(wtBuf,\n                  WTStreamCapsule{.streamId = nextServerBidi,\n                                  .streamData = makeBuf(1),\n                                  .fin = false});\n    writeWTStream(wtBuf,\n                  WTStreamCapsule{.streamId = nextServerUni,\n                                  .streamData = makeBuf(1),\n                                  .fin = false});\n    nextServerBidi += 4;\n    nextServerUni += 4;\n  }\n  deliverWtData(wtBuf.move());\n\n  // wait until client receives stream\n  while (wtHandlerCtx->peerStreams.empty()) {\n    co_await rescheduleN(1);\n  }\n\n  // bidirectionally terminate each stream\n  for (auto& handle : wtHandlerCtx->peerStreams) {\n    // read handle is unconditional for peer-initiated streams\n    CHECK_NOTNULL(handle.readHandle)->stopSending(0);\n    if (handle.writeHandle) {\n      handle.writeHandle->resetStream(0);\n    }\n  }\n\n  // wait for peer codec to receive event\n  co_await wtCodecCb.waitForEvent();\n\n  EXPECT_EQ(wtCodecCb.bidiMaxStreams->maximumStreams, 15);\n  EXPECT_EQ(wtCodecCb.uniMaxStreams->maximumStreams, 15);\n\n  wt->closeSession();\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    WtTest,\n    WtTest,\n    Values(TestParams({.codecProtocol = CodecProtocol::HTTP_2})),\n    paramsToTestName);\n\nclass HttpStreamTransport : public Test {\n public:\n  void SetUp() override {\n    // precondition is that headers have already been produced via\n    // egress source\n    detail::EgressSourcePtr egress{new detail::EgressSource(&evb)};\n    egressSource = egress.get();\n    HTTPMessage msg;\n    msg.setURL(\"/\");\n    egress->validateHeadersAndSkip(msg);\n\n    transport = detail::makeHttpSourceTransport(\n        &evb, std::move(egress), &ingressSource);\n    EXPECT_EQ(transport->getEventBase(), &evb);\n    EXPECT_EQ(transport->getTransport(), nullptr);\n    EXPECT_EQ(transport->getPeerCertificate(), nullptr);\n  }\n\n  void TearDown() override {\n    transport->close();\n    // consume if egress source not done\n    while (!egressSource->sourceComplete()) {\n      folly::coro::blockingWait(egressSource->readBodyEvent(), &evb);\n    }\n  }\n\n  folly::EventBase evb;\n  MockHTTPSource ingressSource;\n  HTTPStreamSource* egressSource{nullptr};\n  std::unique_ptr<folly::coro::TransportIf> transport;\n  folly::CancellationSource cs_;\n};\n\nCO_TEST_F(HttpStreamTransport, ReadAfterIngressComplete) {\n  // after reading a terminal ingress event (i.e. eom or exception), a\n  // subsequent ::read should yield an exception\n  EXPECT_CALL(ingressSource, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent{nullptr, /*inEOM=*/true})));\n\n  folly::IOBufQueue ingress{folly::IOBufQueue::cacheChainLength()};\n  auto readRes = co_await co_awaitTry(\n      transport->read(ingress,\n                      /*minReadSize=*/0,\n                      /*newAllocationSize=*/0,\n                      /*timeout=*/std::chrono::milliseconds(0)));\n  EXPECT_TRUE(readRes.hasValue() && *readRes == 0);\n\n  // reading again after ingress complete should yield an error\n  readRes = co_await co_awaitTry(\n      transport->read(ingress,\n                      /*minReadSize=*/0,\n                      /*newAllocationSize=*/0,\n                      /*timeout=*/std::chrono::milliseconds(0)));\n\n  auto* ex = readRes.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_TRUE(ex &&\n              ex->getType() == folly::AsyncSocketException::INTERNAL_ERROR);\n}\n\nCO_TEST_F(HttpStreamTransport, SimpleRead) {\n  // simple read 100 bytes\n  EXPECT_CALL(ingressSource, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent{makeBuf(100), /*inEOM=*/false})));\n\n  folly::IOBufQueue ingress{folly::IOBufQueue::cacheChainLength()};\n  auto readRes =\n      co_await transport->read(ingress,\n                               /*minReadSize=*/0,\n                               /*newAllocationSize=*/0,\n                               /*timeout=*/std::chrono::milliseconds(0));\n  EXPECT_EQ(readRes, 100);\n}\n\nCO_TEST_F(HttpStreamTransport, DeferredEom) {\n  /**\n   * Yield Padding event (test non-body event get ignored), subsequently yield\n   * 100-byte bytes + eom in the same HTTPBodyEvent. Ensure that the EOM will be\n   * deferred until the next Transport::read.\n   */\n  EXPECT_CALL(ingressSource, readBodyEvent(_))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent{HTTPBodyEvent::Padding{}, 10})))\n      .WillOnce(Return(folly::coro::makeTask<HTTPBodyEvent>(\n          HTTPBodyEvent{makeBuf(100), /*inEOM=*/true})));\n\n  folly::IOBufQueue ingress{folly::IOBufQueue::cacheChainLength()};\n  auto readRes =\n      co_await transport->read(ingress,\n                               /*minReadSize=*/0,\n                               /*newAllocationSize=*/0,\n                               /*timeout=*/std::chrono::milliseconds(0));\n  // first read yields 100 bytes\n  EXPECT_EQ(readRes, 100);\n  // second read yields 0 immediately (doesn't invoke ::readBodyEvent)\n  readRes = co_await transport->read(ingress,\n                                     /*minReadSize=*/0,\n                                     /*newAllocationSize=*/0,\n                                     /*timeout=*/std::chrono::milliseconds(0));\n  EXPECT_EQ(readRes, 0);\n\n  // read after deferred EOM is an error\n  auto readResTry = co_await co_awaitTry(\n      transport->read(ingress,\n                      /*minReadSize=*/0,\n                      /*newAllocationSize=*/0,\n                      /*timeout=*/std::chrono::milliseconds(0)));\n  auto* ex = readResTry.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_TRUE(ex &&\n              ex->getType() == folly::AsyncSocketException::INTERNAL_ERROR);\n\n  // close transport\n  transport->close();\n  // reading the egressSource should yield an empty body + eom\n  auto egressBodyEvent = co_await egressSource->readBodyEvent();\n  EXPECT_TRUE(egressBodyEvent.eventType == HTTPBodyEvent::BODY &&\n              egressBodyEvent.event.body.empty());\n  EXPECT_TRUE(egressBodyEvent.eom);\n}\n\nCO_TEST_F(HttpStreamTransport, WriteBackpressure) {\n  /**\n   * Writes 64KB to the egressSource, then write again to ensure that the\n   * ::write coroutine suspends. After consuming from the egress source, it\n   * should unblock the write accordingly.\n   */\n  folly::IOBufQueue egress{folly::IOBufQueue::cacheChainLength()};\n  // first 64KB writes resolves immediately\n  egress.append(makeBuf(65'535));\n  folly::coro::Transport::WriteInfo info{};\n  auto writeRes = co_withExecutor(&evb,\n                                  transport->write(egress,\n                                                   std::chrono::milliseconds(0),\n                                                   folly::WriteFlags::NONE,\n                                                   &info))\n                      .startInlineUnsafe();\n  EXPECT_TRUE(writeRes.isReady());\n  EXPECT_EQ(info.bytesWritten, 65'535);\n\n  // next 100 byte write will suspend\n  egress.append(makeBuf(100));\n  writeRes =\n      co_withExecutor(&evb, transport->write(egress)).startInlineUnsafe();\n  EXPECT_FALSE(writeRes.isReady());\n\n  // consuming from egressSource will unblock write\n  co_await egressSource->readBodyEvent();\n  // one evb loop\n  evb.loopOnce();\n  EXPECT_TRUE(writeRes.isReady());\n\n  // close transport\n  EXPECT_CALL(ingressSource, stopReading(_));\n  transport->closeWithReset();\n  // next egress read should yield an error\n  auto ev = co_await co_awaitTry(egressSource->readBodyEvent());\n  EXPECT_TRUE(ev.hasException());\n}\n\nCO_TEST_F(HttpStreamTransport, WriteTimeout) {\n  /**\n   * Writes 64KB to the egressSource, then write again to ensure that the\n   * ::write coroutine suspends. Ensure the timeout works as expected and the\n   * error yielded is AsyncSocketException::TIMED_OUT\n   */\n  folly::IOBufQueue egress{folly::IOBufQueue::cacheChainLength()};\n  // first 64KB writes resolves immediately\n  egress.append(makeBuf(65'535));\n  folly::coro::Transport::WriteInfo info{};\n  auto writeRes = co_withExecutor(&evb,\n                                  transport->write(egress,\n                                                   std::chrono::milliseconds(0),\n                                                   folly::WriteFlags::NONE,\n                                                   &info))\n                      .startInlineUnsafe();\n  EXPECT_TRUE(writeRes.isReady());\n  EXPECT_EQ(info.bytesWritten, 65'535);\n\n  // next 100 byte write will suspend\n  egress.append(makeBuf(100));\n  writeRes = co_withExecutor(\n                 &evb, transport->write(egress, std::chrono::milliseconds(10)))\n                 .startInlineUnsafe();\n  EXPECT_FALSE(writeRes.isReady());\n\n  auto res = blockingWait(co_awaitTry(std::move(writeRes)), &evb);\n  auto* ex = res.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_TRUE(ex && ex->getType() == folly::AsyncSocketException::TIMED_OUT);\n  co_return;\n}\n\nCO_TEST_F(HttpStreamTransport, WriteCancellation) {\n  // write cancellation should omit write & yield the appropriate exception\n  cs_.requestCancellation();\n  folly::IOBufQueue egress{folly::IOBufQueue::cacheChainLength()};\n  egress.append(makeBuf(65'535));\n  auto writeRes = co_await co_awaitTry(\n      co_withCancellation(cs_.getToken(), transport->write(egress)));\n  auto* ex = writeRes.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_TRUE(ex && ex->getType() == folly::AsyncSocketException::CANCELED);\n}\n\nCO_TEST_F(HttpStreamTransport, EgressConsumerStopReading) {\n  // consumer of the egress source invoking ::stopReading should trigger a write\n  // error on next write\n  egressSource->stopReading(HTTPErrorCode::NO_ERROR);\n\n  folly::IOBufQueue egress{folly::IOBufQueue::cacheChainLength()};\n  egress.append(makeBuf(65'535));\n  auto writeRes = co_await co_awaitTry(transport->write(egress));\n  auto* ex = writeRes.tryGetExceptionObject<folly::AsyncSocketException>();\n  EXPECT_TRUE(ex && ex->getType() == folly::AsyncSocketException::NOT_OPEN);\n}\n\nCO_TEST_F(HttpStreamTransport, Fatals) {\n  std::array<uint8_t, 10> ingress{};\n  EXPECT_DEATH(\n      co_await transport->read(folly::MutableByteRange{ingress},\n                               /*timeout=*/std::chrono::milliseconds(0)),\n      \"not implemented\");\n  EXPECT_DEATH(co_await transport->write(\n                   folly::ByteRange{ingress.data(), ingress.size()}),\n               \"not implemented\");\n  EXPECT_DEATH(transport->getPeerAddress(), \"not implemented\");\n  EXPECT_DEATH(transport->getLocalAddress(), \"not implemented\");\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/Mocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n\n#include \"proxygen/lib/http/coro/HTTPStreamSourceSinkFactory.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroSessionPool.h\"\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/coro/HTTPCoroSession.h>\n#include <proxygen/lib/http/coro/HTTPSource.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\n#include <folly/coro/GmockHelpers.h>\n\nnamespace proxygen::coro::test {\n\nclass MockHTTPSource : public proxygen::coro::HTTPSource {\n public:\n  MOCK_METHOD(folly::coro::Task<proxygen::coro::HTTPHeaderEvent>,\n              readHeaderEvent,\n              ());\n  MOCK_METHOD(folly::coro::Task<proxygen::coro::HTTPBodyEvent>,\n              readBodyEvent,\n              (uint32_t));\n\n  MOCK_METHOD(void, stopReading, (folly::Optional<const HTTPErrorCode> error));\n\n  MOCK_METHOD(folly::Optional<uint64_t>, getStreamID, (), (const));\n};\n\nclass MockHTTPSessionContext : public HTTPSessionContext {\n public:\n  MOCK_METHOD(void, initiateDrain, ());\n  MOCK_METHOD(bool, isDownstream, (), (const));\n  MOCK_METHOD(bool, isUpstream, (), (const));\n  MOCK_METHOD(uint64_t, getSessionID, (), (const));\n  MOCK_METHOD(folly::EventBase *, getEventBase, (), (const));\n  MOCK_METHOD(CodecProtocol, getCodecProtocol, (), (const));\n  MOCK_METHOD(const folly::SocketAddress &, getLocalAddress, (), (const));\n  MOCK_METHOD(const folly::SocketAddress &, getPeerAddress, (), (const));\n  MOCK_METHOD(const wangle::TransportInfo &,\n              getSetupTransportInfo,\n              (),\n              (const));\n  MOCK_METHOD(bool,\n              getCurrentTransportInfo,\n              (wangle::TransportInfo *, bool),\n              (const));\n  MOCK_METHOD(size_t,\n              getSequenceNumberFromStreamId,\n              (HTTPCodec::StreamID),\n              (const));\n  MOCK_METHOD(uint16_t, getDatagramSizeLimit, (), (const));\n  MOCK_METHOD(const folly::AsyncTransportCertificate *,\n              getPeerCertificate,\n              (),\n              (const));\n  MOCK_METHOD(void, addLifecycleObserver, (LifecycleObserver *));\n  MOCK_METHOD(void, removeLifecycleObserver, (LifecycleObserver *));\n};\n\n/**\n * EventBase must be initialized prior to HTTPCoroSession, and therefore also\n * MockHTTPCoroSession. We get around this by constructing the evb in the first\n * base class.\n */\nclass MockHTTPCoroSessionBase {\n protected:\n  folly::EventBase evb_;\n};\n\nclass MockHTTPCoroSession\n    : public MockHTTPCoroSessionBase\n    , public HTTPCoroSession {\n public:\n  MockHTTPCoroSession()\n      : HTTPCoroSession(\n            &evb_,\n            folly::SocketAddress(\"1.2.3.4\", 12345),\n            folly::SocketAddress(\"4.3.2.1\", 54321),\n            std::make_unique<HTTP1xCodec>(TransportDirection::UPSTREAM),\n            wangle::TransportInfo()) {\n  }\n\n  ~MockHTTPCoroSession() override = default;\n\n  MOCK_METHOD(folly::coro::Task<HTTPSourceHolder>,\n              sendRequest,\n              (HTTPSourceHolder, HTTPCoroSession::RequestReservation));\n\n  MOCK_METHOD((folly::Expected<HTTPSourceHolder, HTTPError>),\n              sendRequest,\n              (RequestReservation reservation,\n               const HTTPMessage &headers,\n               HTTPSourceHolder bodySource),\n              (noexcept));\n\n private:\n  folly::coro::TaskWithExecutor<void> run() override {\n    return co_withExecutor(&evb_,\n                           ([]() -> folly::coro::Task<void> { co_return; })());\n  }\n\n  void sendPing() override {\n  }\n\n  void setConnectionFlowControl(uint32_t) override {\n  }\n  void registerByteEvents(HTTPCodec::StreamID,\n                          folly::Optional<uint64_t>,\n                          folly::Optional<HTTPByteEvent::FieldSectionInfo>,\n                          uint64_t,\n                          std::vector<HTTPByteEventRegistration> &&regs,\n                          bool) override {\n    auto localRegistrations = std::move(regs);\n  }\n  HTTPCodec::StreamID getSessionStreamID() const override {\n    return 0;\n  }\n  bool getCurrentTransportInfoImpl(\n      wangle::TransportInfo * /*tinfo*/) const override {\n    return false;\n  }\n  uint16_t getDatagramSizeLimit() const override {\n    return 0;\n  }\n  const folly::AsyncTransportCertificate *getPeerCertificate() const override {\n    return nullptr;\n  }\n  void handleConnectionError(HTTPErrorCode, std::string) override {\n  }\n  void setupStreamWriteBuf(StreamState &, folly::IOBufQueue &) override {\n  }\n\n  void notifyHeaderWrite(StreamState &, bool) override {\n  }\n  StreamState *createReqStream() override {\n    return nullptr;\n  }\n  bool streamRefusedByGoaway(StreamState &, HTTPCodec::StreamID) override {\n    return false;\n  }\n  void generateResetStream(HTTPCodec::StreamID,\n                           HTTPErrorCode,\n                           bool,\n                           bool) override {\n  }\n  void handleDeferredStopSending(HTTPCodec::StreamID id) override {\n  }\n  void handleIngressLimitExceeded(HTTPCodec::StreamID) override {\n  }\n  uint32_t numTransactionsAvailable() const override {\n    return std::numeric_limits<uint32_t>::max();\n  }\n  bool checkAndHandlePushPromiseComplete(\n      StreamState &, std::unique_ptr<HTTPMessage> &) override {\n    return false;\n  }\n  folly::Expected<std::pair<HTTPCodec::StreamID, HTTPCodec::StreamID>,\n                  ErrorCode>\n  createEgressPushStream() override {\n    return folly::makeUnexpected(ErrorCode::NO_ERROR);\n  }\n};\n\nclass MockHTTPHandler : public HTTPHandler {\n public:\n  MockHTTPHandler() {\n    ON_CALL(*this, handleRequest(testing::_, testing::_, testing::_))\n        .WillByDefault(folly::coro::gmock_helpers::CoReturnByMove(nullptr));\n  }\n\n  MOCK_METHOD(folly::coro::Task<HTTPSourceHolder>,\n              handleRequest,\n              (folly::EventBase *, HTTPSessionContextPtr, HTTPSourceHolder));\n};\n\nclass MockHTTPBodyEventQueue : public HTTPBodyEventQueue {\n public:\n  explicit MockHTTPBodyEventQueue(\n      folly::EventBase *evb,\n      HTTPCodec::StreamID id,\n      HTTPBodyEventQueue::Callback &callback,\n      size_t limit = 65535,\n      std::chrono::milliseconds writeTimeout = std::chrono::seconds(5))\n      : HTTPBodyEventQueue(evb, id, callback, limit, writeTimeout) {\n  }\n\n  MOCK_METHOD(void, contentLengthMismatch, ());\n};\n\nclass MockByteEventCallback : public HTTPByteEventCallback {\n public:\n  MOCK_METHOD(void, onByteEvent, (HTTPByteEvent));\n  MOCK_METHOD(void, onByteEventCanceled, (HTTPByteEvent, HTTPError));\n};\n\nclass MockHTTPCoroSessionPool : public proxygen::coro::HTTPCoroSessionPool {\n public:\n  explicit MockHTTPCoroSessionPool(folly::SocketAddress mockSocketAddress)\n      : HTTPCoroSessionPool(&evb_,\n                            mockSocketAddress.getAddressStr(),\n                            mockSocketAddress.getPort()) {\n  }\n\n  ~MockHTTPCoroSessionPool() override = default;\n\n  MOCK_METHOD(folly::coro::Task<coro::HTTPCoroSessionPool::GetSessionResult>,\n              getSessionWithReservation,\n              ());\n\n  folly::EventBase evb_;\n};\n\nclass MockHTTPStreamSourceSinkFactory\n    : public proxygen::coro::HTTPStreamSourceSinkFactory {\n public:\n  MOCK_METHOD(std::unique_ptr<proxygen::coro::HTTPStreamSourceUpstreamSink>,\n              newHTTPStreamSourceSink,\n              (folly::EventBase *,\n               proxygen::coro::HTTPSessionContextPtr,\n               HTTPTransactionHandler *));\n};\n\nclass MockHTTPStreamSourceUpstreamSink\n    : public proxygen::coro::HTTPStreamSourceUpstreamSink {\n public:\n  MockHTTPStreamSourceUpstreamSink(\n      folly::EventBase *evb,\n      proxygen::coro::HTTPSessionContextPtr sessionCtx,\n      HTTPTransactionHandler *handler,\n      MockHTTPTransaction *mockTransaction)\n      : proxygen::coro::HTTPStreamSourceUpstreamSink(evb, sessionCtx, handler),\n        mockHTTPTransaction_(mockTransaction) {\n  }\n\n  void detachAndAbortIfIncomplete(std::unique_ptr<HTTPSink> httpSink) override {\n    if (mockHTTPTransaction_) {\n      mockHTTPTransaction_->setTransportCallback(nullptr);\n      mockHTTPTransaction_->setHandler(nullptr);\n      if (!(mockHTTPTransaction_->isEgressComplete() ||\n            mockHTTPTransaction_->isEgressEOMQueued()) ||\n          !mockHTTPTransaction_->isIngressComplete()) {\n        mockHTTPTransaction_->sendAbort();\n      }\n    }\n    httpSink.reset();\n  }\n\n  void sendHeaders(const HTTPMessage &headers) override {\n    if (mockHTTPTransaction_) {\n      mockHTTPTransaction_->sendHeaders(headers);\n    }\n  }\n\n  folly::coro::Task<void> transact(\n      proxygen::coro::HTTPCoroSession *upstreamSession,\n      proxygen::coro::HTTPCoroSession::RequestReservation reservation)\n      override {\n    co_return;\n  }\n\n  void sendBody(std::unique_ptr<folly::IOBuf> body) override {\n    if (mockHTTPTransaction_) {\n      mockHTTPTransaction_->sendBody(std::move(body));\n    }\n  }\n\n  void sendEOM() override {\n    if (mockHTTPTransaction_) {\n      mockHTTPTransaction_->sendEOM();\n    }\n  }\n\n  void sendAbort() override {\n    if (mockHTTPTransaction_) {\n      mockHTTPTransaction_->sendAbort();\n    }\n  }\n\n  wangle::TransportInfo fakeTransportInfo_;\n  const wangle::TransportInfo &getSetupTransportInfo() const noexcept override {\n    return fakeTransportInfo_;\n  }\n\n  bool isEgressPaused() const override {\n    if (mockHTTPTransaction_) {\n      return mockHTTPTransaction_->isEgressPaused();\n    }\n    return false;\n  }\n\n  // Logging\n  void describe(std::ostream &) override {};\n\n private:\n  MockHTTPTransaction *mockHTTPTransaction_;\n};\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/TestUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/protocol/CertificateVerifier.h>\n\nnamespace proxygen::coro::test {\n// This is an insecure certificate verifier and is not meant to be\n// used in production. Using it in production would mean that this will\n// leave everyone insecure.\nclass InsecureVerifierDangerousDoNotUseInProduction\n    : public fizz::CertificateVerifier {\n public:\n  ~InsecureVerifierDangerousDoNotUseInProduction() override = default;\n\n  [[nodiscard]] std::shared_ptr<const folly::AsyncTransportCertificate> verify(\n      const std::vector<std::shared_ptr<const fizz::PeerCert>>& certs)\n      const override {\n    return certs.front();\n  }\n\n  [[nodiscard]] std::vector<fizz::Extension> getCertificateRequestExtensions()\n      const override {\n    return {};\n  }\n};\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/test/WindowContainerTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/coro/util/WindowContainer.h>\n\nusing namespace proxygen::coro;\nusing namespace testing;\n\nnamespace proxygen::coro::test {\n\nTEST(WindowContainerTest, BelowKMinThreshold) {\n  /** Verifies that processed() returns greater than 0 when half of the window\n   * has been consumed.\n   */\n  constexpr uint32_t kCapacity = 64 * 1024;\n  WindowContainer container{kCapacity};\n  EXPECT_TRUE(container.reserve(kCapacity, 0));\n  EXPECT_EQ(container.processed((kCapacity / 2) - 1), 0);\n  EXPECT_GT(container.processed(1), 0);\n}\n\nTEST(WindowContainerTest, AboveKMinThreshold) {\n  /** Verifies that when we process more than kMinThreshold bytes, processed()\n   * will return greater than 0, even though half the window has not been\n   * consumed yet. This will result in more frequent WINDOW_UPDATES.\n   */\n  constexpr uint32_t kCapacity = 1024 * 1024;\n  constexpr uint32_t kMinThreshold = 128 * 1024;\n  WindowContainer container{kCapacity};\n  EXPECT_TRUE(container.reserve(kCapacity, 0));\n  EXPECT_EQ(container.processed(kMinThreshold - 1), 0);\n  EXPECT_GT(container.processed(1), 0);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_transport_coro_ssl_transport\n  SRCS\n    CoroSSLTransport.cpp\n  DEPS\n    Folly::folly_io_async_ssl_basic_transport_certificate\n    Folly::folly_io_async_ssl_ssl_errors\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_coro_util_timed_baton\n    Folly::folly_io_async_certificate_identity_verifier\n    Folly::folly_io_async_ssl_context\n    Folly::folly_io_coro_socket\n    Folly::folly_io_iobuf\n    Folly::folly_ssl_ssl_session\n    Folly::folly_ssl_ssl_session_manager\n)\n\nproxygen_add_library(proxygen_http_coro_transport_http_connect_transport\n  SRCS\n    HTTPConnectAsyncTransport.cpp\n    HTTPConnectStream.cpp\n    HTTPConnectTransport.cpp\n  EXPORTED_DEPS\n    proxygen_coro\n    proxygen_http_coro_coro_stream_source\n    Folly::folly_coro_bounded_queue\n    Folly::folly_io_async_async_transport\n    Folly::folly_io_coro_socket\n    Folly::folly_logging_logging\n)\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/CoroSSLTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/CoroSSLTransport.h\"\n#include <folly/logging/xlog.h>\n\n#include <folly/io/Cursor.h>\n#include <folly/io/async/ssl/BasicTransportCertificate.h>\n#include <folly/io/async/ssl/SSLErrors.h>\n\nnamespace {\n\nusing folly::ssl::OpenSSLUtils;\nusing proxygen::coro::CoroSSLTransport;\nusing proxygen::coro::TimedBaton;\n\nconstexpr size_t kMinSSLWriteSize = 1500;\nstatic const uint32_t kMinReadSize = 1460;\nstatic const uint32_t kMaxReadSize = 4000;\nstatic const uint32_t kMaxBufferSize = 128 * 1024;\nconstexpr int CORO_SSL_TRANSPORT_RETRY = -1;\n\nCoroSSLTransport* transportFromBio(BIO* bio) {\n  auto appData = OpenSSLUtils::getBioAppData(bio);\n  XCHECK(appData);\n  auto* transport = reinterpret_cast<CoroSSLTransport*>(appData);\n  XCHECK(transport);\n  return transport;\n}\n\nint coroSSLTransportBioWrite(BIO* bio, const char* buf, int sz) {\n  BIO_clear_retry_flags(bio);\n  return transportFromBio(bio)->bioWrite(buf, sz);\n}\nint coroSSLTransportBioWriteEx(BIO* bio,\n                               const char* buf,\n                               size_t sz,\n                               size_t* nw) {\n  BIO_clear_retry_flags(bio);\n  auto rc = coroSSLTransportBioWrite(bio, buf, sz);\n  if (rc >= 0) {\n    *nw = rc;\n    return 1;\n  } else if (rc == CORO_SSL_TRANSPORT_RETRY) {\n    BIO_set_retry_write(bio);\n  }\n  return 0;\n}\nint coroSSLTransportBioRead(BIO* bio, char* buf, int sz) {\n  BIO_clear_retry_flags(bio);\n  auto rc = transportFromBio(bio)->bioRead(buf, sz);\n  if (rc == CORO_SSL_TRANSPORT_RETRY) {\n    BIO_set_retry_read(bio);\n  }\n  return rc;\n}\nint coroSSLTransportBioReadEx(BIO* bio, char* buf, size_t sz, size_t* nr) {\n  auto rc = coroSSLTransportBioRead(bio, buf, sz);\n  if (rc >= 0) {\n    *nr = rc;\n    return 1;\n  }\n  return 0;\n}\nlong coroSSLTransportBioCtrl(BIO* bio, int i, long l, void* p) {\n  return transportFromBio(bio)->bioCtrl(i, l, p);\n}\nint coroSSLTransportBioCreate(BIO* bio) {\n  BIO_set_init(bio, 1);\n  return 1;\n}\nint coroSSLTransportBioDestroy(BIO* /*bio*/) {\n  return 1;\n}\nlong coroSSLTransportBioCallbackCtrl(BIO* bio, int idx, BIO_info_cb* cb) {\n  return transportFromBio(bio)->bioCallbackCtrl(idx, cb);\n}\n\nfolly::ssl::BioMethodUniquePtr initBioMethod() {\n  BIO_METHOD* newmeth = nullptr;\n  newmeth = BIO_meth_new(BIO_get_new_index(), \"coro_ssl_transport_bio_method\");\n  if (!newmeth) {\n    return nullptr;\n  }\n  BIO_meth_set_create(newmeth, coroSSLTransportBioCreate);\n  BIO_meth_set_destroy(newmeth, coroSSLTransportBioDestroy);\n  BIO_meth_set_ctrl(newmeth, coroSSLTransportBioCtrl);\n  BIO_meth_set_callback_ctrl(newmeth, coroSSLTransportBioCallbackCtrl);\n  BIO_meth_set_read(newmeth, coroSSLTransportBioRead);\n  BIO_meth_set_read_ex(newmeth, coroSSLTransportBioReadEx);\n  BIO_meth_set_write(newmeth, coroSSLTransportBioWrite);\n  BIO_meth_set_write_ex(newmeth, coroSSLTransportBioWriteEx);\n\n  return folly::ssl::BioMethodUniquePtr(newmeth);\n}\n// Note: This is a Leaky Meyer's Singleton. The reason we can't use a non-leaky\n// thing is because we will be setting this BIO_METHOD* inside BIOs owned by\n// various SSL objects which may get callbacks even during teardown. We may\n// eventually try to fix this\nBIO_METHOD* getCoroSSLBioMethod() {\n  static auto const instance = initBioMethod().release();\n  return instance;\n}\n\nstd::optional<std::chrono::steady_clock::time_point> deadlineFromTimeout(\n    std::chrono::milliseconds timeout) {\n  if (timeout.count() > 0) {\n    auto deadline = std::chrono::steady_clock::now() + timeout;\n    return deadline;\n  }\n  return std::nullopt;\n}\n\nstd::chrono::milliseconds timeoutFromDeadline(\n    std::optional<std::chrono::steady_clock::time_point> deadline) {\n  std::chrono::milliseconds timeout{0};\n  if (deadline) {\n    auto now = std::chrono::steady_clock::now();\n    if (now > *deadline) {\n      throw folly::AsyncSocketException(folly::AsyncSocketException::TIMED_OUT,\n                                        \"SSL opereation timed out\");\n    }\n    timeout =\n        std::chrono::duration_cast<std::chrono::milliseconds>(*deadline - now);\n  }\n  return timeout;\n}\n\n// This converts \"illegal\" shutdowns into ZERO_RETURN\ninline bool zero_return(int error, int rc, int errno_copy) {\n  if (error == SSL_ERROR_ZERO_RETURN) {\n    // Peer has closed the connection for writing by sending the\n    // close_notify alert. The underlying transport might not be closed, but\n    // assume it is and return EOF.\n    return true;\n  }\n#ifdef _WIN32\n  // on windows underlying TCP socket may error with this code\n  // if the sending/receiving client crashes or is killed\n  if (error == SSL_ERROR_SYSCALL && errno_copy == WSAECONNRESET) {\n    return true;\n  }\n#endif\n  // NOTE: OpenSSL has a bug where SSL_ERROR_SYSCALL and errno 0 indicates\n  // an unexpected EOF from the peer. This will be changed in OpenSSL 3.0\n  // and reported as SSL_ERROR_SSL with reason\n  // SSL_R_UNEXPECTED_EOF_WHILE_READING. We should then explicitly check for\n  // that. See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html\n  if (rc == SSL_ERROR_SYSCALL && errno_copy == 0) {\n    // ignore anything else in the error queue\n    ERR_clear_error();\n    return true;\n  }\n  return false;\n}\n\nint getCoroSSLTransportExDataIndex() {\n  static auto index = SSL_get_ex_new_index(\n      0, (void*)\"CoroSSLTransport data index\", nullptr, nullptr, nullptr);\n  return index;\n}\n\nCoroSSLTransport* getCoroSSLTransportFromSSL(const SSL* ssl) {\n  return static_cast<CoroSSLTransport*>(\n      SSL_get_ex_data(ssl, getCoroSSLTransportExDataIndex()));\n}\n\nfolly::coro::Task<CoroSSLTransport::IOResult> waitForIO(\n    TimedBaton& baton,\n    std::optional<std::chrono::steady_clock::time_point>& currentDeadline,\n    std::optional<std::chrono::steady_clock::time_point> deadline) {\n  if (!currentDeadline || *currentDeadline > deadline) {\n    // Only set the timeout if the deadline is sooner than the current deadline\n    currentDeadline = deadline;\n    baton.setTimeout(timeoutFromDeadline(deadline));\n  }\n  auto res = co_await baton.wait();\n  currentDeadline.reset();\n  if (res == TimedBaton::Status::cancelled) {\n    // This can happen with outstanding writes in closeWithReset\n    XLOG(DBG6) << \"IO wait cancelled\";\n    co_yield folly::coro::co_error(folly::AsyncSocketException(\n        folly::AsyncSocketException::CANCELED, \"IO wait cancelled\"));\n  } else if (res == TimedBaton::Status::timedout) {\n    co_yield folly::coro::co_error(folly::AsyncSocketException(\n        folly::AsyncSocketException::TIMED_OUT, \"SSL opereation timed out\"));\n  } else {\n    co_return CoroSSLTransport::IOResult::Success;\n  }\n}\n\n} // namespace\n\nusing namespace folly::ssl;\nusing folly::AsyncSocketException;\n\nnamespace proxygen::coro {\nbool CoroSSLTransport::setupSSLBio() {\n  auto sslBio = BIO_new(getCoroSSLBioMethod());\n\n  if (!sslBio) {\n    return false;\n  }\n\n  OpenSSLUtils::setBioAppData(sslBio, this);\n  SSL_set_bio(ssl_.get(), sslBio, sslBio);\n  return true;\n}\n\nCoroSSLTransport::CoroSSLTransport(\n    std::unique_ptr<folly::coro::TransportIf> transport,\n    std::shared_ptr<const folly::SSLContext> sslContext,\n    // const std::shared_ptr<ClientExtensions>& extensions = nullptr,\n    TransportOptions transportOptions)\n    : transport_(std::move(transport)),\n      localAddr_(transport_->getLocalAddress()),\n      peerAddr_(transport_->getPeerAddress()),\n      transportOptions_(std::move(transportOptions)),\n      ctx_(std::move(sslContext)),\n      readsBlocked_(transport_->getEventBase(), std::chrono::milliseconds(0)),\n      writesBlocked_(transport_->getEventBase(), std::chrono::milliseconds(0)) {\n  readsBlocked_.signal();\n  writesBlocked_.signal();\n}\n\nCoroSSLTransport::~CoroSSLTransport() {\n  closeNow();\n  *deleted_ = true;\n}\n\nfolly::coro::Task<void> CoroSSLTransport::connect(\n    // std::shared_ptr<const CertificateVerifier> verifier,\n    folly::Optional<std::string> sni,\n    // Optional<std::string> pskIdentity,\n    // Optional<std::vector<ech::ECHConfig>> echConfigs,\n    std::chrono::milliseconds timeout) {\n  getEventBase()->dcheckIsInEventBaseThread();\n\n  try {\n    ssl_.reset(ctx_->createSSL());\n  } catch (std::exception&) {\n    static const folly::Indestructible<AsyncSocketException> ex(\n        AsyncSocketException::INTERNAL_ERROR,\n        \"error calling SSLContext::createSSL()\");\n    XLOG(ERR) << \"CoroSSLTransport::connect(this=\" << this\n              << \"): \" << ex->what();\n    throw(*ex);\n  }\n\n  if (!setupSSLBio()) {\n    static const folly::Indestructible<AsyncSocketException> ex(\n        AsyncSocketException::INTERNAL_ERROR, \"error creating SSL bio\");\n    XLOG(ERR) << \"CoroSSLTransport::connect(this=\" << this\n              << \"): \" << ex->what();\n    co_yield folly::coro::co_error(*ex);\n  }\n\n  if (!applyVerificationOptions(ssl_)) {\n    static const folly::Indestructible<AsyncSocketException> ex(\n        AsyncSocketException::INTERNAL_ERROR,\n        \"error applying the SSL verification options\");\n    XLOG(ERR) << \"CoroSSLTransport::connect(this=\" << this\n              << \"): \" << ex->what();\n    co_yield folly::coro::co_error(*ex);\n  }\n\n  SSLSessionUniquePtr sessionPtr = sslSessionManager_.getRawSession();\n  if (sessionPtr) {\n    SSL_set_session(ssl_.get(), sessionPtr.get());\n  }\n  if (sni && !sni->empty()) {\n    sni_ = *sni;\n    SSL_set_tlsext_host_name(ssl_.get(), sni_.c_str());\n  }\n\n  SSL_set_ex_data(ssl_.get(), getCoroSSLTransportExDataIndex(), this);\n  sslSessionManager_.attachToSSL(ssl_.get());\n\n  co_return co_await doConnect(timeout);\n}\n\nfolly::coro::Task<void> CoroSSLTransport::doConnect(\n    std::chrono::milliseconds timeout) {\n  co_await folly::coro::co_safe_point;\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      cancelToken, [&] { cancellationSource_.requestCancellation(); }};\n  auto handshakeDeadline = deadlineFromTimeout(timeout);\n  while (true) {\n    int ret;\n    {\n      // If openssl is not built with TSAN then we can get a TSAN false positive\n      // when calling SSL_connect from multiple threads.\n      folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__);\n      ret = SSL_connect(ssl_.get());\n    }\n    XLOG(DBG6) << \"SSL_connect returned=\" << ret;\n    if (ret <= 0) {\n      auto res = co_await folly::coro::co_withCancellation(\n          cancellationSource_.getToken(),\n          handleReturnMaybeIO(ret, handshakeDeadline));\n      if (res == IOResult::EndOfFile) {\n        co_yield folly::coro::co_error(folly::AsyncSocketException(\n            AsyncSocketException::END_OF_FILE, \"EOF during handshake\"));\n      }\n    } else {\n      XLOG(DBG3) << \"CoroSSLTransport \" << this << \": successfully connected\";\n      co_return;\n    }\n  }\n}\n\nbool CoroSSLTransport::applyVerificationOptions(\n    const folly::ssl::SSLUniquePtr& ssl) {\n  // apply the settings specified in verifyPeer_\n  if (verifyPeer_ == folly::SSLContext::SSLVerifyPeerEnum::USE_CTX) {\n    XLOG_IF(WARNING, transportOptions_.verifier) << \"Verifier set but ignored\";\n    if (ctx_->needsPeerVerification()) {\n      if (ctx_->checkPeerName()) {\n        std::string peerNameToVerify =\n            !ctx_->peerFixedName().empty() ? ctx_->peerFixedName() : sni_;\n\n        X509_VERIFY_PARAM* param = SSL_get0_param(ssl.get());\n        if (!X509_VERIFY_PARAM_set1_host(\n                param, peerNameToVerify.c_str(), peerNameToVerify.length())) {\n          return false;\n        }\n      }\n\n      SSL_set_verify(ssl.get(),\n                     ctx_->getVerificationMode(),\n                     CoroSSLTransport::sslVerifyCallback);\n    }\n  } else {\n    if (verifyPeer_ == folly::SSLContext::SSLVerifyPeerEnum::VERIFY ||\n        verifyPeer_ ==\n            folly::SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT) {\n      SSL_set_verify(ssl.get(),\n                     folly::SSLContext::getVerificationMode(verifyPeer_),\n                     CoroSSLTransport::sslVerifyCallback);\n    }\n  }\n\n  return true;\n}\n\nbool CoroSSLTransport::getSSLSessionReused() const {\n  if (ssl_ != nullptr) {\n    return SSL_session_reused(ssl_.get());\n  }\n  return false;\n}\n\nstd::string CoroSSLTransport::getApplicationProtocol() const {\n  const unsigned char* protoName = nullptr;\n  unsigned protoLength = 0;\n  SSL_get0_alpn_selected(ssl_.get(), &protoName, &protoLength);\n  return std::string(reinterpret_cast<const char*>(protoName), protoLength);\n}\n\nconst char* CoroSSLTransport::getNegotiatedCipherName() const {\n  return (ssl_ != nullptr) ? SSL_get_cipher_name(ssl_.get()) : nullptr;\n}\n\nint CoroSSLTransport::getSSLVersion() const {\n  return (ssl_ != nullptr) ? SSL_version(ssl_.get()) : 0;\n}\n\nint CoroSSLTransport::sslVerifyCallback(int preverifyOk,\n                                        X509_STORE_CTX* x509Ctx) {\n  SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(\n      x509Ctx, SSL_get_ex_data_X509_STORE_CTX_idx());\n  auto* self = getCoroSSLTransportFromSSL(ssl);\n\n  XLOG(DBG3) << \"CoroSSLTransport::sslVerifyCallback() this=\" << self << \", \"\n             << \", preverifyOk=\" << preverifyOk;\n\n  if (!preverifyOk) {\n    // OpenSSL verification failure, no need to call CertificateIdentityVerifier\n    return 0;\n  }\n\n  // only invoke the CertificateIdentityVerifier for the leaf certificate and\n  // only if OpenSSL's preverify succeeded\n\n  int currentDepth = X509_STORE_CTX_get_error_depth(x509Ctx);\n  if (currentDepth != 0 || !self->transportOptions_.verifier) {\n    return 1;\n  }\n\n  X509* peerX509 = X509_STORE_CTX_get_current_cert(x509Ctx);\n  X509_up_ref(peerX509);\n  folly::ssl::X509UniquePtr peer{peerX509};\n  auto cn = OpenSSLUtils::getCommonName(peerX509);\n  auto cert = std::make_unique<BasicTransportCertificate>(std::move(cn),\n                                                          std::move(peer));\n\n  try {\n    auto verifiedCert =\n        self->transportOptions_.verifier->verifyLeaf(*cert.get());\n    self->peerCertData_ = std::move(verifiedCert);\n  } catch (folly::CertificateIdentityVerifierException& e) {\n\n    XLOG(ERR) << \"CoroSSLTransport::sslVerifyCallback(this=\" << self\n              << \") Failed to verify leaf certificate identity(ies): \" << e;\n    return 0;\n  }\n\n  return 1;\n}\n\nfolly::coro::Task<size_t> CoroSSLTransport::read(\n    folly::MutableByteRange buf, std::chrono::milliseconds timeout) {\n  SCOPE_EXIT {\n    XLOG(DBG6) << \"read complete\";\n  };\n  if (buf.size() == 0) {\n    co_return 0;\n  }\n  co_await folly::coro::co_safe_point;\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      cancelToken, [&] { cancellationSource_.requestCancellation(); }};\n  auto readDeadline = deadlineFromTimeout(timeout);\n  while (true) {\n    auto ret = SSL_read(ssl_.get(), buf.data(), buf.size());\n    XLOG(DBG6) << \"SSL_read returned=\" << ret;\n    if (ret <= 0) {\n      auto res = co_await folly::coro::co_withCancellation(\n          cancellationSource_.getToken(),\n          handleReturnMaybeIO(ret, readDeadline));\n      if (res == IOResult::EndOfFile) {\n        co_return 0;\n      }\n    } else {\n      co_return ret;\n    }\n  }\n}\n\nfolly::coro::Task<size_t> CoroSSLTransport::read(\n    folly::IOBufQueue& buf,\n    size_t minReadSize,\n    size_t newAllocationSize,\n    std::chrono::milliseconds timeout) {\n  // this flavor or read should have a max read size!\n  auto rbuf = buf.preallocate(minReadSize, newAllocationSize);\n  auto rc =\n      co_await folly::coro::TransportIf::read(rbuf.first, rbuf.second, timeout);\n  buf.postallocate(rc);\n  co_return rc;\n}\n\nfolly::coro::Task<CoroSSLTransport::IOResult>\nCoroSSLTransport::handleReturnMaybeIO(\n    int ret, std::optional<std::chrono::steady_clock::time_point> deadline) {\n  XLOG(DBG6) << \"handleReturnMaybeIO\";\n  int sslError;\n  unsigned long errError;\n  int errnoCopy = errno;\n  if (willBlock(ret, &sslError, &errError)) {\n    if (sslError == SSL_ERROR_WANT_READ) {\n      return transportRead(deadline);\n    } else {\n      return waitForIO(writesBlocked_, writeDeadline_, deadline);\n    }\n  } else {\n    if (zero_return(sslError, ret, errnoCopy)) {\n      return []() -> folly::coro::Task<IOResult> {\n        co_return IOResult::EndOfFile;\n      }();\n    }\n    ERR_clear_error();\n    throw folly::SSLException(sslError, errError, ret, errnoCopy);\n  }\n}\n\nbool CoroSSLTransport::willBlock(int ret,\n                                 int* sslErrorOut,\n                                 unsigned long* errErrorOut) noexcept {\n  XLOG(DBG6) << \"willBlock\";\n  *errErrorOut = 0;\n  int error = *sslErrorOut = SSL_get_error(ssl_.get(), ret);\n  if (error == SSL_ERROR_WANT_READ) {\n    XLOG(DBG6) << \"CoroSSLTransport(\" << this << \"): SSL_ERROR_WANT_READ\";\n    return true;\n  }\n  if (error == SSL_ERROR_WANT_WRITE) {\n    XLOG(DBG6) << \"CoroSSLTransport(\" << this << \"): SSL_ERROR_WANT_WRITE\";\n    return true;\n  }\n\n  // No support yet for SSL_ERROR_WANT_ASYNC\n  unsigned long lastError = *errErrorOut = ERR_get_error();\n  XLOG(DBG6) << \"CoroSSLTransport(\" << this << \"): SSL error: \" << error << \", \"\n             << \"errno: \" << errno << \", \" << \"ret: \" << ret << \", \"\n             << \"read: \" << BIO_number_read(SSL_get_rbio(ssl_.get())) << \", \"\n             << \"written: \" << BIO_number_written(SSL_get_wbio(ssl_.get()))\n             << \", \" << \"func: \" << ERR_func_error_string(lastError) << \", \"\n             << \"reason: \" << ERR_reason_error_string(lastError);\n  return false;\n}\n\nfolly::coro::Task<CoroSSLTransport::IOResult> CoroSSLTransport::transportRead(\n    std::optional<std::chrono::steady_clock::time_point> deadline) {\n  // Only one transport_->read call at a time\n  if (readsBlocked_.getStatus() == TimedBaton::Status::notReady) {\n    co_return co_await waitForIO(readsBlocked_, readDeadline_, deadline);\n  } else {\n    // this flavor or read should have a max read size - it's implicitly\n    // AsyncSocket maxReadsPerEvent_ * kMaxReadSize\n    readsBlocked_.reset();\n    auto rc = co_await transport_->read(transportReadBuf_,\n                                        kMinReadSize,\n                                        kMaxReadSize,\n                                        timeoutFromDeadline(deadline));\n    readsBlocked_.signal();\n    co_return rc == 0 ? IOResult::EndOfFile : IOResult::Success;\n  }\n}\n\nint CoroSSLTransport::bioRead(char* buf, size_t sz) {\n  if (transportReadBuf_.empty()) {\n    return CORO_SSL_TRANSPORT_RETRY;\n  }\n  folly::io::Cursor cursor(transportReadBuf_.front());\n  auto nRead = cursor.pullAtMost(buf, sz);\n  XLOG(DBG6) << \"transportReadBuf_ size=\" << transportReadBuf_.chainLength()\n             << \" returning nRead=\" << nRead;\n  transportReadBuf_.trimStart(nRead);\n  return nRead;\n}\n\nfolly::coro::Task<folly::Unit> CoroSSLTransport::write(\n    folly::ByteRange buf,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo* writeInfo) {\n  if (pendingShutdown_) {\n    throw folly::AsyncSocketException(AsyncSocketException::END_OF_FILE,\n                                      \"write after shutdownWrite\");\n  }\n  return writeImpl(buf, timeout, writeFlags, writeInfo, /*writev=*/false);\n}\n\nfolly::coro::Task<folly::Unit> CoroSSLTransport::writeImpl(\n    folly::ByteRange buf,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags /*writeFlags*/,\n    WriteInfo* writeInfo,\n    bool writev) {\n  if (!writev) {\n    XCHECK_EQ(writers_, 0UL) << \"One write at a time please\";\n  }\n  co_await folly::coro::co_safe_point;\n  auto deadline = deadlineFromTimeout(timeout);\n  {\n    writers_++;\n    SCOPE_EXIT {\n      writers_--;\n    };\n    while (true) {\n      // bioWrite gets the timeout from here\n      writesBlocked_.setTimeout(timeoutFromDeadline(deadline));\n      auto rc = SSL_write(ssl_.get(), buf.data(), buf.size());\n      XLOG(DBG6) << \"SSL_write returned=\" << rc;\n      if (rc <= 0) {\n        auto res = co_await handleReturnMaybeIO(rc, deadline);\n        if (res == IOResult::EndOfFile) {\n          co_yield folly::coro::co_error(folly::AsyncSocketException(\n              AsyncSocketException::END_OF_FILE, \"EOF during write\"));\n        }\n      } else {\n        XCHECK_EQ(static_cast<size_t>(rc), buf.size());\n        break;\n      }\n    }\n  }\n  if (pendingShutdown_) {\n    shutdownWrite();\n  }\n  if (writeInfo) {\n    writeInfo->bytesWritten = buf.size();\n  }\n  co_return folly::Unit();\n}\n\nfolly::coro::Task<folly::Unit> CoroSSLTransport::write(\n    folly::IOBufQueue& data,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo* writeInfo) {\n  if (pendingShutdown_) {\n    co_yield folly::coro::co_error(folly::AsyncSocketException(\n        AsyncSocketException::END_OF_FILE, \"write after shutdownWrite\"));\n  }\n  XCHECK_EQ(writers_, 0UL) << \"One write at a time please\";\n  auto deadline = deadlineFromTimeout(timeout);\n  do {\n    // TODO: If data is smaller than kMinSSLWriteSize, we could skip the extra\n    // coro overhead\n    data.gather(std::min(kMinSSLWriteSize, data.chainLength()));\n    auto pendingWrite = data.pop_front();\n    WriteInfo singleWriteInfo;\n    {\n      writers_++;\n      SCOPE_EXIT {\n        writers_--;\n      };\n      co_await writeImpl(\n          folly::ByteRange(pendingWrite->data(), pendingWrite->length()),\n          timeoutFromDeadline(deadline),\n          writeFlags,\n          &singleWriteInfo,\n          /*writev=*/true);\n    }\n    if (pendingShutdown_) {\n      shutdownWrite();\n    }\n    if (writeInfo) {\n      writeInfo->bytesWritten += singleWriteInfo.bytesWritten;\n    }\n  } while (!data.empty());\n\n  co_return folly::Unit();\n}\n\nint CoroSSLTransport::bioWrite(const char* buf, size_t sz) {\n  // We have to startInlineUnsafe because transport_->write may not execute\n  // before deletion, and it will use-after-free.\n  // We could cancel it, but in particular SSL_shutdown generates a bioWrite\n  // (close_notify) and the dtor calls close and cancels the coros.\n  // Maybe change Transport::write to do the setup inline\n  int ret = sz;\n  try {\n    XLOG(DBG6) << \"transport_->write sz=\" << sz;\n    if (transportBytesOutstanding_ > kMaxBufferSize) {\n      if (writesBlocked_.getStatus() != TimedBaton::Status::notReady) {\n        XLOG(DBG6) << \"Blocking writes, transportBytesOutstanding_=\"\n                   << transportBytesOutstanding_;\n        writesBlocked_.reset();\n      }\n      return CORO_SSL_TRANSPORT_RETRY;\n    }\n    transportBytesOutstanding_ += sz;\n    // TODO: writeFlags\n    co_withExecutor(\n        getEventBase(),\n        transport_->write(\n            folly::ByteRange{reinterpret_cast<const uint8_t*>(buf), sz},\n            writesBlocked_.getTimeout()))\n        .startInlineUnsafe()\n        .via(getEventBase())\n        .thenTry([this, sz, deleted = deleted_](\n                     folly::Try<folly::Unit> result) {\n          XLOG(DBG6) << \"Write completed sz=\" << sz;\n          if (!*deleted) {\n            XCHECK_GE(transportBytesOutstanding_, sz);\n            transportBytesOutstanding_ -= sz;\n            XLOG(DBG6) << \"transportBytesOutstanding_=\"\n                       << transportBytesOutstanding_;\n            if (result.hasException()) {\n              XLOG(ERR) << \"Write error ex=\" << result.exception().what();\n              transport_->closeWithReset();\n              return;\n            }\n            if (transportBytesOutstanding_ <= kMaxBufferSize &&\n                writesBlocked_.getStatus() == TimedBaton::Status::notReady) {\n              XLOG(DBG6) << \"Resuming writes\";\n              writesBlocked_.signal();\n            }\n            if (pendingShutdown_) {\n              shutdownWrite();\n            }\n          }\n        });\n  } catch (const std::exception& ex) {\n    // This is a catch-all so we don't jump past OpenSSL code\n    XLOG(ERR) << ex.what();\n    return -1;\n  }\n  return ret;\n}\n\n// Close both directions of the transport, delayed shutdown OK - allow writes\n// to drain\nvoid CoroSSLTransport::close() {\n  shutdownWrite();\n  if (!shutdownRead()) {\n    if (pendingShutdown_) {\n      // will call transport_->close() from deferred shutdownWrite if needed\n      pendingClose_ = true;\n    } else {\n      // close the transport now\n      transport_->close();\n    }\n  }\n  // otherwise shutdownWrite already called close\n}\n\n// Close immediately.  Fail any unfinished writes, may not write close notify.\nvoid CoroSSLTransport::closeNow() {\n  failWrites();\n  shutdownWrite();\n  if (!shutdownRead() || pendingShutdown_) {\n    // We're not done shutting down, just close the transport\n    transport_->close();\n  }\n  // otherwise shutdownWrite already called close\n}\n\nvoid CoroSSLTransport::failWrites() {\n  // interrupt any write coros\n  writesBlocked_.signal(TimedBaton::Status::cancelled);\n}\n\nvoid CoroSSLTransport::shutdownWrite() {\n  if (ssl_ && (SSL_get_shutdown(ssl_.get()) & SSL_SENT_SHUTDOWN) == 0) {\n    if (writers_ > 0 || transportBytesOutstanding_ > 0) {\n      // Can't call SSL_shutdown until SSL_write calls finish and flush\n      if (!pendingShutdown_) {\n        XLOG(DBG6) << \"Delayed shutdown with pending writes\";\n        pendingShutdown_ = true;\n      }\n      return;\n    }\n    pendingShutdown_ = false;\n    XLOG(DBG6) << \"SSL_shutdown\";\n    int rc = SSL_shutdown(ssl_.get());\n    if (rc == 0) {\n      // peer's notify has not yet been received\n      XLOG(DBG4) << \"SSL writes shutdown\";\n      if (pendingClose_) {\n        transport_->close();\n      } else {\n        transport_->shutdownWrite();\n      }\n    } else if (rc == 1) {\n      // fully shutdown.  There shouldn't be a read in progress (would have\n      // returned 0 already), but call shutdownRead in case.\n      XLOG(DBG4) << \"SSL completely shutdown\";\n      shutdownRead();\n      transport_->close();\n    } else {\n      int sslError;\n      unsigned long errError;\n      // Logs the error\n      (void)willBlock(rc, &sslError, &errError);\n      ERR_clear_error();\n      transport_->closeWithReset();\n    }\n  }\n}\n\nvoid CoroSSLTransport::closeWithReset() {\n  // Don't call shutdownWrite, which begins graceful SSL close\n  failWrites();\n  shutdownRead();\n  transport_->closeWithReset();\n}\n\nbool CoroSSLTransport::shutdownRead() {\n  // interrupt any read coros\n  cancellationSource_.requestCancellation();\n  if (ssl_ && (SSL_get_shutdown(ssl_.get()) & SSL_RECEIVED_SHUTDOWN) == 0) {\n    XLOG(DBG6) << \"Shutting down reads but CLOSE_NOTIFY not received\";\n    return false;\n  }\n  return true;\n}\n\nconst folly::AsyncTransportCertificate* CoroSSLTransport::getPeerCertificate()\n    const {\n  if (peerCertData_) {\n    return peerCertData_.get();\n  }\n  if (ssl_ != nullptr) {\n    auto peerX509 = SSL_get_peer_certificate(ssl_.get());\n    if (peerX509) {\n      // already up ref'd\n      folly::ssl::X509UniquePtr peer(peerX509);\n      auto cn = OpenSSLUtils::getCommonName(peerX509);\n      peerCertData_ = std::make_unique<BasicTransportCertificate>(\n          std::move(cn), std::move(peer));\n    }\n  }\n  return peerCertData_.get();\n}\n\n/* BIO callbacks */\nlong CoroSSLTransport::bioCtrl(int /*i*/, long /*l*/, void* /*p*/) {\n  return 1; //?\n}\nint CoroSSLTransport::bioCallbackCtrl(int /*idx*/, BIO_info_cb* /*cb*/) {\n  return 1; //?\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/CoroSSLTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/coro/Transport.h>\n\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/CertificateIdentityVerifier.h>\n#include <folly/io/async/SSLContext.h>\n#include <folly/ssl/SSLSession.h>\n#include <folly/ssl/SSLSessionManager.h>\n\nnamespace proxygen::coro {\n\nclass CoroSSLTransport : public folly::coro::TransportIf {\n public:\n  struct TransportOptions {\n    // If this verifier is set, it's used during the TLS handshake. It will be\n    // invoked to verify the peer's end-entity leaf certificate after OpenSSL's\n    // chain validation and after calling the HandshakeCB's handshakeVer() and\n    // only if these are successful.\n    TransportOptions() = default;\n    std::shared_ptr<folly::CertificateIdentityVerifier> verifier;\n  };\n\n  CoroSSLTransport(\n      std::unique_ptr<folly::coro::TransportIf> transport,\n      std::shared_ptr<const folly::SSLContext> sslContext,\n      // const std::shared_ptr<ClientExtensions>& extensions = nullptr,\n      TransportOptions transportOptions = CoroSSLTransport::TransportOptions());\n\n  ~CoroSSLTransport() override;\n\n  folly::coro::Task<void> connect(\n      // std::shared_ptr<const CertificateVerifier> verifier,\n      folly::Optional<std::string> sni,\n      std::chrono::milliseconds timeout);\n\n  void setVerificationOption(folly::SSLContext::SSLVerifyPeerEnum verifyPeer) {\n    verifyPeer_ = verifyPeer;\n  }\n\n  std::shared_ptr<folly::ssl::SSLSession> getSSLSession() {\n    return sslSessionManager_.getSession();\n  }\n\n  /**\n   * Sets the SSL session that will be attempted for TLS resumption.\n   */\n  void setSSLSession(std::shared_ptr<folly::ssl::SSLSession> session) {\n    sslSessionManager_.setSession(std::move(session));\n  }\n\n  /**\n   * Determine if the session specified during setSSLSession was reused\n   * or if the server rejected it and issued a new session.\n   */\n  bool getSSLSessionReused() const;\n\n  std::string getApplicationProtocol() const;\n\n  const char* getNegotiatedCipherName() const;\n\n  /**\n   * Get the SSL version for this connection.\n   */\n  int getSSLVersion() const;\n\n  /* TransportIf overrides */\n  folly::EventBase* getEventBase() noexcept override {\n    return transport_->getEventBase();\n  }\n\n  folly::coro::Task<size_t> read(folly::MutableByteRange buf,\n                                 std::chrono::milliseconds timeout =\n                                     std::chrono::milliseconds(0)) override;\n\n  folly::coro::Task<size_t> read(folly::IOBufQueue& buf,\n                                 size_t minReadSize,\n                                 size_t newAllocationSize,\n                                 std::chrono::milliseconds timeout =\n                                     std::chrono::milliseconds(0)) override;\n\n  folly::coro::Task<folly::Unit> write(\n      folly::ByteRange buf,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo* writeInfo = nullptr) override;\n  folly::coro::Task<folly::Unit> write(\n      folly::IOBufQueue& ioBufQueue,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo* writeInfo = nullptr) override;\n\n  folly::SocketAddress getLocalAddress() const noexcept override {\n    return localAddr_;\n  }\n\n  folly::SocketAddress getPeerAddress() const noexcept override {\n    return peerAddr_;\n  }\n\n  void close() override;\n  void shutdownWrite() override;\n  void closeWithReset() override;\n  folly::AsyncTransport* getTransport() const override {\n    return transport_->getTransport();\n  }\n  const folly::AsyncTransportCertificate* getPeerCertificate() const override;\n\n  int bioWrite(const char* buf, size_t sz);\n  int bioRead(char* buf, size_t sz);\n  long bioCtrl(int /*i*/, long /*l*/, void* /*p*/);\n  int bioCallbackCtrl(int /*idx*/, BIO_info_cb* /*cb*/);\n\n  enum class IOResult { Success, EndOfFile };\n\n private:\n  // Callback for SSL_CTX_set_verify()\n  static int sslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx);\n  static CoroSSLTransport* getFromSSL(const SSL* ssl);\n\n  bool setupSSLBio();\n  bool applyVerificationOptions(const folly::ssl::SSLUniquePtr& ssl);\n  folly::coro::Task<void> doConnect(std::chrono::milliseconds timeout);\n  folly::coro::Task<IOResult> handleReturnMaybeIO(\n      int ret, std::optional<std::chrono::steady_clock::time_point> deadline);\n  folly::coro::Task<IOResult> transportRead(\n      std::optional<std::chrono::steady_clock::time_point> deadline);\n  folly::coro::Task<folly::Unit> writeImpl(folly::ByteRange buf,\n                                           std::chrono::milliseconds timeout,\n                                           folly::WriteFlags writeFlags,\n                                           WriteInfo* writeInfo,\n                                           bool writev);\n  bool willBlock(int ret,\n                 int* sslErrorOut,\n                 unsigned long* errErrorOut) noexcept;\n  void failWrites();\n  void closeNow();\n  bool shutdownRead();\n\n  std::unique_ptr<folly::coro::TransportIf> transport_;\n  folly::SocketAddress localAddr_;\n  folly::SocketAddress peerAddr_;\n  TransportOptions transportOptions_;\n\n  std::shared_ptr<const folly::SSLContext> ctx_;\n  folly::ssl::SSLUniquePtr ssl_;\n\n  folly::SSLContext::SSLVerifyPeerEnum verifyPeer_{\n      folly::SSLContext::SSLVerifyPeerEnum::USE_CTX};\n\n  // Manages the session for the socket\n  folly::ssl::SSLSessionManager sslSessionManager_;\n  mutable std::unique_ptr<const folly::AsyncTransportCertificate> peerCertData_;\n  folly::CancellationSource cancellationSource_;\n  std::string sni_;\n  std::shared_ptr<bool> deleted_{std::make_shared<bool>(false)};\n  folly::IOBufQueue transportReadBuf_{folly::IOBufQueue::cacheChainLength()};\n  TimedBaton readsBlocked_;\n  std::optional<std::chrono::steady_clock::time_point> readDeadline_;\n  size_t transportBytesOutstanding_{0};\n  TimedBaton writesBlocked_;\n  std::optional<std::chrono::steady_clock::time_point> writeDeadline_;\n  size_t writers_{0};\n  bool pendingShutdown_{false};\n  bool pendingClose_{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nHTTPConnectAsyncTransport::HTTPConnectAsyncTransport(\n    std::unique_ptr<HTTPConnectStream> connectStream)\n    : connectStream_(std::move(connectStream)) {\n  connectStream_->setHTTPStreamSourceCallback(this);\n}\n\nHTTPConnectAsyncTransport::~HTTPConnectAsyncTransport() {\n  XLOG(DBG4) << \"~HTTPConnectAsyncTransport\";\n  if (auto* readCb = resetReadCb()) {\n    folly::AsyncSocketException ex(\n        folly::AsyncSocketException::AsyncSocketExceptionType::END_OF_FILE,\n        \"Socket closed locally\");\n    readCb->readErr(ex);\n  }\n  cancellationSource_.requestCancellation();\n  // parent dtor will abort the ingress stream if open, so if inRead_, it will\n  // exit\n}\n\nvoid HTTPConnectAsyncTransport::sourceComplete(\n    HTTPCodec::StreamID /*id*/, folly::Optional<HTTPError> error) {\n  if (error) {\n    // TODO: make specific error codes (eg: timeout)\n    errorWrites();\n  }\n}\n\nvoid HTTPConnectAsyncTransport::setReadCB(ReadCallback* callback) {\n  if (readCallback_ == callback) {\n    return;\n  }\n  if (callback && !readCallback_) {\n    // Setting readCallback_\n    readCallback_ = callback;\n    co_withExecutor(getEventBase(),\n                    folly::coro::co_withCancellation(\n                        cancellationSource_.getToken(), read()))\n        .start();\n  } else {\n    XCHECK(!inRead_) << \"Cannot clear the read callback while reading\";\n    readCallback_ = callback;\n  }\n}\n\nfolly::coro::Task<void> HTTPConnectAsyncTransport::read() {\n  co_await folly::coro::co_safe_point;\n  if (deferredEof_) {\n    CHECK_NOTNULL(resetReadCb())->readEOF();\n    co_return;\n  }\n  const auto& cancelToken = co_await folly::coro::co_current_cancellation_token;\n  while (!cancelToken.isCancellationRequested() && connectStream_->canRead() &&\n         readCallback_) {\n    auto ingressSource = connectStream_->ingressSource_;\n    ingressSource->setReadTimeout(std::chrono::milliseconds(0));\n    size_t size = std::numeric_limits<uint32_t>::max();\n    void* buf = nullptr;\n    if (!readCallback_->isBufferMovable()) {\n      readCallback_->getReadBuffer(&buf, &size);\n    }\n    // This is not interruptible without an unrecoverable abort\n    inRead_ = true;\n    XLOG(DBG5) << \"ingressSource->readBodyEvent\";\n    auto bodyEvent = co_await co_awaitTry(ingressSource->readBodyEvent(size));\n    XLOG(DBG5) << \"ingressSource->readBodyEvent done\";\n    if (cancelToken.isCancellationRequested()) {\n      co_return;\n    }\n    inRead_ = false;\n    if (bodyEvent.hasException()) {\n      XLOG(DBG3) << \"readBodyEvent exception\";\n      ingressError_ = getHTTPError(bodyEvent);\n      if (auto* readCb = resetReadCb()) {\n        folly::AsyncSocketException ex(\n            folly::AsyncSocketException::AsyncSocketExceptionType::UNKNOWN,\n            ingressError_->msg);\n        readCb->readErr(ex);\n      }\n      co_return;\n    }\n    XCHECK(readCallback_);\n    if (bodyEvent->eventType == HTTPBodyEvent::BODY &&\n        !bodyEvent->event.body.empty()) {\n      size = bodyEvent->event.body.chainLength();\n      if (readCallback_->isBufferMovable()) {\n        readCallback_->readBufferAvailable(bodyEvent->event.body.move());\n      } else {\n        folly::io::Cursor cursor(bodyEvent->event.body.front());\n        XCHECK(buf);\n        cursor.pullAtMost(buf, size);\n        readCallback_->readDataAvailable(size);\n      }\n    }\n\n    // read callback may have been uninstalled after delivering data above; if\n    // so, we must defer eof signal until next ::read()\n    if (bodyEvent->eom) {\n      if (auto* readCb = resetReadCb()) {\n        readCb->readEOF();\n      } else {\n        deferredEof_ = true;\n      }\n    }\n    // Ignore any other events -- only BODY is allowed in CONNECT so this\n    // is really about future proofing\n  }\n}\n\nvoid HTTPConnectAsyncTransport::writeChain(WriteCallback* callback,\n                                           std::unique_ptr<folly::IOBuf>&& buf,\n                                           folly::WriteFlags /*flags*/) {\n  if (!writable()) {\n    if (callback) {\n      folly::AsyncSocketException ex(\n          folly::AsyncSocketException::AsyncSocketExceptionType::NOT_OPEN,\n          \"writeChain on closed or bad transport\");\n      callback->writeErr(0, ex);\n    }\n    return;\n  }\n  if (!buf) {\n    return;\n  }\n  egressOffset_ += buf->computeChainDataLength();\n\n  auto fcState = connectStream_->egressSource_->body(std::move(buf), 0, false);\n  if (callback) {\n    if (fcState == HTTPStreamSource::FlowControlState::CLOSED) {\n      writeCallbacks_.emplace_back(egressOffset_, callback);\n    } else {\n      // The only error for this is if !canWrite, which we already checked\n      callback->writeSuccess();\n    }\n  }\n}\n\nvoid HTTPConnectAsyncTransport::shutdownRead() {\n  if (readCallback_) {\n    auto readCallback = readCallback_;\n    readCallback_ = nullptr;\n    readCallback->readEOF();\n  }\n  if (connectStream_->canRead()) {\n    XLOG(DBG4) << \"ingressSource_->stopReading from shutdownRead\";\n    connectStream_->ingressSource_->stopReading();\n  }\n}\n\nvoid HTTPConnectAsyncTransport::shutdownWrite() {\n  if (writable()) {\n    XLOG(DBG4) << \"egressSource_->eom from shutdownWrite\";\n    connectStream_->egressSource_->eom();\n  }\n}\n\nvoid HTTPConnectAsyncTransport::shutdownWriteNow() {\n  if (writeCallbacks_.empty()) {\n    shutdownWrite();\n  } else {\n    errorWrites();\n    // if there were pending writes, abort the stream\n    connectStream_->egressSource_->abort(HTTPErrorCode::CANCEL);\n  }\n}\n\nvoid HTTPConnectAsyncTransport::bytesProcessed(HTTPCodec::StreamID /*id*/,\n                                               size_t amount,\n                                               size_t /*toAck*/) {\n  flushedOffset_ += amount;\n  while (!writeCallbacks_.empty() &&\n         flushedOffset_ >= writeCallbacks_.front().first) {\n    auto callback = writeCallbacks_.front().second;\n    writeCallbacks_.pop_front();\n    callback->writeSuccess();\n  }\n}\n\nvoid HTTPConnectAsyncTransport::errorWrites() {\n  while (!writeCallbacks_.empty()) {\n    auto callback = writeCallbacks_.front().second;\n    writeCallbacks_.pop_front();\n    folly::AsyncSocketException ex(\n        folly::AsyncSocketException::AsyncSocketExceptionType::CANCELED,\n        \"Socket error\");\n    // TODO: could fill in the first arg if we track the size of the write\n    callback->writeErr(0, ex);\n  }\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/AsyncTransport.h>\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/http/coro/transport/HTTPConnectStream.h\"\n\nnamespace proxygen::coro {\n\n/**\n * This class can be used as a folly::AsyncTransport that is built over\n * an HTTP CONNECT tunnel (usually via a forward proxy).  Do not use this\n * directly for establishing an HTTPCoroSession via the proxy (eg, end-to-end\n * HTTPS). For that use HTTPClient::getHTTPSessionViaProxy();\n *\n * Example:\n *\n *  auto proxySession = co_await HTTPClient::getHTTPSession(\n *   evb, proxyHost, proxyPort, useTls, useQuic,\n *   connectTimeout, streamTimeout);\n *  auto reservation = proxySession->reserveRequest();\n *  if (!reservation) {\n *    // error\n *  }\n *  auto connectStream = co_await HTTPConnectStream::connectUnique(\n *    proxySession, std::move(*reservation), destinationHostAndPort,\n *    connectTimeout);\n *  auto transport = std::make_shared<HTTPConnectAsyncTransport>(\n *    std::move(connectStream));\n */\n\nclass HTTPConnectAsyncTransport\n    : public folly::AsyncTransport\n    , public HTTPStreamSource::Callback {\n\n public:\n  explicit HTTPConnectAsyncTransport(\n      std::unique_ptr<HTTPConnectStream> connectStream);\n\n  ~HTTPConnectAsyncTransport() override;\n\n  [[nodiscard]] folly::EventBase* getEventBase() const override {\n    return connectStream_->eventBase_;\n  }\n\n  void setReadCB(ReadCallback* callback) override;\n\n  [[nodiscard]] ReadCallback* getReadCallback() const override {\n    return readCallback_;\n  }\n  void write(WriteCallback* callback,\n             const void* buf,\n             size_t bytes,\n             folly::WriteFlags flags = folly::WriteFlags::NONE) override {\n    writeChain(callback, folly::IOBuf::wrapBuffer(buf, bytes), flags);\n  }\n  void writev(WriteCallback* callback,\n              const iovec* vec,\n              size_t count,\n              folly::WriteFlags flags = folly::WriteFlags::NONE) override {\n    for (size_t i = 0; i < count; i++) {\n      write(callback, vec[i].iov_base, vec[i].iov_len, flags);\n    }\n  }\n  void writeChain(WriteCallback* callback,\n                  std::unique_ptr<folly::IOBuf>&& buf,\n                  folly::WriteFlags flags = folly::WriteFlags::NONE) override;\n\n  void close() override {\n    shutdownWrite();\n    shutdownRead();\n  }\n\n  void closeNow() override {\n    shutdownWriteNow();\n    shutdownRead();\n  }\n  void shutdownWrite() override;\n\n  void shutdownWriteNow() override;\n\n  void shutdownRead();\n\n  [[nodiscard]] bool good() const override {\n    return readable() && writable();\n  }\n  [[nodiscard]] bool readable() const override {\n    // TODO: should only return true when data is available (AsyncSocket calls\n    // poll)\n    return connectStream_->canRead();\n  }\n  [[nodiscard]] bool writable() const override {\n    return connectStream_->canWrite();\n  }\n  [[nodiscard]] bool connecting() const override {\n    // Don't hand off the transport until connected\n    return false;\n  }\n  [[nodiscard]] bool error() const override {\n    return ingressError_ || connectStream_->egressError_;\n  }\n  void attachEventBase(folly::EventBase* /*eventBase*/) override {\n    XLOG(FATAL) << \"Cannot change eventBase\";\n  }\n  void detachEventBase() override {\n    XLOG(FATAL) << \"Cannot change eventBase\";\n  }\n  [[nodiscard]] bool isDetachable() const override {\n    return false;\n  }\n  void setSendTimeout(uint32_t sendTimeoutMs) override {\n    if (connectStream_ && connectStream_->egressSource_) {\n      connectStream_->egressSource_->setReadTimeout(\n          std::chrono::milliseconds(sendTimeoutMs));\n    }\n  }\n  [[nodiscard]] uint32_t getSendTimeout() const override {\n    return connectStream_->egressSource_->getReadTimeout().count();\n  }\n  void getLocalAddress(folly::SocketAddress* address) const override {\n    *address = connectStream_->localAddr_;\n  }\n  void getPeerAddress(folly::SocketAddress* address) const override {\n    *address = connectStream_->peerAddr_;\n  }\n\n  [[nodiscard]] bool isEorTrackingEnabled() const override {\n    return false;\n  }\n\n  void setEorTracking(bool track) override {\n    if (track) {\n      XLOG(WARNING)\n          << \"Cannot enable EOR tracking with HTTPConnectAsyncTransport\";\n    }\n  }\n\n  [[nodiscard]] size_t getAppBytesWritten() const override {\n    return egressOffset_;\n  }\n  [[nodiscard]] size_t getRawBytesWritten() const override {\n    return egressOffset_ /* TODO: + HTTP overhead */;\n  }\n  [[nodiscard]] size_t getAppBytesReceived() const override {\n    return ingressOffset_;\n  }\n  [[nodiscard]] size_t getRawBytesReceived() const override {\n    return ingressOffset_ /* TODO: + HTTP overhead */;\n  }\n\n private:\n  folly::coro::Task<void> read();\n  void errorWrites();\n\n  // resets readCallback_ and returns the previous value\n  ReadCallback* resetReadCb() {\n    return std::exchange(readCallback_, nullptr);\n  }\n\n  /* HTTPStreamSource::Callback overrides */\n  void bytesProcessed(HTTPCodec::StreamID id,\n                      size_t amount,\n                      size_t toAck) override;\n  void sourceComplete(HTTPCodec::StreamID id,\n                      folly::Optional<HTTPError> error) override;\n\n  std::unique_ptr<HTTPConnectStream> connectStream_;\n  ReadCallback* readCallback_{nullptr};\n  folly::Optional<HTTPError> ingressError_;\n  size_t egressOffset_{0};\n  size_t flushedOffset_{0};\n  size_t ingressOffset_{0};\n  std::chrono::milliseconds sendTimeout_{std::chrono::seconds(5)};\n  std::list<std::pair<size_t, WriteCallback*>> writeCallbacks_;\n  folly::CancellationSource cancellationSource_;\n  bool inRead_{false};\n  bool deferredEof_{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectStream.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/HTTPConnectStream.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nHTTPConnectStream::HTTPConnectStream(Ownership ownership,\n                                     HTTPCoroSession* session,\n                                     RequestHeaderMap connectHeaders,\n                                     size_t egressBufferSize)\n    : session_(ownership == Ownership::Unique ? session : nullptr),\n      eventBase_(session->getEventBase()),\n      egressBufferSize_(egressBufferSize),\n      egressSource_(new HTTPStreamSource(\n          session->getEventBase(), folly::none, this, egressBufferSize)),\n      localAddr_(session->getLocalAddress()),\n      peerAddr_(session->getPeerAddress()),\n      connectHeaders_(std::move(connectHeaders)) {\n  egressSource_->setHeapAllocated();\n  if (session_) {\n    session_->addLifecycleObserver(this);\n  }\n}\n\nHTTPConnectStream::~HTTPConnectStream() {\n  close();\n  if (session_) {\n    session_->removeLifecycleObserver(this);\n    session_->initiateDrain();\n  }\n  if (egressSource_) {\n    egressSource_->setCallback(nullptr);\n  }\n}\n\nfolly::coro::Task<std::unique_ptr<HTTPConnectStream>>\nHTTPConnectStream::connect(HTTPCoroSession* session,\n                           HTTPCoroSession::RequestReservation reservation,\n                           std::string authority,\n                           std::chrono::milliseconds timeout,\n                           RequestHeaderMap connectHeaders,\n                           size_t egressBufferSize) {\n  std::unique_ptr<HTTPConnectStream> transport{new HTTPConnectStream(\n      Ownership::Shared, session, std::move(connectHeaders), egressBufferSize)};\n  co_await transport->connectImpl(\n      session, std::move(reservation), std::move(authority), timeout);\n  co_return transport;\n}\n\nfolly::coro::Task<std::unique_ptr<HTTPConnectStream>>\nHTTPConnectStream::connectUnique(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    std::string authority,\n    std::chrono::milliseconds timeout,\n    RequestHeaderMap connectHeaders,\n    size_t egressBufferSize) {\n  std::unique_ptr<HTTPConnectStream> transport{new HTTPConnectStream(\n      Ownership::Unique, session, std::move(connectHeaders), egressBufferSize)};\n  co_await transport->connectImpl(\n      session, std::move(reservation), std::move(authority), timeout);\n  co_return transport;\n}\n\nfolly::coro::Task<void> HTTPConnectStream::connectImpl(\n    HTTPCoroSession* session,\n    HTTPCoroSession::RequestReservation reservation,\n    std::string authority,\n    std::chrono::milliseconds timeout) {\n  auto connectRequest = std::make_unique<HTTPMessage>();\n  connectRequest->setMethod(HTTPMethod::CONNECT);\n  connectRequest->setHTTPVersion(1, 1);\n  uint16_t upstreamPort = 0;\n  auto connectURL = connectRequest->setURL(authority);\n  if (connectURL.valid()) {\n    upstreamPort = connectURL.port();\n  }\n  connectRequest->getHeaders().set(HTTP_HEADER_HOST, authority);\n  for (auto& header : connectHeaders_) {\n    connectRequest->getHeaders().add(header.first, std::move(header.second));\n  }\n  connectHeaders_.clear();\n  egressSource_->headers(std::move(connectRequest), false);\n  ingressSource_ = std::make_unique<HTTPSourceHolder>(\n      co_await session->sendRequest(egressSource_, std::move(reservation)));\n  ingressSource_->setReadTimeout(timeout);\n  while (true) {\n    auto headerEvent = co_await ingressSource_->readHeaderEvent();\n    if (!headerEvent.isFinal()) {\n      continue;\n    }\n    auto status = headerEvent.headers->getStatusCode();\n    if (status / 100 != 2) {\n      egressSource_->abort(HTTPErrorCode::CONNECT_ERROR);\n      co_yield folly::coro::co_error(std::runtime_error(folly::to<std::string>(\n          \"CONNECT to \", authority, \" failed status=\", status)));\n    }\n    auto upstreamAddress =\n        headerEvent.headers->getHeaders().getSingleOrEmpty(\"X-Connected-To\");\n    if (!upstreamAddress.empty()) {\n      try {\n        folly::SocketAddress peerAddr;\n        peerAddr.setFromIpPort(upstreamAddress, upstreamPort);\n        peerAddr_ = std::move(peerAddr);\n      } catch (const std::exception& ex) {\n        XLOG(ERR) << \"Upstream returned invalid X-Connected-To: \"\n                  << upstreamAddress << \" err=\" << ex.what();\n      }\n    }\n    break; // meh\n  }\n  // Successfully connected!\n  egressSource_->setStreamID(*ingressSource_->getStreamID());\n  co_return;\n}\n\nvoid HTTPConnectStream::bytesProcessed(HTTPCodec::StreamID id,\n                                       size_t amount,\n                                       size_t toAck) {\n  if (callback_) {\n    callback_->bytesProcessed(id, amount, toAck);\n  }\n}\n\nvoid HTTPConnectStream::windowOpen(HTTPCodec::StreamID id) {\n  if (callback_) {\n    callback_->windowOpen(id);\n  }\n}\n\nvoid HTTPConnectStream::sourceComplete(HTTPCodec::StreamID id,\n                                       folly::Optional<HTTPError> error) {\n  egressSource_ = nullptr;\n  egressError_ = std::move(error);\n  if (callback_) {\n    callback_->sourceComplete(id, egressError_);\n  }\n}\n\nvoid HTTPConnectStream::shutdownRead() {\n  if (canRead()) {\n    ingressSource_->stopReading();\n  }\n}\n\nvoid HTTPConnectStream::shutdownWrite() {\n  if (canWrite()) {\n    egressSource_->eom();\n  }\n}\n\nvoid HTTPConnectStream::close() {\n  shutdownRead();\n  shutdownWrite();\n}\n\nbool HTTPConnectStream::canRead() const {\n  return *ingressSource_;\n}\n\nbool HTTPConnectStream::canWrite() const {\n  return (egressSource_ && !egressSource_->isEOMSeen() && !egressError_);\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectStream.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/coro/Transport.h>\n\n#include \"proxygen/lib/http/coro/HTTPCoroSession.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSource.h\"\n\nnamespace proxygen::coro {\n\n/**\n * HTTPConnectStream represents an HTTP CONNECT tunnel built over an\n * HTTPCoroSession.  It works with all versions of HTTP according to their\n * CONNECT semantics.\n *\n * This object is used in conjunction with a transport.  There are two such\n * transports: HTTPConnectTransport (folly::coro::Transport) and\n * HTTPConnectAsyncTransport (folly::AsyncTransport).\n *\n * There are two APIs to establish a CONNECT tunnel\n *\n *  - connect, which can be used when another object (eg: HTTPCoroSessionPool)\n *    owns and manages the underlying HTTPCoroSession.  This allows multiplexing\n *    connect streams over protocols like HTTP/2 and HTTP/3.\n *\n *  - connectUnique, which will take ownership of the underlying\n *    HTTPCoroSession and be responsible for its destruction when the tunnel is\n *    done\n */\nclass HTTPConnectStream\n    : public HTTPStreamSource::Callback\n    , public LifecycleObserver {\n public:\n  using RequestHeaderMap = std::map<std::string, std::string>;\n\n  /* Establish a CONNECT tunnel on the session to the given authority.\n   * It is assumed that session is owned by some other entity\n   * (eg: HTTPCoroSessionPool).\n   */\n  static folly::coro::Task<std::unique_ptr<HTTPConnectStream>> connect(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      std::string authority,\n      std::chrono::milliseconds timeout,\n      RequestHeaderMap connectHeaders = RequestHeaderMap(),\n      size_t egressBufferSize = 256 * 1024);\n\n  /* Establish a CONNECT tunnel on the session to the given authority.\n   * Takes ownership of the session and initiate closure when the close is\n   * called.\n   */\n  static folly::coro::Task<std::unique_ptr<HTTPConnectStream>> connectUnique(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      std::string authority,\n      std::chrono::milliseconds timeout,\n      RequestHeaderMap connectHeaders = RequestHeaderMap(),\n      size_t egressBufferSize = 256 * 1024);\n\n  ~HTTPConnectStream() override;\n\n  void setHTTPStreamSourceCallback(HTTPStreamSource::Callback* callback) {\n    callback_ = callback;\n  }\n\n  void close();\n\n  [[nodiscard]] bool canRead() const;\n  [[nodiscard]] bool canWrite() const;\n  void shutdownRead();\n  void shutdownWrite();\n\n  HTTPCoroSession* session_{nullptr};\n  folly::EventBase* eventBase_;\n  size_t egressBufferSize_;\n  HTTPStreamSource* egressSource_{nullptr};\n  std::shared_ptr<HTTPSourceHolder> ingressSource_;\n  folly::SocketAddress localAddr_;\n  folly::SocketAddress peerAddr_;\n  folly::Optional<HTTPError> egressError_;\n\n private:\n  enum class Ownership { Unique, Shared };\n  HTTPConnectStream(Ownership ownership,\n                    HTTPCoroSession* session,\n                    RequestHeaderMap connectHeaders,\n                    size_t egressBufferSize);\n\n  folly::coro::Task<void> connectImpl(\n      HTTPCoroSession* session,\n      HTTPCoroSession::RequestReservation reservation,\n      std::string authority,\n      std::chrono::milliseconds timeout);\n\n  /* HTTPCoroSession::InfoCallback overrides */\n  void onDestroy(const HTTPCoroSession& /*sess*/) override {\n    session_ = nullptr;\n  }\n\n  /* HTTPStreamSource::Callback overrides */\n  void bytesProcessed(HTTPCodec::StreamID id,\n                      size_t amount,\n                      size_t toAck) override;\n  void windowOpen(HTTPCodec::StreamID id) override;\n  void sourceComplete(HTTPCodec::StreamID id,\n                      folly::Optional<HTTPError> error) override;\n\n  RequestHeaderMap connectHeaders_;\n  HTTPStreamSource::Callback* callback_{nullptr};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/HTTPConnectTransport.h\"\n#include <folly/logging/xlog.h>\n\nusing folly::AsyncSocketException;\nusing AsyncSocketExceptionType =\n    folly::AsyncSocketException::AsyncSocketExceptionType;\nusing folly::coro::co_error;\n\nnamespace proxygen::coro {\n\nHTTPConnectTransport::HTTPConnectTransport(\n    std::unique_ptr<HTTPConnectStream> connectStream)\n    : connectStream_(std::move(connectStream)),\n      flowControlWindowOpen_(connectStream_->eventBase_,\n                             std::chrono::milliseconds(0)) {\n  if (connectStream_->egressBufferSize_ > 0) {\n    flowControlWindowOpen_.signal();\n  }\n  connectStream_->setHTTPStreamSourceCallback(this);\n}\n\nHTTPConnectTransport::~HTTPConnectTransport() {\n  flowControlWindowOpen_.signal(TimedBaton::Status::cancelled);\n  *deleted_ = true;\n}\n\nvoid HTTPConnectTransport::scheduleAsyncRead(\n    uint32_t size, const folly::CancellationToken& ct) {\n  // indefinite read timeout on ingressSource; we install a custom timeout for\n  // yielding AsyncSocketException in ::read()\n  auto ingressSource = connectStream_->ingressSource_;\n  auto* evb = connectStream_->eventBase_;\n  ingressSource->setReadTimeout(std::chrono::milliseconds(0));\n  co_withExecutor(\n      evb,\n      folly::coro::co_withCancellation(ct, ingressSource->readBodyEvent(size)))\n      .startInlineUnsafe(\n          [this, ingressSource, deleted = deleted_](\n              folly::Try<HTTPBodyEvent> maybeBodyEvent) {\n            XLOG(DBG6) << \"async readBodyEvent done\";\n            if (*deleted) {\n              return;\n            }\n            // wrap HTTPError in AsyncSocketException\n            if (maybeBodyEvent.hasException()) {\n              auto httpErr = getHTTPError(maybeBodyEvent);\n              maybeBodyEvent.emplaceException(AsyncSocketException(\n                  AsyncSocketException::INTERNAL_ERROR, httpErr.describe()));\n            }\n            XCHECK(bodyEvents_.try_enqueue(std::move(maybeBodyEvent)));\n            pendingRead_ = false;\n          },\n          ct);\n}\n\nfolly::coro::Task<size_t> HTTPConnectTransport::read(\n    folly::MutableByteRange buf, std::chrono::milliseconds timeout) {\n  co_await folly::coro::co_safe_point;\n  const bool hasMoreData = connectStream_->canRead() || !bodyEvents_.empty();\n  if (!hasMoreData || buf.size() == 0) {\n    co_return 0;\n  }\n\n  /**\n   * HTTPConnectTransport::read effectively reads from an ingress source (of\n   * concrete type HTTPStreamSource) owned by the client<->proxy\n   * HTTPCoroSession.\n   *\n   * A transport read timeout is a recoverable error; HTTPCoroSession uses this\n   * signal to initiate a drain and wait for more data from the peer. However a\n   * read timeout in HTTPStreamSource::readBodyEvent is an unrecoverable error,\n   * where it drops any currently queued and future ingress events.\n   *\n   * To avoid this mismatch in semantics, we set an infinite read timeout on the\n   * HTTPStreamSource ingress source and schedule our own. On timeout, we\n   * enqueue and yield a timeout exception, however the ingress source will\n   * still be readable\n   */\n  class ReadTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit ReadTimeout(HTTPConnectTransport& transportRef)\n        : transportRef_(transportRef) {\n    }\n    void timeoutExpired() noexcept override {\n      // only enqueue timeout ex if we haven't resolved a bodyEvent yet\n      if (transportRef_.bodyEvents_.empty()) {\n        auto timeoutEx = folly::Try<HTTPBodyEvent>(\n            AsyncSocketException(AsyncSocketException::TIMED_OUT,\n                                 \"Timed out waiting for body event\"));\n        XCHECK(transportRef_.bodyEvents_.try_enqueue(std::move(timeoutEx)));\n      }\n    }\n\n   private:\n    HTTPConnectTransport& transportRef_;\n  } readTimeout{*this};\n\n  size_t nRead = 0;\n  bool eom = false;\n  auto* evb = connectStream_->eventBase_;\n  const auto& ct = co_await folly::coro::co_current_cancellation_token;\n  do {\n    // schedule at most one outstanding ::readBodyEvent on ingress source\n    bool wasScheduled = std::exchange(pendingRead_, true);\n    if (!wasScheduled && connectStream_->canRead()) {\n      scheduleAsyncRead(buf.size(), ct);\n    }\n    if (timeout.count() > 0) {\n      evb->timer().scheduleTimeout(&readTimeout, timeout);\n    }\n    // UnboundedQueue::dequeue will only yield an exception upon cancellation,\n    // passthru cancellation exception to caller\n    auto bodyEvent = co_await bodyEvents_.dequeue();\n    readTimeout.cancelTimeout(); // no-op if not scheduled (i.e. timeout == 0ms)\n\n    if (bodyEvent.hasException()) {\n      co_yield co_error(bodyEvent.exception());\n    }\n    eom = bodyEvent->eom;\n    if (bodyEvent->eventType == HTTPBodyEvent::BODY &&\n        !bodyEvent->event.body.empty()) {\n      folly::io::Cursor c(bodyEvent->event.body.front());\n      nRead = c.pullAtMost(buf.start(), buf.size());\n    }\n  } while (nRead == 0 && !eom);\n  co_return nRead;\n}\n\nfolly::coro::Task<size_t> HTTPConnectTransport::read(\n    folly::IOBufQueue& buf,\n    size_t minReadSize,\n    size_t newAllocationSize,\n    std::chrono::milliseconds timeout) {\n  co_await folly::coro::co_safe_point;\n  const bool hasMoreData = connectStream_->canRead() || !bodyEvents_.empty();\n  if (!hasMoreData) {\n    co_return 0;\n  }\n  auto rbuf = buf.preallocate(minReadSize, newAllocationSize);\n  auto rc = co_await read(\n      folly::MutableByteRange((uint8_t*)rbuf.first, rbuf.second), timeout);\n  buf.postallocate(rc);\n  co_return rc;\n}\n\nfolly::coro::Task<folly::Unit> HTTPConnectTransport::write(\n    folly::ByteRange buf,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo* writeInfo) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  writeBuf.append(buf.start(), buf.size());\n  co_await write(writeBuf, timeout, writeFlags, writeInfo);\n  co_return folly::Unit();\n}\n\nfolly::coro::Task<folly::Unit> HTTPConnectTransport::write(\n    folly::IOBufQueue& ioBufQueue,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags /*writeFlags*/,\n    WriteInfo* writeInfo) {\n  co_await folly::coro::co_safe_point;\n  writeInProgress_ = true;\n  auto deleted = deleted_;\n  SCOPE_EXIT {\n    if (!*deleted) {\n      writeInProgress_ = false;\n    }\n  };\n  auto buf = ioBufQueue.front()->clone();\n  auto size = ioBufQueue.chainLength();\n  if (!connectStream_->canWrite()) {\n    if (connectStream_->egressError_) {\n      co_yield co_error(\n          AsyncSocketException(AsyncSocketException::NOT_OPEN,\n                               connectStream_->egressError_->describe()));\n    } else {\n      co_yield co_error(AsyncSocketException(AsyncSocketException::NOT_OPEN,\n                                             \"write after close\"));\n    }\n  }\n  if (timeout.count() > 0) {\n    flowControlWindowOpen_.setTimeout(timeout);\n  }\n  auto res = co_await flowControlWindowOpen_.wait();\n  // Could get fancy with setting a timer that's cleared in bytesProcessed\n  if (res == TimedBaton::Status::timedout) {\n    co_yield co_error(AsyncSocketException(AsyncSocketException::TIMED_OUT,\n                                           \"Write timed out\"));\n  } else if (res == TimedBaton::Status::cancelled) {\n    co_yield folly::coro::co_stopped_may_throw;\n  }\n  if (!connectStream_->egressSource_) {\n    co_yield co_error(AsyncSocketException(\n        AsyncSocketException::NOT_OPEN, \"HTTPConnectTransport: egress closed\"));\n  }\n  auto flowState =\n      connectStream_->egressSource_->body(std::move(buf), 0, pendingEOM_);\n  if (flowState != HTTPStreamSource::FlowControlState::OPEN) {\n    XLOG(DBG4) << \"Blocking writes\";\n    flowControlWindowOpen_.reset();\n  }\n  if (writeInfo) {\n    writeInfo->bytesWritten = size;\n  }\n  co_return folly::Unit();\n}\n\nvoid HTTPConnectTransport::windowOpen(HTTPCodec::StreamID /*id*/) {\n  XLOG(DBG4) << \"Resuming writes\";\n  flowControlWindowOpen_.signal();\n}\n\nvoid HTTPConnectTransport::close() {\n  shutdownWrite();\n  connectStream_->shutdownRead();\n}\n\nvoid HTTPConnectTransport::shutdownWrite() {\n  if (connectStream_->canWrite()) {\n    if (flowControlWindowOpen_.getStatus() == TimedBaton::Status::notReady &&\n        writeInProgress_) {\n      pendingEOM_ = true;\n    } else {\n      connectStream_->egressSource_->eom();\n    }\n  } // maybe close called twice, or close called after error/abort?\n}\n\nvoid HTTPConnectTransport::closeWithReset() {\n  if (connectStream_->egressSource_) {\n    connectStream_->egressSource_->abort(HTTPErrorCode::CANCEL);\n  }\n  connectStream_->close();\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/HTTPConnectTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/coro/BoundedQueue.h>\n#include <folly/io/coro/Transport.h>\n\n#include \"proxygen/lib/http/coro/transport/HTTPConnectStream.h\"\n\nnamespace proxygen::coro {\n\n/**\n * This class can be used as a folly::coro::Transport that is built over\n * an HTTP CONNECT tunnel (usually via a forward proxy).  Do not use this\n * directly for establishing an HTTPCoroSession via the proxy (eg, end-to-end\n * HTTPS). For that use HTTPClient::getHTTPSessionViaProxy();\n *\n * Example:\n *\n *  auto proxySession = co_await HTTPClient::getHTTPSession(\n *   evb, proxyHost, proxyPort, useTls, useQuic,\n *   connectTimeout, streamTimeout);\n *  auto reservation = proxySession->reserveRequest();\n *  if (!reservation) {\n *    // error\n *  }\n *  auto connectStream = co_await HTTPConnectStream::connectUnique(\n *    proxySession, std::move(*reservation), destinationHostAndPort,\n *    connectTimeout);\n *  auto transport = std::make_shared<HTTPConnectTransport>(\n *    std::move(connectStream));\n */\nclass HTTPConnectTransport\n    : public folly::coro::TransportIf\n    , public HTTPStreamSource::Callback {\n public:\n  explicit HTTPConnectTransport(\n      std::unique_ptr<HTTPConnectStream> connectStream);\n\n  ~HTTPConnectTransport() override;\n\n  /* TransportIf overrides */\n  folly::EventBase* getEventBase() noexcept override {\n    return connectStream_->eventBase_;\n  }\n\n  folly::coro::Task<size_t> read(folly::MutableByteRange buf,\n                                 std::chrono::milliseconds timeout =\n                                     std::chrono::milliseconds(0)) override;\n\n  folly::coro::Task<size_t> read(folly::IOBufQueue& buf,\n                                 size_t minReadSize,\n                                 size_t newAllocationSize,\n                                 std::chrono::milliseconds timeout =\n                                     std::chrono::milliseconds(0)) override;\n\n  folly::coro::Task<folly::Unit> write(\n      folly::ByteRange buf,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo* writeInfo = nullptr) override;\n  folly::coro::Task<folly::Unit> write(\n      folly::IOBufQueue& ioBufQueue,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo* writeInfo = nullptr) override;\n\n  folly::SocketAddress getLocalAddress() const noexcept override {\n    return connectStream_->localAddr_;\n  }\n\n  folly::SocketAddress getPeerAddress() const noexcept override {\n    return connectStream_->peerAddr_;\n  }\n\n  void close() override;\n  void shutdownWrite() override;\n  void closeWithReset() override;\n  folly::AsyncTransport* getTransport() const override {\n    // This is used for TCP socket info\n    return nullptr;\n  }\n  const folly::AsyncTransportCertificate* getPeerCertificate() const override {\n    // TODO: HTTPCoroSession needs to expose this?\n    return nullptr;\n  }\n\n private:\n  void scheduleAsyncRead(uint32_t size, const folly::CancellationToken& ct);\n  /* HTTPStreamSource::Callback overrides */\n  void windowOpen(HTTPCodec::StreamID id) override;\n\n  std::unique_ptr<HTTPConnectStream> connectStream_;\n  TimedBaton flowControlWindowOpen_;\n  std::shared_ptr<bool> deleted_{std::make_shared<bool>(false)};\n  static constexpr uint8_t kMaxEvents{2};\n  folly::coro::BoundedQueue<folly::Try<HTTPBodyEvent>,\n                            /*SingleProducer=*/true,\n                            /*SingleConsumer=*/true>\n      bodyEvents_{kMaxEvents};\n  bool writeInProgress_{false};\n  bool pendingEOM_{false};\n  bool pendingRead_{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/Client.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/CoroSSLTransport.h\"\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/logging/xlog.h>\n#include <folly/portability/GFlags.h>\n#include <iostream>\n\nusing namespace proxygen::coro;\n\nclass Callback : public folly::AsyncSocket::ConnectCallback {\n public:\n  bool connected{false};\n\n  void connectSuccess() noexcept override {\n    XLOG(INFO) << \"Connected\";\n    connected = true;\n  }\n\n  void connectErr(const folly::AsyncSocketException& ex) noexcept override {\n    XLOG(ERR) << \"Connect failed: \" << ex.what();\n  }\n};\n\nint main(int argc, char** argv) {\n  const folly::Init init(&argc, &argv);\n  ::gflags::ParseCommandLineFlags(&argc, &argv, false);\n\n  if (argc < 2) {\n    XLOG(ERR) << \"Usage: async_ssl_transport_client server[:port]\";\n    return 1;\n  }\n\n  folly::SocketAddress serverAddr;\n  serverAddr.setFromHostPort(argv[1]);\n\n  folly::EventBase evb;\n  auto socket = folly::AsyncSocket::newSocket(&evb);\n  Callback callback;\n  socket->connect(&callback, serverAddr, 1000);\n  evb.loop();\n  if (callback.connected) {\n    auto transport = std::make_unique<CoroSSLTransport>(\n        std::make_unique<folly::coro::Transport>(&evb, std::move(socket)),\n        std::make_shared<folly::SSLContext>());\n    co_withExecutor(\n        &evb,\n        [](std::unique_ptr<CoroSSLTransport> transport)\n            -> folly::coro::Task<void> {\n          co_await transport->connect(folly::none, std::chrono::seconds(1));\n          std::string getRequest(\"GET / HTTP/1.0\\r\\n\\r\\n\");\n          co_await transport->write(\n              folly::ByteRange(reinterpret_cast<uint8_t*>(getRequest.data()),\n                               getRequest.length()));\n          folly::IOBufQueue readBuf{folly::IOBufQueue::cacheChainLength()};\n          auto nRead = co_await transport->read(\n              readBuf, 1000, 4000, std::chrono::seconds(1));\n          readBuf.gather(nRead);\n          std::cout << readBuf.move()->moveToFbString();\n        }(std::move(transport)))\n        .start();\n    evb.loop();\n  }\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/CoroSSLTransportTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/CoroSSLTransport.h\"\n#include <folly/logging/xlog.h>\n\n#include <folly/Portability.h>\n\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Collect.h>\n#include <folly/coro/Sleep.h>\n#include <folly/io/async/ssl/BasicTransportCertificate.h>\n#include <folly/io/async/test/AsyncSSLSocketTest.h>\n#include <folly/io/async/test/TestSSLServer.h>\n#include <folly/io/coro/ServerSocket.h>\n#include <folly/io/coro/Transport.h>\n#include <folly/portability/GTest.h>\n#include <folly/testing/TestUtil.h>\n\n#include \"proxygen/lib/http/coro/transport/test/TestCoroTransport.h\"\n\nusing namespace std::chrono_literals;\nusing namespace folly;\nusing namespace folly::coro;\nusing namespace proxygen::coro::test;\nusing folly::ssl::OpenSSLUtils;\nusing folly::test::find_resource;\n\ntemplate <size_t SIZE>\nfolly::coro::Task<Unit> readAll(proxygen::coro::CoroSSLTransport& transport,\n                                std::array<uint8_t, SIZE>& rcvBuf,\n                                std::chrono::milliseconds timeout) {\n  size_t totalBytes{0};\n  while (totalBytes < SIZE) {\n    auto bytesRead = co_await transport.read(\n        MutableByteRange(rcvBuf.data() + totalBytes,\n                         (rcvBuf.data() + rcvBuf.size())),\n        timeout);\n    totalBytes += bytesRead;\n  }\n  co_return unit;\n};\n\nclass FlexibleWriteCallback : public folly::test::WriteCallbackBase {\n public:\n  ~FlexibleWriteCallback() override {\n    if (errorOk) {\n      state = folly::test::STATE_SUCCEEDED;\n    }\n  }\n\n  bool errorOk{false};\n};\n\nclass FlexibleReadCallback : public folly::test::ReadCallback {\n public:\n  explicit FlexibleReadCallback(folly::test::WriteCallbackBase* wcb)\n      : ReadCallback(wcb) {\n  }\n\n  ~FlexibleReadCallback() override {\n    if (errorOk) {\n      state = folly::test::STATE_SUCCEEDED;\n    }\n  }\n\n  bool errorOk{false};\n};\n\nclass FlexibleHandshakeCallback : public folly::test::HandshakeCallback {\n public:\n  explicit FlexibleHandshakeCallback(folly::test::ReadCallback* wcb)\n      : HandshakeCallback(wcb) {\n  }\n\n  ~FlexibleHandshakeCallback() override {\n    if (errorOk) {\n      state = folly::test::STATE_SUCCEEDED;\n    }\n  }\n\n  bool errorOk{false};\n};\n\nnamespace proxygen::coro {\n\nclass TransportTest : public testing::Test {\n public:\n  template <typename F>\n  void run(F f) {\n    blockingWait(co_invoke(std::move(f)), &evb);\n  }\n\n  folly::coro::Task<> requestCancellation() {\n    cancelSource.requestCancellation();\n    co_return;\n  }\n\n  EventBase evb;\n  CancellationSource cancelSource;\n};\n\nclass CoroSSLTransportTest : public TransportTest {\n public:\n  CoroSSLTransportTest()\n      : srv{&acceptCallback,\n            folly::test::TestSSLServer::getDefaultSSLContext()} {\n  }\n\n  folly::coro::Task<std::unique_ptr<CoroSSLTransport>> connect(\n      folly::Optional<folly::SocketAddress> addr = folly::none) {\n    auto socket = co_await Transport::newConnectedSocket(\n        &evb, addr ? *addr : srv.getAddress(), 0ms);\n\n    auto transport = std::make_unique<CoroSSLTransport>(\n        std::make_unique<folly::coro::Transport>(std::move(socket)),\n        sslCtx,\n        transportOptions);\n    transport->setVerificationOption(verifyPeer);\n    if (session) {\n      transport->setSSLSession(session);\n    }\n    co_await transport->connect(sni, std::chrono::seconds(2));\n    co_return transport;\n  }\n  FlexibleWriteCallback writeCallback;\n  folly::Optional<folly::test::ReadErrorCallback> readErrorCallback;\n  FlexibleReadCallback readCallback{&writeCallback};\n  FlexibleHandshakeCallback handshakeCallback{&readCallback};\n  folly::test::SSLServerAcceptCallback acceptCallback{&handshakeCallback};\n  folly::test::TestSSLServer srv;\n\n  std::shared_ptr<folly::SSLContext> sslCtx{\n      std::make_shared<folly::SSLContext>()};\n  folly::SSLContext::SSLVerifyPeerEnum verifyPeer{folly::SSLContext::USE_CTX};\n  CoroSSLTransport::TransportOptions transportOptions;\n  std::shared_ptr<ssl::SSLSession> session;\n  folly::Optional<std::string> sni;\n};\n\nTEST_F(CoroSSLTransportTest, ConnectFailure) {\n  folly::test::HandshakeErrorCallback handshakeErrorCallback(\n      &handshakeCallback);\n  folly::test::TestSSLServer badSrv(&handshakeErrorCallback);\n  run([&]() -> Task<> {\n    EXPECT_THROW(co_await connect(badSrv.getAddress()), AsyncSocketException);\n  });\n  handshakeErrorCallback.state = folly::test::STATE_SUCCEEDED;\n  acceptCallback.state = folly::test::STATE_SUCCEEDED;\n}\n\nTEST_F(CoroSSLTransportTest, ConnectSuccess) {\n  run([&]() -> Task<> {\n    sni = \"example.com\";\n    auto cs = co_await connect();\n    EXPECT_EQ(srv.getAddress(), cs->getPeerAddress());\n    readCallback.setState(folly::test::STATE_SUCCEEDED);\n    auto socket = handshakeCallback.getSocket();\n    EXPECT_EQ(socket->getSSLServerName(), *sni);\n    folly::SocketAddress serverPeerAddr;\n    socket->getPeerAddress(&serverPeerAddr);\n    EXPECT_EQ(serverPeerAddr, cs->getLocalAddress());\n    EXPECT_EQ(cs->getApplicationProtocol(), \"\");\n    EXPECT_EQ(cs->getSSLVersion(), TLS1_3_VERSION);\n    EXPECT_EQ(cs->getNegotiatedCipherName(),\n              std::string(\"TLS_AES_256_GCM_SHA384\"));\n    co_await folly::coro::co_reschedule_on_current_executor;\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ConnectVerifySuccess) {\n  readCallback.errorOk = true;\n  writeCallback.errorOk = true;\n\n  sslCtx->loadTrustedCertificates(find_resource(folly::test::kTestCA).c_str());\n  auto verifier =\n      std::make_shared<folly::test::MockCertificateIdentityVerifier>();\n  transportOptions.verifier = verifier;\n  verifyPeer = folly::SSLContext::VERIFY;\n  EXPECT_CALL(*verifier, verifyLeaf(testing::_))\n      .WillOnce(testing::Return(testing::ByMove(\n          std::make_unique<folly::ssl::BasicTransportCertificate>(\n              \"Asox Company\", nullptr))));\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    EXPECT_EQ(cs->getPeerCertificate()->getIdentity(), \"Asox Company\");\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ConnectResume) {\n  readCallback.errorOk = true;\n  writeCallback.errorOk = true;\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    readCallback.setState(folly::test::STATE_SUCCEEDED);\n    EXPECT_FALSE(cs->getSSLSessionReused());\n\n    constexpr auto kBufSize = 65536;\n\n    // The following is copied from the CoroSSLTransportTest.SimpleReadWrite\n    // test. Perform a round of write/read. This will ensure session tickets are\n    // read when using TLS 1.3.\n    std::array<uint8_t, kBufSize> rcvBuf;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    folly::coro::TransportIf::WriteInfo info;\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms,\n                       folly::WriteFlags::NONE,\n                       &info);\n    EXPECT_EQ(info.bytesWritten, sndBuf.size());\n    co_await readAll(*cs, rcvBuf, 0ms);\n    EXPECT_EQ(0, memcmp(sndBuf.data(), rcvBuf.data(), rcvBuf.size()));\n    EXPECT_EQ(co_await cs->read(folly::MutableByteRange(nullptr, nullptr)), 0);\n    cs->close();\n\n    // the session can then be reused\n    session = cs->getSSLSession();\n    EXPECT_TRUE(session != nullptr);\n\n    auto cs2 = co_await connect();\n    EXPECT_TRUE(cs2->getSSLSessionReused());\n    cs2->close();\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ConnectVerifyFailure) {\n  handshakeCallback.expect_ = folly::test::HandshakeCallback::EXPECT_ERROR;\n  readCallback.errorOk = true;\n  writeCallback.errorOk = true;\n\n  // Socket uses context set to verify\n  verifyPeer = folly::SSLContext::USE_CTX;\n  sslCtx->setVerificationOption(folly::SSLContext::VERIFY);\n  run([&]() -> Task<> {\n    EXPECT_THROW(co_await connect(), AsyncSocketException);\n  });\n\n  // CTX wants to checkPeerName but the name mismatches\n  sslCtx->authenticate(true, true, \"nope\");\n  sslCtx->loadTrustedCertificates(find_resource(folly::test::kTestCA).c_str());\n  run([&]() -> Task<> {\n    EXPECT_THROW(co_await connect(), AsyncSocketException);\n  });\n\n  // Socket overrides context and verifier fails\n  sslCtx->loadTrustedCertificates(find_resource(folly::test::kTestCA).c_str());\n  auto verifier =\n      std::make_shared<folly::test::MockCertificateIdentityVerifier>();\n  transportOptions.verifier = verifier;\n  verifyPeer = folly::SSLContext::VERIFY;\n  EXPECT_CALL(*verifier, verifyLeaf(testing::_))\n      .WillOnce(\n          testing::Throw(folly::CertificateIdentityVerifierException(\"bad\")));\n  run([&]() -> Task<> {\n    EXPECT_THROW(co_await connect(), AsyncSocketException);\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ConnectCancelled) {\n  handshakeCallback.errorOk = true;\n  readCallback.errorOk = true;\n  writeCallback.errorOk = true;\n  acceptCallback.state = folly::test::STATE_SUCCEEDED;\n  run([&]() -> Task<> {\n    co_await folly::coro::collectAll(\n        // token would be cancelled while waiting on connect\n        [&]() -> Task<> {\n          EXPECT_THROW(\n              co_await co_withCancellation(cancelSource.getToken(), connect()),\n              OperationCancelled);\n        }(),\n        requestCancellation());\n    // token was cancelled before read was called\n    EXPECT_THROW(co_await co_withCancellation(cancelSource.getToken(),\n                                              Transport::newConnectedSocket(\n                                                  &evb, srv.getAddress(), 0ms)),\n                 OperationCancelled);\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ConnectEOF) {\n  class HandshakeEOFCallback : public folly::test::SSLServerAcceptCallbackBase {\n   public:\n    explicit HandshakeEOFCallback(folly::test::HandshakeCallback* hcb)\n        : SSLServerAcceptCallbackBase(hcb) {\n    }\n\n    void connAccepted(\n        const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {\n      s->close();\n      state = folly::test::STATE_SUCCEEDED;\n    }\n  };\n\n  HandshakeEOFCallback handshakeEOFCallback(&handshakeCallback);\n  folly::test::TestSSLServer badSrv(&handshakeEOFCallback);\n  run([&]() -> Task<> {\n    EXPECT_THROW(co_await connect(badSrv.getAddress()), AsyncSocketException);\n  });\n  acceptCallback.state = folly::test::STATE_SUCCEEDED;\n  readCallback.errorOk = true;\n  writeCallback.errorOk = true;\n  handshakeCallback.errorOk = true;\n}\n\nTEST_F(CoroSSLTransportTest, SimpleReadWrite) {\n  run([&]() -> Task<> {\n    constexpr auto kBufSize = 65536;\n    auto cs = co_await connect();\n\n    // read using coroutines\n    std::array<uint8_t, kBufSize> rcvBuf;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    folly::coro::TransportIf::WriteInfo info;\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms,\n                       folly::WriteFlags::NONE,\n                       &info);\n    EXPECT_EQ(info.bytesWritten, sndBuf.size());\n    co_await readAll(*cs, rcvBuf, 0ms);\n    EXPECT_EQ(0, memcmp(sndBuf.data(), rcvBuf.data(), rcvBuf.size()));\n\n    // Read with 0 length returns immediately\n    EXPECT_EQ(co_await cs->read(folly::MutableByteRange(nullptr, nullptr)), 0);\n  });\n}\n\nTEST_F(CoroSSLTransportTest, SimpleIOBufReadWrite) {\n  run([&]() -> Task<> {\n    // Exactly fills a buffer mid-loop and triggers deferredReadEOF handling\n    constexpr auto kBufSize = 55 * 1184;\n\n    auto cs = co_await connect();\n\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    folly::coro::TransportIf::WriteInfo info;\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms,\n                       folly::WriteFlags::NONE,\n                       &info);\n    EXPECT_EQ(info.bytesWritten, sndBuf.size());\n\n    // read using coroutines\n    IOBufQueue rcvBuf(IOBufQueue::cacheChainLength());\n    int totalBytes{0};\n    while (totalBytes < kBufSize) {\n      auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 0ms);\n      totalBytes += bytesRead;\n    }\n    auto socket = handshakeCallback.getSocket();\n    socket->getEventBase()->runInEventBaseThread([socket] { socket->close(); });\n    auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 50ms);\n    EXPECT_EQ(bytesRead, 0); // closed\n\n    auto data = rcvBuf.move();\n    data->coalesce();\n    EXPECT_EQ(0, memcmp(sndBuf.data(), data->data(), data->length()));\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ReadCancelled) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    auto reader = [&cs]() -> Task<Unit> {\n      std::array<uint8_t, 1024> rcvBuf;\n      EXPECT_THROW(\n          co_await cs->read(\n              MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n              0ms),\n          OperationCancelled);\n      co_return unit;\n    };\n\n    co_await co_withCancellation(\n        cancelSource.getToken(),\n        folly::coro::collectAll(requestCancellation(), reader()));\n    // token was cancelled before read was called\n    co_await co_withCancellation(cancelSource.getToken(), reader());\n    // This allows pending writes to get \"flushed\", so that when that when the\n    // CoroSSLTransport is destroyed at the end of this scope, the close_notify\n    // can be written (rather than be added to the pending writes that will\n    // performed).\n    co_await folly::coro::co_reschedule_on_current_executor;\n  });\n  readCallback.setState(folly::test::STATE_SUCCEEDED);\n  writeCallback.state = folly::test::STATE_SUCCEEDED;\n}\n\nTEST_F(CoroSSLTransportTest, ReadTimeout) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    std::array<uint8_t, 1024> rcvBuf;\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            50ms),\n        AsyncSocketException);\n  });\n  readCallback.setState(folly::test::STATE_SUCCEEDED);\n  writeCallback.state = folly::test::STATE_SUCCEEDED;\n}\n\nTEST_F(CoroSSLTransportTest, ReadError) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    auto socket = handshakeCallback.getSocket();\n    socket->getEventBase()->runInEventBaseThread([socket] {\n      // closeWithReset was still giving 0 return like FIN?\n      struct linger optLinger = {1, 0};\n      socket->setSockOpt(SOL_SOCKET, SO_LINGER, &optLinger);\n      socket->AsyncSocket::closeNow();\n    });\n\n    std::array<uint8_t, 1024> rcvBuf;\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            50ms),\n        AsyncSocketException);\n  });\n  readCallback.setState(folly::test::STATE_SUCCEEDED);\n  writeCallback.state = folly::test::STATE_SUCCEEDED;\n}\n\nTEST_F(CoroSSLTransportTest, SimpleWritev) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    IOBufQueue sndBuf{folly::IOBufQueue::cacheChainLength()};\n    constexpr auto kBufSize = 65536;\n    std::array<uint8_t, kBufSize> bufA;\n    std::memset(bufA.data(), 'a', bufA.size());\n    std::array<uint8_t, kBufSize> bufB;\n    std::memset(bufB.data(), 'b', bufB.size());\n    sndBuf.append(bufA.data(), bufA.size());\n    sndBuf.append(bufB.data(), bufB.size());\n\n    // write use co-routine\n    Transport::WriteInfo info;\n    co_await cs->write(sndBuf, 500ms, folly::WriteFlags::NONE, &info);\n    // read\n    std::array<uint8_t, kBufSize> rcvBufA;\n    co_await readAll(*cs, rcvBufA, 500ms);\n    EXPECT_EQ(0, memcmp(bufA.data(), rcvBufA.data(), rcvBufA.size()));\n    std::array<uint8_t, kBufSize> rcvBufB;\n    co_await readAll(*cs, rcvBufB, 500ms);\n    EXPECT_EQ(0, memcmp(bufB.data(), rcvBufB.data(), rcvBufB.size()));\n  });\n}\n\nTEST_F(CoroSSLTransportTest, WriteCancelled) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    // reduce the send buffer size so the write wouldn't complete immediately\n    auto asyncSocket = dynamic_cast<folly::AsyncSocket*>(cs->getTransport());\n    XCHECK(asyncSocket);\n    EXPECT_EQ(asyncSocket->setSendBufSize(4096), 0);\n\n    constexpr auto kBufSize = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    auto writer = [&]() -> Task<> {\n      EXPECT_THROW(co_await co_withCancellation(\n                       cancelSource.getToken(),\n                       cs->write(ByteRange(sndBuf.data(),\n                                           sndBuf.data() + sndBuf.size()))),\n                   OperationCancelled);\n    };\n\n    co_await folly::coro::collectAll(requestCancellation(), writer());\n    co_await co_withCancellation(cancelSource.getToken(), writer());\n  });\n  writeCallback.errorOk = true;\n  readCallback.errorOk = true;\n}\n\nTEST_F(CoroSSLTransportTest, ShutdownWrite) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    readCallback.setState(folly::test::STATE_SUCCEEDED);\n    // Sends shutdown\n    cs->shutdownWrite();\n    IOBufQueue rcvBuf(IOBufQueue::cacheChainLength());\n    // Wait for peer to shutdown\n    auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 500ms);\n    EXPECT_EQ(bytesRead, 0); // closed\n  });\n}\n\nTEST_F(CoroSSLTransportTest, CloseWithReadsWrites) {\n  writeCallback.errorOk = true;\n  readCallback.errorOk = true;\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    constexpr auto kBufSize = 256 * 1024;\n    std::array<uint8_t, 1024> rcvBuf;\n    auto readFut =\n        co_withExecutor(\n            &evb,\n            cs->read(MutableByteRange(rcvBuf.data(),\n                                      (rcvBuf.data() + rcvBuf.size())),\n                     0ms))\n            .start();\n\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    auto writeFut =\n        co_withExecutor(\n            &evb,\n            cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                      0ms))\n            .start();\n    // Get the write started, but blocking\n    co_await folly::coro::co_reschedule_on_current_executor;\n\n    cs->close();\n    // Writes complete normally\n    co_await std::move(writeFut);\n    // The read is either cancelled, or completes normally\n    try {\n      co_await std::move(readFut);\n    } catch (OperationCancelled&) {\n    }\n  });\n}\n\nTEST_F(CoroSSLTransportTest, Close) {\n  writeCallback.errorOk = true;\n  readCallback.errorOk = true;\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    cs->close();\n  });\n}\n\nTEST_F(CoroSSLTransportTest, CloseWithReset) {\n  readCallback.state = folly::test::STATE_SUCCEEDED;\n  readErrorCallback.emplace(&writeCallback);\n  handshakeCallback.rcb_ = readErrorCallback.get_pointer();\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    cs->closeWithReset();\n  });\n}\n\nTEST_F(CoroSSLTransportTest, AttemptWriteWithPendingShutdown) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    // Just enough so that we get WANT_WRITE\n    constexpr auto kBufSize = 132 * 1024;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    auto writeFut =\n        co_withExecutor(\n            &evb,\n            cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                      0ms))\n            .start();\n    // Get the write started, but blocking\n    co_await folly::coro::co_reschedule_on_current_executor;\n\n    // Initiate shutdown, which is pending\n    cs->shutdownWrite();\n\n    // Attempting to write now will fail\n    EXPECT_THROW(\n        co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + 1), 0ms),\n        AsyncSocketException);\n    IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n    writeBuf.append(sndBuf.data(), 1);\n    EXPECT_THROW(co_await cs->write(writeBuf, 0ms), AsyncSocketException);\n\n    // Writes complete normally\n    co_await std::move(writeFut);\n    // Read everything the server echoes back\n    std::array<uint8_t, kBufSize> rcvBuf;\n    size_t nRead = 0;\n    do {\n      nRead = co_await cs->read(MutableByteRange(rcvBuf.data(), rcvBuf.size()));\n    } while (nRead != 0);\n  });\n}\n\nTEST_F(CoroSSLTransportTest, ResetWithPendingWrites) {\n  writeCallback.errorOk = true;\n  readCallback.errorOk = true;\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    // Just enough so that we get WANT_WRITE\n    constexpr auto kBufSize = 132 * 1024;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    auto writeFut =\n        co_withExecutor(\n            &evb,\n            cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                      0ms))\n            .start();\n    // Get the write started, but blocking\n    co_await folly::coro::co_reschedule_on_current_executor;\n    cs->closeWithReset();\n    EXPECT_THROW(co_await std::move(writeFut), AsyncSocketException);\n  });\n}\n} // namespace proxygen::coro\n\nnamespace {\n\n/* === BIO wrapper for TestCoroTransport === */\n\nTestCoroTransport* transportFromBio(BIO* bio) {\n  auto appData = OpenSSLUtils::getBioAppData(bio);\n  XCHECK(appData);\n  auto* transport = reinterpret_cast<TestCoroTransport*>(appData);\n  XCHECK(transport);\n  return transport;\n}\n\nint testSSLTransportBioWrite(BIO* bio, const char* buf, int sz) {\n  BIO_clear_retry_flags(bio);\n  transportFromBio(bio)->addReadEvent(folly::IOBuf::copyBuffer(buf, sz), false);\n  return sz;\n}\nint testSSLTransportBioWriteEx(BIO* bio,\n                               const char* buf,\n                               size_t sz,\n                               size_t* nw) {\n  BIO_clear_retry_flags(bio);\n  auto rc = testSSLTransportBioWrite(bio, buf, sz);\n  if (rc >= 0) {\n    *nw = rc;\n    return 1;\n  }\n  return 0;\n}\nint testSSLTransportBioRead(BIO* bio, char* buf, int sz) {\n  BIO_clear_retry_flags(bio);\n  auto transport = transportFromBio(bio);\n  int nRead = 0;\n  while (sz > 0 && !transport->state_->writeEvents.empty()) {\n    if (transport->state_->writeEvents.front().empty()) {\n      transport->state_->writeEvents.pop_front();\n      continue;\n    }\n    folly::io::Cursor c(transport->state_->writeEvents.front().front());\n    auto readFromFront = c.pullAtMost(buf + nRead, sz);\n    sz -= readFromFront;\n    nRead += readFromFront;\n    transport->state_->writeEvents.front().trimStart(readFromFront);\n  }\n  if (nRead == 0) {\n    if (!transport->state_->writesClosed) {\n      // need more\n      BIO_set_retry_read(bio);\n    }\n  }\n  return nRead;\n}\nint testSSLTransportBioReadEx(BIO* bio, char* buf, size_t sz, size_t* nr) {\n  auto rc = testSSLTransportBioRead(bio, buf, sz);\n  if (rc >= 0) {\n    *nr = rc;\n    return 1;\n  }\n  return 0;\n}\nlong testSSLTransportBioCtrl(BIO*, int, long, void*) {\n  return 1;\n}\n\nfolly::ssl::BioMethodUniquePtr initBioMethod() {\n  BIO_METHOD* newmeth = nullptr;\n  newmeth = BIO_meth_new(BIO_get_new_index(), \"test_ssl_transport_bio_method\");\n  if (!newmeth) {\n    return nullptr;\n  }\n  BIO_meth_set_ctrl(newmeth, testSSLTransportBioCtrl);\n  BIO_meth_set_read(newmeth, testSSLTransportBioRead);\n  BIO_meth_set_read_ex(newmeth, testSSLTransportBioReadEx);\n  BIO_meth_set_write(newmeth, testSSLTransportBioWrite);\n  BIO_meth_set_write_ex(newmeth, testSSLTransportBioWriteEx);\n\n  return folly::ssl::BioMethodUniquePtr(newmeth);\n}\n\nBIO_METHOD* getTestSSLBioMethod() {\n  static auto const instance = initBioMethod().release();\n  return instance;\n}\n\n} // namespace\n\nnamespace proxygen::coro {\n\nclass CoroSSLTransportFakeTest : public TransportTest {\n public:\n  folly::coro::Task<std::unique_ptr<CoroSSLTransport>> connect() {\n    auto testTransport =\n        std::make_unique<TestCoroTransport>(&evb, &transportState_);\n    testTransport_ = testTransport.get();\n    auto transport = std::make_unique<CoroSSLTransport>(\n        std::move(testTransport), std::make_shared<SSLContext>());\n\n    co_withExecutor(&evb, accept()).start();\n    co_await transport->connect(folly::none, std::chrono::seconds(2));\n    co_return transport;\n  }\n\n  // Accept an SSL connection via the TestCoroTransport\n  folly::coro::Task<void> accept() {\n    ctx_ = std::make_shared<folly::SSLContext>();\n    ctx_->loadCertificate(find_resource(folly::test::kTestCert).c_str());\n    ctx_->loadPrivateKey(find_resource(folly::test::kTestKey).c_str());\n    ctx_->ciphers(\"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH\");\n    try {\n      ssl_.reset(ctx_->createSSL());\n    } catch (std::exception& ex) {\n      XLOG(ERR) << \"TestSSLTransport::accept(this=\" << this\n                << \"): \" << ex.what();\n      throw;\n    }\n\n    if (!setupSSLBio()) {\n      XLOG(ERR) << \"TestSSLTransport::accept(this=\" << this << \"): \";\n      co_return;\n    }\n\n    while (true) {\n      auto rc = SSL_accept(ssl_.get());\n      if (rc <= 0) {\n        int sslError = SSL_get_error(ssl_.get(), rc);\n        if (sslError == SSL_ERROR_WANT_READ) {\n          // A bit lame, but meh\n          co_await folly::coro::sleep(100ms);\n        } else {\n          XLOG(ERR) << \"SSL_accept error=\" << sslError;\n          sslAccept_.post();\n          break;\n        }\n      } else {\n        XLOG(INFO) << \"accepted\";\n        sslAccept_.post();\n        break;\n      }\n    }\n  }\n\n  bool setupSSLBio() {\n    auto sslBio = BIO_new(getTestSSLBioMethod());\n\n    if (!sslBio) {\n      return false;\n    }\n\n    OpenSSLUtils::setBioAppData(sslBio, testTransport_);\n    SSL_set_bio(ssl_.get(), sslBio, sslBio);\n    return true;\n  }\n\n  TestCoroTransport* testTransport_{nullptr};\n  TestCoroTransport::State transportState_;\n  std::shared_ptr<folly::SSLContext> ctx_;\n  folly::ssl::SSLUniquePtr ssl_;\n  // baton gating the successful or failed SSL_accept\n  folly::coro::Baton sslAccept_;\n};\n\nTEST_F(CoroSSLTransportFakeTest, BackgroundWriteTimeout) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    testTransport_->pauseWrites();\n    constexpr auto kBufSize = 4 * 1024;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    // Write appears to succeed but is running in the background, will timeout\n    // and closeWithReset, cancelling the read\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms);\n\n    std::array<uint8_t, 1024> rcvBuf;\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            0ms),\n        AsyncSocketException);\n\n    // Can't delete testTransport_ while blocked\n    testTransport_->resumeWrites();\n    // let the accept loop complete before destroying test objects\n    co_await sslAccept_;\n  });\n}\n\nTEST_F(CoroSSLTransportFakeTest, WritesBlockedTimeout) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    testTransport_->pauseWrites();\n    constexpr auto kBufSize = 132 * 1024;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    EXPECT_THROW(\n        co_await cs->write(\n            ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()), 500ms),\n        AsyncSocketException);\n    // Can't delete testTransport_ while blocked\n    testTransport_->resumeWrites();\n    // And have to give it 1 loop to wake up before deleting safely\n    co_await folly::coro::co_reschedule_on_current_executor;\n  });\n}\n\nTEST_F(CoroSSLTransportFakeTest, ProtocolError) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    // waiting for server accept to complete ensures corresponding objects\n    // aren't destroyed too early\n    co_await sslAccept_;\n    std::array<uint8_t, 1024> rcvBuf;\n    // Add garbage to the transport read buf\n    testTransport_->addReadEvent(folly::IOBuf::copyBuffer(\"\\0\\0\\0\\0\\0\", 5),\n                                 false);\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            0ms),\n        AsyncSocketException);\n  });\n}\n\n// This tests how CoroSSLTransport handles getting SSL_ERROR_WANT_WRITE as a\n// result for SSL_read, and SSL_ERROR_WANT_READ from SSL_write by having the\n// server request a rehandshake.  It also tests how CoroSSLTransport handles\n// having more than one simultaneous read and write.  It's somewhat dependent on\n// current openssl behavior, and could break if OpenSSL changes.\nTEST_F(CoroSSLTransportFakeTest, WriteFromSSLRead) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    // Pause writes and schedule a big write to exactly fill the transport buf\n    testTransport_->pauseWrites();\n    constexpr auto kBufSize = 128 * 1024;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    // write returns but the next write will block\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       0ms);\n\n    // Schedule a read\n    std::array<uint8_t, 1024> rcvBuf;\n    auto readFut =\n        co_withExecutor(\n            &evb,\n            cs->read(MutableByteRange(rcvBuf.data(),\n                                      (rcvBuf.data() + rcvBuf.size())),\n                     300ms))\n            .start();\n\n    // Wait a loop for read to start/block\n    co_await folly::coro::co_reschedule_on_current_executor;\n\n    // Initiate a renegotiation from the server side\n\n    // SSL_read inside cs->read will return WANT_WRITE, and block on\n    // writes with the read deadline (300ms)\n    SSL_renegotiate(ssl_.get());\n    std::array<uint8_t, kBufSize> serverReadBuf;\n    // Have to call read/write to trigger actual renegotiation\n    auto rc = SSL_read(ssl_.get(), serverReadBuf.data(), serverReadBuf.size());\n    EXPECT_GT(rc, 0);\n    size_t serverReadBytes = rc;\n    co_await folly::coro::co_reschedule_on_current_executor;\n\n    // Start another write, which will also block.  It does not increase the\n    // timeout.  It also triggers a WANT_READ from inside SSL_write\n    auto writeFut =\n        co_withExecutor(\n            &evb, cs->write(ByteRange(sndBuf.data(), sndBuf.data() + 1), 500ms))\n            .start();\n\n    // Now resume writes\n    testTransport_->resumeWrites();\n\n    // read the data and process the renegotiation\n    while (serverReadBytes < kBufSize + 1 ||\n           SSL_renegotiate_pending(ssl_.get())) {\n      rc = SSL_read(ssl_.get(), serverReadBuf.data(), serverReadBuf.size());\n      if (rc <= 0) {\n        int sslError = SSL_get_error(ssl_.get(), rc);\n        if (sslError == SSL_ERROR_WANT_READ) {\n          // A bit lame, but meh\n          co_await folly::coro::sleep(100ms);\n        } else if (sslError == SSL_ERROR_ZERO_RETURN) {\n          XLOG(ERR) << \"SSL_read zer return\";\n          break;\n        } else {\n          XLOG(ERR) << \"SSL_read error=\" << sslError;\n          break;\n        }\n      } else {\n        serverReadBytes += rc;\n        XLOG(DBG6) << \"Server read returned rc=\" << rc\n                   << \" serverReadBytes=\" << serverReadBytes;\n      }\n    }\n    EXPECT_EQ(serverReadBytes, kBufSize + 1);\n    // The 1 byte write completed, shutdown.\n    co_await std::move(writeFut);\n    cs->shutdownWrite();\n    SSL_shutdown(ssl_.get());\n    EXPECT_EQ(co_await std::move(readFut), 0);\n  });\n}\n\n} // namespace proxygen::coro\n\n// timeout when computing timeout\n// applyVerificationOptions fails\n// callback ctrl\n// read EOF from SSL_write\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/HTTPConnectTransportTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/test/HTTPConnectTransportTest.h\"\n#include <proxygen/lib/http/session/test/MockHTTPSessionStats.h>\n\n#include <folly/Portability.h>\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Collect.h>\n#include <folly/logging/xlog.h>\n\nusing namespace std::chrono_literals;\nusing namespace folly;\nusing namespace folly::coro;\n\ntemplate <size_t SIZE>\nfolly::coro::Task<Unit> readAll(folly::coro::TransportIf& transport,\n                                std::array<uint8_t, SIZE>& rcvBuf,\n                                std::chrono::milliseconds timeout) {\n  size_t totalBytes{0};\n  while (totalBytes < SIZE) {\n    auto bytesRead = co_await transport.read(\n        MutableByteRange(rcvBuf.data() + totalBytes,\n                         (rcvBuf.data() + rcvBuf.size())),\n        timeout);\n    totalBytes += bytesRead;\n  }\n  co_return unit;\n};\n\nnamespace proxygen::coro {\n\nclass ReadCallback : public AsyncTransport::ReadCallback {\n public:\n  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {\n    auto buf = readBuf_.preallocate(1500, 4000);\n    *bufReturn = buf.first;\n    *lenReturn = buf.second;\n  }\n\n  void readDataAvailable(size_t len) noexcept override {\n    XLOG(DBG2) << \"readDataAvailable, len \" << len;\n    readBuf_.postallocate(len);\n    if (readBuf_.chainLength() >= expectedLen) {\n      promise.setValue(folly::Unit());\n    }\n  }\n\n  bool isBufferMovable() noexcept override {\n    return bufferMovable;\n  }\n\n  void readBufferAvailable(\n      std::unique_ptr<folly::IOBuf> buf) noexcept override {\n    readBuf_.append(std::move(buf));\n    if (readBuf_.chainLength() >= expectedLen) {\n      promise.setValue(folly::Unit());\n    }\n  }\n\n  void readErr(const AsyncSocketException& ex) noexcept override {\n    XLOG(DBG2) << \"readError \" << ex.what();\n    promise.setException(ex);\n    eofPromise.setException(std::runtime_error(\"EOF waiting for data\"));\n  }\n\n  void readEOF() noexcept override {\n    XLOG(DBG2) << \"readEOF\";\n\n    if (!promise.isFulfilled()) {\n      promise.setException(std::runtime_error(\"EOF waiting for data\"));\n    }\n    eofPromise.setValue(folly::Unit());\n  }\n\n  folly::IOBufQueue readBuf_{folly::IOBufQueue::cacheChainLength()};\n  std::shared_ptr<HTTPConnectAsyncTransport> transport;\n  bool bufferMovable{false};\n  size_t expectedLen{std::numeric_limits<size_t>::max()};\n  folly::Promise<folly::Unit> promise;\n  folly::Promise<folly::Unit> eofPromise;\n};\n\nclass WriteCallback : public AsyncTransport::WriteCallback {\n public:\n  void writeSuccess() noexcept override {\n    XLOG(DBG2) << \"writeSuccess\";\n    promise.setValue(folly::Unit());\n  }\n\n  void writeErr(size_t nBytesWritten,\n                const AsyncSocketException& ex) noexcept override {\n    XLOG(DBG2) << \"writeError: bytesWritten \" << nBytesWritten << \", exception \"\n               << ex.what();\n    promise.setException(ex);\n  }\n\n  std::shared_ptr<HTTPConnectAsyncTransport> transport;\n  folly::Promise<folly::Unit> promise;\n};\n\nclass TransportTest : public testing::Test {\n public:\n  template <typename F>\n  void run(F f) {\n    blockingWait(co_invoke(std::move(f)), &evb);\n  }\n\n  void TearDown() override {\n    evb.loop();\n  }\n\n  folly::coro::Task<> requestCancellation() {\n    cancelSource.requestCancellation();\n    co_return;\n  }\n\n  EventBase evb;\n  CancellationSource cancelSource;\n};\n\nclass HTTPConnectTransportTest : public TransportTest {\n public:\n  void SetUp() override {\n    HTTPServer::Config config;\n    config.socketConfig.bindAddress.setFromIpPort(\"127.0.0.1\", 0);\n    // To help the write timeout test, use the default stream flow control\n    config.plaintextProtocol = \"h2\";\n    config.sessionConfig.settings = {\n        {SettingsId::INITIAL_WINDOW_SIZE, 64 * 1024}};\n    config.numIOThreads = 1;\n    srv = ScopedHTTPServer::start(std::move(config), connectHandler);\n  }\n\n  void TearDown() override {\n    TransportTest::TearDown();\n    // Any connections that are still open will race with drop\n    connectHandler->resetExceptionExpected();\n  }\n\n  folly::coro::Task<std::unique_ptr<HTTPConnectTransport>> connect(\n      HTTPCoroSession* session = nullptr) {\n    std::unique_ptr<HTTPConnectStream> connectStream;\n    if (!session) {\n      session = co_await HTTPCoroConnector::connect(\n          &evb, *srv->address(), 0ms, getConnParams());\n      auto reservation = session->reserveRequest();\n      connectStream =\n          co_await HTTPConnectStream::connectUnique(session,\n                                                    std::move(*reservation),\n                                                    kAuthority,\n                                                    std::chrono::seconds(1),\n                                                    {{\"Foo\", \"Bar\"}},\n                                                    egressBufferSize);\n    } else {\n      auto reservation = session->reserveRequest();\n      connectStream =\n          co_await HTTPConnectStream::connect(session,\n                                              std::move(*reservation),\n                                              kAuthority,\n                                              std::chrono::seconds(1),\n                                              {{\"Foo\", \"Bar\"}},\n                                              egressBufferSize);\n    }\n    co_return std::make_unique<HTTPConnectTransport>(std::move(connectStream));\n  }\n\n  folly::coro::Task<std::shared_ptr<HTTPConnectAsyncTransport>> connectAsync() {\n    auto session = co_await HTTPCoroConnector::connect(\n        &evb, *srv->address(), 0ms, getConnParams());\n    auto reservation = session->reserveRequest();\n    auto connectStream =\n        co_await HTTPConnectStream::connectUnique(session,\n                                                  std::move(*reservation),\n                                                  kAuthority,\n                                                  std::chrono::seconds(1),\n                                                  {{\"Foo\", \"Bar\"}},\n                                                  egressBufferSize);\n    auto transport =\n        std::make_shared<HTTPConnectAsyncTransport>(std::move(connectStream));\n    transport->setReadCB(&rcb);\n    co_return transport;\n  }\n\n  HTTPCoroConnector::ConnectionParams getConnParams() {\n    auto connParams = HTTPCoroConnector::defaultConnectionParams();\n    connParams.plaintextProtocol = \"h2\";\n    return connParams;\n  }\n\n  size_t egressBufferSize{64 * 1024};\n  std::shared_ptr<ConnectHandler> connectHandler{\n      std::make_shared<ConnectHandler>()};\n  std::unique_ptr<ScopedHTTPServer> srv;\n  ReadCallback rcb;\n  WriteCallback wcb;\n};\n\nTEST_F(HTTPConnectTransportTest, ConnectSuccess) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    // Peer address is the 'X-Connected-To' address\n    EXPECT_EQ(\"1.2.3.4\", cs->getPeerAddress().getAddressStr());\n    EXPECT_EQ(connectHandler->peerAddr, cs->getLocalAddress());\n    EXPECT_EQ(cs->getEventBase(), &evb);\n    // TODO:  :/\n    EXPECT_EQ(cs->getTransport(), nullptr);\n    EXPECT_EQ(cs->getPeerCertificate(), nullptr);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ConnectNon200) {\n  connectHandler->expectExceptions();\n  run([&]() -> Task<> {\n    auto connParams = HTTPCoroConnector::defaultConnectionParams();\n    connParams.plaintextProtocol = \"h2\";\n    auto session = co_await HTTPCoroConnector::connect(\n        &evb, *srv->address(), 0ms, connParams);\n    auto reservation = session->reserveRequest();\n    EXPECT_THROW(\n        co_await HTTPConnectStream::connectUnique(session,\n                                                  std::move(*reservation),\n                                                  kAuthority,\n                                                  std::chrono::seconds(1),\n                                                  {{\"Fail\", \"True\"}}),\n        std::runtime_error);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ConnectSlow) {\n  run([&]() -> Task<> {\n    auto connParams = HTTPCoroConnector::defaultConnectionParams();\n    connParams.plaintextProtocol = \"h2\";\n    HTTPCoroConnector::SessionParams sessParams;\n    sessParams.streamReadTimeout = std::chrono::milliseconds(100);\n    // 100ms stream read timeout\n    auto session = co_await HTTPCoroConnector::connect(\n        &evb, *srv->address(), 0ms, connParams, sessParams);\n    auto reservation = session->reserveRequest();\n    // 1s connect timeout, doesn't timeout\n    auto connectStream = co_await HTTPConnectStream::connectUnique(\n        session,\n        std::move(*reservation),\n        kAuthority,\n        std::chrono::seconds(1),\n        {{\"Slow\", \"True\"}, {\"Foo\", \"Bar\"}});\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ConnectShared) {\n  run([&]() -> Task<> {\n    auto connParams = HTTPCoroConnector::defaultConnectionParams();\n    connParams.plaintextProtocol = \"h2\";\n    auto session = co_await HTTPCoroConnector::connect(\n        &evb, *srv->address(), 0ms, connParams);\n    auto sessPtr = session->acquireKeepAlive();\n    auto cs1 = co_await connect(session);\n    auto cs2 = co_await connect(session);\n    auto port = cs1->getLocalAddress().getPort();\n    EXPECT_EQ(port, cs2->getLocalAddress().getPort());\n    cs1.reset();\n    cs2.reset();\n    cs1 = co_await connect(session);\n    EXPECT_EQ(port, cs1->getLocalAddress().getPort());\n    cs1.reset();\n    co_await folly::coro::co_reschedule_on_current_executor;\n    session->initiateDrain();\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ConnectCancelled) {\n  run([&]() -> Task<> {\n    co_await folly::coro::collectAll(\n        // token would be cancelled while waiting on connect\n        [&]() -> Task<> {\n          EXPECT_THROW(\n              co_await co_withCancellation(cancelSource.getToken(), connect()),\n              OperationCancelled);\n        }(),\n        requestCancellation());\n    // token was cancelled before read was called\n    EXPECT_THROW(co_await co_withCancellation(\n                     cancelSource.getToken(),\n                     Transport::newConnectedSocket(&evb, *srv->address(), 0ms)),\n                 OperationCancelled);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, SimpleReadWrite) {\n  run([&]() -> Task<> {\n    // Should fill window\n    constexpr auto kBufSize = 65 * 1024;\n    auto cs = co_await connect();\n\n    // read using coroutines\n    std::array<uint8_t, kBufSize> rcvBuf;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    folly::coro::TransportIf::WriteInfo info;\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms,\n                       folly::WriteFlags::NONE,\n                       &info);\n    EXPECT_EQ(info.bytesWritten, sndBuf.size());\n    co_await readAll(*cs, rcvBuf, 0ms);\n    EXPECT_EQ(0, memcmp(sndBuf.data(), rcvBuf.data(), rcvBuf.size()));\n\n    // Read with 0 length returns immediately\n    EXPECT_EQ(co_await cs->read(folly::MutableByteRange(nullptr, nullptr)), 0);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, SimpleIOBufReadWrite) {\n  run([&]() -> Task<> {\n    // Exactly fills a buffer mid-loop and triggers deferredReadEOF handling\n    constexpr auto kBufSize = 55 * 1184;\n\n    auto cs = co_await connect();\n\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    folly::coro::TransportIf::WriteInfo info;\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms,\n                       folly::WriteFlags::NONE,\n                       &info);\n    EXPECT_EQ(info.bytesWritten, sndBuf.size());\n\n    // read using coroutines\n    IOBufQueue rcvBuf(IOBufQueue::cacheChainLength());\n    int totalBytes{0};\n    while (totalBytes < kBufSize) {\n      auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 0ms);\n      totalBytes += bytesRead;\n    }\n    // Initiate close from the peer\n    connectHandler->evb->runInEventBaseThreadAndWait([this]() {\n      // must run inside evb\n      connectHandler->curRespSource->eom();\n    });\n\n    auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 50ms);\n    EXPECT_EQ(bytesRead, 0); // closed\n    bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 50ms);\n    EXPECT_EQ(bytesRead, 0); // still 0\n\n    auto data = rcvBuf.move();\n    data->coalesce();\n    EXPECT_EQ(0, memcmp(sndBuf.data(), data->data(), data->length()));\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ReadCancelled) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    auto reader = [&cs]() -> Task<Unit> {\n      std::array<uint8_t, 1024> rcvBuf;\n      EXPECT_THROW(\n          co_await cs->read(\n              MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n              0ms),\n          OperationCancelled);\n      co_return unit;\n    };\n\n    co_await co_withCancellation(\n        cancelSource.getToken(),\n        folly::coro::collectAll(requestCancellation(), reader()));\n    // token was cancelled before read was called\n    co_await co_withCancellation(cancelSource.getToken(), reader());\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ReadTimeout) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    std::array<uint8_t, 1024> rcvBuf;\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            50ms),\n        AsyncSocketException);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ReadAfterReadTimeout) {\n  run([&]() -> Task<> {\n    auto* sess = co_await HTTPCoroConnector::connect(\n        &evb, *srv->address(), 0ms, getConnParams());\n    auto connectStream =\n        co_await HTTPConnectStream::connectUnique(sess,\n                                                  *sess->reserveRequest(),\n                                                  kAuthority,\n                                                  std::chrono::seconds(1),\n                                                  {{\"Foo\", \"Bar\"}},\n                                                  egressBufferSize);\n    auto* pConnectStream = connectStream.get();\n    auto cs = std::make_unique<HTTPConnectTransport>(std::move(connectStream));\n\n    std::array<uint8_t, 1024> buf{};\n    /**\n     * We only rx data on the connect stream if we tx data (it's an echo\n     * handler). Since we're reading without tx'ing any data, a 50ms read should\n     * timeout and yield the appropriate exception.\n     */\n    auto read_res = co_await folly::coro::co_awaitTry(cs->read(\n        MutableByteRange(buf.data(), (buf.data() + buf.size())), 50ms));\n    auto asyncSocketEx =\n        CHECK_NOTNULL(read_res.tryGetExceptionObject<AsyncSocketException>());\n    EXPECT_EQ(asyncSocketEx->getType(), AsyncSocketException::TIMED_OUT);\n\n    // instruct server to send response & h2 eom together\n    std::array<unsigned char, 1024> sendBuf = {\"write_fin\"};\n    co_await cs->write(\n        ByteRange(sendBuf.data(), sendBuf.data() + sendBuf.size()));\n\n    // wait until HTTPConnectTransport consumes the body bytes enqueued\n    testing::NiceMock<MockHTTPSessionStats> sessionStats;\n    sess->setSessionStats(&sessionStats);\n    folly::coro::Baton waitUntilRead;\n    ON_CALL(sessionStats, _recordPendingBufferedReadBytes(-1024))\n        .WillByDefault([&]() { waitUntilRead.post(); });\n    co_await waitUntilRead;\n\n    /**\n     * read should return > 0 bytes; the ingress source is no longer readable\n     * but there is a queued event\n     */\n    EXPECT_FALSE(pConnectStream->canRead());\n    read_res = co_await folly::coro::co_awaitTry(cs->read(\n        MutableByteRange(buf.data(), (buf.data() + buf.size())), 50ms));\n\n    XCHECK(!read_res.hasException());\n    EXPECT_GT(read_res.value(), 0);\n    sess->setSessionStats(nullptr);\n\n    /**\n     * another read should return 0; ingress source isn't readable and no queued\n     * events\n     */\n    read_res = co_await folly::coro::co_awaitTry(cs->read(\n        MutableByteRange(buf.data(), (buf.data() + buf.size())), 50ms));\n    EXPECT_EQ(read_res.value(), 0);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ReadError) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    connectHandler->evb->runInEventBaseThreadAndWait([this]() {\n      auto respSource = std::exchange(connectHandler->curRespSource, nullptr);\n      // must run inside evb\n      respSource->abort(HTTPErrorCode::CANCEL);\n    });\n\n    std::array<uint8_t, 1024> rcvBuf;\n    EXPECT_THROW(\n        co_await cs->read(\n            MutableByteRange(rcvBuf.data(), (rcvBuf.data() + rcvBuf.size())),\n            50ms),\n        folly::AsyncSocketException);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, SimpleWritev) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    IOBufQueue sndBuf{folly::IOBufQueue::cacheChainLength()};\n    constexpr auto kBufSize = 65536;\n    std::array<uint8_t, kBufSize> bufA;\n    std::memset(bufA.data(), 'a', bufA.size());\n    std::array<uint8_t, kBufSize> bufB;\n    std::memset(bufB.data(), 'b', bufB.size());\n    sndBuf.append(bufA.data(), bufA.size());\n    sndBuf.append(bufB.data(), bufB.size());\n\n    // write use co-routine\n    co_await cs->write(sndBuf);\n\n    std::array<uint8_t, kBufSize> rcvBufA;\n    co_await readAll(*cs, rcvBufA, 500ms);\n    EXPECT_EQ(0, memcmp(bufA.data(), rcvBufA.data(), rcvBufA.size()));\n    std::array<uint8_t, kBufSize> rcvBufB;\n    co_await readAll(*cs, rcvBufB, 500ms);\n    EXPECT_EQ(0, memcmp(bufB.data(), rcvBufB.data(), rcvBufB.size()));\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, WriteCancelled) {\n  run([&]() -> Task<> {\n    // reduce the send buffer size so the write wouldn't complete immediately\n    egressBufferSize = 4096;\n    auto cs = co_await connect();\n    constexpr auto kBufSize = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    // write use co-routine\n    auto writer = [&]() -> Task<> {\n      EXPECT_THROW(co_await co_withCancellation(\n                       cancelSource.getToken(),\n                       cs->write(ByteRange(sndBuf.data(),\n                                           sndBuf.data() + sndBuf.size()))),\n                   OperationCancelled);\n    };\n\n    co_await folly::coro::collectAll(requestCancellation(), writer());\n    co_await co_withCancellation(cancelSource.getToken(), writer());\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, ShutdownWrite) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    // Sends shutdown\n    cs->shutdownWrite();\n    IOBufQueue rcvBuf(IOBufQueue::cacheChainLength());\n    // Wait for peer to shutdown\n    auto bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 500ms);\n    EXPECT_EQ(bytesRead, 0); // closed\n    bytesRead = co_await cs->read(rcvBuf, 1000, 1000, 500ms);\n    EXPECT_EQ(bytesRead, 0); // still closed\n\n    constexpr auto kBufSize = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    EXPECT_THROW(co_await cs->write(\n                     ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size())),\n                 folly::AsyncSocketException);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, CloseWithReadsWrites) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n\n    constexpr auto kBufSize = 256 * 1024;\n    std::array<uint8_t, 1024> rcvBuf;\n    auto readFut =\n        co_withExecutor(\n            &evb,\n            cs->read(MutableByteRange(rcvBuf.data(),\n                                      (rcvBuf.data() + rcvBuf.size())),\n                     0ms))\n            .start();\n\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    auto writeFut =\n        co_withExecutor(\n            &evb,\n            cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                      0ms))\n            .start();\n    // Get the write started, but blocking\n    co_await folly::coro::co_reschedule_on_current_executor;\n\n    cs->close();\n    // Writes complete normally\n    co_await std::move(writeFut);\n    // The read is cancelled\n    // TODO: This exception comes from stopReading, should we normalize\n    // cancellation errors from read()?\n    EXPECT_THROW(co_await std::move(readFut), folly::AsyncSocketException);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, CloseWithReset) {\n  connectHandler->expectExceptions();\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    cs->closeWithReset();\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, WritesBlockedPendingEOF) {\n  run([&]() -> Task<> {\n    auto cs = co_await connect();\n    constexpr auto kBufSize = 64 * 3 * 1024 + 1;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    // Fills the receiver's window (64k) and\n    //       the BodyEventQueue buffer (64k) +\n    //       the HTTPStreamSource buffer (64k) + 1\n    // This is bigger than I thought :(\n    auto writeFut =\n        co_withExecutor(\n            &evb,\n            cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                      100ms))\n            .start()\n            .via(&evb)\n            .then([](auto&& res) { EXPECT_FALSE(res.hasException()); });\n    co_await folly::coro::co_reschedule_on_current_executor;\n    cs->shutdownWrite();\n    co_await std::move(writeFut);\n\n    IOBufQueue rcvBuf(IOBufQueue::cacheChainLength());\n    // Wait for peer to shutdown\n    std::array<uint8_t, kBufSize> rcvBufB;\n    co_await readAll(*cs, rcvBufB, 500ms);\n    EXPECT_EQ(co_await cs->read(rcvBuf, 1000, 1000, 500ms), 0); // closed\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, WritesBlockedTimeout) {\n  run([&]() -> Task<> {\n    connectHandler->timeout = std::chrono::seconds(1);\n    auto cs = co_await connect();\n    constexpr auto kBufSize = 64 * 3 * 1024 + 1;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n    // Fills the receiver's window (64k) and\n    //       the BodyEventQueue buffer (64k) +\n    //       the HTTPStreamSource buffer (64k) + 1\n    // This is bigger than I thought :(\n    co_await cs->write(ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()),\n                       100ms);\n    // Will timeout waiting to send\n    EXPECT_THROW(\n        co_await cs->write(\n            ByteRange(sndBuf.data(), sndBuf.data() + sndBuf.size()), 100ms),\n        AsyncSocketException);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, AsyncConnectSuccess) {\n  run([&]() -> Task<> {\n    auto cs = co_await connectAsync();\n\n    EXPECT_EQ(\"1.2.3.4\", cs->AsyncTransport::getPeerAddress().getAddressStr());\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, AsyncReadWrite) {\n  run([&]() -> Task<> {\n    auto cs = co_await connectAsync();\n\n    constexpr auto kBufSize = 65536;\n    rcb.expectedLen = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    cs->write(&wcb, sndBuf.data(), sndBuf.size());\n    co_await wcb.promise.getFuture().via(&evb);\n    co_await rcb.promise.getFuture().via(&evb);\n    cs->shutdownWrite();\n    co_await rcb.eofPromise.getFuture().via(&evb);\n  });\n}\n\n// similar to the test above, but we immediately invoke shutdownWrite after\n// write\nTEST_F(HTTPConnectTransportTest, AsyncReadWriteEom) {\n  run([&]() -> Task<> {\n    auto cs = co_await connectAsync();\n\n    constexpr auto kBufSize = 65536;\n    rcb.expectedLen = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    cs->write(&wcb, sndBuf.data(), sndBuf.size());\n    cs->shutdownWrite();\n\n    co_await wcb.promise.getFuture().via(&evb);\n    co_await rcb.promise.getFuture().via(&evb);\n    co_await rcb.eofPromise.getFuture().via(&evb);\n  });\n}\n\nTEST_F(HTTPConnectTransportTest, AsyncReadWriteIOBuf) {\n  run([&]() -> Task<> {\n    auto cs = co_await connectAsync();\n\n    constexpr auto kBufSize = 65536;\n    rcb.bufferMovable = true;\n    rcb.expectedLen = 65536;\n    std::array<uint8_t, kBufSize> sndBuf;\n    std::memset(sndBuf.data(), 'a', sndBuf.size());\n\n    cs->write(&wcb, sndBuf.data(), sndBuf.size());\n    co_await wcb.promise.getFuture().via(&evb);\n    co_await rcb.promise.getFuture().via(&evb);\n    cs->shutdownWrite();\n    co_await rcb.eofPromise.getFuture().via(&evb);\n  });\n}\n\n} // namespace proxygen::coro\n\n// Unit tests to add\n\n// HTTPConnectTransport\n// ===\n// write after egress error\n// fc window cancelled\n\n// HTTPConnectAsyncTransport\n// ===\n// getReadCallback\n// writev\n// close, good, readable, writable, connecting\n// get/setSendTimeout\n// getLocalAddress\n// isEORTrackingEnabled\n// setReadCallback no-op\n// update read callback while waiting (is this a good idea?)\n// deleted while read() active\n// readBodyEvent exception\n// non-body (can't test this since only body is legal, future proofing\n// write when not writable\n// write with null buffer\n// shutdownRead\n// shutdownWriteNow\n// errorWrites with pending callbacks\n// bytesProcessed with pending callbacks\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/HTTPConnectTransportTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPFixedSource.h\"\n#include \"proxygen/lib/http/coro/HTTPStreamSourceHolder.h\"\n#include \"proxygen/lib/http/coro/client/HTTPCoroConnector.h\"\n#include \"proxygen/lib/http/coro/server/ScopedHTTPServer.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectAsyncTransport.h\"\n#include \"proxygen/lib/http/coro/transport/HTTPConnectTransport.h\"\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\n#include <folly/Synchronized.h>\n#include <folly/coro/Sleep.h>\n#include <folly/portability/GTest.h>\n\nnamespace {\nconstexpr char kAuthority[] = \"example.com:443\";\n}\n\nnamespace proxygen::coro {\n\nclass ConnectHandler : public HTTPHandler {\n public:\n  folly::coro::Task<HTTPSourceHolder> handleRequest(\n      folly::EventBase* eventBase,\n      HTTPSessionContextPtr ctx,\n      HTTPSourceHolder requestSource) override {\n    // numIOThreads = 1; only one evb ok to store\n    this->evb = eventBase;\n    peerAddr = ctx->getPeerAddress();\n    auto headerEvent = co_await requestSource.readHeaderEvent();\n    auto resp = std::make_unique<HTTPMessage>();\n    EXPECT_EQ(\n        headerEvent.headers->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n        kAuthority);\n    resp->getHeaders().set(\"X-Connected-To\", \"1.2.3.4\");\n    resp->setStatusCode(200);\n    resp->setHTTPVersion(1, 1);\n    auto respSourcePtr = HTTPStreamSourceHolder::make(eventBase);\n    auto respSource = respSourcePtr->get();\n    // 1xx response, because we can\n    respSource->headers(makeResponse(107));\n    if (headerEvent.headers->getHeaders().exists(\"Slow\")) {\n      co_await folly::coro::sleep(std::chrono::milliseconds(500));\n    }\n    if (headerEvent.headers->getHeaders().exists(\"Fail\")) {\n      respSource->headers(makeResponse(500), /*eom=*/true);\n    } else {\n      EXPECT_EQ(headerEvent.headers->getHeaders().getSingleOrEmpty(\"Foo\"),\n                \"Bar\");\n      respSource->headers(std::move(resp), false);\n    }\n    curRespSource = respSource;\n    co_withExecutor(eventBase,\n                    handleConnect(std::move(requestSource), respSourcePtr))\n        .start();\n    respSourcePtr->start();\n    co_return respSource;\n  }\n\n  folly::coro::Task<void> handleConnect(\n      HTTPSourceHolder reqSource,\n      std::shared_ptr<HTTPStreamSourceHolder> respSourcePtr) {\n    SCOPE_EXIT {\n      curRespSource = nullptr;\n    };\n    bool eom = false;\n    if (timeout.count() > 0) {\n      co_await folly::coro::sleep(timeout);\n    }\n    do {\n      auto bodyEvent = co_await co_awaitTry(reqSource.readBodyEvent());\n      if (bodyEvent.hasValue() &&\n          bodyEvent->eventType == HTTPBodyEvent::EventType::SUSPEND) {\n        co_await std::move(bodyEvent->event.resume);\n        continue;\n      }\n      auto respSource = respSourcePtr->get();\n      if (!respSource) {\n        break;\n      }\n      exceptionExpected.withLock([&bodyEvent](auto& exceptionExpected) {\n        if (exceptionExpected) {\n          EXPECT_EQ(bodyEvent.hasException(), *exceptionExpected);\n        }\n      });\n      if (bodyEvent.hasException()) {\n        co_yield folly::coro::co_error(std::move(bodyEvent.exception()));\n      }\n      eom =\n          bodyEvent->eom ||\n          (bodyEvent->event.body.front()->toString().starts_with(\"write_fin\"));\n      if (bodyEvent->eventType == HTTPBodyEvent::BODY) {\n        respSource->body(bodyEvent->event.body.move(), 0, eom);\n      }\n    } while (!eom);\n    curRespSource = nullptr;\n  }\n\n  void resetExceptionExpected() {\n    exceptionExpected.withLock(\n        [](auto& exceptionExpected) { exceptionExpected.reset(); });\n  }\n\n  void expectExceptions() {\n    exceptionExpected.withLock(\n        [](auto& exceptionExpected) { exceptionExpected = true; });\n  }\n\n  HTTPStreamSource* curRespSource{nullptr};\n  folly::EventBase* evb{nullptr};\n  folly::SocketAddress peerAddr;\n  std::chrono::milliseconds timeout{0};\n\n private:\n  folly::Synchronized<std::optional<bool>, std::mutex> exceptionExpected{false};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/TestCoroTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/transport/test/TestCoroTransport.h\"\n#include <folly/logging/xlog.h>\n\nusing TransportErrorCode = folly::coro::TransportIf::ErrorCode;\n\nnamespace {\nconst uint32_t kAckDelayMs = 50;\n}\n\nnamespace proxygen::coro::test {\n\nfolly::coro::Task<size_t> TestCoroTransport::read(\n    folly::MutableByteRange buf, std::chrono::milliseconds timeout) noexcept {\n  const auto &cancelToken = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      cancelToken, [&]() { XLOG(DBG8) << \"::read() cancelled\"; }};\n\n  while (true) {\n    const bool hasError = state_->readError.has_value();\n    const bool isCancelErr =\n        hasError && *state_->readError == TransportErrorCode::CANCELED;\n    // cancel without data read handled later\n    if (hasError && !isCancelErr) {\n      auto readError = *state_->readError;\n      XLOG(DBG8) << \"::read(); readError=\" << readError;\n      if (readError == TransportErrorCode::TIMED_OUT) {\n        // Timeouts reset after being read out\n        state_->readError = folly::none;\n      }\n      co_yield folly::coro::co_error(\n          folly::AsyncSocketException(readError, \"\"));\n    }\n\n    // either no error or cancel; reset error\n    state_->readError = folly::none;\n\n    if (!state_->readEvents.empty()) {\n      auto &readEvent = state_->readEvents.front();\n      folly::io::Cursor c(readEvent.front());\n      auto rc = c.pullAtMost(buf.start(), buf.end() - buf.start());\n      readEvent.trimStart(rc);\n      if (readEvent.empty()) {\n        state_->readEvents.pop_front();\n      }\n      XLOG(DBG8) << \"::read(); rc=\" << rc;\n      co_return rc;\n    } else if (state_->readEOF) {\n      XLOG(DBG8) << \"::read(); state_->readEOF\";\n      co_return 0;\n    } else if (isCancelErr) {\n      XLOG(DBG8) << \"::read(); readError=\" << *state_->readError;\n      co_yield folly::coro::co_stopped_may_throw;\n    } else {\n      readEvent_.reset();\n      auto status = co_await readEvent_.timedWait(evb_, timeout);\n      if (status == TimedBaton::Status::timedout) {\n        state_->readError = TransportErrorCode::TIMED_OUT;\n      } else if (status == TimedBaton::Status::cancelled &&\n                 !state_->readError) {\n        state_->readError = TransportErrorCode::CANCELED;\n      }\n      XLOG(DBG8) << \"::read(); readEvent_.wait() status=\" << int(status);\n    }\n  }\n  // unreachable\n  co_yield folly::coro::co_error(\n      folly::AsyncSocketException(TransportErrorCode::UNKNOWN, \"\"));\n}\n\nfolly::coro::Task<size_t> TestCoroTransport::read(\n    folly::IOBufQueue &readBuf,\n    size_t minReadSize,\n    size_t newAllocationSize,\n    std::chrono::milliseconds timeout) noexcept {\n  XLOG(DBG8) << __func__;\n  static const size_t kMaxReadsPerEvent = 16;\n  size_t numReads = 0;\n  size_t totalRead = 0;\n  do {\n    auto rbuf = readBuf.preallocate(minReadSize, newAllocationSize);\n    auto rc = co_await read(\n        folly::MutableByteRange((uint8_t *)rbuf.first, rbuf.second), timeout);\n    if (rc == 0) {\n      break;\n    }\n    readBuf.postallocate(rc);\n    totalRead += rc;\n  } while (!state_->readEvents.empty() && ++numReads < kMaxReadsPerEvent);\n  co_return totalRead;\n}\n\nfolly::coro::Task<folly::Unit> TestCoroTransport::write(\n    folly::ByteRange buf,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo *writeInfo) noexcept {\n  // TODO: check for closed\n  return write(folly::IOBuf::copyBuffer(buf), timeout, writeFlags, writeInfo);\n}\n\nfolly::coro::Task<folly::Unit> TestCoroTransport::write(\n    folly::IOBufQueue &ioBufQueue,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo *writeInfo) noexcept {\n  auto head = ioBufQueue.move();\n  auto clone = head->clone();\n  ioBufQueue.append(std::move(head));\n  return write(std::move(clone), timeout, writeFlags, writeInfo);\n}\n\nfolly::coro::Task<folly::Unit> TestCoroTransport::write(\n    std::unique_ptr<folly::IOBuf> buf,\n    std::chrono::milliseconds timeout,\n    folly::WriteFlags writeFlags,\n    WriteInfo *writeInfo) {\n  if (state_->writesClosed) {\n    co_yield folly::coro::co_stopped_may_throw;\n  }\n  folly::IOBufQueue bufQueue{folly::IOBufQueue::cacheChainLength()};\n  bufQueue.append(std::move(buf));\n  auto length = bufQueue.chainLength();\n  state_->writeOffset += length;\n  state_->writeEvents.emplace_back(std::move(bufQueue));\n\n  if (isSet(writeFlags, folly::WriteFlags::TIMESTAMP_WRITE)) {\n    fireByteEvents(folly::AsyncSocketObserverInterface::ByteEvent::Type::WRITE,\n                   state_->writeOffset);\n  }\n  while (writesPaused_) {\n    writeEvent_.reset();\n    auto status = co_await writeEvent_.timedWait(evb_, timeout);\n    if (status == TimedBaton::Status::timedout) {\n      co_yield folly::coro::co_error(folly::AsyncSocketException(\n          TransportErrorCode::TIMED_OUT, \"Timed out waiting for data\"));\n    } else if (status == TimedBaton::Status::cancelled) {\n      co_yield folly::coro::co_error(\n          folly::AsyncSocketException(TransportErrorCode::CANCELED, \"\"));\n    }\n  }\n  if (isSet(writeFlags, folly::WriteFlags::TIMESTAMP_TX) &&\n      byteEventsEnabled_) {\n    if (fastTxAck_) {\n      fireByteEvents(folly::AsyncSocketObserverInterface::ByteEvent::Type::TX,\n                     state_->writeOffset);\n    } else {\n      evb_->runInLoop([this, offset = state_->writeOffset] {\n        fireByteEvents(folly::AsyncSocketObserverInterface::ByteEvent::Type::TX,\n                       offset);\n      });\n    }\n  }\n  if (isSet(writeFlags, folly::WriteFlags::TIMESTAMP_ACK) &&\n      byteEventsEnabled_) {\n    if (fastTxAck_) {\n      fireByteEvents(folly::AsyncSocketObserverInterface::ByteEvent::Type::ACK,\n                     state_->writeOffset);\n    } else {\n      evb_->runAfterDelay(\n          [this, offset = state_->writeOffset] {\n            fireByteEvents(\n                folly::AsyncSocketObserverInterface::ByteEvent::Type::ACK,\n                offset);\n          },\n          kAckDelayMs);\n    }\n  }\n  if (writeInfo) {\n    writeInfo->bytesWritten = length;\n  }\n  co_return folly::unit;\n}\n\nvoid TestCoroTransport::shutdownWrite() {\n  XLOG(DBG8) << __func__;\n  state_->writesClosed = true;\n}\n\nvoid TestCoroTransport::close() {\n  XLOG(DBG8) << __func__;\n  state_->writesClosed = true;\n  if (!state_->closedWithReset) {\n    state_->readEOF = true;\n    readEvent_.signal();\n  }\n}\n\nvoid TestCoroTransport::closeWithReset() {\n  XLOG(DBG8) << __func__;\n  state_->writesClosed = true;\n  state_->readError = TransportErrorCode::NETWORK_ERROR;\n  state_->closedWithReset = true;\n  readEvent_.signal();\n}\n\nvoid TestCoroTransport::addReadEvent(std::unique_ptr<folly::IOBuf> ev,\n                                     bool eof) {\n  XLOG(DBG8) << __func__ << \"; ev=\" << ev.get() << \"; eof=\" << int(eof);\n  folly::IOBufQueue bufQueue{folly::IOBufQueue::cacheChainLength()};\n  if (ev) {\n    bufQueue.append(std::move(ev));\n    state_->readEvents.emplace_back(std::move(bufQueue));\n  }\n  if (eof) {\n    state_->readEOF = true;\n  }\n  readEvent_.signal();\n}\n\nvoid TestCoroTransport::pauseWrites() {\n  XLOG(DBG8) << __func__;\n  writesPaused_ = true;\n}\n\nvoid TestCoroTransport::resumeWrites() {\n  XLOG(DBG8) << __func__;\n  writesPaused_ = false;\n  writeEvent_.signal();\n}\n\nvoid TestCoroTransport::addReadError(TransportErrorCode err) {\n  XLOG(DBG8) << __func__ << \"; err=\" << err;\n  state_->readError = err;\n  readEvent_.signal();\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/transport/test/TestCoroTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n#include <folly/Random.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/async/test/MockAsyncSocket.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/io/coro/Transport.h>\n#include <proxygen/lib/transport/test/MockAsyncTransportCertificate.h>\n\nnamespace proxygen::coro::test {\n\nclass TestCoroTransport : public folly::coro::TransportIf {\n public:\n  struct State {\n    std::list<folly::IOBufQueue> readEvents;\n    folly::Optional<folly::coro::TransportIf::ErrorCode> readError;\n    bool readEOF{false};\n    bool writesClosed{false};\n    bool closedWithReset{false};\n    std::list<folly::IOBufQueue> writeEvents;\n    size_t writeOffset{0};\n  };\n\n  TestCoroTransport(folly::EventBase *evb, TestCoroTransport::State *state)\n      : mockAsyncSocket_(evb), evb_(evb), state_(state) {\n    localPort_ = folly::Random::rand32();\n    peerPort_ = folly::Random::rand32();\n    ON_CALL(mockAsyncTransport_, getWrappedTransport())\n        .WillByDefault(testing::Return(&mockAsyncSocket_));\n    ON_CALL(mockAsyncSocket_, addLifecycleObserver(testing::_))\n        .WillByDefault(testing::Invoke(\n            [this](folly::AsyncSocket::LegacyLifecycleObserver *observer) {\n              observers_.push_back(observer);\n              observer->byteEventsEnabled(&mockAsyncSocket_);\n              observer->observerAttach(&mockAsyncSocket_);\n            }));\n  }\n\n  folly::SocketAddress getLocalAddress() const noexcept override {\n    return {\"::1\", localPort_};\n  }\n\n  folly::SocketAddress getPeerAddress() const noexcept override {\n    return {\"::1\", peerPort_};\n  }\n\n  folly::coro::Task<size_t> read(\n      folly::MutableByteRange buf,\n      std::chrono::milliseconds timeout =\n          std::chrono::milliseconds(0)) noexcept override;\n\n  folly::coro::Task<size_t> read(\n      folly::IOBufQueue &readBuf,\n      size_t minReadSize,\n      size_t newAllocationSize,\n      std::chrono::milliseconds timeout =\n          std::chrono::milliseconds(0)) noexcept override;\n\n  folly::coro::Task<folly::Unit> write(\n      folly::ByteRange buf,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo *writeInfo = nullptr) noexcept override;\n\n  folly::coro::Task<folly::Unit> write(\n      folly::IOBufQueue &ioBufQueue,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo *writeInfo = nullptr) noexcept override;\n\n  folly::coro::Task<folly::Unit> write(\n      std::unique_ptr<folly::IOBuf> buf,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),\n      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,\n      WriteInfo *writeInfo = nullptr);\n\n  void shutdownWrite() override;\n\n  void close() override;\n\n  void closeWithReset() override;\n\n  folly::EventBase *getEventBase() noexcept override {\n    return evb_;\n  }\n\n  folly::AsyncTransport *getTransport() const override {\n    return &mockAsyncSocket_;\n  }\n\n  const folly::AsyncTransportCertificate *getPeerCertificate() const override {\n    return &mockCertificate;\n  }\n\n  void addReadEvent(std::unique_ptr<folly::IOBuf> ev, bool eof);\n\n  void pauseWrites();\n\n  void resumeWrites();\n\n  void addReadError(folly::coro::TransportIf::ErrorCode err);\n\n  void fireByteEvents(folly::AsyncSocketObserverInterface::ByteEvent::Type type,\n                      uint64_t offset) {\n    folly::AsyncSocketObserverInterface::ByteEvent byteEvent;\n    byteEvent.type = type;\n    byteEvent.offset = offset;\n    for (auto observer : observers_) {\n      observer->byteEvent(&mockAsyncSocket_, byteEvent);\n    }\n  }\n\n  void setByteEventsEnabled(bool enabled) {\n    byteEventsEnabled_ = enabled;\n  }\n\n  void setFastTxAckEvents(bool fast) {\n    fastTxAck_ = fast;\n  }\n\n  mutable testing::NiceMock<folly::test::MockAsyncTransport>\n      mockAsyncTransport_;\n  mutable testing::NiceMock<folly::test::MockAsyncSocket> mockAsyncSocket_;\n  std::list<folly::AsyncSocket::LegacyLifecycleObserver *> observers_;\n  bool writesPaused_{false};\n  folly::EventBase *evb_;\n  detail::CancellableBaton readEvent_;\n  detail::CancellableBaton writeEvent_;\n  State *state_;\n  uint16_t localPort_;\n  uint16_t peerPort_;\n  MockAsyncTransportCertificate mockCertificate;\n  bool byteEventsEnabled_{true};\n  bool fastTxAck_{false};\n};\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/AwaitableKeepAlive.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/coro/Baton.h>\n#include <folly/coro/Task.h>\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro::detail {\n\n// forward decls\ntemplate <class T>\nclass EnableAwaitableKeepAlive;\n\ntemplate <class T>\nclass KeepAlivePtr;\n\n/**\n * HTTPCoroSession enables KeepAlive semantics; destruction of the session is\n * owned by HTTPCoroSession itself, however it allows users to block destruction\n * until all acquired KeepAlives are released.\n *\n * KeepAliveState is internal to both EnableAwaitableKeepAlive (owns the state)\n * and KeepAlivePtr (holds a pointer to state); Consequently all members are\n * private with EnableAwaitableKeepAlive & KeepAlivePtr declared as friend\n * classes\n */\ntemplate <class T>\nstruct KeepAliveState {\n private:\n  friend class EnableAwaitableKeepAlive<T>;\n  friend class KeepAlivePtr<T>;\n\n  void keepAliveAcquired() {\n    uint64_t prevCount = refCount_.fetch_add(1, std::memory_order_relaxed);\n    // 0->1 refcount is a logical uaf; abort\n    XCHECK_GE(prevCount, 1ull) << \"use after free\";\n  }\n\n  void keepAliveReleased() {\n    uint64_t prevCount = refCount_.fetch_sub(1, std::memory_order_relaxed);\n    XCHECK_GE(prevCount, 1ull) << \"underflow\";\n    if (prevCount == 1) {\n      refsBaton_.post();\n    }\n  }\n\n  uint64_t getRefCount() {\n    return refCount_.load();\n  }\n\n  T& ref() {\n    return ref_;\n  }\n  const T& ref() const {\n    return ref_;\n  }\n\n  explicit KeepAliveState(T& ref) : ref_(ref) {\n  }\n  ~KeepAliveState() = default;\n  /**\n   * Note that `refCount_` is initially 1 here. This is effectively an implicit\n   * KeepAlive token automatically acquired on construction.\n   * EnableAwaitableKeepAlive::zeroRefs releases this token; this mechanism\n   * enables us to treat a 0->1 refcount increase as a logical use after free.\n   */\n  T& ref_;\n  std::atomic_uint64_t refCount_{1};\n  folly::coro::Baton refsBaton_;\n};\n\ntemplate <class T>\nclass KeepAlivePtr {\n public:\n  KeepAlivePtr() noexcept = default;\n\n  // move constructor, no ref count change\n  KeepAlivePtr(KeepAlivePtr&& other) noexcept\n      : keepAliveState_(other.keepAliveState_) {\n    other.keepAliveState_ = nullptr;\n  }\n\n  KeepAlivePtr(const KeepAlivePtr& other) noexcept\n      : keepAliveState_(other.keepAliveState_) {\n    if (keepAliveState_) {\n      keepAliveState_->keepAliveAcquired();\n    }\n  }\n\n  KeepAlivePtr& operator=(KeepAlivePtr&& other) noexcept {\n    if (this == &other) {\n      return *this;\n    }\n    reset();\n    keepAliveState_ = std::exchange(other.keepAliveState_, nullptr);\n    return *this;\n  }\n\n  KeepAlivePtr& operator=(const KeepAlivePtr& other) noexcept {\n    if (this == &other) {\n      return *this;\n    }\n    reset();\n    if ((keepAliveState_ = other.keepAliveState_)) {\n      keepAliveState_->keepAliveAcquired();\n    }\n    return *this;\n  }\n\n  ~KeepAlivePtr() noexcept {\n    // decrement ref count\n    reset();\n  }\n\n  // getters\n  T* get() {\n    return keepAliveState_ ? &keepAliveState_->ref() : nullptr;\n  }\n  T* operator->() {\n    return get();\n  }\n  // const getters\n  const T* get() const {\n    return keepAliveState_ ? &keepAliveState_->ref() : nullptr;\n  }\n  const T* operator->() const {\n    return get();\n  }\n\n  operator bool() const {\n    return get() != nullptr;\n  }\n\n  // reset\n  void reset() {\n    if (auto* ka = std::exchange(keepAliveState_, nullptr)) {\n      ka->keepAliveReleased();\n    }\n  }\n\n private:\n  friend class EnableAwaitableKeepAlive<T>;\n  explicit KeepAlivePtr(KeepAliveState<T>* keepAliveState)\n      : keepAliveState_(CHECK_NOTNULL(keepAliveState)) {\n    // increment refcount\n    keepAliveState_->keepAliveAcquired();\n  }\n\n  KeepAliveState<T>* keepAliveState_{nullptr};\n};\n\ntemplate <class T>\nclass EnableAwaitableKeepAlive {\n public:\n  KeepAlivePtr<T> acquireKeepAlive() {\n    return KeepAlivePtr<T>{&keepAliveState_};\n  }\n\n  uint64_t numKeepAlives() {\n    return keepAliveState_.getRefCount();\n  }\n\n protected:\n  /**\n   * TODO(@damlaj):\n   *\n   * This no_sanitize(vptr) attribute is here to prevent ubsan builds from\n   * raising false positive errors; see S452115\n   */\n#if defined(__clang__)\n  __attribute__((no_sanitize(\"vptr\")))\n#endif\n  EnableAwaitableKeepAlive()\n      : keepAliveState_{*static_cast<T*>(this)} {\n  }\n  virtual ~EnableAwaitableKeepAlive() = default;\n\n  folly::coro::Task<void> zeroRefs() {\n    // static_assert must be inside function definition otherwise incomplete\n    // type error\n    static_assert(\n        std::is_base_of_v</*Base=*/EnableAwaitableKeepAlive<T>, /*Derived=*/T>);\n\n    // we release the implicit KeepAlive token first\n    keepAliveState_.keepAliveReleased();\n\n    co_await keepAliveState_.refsBaton_;\n  }\n\n private:\n  KeepAliveState<T> keepAliveState_{nullptr};\n};\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_coro_util_timed_baton\n  EXPORTED_DEPS\n    Folly::folly_cancellation_token\n    Folly::folly_coro_baton\n    Folly::folly_coro_task\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_destructor_check\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_util_cancellable_baton\n  SRCS\n    CancellableBaton.cpp\n  EXPORTED_DEPS\n    proxygen_http_coro_util_timed_baton\n)\n\nproxygen_add_library(proxygen_http_coro_util_awaitable_keepalive\n  EXPORTED_DEPS\n    proxygen_http_coro_util_cancellable_baton\n    Folly::folly_coro_baton\n    Folly::folly_coro_task\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_util_window_contiainer\n  EXPORTED_DEPS\n    proxygen_http_window\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_coro_util_DetachableExecutor\n  SRCS\n    DetachableExecutor.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    Folly::folly_intrusive_list\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_coro_util_Transport\n  SRCS\n    Transport.cpp\n  DEPS\n    Folly::folly_io_async_async_base\n    Folly::folly_io_coro_transport_callbacks\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    Folly::folly_io_coro_socket\n)\n\nproxygen_add_library(proxygen_http_coro_util_executor_source_filter\n  SRCS\n    ExecutorSourceFilter.cpp\n  EXPORTED_DEPS\n    proxygen_coro_source\n)\n\nproxygen_add_library(proxygen_http_coro_util_CoroWtSession\n  SRCS\n    CoroWtSession.cpp\n  DEPS\n    Folly::folly_io_coro_socket\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_coro_source\n    proxygen_http_codec_webtransport_webtransport_capsule_codec\n    proxygen_http_coro_coro_stream_source\n    proxygen_http_coro_util_cancellable_baton\n    proxygen_http_webtransport_wt_stream_manager\n    mvfst::mvfst_priority_http_priority_queue\n)\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/CancellableBaton.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n\nnamespace proxygen::coro::detail {\n\nvoid CancellableBaton::signal(TimedBaton::Status status) noexcept {\n  XLOG(DBG8) << __func__ << \" signalled; status=\" << int(status);\n  // only resolve status for the first signaller\n  auto* pStatus = std::exchange(status_, nullptr);\n  if (pStatus && *pStatus == TimedBaton::Status::notReady) {\n    *pStatus = status;\n  }\n  baton_.post();\n}\n\nfolly::coro::Task<TimedBaton::Status> CancellableBaton::wait() noexcept {\n  XCHECK_EQ(status_, nullptr) << \"supports only one awaiting coro\";\n\n  TimedBaton::Status status = TimedBaton::Status::notReady;\n  status_ = &status;\n\n  /**\n   * __Does not support cancellation from another thread__\n   * ::post baton when cancelled; if cancellation is requested, this will\n   * immediately post the baton inline via CancellationCallback.\n   */\n  const auto& ct = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      ct, [this]() { signal(TimedBaton::Status::cancelled); }};\n\n  // folly::coro::Baton implements short-circuit eval to avoid suspension if\n  // already posted; never yields exception, no need to wrap in co_awaitTry\n  co_await baton_;\n\n  status_ = nullptr;\n\n  // possible if posted prior to ::wait(); if so transform status into\n  // ::signalled\n  if (status == TimedBaton::Status::notReady) {\n    status = TimedBaton::Status::signalled;\n  }\n\n  XCHECK(status == TimedBaton::Status::cancelled ||\n         status == TimedBaton::Status::signalled);\n\n  co_return ct.isCancellationRequested() ? TimedBaton::Status::cancelled\n                                         : status;\n}\n\nfolly::coro::Task<TimedBaton::Status> CancellableBaton::timedWaitImpl(\n    folly::EventBase* evb, std::chrono::milliseconds timeout) {\n  // > 0ms timeout must have evb\n  XCHECK(evb && timeout.count() > 0);\n  XCHECK_EQ(status_, nullptr) << \"supports only one awaiting coro\";\n\n  TimedBaton::Status status{TimedBaton::Status::notReady};\n  status_ = &status;\n\n  const auto& ct = co_await folly::coro::co_current_cancellation_token;\n  folly::CancellationCallback cancellationCallback{\n      ct, [this]() { signal(TimedBaton::Status::cancelled); }};\n\n  evb->timer().scheduleTimeout(&timerCb_, timeout);\n\n  co_await baton_;\n\n  status_ = nullptr;\n  timerCb_.cancelTimeout();\n\n  // possible if posted prior to ::wait(); if so transform status into\n  // ::signalled\n  if (status == TimedBaton::Status::notReady) {\n    status = TimedBaton::Status::signalled;\n  }\n\n  co_return status;\n}\n\nvoid DetachableCancellableBaton::detach() noexcept {\n  getTimerCb().cancelTimeout();\n}\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/CancellableBaton.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n\nnamespace proxygen::coro::detail {\n\n/**\n * A simple single waiter single poster wrapper around folly::coro::Baton that\n * supports both cancellation and timeout\n */\nclass CancellableBaton {\n public:\n  CancellableBaton() = default;\n  CancellableBaton(const CancellableBaton&) = delete;\n  CancellableBaton& operator=(const CancellableBaton&) = delete;\n  CancellableBaton(CancellableBaton&&) = delete;\n  CancellableBaton& operator=(CancellableBaton&&) = delete;\n  ~CancellableBaton() {\n    XCHECK_EQ(status_, nullptr);\n  }\n\n  // wait indefinitely until ::signal is called or cancellation requested\n  folly::coro::Task<TimedBaton::Status> wait() noexcept;\n\n  // wait for timeout (indefinite if 0ms), ::signal or cancellation requested\n  folly::coro::Task<TimedBaton::Status> timedWait(\n      folly::EventBase* evb, std::chrono::milliseconds timeout) noexcept {\n    // zero ms timeout considered indefinite timeout\n    return timeout.count() == 0 ? wait() : timedWaitImpl(evb, timeout);\n  }\n\n  void reset() {\n    XLOG(DBG8) << __func__;\n    status_ = nullptr;\n    baton_.reset();\n  }\n\n  void signal() {\n    signal(TimedBaton::Status::signalled);\n  }\n\n  bool ready() const {\n    return baton_.ready();\n  }\n\n protected:\n  folly::HHWheelTimer::Callback& getTimerCb() {\n    return timerCb_;\n  }\n\n private:\n  folly::coro::Task<TimedBaton::Status> timedWaitImpl(\n      folly::EventBase* evb, std::chrono::milliseconds timeout);\n\n  void signal(TimedBaton::Status status) noexcept;\n\n  struct TimerCallback : public folly::HHWheelTimer::Callback {\n    explicit TimerCallback(CancellableBaton& self) : self_(self) {\n    }\n    void timeoutExpired() noexcept override {\n      self_.signal(TimedBaton::Status::timedout);\n    }\n    CancellableBaton& self_;\n  } timerCb_{*this};\n\n  TimedBaton::Status* status_{nullptr};\n  folly::coro::Baton baton_;\n};\n\nstruct DetachableCancellableBaton : public CancellableBaton {\n  void detach() noexcept;\n};\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/CoroWtSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/coro/Transport.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/coro/util/CoroWtSession.h>\n\nnamespace {\nusing namespace proxygen::coro;\nusing folly::coro::co_error;\nusing folly::coro::co_nothrow;\nconstexpr uint64_t kMaxWriteSize = 65'535;\n\nBufQueue* asBodyEv(HTTPBodyEvent& event) {\n  return event.eventType == HTTPBodyEvent::BODY ? &event.event.body : nullptr;\n}\n\n}; // namespace\n\nnamespace proxygen::coro::detail {\n\nCoroWtSession::CoroWtSession(\n    folly::EventBase* evb,\n    WtDir dir,\n    WtStreamManager::WtConfig wtConfig,\n    std::unique_ptr<WebTransportHandler> handler,\n    std::unique_ptr<folly::coro::TransportIf> transport) noexcept\n    : CoroWtSessionBase(dir, wtConfig),\n      WtSessionBase(evb, sm),\n      wtHandler_(std::move(handler)),\n      transport_(std::move(transport)) {\n}\n\nCoroWtSession::~CoroWtSession() noexcept {\n  cs_.requestCancellation();\n}\n\nWtExpected<folly::Unit>::Type CoroWtSession::closeSession(\n    folly::Optional<uint32_t> error) noexcept {\n  cs_.requestCancellation();\n  sm.shutdown(WtStreamManager::CloseSession{.err = error.value_or(0),\n                                            .msg = \"closeSession\"});\n  return folly::unit;\n}\n\nusing WtCapsuleCallback = proxygen::detail::WtCapsuleCallback;\nfolly::coro::Task<void> CoroWtSession::readLoop(Ptr self) {\n  WtCapsuleCallback wtCapsuleCallback{sm, *self};\n  WebTransportCapsuleCodec codec{&wtCapsuleCallback, CodecVersion::H2};\n  folly::IOBufQueue ingressBuf{folly::IOBufQueue::cacheChainLength()};\n\n  while (!sm.isClosed()) {\n    auto readRes = co_await co_awaitTry(transport_->read(\n        ingressBuf,\n        /*minReadSize=*/1460,\n        /*newAllocationSize=*/4000,\n        /*timeout=*/std::chrono::milliseconds(0))); // TODO: timeout should be\n                                                    // changed from 0ms\n    if (readRes.hasException()) {\n      XLOG(DBG4) << __func__ << \"; ex=\" << readRes.exception();\n      break;\n    }\n\n    const bool eom = (*readRes == 0);\n    codec.onIngress(ingressBuf.move(), eom);\n\n    // handle any new peer streams\n    auto peerIds = std::move(wtSmIngressCb.peerStreams);\n    for (auto id : peerIds) {\n      auto handle = sm.getOrCreateBidiHandle(id);\n      if (handle.writeHandle) { // write handle iff bidi stream\n        wtHandler_->onNewBidiStream(handle);\n      } else if (handle.readHandle) {\n        wtHandler_->onNewUniStream(handle.readHandle);\n      }\n    }\n\n    if (eom) {\n      break;\n    }\n  }\n  XLOG(DBG4) << \"CoroWtSession::readLoop exiting\";\n  sm.shutdown(WtStreamManager::CloseSession{.err = 0x00,\n                                            .msg = \"stream ingress closed\"});\n  readLoopFinished();\n}\n\nfolly::coro::Task<void> CoroWtSession::writeLoop(Ptr self) {\n  folly::IOBufQueue egressBuf{folly::IOBufQueue::cacheChainLength()};\n  proxygen::detail::WtEventVisitor eventVisitor{.egress = egressBuf};\n  auto& waitForEventBaton = wtSmEgressCb.waitForEvent;\n\n  while (!eventVisitor.sessionClosed) {\n    // wait for WtStreamManager control events or writable streams\n    XLOG(DBG6) << \"waiting for WtStreamManager event\";\n    co_await waitForEventBaton.wait();\n    waitForEventBaton.reset();\n\n    XLOG(DBG6) << \"received WtStreamManager event\";\n    // always write control frames first\n    auto ctrl = sm.moveEvents();\n    for (auto& ev : ctrl) {\n      std::visit(eventVisitor, ev);\n    }\n\n    auto* wh = sm.nextWritable();\n    while (wh && egressBuf.chainLength() < kMaxWriteSize) {\n      auto id = wh->getID();\n      const auto atMost = kMaxWriteSize - egressBuf.chainLength();\n      auto dequeue = sm.dequeue(*wh, /*atMost=*/atMost);\n      writeWTStream(egressBuf,\n                    WTStreamCapsule{.streamId = id,\n                                    .streamData = std::move(dequeue.data),\n                                    .fin = dequeue.fin});\n      wh = sm.nextWritable();\n    }\n    if (wh) {\n      waitForEventBaton.signal(); // re-signal for remaining streams\n    }\n\n    if (!egressBuf.empty()) {\n      auto writeRes = co_await co_awaitTry(\n          transport_->write(egressBuf)); // TODO: plumb writeTimeout here\n      if (writeRes.hasException()) {\n        XLOG(DBG4) << __func__ << \"; ex=\" << writeRes.exception();\n        break;\n      }\n    }\n  }\n\n  XLOG(DBG4) << \"CoroWtSession::writeLoop exiting\";\n  transport_->shutdownWrite();\n  writeLoopFinished();\n  co_return;\n}\n\nvoid CoroWtSession::start(CoroWtSession::Ptr self) {\n  wtHandler_->onWebTransportSession(self);\n  auto ct = cs_.getToken();\n  auto* eventBase = evb();\n  co_withExecutor(eventBase, co_withCancellation(ct, readLoop(self))).start();\n  co_withExecutor(eventBase, co_withCancellation(ct, writeLoop(self))).start();\n}\n\nvoid CoroWtSession::writeLoopFinished() noexcept {\n  writeLoopDone_ = true;\n  if (readLoopDone_) {\n    std::exchange(wtHandler_, nullptr)->onSessionEnd(folly::none);\n  }\n}\nvoid CoroWtSession::readLoopFinished() noexcept {\n  readLoopDone_ = true;\n  if (writeLoopDone_) {\n    std::exchange(wtHandler_, nullptr)->onSessionEnd(folly::none);\n  }\n}\n\n/**\n * Generally speaking, heap allocated HTTPSources are consumer-owned (i.e. when\n * a consumer reads a terminal event, e.g. exc or eom, it will deallocate/free\n * itself). To simplify lifetime here, we utilize some trickery to create a\n * producer-owned HTTPSource.\n *\n * When a producer is done using the source (i.e. when this Deleter is invoked),\n * we check to see if the consuming side is also done (i.e. ::sourceComplete):\n *\n *     - If consumer is done, both producer and consumer are finished and we can\n *       free the object.\n *\n *     - If the consumer is not done however, we invoke ::setHeapAllocated\n *       (misnomer/confusing here) to transfer ownership to the consumer.\n */\nvoid EgressSourcePtrDeleter::operator()(EgressSource* source) noexcept {\n  if (!source->sourceComplete()) {\n    source->setHeapAllocated();\n    source->setCallback(nullptr);\n    source->abort(HTTPErrorCode::CANCEL);\n    return;\n  }\n  std::default_delete<HTTPStreamSource>{}(source);\n}\n} // namespace proxygen::coro::detail\n\nnamespace {\nusing namespace proxygen::coro::detail;\nusing folly::AsyncSocketException;\nusing AsyncSocketExceptionType = AsyncSocketException::AsyncSocketExceptionType;\n\nfolly::exception_wrapper makeSocketEx(AsyncSocketExceptionType type,\n                                      std::string_view err = \"\") {\n  return folly::make_exception_wrapper<AsyncSocketException>(type,\n                                                             std::string(err));\n}\n\n/**\n * Wraps an egress HTTPStreamSource and an ingress HTTPSource (also of concrete\n * type HTTPStreamSource, but irrelevant for the most part). Exposes reading\n * from an ingress HTTPSource & writing to an egress HTTPSource over a\n * folly::coro::TransportIf interface.\n */\nclass HttpSourceTransport : public folly::coro::TransportIf {\n public:\n  HttpSourceTransport(folly::EventBase* evb,\n                      EgressSourcePtr&& egressSource,\n                      HTTPSourceHolder&& ingress) noexcept\n      : evb_(evb),\n        egressSource_(std::move(egressSource)),\n        ingressSource_(std::move(ingress)) {\n    egressSource_->setCallback(&callback_);\n  }\n\n  ~HttpSourceTransport() noexcept override = default;\n\n  folly::EventBase* getEventBase() noexcept override {\n    return evb_;\n  }\n\n  folly::AsyncTransport* getTransport() const noexcept override {\n    return nullptr;\n  }\n\n  const folly::AsyncTransportCertificate* getPeerCertificate()\n      const noexcept override {\n    return nullptr;\n  }\n\n  folly::coro::Task<size_t> read(\n      folly::IOBufQueue& buf,\n      size_t /*minReadSize*/,\n      size_t /*newAllocationSize*/,\n      std::chrono::milliseconds timeout) noexcept override {\n    if (std::exchange(deferredEof_, false)) {\n      co_return 0;\n    }\n\n    // read after ingress terminal event is read (i.e. eom or exc) is an error\n    if (!ingressSource_.readable()) {\n      co_yield co_error(makeSocketEx(AsyncSocketExceptionType::INTERNAL_ERROR));\n    }\n\n    ingressSource_.setReadTimeout(timeout);\n    // loop until the first BodyEvent or exc is yielded\n    folly::Try<HTTPBodyEvent> ev{\n        HTTPBodyEvent{/*body=*/nullptr, /*inEOM=*/false}};\n    bool done = false;\n    while (!done) {\n      ev = co_await co_awaitTry(ingressSource_.readBodyEvent());\n      if (ev.hasException()) {\n        co_yield co_error(makeSocketEx(AsyncSocketExceptionType::END_OF_FILE,\n                                       ev.exception().what()));\n      }\n      auto* body = asBodyEv(*ev);\n      done = bool(body) || ev->eom; // loop again if not body event\n    }\n\n    uint64_t len = 0;\n    if (auto* body = asBodyEv(*ev)) {\n      len = body->chainLength();\n      buf.append(body->move()); // ok if nullptr\n    }\n    deferredEof_ = len > 0 && ev->eom;\n    co_return len;\n  }\n\n  folly::coro::Task<folly::Unit> write(folly::IOBufQueue& ioBufQueue,\n                                       std::chrono::milliseconds timeout,\n                                       folly::WriteFlags,\n                                       WriteInfo* info) noexcept override {\n    if (callback_.ex) {\n      XLOG(DBG4) << \"id=\" << getId() << \"; ex=\" << callback_.ex.what();\n      co_yield co_error(makeSocketEx(AsyncSocketExceptionType::NOT_OPEN,\n                                     callback_.ex.what()));\n    }\n    auto bytesAvailable = egressSource_->window().getNonNegativeSize();\n    XLOG(DBG4) << \"id=\" << getId() << \"; bytesAvailable=\" << bytesAvailable;\n    if (bytesAvailable == 0) {\n      XLOG(DBG5) << __func__ << \" egress blocked\";\n      callback_.waitForEgress.reset(); // suspend until egress drained\n    }\n    auto res = co_await callback_.waitForEgress.timedWait(evb_, timeout);\n    if (res == TimedBaton::Status::timedout) {\n      XLOG(DBG6) << \"id=\" << getId() << \"; ingress timeout\";\n      co_yield co_error(makeSocketEx(AsyncSocketExceptionType::TIMED_OUT));\n    }\n    if (res == TimedBaton::Status::cancelled) {\n      XLOG(DBG6) << \"id=\" << getId() << \"; cancelled\";\n      co_yield co_error(makeSocketEx(AsyncSocketExceptionType::CANCELED));\n    }\n    auto len = ioBufQueue.chainLength();\n    egressSource_->body(ioBufQueue.move(), /*padding=*/0, /*eom=*/false);\n    if (info) {\n      info->bytesWritten = len;\n    }\n    co_return folly::unit;\n  }\n\n  void close() noexcept override {\n    egressSource_->eom();\n    ingressSource_.setSource(nullptr);\n  }\n\n  void shutdownWrite() noexcept override {\n    egressSource_->eom();\n  }\n\n  void closeWithReset() noexcept override {\n    egressSource_->abort(HTTPErrorCode::CANCEL);\n    ingressSource_.setSource(nullptr);\n  }\n\n  // unimplemented fns\n  folly::coro::Task<folly::Unit> write(folly::ByteRange,\n                                       std::chrono::milliseconds,\n                                       folly::WriteFlags,\n                                       WriteInfo*) noexcept override {\n    XLOG(FATAL) << \"not implemented\";\n  }\n  folly::coro::Task<size_t> read(folly::MutableByteRange,\n                                 std::chrono::milliseconds) noexcept override {\n    XLOG(FATAL) << \"not implemented\";\n  }\n  folly::SocketAddress getLocalAddress() const noexcept override {\n    XLOG(FATAL) << \"not implemented\";\n  }\n  folly::SocketAddress getPeerAddress() const noexcept override {\n    XLOG(FATAL) << \"not implemented\";\n  }\n\n private:\n  uint64_t getId() const {\n    return egressSource_->getID();\n  }\n  folly::EventBase* evb_;\n  EgressSourcePtr egressSource_;\n  HTTPSourceHolder ingressSource_;\n  bool deferredEof_{false};\n  EgressBackPressure callback_; // initially signalled\n};\n\n} // namespace\n\nnamespace proxygen::coro::detail {\n\nstd::unique_ptr<folly::coro::TransportIf> makeHttpSourceTransport(\n    folly::EventBase* evb,\n    EgressSourcePtr&& source,\n    HTTPSourceHolder&& ingress) {\n  return std::make_unique<HttpSourceTransport>(\n      evb, std::move(source), std::move(ingress));\n}\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/CoroWtSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n#include <proxygen/lib/http/coro/HTTPSourceHolder.h>\n#include <proxygen/lib/http/coro/HTTPStreamSource.h>\n#include <proxygen/lib/http/coro/util/CancellableBaton.h>\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n#include <proxygen/lib/http/webtransport/WtUtils.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nnamespace folly::coro {\nclass TransportIf;\n}\n\nnamespace proxygen::coro::detail {\n\nusing WtStreamManager = proxygen::detail::WtStreamManager;\nusing WtDir = proxygen::detail::WtDir;\n\nusing StreamId = HTTPCodec::StreamID;\n\n/**\n * This is a helper utility (applicable to both http/2 and http/3) to apply\n * backpressure when we're blocked on writing data on the upgraded stream (i.e.\n * the stream originally carrying the CONNECT request, referred to as the\n * CONNECT stream by the wt http/3 rfc)\n */\nstruct EgressBackPressure : public HTTPStreamSource::Callback {\n  EgressBackPressure() {\n    waitForEgress.signal(); // initially signalled\n  }\n  void windowOpen(StreamId) override {\n    waitForEgress.signal();\n  }\n  void sourceComplete(StreamId, folly::Optional<HTTPError> err) override {\n    waitForEgress.signal();\n    ex = err.value_or(HTTPError{HTTPErrorCode::NO_ERROR});\n  }\n  CancellableBaton waitForEgress;\n  folly::exception_wrapper ex;\n};\n\n/**\n * This is a helper utility (applicable to both http/2 and http/3) that simply\n * posts a baton once an event is available to be dequeued from WtStreamManager.\n */\nstruct WtStreamManagerEgressCallback : WtStreamManager::EgressCallback {\n  ~WtStreamManagerEgressCallback() override = default;\n  void eventsAvailable() noexcept override {\n    waitForEvent.signal();\n  }\n  CancellableBaton waitForEvent;\n};\n\nstruct EgressSource : HTTPStreamSource {\n public:\n  using HTTPStreamSource::HTTPStreamSource;\n  using HTTPStreamSource::validateHeadersAndSkip;\n};\nstruct EgressSourcePtrDeleter {\n  void operator()(EgressSource*) noexcept;\n};\nusing EgressSourcePtr = std::unique_ptr<EgressSource, EgressSourcePtrDeleter>;\n\nstd::unique_ptr<folly::coro::TransportIf> makeHttpSourceTransport(\n    folly::EventBase* evb,\n    EgressSourcePtr&& egress,\n    HTTPSourceHolder&& ingress);\n\ntemplate <class T>\nstruct WtExpected {\n  using Type = folly::Expected<T, WebTransport::ErrorCode>;\n};\n\n// WtStreamManager must be constructed prior to WtSessionBase; shim class\nstruct CoroWtSessionBase {\n  CoroWtSessionBase(WtDir dir, WtStreamManager::WtConfig wtConfig) noexcept\n      : sm(dir, wtConfig, wtSmEgressCb, wtSmIngressCb, pq) {\n  }\n  quic::HTTPPriorityQueue pq;\n  WtStreamManagerEgressCallback wtSmEgressCb;\n  proxygen::detail::WtStreamManagerIngressCallback wtSmIngressCb;\n\n  WtStreamManager sm;\n};\n\nclass CoroWtSession\n    : public CoroWtSessionBase\n    , public proxygen::detail::WtSessionBase {\n public:\n  using Ptr = std::shared_ptr<CoroWtSession>;\n  CoroWtSession(folly::EventBase* evb,\n                WtDir dir,\n                WtStreamManager::WtConfig wtConfig,\n                std::unique_ptr<WebTransportHandler> handler,\n                std::unique_ptr<folly::coro::TransportIf> transport) noexcept;\n\n  ~CoroWtSession() noexcept override;\n\n  WtExpected<folly::Unit>::Type closeSession(\n      folly::Optional<uint32_t> error = folly::none) noexcept override;\n\n  // launches read & write loops\n  void start(Ptr self);\n\n private:\n  folly::coro::Task<void> readLoop(Ptr self);\n  folly::coro::Task<void> writeLoop(Ptr self);\n\n  void writeLoopFinished() noexcept;\n  void readLoopFinished() noexcept;\n\n  std::unique_ptr<WebTransportHandler> wtHandler_;\n  folly::CancellationSource cs_;\n  std::unique_ptr<folly::coro::TransportIf> transport_;\n  bool readLoopDone_ : 1 {false};\n  bool writeLoopDone_ : 1 {false};\n};\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/DetachableExecutor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/DetachableExecutor.h\"\n\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro::detail {\n\nvoid DetachableExecutor::add(folly::Func fn) {\n  XLOG(DBG8) << __func__ << \"; pEvb_=\" << pEvb_;\n  // must be \"attached\" and running in evb thread\n  XCHECK(pEvb_ && pEvb_->isInEventBaseThread());\n  auto loopCallback = std::make_unique<LoopCallback>(std::move(fn));\n  fnList_.push_back(*loopCallback);\n  pEvb_->runInLoop(loopCallback.release()); // deleted after invocation\n}\n\nvoid DetachableExecutor::detachEvb() {\n  XLOG(DBG8) << __func__ << \"; pEvb_=\" << pEvb_;\n  XCHECK(pEvb_->isInEventBaseThread());\n  XCHECK_EQ(state_, State::Detachable);\n  pEvb_ = nullptr;\n  for (auto& loopCallback : fnList_) {\n    loopCallback.cancelLoopCallback();\n  }\n}\n\nvoid DetachableExecutor::attachEvb(folly::EventBase* evb) {\n  XLOG(DBG8) << __func__ << \"; evb=\" << evb;\n  XCHECK(evb->isInEventBaseThread());\n  pEvb_ = evb;\n  for (auto& loopCallback : fnList_) {\n    pEvb_->runInLoop(&loopCallback);\n  }\n}\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/DetachableExecutor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/IntrusiveList.h>\n#include <folly/io/async/EventBase.h>\n\nnamespace proxygen::coro::detail {\n\n/**\n * An executor that allows changing the underlying eventbase via ::detachEvb and\n * ::attachEvb. Such semantics allows us to suspend all scheduled functions upon\n * detach, and re-schedule them upon attach. This is only possible by exploiting\n * the fact that ::add() via a coroutine executor *in HTTPCoroSession*\n * ::readLoop and ::writeLoop is only ever invoked from within the currently\n * attached EventBase.\n */\nclass DetachableExecutor : public folly::Executor {\n public:\n  explicit DetachableExecutor(folly::EventBase* pEvb)\n      : pEvb_(CHECK_NOTNULL(pEvb)) {\n  }\n  /**\n   * DetachableGuard should only be acquired in strategic suspension points\n   * (e.g. where a coroutine is most likely to be suspended waiting for some\n   * external event to happen) that allow us to migrate eventbases.\n   * Consequently, detachEvb should only be invoked when a DetachableGuard has\n   * been acquired.\n   */\n  enum State : uint8_t { Undetachable = 0, Detachable };\n  struct DetachableGuard {\n    friend class DetachableExecutor;\n    ~DetachableGuard() {\n      stateRef_ = Undetachable;\n    }\n\n   private:\n    explicit DetachableGuard(State& stateRef) : stateRef_(stateRef) {\n      stateRef_ = Detachable;\n    }\n\n   private:\n    State& stateRef_;\n  };\n\n  [[nodiscard]] State getState() const {\n    return state_;\n  }\n\n  DetachableGuard acquireGuard() {\n    return DetachableGuard{state_};\n  }\n\n  void add(folly::Func fn) override;\n\n  void detachEvb();\n  void attachEvb(folly::EventBase* evb);\n\n private:\n  using FunctionLoopCallback = folly::EventBase::FunctionLoopCallback;\n  struct LoopCallback : public FunctionLoopCallback {\n    using FunctionLoopCallback::FunctionLoopCallback;\n    ~LoopCallback() override = default;\n    folly::IntrusiveListHook listHook_;\n  };\n\n  folly::EventBase* pEvb_;\n  State state_{Undetachable};\n  using FunctionList =\n      folly::IntrusiveList<LoopCallback, &LoopCallback::listHook_>;\n  FunctionList fnList_;\n};\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/ExecutorSourceFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/ExecutorSourceFilter.h\"\n\nnamespace proxygen::coro {\n\nfolly::coro::Task<HTTPHeaderEvent> ExecutorSourceFilter::readHeaderEvent() {\n  if (evb_.isInEventBaseThread()) {\n    return readHeaderEventImpl(/*deleteOnDone=*/true);\n  }\n  return folly::coro::co_invoke([this]() -> folly::coro::Task<HTTPHeaderEvent> {\n    co_return co_await co_awaitTry(\n        co_withExecutor(&evb_, readHeaderEventImpl(/*deleteOnDone=*/true)));\n  });\n}\n\nfolly::coro::Task<HTTPBodyEvent> ExecutorSourceFilter::readBodyEvent(\n    uint32_t max) {\n  if (evb_.isInEventBaseThread()) {\n    return readBodyEventImpl(max, /*deleteOnDone=*/true);\n  }\n  return folly::coro::co_invoke([this,\n                                 max]() -> folly::coro::Task<HTTPBodyEvent> {\n    auto ev = co_await co_awaitTry(\n        co_withExecutor(&evb_, readBodyEventImpl(max, /*deleteOnDone=*/true)));\n    // SUSPEND returns a task that must run in the producer's evb\n    while (ev.hasValue() && ev->eventType == HTTPBodyEvent::SUSPEND) {\n      co_await co_awaitTry(co_withExecutor(&evb_, std::move(ev->event.resume)));\n      ev = co_await co_awaitTry(co_withExecutor(\n          &evb_, readBodyEventImpl(max, /*deleteOnDone=*/true)));\n    }\n    co_return ev;\n  });\n}\n\nvoid ExecutorSourceFilter::stopReading(\n    folly::Optional<const HTTPErrorCode> error) {\n  evb_.runImmediatelyOrRunInEventBaseThread(\n      [this, error]() { HTTPSourceFilter::stopReading(error); });\n}\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/ExecutorSourceFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/HTTPSourceFilter.h\"\n\nnamespace proxygen::coro {\n\n/**\n * This class must only be used by a single thread; it enables consuming from an\n * HTTPSource that a foreign/remote evb is producing events for. In other words,\n * it's effecitvely a wrapper to provide SPSC (SingleProducerSingleConsumer)\n * semantics.\n */\nclass ExecutorSourceFilter : public HTTPSourceFilter {\n public:\n  static std::unique_ptr<HTTPSourceFilter> make(folly::EventBase* evb) {\n    auto* executorSource = new ExecutorSourceFilter(*CHECK_NOTNULL(evb));\n    executorSource->setHeapAllocated();\n    return std::unique_ptr<HTTPSourceFilter>(executorSource);\n  }\n\n  folly::coro::Task<HTTPHeaderEvent> readHeaderEvent() override;\n  folly::coro::Task<HTTPBodyEvent> readBodyEvent(uint32_t max) override;\n  void stopReading(\n      folly::Optional<const HTTPErrorCode> error = folly::none) override;\n\n  // const access, ok to read\n  folly::Optional<uint64_t> getStreamID() const override {\n    return HTTPSourceFilter::getStreamID();\n  }\n  void setReadTimeout(std::chrono::milliseconds timeout) override {\n    evb_.runImmediatelyOrRunInEventBaseThread(\n        [this, timeout]() { HTTPSourceFilter::setReadTimeout(timeout); });\n  }\n\n private:\n  explicit ExecutorSourceFilter(folly::EventBase& evb) : evb_(evb) {\n    setHeapAllocated();\n  }\n  folly::EventBase& evb_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/Refcount.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\nclass Refcount {\n public:\n  explicit Refcount(size_t n = 0) : count_(n) {\n    if (count_ == 0) {\n      baton_.signal();\n    }\n  }\n\n  size_t count() const {\n    return count_;\n  }\n\n  void incRef() {\n    if (count_++ == 0) {\n      baton_.reset();\n    }\n  }\n\n  void decRef() {\n    XCHECK_GT(count_, 0UL);\n    if (--count_ == 0) {\n      baton_.signal();\n    }\n  }\n\n  folly::coro::Task<TimedBaton::Status> zeroRefs() {\n    return baton_.wait();\n  }\n\n protected:\n  detail::CancellableBaton baton_;\n\n private:\n  size_t count_;\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/TimedBaton.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/CancellationToken.h>\n#include <folly/coro/Baton.h>\n#include <folly/coro/Task.h>\n#include <folly/io/async/DestructorCheck.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/logging/xlog.h>\n\nnamespace proxygen::coro {\n\n/**\n * This class wraps folly::coro::Baton and provides two additional features:\n *  1) cancellation\n *  2) an optional timeout\n *\n * TimedBaton can only be used by a single thread (it is not thread safe).  If\n * using a non-zero timeout value, it must be used in an active EventBase\n * thread.\n *\n * Once the baton is signaled, it remains in the signaled state until reset().\n */\nclass TimedBaton\n    : public folly::HHWheelTimer::Callback\n    , public folly::DestructorCheck {\n public:\n  explicit TimedBaton(\n      folly::EventBase* eventBase,\n      std::chrono::milliseconds timeout = std::chrono::milliseconds{5000})\n      : eventBase_(eventBase), timeout_(timeout) {\n  }\n\n  enum class Status : uint8_t { notReady, signalled, timedout, cancelled };\n\n  void signal(Status status = Status::signalled) {\n    XCHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n    if (status == status_) {\n      return;\n    }\n    cancelTimeout();\n    if (status_ != Status::notReady && status_ != Status::signalled) {\n      XLOG(DBG4) << \"Ignoring TimedBaton signal while in error state=\"\n                 << uint32_t(status_);\n    } else {\n      status_ = status;\n    }\n    baton_.post();\n  }\n\n  folly::EventBase* getEventBase() const {\n    return eventBase_;\n  }\n\n  [[nodiscard]] Status getStatus() const {\n    return status_;\n  }\n\n  void reset() {\n    XCHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n    status_ = Status::notReady;\n    baton_.reset();\n  }\n\n  [[nodiscard]] std::chrono::milliseconds getTimeout() const {\n    return timeout_;\n  }\n\n  void setTimeout(std::chrono::milliseconds timeout) {\n    XCHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n    timeout_ = timeout;\n    if (waiting_ > 0) {\n      if (timeout_.count() > 0) {\n        XCHECK(eventBase_) << \"Must provide an event base when timeout is set\";\n        eventBase_->timer().scheduleTimeout(this, timeout_);\n      } else {\n        cancelTimeout();\n      }\n    }\n  }\n\n  folly::coro::Task<Status> wait() noexcept {\n    XCHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n    const auto& cancelToken =\n        co_await folly::coro::co_current_cancellation_token;\n    if (cancelToken.isCancellationRequested()) {\n      status_ = Status::cancelled;\n      co_return status_;\n    }\n    folly::CancellationCallback cancellationCallback{\n        cancelToken, [this, gone = destroyed_]() mutable {\n          if (!eventBase_ || eventBase_->isInEventBaseThread()) {\n            this->signal(Status::cancelled);\n            return;\n          }\n\n          eventBase_->runInEventBaseThread([this, gone = std::move(gone)]() {\n            if (!*gone) {\n              this->signal(Status::cancelled);\n            }\n          });\n        }};\n    if (timeout_.count() > 0 && !baton_.ready()) {\n      XCHECK(eventBase_) << \"Must provide an event base when timeout is set\";\n      eventBase_->timer().scheduleTimeout(this, timeout_);\n    }\n    waiting_++;\n    Safety safety(*this);\n    co_await baton_;\n    if (safety.destroyed()) {\n      co_return Status::cancelled;\n    }\n    waiting_--;\n    co_return status_;\n  }\n\n  ~TimedBaton() override {\n    // must be destructed in evb thread\n    XCHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n    *destroyed_ = true;\n    if (status_ == Status::notReady) {\n      signal(Status::cancelled);\n    }\n  }\n\n protected:\n  void timeoutExpired() noexcept override {\n    XLOG(DBG6) << \"in TimedBaton timeout expired\";\n    status_ = Status::timedout;\n    baton_.post();\n  }\n\n  void callbackCanceled() noexcept override {\n    signal(Status::cancelled);\n  }\n\n private:\n  folly::EventBase* eventBase_;\n  std::chrono::milliseconds timeout_;\n  folly::coro::Baton baton_;\n  Status status_{Status::notReady};\n  uint8_t waiting_{0};\n  std::shared_ptr<bool> destroyed_{std::make_shared<bool>(false)};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/Transport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/coro/TransportCallbacks.h>\n#include <folly/logging/xlog.h>\n\n#include \"proxygen/lib/http/coro/util/Transport.h\"\n\nnamespace proxygen::coro::detail {\n\nvoid Transport::detachEventBase() {\n  XCHECK(eventBase_->isInEventBaseThread());\n  XCHECK_EQ(readCB_, nullptr);\n  /**\n   * static_cast<folly::coro::ReadCallback> necessary to ::cancelTimeout below\n   * and ::rescheduleTimeout in attachEventBase. This is ok because the contract\n   * here is that folly::coro::Transport owns the AsyncTransport and is the only\n   * code installing and uninstalling ReadCallbacks.\n   */\n  readCB_ =\n      static_cast<folly::coro::ReadCallback*>(transport_->getReadCallback());\n  XLOG(DBG6) << __func__ << \"; readCB_=\" << readCB_;\n  transport_->setReadCB(nullptr); // ok if already uninstalled\n  if (readCB_) {\n    readCB_->cancelTimeout();\n  }\n  transport_->detachEventBase();\n}\n\nvoid Transport::attachEventBase(folly::EventBase* evb) {\n  XLOG(DBG6) << __func__ << \"; readCB_=\" << readCB_;\n  eventBase_ = evb;\n  transport_->attachEventBase(evb);\n  if (auto* readCB = std::exchange(readCB_, nullptr)) {\n    transport_->setReadCB(readCB);\n    readCB->scheduleTimeout(evb->timer());\n  }\n}\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/Transport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/coro/Transport.h>\n\nnamespace folly::coro {\nclass ReadCallback;\n};\n\nnamespace proxygen::coro::detail {\n\n/**\n * A transport that implements ::detachEvb & ::attachEvb; assumes the parent\n * task awaits ::read() on a DetachableExecutor\n */\nclass Transport : public folly::coro::Transport {\n  using folly::coro::Transport::Transport;\n\n private:\n  void detachEventBase() override;\n  void attachEventBase(folly::EventBase* evb) override;\n  // represents the suspended read callback; set by detachEvb & reset by\n  // attachEvb\n  folly::coro::ReadCallback* readCB_{nullptr};\n};\n\n} // namespace proxygen::coro::detail\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/WindowContainer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/Window.h>\n\nnamespace proxygen::coro {\n\nclass WindowContainer {\n public:\n  explicit WindowContainer(uint32_t capacity) : recvWindow_(capacity) {\n  }\n\n  bool reserve(size_t length, uint16_t padding, bool strict = true) {\n    if (length + padding > std::numeric_limits<uint32_t>::max() ||\n        !recvWindow_.reserve(uint32_t(length) + padding, strict)) {\n      return false;\n    }\n    recvWindow_.free(padding);\n    recvToAck_ += padding;\n    return true;\n  }\n\n  size_t processed(size_t amount) {\n    XCHECK_LE(amount, std::numeric_limits<uint32_t>::max());\n    XCHECK(recvWindow_.free(uint32_t(amount)));\n    recvToAck_ += amount;\n    size_t ret = 0;\n    if (recvToAck_ >= kMinThreshold ||\n        recvToAck_ >= recvWindow_.getCapacity() / kUpdateThreshold) {\n      ret = recvToAck_;\n      recvToAck_ = 0;\n    }\n    return ret;\n  }\n\n  uint32_t setCapacity(uint32_t capacity) {\n    if (capacity < recvWindow_.getCapacity()) {\n      XLOG(ERR) << \"Can't shrink window capacity \" << capacity << \" < \"\n                << recvWindow_.getCapacity();\n      return 0;\n    } else {\n      uint32_t delta = capacity - recvWindow_.getCapacity();\n      XCHECK(recvWindow_.setCapacity(capacity)) << \"setCapacity overflow\";\n      return delta;\n    }\n  }\n\n  int32_t getSize() const {\n    return recvWindow_.getSize();\n  }\n\n  [[nodiscard]] const Window& getWindow() const {\n    return recvWindow_;\n  }\n\n private:\n  const static uint8_t kUpdateThreshold = 2;\n  constexpr static uint32_t kMinThreshold = 128 * 1024;\n\n  Window recvWindow_;\n  size_t recvToAck_{0};\n};\n\n} // namespace proxygen::coro\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/test/AwaitableKeepAliveTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/AwaitableKeepAlive.h\"\n#include \"proxygen/lib/http/coro/util/Refcount.h\"\n#include <folly/coro/GmockHelpers.h>\n#include <folly/coro/GtestHelpers.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n\nnamespace proxygen::coro::test {\n\nusing namespace proxygen::coro;\n\nclass TestObject : public detail::EnableAwaitableKeepAlive<TestObject> {\n public:\n  folly::coro::Task<void> run() {\n    co_await zeroRefs();\n    delete this;\n  }\n};\n\nclass AwaitableSharedPtrTest : public testing::Test {\n public:\n  void SetUp() override {\n    obj_ = new TestObject();\n  }\n\n protected:\n  TestObject* obj_{nullptr};\n};\n\nCO_TEST_F(AwaitableSharedPtrTest, NoRefs) {\n  co_await obj_->run();\n}\n\nCO_TEST_F(AwaitableSharedPtrTest, OneRef) {\n  auto ka = obj_->acquireKeepAlive();\n  auto executor = co_await folly::coro::co_current_executor;\n  co_withExecutor(\n      executor,\n      [](detail::KeepAlivePtr<TestObject> p) -> folly::coro::Task<void> {\n        co_return;\n      }(std::move(ka)))\n      .start();\n  co_await obj_->run();\n}\n\nCO_TEST_F(AwaitableSharedPtrTest, TwoRefs) {\n  {\n    // Ref count goes 1 -> 2, 2 -> 1\n    auto ka = obj_->acquireKeepAlive();\n  }\n  // Back to 1\n  auto ka = obj_->acquireKeepAlive();\n  auto executor = co_await folly::coro::co_current_executor;\n  co_withExecutor(\n      executor,\n      [](detail::KeepAlivePtr<TestObject> p) -> folly::coro::Task<void> {\n        co_return;\n      }(std::move(ka)))\n      .start();\n  co_await obj_->run();\n}\n\nclass TestKeepAlive : public testing::Test {\n  // we can't use TestObject here due to false positive with free & atomic\n  // writes: https://github.com/google/sanitizers/issues/602\n public:\n  class AwaitableNoop : public detail::EnableAwaitableKeepAlive<AwaitableNoop> {\n   public:\n    using detail::EnableAwaitableKeepAlive<AwaitableNoop>::zeroRefs;\n    uint64_t val{0};\n  };\n};\n\n// tests both copy constructor and copy assignment operators\nTEST_F(TestKeepAlive, CopyConstructorAssignment) {\n  // object holds an implicit keepalive on construction\n  AwaitableNoop obj;\n  EXPECT_EQ(obj.numKeepAlives(), 1);\n\n  detail::KeepAlivePtr<AwaitableNoop> empty{};\n  EXPECT_FALSE(empty);\n\n  auto ka = obj.acquireKeepAlive();\n  EXPECT_EQ(obj.numKeepAlives(), 2);\n\n  // copy constructor\n  auto ka2 = ka;\n  EXPECT_EQ(obj.numKeepAlives(), 3);\n  // copy assignment\n  ka2 = empty;\n  EXPECT_EQ(obj.numKeepAlives(), 2);\n  // another copy assignment\n  ka2 = ka;\n  EXPECT_EQ(obj.numKeepAlives(), 3);\n}\n\n// tests both move constructor and move assignment operators\nTEST_F(TestKeepAlive, MoveConstructorAssignment) {\n  // object holds an implicit keepalive on construction\n  AwaitableNoop obj;\n  EXPECT_EQ(obj.numKeepAlives(), 1);\n\n  detail::KeepAlivePtr<AwaitableNoop> empty{};\n  EXPECT_FALSE(empty);\n\n  auto ka = obj.acquireKeepAlive();\n  EXPECT_EQ(obj.numKeepAlives(), 2);\n\n  // move constructor\n  auto ka2 = std::move(ka);\n  EXPECT_EQ(obj.numKeepAlives(), 2);\n  EXPECT_FALSE(ka);\n\n  // move assignment\n  ka2 = std::move(empty);\n  EXPECT_EQ(obj.numKeepAlives(), 1);\n\n  // another move assignment\n  ka2 = obj.acquireKeepAlive();\n  EXPECT_EQ(obj.numKeepAlives(), 2);\n}\n\nTEST_F(TestKeepAlive, ConcurrentAcquireAndReleaseKeepAlives) {\n  constexpr uint8_t kNumThreads{10};\n  constexpr uint8_t kNumKeepAliveCopiesPerThread{100};\n  std::array<folly::ScopedEventBaseThread, kNumThreads> evbThreads{};\n\n  AwaitableNoop obj{};\n  {\n    // initial keep alive\n    auto keepalive = obj.acquireKeepAlive();\n\n    // race a bunch of threads to acquire & release keepalives\n    for (uint8_t idx = 0; idx < evbThreads.size(); idx++) {\n      evbThreads[idx].add([ka = keepalive]() {\n        for (uint8_t i = 0; i < kNumKeepAliveCopiesPerThread; i++) {\n          // copy constructor test\n          auto copy_construct_ka = ka;\n\n          // move constructor test\n          auto move_construct_ka = std::move(copy_construct_ka);\n          EXPECT_TRUE(move_construct_ka);  // should be holding a non-null ptr\n          EXPECT_FALSE(copy_construct_ka); // was moved from\n\n          // default initialized KeepAlivePtr test\n          detail::KeepAlivePtr<AwaitableNoop> assign_ka{};\n          EXPECT_FALSE(assign_ka); // default initialized should return false\n\n          // move assignment test\n          assign_ka = std::move(move_construct_ka);\n          EXPECT_TRUE(assign_ka && !move_construct_ka);\n\n          // copy assignment test\n          assign_ka = ka;\n          EXPECT_TRUE(assign_ka && ka);\n        }\n      });\n    }\n  }\n\n  folly::EventBase evb;\n  folly::coro::blockingWait(obj.zeroRefs(), &evb);\n}\n\n// test acquiring keep alive after the implicit keepalive is released via\n// co_await\nTEST_F(TestKeepAlive, UseAfterFree) {\n  // object holds an implicit keepalive on construction; released on the next\n  // line\n  AwaitableNoop obj;\n  EXPECT_EQ(obj.numKeepAlives(), 1);\n  folly::coro::blockingWait(obj.zeroRefs());\n  EXPECT_EQ(obj.numKeepAlives(), 0);\n\n  // invoking acquireKeepAlive again should trigger an XCHECK\n  EXPECT_DEATH(obj.acquireKeepAlive(), \"use after free\");\n}\n\nTEST_F(TestKeepAlive, SimpleMemberAccess) {\n  AwaitableNoop obj;\n  // default constructed should be empty\n  detail::KeepAlivePtr<AwaitableNoop> ka;\n  XCHECK(!ka);\n\n  ka = obj.acquireKeepAlive();\n  EXPECT_EQ(ka->val, 0);\n  ka->val++;\n\n  // const access\n  const auto& kaRef = ka;\n  EXPECT_EQ(kaRef->val, 1);\n}\n\nclass RefcountTest : public testing::Test {\n protected:\n  folly::EventBase evb_;\n  Refcount refcount_{0};\n};\n\nTEST_F(RefcountTest, IncDec) {\n  refcount_.incRef();\n  EXPECT_EQ(refcount_.count(), 1);\n  bool zero = false;\n  co_withExecutor(&evb_, refcount_.zeroRefs())\n      .start()\n      .via(&evb_)\n      .then([&zero](folly::Try<TimedBaton::Status> status) {\n        EXPECT_FALSE(status.hasException());\n        EXPECT_EQ(*status, TimedBaton::Status::signalled);\n        zero = true;\n      });\n  evb_.loopOnce();\n  EXPECT_FALSE(zero);\n  refcount_.decRef();\n  evb_.loopOnce();\n  EXPECT_TRUE(zero);\n}\n\nTEST_F(RefcountTest, AlreadyZero) {\n  bool zero = false;\n  co_withExecutor(&evb_, refcount_.zeroRefs())\n      .start()\n      .via(&evb_)\n      .then([&zero](folly::Try<TimedBaton::Status> status) {\n        EXPECT_FALSE(status.hasException());\n        EXPECT_EQ(*status, TimedBaton::Status::signalled);\n        zero = true;\n      });\n  evb_.loopOnce();\n  EXPECT_TRUE(zero);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/test/CancellableBatonTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/CancellableBaton.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Sleep.h>\n#include <folly/coro/Task.h>\n#include <folly/futures/ThreadWheelTimekeeper.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n\n#include <folly/portability/GTest.h>\n\nnamespace proxygen::coro::test {\n\nTEST(CancellableBatonTest, Simple) {\n  folly::ManualExecutor executor;\n  detail::CancellableBaton baton;\n\n  auto fut = co_withExecutor(&executor, baton.wait()).startInlineUnsafe();\n\n  // no work enqueued; indefinitely stuck until baton is posted\n  EXPECT_FALSE(fut.isReady());\n  EXPECT_EQ(executor.step(), 0);\n  EXPECT_EQ(executor.step(), 0);\n  EXPECT_EQ(executor.step(), 0);\n  EXPECT_EQ(executor.step(), 0);\n\n  // signal should produce a value\n  baton.signal();\n  EXPECT_GT(executor.drain(), 0);\n\n  EXPECT_TRUE(fut.hasValue());\n  EXPECT_EQ(fut.value(), TimedBaton::Status::signalled);\n}\n\nTEST(CancellableBatonTest, AlreadySignalled) {\n  folly::ManualExecutor executor;\n  detail::CancellableBaton baton;\n  baton.signal();\n\n  auto fut = co_withExecutor(&executor, baton.wait()).startInlineUnsafe();\n\n  // future should already be ready\n  EXPECT_TRUE(fut.isReady());\n  executor.drain();\n\n  EXPECT_TRUE(fut.hasValue());\n  EXPECT_EQ(fut.value(), TimedBaton::Status::signalled);\n\n  baton.reset();\n}\n\nTEST(CancellableBatonTest, CancelViaAnotherThread) {\n  folly::ManualExecutor executor;\n  folly::CancellationSource cs{};\n  folly::Optional<detail::CancellableBaton> cancellableBaton{std::in_place_t{}};\n\n  auto t = std::thread([&]() { cs.requestCancellation(); });\n\n  auto waitTask = folly::coro::co_withCancellation(\n      cs.getToken(), cancellableBaton.value().wait());\n  auto res = folly::coro::blockingWait(std::move(waitTask), &executor);\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n\n  t.join();\n}\n\nTEST(CancellableBatonTest, Timeout) {\n  folly::EventBase evb;\n  detail::CancellableBaton baton;\n\n  // timeeout baton after 250ms\n  auto res = folly::coro::blockingWait(\n      baton.timedWait(&evb, std::chrono::milliseconds(250)), &evb);\n\n  EXPECT_EQ(res, TimedBaton::Status::timedout);\n}\n\nTEST(CancellableBatonTest, TimeoutDetach) {\n  folly::EventBase evb;\n  detail::DetachableCancellableBaton baton;\n\n  // invoked timedWait 10ms => detach => verify future not fulfilled after 10ms\n  auto fut = co_withExecutor(\n                 &evb, baton.timedWait(&evb, std::chrono::milliseconds(10)))\n                 .startInlineUnsafe();\n  baton.detach();\n\n  folly::EventBaseThreadTimekeeper tk{evb};\n  folly::coro::blockingWait(folly::coro::sleep(std::chrono::milliseconds(20)));\n\n  // verify ::detach cancelled scheduled timer\n  EXPECT_FALSE(fut.isReady());\n\n  baton.signal();\n  auto res = folly::coro::blockingWait(std::move(fut), &evb);\n\n  EXPECT_EQ(res, TimedBaton::Status::signalled);\n}\n\nTEST(CancellableBatonTest, PostBeforeTimeoutExpires) {\n  folly::EventBase evb;\n  detail::CancellableBaton baton;\n\n  // post before 250ms timeout\n  evb.runAfterDelay([&]() { baton.signal(); },\n                    /*milliseconds=*/25);\n\n  // timeeout baton after 250ms\n  auto res = folly::coro::blockingWait(\n      baton.timedWait(&evb, std::chrono::milliseconds(250)), &evb);\n\n  EXPECT_EQ(res, TimedBaton::Status::signalled);\n\n  // timer should be cancelled\n  EXPECT_EQ(evb.timer().count(), 0);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/test/DetachableExecutorTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/DetachableExecutor.h\"\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen::coro::detail;\n\nTEST(DetachableExecutor, Simple) {\n  folly::EventBase evb;\n  DetachableExecutor exec{&evb};\n\n  uint8_t num_fns_executed{0};\n  // invoking ::add on an attached executor should behave identically to\n  // EventBase::add\n  exec.add([&num_fns_executed]() { ++num_fns_executed; });\n  evb.loopOnce(EVLOOP_NONBLOCK);\n  EXPECT_EQ(num_fns_executed, 1);\n}\n\nTEST(DetachableExecutor, AcquireGuardTest) {\n  folly::EventBase evb;\n  DetachableExecutor exec{&evb};\n\n  // default state is Undetachable\n  EXPECT_EQ(exec.getState(), DetachableExecutor::State::Undetachable);\n  {\n    // test acquire guard construction and destruction\n    auto g = exec.acquireGuard();\n    EXPECT_EQ(exec.getState(), DetachableExecutor::State::Detachable);\n  }\n  EXPECT_EQ(exec.getState(), DetachableExecutor::State::Undetachable);\n}\n\nTEST(DetachableExecutor, TestDetachPriorToAdd) {\n  // construct DetachableExecutor and immediately detach\n  folly::EventBase evb;\n  DetachableExecutor exec{&evb};\n  {\n    // must acquire guard before detach\n    auto g = exec.acquireGuard();\n    exec.detachEvb();\n  }\n\n  // XCHECK will fail here; invariant is that ::add must be invoked while\n  // attached\n  EXPECT_DEATH(exec.add([]() {}), \"Check failed\");\n}\n\nTEST(DetachableExecutor, TestDetachAfterAdd) {\n  folly::EventBase evb;\n  DetachableExecutor exec{&evb};\n\n  class RunBeforeLoop : public folly::EventBase::LoopCallback {\n   public:\n    explicit RunBeforeLoop(folly::Func&& fn) : fn_(std::move(fn)) {\n    }\n\n    void runLoopCallback() noexcept override {\n      fn_();\n      delete this;\n    }\n\n   private:\n    folly::Func fn_;\n  };\n\n  // enqueue callback, but detach the executor in runBeforeLoop callback (i.e.\n  // detach after ::add but before the cob is executed)\n  uint8_t num_fns_executed{0};\n  exec.add([&num_fns_executed]() { ++num_fns_executed; });\n  evb.runBeforeLoop(new RunBeforeLoop([&]() {\n    auto g = exec.acquireGuard();\n    exec.detachEvb();\n  }));\n\n  evb.loopOnce(EVLOOP_NONBLOCK);\n  EXPECT_EQ(num_fns_executed, 0);\n\n  // if attach with an already queued cob, cob will be scheduled to run in next\n  // iter\n  exec.attachEvb(&evb);\n  evb.loopOnce(EVLOOP_NONBLOCK);\n  EXPECT_EQ(num_fns_executed, 1);\n}\n\nTEST(DetachableExecutor, AddWhenScheduled) {\n  folly::EventBase evb;\n  DetachableExecutor exec{&evb};\n\n  uint8_t num_fns_executed{0};\n  auto incFn = [&]() { num_fns_executed++; };\n\n  // this cob will be scheduled/executed prior to the subsequent\n  // DetachableExecutor::add below\n  evb.runInLoop([&]() {\n    // DetachableExecutor::add here will be invoked while DetachableExecutor is\n    // scheduled\n    exec.add(incFn);\n  });\n  exec.add([&num_fns_executed]() { ++num_fns_executed; });\n\n  // each fns should run in its own loop\n  evb.loopOnce(EVLOOP_NONBLOCK);\n  EXPECT_EQ(num_fns_executed, 1);\n  evb.loopOnce(EVLOOP_NONBLOCK);\n  EXPECT_EQ(num_fns_executed, 2);\n}\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/test/TestHelpers.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/coro/GtestHelpers.h>\n\n#define CO_TEST_X_(test_suite_name, test_name, parent_class, parent_id)        \\\n  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \\\n                \"test_suite_name must not be empty\");                          \\\n  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \\\n                \"test_name must not be empty\");                                \\\n  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \\\n      : public parent_class {                                                  \\\n   public:                                                                     \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \\\n    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \\\n    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &operator=(             \\\n        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \\\n                                     test_name) &) = delete; /* NOLINT */      \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \\\n    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &operator=(             \\\n        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \\\n                               test_name) &&) noexcept = delete; /* NOLINT */  \\\n                                                                               \\\n   private:                                                                    \\\n    void TestBody() override;                                                  \\\n    folly::coro::Task<void> co_TestBody();                                     \\\n    static ::testing::TestInfo *const test_info_ [[maybe_unused]];             \\\n  };                                                                           \\\n                                                                               \\\n  ::testing::TestInfo *const GTEST_TEST_CLASS_NAME_(test_suite_name,           \\\n                                                    test_name)::test_info_ =   \\\n      ::testing::internal::MakeAndRegisterTestInfo(                            \\\n          #test_suite_name,                                                    \\\n          #test_name,                                                          \\\n          nullptr,                                                             \\\n          nullptr,                                                             \\\n          ::testing::internal::CodeLocation(__FILE__, __LINE__),               \\\n          (parent_id),                                                         \\\n          ::testing::internal::SuiteApiResolver<                               \\\n              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \\\n          ::testing::internal::SuiteApiResolver<                               \\\n              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \\\n          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \\\n              test_suite_name, test_name)>);                                   \\\n  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() {        \\\n    try {                                                                      \\\n      folly::coro::blockingWait(co_TestBody(), getExecutor());                 \\\n    } catch (...) {                                                            \\\n      folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR));              \\\n      throw;                                                                   \\\n    }                                                                          \\\n  }                                                                            \\\n  folly::coro::Task<void> GTEST_TEST_CLASS_NAME_(test_suite_name,              \\\n                                                 test_name)::co_TestBody()\n\n// Like CO_TEST_F, but gets a DrivableExecutor from test_suite_name::getExecutor\n#define CO_TEST_F_X(test_fixture, test_name) \\\n  CO_TEST_X_(test_fixture,                   \\\n             test_name,                      \\\n             test_fixture,                   \\\n             ::testing::internal::GetTypeId<test_fixture>())\n\n// Like CO_TEST_P, but gets a DrivableExecutor from test_suite_name::getExecutor\n#define CO_TEST_P_X(test_suite_name, test_name)                                \\\n  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \\\n      : public test_suite_name {                                               \\\n   public:                                                                     \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {                     \\\n    }                                                                          \\\n    void TestBody() override;                                                  \\\n    folly::coro::Task<void> co_TestBody();                                     \\\n                                                                               \\\n   private:                                                                    \\\n    static int AddToRegistry() {                                               \\\n      ::testing::UnitTest::GetInstance()                                       \\\n          ->parameterized_test_registry()                                      \\\n          .GetTestSuitePatternHolder<test_suite_name>(                         \\\n              GTEST_STRINGIFY_(test_suite_name),                               \\\n              ::testing::internal::CodeLocation(__FILE__, __LINE__))           \\\n          ->AddTestPattern(                                                    \\\n              GTEST_STRINGIFY_(test_suite_name),                               \\\n              GTEST_STRINGIFY_(test_name),                                     \\\n              new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \\\n                  test_suite_name, test_name)>(),                              \\\n              ::testing::internal::CodeLocation(__FILE__, __LINE__));          \\\n      return 0;                                                                \\\n    }                                                                          \\\n    static int gtest_registering_dummy_ [[maybe_unused]];                      \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \\\n    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \\\n    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &operator=(             \\\n        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \\\n                                     test_name) &) = delete; /* NOLINT */      \\\n  };                                                                           \\\n  int GTEST_TEST_CLASS_NAME_(test_suite_name,                                  \\\n                             test_name)::gtest_registering_dummy_ =            \\\n      GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry();     \\\n  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() {        \\\n    try {                                                                      \\\n      folly::coro::blockingWait(co_TestBody(), getExecutor());                 \\\n    } catch (...) {                                                            \\\n      folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR));              \\\n      throw;                                                                   \\\n    }                                                                          \\\n  }                                                                            \\\n  folly::coro::Task<void> GTEST_TEST_CLASS_NAME_(test_suite_name,              \\\n                                                 test_name)::co_TestBody()\n"
  },
  {
    "path": "proxygen/lib/http/coro/util/test/TimedBatonTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/coro/util/TimedBaton.h\"\n#include \"proxygen/lib/http/coro/util/test/TestHelpers.h\"\n#include <folly/coro/BlockingWait.h>\n#include <folly/coro/Task.h>\n#include <folly/io/async/ScopedEventBaseThread.h>\n#include <folly/portability/GTest.h>\n\nnamespace proxygen::coro::test {\n\nclass TimedBatonTest : public testing::Test {\n public:\n  folly::EventBase* getExecutor() {\n    return &eventBase;\n  }\n\n  folly::EventBase eventBase;\n  folly::CancellationSource cancelSource;\n  TimedBaton timedBaton{&eventBase, std::chrono::milliseconds(0)};\n};\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonTimeOutTest) {\n  timedBaton.setTimeout(std::chrono::milliseconds(500));\n  auto res = co_await timedBaton.wait();\n  EXPECT_EQ(res, TimedBaton::Status::timedout);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::timedout);\n  // Signalling a baton in this state doesn't change the state\n  timedBaton.signal();\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::timedout);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonChangeTimeOutTest) {\n  // Start with 0 timeout, set timeout while waiting\n  auto fut =\n      co_withExecutor(&eventBase, timedBaton.wait()).start().via(&eventBase);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  EXPECT_FALSE(timedBaton.isScheduled());\n  timedBaton.setTimeout(std::chrono::milliseconds(500));\n  EXPECT_TRUE(timedBaton.isScheduled());\n  auto res = co_await std::move(fut);\n  EXPECT_EQ(res, TimedBaton::Status::timedout);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::timedout);\n  // Signalling a baton in this state doesn't change the state\n  timedBaton.signal();\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::timedout);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonNoTimeOutTest) {\n  // When timeout is 0, there is no timeout\n  auto fut = co_withExecutor(&eventBase,\n                             folly::coro::co_withCancellation(\n                                 cancelSource.getToken(), timedBaton.wait()))\n                 .start()\n                 .via(&eventBase);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  EXPECT_FALSE(timedBaton.isScheduled());\n  cancelSource.requestCancellation();\n  auto res = co_await std::move(fut);\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonDisableTimeOutTest) {\n  // Start with a timeout, set to 0 while waiting\n  timedBaton.setTimeout(std::chrono::milliseconds(500));\n  auto fut = co_withExecutor(&eventBase,\n                             folly::coro::co_withCancellation(\n                                 cancelSource.getToken(), timedBaton.wait()))\n                 .start()\n                 .via(&eventBase);\n  co_await folly::coro::co_reschedule_on_current_executor;\n  timedBaton.setTimeout(std::chrono::milliseconds(0));\n  EXPECT_FALSE(timedBaton.isScheduled());\n  cancelSource.requestCancellation();\n  auto res = co_await std::move(fut);\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n}\n\n// Signal from another task\nCO_TEST_F_X(TimedBatonTest, TimedBatonSignalTest) {\n  timedBaton.setTimeout(std::chrono::seconds(1));\n  auto signalTask = [](TimedBaton* timedBaton) -> folly::coro::Task<void> {\n    timedBaton->signal();\n    co_return;\n  };\n  co_withExecutor(&eventBase, signalTask(&timedBaton)).start();\n  auto res = co_await timedBaton.wait();\n  EXPECT_EQ(res, TimedBaton::Status::signalled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::signalled);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonCancelTest) {\n  timedBaton.setTimeout(std::chrono::seconds(1));\n  // Cancel directly\n  auto fut = co_withExecutor(&eventBase,\n                             folly::coro::co_withCancellation(\n                                 cancelSource.getToken(), timedBaton.wait()))\n                 .start()\n                 .via(&eventBase);\n  cancelSource.requestCancellation();\n  auto res = co_await std::move(fut);\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n\n  // Cancel via another task\n  folly::CancellationSource cancelSource1;\n  timedBaton.reset();\n  auto fut2 = co_withExecutor(&eventBase,\n                              folly::coro::co_withCancellation(\n                                  cancelSource1.getToken(), timedBaton.wait()))\n                  .start()\n                  .via(&eventBase);\n\n  auto cancelTask =\n      [](folly::CancellationSource* cancelSource) -> folly::coro::Task<void> {\n    cancelSource->requestCancellation();\n    co_return;\n  };\n\n  co_withExecutor(&eventBase, cancelTask(&cancelSource1)).start();\n  auto res2 = co_await std::move(fut2);\n  EXPECT_EQ(res2, TimedBaton::Status::cancelled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n  // Signalling a baton in this state doesn't change the state\n  timedBaton.signal();\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n}\n\nCO_TEST_F_X(TimedBatonTest, EarlySignal) {\n  timedBaton.setTimeout(std::chrono::milliseconds(10));\n  timedBaton.signal();\n  auto res = co_await timedBaton.wait();\n  EXPECT_EQ(res, TimedBaton::Status::signalled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::signalled);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonRequestCancellationOutsideEvb) {\n  /**\n   * The idea here is to invoke co_await timedBaton.wait() with\n   * cancellationToken inside evb; and subsequently request cancellation of the\n   * token via CancellationSource::requestCancellation in another thread\n   * (otherEvb here). Callbacks attached to the cancellationToken (via\n   * CancellationCallbacks) are invoked inline on the thread that requests\n   * cancellation (otherEvb here) and can lead to race conditions if not\n   * properly handled inside TimedBaton.\n   */\n  folly::ScopedEventBaseThread otherScopedEvb;\n  folly::EventBase* otherEvb = otherScopedEvb.getEventBase();\n  folly::CancellationSource cancellationSource;\n  auto cancellationToken = cancellationSource.getToken();\n  via(otherEvb).then(\n      [otherEvb, cs = std::move(cancellationSource)](auto&&) mutable {\n        // request cancellation after small 100ms delay to ensure\n        // TimedBaton::wait is invoked first\n        otherEvb->runAfterDelay(\n            [cs = std::move(cs)]() { cs.requestCancellation(); }, 100);\n      });\n\n  // start .wait() inside evb with cancellationToken\n  timedBaton.setTimeout(std::chrono::seconds(5));\n  auto res = co_await folly::coro::co_withCancellation(cancellationToken,\n                                                       timedBaton.wait());\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n  EXPECT_EQ(timedBaton.getStatus(), TimedBaton::Status::cancelled);\n}\n\nCO_TEST_F_X(TimedBatonTest, TimedBatonDestroyTest) {\n  auto timedBaton2 =\n      std::make_unique<TimedBaton>(&eventBase, std::chrono::seconds(10));\n  auto fut =\n      co_withExecutor(&eventBase, timedBaton2->wait()).start().via(&eventBase);\n  // wait() has to run before we delete the object\n  co_await folly::coro::co_reschedule_on_current_executor;\n  EXPECT_TRUE(timedBaton2->isScheduled());\n  timedBaton2.reset();\n  auto res = co_await std::move(fut);\n  EXPECT_EQ(res, TimedBaton::Status::cancelled);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/http/experimental/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_experimental_RFC1867\n  SRCS\n    RFC1867.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_utils_logging\n    Folly::folly_conv\n)\n"
  },
  {
    "path": "proxygen/lib/http/experimental/RFC1867.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/experimental/RFC1867.h>\n#include <proxygen/lib/utils/Logging.h>\n\nusing folly::IOBuf;\nusing folly::IOBufQueue;\nusing folly::StringPiece;\nusing folly::io::Cursor;\nusing std::string;\n\nnamespace {\n// This is required to get HTTP1xCodec ready to parse a header block\nconst string kDummyGet(\"GET / HTTP/1.0\");\n\nenum class BoundaryResult { YES, NO, PARTIAL };\n\nBoundaryResult isBoundary(const IOBuf& buf,\n                          uint32_t offset,\n                          char const* boundary,\n                          size_t boundarylen) {\n  assert(offset <= buf.length());\n  const IOBuf* crtBuf = &buf;\n  do {\n    size_t crtLen = crtBuf->length() - offset;\n    const uint8_t* crtData = crtBuf->data() + offset;\n    size_t cmplen = std::min(crtLen, boundarylen);\n    if (memcmp(crtData, boundary, cmplen) == 0) {\n      if (cmplen == boundarylen) {\n        return BoundaryResult::YES;\n      } else {\n        // beginning of a partial match\n        boundary += cmplen;\n        boundarylen -= cmplen;\n      }\n    } else {\n      return BoundaryResult::NO;\n    }\n    offset = 0;\n    crtBuf = crtBuf->next();\n  } while (crtBuf != &buf);\n\n  return BoundaryResult::PARTIAL;\n}\n\n} // namespace\n\nnamespace proxygen {\n\nstd::unique_ptr<IOBuf> RFC1867Codec::onIngress(std::unique_ptr<IOBuf> data) {\n  static auto dummyBuf =\n      IOBuf::wrapBuffer(kDummyGet.data(), kDummyGet.length());\n  IOBufQueue result{IOBufQueue::cacheChainLength()};\n  bool foundBoundary = false;\n  BoundaryResult br = BoundaryResult::NO;\n\n  input_.append(std::move(data));\n  while (!input_.empty()) {\n    switch (state_) {\n      case ParserState::START:\n        // first time, must start with boundary without leading \\n\n        br = isBoundary(\n            *input_.front(), 0, boundary_.data() + 1, boundary_.length() - 1);\n        if (br == BoundaryResult::NO) {\n          if (callback_) {\n            LOG(ERROR) << \"Invalid starting sequence\";\n            callback_->onError();\n          }\n          state_ = ParserState::ERROR;\n          return nullptr;\n        } else if (br == BoundaryResult::PARTIAL) {\n          return input_.move();\n        }\n        input_.trimStart(boundary_.length() - 1);\n        bytesProcessed_ += boundary_.length() - 1;\n        state_ = ParserState::HEADERS_START;\n        [[fallthrough]];\n\n      case ParserState::HEADERS_START: {\n        if (input_.chainLength() < 3) {\n          return input_.move();\n        }\n        Cursor c(input_.front());\n        char firstTwo[2];\n        c.pull(firstTwo, 2);\n        // We have at least 3 chars available to read\n        uint8_t toTrim = 3;\n        if (memcmp(firstTwo, \"--\", 2) == 0) {\n          do {\n            auto ch = c.read<char>();\n            if (ch == '\\n') {\n              input_.trimStart(toTrim);\n              state_ = ParserState::DONE;\n            } else if (ch == '\\r') {\n              // Every \\r we encounter is a char we must trim but we must\n              // make sure we have sufficient data available in input_ to\n              // keep reading (toTrim is always one pos ahead to handle the\n              // expected \\n)\n              ++toTrim;\n              if (input_.chainLength() < toTrim) {\n                return input_.move();\n              }\n            } else {\n              state_ = ParserState::ERROR;\n            }\n          } while (state_ == ParserState::HEADERS_START);\n          break;\n        }\n      }\n        headerParser_.setParserPaused(false);\n        headerParser_.onIngress(*dummyBuf);\n        CHECK(!parseError_);\n        state_ = ParserState::HEADERS;\n        [[fallthrough]];\n\n      case ParserState::HEADERS:\n        while (!parseError_ && input_.front() &&\n               state_ == ParserState::HEADERS) {\n          size_t bytesParsed = headerParser_.onIngress(*input_.front());\n          input_.trimStart(bytesParsed);\n          bytesProcessed_ += bytesParsed;\n        }\n        if (parseError_) {\n          if (callback_) {\n            LOG(ERROR) << \"Error parsing header data: \";\n            VLOG(3) << IOBufPrinter::printHexFolly(input_.front());\n            callback_->onError();\n          }\n          state_ = ParserState::ERROR;\n          return nullptr;\n        }\n        break;\n\n      case ParserState::FIELD_DATA:\n        result = readToBoundary(foundBoundary);\n        value_.append(result.move());\n        if (!value_.empty() && callback_) {\n          if (callback_->onFieldData(value_.move(), bytesProcessed_) < 0) {\n            LOG(ERROR) << \"Callback returned error\";\n            state_ = ParserState::ERROR;\n            return nullptr;\n          }\n        }\n        if (foundBoundary) {\n          if (callback_) {\n            callback_->onFieldEnd(true, bytesProcessed_);\n          }\n          state_ = ParserState::HEADERS_START;\n        } else {\n          if (input_.chainLength() > 0) {\n            VLOG(5) << \"Trailing input=\"\n                    << IOBufPrinter::printHexFolly(input_.front());\n          }\n          return input_.move();\n        }\n        break;\n      case ParserState::DONE:\n      case ParserState::ERROR:\n        // abort, consume all input\n        return nullptr;\n    }\n  }\n  return nullptr;\n}\n\nvoid RFC1867Codec::onHeadersComplete(HTTPCodec::StreamID /*stream*/,\n                                     std::unique_ptr<HTTPMessage> msg) {\n  static const StringPiece kName(\"name\", 4);\n  static const StringPiece kFilename(\"filename\", 8);\n  static const StringPiece kFormData(\"form-data\", 9);\n\n  const auto& contentDisp =\n      msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_DISPOSITION);\n  string name;\n  folly::Optional<string> filename; // filename is optional\n  HTTPMessage::splitNameValuePieces(\n      contentDisp,\n      ';',\n      '=',\n      [&](folly::StringPiece parameter, folly::StringPiece value) {\n        // TODO: Trim whitespace first\n        // Strip quotes if present\n        if (value.size() >= 2 && value[0] == '\\\"' &&\n            value[value.size() - 1] == '\\\"') {\n          value.reset(value.data() + 1, value.size() - 2);\n        }\n        if (parameter == kName) {\n          name = value.str();\n        } else if (parameter == kFilename) {\n          filename = value.str();\n        } else if (parameter != kFormData) {\n          LOG(WARNING) << \"Ignoring parameter \" << parameter << \" value \\\"\"\n                       << value << '\"';\n        }\n      });\n  if (name.empty()) {\n    if (callback_) {\n      LOG(ERROR) << \"name empty\";\n      callback_->onError();\n    }\n    state_ = ParserState::ERROR;\n    return;\n  } else {\n    state_ = ParserState::FIELD_DATA;\n    if (callback_ && callback_->onFieldStart(\n                         name, filename, std::move(msg), bytesProcessed_) < 0) {\n      field_ = name;\n      LOG(WARNING) << \"Callback returned error\";\n      state_ = ParserState::ERROR;\n    }\n  }\n}\n\nIOBufQueue RFC1867Codec::readToBoundary(bool& foundBoundary) {\n  IOBufQueue result{IOBufQueue::cacheChainLength()};\n  BoundaryResult boundaryResult = BoundaryResult::NO;\n\n  while (!input_.empty() && boundaryResult != BoundaryResult::PARTIAL) {\n    const IOBuf* head = input_.front();\n    uint64_t len = head->length();\n    const uint8_t* ptr = head->data();\n\n    /* iterate through first character matches */\n    while (len > 0 && (ptr = (const uint8_t*)memchr(ptr, boundary_[0], len))) {\n      /* calculate length after match */\n      uint64_t readlen = (ptr - head->data());\n      len = head->length() - readlen;\n      boundaryResult =\n          isBoundary(*head, readlen, boundary_.data(), boundary_.length());\n      if (boundaryResult == BoundaryResult::YES) {\n        CHECK(readlen < head->length());\n        bool hasCr = false;\n        if (readlen == 0 && pendingCR_) {\n          pendingCR_.reset();\n        }\n        if (readlen > 0) {\n          // If the last read char is a CR omit from result\n          Cursor c(head);\n          c.skip(readlen - 1);\n          auto ch = c.read<uint8_t>();\n          if (ch == '\\r') {\n            --readlen;\n            hasCr = true;\n          }\n        }\n        result.append(std::move(pendingCR_));\n        result.append(input_.split(readlen));\n        uint32_t trimLen = boundary_.length() + (hasCr ? 1 : 0);\n        input_.trimStart(trimLen);\n        bytesProcessed_ += readlen + trimLen;\n        foundBoundary = true;\n        return result;\n      } else if (boundaryResult == BoundaryResult::PARTIAL) {\n        break;\n      } else if (pendingCR_) {\n        // not a match, append pending CR to result\n        result.append(std::move(pendingCR_));\n      }\n\n      /* next character */\n      ptr++;\n      len--;\n    }\n    uint64_t resultLen = ptr ? ptr - head->data() : head->length();\n    // Put pendingCR_ in result if there was no partial match in head, or a\n    // partial match starting after the first character\n    if ((boundaryResult == BoundaryResult::NO || resultLen > 0) && pendingCR_) {\n      result.append(std::move(pendingCR_));\n    }\n    // the boundary does not start through resultLen, append it\n    // to result, except maybe the last char if it's a CR.\n    if (resultLen > 0 && head->data()[resultLen - 1] == '\\r') {\n      result.append(input_.split(resultLen - 1));\n      CHECK(!pendingCR_);\n      pendingCR_ = input_.split(1);\n    } else {\n      result.append(input_.split(resultLen));\n    }\n    bytesProcessed_ += resultLen;\n  }\n\n  // reached the end but no boundary found\n  foundBoundary = false;\n\n  return result;\n}\n\nvoid RFC1867Codec::onIngressEOM() {\n  if (state_ == ParserState::FIELD_DATA) {\n    LOG(WARNING) << \"Field not terminated by boundary\";\n    if (callback_) {\n      callback_->onFieldEnd(false, bytesProcessed_);\n    }\n  }\n  if (state_ != ParserState::HEADERS_START && state_ != ParserState::ERROR &&\n      state_ != ParserState::DONE) {\n    if (callback_) {\n      LOG(ERROR) << \"onIngressEOM with state_=\" << (uint8_t)state_;\n      callback_->onError();\n    }\n  }\n  state_ = ParserState::START;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/experimental/RFC1867.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n\nnamespace proxygen {\n\n/**\n * Class for stream-parsing RFC 1867 style post data.  At present it does\n * not support nested multi-part content (multipart/mixed).\n * Can parse multiple POST bodies\n * unless one of them invokes the onError() callback.  After that, the codec is\n * no longer usable.\n */\nclass RFC1867Codec : HTTPCodec::Callback {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    // return < 0 to skip remainder of field callbacks?\n    virtual int onFieldStart(const std::string& name,\n                             folly::Optional<std::string> filename,\n                             std::unique_ptr<HTTPMessage> msg,\n                             uint64_t postBytesProcessed) = 0;\n    virtual int onFieldData(std::unique_ptr<folly::IOBuf>,\n                            uint64_t postBytesProcessed) = 0;\n    /** On reading to end of a part indicated by boundary\n     * @param endedOnBoundary indicate successful part end\n     */\n    virtual void onFieldEnd(bool endedOnBoundary,\n                            uint64_t postBytesProcessed) = 0;\n    virtual void onError() = 0;\n  };\n\n  // boundary is the parameter to Content-Type, eg:\n  //\n  //   Content-type: multipart/form-data, boundary=AaB03x\n  explicit RFC1867Codec(const std::string& boundary) {\n    CHECK(!boundary.empty());\n    boundary_ = folly::to<std::string>(\"\\n--\", boundary);\n    headerParser_.setCallback(this);\n  }\n\n  void setCallback(Callback* callback) {\n    callback_ = callback;\n  }\n\n  // Pass the next piece of input data.  Returns unparsed data that requires\n  // more input to continue\n  std::unique_ptr<folly::IOBuf> onIngress(std::unique_ptr<folly::IOBuf> data);\n\n  // The end of input has been seen.  Validate the parser state and reset\n  // for more parsing.\n  void onIngressEOM();\n\n  uint64_t getBytesProcessed() const {\n    return bytesProcessed_;\n  }\n\n private:\n  enum class ParserState {\n    START,\n    HEADERS_START,\n    HEADERS,\n    FIELD_DATA, // part, or field, not only file\n    DONE,\n    ERROR\n  };\n\n  // HTTPCodec::Callback\n  void onMessageBegin(HTTPCodec::StreamID /*stream*/,\n                      HTTPMessage* /*msg*/) override {\n  }\n  void onHeadersComplete(HTTPCodec::StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override;\n  void onBody(HTTPCodec::StreamID /*stream*/,\n              std::unique_ptr<folly::IOBuf> /*chain*/,\n              uint16_t /*padding*/) override {\n    parseError_ = true;\n    headerParser_.setParserPaused(true);\n  }\n  void onTrailersComplete(HTTPCodec::StreamID /*stream*/,\n                          std::unique_ptr<HTTPHeaders> /*trailers*/) override {\n    parseError_ = true;\n    headerParser_.setParserPaused(true);\n  }\n  void onMessageComplete(HTTPCodec::StreamID /*stream*/,\n                         bool /*upgrade*/) override {\n    headerParser_.setParserPaused(true);\n  }\n\n  void onError(HTTPCodec::StreamID /*stream*/,\n               const HTTPException& /*error*/,\n               bool /*newTxn*/) override {\n    parseError_ = true;\n    headerParser_.setParserPaused(true);\n  }\n\n  folly::IOBufQueue readToBoundary(bool& foundBoundary);\n\n  std::string boundary_;\n  Callback* callback_{nullptr};\n  ParserState state_{ParserState::START};\n  HTTP1xCodec headerParser_{TransportDirection::DOWNSTREAM};\n  std::string field_;\n  folly::IOBufQueue input_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue value_;\n  std::unique_ptr<folly::IOBuf> pendingCR_;\n  uint64_t bytesProcessed_{0};\n  bool parseError_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/experimental/test/RFC1867Test.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/experimental/RFC1867.h>\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\nusing namespace testing;\nusing folly::IOBuf;\nusing folly::IOBufQueue;\nusing std::map;\nusing std::pair;\nusing std::string;\nusing std::unique_ptr;\n\nnamespace {\n\nconst std::string kTestBoundary(\"abcdef\");\n\n/** make multipart content with optional 'filename' parameter\n * @param simpleFields fields containing only 'name' parameter and text value\n * @param explicitFiles name => {filename, file content}\n * @param randomFiles name => {filename, filesize}\n */\nunique_ptr<IOBuf> makePost(\n    const map<string, string>& simpleFields,\n    const map<string, pair<string, string>>& explicitFiles,\n    const map<string, pair<string, size_t>>& randomFiles,\n    const string optExpHeaderSeqEnding = \"\") {\n  IOBufQueue result;\n  for (const auto& kv : simpleFields) {\n    result.append(\"--\");\n    result.append(kTestBoundary);\n    result.append(\"\\r\\nContent-Disposition: form-data; name=\\\"\");\n    result.append(kv.first);\n    result.append(\"\\\"\\r\\n\\r\\n\");\n    result.append(kv.second);\n    result.append(\"\\r\\n\");\n  }\n  for (const auto& kv : explicitFiles) {\n    result.append(\"--\");\n    result.append(kTestBoundary);\n    result.append(\"\\r\\nContent-Disposition: form-data; name=\\\"\");\n    result.append(kv.first + \"\\\"\");\n    auto& file = kv.second;\n    if (!file.first.empty()) {\n      result.append(\"; filename=\\\"\" + file.first + \"\\\"\");\n    }\n    result.append(\n        \"\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"\\r\\n\");\n    result.append(IOBuf::copyBuffer(file.second.data(), file.second.length()));\n    result.append(\"\\r\\n\");\n  }\n  for (const auto& kv : randomFiles) {\n    result.append(\"--\");\n    result.append(kTestBoundary);\n    result.append(\"\\r\\nContent-Disposition: form-data; name=\\\"\");\n    result.append(kv.first + \"\\\"\");\n    auto& file = kv.second;\n    if (!file.first.empty()) {\n      result.append(\"; filename=\\\"\" + file.first + \"\\\"\");\n    }\n    result.append(\n        \"\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"\\r\\n\");\n    result.append(proxygen::makeBuf(file.second));\n    result.append(\"\\r\\n\");\n  }\n  result.append(\"--\");\n  result.append(kTestBoundary);\n  result.append(optExpHeaderSeqEnding);\n\n  return result.move();\n}\n\n} // namespace\n\nnamespace proxygen {\n\nclass Mock1867Callback : public RFC1867Codec::Callback {\n public:\n  MOCK_METHOD(int,\n              onFieldStartImpl,\n              (const string& name,\n               const std::string& filename,\n               std::shared_ptr<HTTPMessage> msg,\n               uint64_t bytesProcessed));\n  int onFieldStartImpl(const string& name,\n                       const std::string& filename,\n                       std::unique_ptr<HTTPMessage> msg,\n                       uint64_t bytesProcessed) {\n    std::shared_ptr<HTTPMessage> sh_msg(msg.release());\n    return onFieldStartImpl(name, filename, sh_msg, bytesProcessed);\n  }\n  int onFieldStart(const string& name,\n                   folly::Optional<std::string> filename,\n                   std::unique_ptr<HTTPMessage> msg,\n                   uint64_t bytesProcessed) override {\n    return onFieldStartImpl(\n        name, filename.value_or(\"\"), std::move(msg), bytesProcessed);\n  }\n  MOCK_METHOD(int, onFieldData, (std::shared_ptr<folly::IOBuf>, uint64_t));\n  int onFieldData(std::unique_ptr<folly::IOBuf> data,\n                  uint64_t bytesProcessed) override {\n    std::shared_ptr<IOBuf> sh_data(data.release());\n    return onFieldData(sh_data, bytesProcessed);\n  }\n\n  MOCK_METHOD(void, onFieldEnd, (bool, uint64_t));\n  MOCK_METHOD(void, onError, ());\n};\n\nclass RFC1867Base {\n public:\n  void SetUp() {\n    codec_.setCallback(&callback_);\n  }\n\n  void parse(unique_ptr<IOBuf> input, size_t chunkSize = 0) {\n    IOBufQueue ibuf{IOBufQueue::cacheChainLength()};\n    ibuf.append(std::move(input));\n    if (chunkSize == 0) {\n      chunkSize = ibuf.chainLength();\n    }\n    unique_ptr<IOBuf> rem;\n    while (!ibuf.empty()) {\n      auto chunk = ibuf.split(std::min(chunkSize, ibuf.chainLength()));\n      if (rem) {\n        rem->prependChain(std::move(chunk));\n        chunk = std::move(rem);\n      }\n      rem = codec_.onIngress(std::move(chunk));\n    }\n    codec_.onIngressEOM();\n  }\n\n protected:\n  void testSimple(unique_ptr<IOBuf> data,\n                  size_t fileSize,\n                  size_t splitSize,\n                  size_t parts);\n\n  StrictMock<Mock1867Callback> callback_;\n  RFC1867Codec codec_{kTestBoundary};\n};\n\nclass RFC1867Test\n    : public testing::Test\n    , public RFC1867Base {\n public:\n  void SetUp() override {\n    RFC1867Base::SetUp();\n  }\n};\n\n/**\n * @param data full multipart content\n * @param filesize sum of all parts\n * @param parts number of parts\n */\nvoid RFC1867Base::testSimple(unique_ptr<IOBuf> data,\n                             size_t fileSize,\n                             size_t splitSize,\n                             size_t parts) {\n  size_t fileLength = 0;\n  IOBufQueue parsedData{IOBufQueue::cacheChainLength()};\n  EXPECT_CALL(callback_, onFieldStartImpl(_, _, _, _))\n      .Times(parts)\n      .WillRepeatedly(Return(0));\n  EXPECT_CALL(callback_, onFieldData(_, _))\n      .WillRepeatedly(Invoke([&](std::shared_ptr<IOBuf> data, uint64_t) {\n        fileLength += data->computeChainDataLength();\n        parsedData.append(data->clone());\n        return 0;\n      }));\n  EXPECT_CALL(callback_, onFieldEnd(true, _))\n      .Times(parts)\n      .WillRepeatedly(Return());\n  parse(data->clone(), splitSize);\n  auto parsedDataBuf = parsedData.move();\n  if (fileLength > 0) {\n    // isChained() called from coalesce below asserts if no data has\n    // been added\n    parsedDataBuf->coalesce();\n  }\n  CHECK_EQ(fileLength, fileSize);\n}\n\nTEST_F(RFC1867Test, TestSimplePost) {\n  size_t fileSize = 17;\n  auto data = makePost(\n      {{\"foo\", \"bar\"}, {\"jojo\", \"binky\"}}, {}, {{\"file1\", {\"\", fileSize}}});\n  testSimple(std::move(data), 3 + 5 + fileSize, 0, 3);\n}\n\nTEST_F(RFC1867Test, TestSplits) {\n  for (size_t i = 1; i < 500; i++) {\n    size_t fileSize = 1000 + i;\n    auto data = makePost(\n        {{\"foo\", \"bar\"}, {\"jojo\", \"binky\"}}, {}, {{\"file1\", {\"\", fileSize}}});\n    testSimple(std::move(data), 3 + 5 + fileSize, i, 3);\n  }\n}\n\nTEST_F(RFC1867Test, TestSplitsWithFilename) {\n  for (size_t i = 1; i < 500; i++) {\n    size_t fileSize = 1000 + i;\n    auto data = makePost({{\"foo\", \"bar\"}, {\"jojo\", \"binky\"}},\n                         {},\n                         {{\"file1\", {\"file1.txt\", fileSize}}});\n    testSimple(std::move(data), 3 + 5 + fileSize, i, 3);\n  }\n}\n\nTEST_F(RFC1867Test, TestHeadersChunkExtraCr) {\n  // We are testing here that we correctly chunk when the parser has just\n  // finished parsing a CR.\n  auto numCRs = 5;\n  auto headerEndingSeq = \"--\" + string(numCRs, '\\r') + \"\\n\";\n  auto fileSize = 10;\n  auto data = makePost({{\"foo\", \"bar\"}, {\"jojo\", \"binky\"}},\n                       {},\n                       {{\"file1\", {\"\", fileSize}}},\n                       headerEndingSeq);\n  // Math ensures we the parser will chunk at a '\\r' with a numCRs-1\n  testSimple(std::move(data), 3 + 5 + fileSize, numCRs - 1, 3);\n}\n\nclass RFC1867CR\n    : public testing::TestWithParam<string>\n    , public RFC1867Base {\n public:\n  void SetUp() override {\n    RFC1867Base::SetUp();\n  }\n};\n\nTEST_P(RFC1867CR, Test) {\n  for (size_t i = 1; i < GetParam().size(); i++) {\n    auto data = makePost({{\"foo\", \"bar\"}, {\"jojo\", \"binky\"}},\n                         {{\"file1\", {\"dummy file name\", GetParam()}}},\n                         {});\n    testSimple(std::move(data), 3 + 5 + GetParam().size(), i, 3);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(ValueTest,\n                         RFC1867CR,\n                         ::testing::Values(\n                             // embedded \\r\\n\n                             string(\"zyx\\r\\nwvu\", 8),\n                             // leading \\r\n                             string(\"\\rzyxwvut\", 8),\n                             // trailing \\r\n                             string(\"zyxwvut\\r\", 8),\n                             // leading \\n\n                             string(\"\\nzyxwvut\", 8),\n                             // trailing \\n\n                             string(\"zyxwvut\\n\", 8),\n                             // all \\r\\n\n                             string(\"\\r\\n\\r\\n\\r\\n\\r\\n\", 8),\n                             // all \\r\n                             string(\"\\r\\r\\r\\r\\r\\r\\r\\r\", 8)));\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/gen_HTTPCommonHeaders.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nset -e\n\n# Setup and validate arguments\n\nif [ \"x$1\" != \"x\" ]; then\n  ENUM_FILE_LIST=\"$1\"\nfi\nif [ \"x$2\" != \"x\" ];then\n  FBCODE_DIR=\"$2\"\nfi\nif [ \"x$3\" != \"x\" ]; then\n  OUTPUT_DIR=\"$3\"\nfi\nif [ \"x$4\" != \"x\" ]; then\n  GPERF=\"$4\"\nfi\n\n# The `awk` scripts aren't nearly as hairy as it seems. The only real trick is\n# the first line. We're processing two files -- the result of a `cat` pipeline\n# above, plus a template. The \"NR == FNR\" compares the current file's\n# line number with the total number of lines we've processed -- i.e., that test\n# means \"am I in the first file?\" So we suck those lines aside. Then we process\n# the second file, replacing \"%%%%%\" with some munging of the lines we sucked\n# aside from the `cat` pipeline. They are written in awk since it isn't really\n# worth the build system BS of calling out to Python (which is unfortunately\n# particularly annoying in Facebook's internal build system) and more portable\n# (and less hairy honestly) than the massive `sed` pipeline which used to be\n# here. And it really isn't that bad, this is the sort of thing `awk` was\n# designed for.\n\nAWK_HEADER_SCRIPT='\n  NR == FNR {\n    n[FNR] = $1;\n    max = FNR;\n    next\n  }\n  $1 == \"%%%%%\" {\n    for (i in n) {\n      h = n[i];\n      gsub(\"-\", \"_\", h);\n      gsub(\":\", \"COLON_\", h);\n      print \"  HTTP_HEADER_\" toupper(h) \" = \" i+1 \",\"\n    };\n    next\n  }\n  $1 == \"$$$$$\" {\n    print \"  constexpr static uint64_t num_codes = \" max+2 \";\";\n    next\n  }\n  {\n    gsub(\"%%name%%\", \"HTTPCommonHeaders\");\n    gsub(\"%%name_enum%%\", \"HTTPHeaderCode\");\n    gsub(\"%%enum_prefix%%\", \"HTTP_HEADER\");\n    gsub(\"%%table_type_name%%\", \"HTTPCommonHeaderTableType\");\n    print\n  }\n'\n\nAWK_SOURCE_SCRIPT='\n  NR == FNR {\n    n[FNR] = $1;\n    next\n  }\n  $1 == \"%%%%%\" {\n    print \"%%\";\n    for (i in n) {\n      h = n[i];\n      gsub(\"-\", \"_\", h);\n      gsub(\":\", \"COLON_\", h);\n      print n[i] \", HTTP_HEADER_\" toupper(h)\n    };\n    print \"%%\";\n    next\n  }\n  {\n    gsub(\"%%name%%\", \"HTTPCommonHeaders\");\n    gsub(\"%%name_internal%%\", \"HTTPCommonHeadersInternal\");\n    gsub(\"%%name_container%%\", \"HTTPCommonHeaderName\");\n    gsub(\"%%name_enum%%\", \"HTTPHeaderCode\");\n    gsub(\"%%header%%\", \"proxygen/lib/http/HTTPCommonHeaders.h\");\n    gsub(\"%%enum_other%%\", \"HTTP_HEADER_OTHER\");\n    gsub(\"%%table_type_name%%\", \"HTTPCommonHeaderTableType\");\n    print\n  }\n'\n\n# Load up the utility method for generating our desired perfect hash table\n# shellcheck source=/dev/null\n. \"${FBCODE_DIR?}/proxygen/lib/utils/gen_perfect_hash_table.sh\"\n\ngenerate_perfect_hash_table \\\n  \"${ENUM_FILE_LIST}\" \\\n  \"${FBCODE_DIR}/proxygen/lib/utils/perfect_hash_table_template.h\" \\\n  \"${AWK_HEADER_SCRIPT}\" \\\n  \"${OUTPUT_DIR}/HTTPCommonHeaders.h\" \\\n  \"${FBCODE_DIR}/proxygen/lib/utils/perfect_hash_table_template.cpp.gperf\" \\\n  \"${AWK_SOURCE_SCRIPT}\" \\\n  \"${OUTPUT_DIR?}/HTTPCommonHeaders.cpp\" \\\n  \"${GPERF}\"\n"
  },
  {
    "path": "proxygen/lib/http/observer/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_observer_session_observer_container\n  EXPORTED_DEPS\n    proxygen_http_observer_session_observer_interface\n    Folly::folly_observer_container\n)\n\nproxygen_add_library(proxygen_http_observer_session_observer_interface\n  SRCS\n    HTTPSessionObserverInterface.cpp\n  EXPORTED_DEPS\n    proxygen_http_message\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_observer_http_transaction_observer_container\n  EXPORTED_DEPS\n    proxygen_http_observer_http_transaction_observer_interface\n    Folly::folly_observer_container\n)\n\nproxygen_add_library(proxygen_http_observer_http_transaction_observer_interface\n  SRCS\n    HTTPTransactionObserverInterface.cpp\n  DEPS\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_http_message\n    proxygen_utils_time_util\n    Folly::folly_optional\n)\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPSessionObserverContainer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/ObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n\nnamespace proxygen {\n\nusing HTTPSessionObserverContainerBaseT = folly::ObserverContainer<\n    HTTPSessionObserverInterface,\n    HTTPSessionObserverAccessor,\n    folly::ObserverContainerBasePolicyDefault<\n        HTTPSessionObserverInterface::Events /* EventEnum */,\n        32 /* BitsetSize (max number of interface events) */>>;\n\nclass HTTPSessionObserverContainer : public HTTPSessionObserverContainerBaseT {\n\n  using HTTPSessionObserverContainerBaseT::HTTPSessionObserverContainerBaseT;\n\n public:\n  ~HTTPSessionObserverContainer() override = default;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPSessionObserverInterface.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <cstdint>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n\nnamespace proxygen {\n\nHTTPSessionObserverInterface::RequestStartedEvent::Builder&&\nHTTPSessionObserverInterface::RequestStartedEvent::Builder::setTimestamp(\n    const TimePoint& timestampIn) {\n  maybeTimestampRef = timestampIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::RequestStartedEvent::Builder&&\nHTTPSessionObserverInterface::RequestStartedEvent::Builder::setRequest(\n    const proxygen::HTTPMessage& requestIn) {\n  maybeRequestRef = requestIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::RequestStartedEvent::Builder&&\nHTTPSessionObserverInterface::RequestStartedEvent::Builder::\n    setTxnObserverAccessor(\n        proxygen::HTTPTransactionObserverAccessor* txnObserverAccessorIn) {\n  maybeTxnObserverAccessorPtr = txnObserverAccessorIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::RequestStartedEvent\nHTTPSessionObserverInterface::RequestStartedEvent::Builder::build() && {\n  return RequestStartedEvent(*this);\n}\n\nHTTPSessionObserverInterface::RequestStartedEvent::RequestStartedEvent(\n    const RequestStartedEvent::BuilderFields& builderFields)\n    : timestamp(*CHECK_NOTNULL(builderFields.maybeTimestampRef.get_pointer())),\n      request(*CHECK_NOTNULL(builderFields.maybeRequestRef.get_pointer())),\n      txnObserverAccessor(builderFields.maybeTxnObserverAccessorPtr) {\n}\n\nHTTPSessionObserverInterface::PreWriteEvent::Builder&&\nHTTPSessionObserverInterface::PreWriteEvent::Builder::setPendingEgressBytes(\n    const uint64_t& pendingEgressBytesIn) {\n  maybePendingEgressBytesRef = pendingEgressBytesIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::PreWriteEvent::Builder&&\nHTTPSessionObserverInterface::PreWriteEvent::Builder::setTimestamp(\n    const TimePoint& timestampIn) {\n  maybeTimestampRef = timestampIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::PreWriteEvent\nHTTPSessionObserverInterface::PreWriteEvent::Builder::build() && {\n  return PreWriteEvent(*this);\n}\n\nHTTPSessionObserverInterface::PreWriteEvent::PreWriteEvent(\n    PreWriteEvent::BuilderFields& builderFields)\n    : pendingEgressBytes(*CHECK_NOTNULL(\n          builderFields.maybePendingEgressBytesRef.get_pointer())),\n      timestamp(*CHECK_NOTNULL(builderFields.maybeTimestampRef.get_pointer())) {\n}\n\nHTTPSessionObserverInterface::PingReplyEvent::Builder&&\nHTTPSessionObserverInterface::PingReplyEvent::Builder::setId(\n    const uint64_t& idIn) {\n  maybeId = idIn;\n  return std::move(*this);\n}\nHTTPSessionObserverInterface::PingReplyEvent::Builder&&\nHTTPSessionObserverInterface::PingReplyEvent::Builder::setTimestamp(\n    const TimePoint& timestampIn) {\n  maybeTimestampRef = timestampIn;\n  return std::move(*this);\n}\n\nHTTPSessionObserverInterface::PingReplyEvent\nHTTPSessionObserverInterface::PingReplyEvent::Builder::build() && {\n  return PingReplyEvent(*this);\n}\n\nHTTPSessionObserverInterface::PingReplyEvent::PingReplyEvent(\n    PingReplyEvent::BuilderFields& builderFields)\n    : id(*CHECK_NOTNULL(builderFields.maybeId.get_pointer())),\n      timestamp(*CHECK_NOTNULL(builderFields.maybeTimestampRef.get_pointer())) {\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPSessionObserverInterface.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <chrono>\n#include <cstdint>\n#include <glog/logging.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <utility>\n\nnamespace proxygen {\n\nclass HTTPTransactionObserverAccessor;\n\n/**\n * Accessor object \"observed\" by HTTPSessionObservers.\n *\n * Instead of passing SessionObservers a pointer to HTTPSession or\n * HTTPSessionBase, we pass a pointer to this SessionAccessor object which\n * exposes functionality that can be used by the observers to get information\n * from the HTTP session and/or use public functions to manipulate the HTTP\n * session.\n *\n * This layer of indirection clearly defines the interface and expectations that\n * observers can expect from the HTTP session, which in turn enables the same\n * observer implementation to be used with different session implementations,\n * including future implementations which may not inherit from HTTPSessionBase.\n *\n * The lifetime of the HTTPSessionAccessor is tied to that of the underlying\n * HTTP session: when the session is destroyed, the accessor is destroyed as\n * well.\n */\nclass HTTPSessionObserverAccessor {\n public:\n  virtual std::size_t sendPing(uint64_t data) = 0;\n  virtual ~HTTPSessionObserverAccessor() = default;\n};\n\n/**\n * Observer of Http session events.\n */\nclass HTTPSessionObserverInterface {\n public:\n  using Clock = std::chrono::steady_clock;\n  using TimePoint = std::chrono::time_point<Clock>;\n  enum class Events {\n    RequestStarted = 1,\n    PreWrite = 2,\n    PingReply = 3,\n  };\n\n  virtual ~HTTPSessionObserverInterface() = default;\n\n  /**\n   * Event structures.\n   */\n\n  struct RequestStartedEvent {\n    const TimePoint timestamp;\n    const HTTPMessage& request;\n    HTTPTransactionObserverAccessor* txnObserverAccessor;\n\n    // Do not support copy or move given that request is a ref.\n    RequestStartedEvent(RequestStartedEvent&&) = delete;\n    RequestStartedEvent& operator=(const RequestStartedEvent&) = delete;\n    RequestStartedEvent& operator=(RequestStartedEvent&& rhs) = delete;\n    RequestStartedEvent(const RequestStartedEvent&) = delete;\n\n    struct BuilderFields {\n      folly::Optional<std::reference_wrapper<const TimePoint>>\n          maybeTimestampRef;\n      folly::Optional<std::reference_wrapper<const HTTPMessage>>\n          maybeRequestRef;\n      HTTPTransactionObserverAccessor* maybeTxnObserverAccessorPtr;\n      explicit BuilderFields() = default;\n    };\n\n    struct Builder : public BuilderFields {\n      Builder&& setTimestamp(const TimePoint& timestampIn);\n      Builder&& setRequest(const proxygen::HTTPMessage& requestIn);\n      Builder&& setTxnObserverAccessor(\n          proxygen::HTTPTransactionObserverAccessor* txnObserverAccessorIn);\n      RequestStartedEvent build() &&;\n      explicit Builder() = default;\n    };\n\n    // Use builder to construct.\n    explicit RequestStartedEvent(const BuilderFields& builderFields);\n  };\n\n  struct PreWriteEvent {\n    const uint64_t pendingEgressBytes;\n    const TimePoint timestamp;\n\n    PreWriteEvent(PreWriteEvent&&) = delete;\n    PreWriteEvent& operator=(const PreWriteEvent&) = delete;\n    PreWriteEvent& operator=(PreWriteEvent&& rhs) = delete;\n    PreWriteEvent(const PreWriteEvent&) = delete;\n\n    struct BuilderFields {\n      folly::Optional<std::reference_wrapper<const uint64_t>>\n          maybePendingEgressBytesRef;\n      folly::Optional<std::reference_wrapper<const TimePoint>>\n          maybeTimestampRef;\n\n      explicit BuilderFields() = default;\n    };\n\n    struct Builder : public BuilderFields {\n      Builder&& setPendingEgressBytes(const uint64_t& pendingEgressBytesIn);\n      Builder&& setTimestamp(const TimePoint& timestampIn);\n\n      PreWriteEvent build() &&;\n      explicit Builder() = default;\n    };\n\n    // Use builder to construct.\n    explicit PreWriteEvent(BuilderFields& builderFields);\n  };\n\n  struct PingReplyEvent {\n    const uint64_t id;\n    const TimePoint timestamp;\n\n    PingReplyEvent(PingReplyEvent&&) = delete;\n    PingReplyEvent& operator=(const PingReplyEvent&) = delete;\n    PingReplyEvent& operator=(PingReplyEvent&& rhs) = delete;\n    PingReplyEvent(const PingReplyEvent&) = delete;\n\n    struct BuilderFields {\n      folly::Optional<std::reference_wrapper<const uint64_t>> maybeId;\n      folly::Optional<std::reference_wrapper<const TimePoint>>\n          maybeTimestampRef;\n      explicit BuilderFields() = default;\n    };\n\n    struct Builder : public BuilderFields {\n      Builder&& setId(const uint64_t& idIn);\n      Builder&& setTimestamp(const TimePoint& timestampIn);\n      PingReplyEvent build() &&;\n      explicit Builder() = default;\n    };\n\n    // Use builder to construct.\n    explicit PingReplyEvent(BuilderFields& builderFields);\n  };\n\n  /**\n   * Events.\n   */\n\n  /**\n   * headersComplete() is invoked when all HTTP request headers are received.\n   *\n   * @param session  Http session.\n   * @param event    RequestStartedEvent with details.\n   */\n  virtual void requestStarted(HTTPSessionObserverAccessor* /* session */,\n                              const RequestStartedEvent& /* event */) noexcept {\n  }\n\n  /**\n   * preWrite() is invoked just before transactions are about to write to\n   * transport.\n   *\n   * @param session  Http session.\n   * @param event    PreWriteEvent with details.\n   */\n  virtual void preWrite(HTTPSessionObserverAccessor* /* session */,\n                        const PreWriteEvent& /* event */) noexcept {\n  }\n\n  virtual void pingReply(HTTPSessionObserverAccessor* /* session */,\n                         const PingReplyEvent& /* event */) noexcept {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPTransactionObserverContainer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/ObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverInterface.h>\n\nnamespace proxygen {\n\nclass HTTPTransactionObserverAccessor;\n\nusing HTTPTransactionObserverContainerBaseT = folly::ObserverContainer<\n    HTTPTransactionObserverInterface,\n    HTTPTransactionObserverAccessor,\n    folly::ObserverContainerBasePolicyDefault<\n        HTTPTransactionObserverInterface::Events /* EventEnum */,\n        32 /* BitsetSize (max number of interface events) */>>;\n\nclass HTTPTransactionObserverContainer\n    : public HTTPTransactionObserverContainerBaseT {\n public:\n  using HTTPTransactionObserverContainerBaseT::\n      HTTPTransactionObserverContainerBaseT;\n  ~HTTPTransactionObserverContainer() override = default;\n};\n\n/**\n * Accessor object observed by HTTPTransactionObserver(s).\n */\nclass HTTPTransactionObserverAccessor {\n public:\n  virtual ~HTTPTransactionObserverAccessor() = default;\n\n  virtual bool addObserver(\n      HTTPTransactionObserverContainer::Observer* observer) = 0;\n\n  virtual bool addObserver(\n      std::shared_ptr<HTTPTransactionObserverContainer::Observer> observer) = 0;\n\n  [[nodiscard]] virtual uint64_t getTxnId() const = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPTransactionObserverInterface.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <glog/logging.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverInterface.h>\n#include <utility>\n\nnamespace proxygen {\n\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder&&\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder::setTimestamp(\n    const proxygen::TimePoint& timestampIn) {\n  maybeTimestampRef = timestampIn;\n  return std::move(*this);\n}\n\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder&&\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder::setType(Type typeIn) {\n  type = typeIn;\n  return std::move(*this);\n}\n\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder&&\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder::setNumBytes(\n    const size_t numBytesIn) {\n  numBytes = numBytesIn;\n  return std::move(*this);\n}\n\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder&&\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder::setHeaders(\n    const HTTPMessage& headersIn) {\n  maybeHeadersRef = headersIn;\n  return std::move(*this);\n}\n\nHTTPTransactionObserverInterface::TxnBytesEvent\nHTTPTransactionObserverInterface::TxnBytesEvent::Builder::build() && {\n  return TxnBytesEvent(*this);\n}\n\nHTTPTransactionObserverInterface::TxnBytesEvent::TxnBytesEvent(\n    const TxnBytesEvent::BuilderFields& builderFields)\n    : timestamp(*CHECK_NOTNULL(builderFields.maybeTimestampRef.get_pointer())),\n      type(builderFields.type),\n      maybeNumBytes(builderFields.numBytes),\n      maybeHeadersRef(builderFields.maybeHeadersRef) {\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/observer/HTTPTransactionObserverInterface.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\nclass HTTPTransactionObserverAccessor;\n\n/**\n * Observer of HTTP transaction events.\n */\nclass HTTPTransactionObserverInterface {\n public:\n  virtual ~HTTPTransactionObserverInterface() = default;\n\n  enum class Events {\n    TxnBytes = 1,\n  };\n\n  struct TxnBytesEvent {\n    enum class Type : uint8_t {\n      LAST_HEADER_BYTE_READ,\n      BODY_BYTES_READ,\n      FIRST_HEADER_BYTE_WRITE,\n      FIRST_BODY_BYTE_WRITE,\n      FIRST_BODY_BYTE_ACK,\n      LAST_BODY_BYTE_WRITE,\n      LAST_BODY_BYTE_ACK,\n      BODY_BYTES_GENERATED,\n      HEADER_BYTES_GENERATED,\n    };\n\n    const proxygen::TimePoint timestamp;\n    const Type type;\n    const folly::Optional<size_t> maybeNumBytes;\n    const folly::Optional<std::reference_wrapper<const HTTPMessage>>\n        maybeHeadersRef;\n\n    TxnBytesEvent(TxnBytesEvent&&) = delete;\n    TxnBytesEvent& operator=(const TxnBytesEvent&) = delete;\n    TxnBytesEvent& operator=(TxnBytesEvent&& rhs) = delete;\n    TxnBytesEvent(const TxnBytesEvent&) = delete;\n\n    struct BuilderFields {\n      folly::Optional<std::reference_wrapper<const proxygen::TimePoint>>\n          maybeTimestampRef;\n      Type type;\n      size_t numBytes;\n      folly::Optional<std::reference_wrapper<const HTTPMessage>>\n          maybeHeadersRef;\n      explicit BuilderFields() = default;\n    };\n\n    struct Builder : public BuilderFields {\n      Builder&& setTimestamp(const proxygen::TimePoint& timestampIn);\n      Builder&& setType(Type typeIn);\n      Builder&& setNumBytes(const size_t numBytes);\n      Builder&& setHeaders(const proxygen::HTTPMessage& headersIn);\n      TxnBytesEvent build() &&;\n      explicit Builder() = default;\n    };\n\n    // Use builder to construct\n    explicit TxnBytesEvent(const BuilderFields& builderFields);\n  };\n\n  virtual void onBytesEvent(HTTPTransactionObserverAccessor* /* txn */,\n                            const TxnBytesEvent& /* event */) noexcept {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/AckLatencyEvent.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n\nnamespace proxygen {\n\nstruct AckLatencyEvent {\n  // The byte number that was acknowledged.\n  unsigned int byteNo;\n  // The latency between sending the byte and receiving the ack for that byte.\n  std::chrono::nanoseconds latency;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/ByteEventTracker.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n\n#include <string>\n\nnamespace proxygen {\n\nByteEventTracker::~ByteEventTracker() {\n  drainByteEvents();\n}\n\nvoid ByteEventTracker::absorb(ByteEventTracker&& other) {\n  byteEvents_ = std::move(other.byteEvents_);\n}\n\n// The purpose of self is to represent shared ownership during\n// processByteEvents.  This allows the owner to release ownership of the tracker\n// from a callback without causing problems\nbool ByteEventTracker::processByteEvents(std::shared_ptr<ByteEventTracker> self,\n                                         uint64_t bytesWritten) {\n  // update our local cache of the number of bytes written so far\n  DCHECK(bytesWritten >= bytesWritten_);\n  bytesWritten_ = bytesWritten;\n\n  while (!byteEvents_.empty() &&\n         (byteEvents_.front().byteOffset_ <= bytesWritten)) {\n    ByteEvent& event = byteEvents_.front();\n    int64_t latency;\n    auto txn = event.getTransaction();\n\n    switch (event.eventType_) {\n      case ByteEvent::FIRST_HEADER_BYTE:\n        txn->onEgressHeaderFirstByte();\n        break;\n      case ByteEvent::FIRST_BYTE:\n        txn->onEgressBodyFirstByte();\n        break;\n      case ByteEvent::LAST_BYTE:\n        txn->onEgressBodyLastByte();\n        break;\n      case ByteEvent::TRACKED_BYTE:\n        txn->onEgressTrackedByte();\n        break;\n      case ByteEvent::PING_REPLY_SENT:\n        latency = event.getLatency();\n        if (callback_) {\n          callback_->onPingReplyLatency(latency);\n        }\n        break;\n      case ByteEvent::SECOND_TO_LAST_PACKET:\n        // we don't track the write flush, so do nothing...\n        break;\n    }\n\n    // notify that the offset the ByteEvent is associated with has been written\n    // to the socket\n    onByteEventWrittenToSocket(event);\n\n    // deliver to the callback\n    if (callback_) {\n      callback_->onTxnByteEventWrittenToBuf(event);\n    }\n    if (event.callback_) {\n      event.callback_(event);\n    }\n    VLOG(5) << \" removing ByteEvent \" << event;\n    // explicitly remove from the list, in case delete event triggers a\n    // callback that would absorb this ByteEventTracker.\n    byteEvents_.pop_front_and_dispose([](ByteEvent* event) { delete event; });\n  }\n\n  return self.use_count() == 1;\n}\n\nsize_t ByteEventTracker::drainByteEvents() {\n  size_t numEvents = 0;\n  // everything is dead from here on, let's just drop all extra refs to txns\n  while (!byteEvents_.empty()) {\n    byteEvents_.pop_front_and_dispose([](ByteEvent* event) { delete event; });\n    ++numEvents;\n  }\n  return numEvents;\n}\n\nvoid ByteEventTracker::addLastByteEvent(HTTPTransaction* txn,\n                                        uint64_t byteNo,\n                                        ByteEvent::Callback callback) noexcept {\n  VLOG(5) << \" adding last byte event for \" << byteNo;\n  auto* event =\n      new TransactionByteEvent(byteNo, ByteEvent::LAST_BYTE, txn, callback);\n  byteEvents_.push_back(*event);\n}\n\nvoid ByteEventTracker::addTrackedByteEvent(\n    HTTPTransaction* txn,\n    uint64_t byteNo,\n    ByteEvent::Callback callback) noexcept {\n  VLOG(5) << \" adding tracked byte event for \" << byteNo;\n  auto* event =\n      new TransactionByteEvent(byteNo, ByteEvent::TRACKED_BYTE, txn, callback);\n  byteEvents_.push_back(*event);\n}\n\nvoid ByteEventTracker::addPingByteEvent(size_t pingSize,\n                                        TimePoint timestamp,\n                                        uint64_t bytesScheduled,\n                                        ByteEvent::Callback callback) {\n  // register a byte event on ping reply sent, and adjust the byteOffset_\n  // for others by one ping size\n  uint64_t offset = bytesScheduled + pingSize;\n  auto i = byteEvents_.rbegin();\n  for (; i != byteEvents_.rend(); ++i) {\n    if (i->byteOffset_ > bytesScheduled) {\n      VLOG(5) << \"pushing back ByteEvent from \" << *i << \" to \"\n              << ByteEvent(i->byteOffset_ + pingSize, i->eventType_);\n      i->byteOffset_ += pingSize;\n    } else {\n      break; // the rest of the events are already scheduled\n    }\n  }\n\n  ByteEvent* be = new PingByteEvent(offset, timestamp, callback);\n  if (i == byteEvents_.rend()) {\n    byteEvents_.push_front(*be);\n  } else if (i == byteEvents_.rbegin()) {\n    byteEvents_.push_back(*be);\n  } else {\n    --i;\n    CHECK_GT(i->byteOffset_, bytesScheduled);\n    byteEvents_.insert(i.base(), *be);\n  }\n}\n\nvoid ByteEventTracker::addFirstBodyByteEvent(uint64_t offset,\n                                             HTTPTransaction* txn,\n                                             ByteEvent::Callback callback) {\n  byteEvents_.push_back(\n      *new TransactionByteEvent(offset, ByteEvent::FIRST_BYTE, txn, callback));\n}\n\nvoid ByteEventTracker::addFirstHeaderByteEvent(uint64_t offset,\n                                               HTTPTransaction* txn,\n                                               ByteEvent::Callback callback) {\n  // onWriteSuccess() is called after the entire header has been written.\n  // It does not catch partial write case.\n  byteEvents_.push_back(*new TransactionByteEvent(\n      offset, ByteEvent::FIRST_HEADER_BYTE, txn, callback));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/ByteEventTracker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/AckLatencyEvent.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/TransactionByteEvents.h>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\nclass TTLBAStats;\n\n/**\n * ByteEventTracker can be used to fire application callbacks when a given\n * byte of a transport stream has been processed.  The primary usage is to\n * fire the callbacks when the byte is accepted by the transport, not when\n * the byte has been written on the wire, or acknowledged.\n *\n * Subclasses may implement handling of acknowledgement timing.\n */\nclass ByteEventTracker {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void onPingReplyLatency(int64_t latency) noexcept = 0;\n    virtual void onTxnByteEventWrittenToBuf(\n        const ByteEvent& event) noexcept = 0;\n    virtual void onDeleteTxnByteEvent() noexcept = 0;\n  };\n\n  virtual ~ByteEventTracker();\n  explicit ByteEventTracker(Callback* callback) : callback_(callback) {\n  }\n\n  /**\n   * Assumes the byte events of another ByteEventTracker that this object\n   * is replacing.\n   */\n  virtual void absorb(ByteEventTracker&& other);\n  void setCallback(Callback* callback) {\n    callback_ = callback;\n  }\n\n  /**\n   * drainByteEvents should be called to clear out any pending events holding\n   * transactions when processByteEvents will no longer be called\n   */\n  virtual size_t drainByteEvents();\n\n  /**\n   * processByteEvents is called whenever the transport has accepted more bytes.\n   * bytesWritten is the number of bytes written to the transport over its\n   * lifetime.\n   */\n  virtual bool processByteEvents(std::shared_ptr<ByteEventTracker> self,\n                                 uint64_t bytesWritten);\n\n  /**\n   * Called when a ByteEvent offset has been written to the socket.\n   *\n   * Triggered by processByteEvents. Can be overridden by subclasses to trigger\n   * adding timestamps on socket writes.\n   */\n  virtual void onByteEventWrittenToSocket(const ByteEvent& /* event */) {\n  }\n\n  /**\n   * The following methods add byte events for tracking\n   */\n  void addPingByteEvent(size_t pingSize,\n                        TimePoint timestamp,\n                        uint64_t bytesScheduledBeforePing,\n                        ByteEvent::Callback callback = nullptr);\n\n  virtual void addFirstBodyByteEvent(uint64_t offset,\n                                     HTTPTransaction* txn,\n                                     ByteEvent::Callback callback = nullptr);\n\n  virtual void addFirstHeaderByteEvent(uint64_t offset,\n                                       HTTPTransaction* txn,\n                                       ByteEvent::Callback callback = nullptr);\n\n  virtual void addLastByteEvent(\n      HTTPTransaction* txn,\n      uint64_t byteNo,\n      ByteEvent::Callback callback = nullptr) noexcept;\n  virtual void addTrackedByteEvent(\n      HTTPTransaction* txn,\n      uint64_t byteNo,\n      ByteEvent::Callback callback = nullptr) noexcept;\n\n  /**\n   * Disables socket timestamp tracking and drains any related events.\n   *\n   * Returns the number of socket timestamp events drained, if any.\n   * Only implemented for trackers with socket timestamp capabilities.\n   */\n  virtual size_t disableSocketTimestampEvents() {\n    // not implemented for base ByteEventTracker\n    return 0;\n  }\n\n  /** The base ByteEventTracker cannot track NIC TX. */\n  virtual void addTxByteEvent(uint64_t /*offset*/,\n                              ByteEvent::EventType /*eventType*/,\n                              HTTPTransaction* /*txn*/,\n                              ByteEvent::Callback = nullptr) {\n  }\n\n  /** The base ByteEventTracker cannot track ACKs. */\n  virtual void addAckByteEvent(uint64_t /*offset*/,\n                               ByteEvent::EventType /*eventType*/,\n                               HTTPTransaction* /*txn*/,\n                               ByteEvent::Callback = nullptr) {\n  }\n\n  /**\n   * HTTPSession uses preSend to truncate writes when timestamping is required.\n   *\n   * In TX and ACK-tracking ByteEventTrackers, preSend should examine pending\n   * byte events and return the number of bytes until the next byte requiring\n   * timestamping or 0 if none are pending. In addition, when returning non-zero\n   * value preSend should indicate the timestamping required (TX and/or ACK).\n   */\n  virtual uint64_t preSend(bool* /*cork*/,\n                           bool* /*timestampTx*/,\n                           bool* /*timestampAck*/,\n                           uint64_t /*bytesWritten*/) {\n    return 0;\n  }\n\n  virtual void setTTLBAStats(TTLBAStats* /* stats */) {\n  }\n\n protected:\n  // the last value of byteWritten passed to processByteEvents\n  // should always increase\n  uint64_t bytesWritten_ = 0;\n\n  // byteEvents_ is in the ascending order of ByteEvent::byteOffset_\n  folly::CountedIntrusiveList<ByteEvent, &ByteEvent::listHook> byteEvents_;\n\n  Callback* callback_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/ByteEvents.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/ByteEvents.h>\n\n#include <folly/Conv.h>\n\n#include <ostream>\n#include <proxygen/lib/utils/Time.h>\n#include <string>\n\nnamespace proxygen {\n\nconst char* const kTypeStrings[] = {\n    \"FIRST_BYTE\",\n    \"LAST_BYTE\",\n    \"PING_REPLY_SENT\",\n    \"FIRST_HEADER_BYTE\",\n    \"TRACKED_BYTE\",\n    \"SECOND_TO_LAST_PACKET\",\n};\n\nstd::ostream& operator<<(std::ostream& os, const ByteEvent& be) {\n  os << folly::to<std::string>(\n      \"(\", kTypeStrings[be.eventType_], \", \", be.byteOffset_, \")\");\n  return os;\n}\n\nint64_t PingByteEvent::getLatency() {\n  return millisecondsSince(pingRequestReceivedTime_).count();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/ByteEvents.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/IntrusiveList.h>\n#include <folly/Portability.h>\n#include <functional>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\nclass HTTPTransaction;\nclass ByteEvent {\n public:\n  enum class EventFlags : uint8_t { ACK = 0x01, TX = 0x02 };\n\n  enum EventType : unsigned {\n    FIRST_BYTE,\n    LAST_BYTE,\n    PING_REPLY_SENT,\n    FIRST_HEADER_BYTE,\n    TRACKED_BYTE,\n    SECOND_TO_LAST_PACKET,\n  };\n  using Callback = std::function<void(ByteEvent&)>;\n  FOLLY_PUSH_WARNING\n  FOLLY_CLANG_DISABLE_WARNING(\"-Wsigned-enum-bitfield\")\n  ByteEvent(uint64_t byteOffset,\n            EventType eventType,\n            Callback callback = nullptr)\n      : eventType_(eventType),\n        timestampTx_(false),\n        timestampAck_(false),\n        byteOffset_(byteOffset),\n        callback_(callback) {\n  }\n  FOLLY_POP_WARNING\n  virtual ~ByteEvent() = default;\n  EventType getType() const {\n    return eventType_;\n  }\n  uint64_t getByteOffset() const {\n    return byteOffset_;\n  }\n  virtual HTTPTransaction* getTransaction() const {\n    return nullptr;\n  }\n  virtual int64_t getLatency() {\n    return -1;\n  }\n\n  folly::SafeIntrusiveListHook listHook;\n  EventType eventType_ : 3; // packed w/ byteOffset_\n  // (tx|ack)Tracked_ is used by TX and ACK-tracking ByteEventTrackers to\n  // mark if TX and/or ACK timestamping has been requested for this ByteEvent.\n  // The ByteEventTracker's configuration determines which events TX and ACK\n  // timestamping is requested for.\n  //\n  // For ByteEvents with timestamps requested, TX and ACK timestamps can be\n  // captured by the ByteEventTracker::Callback handler by requesting them\n  // via calls to addTxByteEvent and addAckByteEvent respectively when the\n  // handler is processing the callback for onByteEvent. If the handler does\n  // not add these events, the timestamps will still be generated but will not\n  // be delivered to the handler.\n  bool timestampTx_ : 1;  // packed w/ byteOffset_\n  bool timestampAck_ : 1; // packed w/ byteOffset_\n  uint64_t byteOffset_ : (8 * sizeof(uint64_t) - 5);\n  Callback callback_{nullptr};\n};\n\nconstexpr ByteEvent::EventFlags operator|(const ByteEvent::EventFlags& lhs,\n                                          const ByteEvent::EventFlags& rhs) {\n  return static_cast<ByteEvent::EventFlags>(\n      std::underlying_type<ByteEvent::EventFlags>::type(lhs) |\n      std::underlying_type<ByteEvent::EventFlags>::type(rhs));\n}\n\nconstexpr bool operator&(const ByteEvent::EventFlags& lhs,\n                         const ByteEvent::EventFlags& rhs) {\n  return (std::underlying_type<ByteEvent::EventFlags>::type(lhs) &\n          std::underlying_type<ByteEvent::EventFlags>::type(rhs));\n}\n\nstd::ostream& operator<<(std::ostream& os, const ByteEvent& txn);\n\nclass PingByteEvent : public ByteEvent {\n public:\n  PingByteEvent(uint64_t byteOffset,\n                TimePoint pingRequestReceivedTime,\n                ByteEvent::Callback callback)\n      : ByteEvent(byteOffset, PING_REPLY_SENT, callback),\n        pingRequestReceivedTime_(pingRequestReceivedTime) {\n  }\n\n  int64_t getLatency() override;\n\n  TimePoint pingRequestReceivedTime_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_session_session\n  SRCS\n    HTTPSession.cpp\n    HTTPSessionBase.cpp\n  DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_session_stats\n    fizz::fizz\n    Folly::folly_conv\n    Folly::folly_cpp_attributes\n    Folly::folly_logging_logging\n    Folly::folly_random\n    Folly::folly_tracing_scoped_trace_section\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_compress_hpack\n    proxygen_http_codec_http2_codec\n    proxygen_http_codec_rate_limit_filters\n    proxygen_http_http_header_size\n    proxygen_http_http_utils\n    proxygen_http_observer_session_observer_container\n    proxygen_http_observer_session_observer_interface\n    proxygen_http_session_http_event\n    proxygen_http_session_http_session_activity_tracker\n    proxygen_http_session_http_transaction\n    proxygen_http_session_secondary_auth_mgr_base\n    proxygen_http_session_transaction_byte_events\n    proxygen_utils_shared_wheel_timer\n    proxygen_utils_time_util\n    wangle::wangle_acceptor_acceptor_core\n    fizz::fizz\n    Folly::folly_container_f14_hash\n    Folly::folly_intrusive_list\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_async_async_ssl_socket\n    Folly::folly_io_async_async_udp_socket\n    Folly::folly_io_async_ssl_context\n    Folly::folly_io_iobuf\n    Folly::folly_optional\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_session_server\n  SRCS\n    CodecErrorResponseHandler.cpp\n    HTTPDirectResponseHandler.cpp\n    HTTPDownstreamSession.cpp\n    HTTPErrorPage.cpp\n    HTTPSessionAcceptor.cpp\n    SimpleController.cpp\n  DEPS\n    proxygen_http_codec_http1x_codec\n    proxygen_http_codec_http2_codec\n    proxygen_http_session_http_session_codec_factory\n    Folly::folly_conv\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_codec_codec_factory\n    proxygen_http_http_headers\n    proxygen_http_session_http_transaction\n    proxygen_http_session_session\n    proxygen_services_http_acceptor\n    proxygen_utils_shared_wheel_timer\n    Folly::folly_io_async_async_ssl_socket\n)\n\nproxygen_add_library(proxygen_http_session_http_session_activity_tracker\n  SRCS\n    HTTPSessionActivityTracker.cpp\n  DEPS\n    proxygen_http_session_http_transaction\n    proxygen_http_session_transaction_byte_events\n  EXPORTED_DEPS\n    wangle::wangle_acceptor_acceptor_core\n)\n\nproxygen_add_library(proxygen_http_session_byte_events\n  SRCS\n    ByteEvents.cpp\n  DEPS\n    Folly::folly_conv\n  EXPORTED_DEPS\n    proxygen_utils_time_util\n    Folly::folly_intrusive_list\n    Folly::folly_portability\n)\n\nproxygen_add_library(proxygen_http_session_transaction_byte_events\n  SRCS\n    ByteEventTracker.cpp\n  EXPORTED_DEPS\n    proxygen_http_session_byte_events\n    proxygen_http_session_http_transaction\n    proxygen_utils_time_util\n)\n\nproxygen_add_library(proxygen_http_session_hq_byte_event_tracker\n  SRCS\n    HQByteEventTracker.cpp\n  EXPORTED_DEPS\n    proxygen_http_session_transaction_byte_events\n    mvfst::mvfst_api_transport\n)\n\nproxygen_add_library(proxygen_http_session_http2_priority_queue\n  SRCS\n    HTTP2PriorityQueue.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_http2_codec\n    proxygen_utils_shared_wheel_timer\n    Folly::folly_intrusive_list\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_session_http_event\n  SRCS\n    HTTPEvent.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_http_utils\n    glog::glog\n)\n\nproxygen_add_library(proxygen_http_session_http_state_machine_lib\n  SRCS\n    HTTPTransactionEgressSM.cpp\n    HTTPTransactionIngressSM.cpp\n  DEPS\n    Folly::folly_indestructible\n  EXPORTED_DEPS\n    proxygen_utils_state_machine\n)\n\nproxygen_add_library(proxygen_http_session_http_transaction\n  SRCS\n    HTTPTransaction.cpp\n  DEPS\n    proxygen_http_codec_webtransport_webtransport_framer\n    proxygen_http_http_headers\n    proxygen_http_session_stats\n    proxygen_http_webtransport_httpwebtransport\n    Folly::folly_conv\n    Folly::folly_io_async_event_base_manager\n    Folly::folly_tracing_scoped_trace_section\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_error\n    proxygen_http_codec_codec_common\n    proxygen_http_http_header_size\n    proxygen_http_http_utils\n    proxygen_http_message\n    proxygen_http_observer_http_transaction_observer_container\n    proxygen_http_observer_http_transaction_observer_interface\n    proxygen_http_session_byte_events\n    proxygen_http_session_http2_priority_queue\n    proxygen_http_session_http_event\n    proxygen_http_session_http_state_machine_lib\n    proxygen_http_sink_flow_control_info\n    proxygen_http_webtransport\n    proxygen_http_webtransport_webtransportimpl\n    proxygen_http_window\n    proxygen_utils_shared_wheel_timer\n    proxygen_utils_time_util\n    proxygen_utils_trace\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_transport\n    Folly::folly_io_async_delayed_destruction\n    Folly::folly_lang_assume\n    Folly::folly_network_address\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_http_session_http_upstream_session\n  SRCS\n    HTTPUpstreamSession.cpp\n  DEPS\n    proxygen_http_codec_http2_codec\n    proxygen_http_session_http_transaction\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_io_async_async_ssl_socket\n  EXPORTED_DEPS\n    proxygen_http_codec_compress_header_codec\n    proxygen_http_session_session\n    proxygen_http_session_stats\n    Folly::folly_io_async_ssl_context\n)\n\nproxygen_add_library(proxygen_http_session_http_session_codec_factory\n  SRCS\n    HTTPDefaultSessionCodecFactory.cpp\n  DEPS\n    proxygen_http_codec_codec_common\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_factory\n    proxygen_services_acceptor_configuration\n)\n\nproxygen_add_library(proxygen_http_session_secondary_auth_mgr_base\n  EXPORTED_DEPS\n    proxygen_http_codec_direction\n    fizz::fizz\n)\n\nproxygen_add_library(proxygen_http_session_secondary_auth_mgr\n  SRCS\n    SecondaryAuthManager.cpp\n  DEPS\n    fizz::fizz\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_http_session_secondary_auth_mgr_base\n    fizz::fizz\n)\n\nproxygen_add_library(proxygen_http_session_stream_dispatcher\n  SRCS\n    HQStreamDispatcher.cpp\n  EXPORTED_DEPS\n    proxygen_http_codec_hq_codec\n    mvfst::mvfst_api_transport\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_session_hq_session\n  SRCS\n    HQSession.cpp\n    HQStreamBase.cpp\n  DEPS\n    proxygen_http_codec_webtransport_webtransport_framer\n    proxygen_http_priority_functions\n    proxygen_http_session_stats\n    proxygen_http_webtransport_httpwebtransport\n    mvfst::mvfst_folly_utils\n    mvfst::mvfst_logging_qlogger_constants\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_cpp_attributes\n    Folly::folly_io_async_async_transport\n    Folly::folly_scope_guard\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_codec_hq_codec\n    proxygen_http_codec_http1x_codec\n    proxygen_http_codec_http2_codec\n    proxygen_http_http_utils\n    proxygen_http_session_hq_byte_event_tracker\n    proxygen_http_session_http_transaction\n    proxygen_http_session_quic_protocol_info\n    proxygen_http_session_session\n    proxygen_http_session_stream_dispatcher\n    proxygen_http_session_webtransport_filter\n    proxygen_utils_conditional_gate\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_common_buf_util\n    mvfst::mvfst_common_events_folly_eventbase\n    mvfst::mvfst_constants\n    mvfst::mvfst_priority_http_priority_queue\n    mvfst::mvfst_priority_priority_queue\n    Folly::folly_container_evicting_cache_map\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_async_socket\n    Folly::folly_io_async_delayed_destruction\n    Folly::folly_io_iobuf\n    Folly::folly_lang_assume\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_http_session_hq_upstream_session\n  SRCS\n    HQUpstreamSession.cpp\n  DEPS\n    wangle::wangle_acceptor_acceptor_core\n  EXPORTED_DEPS\n    proxygen_http_session_hq_session\n    mvfst::mvfst_common_events_folly_eventbase\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_http_session_hq_downstream_session\n  SRCS\n    HQDownstreamSession.cpp\n  EXPORTED_DEPS\n    proxygen_http_session_hq_session\n)\n\nproxygen_add_library(proxygen_http_session_quic_protocol_info\n  EXPORTED_DEPS\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_common_optional\n    wangle::wangle_acceptor_acceptor_core\n)\n\nproxygen_add_library(proxygen_http_session_webtransport_filter\n  EXPORTED_DEPS\n    proxygen_http_codec_hq_codec\n    proxygen_http_codec_webtransport_webtransport_capsule_codec\n    proxygen_http_http_message_filters\n    proxygen_http_session_http_transaction\n    proxygen_http_webtransport\n    Folly::folly_logging_logging\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/session/CodecErrorResponseHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/CodecErrorResponseHandler.h>\n\nusing folly::IOBuf;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nCodecErrorResponseHandler::CodecErrorResponseHandler(ErrorCode /*statusCode*/)\n    : txn_(nullptr) {\n}\n\nCodecErrorResponseHandler::~CodecErrorResponseHandler() = default;\n\nvoid CodecErrorResponseHandler::setTransaction(HTTPTransaction* txn) noexcept {\n  txn_ = txn;\n}\n\nvoid CodecErrorResponseHandler::detachTransaction() noexcept {\n  delete this;\n}\n\nvoid CodecErrorResponseHandler::onHeadersComplete(\n    std::unique_ptr<HTTPMessage> /*msg*/) noexcept {\n  VLOG(4) << \"discarding headers\";\n}\n\nvoid CodecErrorResponseHandler::onBody(unique_ptr<IOBuf> /*chain*/) noexcept {\n  VLOG(4) << \"discarding request body\";\n}\n\nvoid CodecErrorResponseHandler::onTrailers(\n    unique_ptr<HTTPHeaders> /*trailers*/) noexcept {\n  VLOG(4) << \"discarding request trailers\";\n}\n\nvoid CodecErrorResponseHandler::onEOM() noexcept {\n}\n\nvoid CodecErrorResponseHandler::onUpgrade(\n    UpgradeProtocol /*protocol*/) noexcept {\n}\n\nvoid CodecErrorResponseHandler::onError(const HTTPException& error) noexcept {\n  VLOG(4) << \"processing error \" << error;\n  txn_->sendAbort();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/CodecErrorResponseHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass HTTPErrorPage;\n\nclass CodecErrorResponseHandler : public HTTPTransaction::Handler {\n public:\n  explicit CodecErrorResponseHandler(ErrorCode statusCode);\n\n  // HTTPTransaction::Handler methods\n  void setTransaction(HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(UpgradeProtocol protocol) noexcept override;\n  void onError(const HTTPException& error) noexcept override;\n  // These are no-ops since the error response is already in memory\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n\n private:\n  ~CodecErrorResponseHandler() override;\n\n  HTTPTransaction* txn_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQByteEventTracker.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQByteEventTracker.h>\n\nnamespace {\nclass HQTransportByteEvent\n    : public proxygen::TransactionByteEvent\n    , public quic::ByteEventCallback {\n\n public:\n  using TransactionByteEvent::TransactionByteEvent;\n\n  void onByteEvent(quic::ByteEvent byteEvent) override {\n    if (txn_) {\n      switch (byteEvent.type) {\n        case quic::ByteEvent::Type::TX:\n          txn_->onEgressTrackedByteEventTX(*this);\n          break;\n        case quic::ByteEvent::Type::ACK:\n          txn_->onEgressTrackedByteEventAck(*this);\n          break;\n      }\n    }\n    delete this;\n  }\n\n  void onByteEventCanceled(\n      quic::ByteEventCancellation /* cancellation */) override {\n    delete this;\n  }\n};\n} // namespace\n\nnamespace proxygen {\n\nHQByteEventTracker::HQByteEventTracker(Callback* callback,\n                                       quic::QuicSocket* socket,\n                                       quic::StreamId streamId)\n    : ByteEventTracker(callback), socket_(socket), streamId_(streamId) {\n}\n\nvoid HQByteEventTracker::onByteEventWrittenToSocket(const ByteEvent& event) {\n  // create a ByteEvent\n  const auto& txn = event.getTransaction();\n  const auto& streamOffset = event.getByteOffset();\n  switch (event.eventType_) {\n    case ByteEvent::FIRST_BYTE:\n      [[fallthrough]];\n    case ByteEvent::LAST_BYTE: {\n      // install TX callback\n      {\n        auto cb = new HQTransportByteEvent(\n            streamOffset, event.eventType_, txn, nullptr);\n        auto ret = socket_->registerTxCallback(streamId_, streamOffset, cb);\n        if (ret.hasError()) {\n          // failed to install callback; destroy\n          delete cb;\n        }\n      }\n      // install ACK callback\n      {\n        auto cb = new HQTransportByteEvent(\n            streamOffset, event.eventType_, txn, nullptr);\n        auto ret =\n            socket_->registerDeliveryCallback(streamId_, streamOffset, cb);\n        if (ret.hasError()) {\n          // failed to install callback; destroy\n          delete cb;\n        }\n      }\n      break;\n    }\n    default:\n      break;\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQByteEventTracker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n#include <quic/api/QuicSocket.h>\n\nnamespace proxygen {\n\n/**\n * ByteEventTracker specialized for HQSession.\n */\nclass HQByteEventTracker : public ByteEventTracker {\n public:\n  HQByteEventTracker(Callback* callback,\n                     quic::QuicSocket* socket,\n                     quic::StreamId streamId);\n\n  /**\n   * Called when a ByteEvent offset has been written to the socket.\n   *\n   * Triggered by processByteEvents.\n   *\n   * HQByteEventTracker implementation registers callbacks for TX and ACK events\n   * with the underlying QuicSocket when certain ByteEvents are written.\n   */\n  void onByteEventWrittenToSocket(const ByteEvent& event) override;\n\n private:\n  quic::QuicSocket* const socket_;\n  const quic::StreamId streamId_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQDownstreamSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQDownstreamSession.h>\n\nnamespace proxygen {\n\nvoid HQDownstreamSession::onTransportReady() noexcept {\n  HQDownstreamSession::DestructorGuard dg(this);\n  if (!onTransportReadyCommon()) {\n    return;\n  }\n  if (infoCallback_) {\n    infoCallback_->onTransportReady(*this);\n  }\n  transportReadyNotified_ = true;\n}\n\nvoid HQDownstreamSession::onFullHandshakeDone() noexcept {\n  HQDownstreamSession::DestructorGuard dg(this);\n  if (infoCallback_) {\n    infoCallback_->onFullHandshakeCompletion(*this);\n  }\n}\n\nvoid HQDownstreamSession::onAppRateLimited() noexcept {\n  invokeOnEgressStreams(([](HQStreamTransportBase* stream) {\n                          stream->txn_.onEgressTransportAppRateLimited();\n                        }),\n                        false /* includeDetached */);\n}\n\nvoid HQDownstreamSession::onConnectionSetupErrorHandler(\n    quic::QuicError /* error */) noexcept {\n  // Currently the users of this callback treat it like a connect error,\n  // not a general connection error. Since we don't have proper separation\n  // suppress the errors after onTransportReady has happened.\n  if (infoCallback_ && !transportReadyNotified_) {\n    infoCallback_->onConnectionError(*this);\n  }\n}\n\nvoid HQDownstreamSession::setupOnHeadersComplete(HTTPTransaction* txn,\n                                                 HTTPMessage* msg) {\n  HTTPTransaction::Handler* handler =\n      getController()->getRequestHandler(*txn, msg);\n  CHECK(handler);\n  txn->setHandler(handler);\n  if (infoCallback_) {\n    infoCallback_->onIngressMessage(*this, *msg);\n  }\n}\n\nbool HQDownstreamSession::isDetachable(bool) const {\n  LOG(FATAL) << __func__ << \" is an upstream interface\";\n}\n\nvoid HQDownstreamSession::attachThreadLocals(\n    folly::EventBase*,\n    std::shared_ptr<const folly::SSLContext>,\n    const WheelTimerInstance&,\n    HTTPSessionStats*,\n    FilterIteratorFn,\n    HeaderCodec::Stats*,\n    HTTPSessionController*) {\n  LOG(FATAL) << __func__ << \" is an upstream interface\";\n}\n\nvoid HQDownstreamSession::detachThreadLocals(bool) {\n  LOG(FATAL) << __func__ << \" is an upstream interface\";\n}\n\nbool HQDownstreamSession::pushAllowedByGoaway(hq::PushId pushId) {\n  // TODO: This is always true since we ignore client sent GOAWAY right now\n  return pushId < peerMinUnseenId_;\n}\n\nHQDownstreamSession::HQEgressPushStream* FOLLY_NULLABLE\nHQDownstreamSession::createEgressPushStream(hq::PushId pushId,\n                                            quic::StreamId streamId,\n                                            quic::StreamId parentStreamId) {\n\n  VLOG(4) << __func__ << \"sess=\" << *this << \" pushId=\" << pushId\n          << \" isClosing()=\" << isClosing() << \" streamId=\" << streamId\n          << \" parentStreamId=\" << parentStreamId;\n\n  // Use version utils to ensure that the session is not in draining state\n  if (!pushAllowedByGoaway(pushId)) {\n    VLOG(3) << __func__ << \" Not creating - session is draining\"\n            << \" sess=\" << *this << \" pushId=\" << pushId\n            << \" isClosing()=\" << isClosing() << \" streamId=\" << streamId\n            << \" parentStreamId=\" << parentStreamId;\n    return nullptr;\n  }\n\n  auto codec = createCodec(streamId);\n\n  auto matchPair = egressPushStreams_.emplace(\n      std::piecewise_construct,\n      std::forward_as_tuple(streamId),\n      std::forward_as_tuple(\n          *this,\n          streamId,\n          pushId,\n          parentStreamId,\n          getNumTxnServed(),\n          std::move(codec),\n          WheelTimerInstance(transactionsTimeout_, getEventBase())));\n  incrementSeqNo();\n\n  pushIdToStreamId_[pushId] = streamId;\n  streamIdToPushId_[streamId] = pushId;\n\n  CHECK(matchPair.second) << \"Emplacement failed, despite earlier \"\n                             \"existence check.\";\n\n  // Generate the stream preface\n  matchPair.first->second.generateStreamPreface();\n\n  // Generate the push id\n  matchPair.first->second.generateStreamPushId();\n\n  // Notify pending egress on the stream\n  matchPair.first->second.notifyPendingEgress();\n\n  // tracks max historical streams\n  HTTPSessionBase::onNewOutgoingStream(getNumOutgoingStreams());\n\n  return &matchPair.first->second;\n}\n\n// this is the creation of outgoing pushed transaction\nHTTPTransaction* FOLLY_NULLABLE HQDownstreamSession::newPushedTransaction(\n    HTTPCodec::StreamID parentRequestStreamId,\n    HTTPTransaction::PushHandler* handler,\n    ProxygenError* error) {\n\n  if (isClosing()) {\n    VLOG(3) << __func__ << \" Not creating transaction - draining \";\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorTransportIsDraining);\n    return nullptr;\n  }\n\n  auto parentRequestStream = findNonDetachedStream(parentRequestStreamId);\n  if (!parentRequestStream) {\n    VLOG(3) << __func__\n            << \" Not creating transaction - request stream StreamID=\"\n            << parentRequestStreamId << \" not found\";\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorParentStreamNotExist);\n    return nullptr;\n  }\n\n  // Allocate a new egress unidirectional stream from the socket\n  // this method will throw exception on failure\n  auto pushStreamId = sock_->createUnidirectionalStream();\n\n  // Record the newly created push transaction in the session\n  // NOTE: should be stored in the transaction\n  // NOTE: should be cleaned up when the transaction is closed\n  if (!pushStreamId) {\n    VLOG(2) << __func__ << \" failed to create new unidirectional stream\";\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorCreatingStream);\n    return nullptr;\n  }\n\n  auto pushId = createNewPushId();\n\n  // Create the actual outgoing Egress Push stream.\n  auto pushStream = createEgressPushStream(\n      pushId, pushStreamId.value(), parentRequestStreamId);\n\n  if (!pushStream) {\n    LOG(ERROR) << \"Creation of the push stream failed, pushID=\" << pushId;\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorCreatingStream);\n    return nullptr;\n  }\n\n  VLOG(4) << \"New pushed transaction: pushId=\" << pushId\n          << \"; pushStreamId=\" << pushStreamId.value()\n          << \"; assocStreamId=\" << parentRequestStreamId;\n\n  pushStream->txn_.setHandler(handler);\n  return &pushStream->txn_;\n}\n\nHQSession::HQStreamTransportBase* HQDownstreamSession::findPushStream(\n    quic::StreamId streamId) {\n  return findEgressPushStream(streamId);\n}\n\nHQDownstreamSession::HQEgressPushStream* FOLLY_NULLABLE\nHQDownstreamSession::findEgressPushStream(quic::StreamId streamId) {\n  auto it = egressPushStreams_.find(streamId);\n  if (it == egressPushStreams_.end()) {\n    return nullptr;\n  } else {\n    auto pstream = &it->second;\n    DCHECK(pstream->isUsing(streamId));\n    return pstream;\n  }\n}\n\nbool HQDownstreamSession::erasePushStream(quic::StreamId streamId) {\n  auto pushIdIter = streamIdToPushId_.find(streamId);\n  if (pushIdIter != streamIdToPushId_.end()) {\n    auto pushId = pushIdIter->second;\n    pushIdToStreamId_.erase(pushId);\n    streamIdToPushId_.erase(pushIdIter);\n  }\n  return egressPushStreams_.erase(streamId);\n}\n\nuint32_t HQDownstreamSession::numberOfEgressPushStreams() const {\n  return egressPushStreams_.size();\n}\n\n// Push-stream implementation of the \"sendPushPromise\"\n// It invokes HQStreamTransport::sendPushPromise\n// Since this method does not uses codecs directly, it should not\n// set an active codec\nvoid HQDownstreamSession::HQEgressPushStream::sendPushPromise(\n    HTTPTransaction* txn,\n    folly::Optional<hq::PushId> pushId,\n    const HTTPMessage& headers,\n    HTTPHeaderSize* size,\n    bool includeEOM) {\n\n  CHECK(txn) << \"Must be invoked on a live transaction\";\n  CHECK(txn->getAssocTxnId())\n      << \"Must be invoked on a transaction with a parent\";\n  CHECK_EQ(txn_.getID(), txn->getID()) << \" Transaction stream mismatch\";\n  CHECK(pushId == folly::none) << \" The push id is stored in the egress stream,\"\n                               << \" and should not be passed by the session\";\n\n  auto parentStreamId = txn->getAssocTxnId();\n#if DEBUG\n  HQDownstreamSession& session = dynamic_cast<HQDownstreamSession&>(session_);\n#else\n  auto& session = static_cast<HQDownstreamSession&>(session_);\n#endif\n  auto parentStream = session.findNonDetachedStream(*parentStreamId);\n  if (!parentStream) {\n    session_.dropConnectionAsync(\n        quic::QuicError(quic::TransportErrorCode::STREAM_STATE_ERROR,\n                        \"Send push promise on a stream without a parent\"),\n        kErrorConnection);\n    return;\n  }\n  // Redirect to the parent transaction\n  parentStream->sendPushPromise(txn, pushId_, headers, size, includeEOM);\n}\n\nsize_t HQDownstreamSession::HQEgressPushStream::generateStreamPushId() {\n  // reserve space for max quic interger len\n  auto result = hq::writeStreamPreface(writeBuf_, pushId_);\n  CHECK(!result.hasError())\n      << __func__ << \" QUIC integer encoding error value=\" << pushId_;\n\n  return *result;\n}\n\n// Return a new push id that can be used for outgoing transactions\nhq::PushId HQDownstreamSession::createNewPushId() {\n  auto newPushId = nextAvailablePushId_++;\n  return newPushId;\n}\n\nfolly::Optional<HTTPHeaders> HQDownstreamSession::getExtraHeaders(\n    const HTTPMessage& headers, quic::StreamId streamId) {\n  if (!sock_) {\n    return folly::none;\n  }\n  if (headers.getHeaders().exists(HTTP_HEADER_PRIORITY)) {\n    return folly::none;\n  }\n  auto priority = sock_->getStreamPriority(streamId);\n  if (!priority) {\n    return folly::none;\n  }\n  HTTPHeaders extraHeaders;\n  quic::HTTPPriorityQueue::Priority httpPri(*priority);\n  extraHeaders.add(HTTP_HEADER_PRIORITY,\n                   httpPriorityToString(HTTPPriority(httpPri->urgency,\n                                                     httpPri->incremental,\n                                                     httpPri->order,\n                                                     httpPri->paused)));\n  return extraHeaders;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQDownstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HQSession.h>\n\nnamespace proxygen {\n\nclass HQDownstreamSession : public HQSession {\n public:\n  HQDownstreamSession(const std::chrono::milliseconds transactionsTimeout,\n                      HTTPSessionController* controller,\n                      const wangle::TransportInfo& tinfo,\n                      InfoCallback* sessionInfoCb)\n      : HQSession(transactionsTimeout,\n                  controller,\n                  proxygen::TransportDirection::DOWNSTREAM,\n                  tinfo,\n                  sessionInfoCb) {\n  }\n\n  void onTransportReady() noexcept override;\n\n  void onFullHandshakeDone() noexcept override;\n\n  void onAppRateLimited() noexcept override;\n\n  HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn) override {\n    return getController()->getTransactionTimeoutHandler(txn,\n                                                         getLocalAddress());\n  }\n\n  void setupOnHeadersComplete(HTTPTransaction* txn, HTTPMessage* msg) override;\n\n  void onConnectionSetupErrorHandler(quic::QuicError) noexcept override;\n\n  bool isDetachable(bool) const override;\n\n  void attachThreadLocals(folly::EventBase*,\n                          std::shared_ptr<const folly::SSLContext>,\n                          const WheelTimerInstance&,\n                          HTTPSessionStats*,\n                          FilterIteratorFn,\n                          HeaderCodec::Stats*,\n                          HTTPSessionController*) override;\n\n  void detachThreadLocals(bool) override;\n\n  bool isReplaySafe() const override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n  // Create a new pushed transaction.\n  HTTPTransaction* newPushedTransaction(\n      HTTPCodec::StreamID,           /* parentRequestStreamId */\n      HTTPTransaction::PushHandler*, /* handler */\n      ProxygenError* error = nullptr) override;\n\n  /**\n   * Returns true iff a new outgoing transaction can be made on this session\n   */\n  bool supportsMoreTransactions() const override {\n    return sock_ && sock_->getNumOpenableUnidirectionalStreams() &&\n           HTTPSessionBase::supportsMoreTransactions();\n  }\n\n  uint32_t getNumOutgoingStreams() const override {\n    // need transport API\n    return static_cast<uint32_t>(numberOfEgressPushStreams());\n  }\n\n  uint32_t getNumIncomingStreams() const override {\n    // need transport API\n    return static_cast<uint32_t>(streams_.size());\n  }\n\n  folly::Optional<HTTPHeaders> getExtraHeaders(\n      const HTTPMessage& haeders, quic::StreamId streamId) override;\n\n protected:\n  ~HQDownstreamSession() override {\n    CHECK_EQ(getNumStreams(), 0);\n  }\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4250) // inherits 'proxygen::detail::..' via dominance\n#endif\n\n  /**\n   * Server side representation of a push stream\n   * Does not support ingress\n   */\n  class HQEgressPushStream\n      : public detail::singlestream::SSEgress\n      , public HQStreamTransportBase {\n   public:\n    HQEgressPushStream(HQSession& session,\n                       quic::StreamId streamId,\n                       hq::PushId pushId,\n                       folly::Optional<HTTPCodec::StreamID> parentTxnId,\n                       uint32_t seqNo,\n                       std::unique_ptr<HTTPCodec> codec,\n                       const WheelTimerInstance& timeout,\n                       HTTPSessionStats* stats = nullptr,\n                       http2::PriorityUpdate priority = hqDefaultPriority)\n        : detail::singlestream::SSEgress(streamId),\n          HQStreamTransportBase(session,\n                                TransportDirection::DOWNSTREAM,\n                                streamId,\n                                seqNo,\n                                timeout,\n                                stats,\n                                priority,\n                                parentTxnId,\n                                hq::UnidirectionalStreamType::PUSH),\n          pushId_(pushId) {\n      // Request streams are eagerly initialized\n      initCodec(std::move(codec), __func__);\n      // DONT init ingress on egress-only stream\n    }\n\n    hq::PushId getPushId() const {\n      return pushId_;\n    }\n\n    // Unlike request streams and ingres push streams,\n    // the egress push stream does not have to flush\n    // ingress queues\n    void transactionTimeout(HTTPTransaction* txn) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      DCHECK(txn == &txn_);\n    }\n\n    void sendPushPromise(HTTPTransaction* /* txn */,\n                         folly::Optional<hq::PushId> /* pushId */,\n                         const HTTPMessage& /* headers */,\n                         HTTPHeaderSize* /* outSize */,\n                         bool /* includeEOM */) override;\n\n    /**\n     * Write the encoded push id to the egress stream.\n     */\n    size_t generateStreamPushId();\n\n    // Egress only stream should not pause ingress\n    void pauseIngress(HTTPTransaction* /* txn */) noexcept override {\n      VLOG(4) << __func__\n              << \" Ingress function called on egress-only stream, ignoring\";\n    }\n\n    // Egress only stream should not pause ingress\n    void resumeIngress(HTTPTransaction* /* txn */) noexcept override {\n      VLOG(4) << __func__\n              << \" Ingress function called on egress-only stream, ignoring\";\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n        uint64_t /* maxData */) override {\n      return folly::unit;\n    }\n\n   private:\n    hq::PushId pushId_; // The push id in context of which this stream is sent\n  }; // HQEgressPushStream\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n  std::unordered_map<quic::StreamId, HQEgressPushStream> egressPushStreams_;\n\n  // Find an egress push stream\n  HQEgressPushStream* findEgressPushStream(quic::StreamId);\n\n  uint32_t numberOfEgressPushStreams() const;\n\n  HQEgressPushStream* FOLLY_NULLABLE\n  createEgressPushStream(hq::PushId pushId,\n                         quic::StreamId streamId,\n                         quic::StreamId parentStreamId);\n\n  HQStreamTransportBase* findPushStream(quic::StreamId id) override;\n\n  // Only need to search ingress push streams, so this is a no-op\n  void findPushStreams(\n      std::unordered_set<HQStreamTransportBase*>& streams) override {\n    for (auto& pstream : egressPushStreams_) {\n      streams.insert(&pstream.second);\n    }\n  }\n\n  bool erasePushStream(quic::StreamId streamId) override;\n\n  void dispatchPushStream(quic::StreamId /* pushStreamId */,\n                          hq::PushId /* pushId */,\n                          size_t /* toConsume */) override {\n    LOG(DFATAL) << \"nope\";\n  }\n\n  // This is the current method of creating new push IDs.\n  hq::PushId createNewPushId();\n\n  bool pushAllowedByGoaway(hq::PushId pushId);\n\n  // Value of the next pushId, used for outgoing push transactions\n  hq::PushId nextAvailablePushId_{0};\n\n  // Whether or not we have already received an onTransportReady callback.\n  bool transportReadyNotified_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQSession.h>\n\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/codec/QPACKDecoderCodec.h>\n#include <proxygen/lib/http/codec/QPACKEncoderCodec.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/WebTransportFilter.h>\n#include <proxygen/lib/http/webtransport/HTTPWebTransport.h>\n\n#include <folly/CppAttributes.h>\n#include <folly/ScopeGuard.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/AsyncTransport.h>\n#include <folly/io/async/DelayedDestructionBase.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <quic/QuicConstants.h>\n#include <quic/common/BufUtil.h>\n#include <quic/folly_utils/Utils.h>\n#include <quic/logging/QLoggerConstants.h>\n#include <sstream>\n#include <wangle/acceptor/ConnectionManager.h>\n\nnamespace {\nstatic const uint16_t kMaxReadsPerLoop = 16;\n\nconstexpr std::string_view kQuicProtocolName = \"QUIC\";\nconstexpr uint64_t kMaxQuarterStreamId = (1ull << 60) - 1;\n\nusing namespace proxygen::HTTP3;\nbool noError(quic::QuicErrorCode error) {\n  return (error.type() == quic::QuicErrorCode::Type::LocalErrorCode &&\n          (*error.asLocalErrorCode() == quic::LocalErrorCode::NO_ERROR ||\n           *error.asLocalErrorCode() == quic::LocalErrorCode::IDLE_TIMEOUT)) ||\n         (error.type() == quic::QuicErrorCode::Type::ApplicationErrorCode &&\n          (proxygen::HTTP3::ErrorCode(*error.asApplicationErrorCode()) ==\n               proxygen::HTTP3::ErrorCode::HTTP_NO_ERROR ||\n           uint16_t(*error.asApplicationErrorCode()) ==\n               uint16_t(quic::GenericApplicationErrorCode::NO_ERROR))) ||\n         (error.type() == quic::QuicErrorCode::Type::TransportErrorCode &&\n          *error.asTransportErrorCode() == quic::TransportErrorCode::NO_ERROR);\n}\n\nbool isVlogLevel(const quic::TransportErrorCode& code) {\n  return code == quic::TransportErrorCode::INVALID_MIGRATION;\n}\n\nbool isVlogLevel(const quic::LocalErrorCode& code) {\n  return code == quic::LocalErrorCode::CONNECTION_ABANDONED;\n}\n\nbool isVlogLevel(const quic::QuicErrorCode& error) {\n  switch (error.type()) {\n    case quic::QuicErrorCode::Type::TransportErrorCode:\n      return isVlogLevel(*error.asTransportErrorCode());\n    case quic::QuicErrorCode::Type::LocalErrorCode:\n      return isVlogLevel(*error.asLocalErrorCode());\n    default:\n      return false;\n  }\n}\n\n// handleSessionError is mostly setup to process application error codes\n// that we want to send.  If we receive an application error code, convert to\n// HTTP_CLOSED_CRITICAL_STREAM\nquic::QuicErrorCode quicControlStreamError(quic::QuicErrorCode error) {\n  switch (error.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode:\n      return quic::QuicErrorCode(\n          proxygen::HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n    case quic::QuicErrorCode::Type::LocalErrorCode:\n    case quic::QuicErrorCode::Type::TransportErrorCode:\n      return error;\n  }\n  folly::assume_unreachable();\n}\n\nquic::HTTPPriorityQueue::Priority toQuicPriority(\n    const proxygen::HTTPPriority& pri) {\n  return pri.paused ? quic::HTTPPriorityQueue::Priority(\n                          quic::HTTPPriorityQueue::Priority::PAUSED)\n                    : quic::HTTPPriorityQueue::Priority(\n                          pri.urgency, pri.incremental, pri.orderId);\n}\n\n// Get the size of the WebTransport stream preface without actually encoding it\nfolly::Expected<size_t, quic::QuicError> getWebTransportPrefaceSize(\n    proxygen::HTTPCodec::StreamID streamId, quic::StreamId wtSessionId) {\n  // The preface contains two QuicIntegers: One which represents whether the\n  // stream is unidirectional or bidirectional, and the other of which\n  // represents the session id. We just sum up the sizes of the two if they\n  // were to be encoded as QuicIntegers.\n  uint64_t encodedStreamType =\n      quic::isUnidirectionalStream(streamId)\n          ? folly::to_underlying(\n                proxygen::hq::UnidirectionalStreamType::WEBTRANSPORT)\n          : folly::to_underlying(\n                proxygen::hq::BidirectionalStreamType::WEBTRANSPORT);\n  auto maybeStreamTypeEncodedSize = quic::getQuicIntegerSize(encodedStreamType);\n  if (maybeStreamTypeEncodedSize.hasError()) {\n    return folly::makeUnexpected(maybeStreamTypeEncodedSize.error());\n  }\n  auto maybeSessionIdEncodedSize = quic::getQuicIntegerSize(wtSessionId);\n  if (maybeSessionIdEncodedSize.hasError()) {\n    return folly::makeUnexpected(maybeSessionIdEncodedSize.error());\n  }\n  return *maybeStreamTypeEncodedSize + *maybeSessionIdEncodedSize;\n}\n\n// Returns the number of bytes written. 0 if error.\nuint32_t writeWTStreamPrefaceToSock(\n    quic::QuicSocket& sock,\n    quic::StreamId wtStreamId,\n    quic::StreamId wtSessionId,\n    proxygen::hq::WebTransportStreamType streamType) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  auto res =\n      proxygen::hq::writeWTStreamPreface(writeBuf, streamType, wtSessionId);\n  if (!res) {\n    LOG(ERROR) << \"Failed to write WT stream preface\";\n    return 0;\n  }\n  auto writeRes = sock.writeChain(wtStreamId, writeBuf.move(), false);\n  if (writeRes.hasError()) {\n    LOG(ERROR) << \"Failed to write stream preface to socket\";\n    return 0;\n  }\n  return res.value();\n}\n} // namespace\n\nusing namespace proxygen::hq;\n\nnamespace proxygen {\n\nconst std::string kH3FBCurrentDraft(\"h3-fb-05\");\nconst std::string kH3AliasV1(\"h3-alias-01\");\nconst std::string kH3AliasV2(\"h3-alias-02\");\nconst std::string kH3(\"h3\");\nconst std::string kHQ(\"hq-interop\");\n\n// TODO: remove these constants, the library no longer negotiates them\nconst std::string kH3CurrentDraft(\"h3-29\");\nconst std::string kHQCurrentDraft(\"hq-29\");\n\nconst http2::PriorityUpdate hqDefaultPriority{\n    .streamDependency = kSessionStreamId, .exclusive = false, .weight = 15};\n\nHQSession::~HQSession() {\n  VLOG(3) << *this << \" closing\";\n  runDestroyCallbacks();\n}\n\nvoid HQSession::setSessionStats(HTTPSessionStats* stats) {\n  HTTPSessionBase::setSessionStats(stats);\n  invokeOnAllStreams([&stats](HQStreamTransportBase* stream) {\n    stream->byteEventTracker_.setTTLBAStats(stats);\n  });\n}\n\nvoid HQSession::onNewBidirectionalStream(quic::StreamId id) noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": new streamID=\" << id;\n  // The transport should never call onNewBidirectionalStream before\n  // onTransportReady\n\n  // Reject all bidirectional, server-initiated streams, unless WT is supported\n  if (id == kMaxClientBidiStreamId ||\n      (isUpstream(direction_) && !supportsWebTransport())) {\n    abortStream(HTTPException::Direction::INGRESS_AND_EGRESS,\n                id,\n                HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n    return;\n  }\n  auto hqStream = findNonDetachedStream(id);\n  DCHECK(!hqStream);\n  if (supportsWebTransport()) {\n    bidirectionalReadDispatcher_.takeTemporaryOwnership(id);\n    sock_->setPeekCallback(id, &bidirectionalReadDispatcher_);\n  } else {\n    dispatchRequestStreamImpl(id);\n  }\n}\n\nvoid HQSession::dispatchRequestStream(quic::StreamId id) {\n  if (!sock_->good()) {\n    LOG(ERROR) << \"Bad socket sess=\" << *this;\n    return;\n  }\n  sock_->setPeekCallback(id, nullptr);\n  dispatchRequestStreamImpl(id);\n}\n\nvoid HQSession::dispatchRequestStreamImpl(quic::StreamId id) {\n  if (maybeRejectRequestAfterGoaway(id)) {\n    return;\n  }\n  // id < kMaxClientBidiStreamId, so id + 4 will not wrap\n  minUnseenIncomingStreamId_ = std::max(minUnseenIncomingStreamId_, id + 4);\n  auto hqStream = createStreamTransport(id);\n  DCHECK(hqStream);\n  sock_->setReadCallback(id, this);\n  if (ingressLimitExceeded()) {\n    sock_->pauseRead(id);\n  }\n  if (id == 0 && version_ == HQVersion::HQ) {\n    // generate grease frame\n    auto writeGreaseFrameResult = hq::writeGreaseFrame(hqStream->writeBuf_);\n    if (writeGreaseFrameResult.hasError()) {\n      VLOG(2) << __func__ << \" failed to create grease frame: \" << *this\n              << \". Error = \" << writeGreaseFrameResult.error();\n    }\n  }\n}\n\nvoid HQSession::onBidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (isUpstream(direction_)) {\n    VLOG(4) << \"Got new max number of concurrent streams we can initiate: \"\n            << numStreamsAvailable << \" sess=\" << *this;\n    if (infoCallback_ && supportsMoreTransactions()) {\n      infoCallback_->onSettingsOutgoingStreamsNotFull(*this);\n    }\n  }\n}\n\nvoid HQSession::onNewUnidirectionalStream(quic::StreamId id) noexcept {\n  // This is where a new unidirectional ingress stream is available\n  // Try to check whether this is a push\n  // if yes, register this as a push\n  VLOG(4) << __func__ << \" sess=\" << *this << \": new streamID=\" << id;\n  // The transport should never call onNewUnidirectionalStream\n  // before onTransportReady\n  // The new stream should not exist yet.\n  auto existingStream = findStream(id);\n  DCHECK(!existingStream) << \"duplicate \" << __func__ << \" for streamID=\" << id;\n  // This has to be a new control or push stream, but we haven't read the\n  // preface yet\n  // Assign the stream to the dispatcher\n  unidirectionalReadDispatcher_.takeTemporaryOwnership(id);\n  sock_->setPeekCallback(id, &unidirectionalReadDispatcher_);\n}\n\nvoid HQSession::onStopSending(quic::StreamId id,\n                              quic::ApplicationErrorCode error) noexcept {\n  auto errorCode = static_cast<HTTP3::ErrorCode>(error);\n  VLOG(3) << __func__ << \" sess=\" << *this << \": new streamID=\" << id\n          << \" error=\" << toString(errorCode);\n  auto stream = findStream(id);\n  if (stream) {\n    handleWriteError(stream, error);\n  } else if (supportsWebTransport() &&\n             // TODO: is it valid to STOP_SENDING WebTransport streams with\n             // error codes outside this range (eg: REJECTED)?\n             WebTransport::isEncodedApplicationErrorCode(error)) {\n    // might be a WT stream, but there's no mapping HERE to find the session ID\n    // just tell all sessions to stop sending\n    auto appErrorCode = WebTransport::toApplicationErrorCode(errorCode);\n    if (!appErrorCode) {\n      return;\n    }\n\n    for (auto& streamIt : streams_) {\n      if (!streamIt.second.detached_ &&\n          streamIt.second.txn_.isWebTransportConnectStream()) {\n        if (streamIt.second.txn_.onWebTransportStopSending(id, *appErrorCode)) {\n          break;\n        }\n      }\n    }\n  }\n}\n\nvoid HQSession::onKnob(uint64_t knobSpace,\n                       uint64_t knobId,\n                       quic::BufPtr knobBlob) {\n  VLOG(3) << __func__ << \" sess=\" << *this\n          << \" knob frame received: \" << \" KnobSpace: \" << std::hex << knobSpace\n          << \" KnobId: \" << knobId\n          << \" KnobBlob: \" << knobBlob->to<std::string>();\n}\n\nbool HQSession::maybeRejectRequestAfterGoaway(quic::StreamId id) {\n  // Cancel any stream that is out of the range allowed by GOAWAY\n  if (drainState_ != DrainState::NONE) {\n    // You can't check upstream here, because upstream GOAWAY sends PUSH IDs.\n    // It could be checked in HQUpstreamSesssion::onNewPushStream\n    if (isDownstream(direction_) && sock_->isBidirectionalStream(id) &&\n        id >= getGoawayStreamId()) {\n      abortStream(HTTPException::Direction::INGRESS_AND_EGRESS,\n                  id,\n                  HTTP3::ErrorCode::HTTP_REQUEST_REJECTED);\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool HQSession::onTransportReadyCommon() noexcept {\n  localAddr_ = sock_->getLocalAddress();\n  peerAddr_ = sock_->getPeerAddress();\n  initQuicProtocolInfo(*quicInfo_, *sock_);\n  // NOTE: this can drop the connection if the next protocol is not supported\n  if (!getAndCheckApplicationProtocol()) {\n    return false;\n  }\n  transportInfo_.acceptTime = getCurrentTime();\n  getCurrentTransportInfoWithoutUpdate(&transportInfo_);\n  transportInfo_.setupTime = millisecondsSince(transportStart_);\n  transportInfo_.connectLatency = millisecondsSince(transportStart_).count();\n  transportInfo_.protocolInfo = quicInfo_;\n  if (!createEgressControlStreams()) {\n    return false;\n  }\n  // Apply the default settings\n  SettingsList defaultSettings = {};\n  if (datagramEnabled_) {\n    // If local supports Datagram, assume the peer does too, until receiving\n    // peer settings\n    defaultSettings.emplace_back(SettingsId::_HQ_DATAGRAM, 1);\n    sock_->setDatagramCallback(this);\n  }\n  sock_->setPingCallback(this);\n  // TODO: 0-RTT settings\n  applySettings(defaultSettings);\n  // notifyPendingShutdown may be invoked before onTransportReady,\n  // so we need to address that here by kicking the GOAWAY logic if needed\n  if (drainState_ == DrainState::PENDING) {\n    sendGoaway();\n  }\n\n  informSessionControllerTransportReady();\n  return true;\n}\n\nbool HQSession::createEgressControlStreams() {\n  if (!createEgressControlStream(UnidirectionalStreamType::CONTROL) ||\n      !createEgressControlStream(UnidirectionalStreamType::QPACK_ENCODER) ||\n      !createEgressControlStream(UnidirectionalStreamType::QPACK_DECODER)) {\n    return false;\n  }\n\n  sendSettings();\n  scheduleWrite();\n  return true;\n}\n\nbool HQSession::createEgressControlStream(UnidirectionalStreamType streamType) {\n  auto id = sock_->createUnidirectionalStream();\n  if (id.hasError()) {\n    LOG(ERROR) << \"Failed to create \" << streamType\n               << \" unidirectional stream. error='\" << id.error() << \"'\";\n    onConnectionError(\n        quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                        \"Failed to create unidirectional stream\"));\n    return false;\n  }\n\n  auto matchPair = controlStreams_.emplace(\n      std::piecewise_construct,\n      std::forward_as_tuple(streamType),\n      std::forward_as_tuple(*this, id.value(), streamType));\n  CHECK(matchPair.second) << \"Emplacement failed\";\n  sock_->setControlStream(id.value());\n  matchPair.first->second.generateStreamPreface();\n  return true;\n}\n\nHQSession::HQControlStream* FOLLY_NULLABLE\nHQSession::createIngressControlStream(quic::StreamId id,\n                                      UnidirectionalStreamType streamType) {\n\n  auto ctrlStream = findControlStream(streamType);\n  // this is an error in the use of the API, egress control streams must get\n  // created at the very beginning\n  if (!ctrlStream) {\n    LOG(FATAL) << \"Cannot create ingress control stream without an egress \"\n                  \"stream streamID=\"\n               << id << \" sess=\" << *this;\n  }\n\n  if (ctrlStream->ingressCodec_) {\n    LOG(ERROR) << \"Too many \" << streamType << \" streams for sess=\" << *this;\n    dropConnectionAsync(\n        quic::QuicError(HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR,\n                        \"HTTP wrong stream count\"),\n        kErrorConnection);\n    return nullptr;\n  }\n\n  ctrlStream->setIngressStreamId(id);\n  ctrlStream->setIngressCodec(createControlCodec(streamType, *ctrlStream));\n  return ctrlStream;\n}\n\nstd::unique_ptr<hq::HQUnidirectionalCodec> HQSession::createControlCodec(\n    hq::UnidirectionalStreamType type, HQControlStream& controlStream) {\n  switch (type) {\n    case hq::UnidirectionalStreamType::CONTROL: {\n      auto codec = std::make_unique<hq::HQControlCodec>(\n          controlStream.getIngressStreamId(),\n          direction_,\n          hq::StreamDirection::INGRESS,\n          ingressSettings_,\n          type);\n      codec->setCallback(&controlStream);\n      return codec;\n    }\n    // This is quite weird for now. The stream types are defined  based on\n    // the component that initiates them, so the ingress stream from the\n    // QPACK Encoder is linked to the local QPACKDecoder, and viceversa\n    case hq::UnidirectionalStreamType::QPACK_ENCODER:\n      return std::make_unique<hq::QPACKEncoderCodec>(qpackCodec_,\n                                                     controlStream);\n    case hq::UnidirectionalStreamType::QPACK_DECODER:\n      return std::make_unique<hq::QPACKDecoderCodec>(qpackCodec_,\n                                                     controlStream);\n    default:\n      LOG(FATAL) << \"Failed to create ingress codec\";\n  }\n}\n\nbool HQSession::getAndCheckApplicationProtocol() {\n  CHECK(sock_);\n  auto alpn = sock_->getAppProtocol();\n  if (alpn) {\n    if (alpn == kH3FBCurrentDraft || alpn == kH3AliasV1 || alpn == kH3AliasV2 ||\n        alpn == kH3) {\n      version_ = HQVersion::HQ;\n    }\n  }\n  if (!alpn || !version_) {\n    // next protocol not specified or version not supported, close connection\n    // with error\n    LOG(ERROR) << \"next protocol not supported: \"\n               << (alpn ? *alpn : \"no protocol\") << \" sess=\" << *this;\n\n    onConnectionError(quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                                      \"ALPN not supported\"));\n    return false;\n  }\n  alpn_ = *alpn;\n  versionUtilsReady_.set();\n  return true;\n}\n\nvoid HQSession::onReplaySafe() noexcept {\n  // We might have got onTransportReady with 0-rtt in which case we only get the\n  // server connection id after replay safe.\n  quicInfo_->serverConnectionId = sock_->getServerConnectionId();\n  if (infoCallback_) {\n    infoCallback_->onFullHandshakeCompletion(*this);\n  }\n\n  for (auto callback : waitingForReplaySafety_) {\n    callback->onReplaySafe();\n  }\n  waitingForReplaySafety_.clear();\n}\n\nvoid HQSession::onConnectionEnd(quic::QuicError error) noexcept {\n  if (noError(error.code)) {\n    onConnectionEnd();\n  } else {\n    onConnectionError(std::move(error));\n  }\n}\n\nvoid HQSession::onConnectionEnd() noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  // The transport will not call onConnectionEnd after we call close(),\n  // so there is no need for us here to handle re-entrancy\n  // checkForShutdown->close->onConnectionEnd.\n  drainState_ = DrainState::DONE;\n  qpackCodec_.encoderStreamEnd();\n  qpackCodec_.decoderStreamEnd();\n  closeWhenIdle();\n}\n\nvoid HQSession::onConnectionSetupError(quic::QuicError code) noexcept {\n  onConnectionError(std::move(code));\n}\n\nvoid HQSession::onConnectionError(quic::QuicError code) noexcept {\n  // the connector will drop the connection in case of connect error\n  HQSession::DestructorGuard dg(this);\n  VLOG(4) << __func__ << \" sess=\" << *this\n          << \": connection error=\" << code.message;\n\n  // Map application errors here to kErrorConnectionReset: eg, the peer tore\n  // down the connection\n  auto proxygenErr = toProxygenError(code.code, /*fromPeer=*/true);\n  if (proxygenErr == kErrorNone && !streams_.empty()) {\n    // Peer closed with NO_ERROR but there are open streams\n    proxygenErr = kErrorEOF;\n  }\n  if (infoCallback_) {\n    infoCallback_->onIngressError(*this, proxygenErr);\n  }\n\n  if (code.code.type() == quic::QuicErrorCode::Type::ApplicationErrorCode &&\n      isQPACKError(\n          static_cast<HTTP3::ErrorCode>(*code.code.asApplicationErrorCode()))) {\n    LOG(ERROR) << \"Connection QPACK error err=\"\n               << static_cast<uint32_t>(*code.code.asApplicationErrorCode())\n               << \" msg=\" << code.message << \" \" << *this;\n  } else if (!noError(code.code)) {\n    std::stringstream msgStream;\n    msgStream << \"Connection closed with error err=\" << code.code\n              << \" msg=\" << code.message << \" \" << *this;\n    if (isVlogLevel(code.code)) {\n      VLOG(3) << msgStream.str();\n    } else {\n      LOG(ERROR) << msgStream.str();\n    }\n  }\n\n  sessionDropReason_ = SessionDropReason{.quicError = std::move(code),\n                                         .proxygenError = proxygenErr};\n  // force close all streams.\n  // close with error won't invoke any connection callback, reentrancy safe\n  dropConnectionSync(sessionDropReason_->quicError,\n                     sessionDropReason_->proxygenError);\n}\n\nbool HQSession::getCurrentTransportInfo(wangle::TransportInfo* tinfo) {\n  getCurrentTransportInfoWithoutUpdate(tinfo);\n  tinfo->setupTime = transportInfo_.setupTime;\n  tinfo->secure = transportInfo_.secure;\n  tinfo->appProtocol = transportInfo_.appProtocol;\n  tinfo->connectLatency = transportInfo_.connectLatency;\n  // Copy props from the transport info.\n  transportInfo_.rtt = tinfo->rtt;\n  transportInfo_.rtt_var = tinfo->rtt_var;\n  if (sock_) {\n    updateQuicProtocolInfo(*quicInfo_, *sock_);\n    tinfo->protocolInfo = quicInfo_;\n    auto flowControl = sock_->getConnectionFlowControl();\n    if (!flowControl.hasError() && flowControl->sendWindowAvailable) {\n      tinfo->recvwnd = flowControl->receiveWindowAvailable;\n    }\n  }\n  return true;\n}\n\nbool HQSession::getCurrentTransportInfoWithoutUpdate(\n    wangle::TransportInfo* tinfo) const {\n  tinfo->validTcpinfo = true;\n  tinfo->appProtocol = std::make_shared<std::string>(alpn_);\n  tinfo->securityType = kQuicProtocolName;\n  tinfo->protocolInfo = quicInfo_;\n  if (sock_) {\n    auto quicInfo = sock_->getTransportInfo();\n    tinfo->rtt = quicInfo.srtt;\n    tinfo->rtt_var = static_cast<int64_t>(quicInfo.rttvar.count());\n    tinfo->caAlgo = std::string(\n        congestionControlTypeToString(quicInfo.congestionControlType));\n    // Cwnd is logged in terms of MSS.\n    tinfo->cwnd =\n        static_cast<int64_t>(quicInfo.congestionWindow / quicInfo.mss);\n    tinfo->mss = quicInfo.mss;\n    tinfo->cwndBytes = static_cast<int64_t>(quicInfo.congestionWindow);\n    tinfo->rtx = static_cast<int64_t>(quicInfo.packetsRetransmitted);\n    tinfo->rtx_tm = static_cast<int64_t>(quicInfo.timeoutBasedLoss);\n    tinfo->rto = static_cast<int64_t>(quicInfo.pto.count());\n    tinfo->totalBytes = static_cast<int64_t>(quicInfo.bytesSent);\n    // For calculation of packet loss rate\n    tinfo->totalPackets = static_cast<int64_t>(quicInfo.totalPacketsSent);\n    tinfo->totalPacketsLost =\n        static_cast<int64_t>(quicInfo.totalPacketsMarkedLost);\n\n    // Populate key exchange algorithm if available\n    auto tlsSummary = sock_->getTLSSummary();\n    if (tlsSummary && !tlsSummary->namedGroup.empty()) {\n      tinfo->keyExchange =\n          std::make_shared<std::string>(tlsSummary->namedGroup);\n    }\n  }\n  // TODO: fill up other properties.\n  return true;\n}\n\nbool HQSession::getCurrentStreamTransportInfo(QuicStreamProtocolInfo* qspinfo,\n                                              quic::StreamId streamId) {\n  if (sock_) {\n    auto streamTransportInfo = sock_->getStreamTransportInfo(streamId);\n    if (streamTransportInfo) {\n      qspinfo->streamTransportInfo = streamTransportInfo.value();\n      return true;\n    }\n  }\n  return false;\n}\n\nsize_t HQSession::sendPriority(HTTPCodec::StreamID id, HTTPPriority priority) {\n  auto stream = findStreamImpl(id);\n  if (!stream) {\n    return 0;\n  }\n  if (enableEgressPrioritization_) {\n    stream->setPriority(*sock_, id, priority);\n  }\n  // PRIORITY_UPDATE frames are sent by clients on the control stream.\n  // Servers do not send PRIORITY_UPDATE\n  if (isDownstream(direction_)) {\n    return 0;\n  }\n  auto controlStream = findControlStream(UnidirectionalStreamType::CONTROL);\n  if (!controlStream) {\n    return 0;\n  }\n  auto g = folly::makeGuard(controlStream->setActiveCodec(__func__));\n  auto ret = controlStream->codecFilterChain->generatePriority(\n      controlStream->writeBuf_, id, priority);\n  scheduleWrite();\n  return ret;\n}\n\nsize_t HQSession::sendPushPriority(hq::PushId pushId, HTTPPriority priority) {\n  auto iter = pushIdToStreamId_.find(pushId);\n  if (iter == pushIdToStreamId_.end()) {\n    return 0;\n  }\n  auto streamId = iter->second;\n  auto stream = findPushStream(streamId);\n  if (!stream) {\n    LOG(ERROR) << \"Cannot find push streamId=\" << streamId\n               << \" with pushId=\" << pushId << \" presented in id map\";\n    return 0;\n  }\n\n  if (enableEgressPrioritization_) {\n    stream->setPriority(*sock_, streamId, priority);\n  }\n  auto controlStream = findControlStream(UnidirectionalStreamType::CONTROL);\n  if (!controlStream) {\n    return 0;\n  }\n  auto g = folly::makeGuard(controlStream->setActiveCodec(__func__));\n  auto ret = controlStream->codecFilterChain->generatePushPriority(\n      controlStream->writeBuf_, pushId, priority);\n  scheduleWrite();\n  return ret;\n}\n\nsize_t HQSession::HQStreamTransportBase::changePriority(\n    HTTPTransaction* txn, HTTPPriority httpPriority) noexcept {\n  CHECK_EQ(txn, &txn_);\n  if (queueHandle_.isStreamTransportEnqueued()) {\n    // If the stream is already enqueued, we need to update the priority\n    auto id = queueHandle_.getStreamId();\n    quic::HTTPPriorityQueue::Priority priority(httpPriority.urgency,\n                                               httpPriority.incremental);\n    session_.httpPriorityQueue_.insertOrUpdate(id, priority);\n  }\n  // For a client there is no point in changing priority if the response has\n  // been fully received\n  if (isUpstream(session_.direction_) && txn->isIngressEOMSeen()) {\n    return 0;\n  }\n  if (txn->isPushed()) {\n    auto pushId = txn->getID();\n    return session_.sendPushPriority(pushId, httpPriority);\n  }\n  return session_.sendPriority(txn->getID(), httpPriority);\n}\n\nsize_t HQSession::HQStreamTransportBase::writeBufferSize() const {\n  return writeBuf_.chainLength();\n}\n\nbool HQSession::HQStreamTransportBase::hasWriteBuffer() const {\n  return writeBuf_.chainLength() != 0;\n}\n\nbool HQSession::HQStreamTransportBase::hasPendingBody() const {\n  return hasWriteBuffer() ||\n         (queueHandle_.isTransactionEnqueued() && txn_.hasPendingBody());\n}\n\nbool HQSession::HQStreamTransportBase::hasPendingEOM() const {\n  return pendingEOM_ ||\n         (queueHandle_.isTransactionEnqueued() && txn_.isEgressEOMQueued());\n}\n\nbool HQSession::HQStreamTransportBase::hasPendingEgress() const {\n  return hasWriteBuffer() || pendingEOM_ ||\n         queueHandle_.isTransactionEnqueued();\n}\n\nbool HQSession::HQStreamTransportBase::wantsOnWriteReady(size_t canSend) const {\n  // The txn wants onWriteReady if it's enqueued AND\n  //   a) There is available flow control and it has body OR\n  //   b) All body is egressed and it has only pending EOM\n  return queueHandle_.isTransactionEnqueued() &&\n         ((canSend > writeBufferSize() && txn_.hasPendingBody()) ||\n          (!txn_.hasPendingBody() && txn_.isEgressEOMQueued()));\n}\n\nvoid HQSession::drainImpl() {\n  if (drainState_ != DrainState::NONE) {\n    // no op\n    VLOG(5) << \"Already draining sess=\" << *this;\n    return;\n  }\n  drainState_ = DrainState::PENDING;\n  sendGoaway();\n  setCloseReason(ConnectionCloseReason::SHUTDOWN);\n}\n\nvoid HQSession::sendGoaway() {\n  if (isUpstream(direction_) || drainState_ == DrainState::DONE ||\n      !versionUtilsReady_.allConditionsMet()) {\n    return;\n  }\n  // send GOAWAY frame on the control stream\n  DCHECK(drainState_ == DrainState::PENDING ||\n         drainState_ == DrainState::FIRST_GOAWAY);\n\n  auto connCtrlStream = findControlStream(UnidirectionalStreamType::CONTROL);\n  auto g = folly::makeGuard(connCtrlStream->setActiveCodec(__func__));\n  DCHECK(connCtrlStream);\n  auto goawayStreamId = getGoawayStreamId();\n  auto generated = connCtrlStream->codecFilterChain->generateGoaway(\n      connCtrlStream->writeBuf_, goawayStreamId, ErrorCode::NO_ERROR);\n  auto writeOffset =\n      sock_->getStreamWriteOffset(connCtrlStream->getEgressStreamId());\n  auto writeBufferedBytes =\n      sock_->getStreamWriteBufferedBytes(connCtrlStream->getEgressStreamId());\n  if (generated == 0 || writeOffset.hasError() ||\n      writeBufferedBytes.hasError()) {\n    // shortcut to shutdown\n    LOG(ERROR) << \" error generating GOAWAY sess=\" << *this;\n    drainState_ = DrainState::DONE;\n    return;\n  }\n  VLOG(3) << \"generated GOAWAY maxStreamID=\" << goawayStreamId\n          << \" sess=\" << *this;\n\n  auto totalStreamLength = *writeOffset + *writeBufferedBytes +\n                           connCtrlStream->writeBuf_.chainLength();\n  CHECK_GT(totalStreamLength, 0);\n  auto res =\n      sock_->registerDeliveryCallback(connCtrlStream->getEgressStreamId(),\n                                      totalStreamLength - 1,\n                                      connCtrlStream);\n  if (res.hasError()) {\n    // shortcut to shutdown\n    LOG(ERROR) << \" error generating GOAWAY sess=\" << *this;\n    drainState_ = DrainState::DONE;\n    return;\n  }\n  scheduleWrite();\n  if (drainState_ == DrainState::PENDING) {\n    drainState_ = DrainState::FIRST_GOAWAY;\n  } else {\n    DCHECK_EQ(drainState_, DrainState::FIRST_GOAWAY);\n    drainState_ = DrainState::SECOND_GOAWAY;\n  }\n}\n\nquic::StreamId HQSession::getGoawayStreamId() {\n  DCHECK(isDownstream(direction_));\n  if (drainState_ == DrainState::NONE || drainState_ == DrainState::PENDING) {\n    return HTTPCodec::MaxStreamID;\n  }\n  // If/when we send client GOAWAYs, change this to return\n  // minUnseenIncomingPushId_ in that case.\n  return minUnseenIncomingStreamId_;\n}\n\nsize_t HQSession::sendSettings() {\n  for (auto& setting : egressSettings_.getAllSettings()) {\n    auto id = httpToHqSettingsId(setting.id);\n    if (id) {\n      switch (*id) {\n        case hq::SettingId::HEADER_TABLE_SIZE:\n          qpackCodec_.setDecoderHeaderTableMaxSize(setting.value);\n          break;\n        case hq::SettingId::QPACK_BLOCKED_STREAMS:\n          qpackCodec_.setMaxBlocking(setting.value);\n          break;\n        case hq::SettingId::MAX_HEADER_LIST_SIZE:\n          // TODO: qpackCodec_.setMaxUncompressed(setting.value)\n          break;\n        case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n        case hq::SettingId::H3_DATAGRAM:\n        case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n        case hq::SettingId::H3_DATAGRAM_RFC:\n        case hq::SettingId::H3_WT_MAX_SESSIONS:\n        case hq::SettingId::WT_INITIAL_MAX_DATA:\n          break;\n        case hq::SettingId::ENABLE_WEBTRANSPORT:\n          if (setting.value) {\n            VLOG(4) << \"enable_webtransport sess=\" << *this;\n            supportsWebTransport_.set(\n                folly::to_underlying(SettingEnabled::SELF));\n          }\n          break;\n      }\n    }\n  }\n\n  auto connCtrlStream = findControlStream(UnidirectionalStreamType::CONTROL);\n  auto g = folly::makeGuard(connCtrlStream->setActiveCodec(__func__));\n  DCHECK(connCtrlStream);\n  auto generated = connCtrlStream->codecFilterChain->generateSettings(\n      connCtrlStream->writeBuf_);\n  scheduleWrite();\n  return generated;\n}\n\nvoid HQSession::notifyPendingShutdown() {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  drainImpl();\n}\n\nvoid HQSession::closeWhenIdle() {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  drainImpl();\n  cleanupPendingStreams();\n  checkForShutdown();\n}\n\nvoid HQSession::dropConnection(const std::string& errorMsg) {\n  std::string msg = errorMsg.empty() ? \"Stopping\" : errorMsg;\n  dropConnectionSync(\n      quic::QuicError(HTTP3::ErrorCode::HTTP_NO_ERROR, std::move(msg)),\n      kErrorDropped);\n}\n\nvoid HQSession::dropConnectionAsync(quic::QuicError errorCode,\n                                    ProxygenError proxygenError) {\n  if (!sessionDropReason_.has_value()) {\n    sessionDropReason_ = SessionDropReason{.quicError = std::move(errorCode),\n                                           .proxygenError = proxygenError};\n    scheduleLoopCallback(true);\n  } else {\n    VLOG(4) << \"Session already scheduled to be dropped: sess=\" << *this;\n  }\n}\n\nvoid HQSession::dropConnectionSync(quic::QuicError errorCode,\n                                   ProxygenError proxygenError) {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  HQSession::DestructorGuard dg(this);\n  // dropping_ is used to guard against dropConnection->onError->dropConnection\n  // re-entrancy. instead drainState_ = DONE means the connection can only be\n  // deleted naturally in checkForShutdown.\n  // We can get here with drainState_ == DONE, if somthing is holding a\n  // DestructorGuardon the session when it gets dropped.\n  if (dropping_) {\n    VLOG(5) << \"Already dropping sess=\" << *this;\n    return;\n  }\n  dropping_ = true;\n  onConnectionSetupErrorHandler(errorCode);\n  if (getNumStreams() > 0) {\n    // should deliver errors to all open streams, they will all detach-\n    sock_->close(std::move(errorCode));\n    sock_.reset();\n    setCloseReason(ConnectionCloseReason::SHUTDOWN);\n    // If the txn had no registered cbs, there could be streams left\n    // But we are not supposed to unregister the read callback, so this really\n    // shouldn't happen\n    invokeOnAllStreams([proxygenError](HQStreamTransportBase* stream) {\n      stream->errorOnTransaction(proxygenError, \"Dropped connection\");\n    });\n  } else {\n    // Can only be here if this wasn't fully drained. Cases like\n    //  notify + drop  (PENDING)\n    //  notify + CLOSE_SENT (in last request) + reset (no response) + drop\n    //  CLOSE_RECEIVED (in last response) + drop\n    // In any of these cases, it's ok to just close the socket.\n    // Note that the socket could already be deleted in case multiple calls\n    // happen, under a destructod guard.\n    if (sock_) {\n      // this should be closeNow()\n      sock_->close(std::move(errorCode));\n      sock_.reset();\n    }\n  }\n  drainState_ = DrainState::DONE;\n  cancelLoopCallback();\n  checkForShutdown();\n  if (VLOG_IS_ON(5)) {\n    unidirectionalReadDispatcher_.invokeOnPendingStreamIDs(\n        [&](quic::StreamId pendingStreamId) {\n          VLOG(5) << __func__ << \" pendingStreamStillOpen: \" << pendingStreamId;\n        });\n  }\n  CHECK_EQ(getNumStreams(), 0);\n}\n\nvoid HQSession::checkForShutdown() {\n  // For HQ upstream connections with a control stream, if the client wants to\n  // go away, it can just stop creating new connections and set drainining\n  // state to DONE, so that it will just shut down the socket when all the\n  // request streams are done. In the process it will still be able to receive\n  // and process GOAWAYs from the server\n  if (isUpstream(direction_) && drainState_ == DrainState::PENDING) {\n    if (VLOG_IS_ON(5)) {\n      unidirectionalReadDispatcher_.invokeOnPendingStreamIDs(\n          [&](quic::StreamId pendingStreamId) {\n            VLOG(5) << __func__\n                    << \" pendingStreamStillOpen: \" << pendingStreamId;\n          });\n    }\n    drainState_ = DrainState::DONE;\n  }\n\n  // This is somewhat inefficient, checking every stream for possible detach\n  // when we know explicitly earlier which ones are ready.  This is here to\n  // minimize issues with iterator invalidation.\n  invokeOnAllStreams(\n      [](HQStreamTransportBase* stream) { stream->checkForDetach(); });\n  if (drainState_ == DrainState::DONE && (getNumStreams() == 0) &&\n      !isLoopCallbackScheduled()) {\n    if (sock_) {\n      auto err = HTTP3::ErrorCode::HTTP_NO_ERROR;\n      sock_->close(quic::QuicError(quic::QuicErrorCode(err), toString(err)));\n      sock_.reset();\n    }\n\n    destroy();\n  }\n}\n\nvoid HQSession::errorOnTransactionId(quic::StreamId id,\n                                     const HTTPException& ex) {\n  auto stream = findStream(id);\n  if (stream) {\n    stream->errorOnTransaction(ex);\n  }\n}\n\nvoid HQSession::HQStreamTransportBase::errorOnTransaction(\n    ProxygenError err, const std::string& errorMsg) {\n  std::string extraErrorMsg;\n  if (!errorMsg.empty()) {\n    extraErrorMsg = folly::to<std::string>(\". \", errorMsg);\n  }\n  auto streamIdStr =\n      hasStreamId() ? folly::to<std::string>(getStreamId()) : \"n/a\";\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   folly::to<std::string>(getErrorString(err),\n                                          \" on transaction id: \",\n                                          streamIdStr,\n                                          extraErrorMsg));\n  ex.setProxygenError(err);\n  errorOnTransaction(ex);\n}\n\nvoid HQSession::HQStreamTransportBase::errorOnTransaction(\n    const HTTPException& ex) {\n  auto isIngressException = ex.isIngressException();\n  auto isEgressException = ex.isEgressException();\n  if (!detached_) {\n    txn_.onError(ex);\n  }\n  if (isIngressException) {\n    abortIngress();\n  }\n  if (isEgressException) {\n    abortEgress(true);\n  }\n}\n\nHQSession::HQStreamTransportBase* FOLLY_NULLABLE\nHQSession::findNonDetachedStream(quic::StreamId streamId) {\n  return findStreamImpl(streamId,\n                        true /* includeEgress */,\n                        true /* includeIngress */,\n                        false /* includeDetached */);\n}\n\nHQSession::HQStreamTransportBase* FOLLY_NULLABLE\nHQSession::findStream(quic::StreamId streamId) {\n  return findStreamImpl(streamId,\n                        true /* includeEgress */,\n                        true /* includeIngress */,\n                        true /* includeDetached */);\n}\n\nHQSession::HQStreamTransportBase* FOLLY_NULLABLE\nHQSession::findIngressStream(quic::StreamId streamId, bool includeDetached) {\n  return findStreamImpl(streamId,\n                        false /* includeEgress */,\n                        true /* includeIngress */,\n                        includeDetached);\n}\n\nHQSession::HQStreamTransportBase* FOLLY_NULLABLE\nHQSession::findEgressStream(quic::StreamId streamId, bool includeDetached) {\n  return findStreamImpl(streamId,\n                        true /* includeEgress */,\n                        false /* includeIngress */,\n                        includeDetached);\n}\n\nHQSession::HQStreamTransportBase* FOLLY_NULLABLE\nHQSession::findStreamImpl(quic::StreamId streamId,\n                          bool includeEgress,\n                          bool includeIngress,\n                          bool includeDetached) {\n  HQStreamTransportBase* pstream{nullptr};\n  auto it = streams_.find(streamId);\n  if (it != streams_.end()) {\n    pstream = &it->second;\n  }\n  if (!pstream && (includeIngress || includeEgress)) {\n    pstream = findPushStream(streamId);\n  }\n  if (!pstream) {\n    return nullptr;\n  }\n  CHECK(pstream);\n  DCHECK(pstream->isUsing(streamId));\n  if (!includeDetached) {\n    if (pstream->detached_) {\n      return nullptr;\n    }\n  }\n  return pstream;\n}\n\nHQSession::HQControlStream* FOLLY_NULLABLE\nHQSession::findControlStream(UnidirectionalStreamType streamType) {\n  auto it = controlStreams_.find(streamType);\n  if (it == controlStreams_.end()) {\n    return nullptr;\n  } else {\n    return &it->second;\n  }\n}\n\nHQSession::HQControlStream* FOLLY_NULLABLE\nHQSession::findControlStream(quic::StreamId streamId) {\n  auto it = std::find_if(\n      controlStreams_.begin(),\n      controlStreams_.end(),\n      [&](const std::pair<const UnidirectionalStreamType, HQControlStream>&\n              entry) { return entry.second.isUsing(streamId); });\n  if (it == controlStreams_.end()) {\n    return nullptr;\n  } else {\n    return &it->second;\n  }\n}\n\nbool HQSession::eraseStream(quic::StreamId streamId) {\n  // Try different possible locations and remove the\n  // stream\n  bool erased = false;\n  if (streams_.erase(streamId)) {\n    erased = true;\n  }\n\n  // TODO: only do this when stream is server-uni\n  erased |= erasePushStream(streamId);\n\n  return erased;\n}\n\nvoid HQSession::runLoopCallback() noexcept {\n  // We schedule this callback to run at the end of an event\n  // loop iteration if either of two conditions has happened:\n  //   * The session has generated some egress data (see scheduleWrite())\n  //   * Reads have become unpaused (see resumeReads())\n\n  inLoopCallback_ = true;\n  HQSession::DestructorGuard dg(this);\n  auto scopeg = folly::makeGuard([this] {\n    // This ScopeGuard needs to be under the above DestructorGuard\n    updatePendingWrites();\n    checkForShutdown();\n    inLoopCallback_ = false;\n  });\n\n  if (sessionDropReason_.has_value()) {\n    dropConnectionSync(sessionDropReason_->quicError,\n                       sessionDropReason_->proxygenError);\n    return;\n  }\n\n  readsPerLoop_ = 0;\n\n  // First process the read data\n  //   - and maybe resume reads on the stream\n  processReadData();\n\n  readDataProcessed();\n\n  // Then handle the writes\n  // Write all the control streams first\n  auto maxToSendOrig = maxToSend_;\n  maxToSend_ -= writeControlStreams(maxToSend_);\n  // Then write the request streams\n  if (!httpPriorityQueue_.empty() && maxToSend_ > 0) {\n    // TODO: we could send FIN only?\n    maxToSend_ = writeRequestStreams(maxToSend_);\n  }\n  auto sent = maxToSendOrig - maxToSend_;\n  if (sent > 0) {\n    if (infoCallback_) {\n      infoCallback_->onWrite(*this, sent);\n    }\n  }\n  // Zero out maxToSend_ here.  We won't egress anything else until the next\n  // onWriteReady call\n  maxToSend_ = 0;\n\n  if (!httpPriorityQueue_.empty()) {\n    scheduleWrite();\n  }\n\n  // Maybe schedule the next loop callback\n  VLOG(4) << \"sess=\" << *this << \" maybe schedule the next loop callback. \"\n          << \" pending writes: \" << !httpPriorityQueue_.empty()\n          << \" pending processing reads: \" << pendingProcessReadSet_.size();\n  if (!pendingProcessReadSet_.empty()) {\n    scheduleLoopCallback(false);\n  }\n  // checkForShutdown is now in ScopeGuard\n}\n\nvoid HQSession::readDataProcessed() {\n  auto ici = qpackCodec_.encodeInsertCountInc();\n  if (ici) {\n    auto QPACKDecoderStream =\n        findControlStream(UnidirectionalStreamType::QPACK_DECODER);\n    DCHECK(QPACKDecoderStream);\n    QPACKDecoderStream->writeBuf_.append(std::move(ici));\n    // don't need to explicitly schedule write because this is called in the\n    // loop before control streams are written\n  }\n}\n\nvoid HQSession::scheduleWrite() {\n  // always call for the whole connection and iterate trough all the streams\n  // in onWriteReady\n  if (scheduledWrite_) {\n    return;\n  }\n\n  scheduledWrite_ = true;\n  if (auto* evb = getEventBase()) {\n    // Pass nullptr for RequestContext. The write scheduler handles writes for\n    // all streams on this connection; its CPU cost should not be attributed to\n    // any single request.\n    evb->runInLoop(&writeScheduler_, /*thisIteration=*/true, /*rctx=*/nullptr);\n  } else {\n    sock_->notifyPendingWriteOnConnection(this);\n  }\n}\n\nvoid HQSession::WriteScheduler::runLoopCallback() noexcept {\n  if (session_.sock_) {\n    session_.sock_->notifyPendingWriteOnConnection(&session_);\n  }\n}\n\nvoid HQSession::scheduleLoopCallback(bool thisIteration) {\n  if (sock_ && sock_->getEventBase()) {\n    if (!isLoopCallbackScheduled()) {\n      // Pass nullptr for RequestContext. Session-level callbacks handle work\n      // for all streams; their cost should not be attributed to any single\n      // request.\n      getEventBase()->runInLoop(this, thisIteration, /*rctx=*/nullptr);\n    }\n  }\n}\n\nvoid HQSession::resumeReads(quic::StreamId streamId) {\n  VLOG(4) << __func__ << \" sess=\" << *this\n          << \": resuming reads id=\" << streamId;\n  sock_->resumeRead(streamId);\n  scheduleLoopCallback(true);\n  // TODO: ideally we should cancel the managed timeout when all the streams are\n  // paused and then restart it when the timeouts are unpaused\n}\n\nvoid HQSession::resumeReads() {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": resuming reads\";\n  invokeOnIngressStreams([this](HQStreamTransportBase* hqStream) {\n    if (sock_) {\n      sock_->resumeRead(hqStream->getIngressStreamId());\n    }\n  });\n}\n\nvoid HQSession::pauseReads(quic::StreamId streamId) {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": pausing reads id=\" << streamId;\n  sock_->pauseRead(streamId);\n}\n\nvoid HQSession::pauseReads() {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": pausing reads\";\n  invokeOnIngressStreams([this](HQStreamTransportBase* hqStream) {\n    sock_->pauseRead(hqStream->getIngressStreamId());\n  });\n}\n\nvoid HQSession::readAvailable(quic::StreamId id) noexcept {\n  // this is the bidirectional callback\n  VLOG(4) << __func__ << \" sess=\" << *this\n          << \": readAvailable on streamID=\" << id;\n  if (readsPerLoop_ >= kMaxReadsPerLoop) {\n    if (sessionStats_) {\n      sessionStats_->recordReadPerLoopLimitExceeded();\n    }\n    VLOG(3) << __func__ << \": skipping read for streamID=\" << id\n            << \" maximum reads per loop reached\" << \" sess=\" << *this;\n\n    return;\n  }\n  readsPerLoop_++;\n  readRequestStream(id);\n\n  scheduleLoopCallback(true);\n}\n\nvoid HQSession::readError(quic::StreamId id, quic::QuicError error) noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": readError streamID=\" << id\n          << \" error: \" << error;\n\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   folly::to<std::string>(\"Got error=\", quic::toString(error)));\n\n  switch (error.code.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode: {\n      auto errorCode =\n          static_cast<HTTP3::ErrorCode>(*error.code.asApplicationErrorCode());\n      VLOG(3) << \"readError: QUIC Application Error: \" << toString(errorCode)\n              << \" streamID=\" << id << \" sess=\" << *this;\n      ex.setHttp3ErrorCode(errorCode);\n      auto stream = findNonDetachedStream(id);\n      if (stream) {\n        stream->onResetStream(errorCode, ex);\n      } else {\n        // When a stream is erased, it's callback is cancelled, so it really\n        // should be here\n        VLOG(3)\n            << \"readError: received application error=\" << toString(errorCode)\n            << \" for detached streamID=\" << id << \" sess=\" << *this;\n      }\n      break;\n    }\n    case quic::QuicErrorCode::Type::LocalErrorCode: {\n      quic::LocalErrorCode& errorCode = *error.code.asLocalErrorCode();\n      VLOG(3) << \"readError: QUIC Local Error: \" << errorCode\n              << \" streamID=\" << id << \" sess=\" << *this;\n      if (errorCode == quic::LocalErrorCode::CONNECT_FAILED) {\n        ex.setProxygenError(kErrorConnect);\n      } else if (errorCode == quic::LocalErrorCode::IDLE_TIMEOUT) {\n        ex.setProxygenError(kErrorEOF);\n      } else {\n        ex.setProxygenError(kErrorShutdown);\n      }\n      errorOnTransactionId(id, ex);\n      break;\n    }\n    case quic::QuicErrorCode::Type::TransportErrorCode: {\n      quic::TransportErrorCode& errorCode = *error.code.asTransportErrorCode();\n      VLOG(3) << \"readError: QUIC Transport Error: \" << errorCode\n              << \" streamID=\" << id << \" sess=\" << *this;\n      ex.setProxygenError(kErrorConnectionReset);\n      // TODO: set Quic error when fbcode/quic/QuicConstants.h is OSS\n      ex.setErrno(uint32_t(errorCode));\n      errorOnTransactionId(id, ex);\n      break;\n    }\n  }\n}\n\nvoid HQSession::timeoutExpired() noexcept {\n  VLOG(3) << \"ManagedConnection timeoutExpired \" << *this;\n  if (getNumStreams() > 0) {\n    VLOG(3) << \"ignoring session timeout \" << *this;\n    resetTimeout();\n    return;\n  }\n  VLOG(3) << \"Timeout with nothing pending \" << *this;\n  setCloseReason(ConnectionCloseReason::TIMEOUT);\n  closeWhenIdle();\n}\n\nfolly::Optional<UnidirectionalStreamType> HQSession::parseUniStreamPreface(\n    uint64_t preface) {\n  hq::UnidirectionalTypeF parse = [](hq::UnidirectionalStreamType type)\n      -> folly::Optional<UnidirectionalStreamType> { return type; };\n  auto res = hq::withType(preface, parse);\n  if (res && *res == hq::UnidirectionalStreamType::WEBTRANSPORT &&\n      !supportsWebTransport()) {\n    LOG(ERROR) << \"WT stream when it is unsupported sess=\" << *this;\n    return folly::none;\n  }\n  return res;\n}\n\nvoid HQSession::readControlStream(HQControlStream* ctrlStream) {\n  DCHECK(ctrlStream);\n  auto readRes = sock_->read(ctrlStream->getIngressStreamId(), 0);\n  if (readRes.hasError()) {\n    LOG(ERROR) << \"Got synchronous read error=\" << readRes.error();\n    readError(ctrlStream->getIngressStreamId(),\n              quic::QuicError(readRes.error(), \"sync read error\"));\n    return;\n  }\n  resetTimeout();\n  quic::BufPtr data = std::move(readRes.value().first);\n  auto readSize = data ? data->computeChainDataLength() : 0;\n  VLOG(4) << \"Read \" << readSize << \" bytes from control stream\";\n  ctrlStream->readBuf_.append(std::move(data));\n  ctrlStream->readEOF_ = readRes.value().second;\n\n  if (infoCallback_) {\n    infoCallback_->onRead(\n        *this,\n        readSize,\n        static_cast<HTTPCodec::StreamID>(ctrlStream->getIngressStreamId()));\n  }\n  // GOAWAY may trigger session destroy, need a guard for that\n  DestructorGuard dg(this);\n  ctrlStream->processReadData();\n}\n\n// Dispatcher method implementation\nvoid HQSession::dispatchControlStream(quic::StreamId id,\n                                      hq::UnidirectionalStreamType type,\n                                      size_t toConsume) {\n  VLOG(4) << __func__ << \" streamID=\" << id << \" type=\" << type\n          << \" toConsume=\" << toConsume;\n\n  auto consumeRes = sock_->consume(id, toConsume);\n  CHECK(!consumeRes.hasError()) << \"Unexpected error consuming bytes\";\n\n  // Notify the read callback\n  if (infoCallback_) {\n    infoCallback_->onRead(\n        *this, toConsume, static_cast<HTTPCodec::StreamID>(id));\n  }\n\n  auto ctrlStream = createIngressControlStream(id, type);\n  if (!ctrlStream) {\n    rejectStream(id);\n    return;\n  }\n  sock_->setControlStream(id);\n\n  // After reading the preface we can switch to the regular readCallback\n  sock_->setPeekCallback(id, nullptr);\n  sock_->setReadCallback(id, &controlStreamReadCallback_);\n\n  // The transport will send notifications via the read callback\n  // for the *future* events, but not for this one.\n  // In case there is additional data on the control stream,\n  // it can be not seen until the next read notification.\n  // To mitigate that, we propagate the onReadAvailable to the control stream.\n  controlStreamReadAvailable(id);\n}\n\nvoid HQSession::rejectStream(quic::StreamId id) {\n  if (!sock_) {\n    return;\n  }\n  // Do not read data for unknown unidirectional stream types.  Send\n  // STOP_SENDING and rely on the peer sending a RESET to clear the stream in\n  // the transport, or the transport to detect if the peer has sent everything.\n  VLOG(4) << \"rejectStream id=\" << id;\n  sock_->stopSending(id, HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  if (sock_->isBidirectionalStream(id)) {\n    sock_->resetStream(id, HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  }\n  sock_->setReadCallback(id, nullptr, std::nullopt);\n  sock_->setPeekCallback(id, nullptr);\n}\n\nsize_t HQSession::cleanupPendingStreams() {\n  std::vector<quic::StreamId> streamsToCleanup;\n\n  // Cleanup pending streams in the dispatchers\n  unidirectionalReadDispatcher_.cleanup();\n  bidirectionalReadDispatcher_.cleanup();\n\n  // These streams have been dispatched as push streams but are missing their\n  // push promise\n  cleanupUnboundPushStreams(streamsToCleanup);\n\n  // Clean up the streams by detaching all callbacks\n  for (auto pendingStreamId : streamsToCleanup) {\n    clearStreamCallbacks(pendingStreamId);\n  }\n\n  return streamsToCleanup.size();\n}\n\nvoid HQSession::clearStreamCallbacks(quic::StreamId id) {\n  if (sock_) {\n    sock_->setReadCallback(id, nullptr, std::nullopt);\n    sock_->setPeekCallback(id, nullptr);\n  } else {\n    VLOG(4) << \"Attempt to clear callbacks on closed socket\";\n  }\n}\n\nvoid HQSession::controlStreamReadAvailable(quic::StreamId id) {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": streamID=\" << id;\n  auto ctrlStream = findControlStream(id);\n  if (!ctrlStream) {\n    LOG(ERROR) << \"Got readAvailable on unknown stream id=\" << id\n               << \" sess=\" << *this;\n    return;\n  }\n  readControlStream(ctrlStream);\n}\n\nvoid HQSession::controlStreamReadError(quic::StreamId id,\n                                       const quic::QuicError& error) {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": readError streamID=\" << id\n          << \" error: \" << error;\n\n  auto ctrlStream = findControlStream(id);\n\n  if (!ctrlStream) {\n    const quic::LocalErrorCode* err = error.code.asLocalErrorCode();\n    bool shouldLog = !err || (*err != quic::LocalErrorCode::NO_ERROR);\n    LOG_IF(ERROR, shouldLog)\n        << __func__ << \" received read error=\" << error\n        << \" for unknown control streamID=\" << id << \" sess=\" << *this;\n    return;\n  }\n\n  handleSessionError(ctrlStream,\n                     StreamDirection::INGRESS,\n                     quicControlStreamError(error.code),\n                     toProxygenError(error.code));\n}\n\nvoid HQSession::readRequestStream(quic::StreamId id) noexcept {\n  auto hqStream = findIngressStream(id, false /* includeDetached */);\n  if (!hqStream) {\n    // can we even get readAvailable after a stream is marked for detach ?\n    DCHECK(findStream(id));\n    return;\n  }\n  // Read as much as you possibly can!\n  auto readRes = sock_->read(id, 0);\n\n  if (readRes.hasError()) {\n    LOG(ERROR) << \"Got synchronous read error=\" << readRes.error();\n    readError(id, quic::QuicError(readRes.error(), \"sync read error\"));\n    return;\n  }\n\n  resetTimeout();\n  quic::BufPtr data = std::move(readRes.value().first);\n  auto readSize = data ? data->computeChainDataLength() : 0;\n  hqStream->readEOF_ = readRes.value().second;\n  VLOG(3) << \"Got streamID=\" << hqStream->getStreamId() << \" len=\" << readSize\n          << \" eof=\" << uint32_t(hqStream->readEOF_) << \" sess=\" << *this;\n  if (hqStream->readEOF_) {\n    auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n        std::chrono::steady_clock::now() - hqStream->createdTime);\n    if (sock_ && sock_->getState() && sock_->getState()->qLogger) {\n      sock_->getState()->qLogger->addStreamStateUpdate(\n          id, quic::kOnEOM, timeDiff);\n    }\n  } else if (readSize == 0) {\n    VLOG(3) << \"Got a blank read, ignoring sess=\" << *this;\n    return;\n  }\n  // Just buffer the data and postpone processing in the loop callback\n  hqStream->readBuf_.append(std::move(data));\n\n  if (infoCallback_) {\n    infoCallback_->onRead(*this, readSize, hqStream->getStreamId());\n  }\n\n  pendingProcessReadSet_.insert(id);\n}\n\nvoid HQSession::processReadData() {\n  for (auto it = pendingProcessReadSet_.begin();\n       it != pendingProcessReadSet_.end();) {\n    auto g = folly::makeGuard([&]() {\n      // the codec may not have processed all the data, but we won't ask again\n      // until we get more\n      // TODO: set a timeout?\n      it = pendingProcessReadSet_.erase(it);\n    });\n\n    HQStreamTransportBase* ingressStream =\n        findIngressStream(*it, true /* includeDetached */);\n\n    if (!ingressStream) {\n      // ingress on a transaction may cause other transactions to get deleted\n      continue;\n    }\n\n    // Check whether the stream has been detached\n    if (ingressStream->detached_) {\n      VLOG(4) << __func__ << \" killing pending read data for detached txn=\"\n              << ingressStream->txn_;\n      ingressStream->readBuf_.move();\n      ingressStream->readEOF_ = false;\n      continue;\n    }\n\n    // Feed it to the codec\n    auto blocked = ingressStream->processReadData();\n    if (!blocked) {\n      if (ingressStream->readEOF_) {\n        ingressStream->onIngressEOF();\n      }\n      continue;\n    }\n  }\n}\n\nvoid HQSession::headersComplete(HTTPMessage* /*msg*/) {\n  auto QPACKDecoderStream =\n      findControlStream(UnidirectionalStreamType::QPACK_DECODER);\n\n  if (QPACKDecoderStream && !QPACKDecoderStream->writeBuf_.empty()) {\n    scheduleWrite();\n  }\n}\n\nvoid HQSession::onSettings(const SettingsList& settings) {\n  applySettings(settings);\n  if (infoCallback_) {\n    infoCallback_->onSettings(*this, settings);\n  }\n  receivedSettings_ = true;\n}\n\nvoid HQSession::applySettings(const SettingsList& settings) {\n  DestructorGuard g(this);\n  VLOG(3) << \"Got SETTINGS sess=\" << *this;\n\n  uint32_t tableSize = kDefaultIngressHeaderTableSize;\n  uint32_t blocked = kDefaultIngressQpackBlockedStream;\n  bool datagram = false;\n  bool hasWT = false;\n  [[maybe_unused]] uint32_t numPlaceholders = kDefaultIngressNumPlaceHolders;\n  for (auto& setting : settings) {\n    auto id = httpToHqSettingsId(setting.id);\n    if (id) {\n      switch (*id) {\n        case hq::SettingId::HEADER_TABLE_SIZE:\n          tableSize = setting.value;\n          break;\n        case hq::SettingId::QPACK_BLOCKED_STREAMS:\n          blocked = setting.value;\n          break;\n        case hq::SettingId::MAX_HEADER_LIST_SIZE:\n          // this setting is stored in ingressSettings_ and enforced in the\n          // StreamCodec\n          break;\n        case hq::SettingId::ENABLE_CONNECT_PROTOCOL:\n          VLOG(3) << \"Peer sent ENABLE_CONNECT_PROTOCOL=\" << setting.value;\n          break;\n        case hq::SettingId::H3_DATAGRAM:\n        case hq::SettingId::H3_DATAGRAM_DRAFT_8:\n        case hq::SettingId::H3_DATAGRAM_RFC:\n          datagram = static_cast<bool>(setting.value);\n          break;\n        case hq::SettingId::ENABLE_WEBTRANSPORT:\n          hasWT = setting.value;\n          VLOG(3) << \"Peer sent ENABLE_WEBTRANSPORT: \" << uint32_t(hasWT);\n          supportsWebTransport_.set(folly::to_underlying(SettingEnabled::PEER));\n          break;\n        case hq::SettingId::H3_WT_MAX_SESSIONS:\n          hasWT = setting.value > 0;\n          VLOG(3) << \"Peer sent WEBTRANSPORT_MAX_SESSIONS: \" << uint32_t(hasWT);\n          supportsWebTransport_.set(folly::to_underlying(SettingEnabled::PEER));\n          break;\n        case hq::SettingId::WT_INITIAL_MAX_DATA:\n          VLOG(3) << \"Peer sent WT_INITIAL_MAX_DATA: \" << setting.value;\n          wtInitialSendWindow_ = setting.value;\n          break;\n      }\n    }\n  }\n  qpackCodec_.setEncoderHeaderTableSize(tableSize);\n  qpackCodec_.setMaxVulnerable(blocked);\n\n  // If H3 datagram is enabled but datagram was not negotiated at the\n  // transport, close the connection\n  if (datagram && sock_->getDatagramSizeLimit() == 0) {\n    dropConnectionAsync(\n        quic::QuicError(HTTP3::ErrorCode::HTTP_SETTINGS_ERROR,\n                        \"H3_DATAGRAM without transport support\"),\n        kErrorConnection);\n  }\n  // H3 Datagram flows are bi-directional, enable only of local and peer\n  // support it\n  datagramEnabled_ &= datagram;\n\n  VLOG(3) << \"Applied SETTINGS sess=\" << *this << \" size=\" << tableSize\n          << \" blocked=\" << blocked;\n}\n\nvoid HQSession::onGoaway(uint64_t minUnseenId,\n                         ErrorCode code,\n                         std::unique_ptr<folly::IOBuf> /* debugData */) {\n  // NOTE: This function needs to be idempotent. i.e. be a no-op if invoked\n  // twice with the same lastGoodStreamID\n  if (isDownstream(direction_)) {\n    VLOG(3) << \"Ignoring downstream GOAWAY minUnseenId=\" << minUnseenId\n            << \" sess=\" << *this;\n    return;\n  }\n  VLOG(3) << \"Got GOAWAY minUnseenId=\" << minUnseenId << \" sess=\" << *this;\n  if (minUnseenId > peerMinUnseenId_) {\n    LOG(ERROR) << \"Goaway id increased=\" << minUnseenId << \" sess=\" << *this;\n    dropConnectionAsync(\n        quic::QuicError(HTTP3::ErrorCode::HTTP_ID_ERROR, \"GOAWAY id increased\"),\n        kErrorMalformedInput);\n    return;\n  }\n  peerMinUnseenId_ = minUnseenId;\n  setCloseReason(ConnectionCloseReason::GOAWAY);\n  // drains existing streams and prevents new streams to be created\n  drainImpl();\n\n  invokeOnNonDetachedStreams([this, code](HQStreamTransportBase* stream) {\n    // Invoke onGoaway on all transactions\n    stream->txn_.onGoaway(code);\n    // Abort transactions which have been initiated locally but not created\n    // successfully at the remote end\n    if (stream->getStreamId() >= peerMinUnseenId_) {\n      stream->errorOnTransaction(kErrorStreamUnacknowledged, \"\");\n    }\n  });\n\n  if (drainState_ == DrainState::NONE || drainState_ == DrainState::PENDING) {\n    drainState_ = DrainState::FIRST_GOAWAY;\n  } else if (drainState_ == DrainState::FIRST_GOAWAY) {\n    drainState_ = DrainState::DONE;\n  }\n  checkForShutdown();\n}\n\nvoid HQSession::onPriority(quic::StreamId streamId, const HTTPPriority& pri) {\n  CHECK(isDownstream(direction_));\n  if (drainState_ != DrainState::NONE) {\n    return;\n  }\n  CHECK(sock_);\n  // This also covers push streams:\n  auto stream = findStream(streamId);\n  if (!stream || (!stream->txn_.isPushed() && !stream->hasHeaders_)) {\n    // We are supposed to drop the connection with HTTP_ID_ERROR if the streamId\n    // points to a control stream. But why should I spend CPU looking it up?\n    priorityUpdatesBuffer_.insert(streamId, pri);\n    return;\n  }\n  if (enableEgressPrioritization_) {\n    stream->setPriority(*sock_, streamId, pri);\n  }\n}\n\nvoid HQSession::onPushPriority(hq::PushId pushId, const HTTPPriority& pri) {\n  CHECK(isDownstream(direction_));\n  if (drainState_ != DrainState::NONE) {\n    return;\n  }\n  CHECK(sock_);\n\n  if (maxAllowedPushId_.hasValue() && *maxAllowedPushId_ < pushId) {\n    VLOG(4) << \"Priority update stream id=\" << pushId\n            << \" greater than max allowed push id=\" << *maxAllowedPushId_;\n    dropConnectionAsync(quic::QuicError(HTTP3::ErrorCode::HTTP_ID_ERROR,\n                                        \"PushId is beyond max allowed push id\"),\n                        kErrorMalformedInput);\n    return;\n  }\n  auto iter = pushIdToStreamId_.find(pushId);\n  if (iter == pushIdToStreamId_.end()) {\n    VLOG(4) << \"Priority update of unknown push id=\" << pushId;\n    return;\n  }\n  auto streamId = iter->second;\n  auto stream = findPushStream(streamId);\n  if (!stream) {\n    return;\n  }\n  if (enableEgressPrioritization_) {\n    stream->setPriority(*sock_, streamId, pri);\n  }\n}\n\nvoid HQSession::notifyEgressBodyBuffered(int64_t bytes) {\n  if (HTTPSessionBase::notifyEgressBodyBuffered(bytes, true) &&\n      !inLoopCallback_ && !isLoopCallbackScheduled() && sock_) {\n    getEventBase()->runInLoop(this);\n  }\n}\n\nvoid HQSession::onFlowControlUpdate(quic::StreamId id) noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": streamID=\" << id;\n\n  auto flowControl = sock_->getStreamFlowControl(id);\n  if (flowControl.hasError()) {\n    LOG(ERROR) << \"Got error=\" << flowControl.error() << \" streamID=\" << id;\n    return;\n  }\n\n  auto ctrlStream = findControlStream(id);\n  if (ctrlStream && flowControl->sendWindowAvailable > 0) {\n    if (sock_ && sock_->getState() && sock_->getState()->qLogger) {\n      sock_->getState()->qLogger->addStreamStateUpdate(\n          id,\n          quic::getFlowControlWindowAvailable(flowControl->sendWindowAvailable),\n          std::chrono::duration_cast<std::chrono::milliseconds>(\n              std::chrono::steady_clock::now() - ctrlStream->createdTime));\n    }\n    scheduleWrite();\n    return;\n  }\n\n  auto stream = findEgressStream(id);\n\n  if (!stream) {\n    LOG(ERROR) << \"Got flow control update for unknown streamID=\" << id\n               << \" sess=\" << this;\n    return;\n  }\n\n  auto& txn = stream->txn_;\n  // Check if this stream has flow control, or has only EOM pending\n  if (flowControl->sendWindowAvailable > 0 ||\n      (!stream->hasPendingBody() && stream->hasPendingEOM())) {\n    // TODO: are we intentionally piggyback the time value for flow control\n    // window here?\n    if (sock_ && sock_->getState() && sock_->getState()->qLogger) {\n      sock_->getState()->qLogger->addStreamStateUpdate(\n          id,\n          quic::getFlowControlWindowAvailable(flowControl->sendWindowAvailable),\n          std::chrono::duration_cast<std::chrono::milliseconds>(\n              std::chrono::steady_clock::now() - stream->createdTime));\n    }\n    if (stream->hasPendingEgress()) {\n      stream->signalPendingEgressStreamTransport();\n    }\n    if (!stream->detached_ && txn.isEgressPaused()) {\n      // txn might be paused\n      txn.resumeEgress();\n    }\n    scheduleWrite();\n  }\n}\n\nvoid HQSession::onConnectionWriteReady(uint64_t maxToSend) noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": maxToSend=\" << maxToSend;\n  scheduledWrite_ = false;\n  maxToSend_ = maxToSend;\n\n  scheduleLoopCallback(true);\n}\n\nvoid HQSession::onConnectionWriteError(quic::QuicError error) noexcept {\n  scheduledWrite_ = false;\n  VLOG(4) << __func__ << \" sess=\" << *this << \": writeError error=\" << error;\n  // Leave this as a no-op.  We will most likely get onConnectionError soon\n}\n\nuint64_t HQSession::writeControlStreams(uint64_t maxEgress) {\n  uint64_t maxEgressOrig = maxEgress;\n  // NOTE: process the control streams in the order they are stored\n  // this could potentially lead to stream starvation\n  for (auto& it : controlStreams_) {\n    if (it.second.writeBuf_.empty()) {\n      continue;\n    }\n    auto sent = controlStreamWriteImpl(&it.second, maxEgress);\n    DCHECK_LE(sent, maxEgress);\n    maxEgress -= sent;\n    if (maxEgress == 0) {\n      break;\n    }\n  }\n  return maxEgressOrig - maxEgress;\n}\n\nuint64_t HQSession::controlStreamWriteImpl(HQControlStream* ctrlStream,\n                                           uint64_t maxEgress) {\n  auto egressStreamId = ctrlStream->getEgressStreamId();\n  auto flowControl = sock_->getStreamFlowControl(egressStreamId);\n  if (flowControl.hasError()) {\n    LOG(ERROR)\n        << \"Got error=\" << flowControl.error() << \" streamID=\" << egressStreamId\n        << \" bufLen=\" << static_cast<int>(ctrlStream->writeBuf_.chainLength())\n        << \" readEOF=\" << ctrlStream->readEOF_;\n    handleSessionError(ctrlStream,\n                       StreamDirection::EGRESS,\n                       quicControlStreamError(flowControl.error()),\n                       toProxygenError(flowControl.error()));\n    return 0;\n  }\n\n  auto streamSendWindow = flowControl->sendWindowAvailable;\n  size_t canSend = std::min(streamSendWindow, maxEgress);\n  auto sendLen = std::min(canSend, ctrlStream->writeBuf_.chainLength());\n  auto tryWriteBuf = ctrlStream->writeBuf_.splitAtMost(canSend);\n\n  VLOG(4) << __func__ << \" before write sess=\" << *this\n          << \": streamID=\" << egressStreamId << \" maxEgress=\" << maxEgress\n          << \" sendWindow=\" << streamSendWindow << \" sendLen=\" << sendLen;\n\n  auto writeRes = sock_->writeChain(\n      egressStreamId, std::move(tryWriteBuf), false /*eof*/, nullptr);\n\n  if (writeRes.hasError()) {\n    LOG(ERROR) << \" Got error=\" << writeRes.error()\n               << \" streamID=\" << egressStreamId;\n    // Going to call this a write error no matter what the underlying reason was\n    handleSessionError(ctrlStream,\n                       StreamDirection::EGRESS,\n                       quicControlStreamError(writeRes.error()),\n                       kErrorWrite);\n    return 0;\n  }\n\n  VLOG(4)\n      << __func__ << \" after write sess=\" << *this\n      << \": streamID=\" << ctrlStream->getEgressStreamId() << \" sent=\" << sendLen\n      << \" buflen=\" << static_cast<int>(ctrlStream->writeBuf_.chainLength());\n  return sendLen;\n}\n\nvoid HQSession::handleSessionError(HQStreamBase* stream,\n                                   StreamDirection streamDir,\n                                   quic::QuicErrorCode err,\n                                   ProxygenError proxygenError) {\n  // This is most likely a control stream\n  std::string appErrorMsg;\n  HTTP3::ErrorCode appError = HTTP3::ErrorCode::HTTP_NO_ERROR;\n  auto ctrlStream = dynamic_cast<HQControlStream*>(stream);\n  if (ctrlStream) {\n    auto id = (streamDir == StreamDirection::EGRESS\n                   ? ctrlStream->getEgressStreamId()\n                   : ctrlStream->getIngressStreamId());\n    // We will miss spurious control stream RST or write errors in the logs\n    VLOG(3) << \"Got error on control stream error=\" << err << \" streamID=\" << id\n            << \" Dropping connection. sess=\" << *this;\n    appErrorMsg = \"HTTP error on control stream\";\n  } else {\n    auto requestStream = dynamic_cast<HQStreamTransport*>(stream);\n    CHECK(requestStream);\n    auto id = requestStream->getEgressStreamId();\n    LOG(ERROR) << \"Got error on request stream error=\" << err\n               << \" streamID=\" << id << \" Dropping connection. sess=\" << *this;\n    appErrorMsg = \"HTTP error on request stream\";\n    // for request streams this function must be called with an ApplicationError\n    DCHECK(err.asApplicationErrorCode());\n  }\n  // errors on a control stream means we must drop the entire connection,\n  // but there are some errors that we expect during shutdown\n  bool shouldDrop = false;\n  switch (err.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode:\n      // An ApplicationErrorCode is expected when\n      //  1. The peer resets a control stream\n      //  2. A control codec detects a connection error on a control stream\n      //  3. A stream codec detects a connection level error (eg: compression)\n      // We always want to drop the connection in these cases.\n      appError = static_cast<HTTP3::ErrorCode>(*err.asApplicationErrorCode());\n      shouldDrop = true;\n      break;\n    case quic::QuicErrorCode::Type::LocalErrorCode:\n      // a LocalErrorCode::NO_ERROR is expected whenever the socket gets\n      // closed without error\n      shouldDrop =\n          (*err.asLocalErrorCode() != quic::LocalErrorCode::NO_ERROR &&\n           *err.asLocalErrorCode() != quic::LocalErrorCode::SHUTTING_DOWN);\n      break;\n    case quic::QuicErrorCode::Type::TransportErrorCode:\n      shouldDrop = true;\n      break;\n  }\n  if (!shouldDrop) {\n    return;\n  }\n  if (ctrlStream && appError == HTTP3::ErrorCode::HTTP_NO_ERROR) {\n    // If we got a local or transport error reading or writing on a\n    // control stream, send CLOSED_CRITICAL_STREAM.\n    appError = HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM;\n  }\n  // we cannot just simply drop the connection here, since in case of a\n  // close received from the remote, we may have other readError callbacks on\n  // other streams after this one. So run in the next loop callback, in this\n  // same loop\n  dropConnectionAsync(quic::QuicError(appError, std::move(appErrorMsg)),\n                      proxygenError);\n}\n\nuint64_t HQSession::writeRequestStreams(uint64_t maxEgress) noexcept {\n  // requestStreamWriteImpl may call txn->onWriteReady\n  while (!httpPriorityQueue_.empty() && maxEgress != 0) {\n    auto id = httpPriorityQueue_.peekNextScheduledID().asStreamID();\n    auto stream = findStream(id);\n    CHECK(stream);\n    auto pri = stream->queueHandle_.getPriority();\n    auto maxStreamEgress = maxEgress;\n    if (pri->incremental && httpPriorityQueue_.getRoundRobinElements() > 1) {\n      maxStreamEgress =\n          std::min(maxEgress, static_cast<uint64_t>(1500)); // cap max egress\n      httpPriorityQueue_.consume(maxStreamEgress);\n    }\n    auto sent = requestStreamWriteImpl(stream, maxStreamEgress, 1);\n    DCHECK_LE(sent, maxEgress);\n    maxEgress -= sent;\n  }\n\n  return maxEgress;\n}\n\nvoid HQSession::handleWriteError(HQStreamTransportBase* hqStream,\n                                 quic::QuicErrorCode err) {\n  // We call this INGRESS_AND_EGRESS so it fully terminates the\n  // HTTPTransaction state machine.\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   folly::to<std::string>(\"Got error=\", quic::toString(err)));\n  switch (err.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode: {\n      // If we have an application error code, it must have\n      // come from the peer (most likely STOP_SENDING). This\n      // is logically a stream abort, not a write error\n      auto h3ErrorCode =\n          static_cast<HTTP3::ErrorCode>(*err.asApplicationErrorCode());\n      ex.setHttp3ErrorCode(h3ErrorCode);\n      ex.setCodecStatusCode(hqToHttpErrorCode(h3ErrorCode));\n      ex.setProxygenError(h3ErrorCode == HTTP3::ErrorCode::HTTP_REQUEST_REJECTED\n                              ? kErrorStreamUnacknowledged\n                              : kErrorStreamAbort);\n      break;\n    }\n    case quic::QuicErrorCode::Type::LocalErrorCode: {\n      ex.setErrno(uint32_t(*err.asLocalErrorCode()));\n      ex.setProxygenError(kErrorWrite);\n      break;\n    }\n    case quic::QuicErrorCode::Type::TransportErrorCode: {\n      CHECK(false) << \"Unexpected errorCode=\" << *err.asTransportErrorCode();\n    }\n  }\n  // Do I need a dguard here?\n  abortStream(ex.getDirection(),\n              hqStream->getStreamId(),\n              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  hqStream->errorOnTransaction(ex);\n}\n\ntemplate <typename WriteFunc, typename DataType>\nsize_t HQSession::handleWrite(WriteFunc writeFunc,\n                              HQStreamTransportBase* hqStream,\n                              DataType dataType,\n                              size_t dataChainLen,\n                              bool sendEof) {\n  quic::QuicSocket::DeliveryCallback* deliveryCallback =\n      sendEof ? this : nullptr;\n  auto writeRes = writeFunc(hqStream->getEgressStreamId(),\n                            std::forward<DataType>(dataType),\n                            sendEof,\n                            deliveryCallback);\n  if (writeRes.hasError()) {\n    LOG(ERROR) << \" Got error=\" << writeRes.error()\n               << \" streamID=\" << hqStream->getEgressStreamId();\n    handleWriteError(hqStream, writeRes.error());\n    return 0;\n  }\n\n  auto sent = dataChainLen;\n  if (sendEof) {\n    // This will hold the transaction open until onDeliveryAck or onCanceled\n    DCHECK(deliveryCallback);\n    hqStream->txn_.incrementPendingByteEvents();\n    // NOTE: This may not be necessary long term, once we properly implement\n    // detach or when we enforce flow control for headers and EOM\n    hqStream->pendingEOM_ = false;\n  }\n  hqStream->bytesWritten_ += sent;\n  // hqStream's byteEventTracker cannot be changed, so no need to pass\n  // shared_ptr or use in while loop\n  hqStream->byteEventTracker_.processByteEvents(\n      nullptr, hqStream->streamEgressCommittedByteOffset());\n  return sent;\n}\n\nuint64_t HQSession::requestStreamWriteImpl(HQStreamTransportBase* hqStream,\n                                           uint64_t maxEgress,\n                                           double ratio) {\n  CHECK(hqStream->queueHandle_.isStreamTransportEnqueued());\n  HTTPTransaction::DestructorGuard dg(&hqStream->txn_);\n\n  auto streamId = hqStream->getStreamId();\n  auto flowControl = sock_->getStreamFlowControl(streamId);\n  if (flowControl.hasError()) {\n    LOG(ERROR)\n        << \"Got error=\" << flowControl.error() << \" streamID=\" << streamId\n        << \" detached=\" << hqStream->detached_\n        << \" readBufLen=\" << static_cast<int>(hqStream->readBuf_.chainLength())\n        << \" writeBufLen=\" << static_cast<int>(hqStream->writeBufferSize())\n        << \" readEOF=\" << hqStream->readEOF_\n        << \" ingressError_=\" << static_cast<int>(hqStream->ingressError_)\n        << \" eomGate_=\" << hqStream->eomGate_;\n    handleWriteError(hqStream, flowControl.error());\n    return 0;\n  }\n\n  auto streamSendWindow = flowControl->sendWindowAvailable;\n\n  size_t canSend = std::min(streamSendWindow, maxEgress);\n\n  // we may have already buffered more than the amount the transport can take,\n  // or the txn may not have any more body bytes/EOM to add. In case, there is\n  // no need to call txn->onWriteReady.\n  if (hqStream->wantsOnWriteReady(canSend)) {\n    // Populate the write buffer by telling the transaction how much\n    // room is available for body data\n    size_t maxBodySend = canSend - hqStream->writeBufferSize();\n    VLOG(4) << __func__ << \" asking txn for more bytes sess=\" << *this\n            << \": streamID=\" << streamId << \" canSend=\" << canSend\n            << \" remain=\" << hqStream->writeBufferSize()\n            << \" pendingEOM=\" << hqStream->pendingEOM_\n            << \" maxBodySend=\" << maxBodySend << \" ratio=\" << ratio;\n    hqStream->txn_.onWriteReady(maxBodySend, ratio);\n    // onWriteReady may not be able to detach any byte from the deferred egress\n    // body bytes, in case it's getting rate limited.\n    // In that case the txn will get removed from the egress queue from\n    // onWriteReady\n    if (!hqStream->hasWriteBuffer() && !hqStream->pendingEOM_) {\n      return 0;\n    }\n  }\n\n  auto bufWritter = [&](quic::StreamId streamId,\n                        std::unique_ptr<folly::IOBuf> data,\n                        bool sendEof,\n                        quic::QuicSocket::DeliveryCallback* deliveryCallback) {\n    return sock_->writeChain(\n        streamId, std::move(data), sendEof, deliveryCallback);\n  };\n\n  size_t sent = 0;\n  auto bufSendLen = std::min(canSend, hqStream->writeBuf_.chainLength());\n  auto tryWriteBuf = hqStream->writeBuf_.splitAtMost(canSend);\n  bool sendEof = (hqStream->pendingEOM_ && !hqStream->hasPendingBody());\n  if (bufSendLen > 0 || sendEof) {\n    VLOG(4) << __func__ << \" before write sess=\" << *this\n            << \": streamID=\" << streamId << \" maxEgress=\" << maxEgress\n            << \" sendWindow=\" << streamSendWindow\n            << \" tryToSend=\" << tryWriteBuf->computeChainDataLength()\n            << \" sendEof=\" << sendEof;\n    sent = handleWrite(std::move(bufWritter),\n                       hqStream,\n                       std::move(tryWriteBuf),\n                       bufSendLen,\n                       sendEof);\n  }\n\n  VLOG(4) << __func__ << \" after write sess=\" << *this\n          << \": streamID=\" << streamId << \" sent=\" << sent\n          << \" buflen=\" << hqStream->writeBufferSize()\n          << \" hasPendingBody=\" << hqStream->txn_.hasPendingBody()\n          << \" EOM=\" << hqStream->pendingEOM_;\n  CHECK_GE(maxEgress, sent);\n\n  bool flowControlBlocked = (sent == streamSendWindow && !sendEof);\n  if (flowControlBlocked) {\n    // TODO: this one doesn't create trouble, but it's certainly not logging the\n    // extra params anyway.\n    if (sock_ && sock_->getState() && sock_->getState()->qLogger) {\n      sock_->getState()->qLogger->addStreamStateUpdate(\n          streamId,\n          quic::kStreamBlocked,\n          std::chrono::duration_cast<std::chrono::milliseconds>(\n              std::chrono::steady_clock::now() - hqStream->createdTime));\n    }\n  }\n  // sendAbort can clear the egress queue, so this stream may no longer be\n  // enqueued\n  if (hqStream->queueHandle_.isStreamTransportEnqueued() &&\n      (!hqStream->hasPendingEgress() || flowControlBlocked)) {\n    VLOG(4) << \"clearPendingEgress for \" << hqStream->txn_;\n    hqStream->clearPendingEgressStreamTransport();\n  }\n  if (flowControlBlocked && !hqStream->txn_.isEgressComplete()) {\n    VLOG(4) << __func__ << \" txn flow control blocked, txn=\" << hqStream->txn_;\n    hqStream->txn_.pauseEgress();\n  }\n  return sent;\n}\n\nvoid HQSession::onDeliveryAck(quic::StreamId id,\n                              uint64_t offset,\n                              std::chrono::microseconds rtt) {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": streamID=\" << id\n          << \" offset=\" << offset;\n  auto pEgressStream = findEgressStream(id, true /* includeDetached */);\n  DCHECK(pEgressStream);\n  if (pEgressStream) {\n    pEgressStream->txn_.onEgressLastByteAck(\n        std::chrono::duration_cast<std::chrono::milliseconds>(rtt));\n    pEgressStream->txn_.decrementPendingByteEvents();\n  } else {\n    LOG(ERROR) << \" not expecting to receive delivery ack for erased stream\";\n  }\n}\n\nvoid HQSession::onCanceled(quic::StreamId id, uint64_t /*offset*/) {\n  VLOG(3) << __func__ << \" sess=\" << *this << \": streamID=\" << id;\n  auto pEgressStream = findEgressStream(id);\n  if (pEgressStream) {\n    pEgressStream->txn_.decrementPendingByteEvents();\n  } else {\n    LOG(DFATAL) << __func__ << \" sess=\" << *this << \": streamID=\" << id\n                << \" onCanceled but txn missing, aborted without reset?\";\n  }\n}\n\nvoid HQSession::HQControlStream::onDeliveryAck(\n    quic::StreamId id, uint64_t /*offset*/, std::chrono::microseconds /*rtt*/) {\n  // We set the delivery callback for the control stream to keep track of the\n  // GOAWAY being delivered to the remote endpoint. When that happens we can\n  // send a second GOAWAY. sendGoaway is a no-op after the second time\n  VLOG(3) << \"GOAWAY received by remote endpoint on streamID=\" << id\n          << \" sess=\" << session_;\n  session_.onGoawayAck();\n}\n\nvoid HQSession::HQControlStream::onCanceled(quic::StreamId id,\n                                            uint64_t /*offset*/) {\n  // This shouldn't really happen, but in case it does let's accelerate draining\n  VLOG(3) << \"GOAWAY delivery callback canceled on streamID=\" << id\n          << \" sess=\" << session_;\n  session_.drainState_ = DrainState::DONE;\n  // if we are shutting down, do so in the loop callback\n  session_.scheduleLoopCallback(false);\n}\n\nvoid HQSession::onGoawayAck() {\n  if (drainState_ == DrainState::FIRST_GOAWAY) {\n    sendGoaway();\n  } else if (drainState_ == DrainState::SECOND_GOAWAY) {\n    drainState_ = DrainState::DONE;\n  }\n  // if we are shutting down, do so in the loop callback\n  scheduleLoopCallback(false);\n}\n\nHQSession::HQStreamTransport* FOLLY_NULLABLE\nHQSession::createStreamTransport(quic::StreamId streamId) {\n  VLOG(3) << __func__ << \" sess=\" << *this;\n\n  // Checking for egress and ingress streams as well\n  auto streamAlreadyExists = findStream(streamId);\n  if (!sock_->good() || streamAlreadyExists) {\n    VLOG(3) << __func__ << \" Refusing to add a transaction on a closing \"\n            << \" session / existing transaction\"\n            << \" sock good: \" << sock_->good()\n            << \"; streams count: \" << streams_.count(streamId) << \"; streamId \"\n            << streamId;\n    return nullptr;\n  }\n\n  // If this is the first transport, invoke the connection\n  // activation callbacks.\n  // NOTE: Should this be called when an an ingress push stream\n  // is created ?\n  if (getNumStreams() == 0) {\n    if (infoCallback_) {\n      infoCallback_->onActivateConnection(*this);\n    }\n    if (getConnectionManager()) {\n      getConnectionManager()->onActivated(*this);\n    }\n  }\n\n  // The transport should never call createStreamTransport before\n  // onTransportReady\n  std::unique_ptr<HTTPCodec> codec = createCodec(streamId);\n  auto matchPair = streams_.emplace(\n      std::piecewise_construct,\n      std::forward_as_tuple(streamId),\n      std::forward_as_tuple(\n          *this,\n          direction_,\n          streamId,\n          getNumTxnServed(),\n          std::move(codec),\n          WheelTimerInstance(transactionsTimeout_, getEventBase()),\n          sessionStats_,\n          hqDefaultPriority,\n          folly::none /* assocStreamId */));\n  incrementSeqNo();\n\n  CHECK(matchPair.second) << \"Emplacement failed, despite earlier \"\n                             \"existence check.\";\n\n  // tracks max historical streams\n  HTTPSessionBase::onNewOutgoingStream(getNumOutgoingStreams());\n  if (infoCallback_) {\n    infoCallback_->onTransactionAttached(*this);\n  }\n\n  return &matchPair.first->second;\n}\n\nstd::unique_ptr<HTTPCodec> HQSession::createCodec(quic::StreamId streamId) {\n  auto QPACKEncoderStream =\n      findControlStream(UnidirectionalStreamType::QPACK_ENCODER);\n  DCHECK(QPACKEncoderStream);\n  auto QPACKDecoderStream =\n      findControlStream(UnidirectionalStreamType::QPACK_DECODER);\n  DCHECK(QPACKDecoderStream);\n  auto codec = std::make_unique<hq::HQStreamCodec>(\n      streamId,\n      direction_,\n      qpackCodec_,\n      QPACKEncoderStream->writeBuf_,\n      QPACKDecoderStream->writeBuf_,\n      [this, id = QPACKEncoderStream->getEgressStreamId()] {\n        if (!sock_) {\n          return uint64_t(0);\n        }\n        auto res = sock_->getStreamFlowControl(id);\n        if (res.hasError()) {\n          return uint64_t(0);\n        }\n        return res->sendWindowAvailable;\n      },\n      ingressSettings_);\n  codec->setStrictValidation(strictValidation_);\n  return codec;\n}\n\nHQSession::HQStreamTransportBase::HQStreamTransportBase(\n    HQSession& session,\n    TransportDirection direction,\n    quic::StreamId streamId,\n    uint32_t seqNo,\n    const WheelTimerInstance& wheelTimer,\n    HTTPSessionStats* stats,\n    http2::PriorityUpdate priority,\n    folly::Optional<HTTPCodec::StreamID> parentTxnId,\n    folly::Optional<hq::UnidirectionalStreamType> type)\n    : HQStreamBase(session, session.codec_, type),\n      HTTP2PriorityQueueBase(kSessionStreamId),\n      txn_(direction,\n           static_cast<HTTPCodec::StreamID>(streamId),\n           seqNo,\n           *this,\n           *this,\n           wheelTimer.getWheelTimer(),\n           wheelTimer.getDefaultTimeout(),\n           stats,\n           false, // useFlowControl\n           0,     // receiveInitialWindowSize\n           0,     // sendInitialWindowSize,\n           priority,\n           parentTxnId,\n           session_.setIngressTimeoutAfterEom_),\n      byteEventTracker_(nullptr, session.getQuicSocket(), streamId) {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  byteEventTracker_.setTTLBAStats(session_.sessionStats_);\n  quicStreamProtocolInfo_ = std::make_shared<QuicStreamProtocolInfo>();\n}\n\nvoid HQSession::HQStreamTransportBase::initCodec(\n    std::unique_ptr<HTTPCodec> codec, const std::string& where) {\n  VLOG(3) << where << \" \" << __func__ << \" txn=\" << txn_;\n  CHECK(session_.sock_)\n      << \"Socket is null drainState=\" << (int)session_.drainState_\n      << \" streams=\" << session_.getNumStreams();\n  realCodec_ = std::move(codec);\n  if (session_.version_ == HQVersion::HQ) {\n    auto c = dynamic_cast<hq::HQStreamCodec*>(realCodec_.get());\n    CHECK(c) << \"HQ should use HQStream codec\";\n    c->setActivationHook([this] { return setActiveCodec(\"self\"); });\n  }\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  if (isUpstream(session_.direction_) || txn_.isPushed()) {\n    codecStreamId_ = codecFilterChain->createStream();\n  }\n  hasCodec_ = true;\n}\n\nvoid HQSession::HQStreamTransportBase::initIngress(const std::string& where) {\n  VLOG(3) << where << \" \" << __func__ << \" txn=\" << txn_;\n  CHECK(session_.sock_)\n      << \"Socket is null drainState=\" << (int)session_.drainState_\n      << \" streams=\" << session_.getNumStreams();\n\n  if (session_.receiveStreamWindowSize_.has_value()) {\n    session_.sock_->setStreamFlowControlWindow(\n        getIngressStreamId(), session_.receiveStreamWindowSize_.value());\n  }\n\n  auto g = folly::makeGuard(setActiveCodec(where));\n\n  codecFilterChain->setCallback(this);\n  eomGate_.then([this] { txn_.onIngressEOM(); });\n  hasIngress_ = true;\n}\n\nHTTPTransaction* FOLLY_NULLABLE\nHQSession::newTransaction(HTTPTransaction::Handler* handler) {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  setStreamLimitExceeded(false);\n\n  if (drainState_ == DrainState::CLOSE_SENT ||\n      drainState_ == DrainState::FIRST_GOAWAY ||\n      drainState_ == DrainState::DONE) {\n    VLOG(4) << __func__ << \" newTransaction after drain: \" << *this;\n    return nullptr;\n  }\n  if (!sock_->good()) {\n    VLOG(4) << __func__ << \" newTransaction after sock went bad: \" << this;\n    return nullptr;\n  }\n\n  // TODO stream limit handling\n  auto quicStreamId = sock_->createBidirectionalStream();\n  if (!quicStreamId) {\n    VLOG(2) << __func__ << \" failed to create new stream: \" << this;\n    setStreamLimitExceeded(true);\n    return nullptr;\n  }\n\n  auto hqStream = createStreamTransport(quicStreamId.value());\n\n  if (quicStreamId.value() == 0 && version_ == HQVersion::HQ) {\n    // generate grease frame\n    auto writeGreaseFrameResult = hq::writeGreaseFrame(hqStream->writeBuf_);\n    if (writeGreaseFrameResult.hasError()) {\n      VLOG(2) << __func__ << \" failed to create grease frame: \" << *this\n              << \". Error = \" << writeGreaseFrameResult.error();\n      return nullptr;\n    }\n  }\n\n  if (hqStream) {\n    // DestructorGuard dg(this);\n    hqStream->txn_.setHandler(CHECK_NOTNULL(handler));\n    sock_->setReadCallback(quicStreamId.value(), this);\n    if (ingressLimitExceeded()) {\n      sock_->pauseRead(quicStreamId.value());\n    }\n    return &hqStream->txn_;\n  } else {\n    VLOG(3) << __func__ << \"Failed to create new transaction on \"\n            << quicStreamId.value();\n    abortStream(HTTPException::Direction::INGRESS_AND_EGRESS,\n                quicStreamId.value(),\n                HTTP3::ErrorCode::HTTP_INTERNAL_ERROR);\n    return nullptr;\n  }\n}\n\nvoid HQSession::startNow() {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  CHECK(!started_);\n  CHECK(sock_);\n  started_ = true;\n  transportInfo_.secure = true;\n  transportInfo_.validTcpinfo = true;\n  transportStart_ = getCurrentTime();\n  // Initialize the local and peer address.\n  // These will be updated in onTransportReadyCommon() in case they change (e.g.\n  // happy eyeballs)\n  localAddr_ = sock_->getLocalAddress();\n  peerAddr_ = sock_->getPeerAddress();\n  // TODO: invoke socket.start() here\n  resetTimeout();\n}\n\nvoid HQSession::HQStreamTransportBase::checkForDetach() {\n  if (detached_ && readBuf_.empty() && !hasWriteBuffer() && !pendingEOM_ &&\n      !queueHandle_.isStreamTransportEnqueued()) {\n    session_.detachStreamTransport(this);\n  }\n}\n\nbool HQSession::HQStreamTransportBase::getCurrentTransportInfo(\n    wangle::TransportInfo* tinfo) {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(quicStreamProtocolInfo_.get());\n  bool success = session_.getCurrentTransportInfo(tinfo);\n\n  // Save connection-level protocol fields in the HQStreamTransport-level\n  // protocol info.\n  if (success) {\n    auto connectionTransportInfo =\n        dynamic_cast<QuicProtocolInfo*>(tinfo->protocolInfo.get());\n    if (connectionTransportInfo) {\n      // NOTE: slicing assignment; stream-level fields of\n      // quicStreamProtocolInfo_ are not changed while the connection\n      // level fields are overwritten.\n      *quicStreamProtocolInfo_ = *connectionTransportInfo;\n    }\n  }\n\n  // Update the HQStreamTransport-level protocol info with the\n  // stream info from the the QUIC transport\n  if (hasIngressStreamId() || hasEgressStreamId()) {\n    session_.getCurrentStreamTransportInfo(quicStreamProtocolInfo_.get(),\n                                           getStreamId());\n  }\n\n  // Set the transport info query result to the HQStreamTransport protocol\n  // info\n  tinfo->protocolInfo = quicStreamProtocolInfo_;\n\n  return success;\n}\n\nHTTPTransaction::Transport::Type\nHQSession::HQStreamTransportBase::getSessionType() const noexcept {\n  return session_.getType();\n}\n\nvoid HQSession::detachStreamTransport(HQStreamTransportBase* hqStream) {\n  // Special case - streams that dont have either ingress stream id\n  // or egress stream id dont need to be actually detached\n  // prior to being erased\n  if (hqStream->hasIngressStreamId() || hqStream->hasEgressStreamId()) {\n    auto streamId = hqStream->getStreamId();\n    VLOG(4) << __func__ << \" streamID=\" << streamId;\n    CHECK(findStream(streamId));\n    if (sock_ && hqStream->hasIngressStreamId()) {\n      clearStreamCallbacks(streamId);\n    }\n    eraseStream(streamId);\n  } else {\n    VLOG(4) << __func__ << \" streamID=NA\";\n    eraseUnboundStream(hqStream);\n  }\n\n  if (getNumStreams() == 0) {\n    if (infoCallback_) {\n      infoCallback_->onDeactivateConnection(*this);\n    }\n    if (getConnectionManager()) {\n      getConnectionManager()->onDeactivated(*this);\n    }\n    resetTimeout();\n  }\n\n  if (infoCallback_) {\n    infoCallback_->onTransactionDetached(*this);\n  }\n}\n\nvoid HQSession::HQControlStream::processReadData() {\n  bool isControl = (*type_ == hq::UnidirectionalStreamType::CONTROL);\n  std::unique_ptr<HTTPCodec> savedCodec;\n  HQUnidirectionalCodec* ingressCodecPtr = ingressCodec_.get();\n  if (isControl) {\n    // We need ingressCodec_ to be realCodec_, to correctly wire up the filter\n    // chain callbacks\n    savedCodec = std::move(realCodec_);\n    realCodec_.reset(static_cast<HQControlCodec*>(ingressCodec_.release()));\n    CHECK(!ingressCodec_);\n  }\n  auto g1 = folly::makeGuard([&] {\n    if (!isControl) {\n      return;\n    }\n    CHECK(!ingressCodec_);\n    ingressCodec_.reset(static_cast<HQControlCodec*>(realCodec_.release()));\n    realCodec_ = std::move(savedCodec);\n  });\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  if (isControl) {\n    // Now ingressCodec_ has been pushed onto the codec stack.  Restore the\n    // egress codec, in case an ingress callback triggers egress\n    CHECK(!realCodec_);\n    realCodec_ = std::move(savedCodec);\n  }\n  auto g2 = folly::makeGuard([&] {\n    if (!isControl) {\n      return;\n    }\n    savedCodec = std::move(realCodec_);\n  });\n\n  CHECK(ingressCodecPtr->isIngress());\n  auto initialLength = readBuf_.chainLength();\n  if (initialLength > 0) {\n    auto ret = ingressCodecPtr->onUnidirectionalIngress(readBuf_.move());\n    VLOG(4) << \"streamID=\" << getIngressStreamId() << \" parsed bytes=\"\n            << static_cast<int>(initialLength - readBuf_.chainLength())\n            << \" from readBuf remain=\" << readBuf_.chainLength()\n            << \" eof=\" << readEOF_;\n    readBuf_.append(std::move(ret));\n  }\n  if (readEOF_ && readBuf_.chainLength() == 0) {\n    ingressCodecPtr->onUnidirectionalIngressEOF();\n  }\n}\n\nbool HQSession::HQStreamTransportBase::processReadData() {\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  if (eomGate_.get(EOMType::CODEC) && readBuf_.chainLength() > 0) {\n    // why are we calling processReadData with no data?\n    VLOG(3) << \" Received data after HTTP EOM for txn=\" << txn_\n            << \", len=\" << static_cast<int>(readBuf_.chainLength());\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                     \"Unexpected data after request\");\n    errorOnTransaction(ex);\n    return false;\n  }\n  while (!ingressError_ && readBuf_.chainLength() > 0) {\n    // Skip any 0 length buffers before invoking the codec. Since readBuf_ is\n    // not empty, we are guaranteed to find a non-empty buffer.\n    while (readBuf_.front()->length() == 0) {\n      readBuf_.pop_front();\n    }\n    size_t bytesParsed = codecFilterChain->onIngress(*readBuf_.front());\n    VLOG(4) << \"streamID=\" << getStreamId()\n            << \" parsed bytes=\" << static_cast<int>(bytesParsed)\n            << \" from readBuf remain=\" << readBuf_.chainLength()\n            << \" eof=\" << readEOF_;\n    if (bytesParsed == 0) {\n      break;\n    }\n    readBuf_.trimStart(bytesParsed);\n  }\n  if (ingressError_) {\n    abortIngress();\n  }\n  return (readBuf_.chainLength() > 0);\n}\n\nvoid HQSession::HQStreamTransportBase::setPriority(quic::QuicSocket& sock,\n                                                   quic::StreamId streamId,\n                                                   proxygen::HTTPPriority pri) {\n  auto httpPri = toQuicPriority(pri);\n  sock.setStreamPriority(streamId, httpPri);\n  queueHandle_.setPriority(httpPri);\n  auto id = quic::PriorityQueue::Identifier::fromStreamID(streamId);\n  session_.httpPriorityQueue_.updateIfExist(id, toQuicPriority(pri));\n}\n\n// This method can be invoked via several paths:\n//  - last header in the response has arrived\n//  - triggered by QPACK\n//  - push promise has arrived\n//  - 1xx information header (e.g. 100 continue)\n// The method is safe to use in all the above scenarios\n// see specific comments in the method body\nvoid HQSession::HQStreamTransportBase::onHeadersComplete(\n    HTTPCodec::StreamID streamID, std::unique_ptr<HTTPMessage> msg) {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  msg->dumpMessage(3);\n  // TODO: the codec will set this\n  msg->setAdvancedProtocolString(session_.alpn_);\n  msg->setSecure(true);\n  CHECK(codecStreamId_);\n  CHECK_EQ(streamID, *codecStreamId_);\n\n  if (msg->isRequest() && session_.userAgent_.empty()) {\n    session_.userAgent_ = session_.codec_->getUserAgent();\n  }\n\n  hasHeaders_ = true;\n\n  //  setupOnHeadersComplete is only implemented\n  //  in the HQDownstreamSession, which does not\n  //  receive push promises. Will only be called once\n  session_.setupOnHeadersComplete(&txn_, msg.get());\n\n  // Inform observers when request headers (i.e. ingress, from downstream\n  // client) are processed.\n  if (isDownstream(session_.direction_)) {\n    if (msg.get()) {\n      const auto event =\n          HTTPSessionObserverInterface::RequestStartedEvent::Builder()\n              .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n              .setRequest(*msg)\n              .setTxnObserverAccessor(txn_.getObserverAccessor())\n              .build();\n      session_.sessionObserverContainer_.invokeInterfaceMethod<\n          HTTPSessionObserverInterface::Events::RequestStarted>(\n          [&event](auto observer, auto observed) {\n            observer->requestStarted(observed, event);\n          });\n    }\n    if (HTTPWebTransport::isConnectMessage(*msg)) {\n      VLOG(3) << \"Peer sent WT Connect\";\n      session_.supportsWebTransport_.set(\n          folly::to_underlying(SettingEnabled::PEER));\n    }\n  } else {\n    if (msg->isResponse() && msg->getStatusCode() == 200 &&\n        txn_.isWebTransportConnectStream()) {\n      if (!session_.wtFilter_) {\n        VLOG(4) << \"Received a 200 resp for a WT connect req\";\n        session_.wtFilter_ = WebTransportFilter::make(&txn_, CodecVersion::H3);\n        session_.wtFilter_->setWebTransportImpl(txn_.getWebTransport());\n      }\n    }\n  }\n\n  if (!txn_.getHandler()) {\n    txn_.sendAbort();\n    return;\n  }\n\n  // TODO: cleanup this comment\n  // for h1q-fb-v1 start draining on receipt of a Connection:: close header\n  // if we are getting a response, transportReady has been called!\n  session_.headersComplete(msg.get());\n\n  // onHeadersComplete can be triggered by data from a different stream ID\n  // - specifically, the QPACK encoder stream.  If that's true, then there may\n  // be unparsed data in HQStreamTransport.  Add this stream's id to the\n  // read set and schedule a loop callback to restart it.\n  if (session_.pendingProcessReadSet_.find(getStreamId()) ==\n          session_.pendingProcessReadSet_.end() &&\n      !readBuf_.empty()) {\n    session_.pendingProcessReadSet_.insert(getStreamId());\n    session_.scheduleLoopCallback();\n  }\n\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kOnHeaders, timeDiff);\n  }\n\n  // In case a priority update was received on the control stream before\n  // getting here that overrides the initial priority received in the headers\n  if (sock && session_.enableEgressPrioritization_) {\n    auto itr = session_.priorityUpdatesBuffer_.find(streamId);\n    if (itr != session_.priorityUpdatesBuffer_.end()) {\n      setPriority(*sock, streamId, itr->second);\n    } else {\n      const auto httpPriority = httpPriorityFromHTTPMessage(*msg);\n      if (httpPriority) {\n        setPriority(*sock, streamId, httpPriority.value());\n      }\n    }\n  }\n\n  // Tell the HTTPTransaction to start processing the message now\n  // that the full ingress headers have arrived.\n  // Depending on the push promise latch, the message is delivered to\n  // the current transaction (no push promise) or to a freshly created\n  // pushed transaction. The latter is done via \"onPushPromiseHeadersComplete\"\n  // callback\n  if (ingressPushId_) {\n    onPushPromiseHeadersComplete(*ingressPushId_, streamID, std::move(msg));\n    ingressPushId_ = folly::none;\n  } else {\n    txn_.onIngressHeadersComplete(std::move(msg));\n  }\n  if (auto httpSessionActivityTracker =\n          session_.getHTTPSessionActivityTracker()) {\n    httpSessionActivityTracker->reportActivity();\n  }\n\n  // The stream can now receive datagrams: check for any pending datagram and\n  // deliver it to the handler\n  if (session_.datagramEnabled_ && !session_.datagramsBuffer_.empty()) {\n    auto itr = session_.datagramsBuffer_.find(streamId);\n    if (itr != session_.datagramsBuffer_.end()) {\n      auto& vec = itr->second;\n      for (auto& datagram : vec) {\n        txn_.onDatagram(std::move(datagram));\n      }\n      session_.datagramsBuffer_.erase(itr);\n    }\n  }\n}\n\nvoid HQSession::HQStreamTransportBase::transactionTimeout(\n    HTTPTransaction* txn) noexcept {\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  DCHECK(txn == &txn_);\n\n  if (txn->isPushed()) {\n    if (!hasIngressStreamId()) {\n      // This transaction has not been assigned a stream id yet.\n      // Do not attempt to close the stream but do invoke\n      // the timeout on the txn\n      VLOG(3) << \"Transaction timeout on pushedTxn pushId=\" << txn->getID();\n      txn_.onIngressTimeout();\n      return;\n    }\n  }\n  // Verify that the transaction has egress or ingress stream\n  DCHECK(hasIngressStreamId() || hasEgressStreamId())\n      << \"Timeout on transaction without stream id txnID=\" << txn->getID()\n      << \" isPushed=\" << txn->isPushed();\n  // A transaction has timed out.  If the transaction does not have\n  // a Handler yet, because we haven't yet received the full request\n  // headers, we give it a DirectResponseHandler that generates an\n  // error page.\n  VLOG(3) << \"Transaction timeout for streamID=\" << getStreamId();\n\n  if (!codecStreamId_) {\n    // transactionTimeout before onMessageBegin\n    codecStreamId_ = codecFilterChain->createStream();\n  }\n\n  if (!txn_.getHandler() &&\n      txn_.getEgressState() == HTTPTransactionEgressSM::State::Start) {\n    VLOG(4) << \" Timed out receiving headers. \" << this;\n    if (session_.infoCallback_) {\n      session_.infoCallback_->onIngressError(session_, kErrorTimeout);\n    }\n\n    VLOG(4) << \" creating direct error handler. \" << this;\n    auto handler = session_.getTransactionTimeoutHandler(&txn_);\n    txn_.setHandler(handler);\n  }\n\n  // There may be unparsed ingress.  Discard it.\n  abortIngress();\n\n  // Tell the transaction about the timeout.  The transaction will\n  // communicate the timeout to the handler, and the handler will\n  // decide how to proceed.\n  if (hasIngressStreamId()) {\n    session_.abortStream(HTTPException::Direction::INGRESS,\n                         getIngressStreamId(),\n                         HTTP3::ErrorCode::HTTP_INTERNAL_ERROR);\n  }\n\n  txn_.onIngressTimeout();\n}\n\nvoid HQSession::abortStream(HTTPException::Direction dir,\n                            quic::StreamId id,\n                            HTTP3::ErrorCode err) {\n  VLOG(4) << __func__ << \"sess=\" << *this << \" id=\" << id << \" err=\" << err;\n  CHECK(sock_);\n  if (isUpstream(direction_) &&\n      err == HTTP3::ErrorCode::HTTP_REQUEST_REJECTED) {\n    // Clients MUST NOT use the H3_REQUEST_REJECTED error code, except when a\n    // server has requested closure of the request stream with this error code\n    //  -- Safest just to never use it.\n    err = HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED;\n  }\n  if (dir != HTTPException::Direction::EGRESS &&\n      (sock_->isBidirectionalStream(id) || isPeerUniStream(id))) {\n    // Any INGRESS abort generates a QPACK cancel\n    abortStream(id);\n    // This will do the stopSending for us.\n    sock_->setReadCallback(id, nullptr, err);\n    sock_->setPeekCallback(id, nullptr);\n  }\n  if (dir != HTTPException::Direction::INGRESS &&\n      (sock_->isBidirectionalStream(id) || isSelfUniStream(id))) {\n    sock_->resetStream(id, err);\n  }\n}\n\nvoid HQSession::abortStream(quic::StreamId id) {\n  if (sock_ && sock_->getState() && sock_->getState()->qLogger) {\n    sock_->getState()->qLogger->addStreamStateUpdate(\n        id, quic::kAbort, std::nullopt);\n  }\n  auto cancel = qpackCodec_.encodeCancelStream(id);\n  auto QPACKDecoderStream =\n      findControlStream(hq::UnidirectionalStreamType::QPACK_DECODER);\n  DCHECK(QPACKDecoderStream);\n  QPACKDecoderStream->writeBuf_.append(std::move(cancel));\n  scheduleWrite();\n}\n\nvoid HQSession::HQStreamTransportBase::updatePriority(\n    const HTTPMessage& headers) noexcept {\n  const auto& sock = session_.sock_;\n  auto streamId = getStreamId();\n  auto httpPriority = httpPriorityFromHTTPMessage(headers);\n  if (sock && httpPriority && session_.enableEgressPrioritization_) {\n    setPriority(*sock, streamId, httpPriority.value());\n  }\n}\n\nstd::pair<uint64_t, uint64_t>\nHQSession::HQStreamTransportBase::generateHeadersCommon(\n    quic::StreamId streamId,\n    const HTTPMessage& headers,\n    bool includeEOM,\n    HTTPHeaderSize* size) noexcept {\n  const uint64_t oldOffset = streamWriteByteOffset();\n  CHECK(codecStreamId_)\n      << \"Trying to send headers on an half open stream isRequest=\"\n      << headers.isRequest()\n      << \"; assocTxnId=\" << txn_.getAssocTxnId().value_or(-1)\n      << \"; txn=\" << txn_.getID();\n  codecFilterChain->generateHeader(writeBuf_,\n                                   *codecStreamId_,\n                                   headers,\n                                   includeEOM,\n                                   size,\n                                   session_.getExtraHeaders(headers, streamId));\n\n  const uint64_t newOffset = streamWriteByteOffset();\n  if (size) {\n    VLOG(4) << \"sending headers, size=\" << size->compressed\n            << \", compressedBlock=\" << size->compressedBlock\n            << \", uncompressedSize=\" << size->uncompressed << \" txn=\" << txn_;\n  }\n\n  // only do it for downstream now to bypass handling upstream reuse cases\n  if (/* session_.direction_ == TransportDirection::DOWNSTREAM && */\n      headers.isResponse() && newOffset > oldOffset &&\n      // catch 100-ish response?\n      !txn_.testAndSetFirstHeaderByteSent()) {\n    byteEventTracker_.addFirstHeaderByteEvent(newOffset, &txn_);\n  }\n\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n\n  auto sock = session_.sock_;\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kHeaders, timeDiff);\n  }\n\n  if ((newOffset > 0) &&\n      (headers.isRequest() ||\n       (headers.isResponse() && headers.getStatusCode() >= 200))) {\n    // Track last egress header and notify the handler when the receiver acks\n    // the headers.\n    // We need to track last byte sent offset, so substract one here.\n    armEgressHeadersAckCb(newOffset - 1);\n  }\n\n  return std::make_pair(oldOffset, newOffset);\n}\n\nvoid HQSession::HQStreamTransportBase::sendHeaders(HTTPTransaction* txn,\n                                                   const HTTPMessage& headers,\n                                                   HTTPHeaderSize* size,\n                                                   bool includeEOM) noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n\n  updatePriority(headers);\n  // If this is a push promise, send it on the parent stream.\n  // The accounting will happen in the nested context\n  if (headers.isRequest() && txn->getAssocTxnId()) {\n    sendPushPromise(txn, folly::none, headers, size, includeEOM);\n    return;\n  }\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  auto streamId = getStreamId();\n  auto headerGenOffsets =\n      generateHeadersCommon(streamId, headers, includeEOM, size);\n  auto oldOffset = headerGenOffsets.first;\n  auto newOffset = headerGenOffsets.second;\n\n  if (includeEOM) {\n    CHECK_GE(newOffset, oldOffset);\n    session_.handleLastByteEvents(&byteEventTracker_,\n                                  &txn_,\n                                  newOffset - oldOffset,\n                                  streamWriteByteOffset(),\n                                  true);\n  }\n\n  pendingEOM_ = includeEOM;\n  // Headers can be empty for a 0.9 response\n  if (writeBuf_.chainLength() > 0 || pendingEOM_) {\n    notifyPendingEgress();\n  }\n\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  if (includeEOM) {\n    if (sock && sock->getState() && sock->getState()->qLogger) {\n      sock->getState()->qLogger->addStreamStateUpdate(\n          streamId, quic::kEOM, timeDiff);\n    }\n  }\n\n  // If this is a client sending request headers to upstream\n  // invoke requestStarted event for attached observers.\n  if (isUpstream(session_.direction_)) {\n    const auto event =\n        HTTPSessionObserverInterface::RequestStartedEvent::Builder()\n            .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n            .setRequest(headers)\n            .setTxnObserverAccessor(txn->getObserverAccessor())\n            .build();\n    session_.sessionObserverContainer_.invokeInterfaceMethod<\n        HTTPSessionObserverInterface::Events::RequestStarted>(\n        [&event](auto observer, auto observed) {\n          observer->requestStarted(observed, event);\n        });\n  }\n}\n\nsize_t HQSession::HQStreamTransportBase::sendEOM(\n    HTTPTransaction* txn, const HTTPHeaders* trailers) noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n\n  size_t encodedSize = 0;\n\n  CHECK(codecStreamId_);\n  if (trailers) {\n    encodedSize = codecFilterChain->generateTrailers(\n        writeBuf_, *codecStreamId_, *trailers);\n  }\n\n  encodedSize += codecFilterChain->generateEOM(writeBuf_, *codecStreamId_);\n\n  // This will suppress the call to onEgressBodyLastByte in\n  // handleLastByteEvents, since we're going to add a last byte event anyways.\n  // This safely keeps the txn open until we egress the FIN to the transport.\n  // At that point, the deliveryCallback should also be registered.\n  // Note: even if the byteEventTracker_ is already at streamWriteByteOffset(),\n  // it is still invoked with the same offset after egressing the FIN.\n  bool pretendPiggybacked = (encodedSize == 0);\n  session_.handleLastByteEvents(&byteEventTracker_,\n                                &txn_,\n                                encodedSize,\n                                streamWriteByteOffset(),\n                                pretendPiggybacked);\n  if (pretendPiggybacked) {\n    byteEventTracker_.addLastByteEvent(txn, streamWriteByteOffset());\n  }\n  // For H1 without chunked transfer-encoding, generateEOM is a no-op\n  // We need to make sure writeChain(eom=true) gets called\n  pendingEOM_ = true;\n  notifyPendingEgress();\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kEOM, timeDiff);\n  }\n  return encodedSize;\n}\n\nsize_t HQSession::HQStreamTransportBase::sendAbort(\n    HTTPTransaction* txn, ErrorCode errorCode) noexcept {\n  return sendAbortImpl(toHTTP3ErrorCode(errorCode),\n                       folly::to<std::string>(\"Application aborts, errorCode=\",\n                                              getErrorCodeString(errorCode),\n                                              \" txnID=\",\n                                              txn->getID(),\n                                              \" isPushed=\",\n                                              txn->isPushed()));\n}\n\nsize_t HQSession::HQStreamTransportBase::sendAbortImpl(HTTP3::ErrorCode code,\n                                                       std::string errorMsg) {\n  VLOG(4) << __func__ << \" txn=\" << txn_ << \" msg=\" << errorMsg;\n\n  // If the HQ stream is bound to a transport stream, abort it.\n  if (hasStreamId()) {\n    session_.abortStream(getStreamDirection(), getStreamId(), code);\n  }\n  // Like abortIngress, but not safe to clear readBuf_, because we may be\n  // parsing it.  If we are, then abortIngress will be called at the end of\n  // processReadData.  If not, then the STOP_SENDING we emit will trigger a peer\n  // RST_STREAM (eventually), which will clear the readBuf_.\n  ingressError_ = true;\n  codecFilterChain->setParserPaused(true);\n\n  if (hasEgressStreamId()) {\n    abortEgress(true);\n  }\n  // NOTE: What about the streams that only `hasIngressStreamId()` ?\n  // At the time being, the only case of ingress-only transport stream\n  // is an ingress push stream. The essential procedure for aborting\n  // the ingress push streams is the same as above - abort the stream via\n  // sending the \"stop sending\" frame on the control stream.\n  //\n  // Additional logic that is specific to the ingress push stream, such as\n  // sending `CANCEL_PUSH` message, does not belong to `HQSession` level, but\n  // to `HQUpstreamSesssion::HQIngressPushStream::sendAbort`, which\n  // invokes this method.\n\n  // We generated 0 application bytes so return 0?\n  return 0;\n}\n\nvoid HQSession::HQStreamTransportBase::abortIngress() {\n  VLOG(4) << \"Aborting ingress for \" << txn_;\n  ingressError_ = true;\n  readBuf_.move();\n  codecFilterChain->setParserPaused(true);\n}\n\nvoid HQSession::HQStreamTransportBase::abortEgress(bool checkForDetach) {\n  VLOG(4) << \"Aborting egress for \" << txn_;\n  byteEventTracker_.drainByteEvents();\n  writeBuf_.move();\n  pendingEOM_ = false;\n  if (queueHandle_.isStreamTransportEnqueued()) {\n    VLOG(4) << \"clearPendingEgress for \" << txn_;\n    clearPendingEgressStreamTransport();\n  }\n  if (checkForDetach) {\n    HTTPTransaction::DestructorGuard dg(&txn_);\n  }\n}\n\nvoid HQSession::HQControlStream::onError(HTTPCodec::StreamID streamID,\n                                         const HTTPException& error,\n                                         bool /* newTxn */) {\n  // All the errors on the control stream are to be considered session errors\n  // anyway, so just use the ingress stream id\n  if (streamID == kSessionStreamId) {\n    streamID = getIngressStreamId();\n  }\n  if (session_.infoCallback_) {\n    session_.infoCallback_->onIngressError(\n        session_,\n        isQPACKError(error.getHttp3ErrorCode()) ? kErrorBadDecompress\n                                                : kErrorMessage);\n  }\n  LOG(ERROR) << \"Got error on control stream error=\"\n             << toString(error.getHttp3ErrorCode()) << \" streamID=\" << streamID\n             << \" sess=\" << session_;\n  session_.handleSessionError(\n      CHECK_NOTNULL(session_.findControlStream(streamID)),\n      StreamDirection::INGRESS,\n      error.getHttp3ErrorCode(),\n      kErrorConnection);\n}\n\nvoid HQSession::HQStreamTransportBase::onError(HTTPCodec::StreamID streamID,\n                                               const HTTPException& error,\n                                               bool /* newTxn */) {\n  VLOG(4) << __func__ << \" (from Codec) txn=\" << txn_ << \" err=\" << error;\n  // Codec must either call onMessageComplete or onError, but not both\n  // I think.  The exception might be if stream has more than one HTTP\n  // message on it.\n  CHECK(!eomGate_.get(EOMType::CODEC));\n  ingressError_ = true;\n\n  if (streamID == kSessionStreamId) {\n    if (session_.infoCallback_) {\n      session_.infoCallback_->onIngressError(\n          session_,\n          isQPACKError(error.getHttp3ErrorCode()) ? kErrorBadDecompress\n                                                  : kErrorMessage);\n    }\n    LOG(ERROR) << \"Got session error error=\"\n               << toString(error.getHttp3ErrorCode()) << \" msg=\" << error\n               << \" streamID=\" << getIngressStreamId() << \" sess=\" << session_;\n    session_.handleSessionError(this,\n                                StreamDirection::INGRESS,\n                                error.getHttp3ErrorCode(),\n                                kErrorConnection);\n    return;\n  }\n\n  if (!codecStreamId_ && error.hasHttpStatusCode() && streamID != 0) {\n    // onError before onMessageBegin\n    codecStreamId_ = streamID;\n  }\n\n  if (!txn_.getHandler() &&\n      txn_.getEgressState() == HTTPTransactionEgressSM::State::Start) {\n    if (error.getDirection() != HTTPException::Direction::INGRESS) {\n      // Direct error handler only process INGRESS\n      LOG(DFATAL) << \"Codec gave egress error with no handler sess=\"\n                  << session_;\n    }\n    session_.abortStream(HTTPException::Direction::INGRESS,\n                         getIngressStreamId(),\n                         error.getHttp3ErrorCode());\n    session_.handleErrorDirectly(&txn_, error);\n    return;\n  }\n\n  txn_.onError(error);\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kOnError, timeDiff);\n  }\n}\n\nvoid HQSession::HQStreamTransportBase::onResetStream(HTTP3::ErrorCode errorCode,\n                                                     HTTPException ex) {\n  // kErrorStreamAbort prevents HTTPTransaction from calling sendAbort in reply.\n  // We use this code and manually call sendAbort here for appropriate cases\n  HTTP3::ErrorCode replyError = HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED;\n  if (isDownstream(session_.direction_) && !txn_.isIngressStarted()) {\n    // Downstream ingress closed with no ingress yet, we can send REJECTED\n    // It's actually ok if we've received headers but not made any\n    // calls to the handler, but there's no API for that.\n    replyError = HTTP3::ErrorCode::HTTP_REQUEST_REJECTED;\n  }\n\n  if (errorCode == HTTP3::ErrorCode::HTTP_REQUEST_REJECTED) {\n    VLOG_IF(2, isDownstream(session_.direction_))\n        << \"RST_STREAM/REJECTED should not be sent by clients txn=\" << txn_;\n    // kErrorStreamUnacknowledged signals that this is safe to retry\n    ex.setProxygenError(kErrorStreamUnacknowledged);\n  } else {\n    ex.setProxygenError(kErrorStreamAbort);\n  }\n  if (errorCode == HTTP3::ErrorCode::GIVEUP_ZERO_RTT) {\n    // This error code comes from application who wants to error out all\n    // transactions over hqsession because QUIC lost race with TCP. Passing this\n    // error back to transactions through onError so that they can be retried.\n    ex.setProxygenError(kErrorEarlyDataFailed);\n  }\n  ex.setHttp3ErrorCode(errorCode);\n  auto msg = ex.what();\n  errorOnTransaction(ex);\n  sendAbortImpl(replyError, msg);\n}\n\nvoid HQSession::HQStreamTransportBase::notifyPendingEgress() noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  signalPendingEgressStreamTransport();\n  session_.scheduleWrite();\n}\n\nvoid HQSession::HQStreamTransportBase::coalesceEOM(size_t encodedBodyBytes) {\n  session_.handleLastByteEvents(&byteEventTracker_,\n                                &txn_,\n                                encodedBodyBytes,\n                                streamWriteByteOffset(),\n                                true);\n  VLOG(3) << \"sending EOM in body for streamID=\" << getStreamId()\n          << \" txn=\" << txn_;\n  pendingEOM_ = true;\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kEOM, timeDiff);\n  }\n}\n\nsize_t HQSession::HQStreamTransportBase::sendBody(\n    HTTPTransaction* txn,\n    std::unique_ptr<folly::IOBuf> body,\n    bool includeEOM,\n    bool /* trackLastByteFlushed */) noexcept {\n  auto bodyLength = body->computeChainDataLength();\n  VLOG(4) << __func__ << \" len=\" << bodyLength << \" eof=\" << includeEOM\n          << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n  uint64_t offset = streamWriteByteOffset();\n\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  CHECK(codecStreamId_);\n  size_t encodedSize = codecFilterChain->generateBody(writeBuf_,\n                                                      *codecStreamId_,\n                                                      std::move(body),\n                                                      HTTPCodec::NoPadding,\n                                                      includeEOM);\n  bodyBytesEgressed_ += bodyLength;\n  if (auto httpSessionActivityTracker =\n          session_.getHTTPSessionActivityTracker()) {\n    httpSessionActivityTracker->addTrackedEgressByteEvent(\n        offset, encodedSize, &byteEventTracker_, txn);\n  }\n  if (encodedSize > 0 && !txn->testAndSetFirstByteSent()) {\n    byteEventTracker_.addFirstBodyByteEvent(offset, txn);\n  }\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kBody, timeDiff);\n  }\n  if (includeEOM) {\n    coalesceEOM(encodedSize);\n  }\n  notifyPendingEgress();\n  return encodedSize;\n}\n\nsize_t HQSession::HQStreamTransportBase::sendChunkHeader(\n    HTTPTransaction* txn, size_t length) noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  CHECK(codecStreamId_);\n  size_t encodedSize =\n      codecFilterChain->generateChunkHeader(writeBuf_, *codecStreamId_, length);\n  notifyPendingEgress();\n  return encodedSize;\n}\n\nsize_t HQSession::HQStreamTransportBase::sendChunkTerminator(\n    HTTPTransaction* txn) noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  CHECK(codecStreamId_);\n  size_t encodedSize =\n      codecFilterChain->generateChunkTerminator(writeBuf_, *codecStreamId_);\n  notifyPendingEgress();\n  return encodedSize;\n}\n\nsize_t HQSession::HQStreamTransportBase::sendPadding(\n    HTTPTransaction* txn, uint16_t padding) noexcept {\n  VLOG(4) << __func__ << \" txn=\" << txn_ << \" padding=\" << padding;\n  CHECK(hasEgressStreamId()) << __func__ << \" invoked on stream without egress\";\n  DCHECK(txn == &txn_);\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  CHECK(codecStreamId_);\n  size_t encodedSize =\n      codecFilterChain->generatePadding(writeBuf_, *codecStreamId_, padding);\n  if (encodedSize > 0) {\n    notifyPendingEgress();\n  }\n  return encodedSize;\n}\n\nvoid HQSession::HQStreamTransportBase::onMessageBegin(\n    HTTPCodec::StreamID streamID, HTTPMessage* /* msg */) {\n  VLOG(4) << __func__ << \" txn=\" << txn_ << \" streamID=\" << streamID\n          << \" ingressPushId=\" << ingressPushId_.value_or(-1);\n\n  if (ingressPushId_) {\n    constexpr auto error =\n        \"Received onMessageBegin in the middle of push promise\";\n    LOG(ERROR) << error << \" streamID=\" << streamID << \" session=\" << session_;\n    // TODO: Audit this error code\n    session_.dropConnectionAsync(\n        quic::QuicError(HTTP3::ErrorCode::HTTP_FRAME_ERROR, error),\n        kErrorDropped);\n    return;\n  }\n\n  if (session_.infoCallback_) {\n    session_.infoCallback_->onRequestBegin(session_);\n  }\n\n  // NOTE: for H2 this is where we create a new stream and transaction.\n  // for HQ there is nothing to do here, except caching the codec streamID\n  codecStreamId_ = streamID;\n\n  // Reset the pending pushID, since the subsequent invocation of\n  // `onHeadersComplete` won't be associated with a push\n  ingressPushId_ = folly::none;\n}\n\nvoid HQSession::HQStreamTransportBase::trackEgressBodyOffset(\n    uint64_t bodyOffset, proxygen::ByteEvent::EventFlags eventFlags) {\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n  // This calculation is only accurate for a body offset in the most recently\n  // generated DATA frame.  Any earlier offsets will skew large by factoring in\n  // more recent frame headers or non-DATA frames.  Any later offsets will skew\n  // small because the number of non-body bytes is not known.\n  uint64_t streamOffset =\n      (streamWriteByteOffset() - bodyBytesEgressed_) + bodyOffset;\n  // We need to track last byte sent offset, so substract one here.\n  auto offset = streamOffset - 1;\n  armEgressBodyCallbacks(bodyOffset, offset, eventFlags);\n  VLOG(4) << __func__ << \": armed body byte event cb for offset=\" << offset\n          << \"; body offset=\" << bodyOffset\n          << \"; flags=\" << uint32_t(eventFlags) << \"; txn=\" << txn_;\n}\n\nvoid HQSession::HQStreamTransportBase::armStreamByteEventCb(\n    uint64_t streamOffset, quic::ByteEvent::Type type) {\n  auto res = session_.sock_->registerByteEventCallback(\n      type, getEgressStreamId(), streamOffset, this);\n  if (res.hasError()) {\n    auto errStr = folly::to<std::string>(\n        \"failed to register byte event callback: \", toString(res.error()));\n    LOG(ERROR) << errStr;\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, errStr);\n    ex.setProxygenError(kErrorNetwork);\n    errorOnTransaction(ex);\n    return;\n  }\n  numActiveDeliveryCallbacks_++;\n\n  // Increment pending byte events so the transaction won't detach until we get\n  // and ack/cancel from transport here.\n  txn_.incrementPendingByteEvents();\n\n  VLOG(4) << __func__ << \": registered type=\" << uint32_t(type)\n          << \" callback for offset=\" << streamOffset << \"; sess=\" << session_\n          << \"; txn=\" << txn_;\n}\n\nvoid HQSession::HQStreamTransportBase::armEgressHeadersAckCb(\n    uint64_t streamOffset) {\n  VLOG(4) << __func__ << \": registering headers delivery callback for offset=\"\n          << streamOffset << \"; sess=\" << session_ << \"; txn=\" << txn_;\n  armStreamByteEventCb(streamOffset, quic::ByteEvent::Type::ACK);\n  egressHeadersAckOffset_ = streamOffset;\n}\n\nvoid HQSession::HQStreamTransportBase::armEgressBodyCallbacks(\n    uint64_t bodyOffset,\n    uint64_t streamOffset,\n    proxygen::ByteEvent::EventFlags eventFlags) {\n  VLOG(4) << __func__ << \": registering body byte event callback for offset=\"\n          << streamOffset << \"; flags=\" << uint32_t(eventFlags)\n          << \"; sess=\" << session_ << \"; txn=\" << txn_;\n  if (eventFlags & proxygen::ByteEvent::EventFlags::TX) {\n    armStreamByteEventCb(streamOffset, quic::ByteEvent::Type::TX);\n    auto res = egressBodyByteEventOffsets_.try_emplace(\n        streamOffset, BodyByteOffset(bodyOffset, 1));\n    if (!res.second) {\n      res.first->second.callbacks++;\n    }\n  }\n  if (eventFlags & proxygen::ByteEvent::EventFlags::ACK) {\n    armStreamByteEventCb(streamOffset, quic::ByteEvent::Type::ACK);\n    auto res = egressBodyByteEventOffsets_.try_emplace(\n        streamOffset, BodyByteOffset(bodyOffset, 1));\n    if (!res.second) {\n      res.first->second.callbacks++;\n    }\n  }\n}\n\nvoid HQSession::HQStreamTransportBase::handleHeadersAcked(\n    uint64_t streamOffset) {\n  CHECK(egressHeadersAckOffset_);\n  if (*egressHeadersAckOffset_ != streamOffset) {\n    LOG(ERROR)\n        << \": bad offset for egress headers ack: e=\" << *egressHeadersAckOffset_\n        << \", r=\" << streamOffset << \"; sess=\" << session_ << \"; txn=\" << txn_;\n    return;\n  }\n\n  VLOG(4) << __func__ << \": got delivery ack for egress headers, stream offset=\"\n          << streamOffset << \"; sess=\" << session_ << \"; txn=\" << txn_;\n\n  resetEgressHeadersAckOffset();\n  txn_.onLastEgressHeaderByteAcked();\n}\n\nvoid HQSession::HQStreamTransportBase::handleBodyEvent(\n    uint64_t streamOffset, quic::ByteEvent::Type type) {\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n\n  auto bodyOffset = resetEgressBodyEventOffset(streamOffset);\n  if (!bodyOffset) {\n    LOG(DFATAL) << __func__ << \": received an unexpected byte event at offset \"\n                << streamOffset << \"; sess=\" << session_ << \"; txn=\" << txn_;\n    return;\n  }\n  VLOG(4) << __func__ << \": got byte event type=\" << uint32_t(type)\n          << \" for egress body, bodyOffset=\" << *bodyOffset\n          << \"; sess=\" << session_ << \"; txn=\" << txn_;\n\n  if (type == quic::ByteEvent::Type::ACK) {\n    txn_.onEgressBodyBytesAcked(*bodyOffset);\n  } else if (type == quic::ByteEvent::Type::TX) {\n    txn_.onEgressBodyBytesTx(*bodyOffset);\n  }\n}\n\nvoid HQSession::HQStreamTransportBase::handleBodyEventCancelled(\n    uint64_t streamOffset, quic::ByteEvent::Type) {\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n\n  auto bodyOffset = resetEgressBodyEventOffset(streamOffset);\n  if (!bodyOffset) {\n    LOG(DFATAL) << __func__\n                << \": received an unexpected onCanceled event at offset \"\n                << streamOffset;\n    return;\n  }\n  // Use the same callback whether the body did not TX or did not ACK.  Caller\n  // may received this more than once if they asked to track both.\n  txn_.onEgressBodyDeliveryCanceled(*bodyOffset);\n}\n\nvoid HQSession::HQStreamTransportBase::onByteEvent(quic::ByteEvent byteEvent) {\n  VLOG(4) << __func__ << \": got byte event type=\" << uint32_t(byteEvent.type)\n          << \" for offset=\" << byteEvent.offset << \"; sess=\" << session_\n          << \"; txn=\" << txn_;\n\n  DCHECK_GT(numActiveDeliveryCallbacks_, 0);\n  numActiveDeliveryCallbacks_--;\n  txn_.decrementPendingByteEvents();\n\n  // For a given type (ACK|TX), onByteEvent calls will be called from QuicSocket\n  // with monotonically increasing offsets.\n  if (egressHeadersAckOffset_) {\n    if (byteEvent.type == quic::ByteEvent::Type::ACK) {\n      handleHeadersAcked(byteEvent.offset);\n      return;\n    }\n    // else we don't track header byte tx (yet), but it could be a body TX\n  }\n\n  handleBodyEvent(byteEvent.offset, byteEvent.type);\n}\n\nvoid HQSession::HQStreamTransportBase::onByteEventCanceled(\n    quic::ByteEventCancellation cancellation) {\n  VLOG(3) << __func__ << \": data cancelled on stream=\" << cancellation.id\n          << \", type=\" << uint32_t(cancellation.type)\n          << \", offset=\" << cancellation.offset << \"; sess=\" << session_\n          << \"; txn=\" << txn_;\n  DCHECK_GT(numActiveDeliveryCallbacks_, 0);\n  numActiveDeliveryCallbacks_--;\n  txn_.decrementPendingByteEvents();\n\n  // Are byte events of a given type always cancelled in offset order?\n\n  if (egressHeadersAckOffset_) {\n    if (cancellation.type == quic::ByteEvent::Type::ACK) {\n      resetEgressHeadersAckOffset();\n      return;\n    }\n    // else we don't track header byte tx (yet), but it could be a body TX\n  }\n\n  handleBodyEventCancelled(cancellation.offset, cancellation.type);\n}\n\n// Methods specific to StreamTransport subclasses\nvoid HQSession::HQStreamTransportBase::onPushMessageBegin(\n    HTTPCodec::StreamID pushID,\n    HTTPCodec::StreamID assocStreamID,\n    HTTPMessage* /* msg */) {\n  VLOG(4) << __func__ << \" txn=\" << txn_ << \" streamID=\" << getIngressStreamId()\n          << \" assocStreamID=\" << assocStreamID\n          << \" ingressPushId=\" << ingressPushId_.value_or(-1);\n\n  if (ingressPushId_) {\n    constexpr auto error =\n        \"Received onPushMessageBegin in the middle of push promise\";\n    LOG(ERROR) << error;\n    // TODO: Audit this error code\n    session_.dropConnectionAsync(\n        quic::QuicError(HTTP3::ErrorCode::HTTP_FRAME_ERROR, error),\n        kErrorDropped);\n    return;\n  }\n\n  if (session_.infoCallback_) {\n    session_.infoCallback_->onRequestBegin(session_);\n  }\n\n  // Notify the testing callbacks\n  if (session_.serverPushLifecycleCb_) {\n    session_.serverPushLifecycleCb_->onPushPromiseBegin(\n        assocStreamID, static_cast<hq::PushId>(pushID));\n  }\n\n  ingressPushId_ = static_cast<hq::PushId>(pushID);\n}\n\nHQSession::HQStreamTransportBase* HQSession::findWTSessionOrAbort(\n    quic::StreamId sessionID, quic::StreamId streamID) {\n  CHECK(supportsWebTransport());\n  auto wtSession = findNonDetachedStream(sessionID);\n  if (!wtSession || !wtSession->txn_.isWebTransportConnectStream()) {\n    LOG(ERROR) << \"Missing or invalid webtransport connect stream id=\"\n               << sessionID << \" for peer initiated stream id=\" << streamID;\n    // need to error stopSending/reset this stream\n    abortStream(HTTPException::Direction::INGRESS_AND_EGRESS,\n                streamID,\n                HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR);\n    return nullptr;\n  }\n  return wtSession;\n}\n\n// Peer initiated Uni WT streams\nvoid HQSession::dispatchUniWTStream(quic::StreamId streamID,\n                                    quic::StreamId sessionID,\n                                    size_t toConsume) {\n  sock_->setPeekCallback(streamID, nullptr);\n  auto consumeRes = sock_->consume(streamID, toConsume);\n  CHECK(!consumeRes.hasError()) << \"Unexpected error consuming bytes\";\n  VLOG(6) << __func__ << \" sess=\" << *this << \" id=\" << streamID\n          << \" wt-sess-id=\" << sessionID;\n\n  // Notify the read callback\n  if (infoCallback_) {\n    infoCallback_->onRead(\n        *this, toConsume, static_cast<HTTPCodec::StreamID>(streamID));\n  }\n\n  auto parent = findWTSessionOrAbort(sessionID, streamID);\n  if (!parent) {\n    return;\n  }\n  auto handle = parent->txn_.onWebTransportUniStream(streamID);\n  sock_->setReadCallback(streamID, handle);\n}\n\n// Peer initiated Bidi WT streams\nvoid HQSession::dispatchBidiWTStream(HTTPCodec::StreamID streamID,\n                                     HTTPCodec::StreamID sessionID,\n                                     size_t toConsume) {\n  sock_->setPeekCallback(streamID, nullptr);\n  auto consumeRes = sock_->consume(streamID, toConsume);\n  CHECK(!consumeRes.hasError()) << \"Unexpected error consuming bytes\";\n  VLOG(6) << __func__ << \" sess=\" << *this << \" id=\" << streamID\n          << \" wt-sess-id=\" << sessionID;\n\n  auto parent = findWTSessionOrAbort(sessionID, streamID);\n  if (!parent) {\n    return;\n  }\n\n  auto handle = parent->txn_.onWebTransportBidiStream(streamID);\n  sock_->setReadCallback(streamID, handle.readHandle);\n}\n\n// Methods specific to StreamTransport subclasses\n//\n//\n\n// Request-stream implementation of the \"sendPushPromise\"\n// HQEgressPushStream::sendPushPromise calls this\nvoid HQSession::HQStreamTransport::sendPushPromise(\n    HTTPTransaction* txn,\n    folly::Optional<hq::PushId> pushId,\n    const HTTPMessage& headers,\n    HTTPHeaderSize* size,\n    bool includeEOM) {\n  CHECK(txn);\n\n  CHECK(pushId.has_value()) << \" Request stream impl expects pushID to be set\";\n  const uint64_t oldOffset = streamWriteByteOffset();\n  auto g = folly::makeGuard(setActiveCodec(__func__));\n\n  codecFilterChain->generatePushPromise(\n      writeBuf_, *codecStreamId_, headers, pushId.value(), includeEOM, size);\n\n  const uint64_t newOffset = streamWriteByteOffset();\n  if (size) {\n    VLOG(4) << \"sending push promise, size=\" << size->compressed\n            << \", uncompressedSize=\" << size->uncompressed << \" txn=\" << txn_;\n  }\n\n  if (includeEOM) {\n    CHECK_GE(newOffset, oldOffset);\n    session_.handleLastByteEvents(&byteEventTracker_,\n                                  &txn_,\n                                  newOffset - oldOffset,\n                                  streamWriteByteOffset(),\n                                  true);\n  }\n\n  pendingEOM_ = includeEOM;\n  notifyPendingEgress();\n\n  auto timeDiff = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::steady_clock::now() - createdTime);\n  auto sock = session_.sock_;\n  auto streamId = getStreamId();\n  if (sock && sock->getState() && sock->getState()->qLogger) {\n    sock->getState()->qLogger->addStreamStateUpdate(\n        streamId, quic::kPushPromise, timeDiff);\n  }\n  if (includeEOM) {\n    if (sock && sock->getState() && sock->getState()->qLogger) {\n      sock->getState()->qLogger->addStreamStateUpdate(\n          streamId, quic::kEOM, timeDiff);\n    }\n  }\n}\n\nHTTPTransaction* FOLLY_NULLABLE\nHQSession::HQStreamTransport::newPushedTransaction(\n    HTTPCodec::StreamID parentRequestStreamId,\n    HTTPTransaction::PushHandler* handler,\n    ProxygenError* error) noexcept {\n\n  CHECK_EQ(parentRequestStreamId, txn_.getID());\n\n  return session_.newPushedTransaction(\n      parentRequestStreamId, // stream id of the egress push stream\n      handler,\n      error);\n}\n\nvoid HQSession::HQStreamTransport::onPushPromiseHeadersComplete(\n    hq::PushId pushID,\n    HTTPCodec::StreamID assocStreamID,\n    std::unique_ptr<HTTPMessage> msg) {\n  VLOG(4) << \"processing new Push Promise msg=\" << msg.get()\n          << \" streamID=\" << assocStreamID << \" maybePushID=\" << pushID\n          << \", txn= \" << txn_;\n\n  // Notify the testing callbacks\n  if (session_.serverPushLifecycleCb_) {\n    session_.serverPushLifecycleCb_->onPushPromise(\n        assocStreamID, pushID, msg.get());\n  }\n\n  // Create ingress push stream (will also create the transaction)\n  // If a corresponding nascent push stream is ready, it will be\n  // bound to the newly created stream.\n  // virtual function call into UpstreamSession.  This will crash if it happens\n  // downstream.\n  auto pushStream = session_.createIngressPushStream(assocStreamID, pushID);\n  CHECK(pushStream);\n\n  // Notify the *parent* transaction that the *pushed* transaction has been\n  // successfully created.\n  txn_.onPushedTransaction(&pushStream->txn_);\n\n  // Notify the *pushed* transaction on the push promise headers\n  // This has to be called AFTER \"onPushedTransaction\" upcall\n  pushStream->txn_.onIngressHeadersComplete(std::move(msg));\n}\n\nvoid HQSession::onDatagramsAvailable() noexcept {\n  auto result = sock_->readDatagramBufs();\n  if (result.hasError()) {\n    LOG(ERROR) << \"Got error while reading datagrams: error=\"\n               << toString(result.error());\n    dropConnectionAsync(quic::QuicError(HTTP3::ErrorCode::HTTP_INTERNAL_ERROR,\n                                        \"H3_DATAGRAM: internal error \"),\n                        kErrorConnection);\n    return;\n  }\n  VLOG(4) << \"Received \" << result.value().size()\n          << \" datagrams. sess=\" << *this;\n  for (auto& datagram : result.value()) {\n    folly::io::Cursor cursor(datagram.get());\n    auto quarterStreamId = quic::follyutils::decodeQuicInteger(cursor);\n    if (!quarterStreamId || quarterStreamId->first > kMaxQuarterStreamId) {\n      dropConnectionAsync(\n          quic::QuicError(HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR,\n                          \"H3_DATAGRAM: error decoding stream-id\"),\n          kErrorConnection);\n      break;\n    }\n    auto streamId = quarterStreamId->first * 4;\n    auto stream = findNonDetachedStream(streamId);\n    quic::Optional<std::pair<uint64_t, size_t>> ctxId;\n    if (!stream || (!stream->txn_.isWebTransportConnectStream() &&\n                    !stream->txn_.isConnectUdpStream())) {\n      // TODO: draft 8 and rfc don't include context ID\n      ctxId = quic::follyutils::decodeQuicInteger(cursor);\n      if (!ctxId) {\n        dropConnectionAsync(\n            quic::QuicError(HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR,\n                            \"H3_DATAGRAM: error decoding context-id\"),\n            kErrorConnection);\n      }\n    }\n    quic::BufQueue datagramQ;\n    datagramQ.append(std::move(datagram));\n    datagramQ.trimStart(quarterStreamId->second + (ctxId ? ctxId->second : 0));\n\n    if (!stream || !stream->hasHeaders_) {\n      VLOG(4) << \"Stream cannot receive datagrams yet. streamId=\" << streamId\n              << \" len=\" << datagramQ.chainLength() << \" sess=\" << *this;\n      // TODO: a possible optimization would be to discard datagrams destined\n      // to streams that were already closed\n      auto itr = datagramsBuffer_.find(streamId);\n      if (itr == datagramsBuffer_.end()) {\n        itr = datagramsBuffer_.insert(streamId, {}).first;\n      }\n      auto& vec = itr->second;\n      if (vec.size() < vec.max_size()) {\n        vec.emplace_back(datagramQ.move());\n      } else {\n        // buffer is full: discard the datagram\n        datagramQ.move();\n      }\n      continue;\n    }\n\n    VLOG(4) << \"Received datagram for streamId=\" << streamId << \" ctx=\"\n            << (ctxId ? folly::to<std::string>(ctxId->first) : std::string())\n            << \" len=\" << datagramQ.chainLength() << \" sess=\" << *this;\n    stream->txn_.onDatagram(datagramQ.move());\n  }\n}\n\nuint16_t HQSession::HQStreamTransport::getDatagramSizeLimit() const noexcept {\n  if (!session_.datagramEnabled_) {\n    return 0;\n  }\n  auto transportMaxDatagramSize = session_.sock_->getDatagramSizeLimit();\n  if (transportMaxDatagramSize < kMaxDatagramHeaderSize) {\n    return 0;\n  }\n  return session_.sock_->getDatagramSizeLimit() - kMaxDatagramHeaderSize;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::sendDatagram(\n    std::unique_ptr<folly::IOBuf> datagram) {\n  if (!streamId_.hasValue() || !session_.datagramEnabled_) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  // Prepend the H3 Datagram header to the datagram payload\n  // HTTP/3 Datagram {\n  //   Quarter Stream ID (i),\n  //   [Context ID (i)],\n  //   HTTP/3 Datagram Payload (..),\n  // }\n  quic::BufPtr headerBuf = quic::BufPtr(\n      folly::IOBuf::create(session_.sock_->getDatagramSizeLimit()));\n  quic::BufAppender appender(headerBuf.get(), kMaxDatagramHeaderSize);\n  auto streamIdRes = quic::encodeQuicInteger(\n      streamId_.value() / 4, [&](auto val) { appender.writeBE(val); });\n  if (streamIdRes.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  if (!txn_.isWebTransportConnectStream() && !txn_.isConnectUdpStream()) {\n    // Always use context-id = 0 for now\n    auto ctxIdRes =\n        quic::encodeQuicInteger(0, [&](auto val) { appender.writeBE(val); });\n    if (ctxIdRes.hasError()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n  VLOG(4) << \"Sending datagram for streamId=\" << streamId_.value()\n          << \" len=\" << datagram->computeChainDataLength()\n          << \" sess=\" << session_;\n  quic::BufQueue queue(std::move(headerBuf));\n  queue.append(std::move(datagram));\n  auto writeRes = session_.sock_->writeDatagram(queue.move());\n  if (writeRes.hasError()) {\n    LOG(ERROR) << \"Failed to send datagram for streamId=\" << streamId_.value();\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::newWebTransportBidiStream() {\n  auto id = session_.sock_->createBidirectionalStream();\n  if (!id) {\n    LOG(ERROR) << \"Failed to create new bidirectional stream\";\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  auto numPrefaceBytesWritten =\n      writeWTStreamPrefaceToSock(*session_.sock_,\n                                 *id,\n                                 getEgressStreamId(),\n                                 hq::WebTransportStreamType::BIDI);\n  if (numPrefaceBytesWritten == 0) {\n    LOG(ERROR) << \"Failed to write bidirectional stream preface\";\n    // TODO: resetStream/stopSending?\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  return *id;\n}\n\nfolly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::newWebTransportUniStream() {\n  auto id = session_.sock_->createUnidirectionalStream();\n  if (!id) {\n    LOG(ERROR) << \"Failed to create unidirectional stream. error='\"\n               << id.error() << \"'\";\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  auto numPrefaceBytesWritten =\n      writeWTStreamPrefaceToSock(*session_.sock_,\n                                 *id,\n                                 getEgressStreamId(),\n                                 hq::WebTransportStreamType::UNI);\n  if (numPrefaceBytesWritten == 0) {\n    LOG(ERROR) << \"Failed to write unidirectional stream preface\";\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  return *id;\n}\n\nbool HQSession::HQStreamTransport::canCreateUniStream() {\n  return session_.sock_->getNumOpenableUnidirectionalStreams() > 0;\n}\n\nbool HQSession::HQStreamTransport::canCreateBidiStream() {\n  return session_.sock_->getNumOpenableBidirectionalStreams() > 0;\n}\n\nfolly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::sendWebTransportStreamData(\n    HTTPCodec::StreamID id,\n    std::unique_ptr<folly::IOBuf> data,\n    bool eof,\n    WebTransport::ByteEventCallback* deliveryCallback) {\n  if (deliveryCallback) {\n    auto maybePrefaceSize = getWebTransportPrefaceSize(id, getEgressStreamId());\n    if (maybePrefaceSize) {\n      deliveryCallback->setWritePrefaceSize(*maybePrefaceSize);\n    }\n  }\n  auto res =\n      session_.sock_->writeChain(id, std::move(data), eof, deliveryCallback);\n  if (res.hasError()) {\n    LOG(ERROR) << \"Failed to write WT stream data\";\n    return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n  }\n  auto flowControl = session_.sock_->getStreamFlowControl(id);\n  if (!flowControl) {\n    LOG(ERROR) << \"Failed to get flow control\";\n    return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n  }\n  if (!eof && flowControl->sendWindowAvailable == 0) {\n    VLOG(4) << \"FC window closed\";\n    return WebTransport::FCState::BLOCKED;\n  } else {\n    return WebTransport::FCState::UNBLOCKED;\n  }\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::sendWTMaxData(uint64_t maxData) {\n  WTMaxDataCapsule capsule{maxData};\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  if (auto res = writeWTMaxData(buf, capsule); !res.has_value()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n  }\n  txn_.sendBody(buf.move());\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::notifyPendingWriteOnStream(\n    HTTPCodec::StreamID id, quic::StreamWriteCallback* wcb) {\n  CHECK(session_.sock_);\n  session_.sock_->notifyPendingWriteOnStream(id, wcb);\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::resetWebTransportEgress(HTTPCodec::StreamID id,\n                                                      uint32_t errorCode) {\n  if (session_.sock_) {\n    auto res = session_.sock_->resetStream(\n        id,\n        quic::ApplicationErrorCode(WebTransport::toHTTPErrorCode(errorCode)));\n    if (res.hasError()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::setWebTransportStreamPriority(\n    HTTPCodec::StreamID id, quic::PriorityQueue::Priority pri) {\n  if (session_.sock_) {\n    auto res = session_.sock_->setStreamPriority(id, pri);\n    if (res.hasError()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::pauseWebTransportIngress(HTTPCodec::StreamID id) {\n  auto res = session_.sock_->pauseRead(id);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::resumeWebTransportIngress(\n    HTTPCodec::StreamID id) {\n  auto res = session_.sock_->resumeRead(id);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHQSession::HQStreamTransport::stopReadingWebTransportIngress(\n    HTTPCodec::StreamID id, folly::Optional<uint32_t> errorCode) {\n  if (session_.sock_) {\n    quic::Optional<quic::ApplicationErrorCode> quicErrorCode;\n    if (errorCode) {\n      quicErrorCode =\n          quic::ApplicationErrorCode(WebTransport::toHTTPErrorCode(*errorCode));\n    }\n    auto res = session_.sock_->setReadCallback(id, nullptr, quicErrorCode);\n    if (res.hasError()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n  return folly::unit;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HQSession& session) {\n  session.describe(os);\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, HQSession::DrainState drainState) {\n  switch (drainState) {\n    case HQSession::DrainState::NONE:\n      os << \"none\";\n      break;\n    case HQSession::DrainState::PENDING:\n      os << \"pending\";\n      break;\n    case HQSession::DrainState::CLOSE_SENT:\n      os << \"close_sent\";\n      break;\n    case HQSession::DrainState::CLOSE_RECEIVED:\n      os << \"close_recvd\";\n      break;\n    case HQSession::DrainState::FIRST_GOAWAY:\n      os << \"first_goaway\";\n      break;\n    case HQSession::DrainState::SECOND_GOAWAY:\n      os << \"second_goaway\";\n      break;\n    case HQSession::DrainState::DONE:\n      os << \"done\";\n      break;\n    default:\n      folly::assume_unreachable();\n  }\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/container/EvictingCacheMap.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/DelayedDestructionBase.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/lang/Assume.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/session/HQByteEventTracker.h>\n#include <proxygen/lib/http/session/HQStreamBase.h>\n#include <proxygen/lib/http/session/HQStreamDispatcher.h>\n#include <proxygen/lib/http/session/HTTPSessionBase.h>\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/QuicProtocolInfo.h>\n#include <proxygen/lib/http/session/ServerPushLifecycle.h>\n#include <proxygen/lib/http/session/WebTransportFilter.h>\n#include <proxygen/lib/utils/ConditionalGate.h>\n#include <quic/QuicConstants.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/common/BufUtil.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n#include <quic/priority/PriorityQueue.h>\n\nnamespace proxygen {\n\nclass HTTPSessionController;\nclass HQSession;\n\nnamespace hq {\nclass HQStreamCodec;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HQSession& session);\n\nenum class HQVersion : uint8_t {\n  H1Q_FB_V1, // HTTP1.1 on each stream, no control stream. For Interop only\n  HQ,        // The real McCoy\n};\n\nextern const std::string kH3;\nextern const std::string kH3AliasV1;\nextern const std::string kH3AliasV2;\nextern const std::string kHQ;\nextern const std::string kH3FBCurrentDraft;\n// TODO: Remove these constants, the session no longer negotiates these\nextern const std::string kH3CurrentDraft;\nextern const std::string kHQCurrentDraft;\n\n// Default Priority Node\nextern const proxygen::http2::PriorityUpdate hqDefaultPriority;\n\nusing HQVersionType = std::underlying_type<HQVersion>::type;\n\nconstexpr uint8_t kMaxDatagramHeaderSize = 16;\n// Maximum number of datagrams to buffer per stream\nconstexpr uint8_t kDefaultMaxBufferedDatagrams = 5;\n// Maximum number of streams with datagrams buffered\nconstexpr uint8_t kMaxStreamsWithBufferedDatagrams = 10;\n// Maximum number of priority updates received when stream is not available\nconstexpr uint8_t kMaxBufferedPriorityUpdates = 10;\n\nclass HQSession\n    : public quic::QuicSocket::ConnectionSetupCallback\n    , public quic::QuicSocket::ConnectionCallback\n    , public quic::QuicSocket::ReadCallback\n    , public quic::QuicSocket::WriteCallback\n    , public quic::QuicSocket::DeliveryCallback\n    , public quic::QuicSocket::DatagramCallback\n    , public quic::QuicSocket::PingCallback\n    , public HTTPSessionBase\n    , public folly::EventBase::LoopCallback\n    , public HQUniStreamDispatcher::Callback\n    , public HQBidiStreamDispatcher::Callback {\n  // Forward declarations\n public:\n  class HQStreamTransportBase;\n\n protected:\n  class HQStreamTransport;\n\n private:\n  class HQControlStream;\n\n  static constexpr uint8_t kMaxCodecStackDepth = 3;\n\n public:\n  folly::Optional<hq::PushId> getMaxAllowedPushId() {\n    return maxAllowedPushId_;\n  }\n\n  void setServerPushLifecycleCallback(ServerPushLifecycleCallback* cb) {\n    serverPushLifecycleCb_ = cb;\n  }\n\n  class ConnectCallback {\n   public:\n    virtual ~ConnectCallback() = default;\n\n    /**\n     * This function is not terminal of the callback, downstream should expect\n     * onReplaySafe to be invoked after connectSuccess.\n     * onReplaySafe is invoked right after connectSuccess if zero rtt is not\n     * attempted.\n     * In zero rtt case, onReplaySafe might never be invoked if e.g. server\n     * does not respond.\n     */\n    virtual void connectSuccess() {\n      // Default empty implementation is provided in case downstream does not\n      // attempt zero rtt data.\n    }\n\n    /**\n     * Terminal callback.\n     */\n    virtual void onReplaySafe() = 0;\n\n    /**\n     * Terminal callback.\n     */\n    virtual void connectError(quic::QuicError code) = 0;\n\n    /**\n     * Callback for the first time transport has processed a packet from peer.\n     */\n    virtual void onFirstPeerPacketProcessed() {\n    }\n  };\n\n  ~HQSession() override;\n\n  HTTPTransaction::Transport::Type getType() const noexcept override {\n    return HTTPTransaction::Transport::Type::QUIC;\n  }\n\n  void setSocket(std::shared_ptr<quic::QuicSocket> sock) noexcept {\n    sock_ = sock;\n    if (infoCallback_) {\n      infoCallback_->onCreate(*this);\n    }\n\n    if (quicInfo_) {\n      quicInfo_->transportSettings = sock_->getTransportSettings();\n    }\n  }\n\n  std::shared_ptr<QuicProtocolInfo> getQuicInfo() {\n    return quicInfo_;\n  }\n\n  void setForceUpstream1_1(bool force) {\n    forceUpstream1_1_ = force;\n  }\n  void setStrictValidation(bool strictValidation) {\n    strictValidation_ = strictValidation;\n  }\n\n  void setEnableEgressPrioritization(bool enableEgressPrioritization) {\n    enableEgressPrioritization_ = enableEgressPrioritization;\n  }\n\n  void setSessionStats(HTTPSessionStats* stats) override;\n\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override;\n\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override;\n\n  void onBidirectionalStreamsAvailable(\n      uint64_t /*numStreamsAvailable*/) noexcept override;\n\n  void onStopSending(quic::StreamId id,\n                     quic::ApplicationErrorCode error) noexcept override;\n\n  void onConnectionEnd() noexcept override;\n\n  void onConnectionEnd(quic::QuicError error) noexcept override;\n\n  void onConnectionSetupError(quic::QuicError code) noexcept override;\n\n  void onConnectionError(quic::QuicError code) noexcept override;\n\n  void onKnob(uint64_t knobSpace,\n              uint64_t knobId,\n              quic::BufPtr knobBlob) override;\n\n  // returns false in case of failure\n  bool onTransportReadyCommon() noexcept;\n\n  void onReplaySafe() noexcept override;\n\n  void onFlowControlUpdate(quic::StreamId id) noexcept override;\n\n  // quic::QuicSocket::ReadCallback\n  void readAvailable(quic::StreamId id) noexcept override;\n\n  void readError(quic::StreamId id, quic::QuicError error) noexcept override;\n\n  // quic::QuicSocket::WriteCallback\n  void onConnectionWriteReady(uint64_t maxToSend) noexcept override;\n\n  void onConnectionWriteError(quic::QuicError error) noexcept override;\n\n  // quic::QuicSocket::DatagramCallback\n  void onDatagramsAvailable() noexcept override;\n\n  // quic::QuicSocket::PingCallback\n  void pingAcknowledged() noexcept override {\n    resetTimeout();\n  }\n  void pingTimeout() noexcept override {\n  }\n  void onPing() noexcept override {\n    resetTimeout();\n  }\n\n  // Only for UpstreamSession\n  HTTPTransaction* newTransaction(HTTPTransaction::Handler* handler) override;\n\n  void startNow() override;\n\n  void describe(std::ostream& os) const override {\n    using quic::operator<<;\n    os << \"proto=\" << alpn_;\n    auto clientCid = (sock_ && sock_->getClientConnectionId())\n                         ? *sock_->getClientConnectionId()\n                         : quic::ConnectionId::createZeroLength();\n    auto serverCid = (sock_ && sock_->getServerConnectionId())\n                         ? *sock_->getServerConnectionId()\n                         : quic::ConnectionId::createZeroLength();\n    if (isDownstream(direction_)) {\n      os << \", UA=\" << userAgent_ << \", client CID=\" << clientCid\n         << \", server CID=\" << serverCid << \", downstream=\" << getPeerAddress()\n         << \", \" << getLocalAddress() << \"=local\";\n    } else {\n      os << \", client CID=\" << clientCid << \", server CID=\" << serverCid\n         << \", local=\" << getLocalAddress() << \", \" << getPeerAddress()\n         << \"=upstream\";\n    }\n    os << \", drain=\" << drainState_;\n  }\n\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData = nullptr);\n\n  void onSettings(const SettingsList& settings);\n\n  void onPriority(quic::StreamId streamId, const HTTPPriority& pri);\n  void onPushPriority(hq::PushId pushId, const HTTPPriority& pri);\n\n  folly::AsyncTransport* getTransport() override {\n    return nullptr;\n  }\n\n  folly::EventBase* getEventBase() const override {\n    if (sock_ && sock_->getEventBase()) {\n      return sock_->getEventBase()\n          ->getTypedEventBase<quic::FollyQuicEventBase>()\n          ->getBackingEventBase();\n    }\n    return nullptr;\n  }\n\n  const folly::AsyncTransport* getTransport() const override {\n    return nullptr;\n  }\n\n  bool hasActiveTransactions() const override {\n    return getNumStreams() > 0;\n  }\n\n  uint32_t getNumStreams() const override {\n    return getNumOutgoingStreams() + getNumIncomingStreams();\n  }\n\n  CodecProtocol getCodecProtocol() const override {\n    return CodecProtocol::HTTP_3;\n  }\n\n  const TimePoint& getTransportStart() const {\n    return transportStart_;\n  }\n\n  /**\n   * Set flow control properties on an already started session.\n   * QUIC requires both stream and connection flow control window sizes to be\n   * specified in the initial transport handshake. Specifying\n   * SETTINGS_INITIAL_WINDOW_SIZE in the SETTINGS frame is an error.\n   *\n   * @param initialReceiveWindow      (unused)\n   * @param receiveStreamWindowSize   per-stream receive window for NEW streams;\n   * @param receiveSessionWindowSize  per-session receive window;\n   */\n  void setFlowControl(size_t /* initialReceiveWindow */,\n                      size_t receiveStreamWindowSize,\n                      size_t receiveSessionWindowSize) override {\n    if (sock_) {\n      sock_->setConnectionFlowControlWindow(receiveSessionWindowSize);\n    }\n    receiveStreamWindowSize_ = (uint32_t)receiveStreamWindowSize;\n    HTTPSessionBase::setReadBufferLimit((uint32_t)receiveSessionWindowSize);\n  }\n\n  /**\n   * Set outgoing settings for this session\n   */\n  void setEgressSettings(const SettingsList& settings) override {\n    for (const auto& setting : settings) {\n      egressSettings_.setSetting(setting.id, setting.value);\n    }\n    const auto maxHeaderListSize =\n        egressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE);\n    if (maxHeaderListSize) {\n      versionUtilsReady_.then([this, size = maxHeaderListSize->value] {\n        qpackCodec_.setMaxUncompressed(size);\n      });\n    }\n    auto datagramEnabled = egressSettings_.getSetting(SettingsId::_HQ_DATAGRAM);\n    auto datagramDraft8Enabled =\n        egressSettings_.getSetting(SettingsId::_HQ_DATAGRAM_DRAFT_8);\n    auto datagramRFCEnabled =\n        egressSettings_.getSetting(SettingsId::_HQ_DATAGRAM_RFC);\n    // if enabling H3 datagrams check that the transport supports datagrams\n    if ((datagramEnabled && datagramEnabled->value) ||\n        (datagramDraft8Enabled && datagramDraft8Enabled->value) ||\n        (datagramRFCEnabled && datagramRFCEnabled->value)) {\n      datagramEnabled_ = true;\n    }\n  }\n\n  [[nodiscard]] bool supportsWebTransport() const {\n    return supportsWebTransport_.all();\n  }\n\n  WebTransportFilter* getWebTransportFilter() const {\n    return wtFilter_.get();\n  }\n\n  void setMaxConcurrentIncomingStreams(uint32_t /*num*/) override {\n    // need transport API\n  }\n\n  /**\n   * Send a settings frame\n   */\n  size_t sendSettings() override;\n\n  /**\n   * Causes a ping to be sent on the session. If the underlying protocol\n   * doesn't support pings, this will return 0. Otherwise, it will return\n   * the number of bytes written on the transport to send the ping.\n   */\n  size_t sendPing() override {\n    sock_->sendPing(std::chrono::milliseconds(0));\n    return 0;\n  }\n\n  size_t sendPing(uint64_t data) override {\n    sock_->sendPing(std::chrono::milliseconds(data));\n    return 0;\n  }\n\n  /**\n   * Sends a knob frame on the session.\n   */\n  quic::Expected<void, quic::LocalErrorCode> sendKnob(uint64_t knobSpace,\n                                                      uint64_t knobId,\n                                                      quic::BufPtr knobBlob) {\n    return sock_->setKnob(knobSpace, knobId, std::move(knobBlob));\n  }\n\n  size_t sendPriority(HTTPCodec::StreamID id, HTTPPriority pri);\n  size_t sendPushPriority(hq::PushId pushId, HTTPPriority pri);\n\n  /**\n   * Get session-level transport info.\n   * NOTE: The protocolInfo will be set to connection-level pointer.\n   */\n  bool getCurrentTransportInfo(wangle::TransportInfo* /*tinfo*/) override;\n\n  /**\n   *  Get session level AND stream level transport info.\n   *  NOTE: the protocolInfo will be set to stream-level pointer.\n   */\n  bool getCurrentStreamTransportInfo(QuicStreamProtocolInfo* /*qspinfo*/,\n                                     quic::StreamId /*streamId*/);\n\n  bool connCloseByRemote() override {\n    return false;\n  }\n\n  // From ManagedConnection\n  void timeoutExpired() noexcept override;\n\n  bool isBusy() const override {\n    return getNumStreams() > 0;\n  }\n  void notifyPendingShutdown() override;\n  void closeWhenIdle() override;\n  void dropConnection(const std::string& errorMsg = \"\") override;\n  void dumpConnectionState(uint8_t /*loglevel*/) override {\n  }\n\n  /*\n   * dropConnectionSync drops the connection immediately.\n   * This means that when invoked internally may need a destructor guard and\n   * the socket will be invalid after it is invoked.\n   *\n   * errorCode is passed to transport CLOSE_CONNNECTION frame\n   *\n   * proxygenError is delivered to open transactions\n   */\n  void dropConnectionSync(quic::QuicError errorCode,\n                          ProxygenError proxygenError);\n\n  // Invokes dropConnectionSync at the beginning of the next loopCallback\n  void dropConnectionAsync(quic::QuicError errorCode,\n                           ProxygenError proxygenError);\n\n  bool getCurrentTransportInfoWithoutUpdate(\n      wangle::TransportInfo* /*tinfo*/) const override;\n\n  void setHeaderCodecStats(HeaderCodec::Stats* stats) override {\n    versionUtilsReady_.then([this, stats] { qpackCodec_.setStats(stats); });\n  }\n\n  void setHeaderIndexingStrategy(\n      const HeaderIndexingStrategy* indexingStrat) override {\n    versionUtilsReady_.then([this, indexingStrat] {\n      qpackCodec_.setHeaderIndexingStrategy(indexingStrat);\n    });\n  }\n\n  void enableDoubleGoawayDrain() override {\n  }\n\n  // Upstream interface\n  bool isReusable() const override {\n    VLOG(4) << __func__ << \" sess=\" << *this;\n    return !isClosing();\n  }\n\n  bool isClosing() const override {\n    VLOG(4) << __func__ << \" sess=\" << *this;\n    return (drainState_ != DrainState::NONE || dropping_);\n  }\n\n  void drain() override {\n    notifyPendingShutdown();\n  }\n\n  folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n      uint8_t /*level*/) override {\n    return folly::none;\n  }\n\n  virtual quic::QuicSocket* getQuicSocket() const {\n    return sock_.get();\n  }\n\n  struct SessionDropReason {\n    quic::QuicError quicError{quic::TransportErrorCode::NO_ERROR};\n    ProxygenError proxygenError{ProxygenError::kErrorNone};\n  };\n  const folly::Optional<SessionDropReason>& getSessionDropReason() const {\n    return sessionDropReason_;\n  }\n\n  // Override HTTPSessionBase address getter functions\n  const folly::SocketAddress& getLocalAddress() const noexcept override {\n    return sock_ && sock_->good() ? sock_->getLocalAddress() : localAddr_;\n  }\n\n  const folly::SocketAddress& getPeerAddress() const noexcept override {\n    return sock_ && sock_->good() ? sock_->getPeerAddress() : peerAddr_;\n  }\n\n  void enablePingProbes(std::chrono::seconds /*interval*/,\n                        std::chrono::seconds /*timeout*/,\n                        bool /*extendIntervalOnIngress*/,\n                        bool /*immediate*/) override {\n    // TODO\n  }\n\n protected:\n  // Finds any transport-like stream that has not been detached\n  // by quic stream id\n  HQStreamTransportBase* findNonDetachedStream(quic::StreamId streamId);\n\n  //  Find any transport-like stream by quic stream id\n  HQStreamTransportBase* findStream(quic::StreamId streamId);\n\n  // Find any transport-like stream suitable for ingress (request/push-ingress)\n  HQStreamTransportBase* findIngressStream(quic::StreamId streamId,\n                                           bool includeDetached = false);\n  // Find any transport-like stream suitable for egress (request/push-egress)\n  HQStreamTransportBase* findEgressStream(quic::StreamId streamId,\n                                          bool includeDetached = false);\n\n  /**\n   * The following functions invoke a callback on all or on all non-detached\n   * request streams. It does an extra lookup per stream but it is safe. Note\n   * that if the callback *adds* streams, they will not get the callback.\n   */\n  void invokeOnAllStreams(std::function<void(HQStreamTransportBase*)> fn) {\n    invokeOnStreamsImpl(\n        std::move(fn),\n        [this](quic::StreamId id) { return this->findStream(id); },\n        true);\n  }\n\n  void invokeOnEgressStreams(std::function<void(HQStreamTransportBase*)> fn,\n                             bool includeDetached = false) {\n    invokeOnStreamsImpl(std::move(fn),\n                        [this, includeDetached](quic::StreamId id) {\n                          return this->findEgressStream(id, includeDetached);\n                        });\n  }\n\n  void invokeOnIngressStreams(std::function<void(HQStreamTransportBase*)> fn,\n                              bool includeDetached = false) {\n    invokeOnStreamsImpl(\n        std::move(fn),\n        [this, includeDetached](quic::StreamId id) {\n          return this->findIngressStream(id, includeDetached);\n        },\n        true);\n  }\n\n  void invokeOnNonDetachedStreams(\n      std::function<void(HQStreamTransportBase*)> fn) {\n    invokeOnStreamsImpl(std::move(fn), [this](quic::StreamId id) {\n      return this->findNonDetachedStream(id);\n    });\n  }\n\n  virtual HQStreamTransportBase* findPushStream(quic::StreamId) = 0;\n\n  virtual void findPushStreams(\n      std::unordered_set<HQStreamTransportBase*>& streams) = 0;\n\n  // Apply the function on the streams found by the two locators.\n  // Note that same stream can be returned by a find-by-stream-id\n  // and find-by-push-id locators.\n  // This is mitigated by collecting the streams in an unordered set\n  // prior to application of the funtion\n  // Note that the function is allowed to delete a stream by invoking\n  // erase stream, but the locators are not allowed to do so.\n  // Note that neither the locators nor the function are allowed\n  // to call \"invokeOnStreamsImpl\"\n  void invokeOnStreamsImpl(\n      std::function<void(HQStreamTransportBase*)> fn,\n      std::function<HQStreamTransportBase*(quic::StreamId)> findByStreamIdFn,\n      bool includePush = false) {\n    DestructorGuard g(this);\n    std::unordered_set<HQStreamTransportBase*> streams;\n    streams.reserve(getNumStreams());\n\n    for (const auto& txn : streams_) {\n      HQStreamTransportBase* pstream = findByStreamIdFn(txn.first);\n      if (pstream) {\n        streams.insert(pstream);\n      }\n    }\n\n    if (includePush) {\n      findPushStreams(streams);\n    }\n\n    for (HQStreamTransportBase* pstream : streams) {\n      CHECK(pstream);\n      fn(pstream);\n    }\n  }\n\n  // Erase the stream. Returns true if the stream\n  // has been erased\n  bool eraseStream(quic::StreamId);\n\n  virtual bool erasePushStream(quic::StreamId streamId) = 0;\n\n  void resumeReadsForPushStream(quic::StreamId streamId) {\n    pendingProcessReadSet_.insert(streamId);\n    resumeReads(streamId);\n  }\n\n  // Find a control stream by type\n  HQControlStream* findControlStream(hq::UnidirectionalStreamType streamType);\n\n  // Find a control stream by stream id (either ingress or egress)\n  HQControlStream* findControlStream(quic::StreamId streamId);\n\n  virtual HQStreamTransportBase* createIngressPushStream(quic::StreamId,\n                                                         hq::PushId) {\n    return nullptr;\n  }\n\n  virtual void eraseUnboundStream(HQStreamTransportBase*) {\n  }\n\n  virtual HTTPTransaction* newPushedTransaction(\n      HTTPCodec::StreamID,           /* parentRequestStreamId */\n      HTTPTransaction::PushHandler*, /* handler */\n      ProxygenError*) {\n    return nullptr;\n  }\n\n  // Callback methods that are invoked by the stream dispatchers\n  void dispatchControlStream(quic::StreamId /* id */,\n                             hq::UnidirectionalStreamType /* type */,\n                             size_t /* toConsume */) override;\n\n  void dispatchRequestStream(quic::StreamId /* streamId */) override;\n  void dispatchRequestStreamImpl(quic::StreamId /* streamId */);\n\n  std::chrono::milliseconds getDispatchTimeout() const override {\n    return transactionsTimeout_;\n  }\n\n  void rejectStream(quic::StreamId /* id */) override;\n\n  folly::Optional<hq::UnidirectionalStreamType> parseUniStreamPreface(\n      uint64_t preface) override;\n\n  folly::Optional<hq::BidirectionalStreamType> parseBidiStreamPreface(\n      uint64_t preface) override {\n    if (preface ==\n        folly::to_underlying(hq::BidirectionalStreamType::WEBTRANSPORT)) {\n      if (supportsWebTransport()) {\n        return hq::BidirectionalStreamType::WEBTRANSPORT;\n      } else {\n        LOG(ERROR) << \"WT stream when it is unsupported sess=\" << *this;\n        return folly::none;\n      }\n    }\n    if (isDownstream(direction_)) {\n      return hq::BidirectionalStreamType::REQUEST;\n    }\n    return folly::none;\n  }\n\n  HQStreamTransportBase* FOLLY_NULLABLE\n  findWTSessionOrAbort(quic::StreamId sessionID, quic::StreamId streamId);\n\n  /**\n   * HQSession is an HTTPSessionBase that uses QUIC as the underlying transport\n   *\n   * HQSession is an abstract base class and cannot be instantiated\n   * directly. If you want to handle requests and send responses (act as a\n   * server), construct a HQDownstreamSession. If you want to make\n   * requests and handle responses (act as a client), construct a\n   * HQUpstreamSession.\n   */\n  HQSession(const std::chrono::milliseconds transactionsTimeout,\n            HTTPSessionController* controller,\n            proxygen::TransportDirection direction,\n            const wangle::TransportInfo& tinfo,\n            InfoCallback* sessionInfoCb)\n      : HTTPSessionBase(folly::SocketAddress(),\n                        folly::SocketAddress(),\n                        controller,\n                        tinfo,\n                        sessionInfoCb,\n                        std::make_unique<HTTP1xCodec>(direction),\n                        WheelTimerInstance(),\n                        hq::kSessionStreamId),\n        direction_(direction),\n        transactionsTimeout_(transactionsTimeout),\n        started_(false),\n        dropping_(false),\n        inLoopCallback_(false),\n        unidirectionalReadDispatcher_(*this, direction),\n        bidirectionalReadDispatcher_(*this, direction),\n        controlStreamReadCallback_(*this),\n        sessionObserverAccessor_(this),\n        sessionObserverContainer_(&sessionObserverAccessor_) {\n    codec_.add<HTTPChecks>();\n    // dummy, ingress, egress\n    codecStack_.reserve(kMaxCodecStackDepth);\n    codecStack_.emplace_back(nullptr, nullptr, nullptr);\n\n    attachToSessionController();\n    quicInfo_ = std::make_shared<QuicProtocolInfo>();\n    initCodecHeaderIndexingStrategy();\n  }\n\n  // EventBase::LoopCallback methods\n  void runLoopCallback() noexcept override;\n\n  /**\n   * Called by transactionTimeout if the transaction has no handler.\n   */\n  virtual HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn) = 0;\n\n  /**\n   * Called by onHeadersComplete(). This function allows downstream and\n   * upstream to do any setup (like preparing a handler) when headers are\n   * first received from the remote side on a given transaction.\n   */\n  virtual void setupOnHeadersComplete(HTTPTransaction* txn,\n                                      HTTPMessage* msg) = 0;\n\n  /**\n   * Executed on connection setup failure.\n   */\n  virtual void onConnectionSetupErrorHandler(\n      quic::QuicError error) noexcept = 0;\n\n  void applySettings(const SettingsList& settings);\n\n  virtual void connectSuccess() noexcept {\n  }\n\n  bool isPeerUniStream(quic::StreamId id) {\n    return sock_->isUnidirectionalStream(id) &&\n           ((isDownstream(direction_) && sock_->isClientStream(id)) ||\n            (isUpstream(direction_) && sock_->isServerStream(id)));\n  }\n\n  bool isSelfUniStream(quic::StreamId id) {\n    return sock_->isUnidirectionalStream(id) &&\n           ((isDownstream(direction_) && sock_->isServerStream(id)) ||\n            (isUpstream(direction_) && sock_->isClientStream(id)));\n  }\n\n  void abortStream(HTTPException::Direction dir,\n                   quic::StreamId id,\n                   HTTP3::ErrorCode err);\n\n  // Get extra HTTP headers we want to add to the HTTPMessage in sendHeaders.\n  virtual folly::Optional<HTTPHeaders> getExtraHeaders(const HTTPMessage&,\n                                                       quic::StreamId) {\n    return folly::none;\n  }\n\n  proxygen::TransportDirection direction_;\n  std::chrono::milliseconds transactionsTimeout_;\n  TimePoint transportStart_;\n\n  std::shared_ptr<quic::QuicSocket> sock_;\n\n  // Callback pointer used for correctness testing. Not used\n  // for session logic.\n  ServerPushLifecycleCallback* serverPushLifecycleCb_{nullptr};\n\n  // Debug feature to test the impact of request body prioritization.\n  bool enableEgressPrioritization_{true};\n\n private:\n  std::unique_ptr<HTTPCodec> createStreamCodec(quic::StreamId streamId);\n\n  // Creates a request stream. All streams that are not control streams\n  // or Push streams are request streams.\n  HQStreamTransport* createStreamTransport(quic::StreamId streamId);\n\n  bool createEgressControlStreams();\n\n  // Creates outgoing control stream.\n  bool createEgressControlStream(hq::UnidirectionalStreamType streamType);\n\n  // Creates incoming control stream\n  HQControlStream* createIngressControlStream(\n      quic::StreamId id, hq::UnidirectionalStreamType streamType);\n\n  virtual void cleanupUnboundPushStreams(std::vector<quic::StreamId>&) {\n  }\n\n  // gets the ALPN from the transport and returns whether the protocol is\n  // supported. Drops the connection if not supported\n  bool getAndCheckApplicationProtocol();\n\n  // Use ALPN to set the correct version utils strategy.\n  void setVersionUtils();\n\n  // Used during 2-phased GOAWAY messages, and EOF sending.\n  void onDeliveryAck(quic::StreamId id,\n                     uint64_t offset,\n                     std::chrono::microseconds rtt) override;\n\n  void onCanceled(quic::StreamId id, uint64_t offset) override;\n\n  // helper functions for reads\n  void readRequestStream(quic::StreamId id) noexcept;\n  void readControlStream(HQControlStream* controlStream);\n\n  // Runs the codecs on all request streams that have received data\n  // during the last event loop\n  void processReadData();\n\n  // Pausing reads prevents the read callback to be invoked on the stream\n  void resumeReads(quic::StreamId id);\n\n  // Resume all ingress transactions\n  void resumeReads();\n\n  // Resuming the reads allows the read callback to be involved\n  void pauseReads(quic::StreamId id);\n\n  // Pause all ingress transactions\n  void pauseReads();\n\n  void notifyEgressBodyBuffered(int64_t bytes);\n\n  // The max allowed push id value. Value folly::none indicates that\n  // a. For downstream session: MAX_PUSH_ID has not been received\n  // b. For upstream session: MAX_PUSH_ID has been explicitly set to none\n  // In both cases, maxAllowedPushId_ == folly::none means that no push id\n  // is allowed. Default to kEightByteLimit assuming this session will\n  // be using push.\n  folly::Optional<hq::PushId> maxAllowedPushId_{folly::none};\n\n  // Schedule the loop callback.\n  // To keep this consistent with EventBase::runInLoop run in the next loop\n  // by default\n  void scheduleLoopCallback(bool thisIteration = false);\n\n  // helper functions for writes\n  uint64_t writeRequestStreams(uint64_t maxEgress) noexcept;\n  void scheduleWrite();\n  void handleWriteError(HQStreamTransportBase* hqStream,\n                        quic::QuicErrorCode err);\n\n  /**\n   * Handles the write to the socket and errors for a request stream.\n   * Returns the number of bytes written from data.\n   */\n  template <typename WriteFunc, typename DataType>\n  size_t handleWrite(WriteFunc writeFunc,\n                     HQStreamTransportBase* hqStream,\n                     DataType dataType,\n                     size_t dataChainLen,\n                     bool sendEof);\n\n  /**\n   * Helper function to perform writes on a single request stream\n   * The first argument defines whether the implementation should\n   * call onWriteReady on the transaction to get data allocated\n   * in the write buffer.\n   * Returns the number of bytes written to the transport\n   */\n  uint64_t requestStreamWriteImpl(HQStreamTransportBase* hqStream,\n                                  uint64_t maxEgress,\n                                  double ratio);\n\n  uint64_t writeControlStreams(uint64_t maxEgress);\n  uint64_t controlStreamWriteImpl(HQControlStream* ctrlStream,\n                                  uint64_t maxEgress);\n  void handleSessionError(HQStreamBase* stream,\n                          hq::StreamDirection streamDir,\n                          quic::QuicErrorCode err,\n                          ProxygenError proxygenError);\n\n  void detachStreamTransport(HQStreamTransportBase* hqStream);\n\n  void drainImpl();\n\n  void checkForShutdown();\n  void onGoawayAck();\n  quic::StreamId getGoawayStreamId();\n\n  void errorOnTransactionId(quic::StreamId id, const HTTPException& ex);\n\n  /**\n   * Shared implementation of \"findXXXstream\" methods\n   */\n  HQStreamTransportBase* findStreamImpl(quic::StreamId streamId,\n                                        bool includeEgress = true,\n                                        bool includeIngress = true,\n                                        bool includeDetached = true);\n\n  /**\n   * Shared implementation of \"numberOfXXX\" methods\n   */\n  uint32_t countStreamsImpl(bool includeEgress = true,\n                            bool includeIngress = true) const;\n\n  std::list<folly::AsyncTransport::ReplaySafetyCallback*>\n      waitingForReplaySafety_;\n\n  /**\n   * With HTTP/1.1 codecs, graceful shutdown happens when the session has sent\n   * and received a Connection: close header, and all streams have completed.\n   *\n   * The application can signal intent to drain by calling notifyPendingShutdown\n   * (or its alias, drain).  The peer can signal intent to drain by including\n   * a Connection: close header.\n   *\n   * closeWhenIdle will bypass the requirement to send/receive Connection:\n   * close, and the socket will terminate as soon as the stream count reaches 0.\n   *\n   * dropConnection will forcibly close all streams and guarantee that the\n   * HQSession has been deleted before exiting.\n   *\n   * The intent is that an application will first notifyPendingShutdown() all\n   * open sessions.  Then after some period of time, it will call closeWhenIdle.\n   * As a last resort, it will call dropConnection.\n   *\n   * Note we allow the peer to create streams after draining because of out\n   * of order delivery.\n   *\n   * drainState_ tracks the progress towards shutdown.\n   *\n   *  NONE - no shutdown requested\n   *  PENDING - shutdown requested but no Connection: close seen\n   *  CLOSE_SENT - sent Connection: close but not received\n   *  CLOSE_RECEIVED - received Connection: close but not sent\n   *  DONE - sent and received Connection: close.\n   *\n   *  NONE ---> PENDING ---> CLOSE_SENT --+--> DONE\n   *    |          |                      |\n   *    +----------+-------> CLOSE_RECV --+\n   *\n   * For sessions with a control stream shutdown is driven by GOAWAYs.\n   * Only the server can send GOAWAYs so the behavior is asymmetric between\n   * upstream and downstream\n   *\n   *  NONE - no shutdown requested\n   *  PENDING - shutdown requested but no GOAWAY sent/received yet\n   *  FIRST_GOAWAY - first GOAWAY received/sent\n   *  SECOND_GOAWAY - downstream only - second GOAWAY sent\n   *  DONE - two GOAWAYs sent/received. can close when request streams are done\n   *\n   */\n  enum DrainState : uint8_t {\n    NONE = 0,\n    PENDING = 1,\n    CLOSE_SENT = 2,\n    CLOSE_RECEIVED = 3,\n    FIRST_GOAWAY = 4,\n    SECOND_GOAWAY = 5,\n    DONE = 6\n  };\n\n  DrainState drainState_{DrainState::NONE};\n  bool started_ : 1;\n  bool dropping_ : 1;\n  bool inLoopCallback_ : 1;\n  folly::Optional<SessionDropReason> sessionDropReason_;\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4250) // inherits 'proxygen::detail::..' via dominance\n#endif\n\n  // A control stream is created as egress first, then the ingress counterpart\n  // is linked as soon as we read the stream preface on the associated stream\n  class HQControlStream\n      : public detail::composite::CSBidir\n      , public HQStreamBase\n      , public hq::HQUnidirectionalCodec::Callback\n      , public quic::QuicSocket::DeliveryCallback {\n   public:\n    HQControlStream() = delete;\n    HQControlStream(HQSession& session,\n                    quic::StreamId egressStreamId,\n                    hq::UnidirectionalStreamType type)\n        : detail::composite::CSBidir(egressStreamId, folly::none),\n          HQStreamBase(session, session.codec_, type) {\n      createEgressCodec();\n    }\n\n    void createEgressCodec() {\n      CHECK(type_.has_value());\n      switch (*type_) {\n        case hq::UnidirectionalStreamType::CONTROL:\n          realCodec_ =\n              std::make_unique<hq::HQControlCodec>(getEgressStreamId(),\n                                                   session_.direction_,\n                                                   hq::StreamDirection::EGRESS,\n                                                   session_.egressSettings_,\n                                                   *type_);\n          break;\n        case hq::UnidirectionalStreamType::QPACK_ENCODER:\n        case hq::UnidirectionalStreamType::QPACK_DECODER:\n          // These are statically allocated in the session\n          break;\n        default:\n          LOG(FATAL)\n              << \"Failed to create egress codec.\"\n              << \" unrecognized stream type=\" << static_cast<uint64_t>(*type_);\n      }\n    }\n\n    void setIngressCodec(std::unique_ptr<hq::HQUnidirectionalCodec> codec) {\n      ingressCodec_ = std::move(codec);\n    }\n\n    void processReadData();\n\n    // QuicSocket::DeliveryCallback\n    void onDeliveryAck(quic::StreamId id,\n                       uint64_t offset,\n                       std::chrono::microseconds rtt) override;\n    void onCanceled(quic::StreamId id, uint64_t offset) override;\n\n    // HTTPCodec::Callback\n    void onMessageBegin(HTTPCodec::StreamID /*stream*/,\n                        HTTPMessage* /*msg*/) override {\n      LOG(FATAL) << __func__ << \" called on a Control Stream.\";\n    }\n\n    void onHeadersComplete(HTTPCodec::StreamID /*stream*/,\n                           std::unique_ptr<HTTPMessage> /*msg*/) override {\n      LOG(FATAL) << __func__ << \" called on a Control Stream.\";\n    }\n\n    void onBody(HTTPCodec::StreamID /*stream*/,\n                std::unique_ptr<folly::IOBuf> /*chain*/,\n                uint16_t /*padding*/) override {\n      LOG(FATAL) << __func__ << \" called on a Control Stream.\";\n    }\n\n    void onTrailersComplete(\n        HTTPCodec::StreamID /*stream*/,\n        std::unique_ptr<HTTPHeaders> /*trailers*/) override {\n      LOG(FATAL) << __func__ << \" called on a Control Stream.\";\n    }\n\n    void onMessageComplete(HTTPCodec::StreamID /*stream*/,\n                           bool /*upgrade*/) override {\n      LOG(FATAL) << __func__ << \" called on a Control Stream.\";\n    }\n\n    void onError(HTTPCodec::StreamID /*stream*/,\n                 const HTTPException& /*error*/,\n                 bool /* newTxn */ = false) override;\n\n    void onGoaway(uint64_t lastGoodStreamID,\n                  ErrorCode code,\n                  std::unique_ptr<folly::IOBuf> debugData = nullptr) override {\n      session_.onGoaway(lastGoodStreamID, code, std::move(debugData));\n    }\n\n    void onSettings(const SettingsList& settings) override {\n      session_.onSettings(settings);\n    }\n\n    void onPriority(HTTPCodec::StreamID id, const HTTPPriority& pri) override {\n      session_.onPriority(id, pri);\n    }\n\n    void onPushPriority(HTTPCodec::StreamID id,\n                        const HTTPPriority& pri) override {\n      session_.onPushPriority(id, pri);\n    }\n\n    std::unique_ptr<hq::HQUnidirectionalCodec> ingressCodec_;\n    bool readEOF_{false};\n  }; // HQControlStream\n\n  // Callback for the control stream - follows the read api\n  struct ControlStreamReadCallback : public quic::QuicSocket::ReadCallback {\n    explicit ControlStreamReadCallback(HQSession& session) : session_(session) {\n    }\n    ~ControlStreamReadCallback() override = default;\n    void readAvailable(quic::StreamId id) noexcept override {\n      session_.controlStreamReadAvailable(id);\n    }\n    void readError(quic::StreamId id, quic::QuicError error) noexcept override {\n      session_.controlStreamReadError(id, error);\n    }\n\n   protected:\n    HQSession& session_;\n  };\n\n public:\n  class HQStreamTransportBase\n      : public HQStreamBase\n      , public HTTPTransaction::Transport\n      , public HTTP2PriorityQueueBase\n      , public quic::ByteEventCallback {\n   protected:\n    HQStreamTransportBase(\n        HQSession& session,\n        TransportDirection direction,\n        quic::StreamId streamId,\n        uint32_t seqNo,\n        const WheelTimerInstance& wheelTimer,\n        HTTPSessionStats* stats = nullptr,\n        http2::PriorityUpdate priority = hqDefaultPriority,\n        folly::Optional<HTTPCodec::StreamID> parentTxnId = HTTPCodec::NoStream,\n        folly::Optional<hq::UnidirectionalStreamType> type = folly::none);\n\n    void initCodec(std::unique_ptr<HTTPCodec> /* codec */,\n                   const std::string& /* where */);\n\n    void initIngress(const std::string& /* where */);\n\n    HTTPSessionBase* getHTTPSessionBase() override {\n      return &(getSession());\n    }\n\n   public:\n    HQStreamTransportBase() = delete;\n\n    bool hasCodec() const {\n      return hasCodec_;\n    }\n\n    bool hasIngress() const {\n      return hasIngress_;\n    }\n\n    // process data in the read buffer, returns true if the codec is blocked\n    bool processReadData();\n\n    // Process data from QUIC onDataAvailable callback.\n    void processPeekData(\n        const folly::Range<quic::QuicSocket::PeekIterator>& peekData);\n\n    // QuicSocket::DeliveryCallback\n    void onByteEvent(quic::ByteEvent byteEvent) override;\n    void onByteEventCanceled(quic::ByteEventCancellation cancellation) override;\n\n    // HTTPCodec::Callback methods\n    void onMessageBegin(HTTPCodec::StreamID streamID,\n                        HTTPMessage* /* msg */) override;\n\n    void onPushMessageBegin(HTTPCodec::StreamID /* pushID */,\n                            HTTPCodec::StreamID /* parentTxnId */,\n                            HTTPMessage* /* msg */) override;\n\n    virtual void onPushPromiseHeadersComplete(\n        hq::PushId /* pushID */,\n        HTTPCodec::StreamID /* assoc streamID */,\n        std::unique_ptr<HTTPMessage> /* msg */) {\n      LOG(ERROR) << \"push promise: txn=\" << txn_ << \" TODO\";\n    }\n\n    void onHeadersComplete(HTTPCodec::StreamID streamID,\n                           std::unique_ptr<HTTPMessage> msg) override;\n\n    void onBody(HTTPCodec::StreamID /* streamID */,\n                std::unique_ptr<folly::IOBuf> chain,\n                uint16_t padding) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      CHECK(chain);\n      auto len = chain->computeChainDataLength();\n      if (session_.onBodyImpl(std::move(chain), len, padding, &txn_)) {\n        session_.pauseReads();\n      }\n    }\n\n    void onChunkHeader(HTTPCodec::StreamID /* stream */,\n                       size_t length) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      txn_.onIngressChunkHeader(length);\n    }\n\n    void onChunkComplete(HTTPCodec::StreamID /* stream */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      txn_.onIngressChunkComplete();\n    }\n\n    void onTrailersComplete(HTTPCodec::StreamID /* streamID */,\n                            std::unique_ptr<HTTPHeaders> trailers) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      txn_.onIngressTrailers(std::move(trailers));\n    }\n\n    void onMessageComplete(HTTPCodec::StreamID /* streamID */,\n                           bool /* upgrade */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      // for 1xx responses (excluding 101) onMessageComplete may be called\n      // more than once\n      if (txn_.isUpstream() && txn_.extraResponseExpected()) {\n        return;\n      }\n      if (session_.infoCallback_) {\n        session_.infoCallback_->onRequestEnd(session_,\n                                             txn_.getMaxDeferredSize());\n      }\n      // Pause the parser, which will prevent more than one message from being\n      // processed\n      auto g = folly::makeGuard(setActiveCodec(__func__));\n      codecFilterChain->setParserPaused(true);\n      eomGate_.set(EOMType::CODEC);\n    }\n\n    void onIngressEOF() {\n      // Can only call this once\n      CHECK(!eomGate_.get(EOMType::TRANSPORT));\n      if (ingressError_) {\n        // This codec has already errored, no need to give it more input\n        return;\n      }\n      auto g = folly::makeGuard(setActiveCodec(__func__));\n      codecFilterChain->onIngressEOF();\n      eomGate_.set(EOMType::TRANSPORT);\n    }\n\n    void onError(HTTPCodec::StreamID streamID,\n                 const HTTPException& error,\n                 bool newTxn) override;\n\n    // Invoked when we get a RST_STREAM from the transport\n    void onResetStream(HTTP3::ErrorCode error, HTTPException ex);\n\n    void onAbort(HTTPCodec::StreamID /* streamID */,\n                 ErrorCode /* code */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      // Can't really get here since no HQ codecs can produce aborts.\n      // The entry point is onResetStream via readError()\n      LOG(DFATAL) << \"Unexpected abort\";\n    }\n\n    void onFrameHeader(HTTPCodec::StreamID /* stream_id */,\n                       uint8_t /* flags */,\n                       uint64_t /* length */,\n                       uint64_t /* type */,\n                       uint16_t /* version */ = 0) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onGoaway(\n        uint64_t /* lastGoodStreamID */,\n        ErrorCode /* code */,\n        std::unique_ptr<folly::IOBuf> /* debugData */ = nullptr) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onPingRequest(uint64_t /* data */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onPingReply(uint64_t /* data */) override {\n      // This method should not get called\n      LOG(FATAL) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onWindowUpdate(HTTPCodec::StreamID /* stream */,\n                        uint32_t /* amount */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onSettings(const SettingsList& /*settings*/) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void onSettingsAck() override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    uint32_t numOutgoingStreams() const override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return 0;\n    }\n\n    uint32_t numIncomingStreams() const override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return 0;\n    }\n\n    // HTTPTransaction::Transport methods\n\n    // For parity with H2, pause/resumeIngress now a no-op.  All transactions\n    // will pause when total buffered egress exceeds the configured limit, which\n    // should be equal to the recv flow control window\n    void pauseIngress(HTTPTransaction* /* txn */) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void resumeIngress(HTTPTransaction* /* txn */) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    void transactionTimeout(HTTPTransaction* /* txn */) noexcept override;\n\n    void sendHeaders(HTTPTransaction* txn,\n                     const HTTPMessage& headers,\n                     HTTPHeaderSize* size,\n                     bool includeEOM) noexcept override;\n\n    size_t sendBody(HTTPTransaction* txn,\n                    std::unique_ptr<folly::IOBuf> body,\n                    bool includeEOM,\n                    bool trackLastByteFlushed) noexcept override;\n\n    size_t sendChunkHeader(HTTPTransaction* txn,\n                           size_t length) noexcept override;\n\n    size_t sendChunkTerminator(HTTPTransaction* txn) noexcept override;\n\n    size_t sendPadding(HTTPTransaction* txn, uint16_t bytes) noexcept override;\n\n    size_t sendEOM(HTTPTransaction* txn,\n                   const HTTPHeaders* trailers) noexcept override;\n\n    size_t sendAbort(HTTPTransaction* txn,\n                     ErrorCode statusCode) noexcept override;\n\n    size_t sendAbortImpl(HTTP3::ErrorCode errorCode, std::string errorMsg);\n\n    size_t changePriority(HTTPTransaction* txn,\n                          HTTPPriority pri) noexcept override;\n\n    size_t sendWindowUpdate(HTTPTransaction* /* txn */,\n                            uint32_t /* bytes */) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      CHECK(hasEgressStreamId())\n          << __func__ << \" invoked on stream without egress\";\n      return 0;\n    }\n\n    // Send a push promise. Has different implementations in\n    // request streams / push streams\n    virtual void sendPushPromise(HTTPTransaction* /* txn */,\n                                 folly::Optional<hq::PushId> /* pushId */,\n                                 const HTTPMessage& /* headers */,\n                                 HTTPHeaderSize* /* outSize */,\n                                 bool /* includeEOM */) {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      CHECK(hasEgressStreamId())\n          << __func__ << \" invoked on stream without egress\";\n    }\n\n    void notifyPendingEgress() noexcept override;\n\n    void detach(HTTPTransaction* /* txn */) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      detached_ = true;\n      session_.httpPriorityQueue_.erase(queueHandle_.getStreamId());\n      session_.scheduleLoopCallback();\n    }\n    void checkForDetach();\n\n    void notifyIngressBodyProcessed(uint32_t bytes) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      if (session_.notifyBodyProcessed(bytes)) {\n        session_.resumeReads();\n      }\n    }\n\n    void notifyEgressBodyBuffered(int64_t bytes) noexcept override {\n      session_.notifyEgressBodyBuffered(bytes);\n    }\n\n    const folly::SocketAddress& getLocalAddress() const noexcept override {\n      return session_.getLocalAddress();\n    }\n\n    const folly::SocketAddress& getPeerAddress() const noexcept override {\n      return session_.getPeerAddress();\n    }\n\n    void describe(std::ostream& os) const override {\n      session_.describe(os);\n    }\n\n    const wangle::TransportInfo& getSetupTransportInfo()\n        const noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return session_.transportInfo_;\n    }\n\n    [[nodiscard]] std::chrono::seconds getLatestIdleTime() const override {\n      return session_.getLatestIdleTime();\n    }\n\n    bool getCurrentTransportInfo(wangle::TransportInfo* tinfo) override;\n\n    void getFlowControlInfo(\n        HTTPTransaction::FlowControlInfo* /*info*/) override {\n      // Not implemented\n    }\n\n    HTTPTransaction::Transport::Type getSessionType() const noexcept override;\n\n    const HTTPCodec& getCodec() const noexcept override {\n      return HQStreamBase::getCodec();\n    }\n\n    void drain() override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n    }\n\n    bool isDraining() const override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return false;\n    }\n\n    HTTPTransaction* newPushedTransaction(\n        HTTPCodec::StreamID /* parentTxnId */,\n        HTTPTransaction::PushHandler* /* handler */,\n        ProxygenError* /* error */ = nullptr) noexcept override {\n      LOG(FATAL) << __func__ << \" Only available via request stream\";\n      folly::assume_unreachable();\n    }\n\n    std::string getSecurityProtocol() const override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return \"quic/tls1.3\";\n    }\n\n    void addWaitingForReplaySafety(folly::AsyncTransport::ReplaySafetyCallback*\n                                       callback) noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      if (session_.sock_->replaySafe()) {\n        callback->onReplaySafe();\n      } else {\n        session_.waitingForReplaySafety_.push_back(callback);\n      }\n    }\n\n    void removeWaitingForReplaySafety(\n        folly::AsyncTransport::ReplaySafetyCallback* callback) noexcept\n        override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      session_.waitingForReplaySafety_.remove(callback);\n    }\n\n    bool needToBlockForReplaySafety() const override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return false;\n    }\n\n    const folly::AsyncTransport* getUnderlyingTransport()\n        const noexcept override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return nullptr;\n    }\n\n    quic::TransportInfo getTransportInfo() const override {\n      if (session_.sock_) {\n        return session_.sock_->getTransportInfo();\n      }\n      return {};\n    }\n\n    bool isReplaySafe() const override {\n      return session_.isReplaySafe();\n    }\n\n    void setHTTP2PrioritiesEnabled(bool /* enabled */) override {\n    }\n    bool getHTTP2PrioritiesEnabled() const override {\n      return false;\n    }\n\n    folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n        uint8_t /* pri */) override {\n      VLOG(4) << __func__ << \" txn=\" << txn_;\n      return HTTPMessage::HTTP2Priority(hqDefaultPriority.streamDependency,\n                                        hqDefaultPriority.exclusive,\n                                        hqDefaultPriority.weight);\n    }\n\n    folly::Optional<HTTPPriority> getHTTPPriority() override {\n      if (session_.sock_ && hasStreamId()) {\n        auto sp = session_.sock_->getStreamPriority(getStreamId());\n        if (sp) {\n          quic::HTTPPriorityQueue::Priority priority(sp.value());\n          return HTTPPriority(\n              priority->urgency, priority->incremental, priority->order);\n        }\n      }\n      return folly::none;\n    }\n\n    folly::Optional<HTTPTransaction::ConnectionToken> getConnectionToken()\n        const noexcept override {\n      return session_.connectionToken_;\n    }\n\n    void trackEgressBodyOffset(uint64_t bodyOffset,\n                               proxygen::ByteEvent::EventFlags flags) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxStreams(\n        uint64_t /*maxStreams*/, bool /*isBidi*/) override {\n      return folly::unit;\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTStreamsBlocked(\n        uint64_t /*maxStreams*/, bool /*isBidi*/) override {\n      return folly::unit;\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTDataBlocked(\n        uint64_t /*maxData*/) override {\n      return folly::unit;\n    }\n\n    /**\n     * Returns whether or no we have any body bytes buffered in the stream, or\n     * the txn has any body bytes buffered.\n     */\n    size_t writeBufferSize() const;\n    bool hasWriteBuffer() const;\n    bool hasPendingBody() const;\n    bool hasPendingEOM() const;\n    bool hasPendingEgress() const;\n\n    void setPriority(quic::QuicSocket& sock,\n                     quic::StreamId id,\n                     proxygen::HTTPPriority pri);\n    /**\n     * Adapter class for managing different enqueued state between\n     * HTTPTransaction and HQStreamTransport.  The decouples whether the\n     * transaction thinks it is enqueued for egress (which impacts txn lifetime)\n     * and whether the HQStreamTransport is enqueued (which impacts the\n     * actual egress algorithm).  Note all 4 states are possible.\n     */\n    class HQPriHandle : public HTTP2PriorityQueue::BaseNode {\n     public:\n      void setStreamId(quic::StreamId streamId) {\n        id_ = quic::PriorityQueue::Identifier::fromStreamID(streamId);\n      }\n\n      [[nodiscard]] quic::PriorityQueue::Identifier getStreamId() const {\n        return id_;\n      }\n\n      // HQStreamTransport is enqueued\n      bool isStreamTransportEnqueued() const {\n        return streamTransportEnqueued_;\n      }\n\n      bool isTransactionEnqueued() const {\n        return transactionEnqueued_;\n      }\n\n      void setTransactionEnqueued(bool enqueued) {\n        transactionEnqueued_ = enqueued;\n      }\n\n      void setStreamTransportEnqueued(bool enqueued) {\n        streamTransportEnqueued_ = enqueued;\n      }\n\n      [[nodiscard]] bool isEnqueued() const override {\n        return transactionEnqueued_;\n      }\n\n      void setPriority(quic::HTTPPriorityQueue::Priority pri) {\n        pri_ = pri;\n      }\n\n      quic::HTTPPriorityQueue::Priority getPriority() {\n        return pri_;\n      }\n\n      [[nodiscard]] uint64_t calculateDepth(\n          bool /* includeVirtual = true*/) const override {\n        return 0;\n      }\n\n     private:\n      bool transactionEnqueued_{false};\n      bool streamTransportEnqueued_{false};\n      quic::PriorityQueue::Identifier id_;\n      quic::HTTPPriorityQueue::Priority pri_{3, false};\n    };\n\n    HTTP2PriorityQueueBase::Handle addTransaction(\n        HTTPCodec::StreamID id,\n        http2::PriorityUpdate /*pri*/,\n        HTTPTransaction* /*txn*/,\n        bool /*permanent*/,\n        uint64_t* /*depth*/) override {\n      queueHandle_.setStreamId(id);\n      return &queueHandle_;\n    }\n\n    // Update the priority of an existing node\n    HTTP2PriorityQueueBase::Handle updatePriority(\n        HTTP2PriorityQueueBase::Handle /*handle*/,\n        http2::PriorityUpdate /*pri*/,\n        uint64_t* /*depth*/) override {\n      // no-op\n      return nullptr;\n    }\n\n    // Remove the transaction from the priority tree\n    void removeTransaction(HTTP2PriorityQueueBase::Handle /*handle*/) override {\n      if (queueHandle_.isStreamTransportEnqueued() ||\n          queueHandle_.isTransactionEnqueued()) {\n        session_.httpPriorityQueue_.erase(queueHandle_.getStreamId());\n      }\n      queueHandle_.setTransactionEnqueued(false);\n      queueHandle_.setStreamTransportEnqueued(false);\n    }\n\n    // Notify the queue when a transaction has egress\n    void signalPendingEgress(HTTP2PriorityQueueBase::Handle /*h*/) override {\n      queueHandle_.setTransactionEnqueued(true);\n      signalPendingEgressStreamTransport();\n    }\n\n    void signalPendingEgressStreamTransport() {\n      if (queueHandle_.isStreamTransportEnqueued()) {\n        return;\n      }\n      auto flowControl =\n          session_.sock_->getStreamFlowControl(getEgressStreamId());\n      if (!flowControl.hasError() && flowControl->sendWindowAvailable > 0) {\n        session_.httpPriorityQueue_.insertOrUpdate(queueHandle_.getStreamId(),\n                                                   queueHandle_.getPriority());\n        queueHandle_.setStreamTransportEnqueued(true);\n      } else {\n        VLOG(4) << \"Delay pending egress signal on blocked txn=\" << txn_;\n      }\n    }\n\n    // Notify the queue when a transaction no longer has egress\n    void clearPendingEgress(HTTP2PriorityQueueBase::Handle /*h*/) override {\n      CHECK(queueHandle_.isTransactionEnqueued());\n      queueHandle_.setTransactionEnqueued(false);\n      if (pendingEOM_ || hasWriteBuffer()) {\n        // no-op\n        // Only HQSession can clearPendingEgress for these cases\n        return;\n      }\n      clearPendingEgressStreamTransport();\n    }\n\n    void clearPendingEgressStreamTransport() {\n      // The transaction has pending body data, but it decided to remove itself\n      // from the egress queue since it's rate-limited\n      if (queueHandle_.isStreamTransportEnqueued()) {\n        session_.httpPriorityQueue_.erase(queueHandle_.getStreamId());\n        queueHandle_.setStreamTransportEnqueued(false);\n      }\n    }\n\n    void addPriorityNode(HTTPCodec::StreamID /*id*/,\n                         HTTPCodec::StreamID /*parent*/) override {\n      // no-op\n      return;\n    }\n\n    /**\n     * How many egress bytes we committed to transport, both written and\n     * skipped.\n     */\n    uint64_t streamEgressCommittedByteOffset() const {\n      return bytesWritten_;\n    }\n\n    /**\n     * streamEgressCommittedByteOffset() plus any pending bytes in the egress\n     * queue.\n     */\n    uint64_t streamWriteByteOffset() const {\n      return streamEgressCommittedByteOffset() + writeBuf_.chainLength();\n    }\n\n    void abortIngress();\n\n    void abortEgress(bool checkForDetach);\n\n    void errorOnTransaction(ProxygenError err, const std::string& errorMsg);\n    void errorOnTransaction(const HTTPException& ex);\n\n    bool wantsOnWriteReady(size_t canSend) const;\n\n    HQPriHandle queueHandle_;\n    HTTPTransaction txn_;\n    // need to send EOM\n    bool pendingEOM_{false};\n    // have read EOF\n    bool readEOF_{false};\n    bool hasCodec_{false};\n    bool hasIngress_{false};\n    bool detached_{false};\n    bool ingressError_{false};\n    bool hasHeaders_{false};\n    enum class EOMType { CODEC, TRANSPORT };\n    ConditionalGate<EOMType, 2> eomGate_;\n\n    folly::Optional<HTTPCodec::StreamID> codecStreamId_;\n\n    HQByteEventTracker byteEventTracker_;\n\n    // Stream + session protocol info\n    std::shared_ptr<QuicStreamProtocolInfo> quicStreamProtocolInfo_;\n\n    void armStreamByteEventCb(uint64_t streamOffset,\n                              quic::ByteEvent::Type type);\n    void armEgressHeadersAckCb(uint64_t streamOffset);\n    void armEgressBodyCallbacks(uint64_t bodyOffset,\n                                uint64_t streamOffset,\n                                proxygen::ByteEvent::EventFlags eventFlags);\n    void resetEgressHeadersAckOffset() {\n      egressHeadersAckOffset_ = folly::none;\n    }\n    folly::Optional<uint64_t> resetEgressBodyEventOffset(\n        uint64_t streamOffset) {\n      auto it = egressBodyByteEventOffsets_.find(streamOffset);\n      if (it != egressBodyByteEventOffsets_.end()) {\n        CHECK_GT(it->second.callbacks, 0);\n        it->second.callbacks--;\n        auto bodyOffset = it->second.bodyOffset;\n        if (it->second.callbacks == 0) {\n          egressBodyByteEventOffsets_.erase(it);\n        }\n        return bodyOffset;\n      }\n      return folly::none;\n    }\n\n    uint64_t numActiveDeliveryCallbacks() const {\n      return numActiveDeliveryCallbacks_;\n    }\n\n   private:\n    void updatePriority(const HTTPMessage& headers) noexcept;\n\n    // Return the old and new offset of the stream\n    std::pair<uint64_t, uint64_t> generateHeadersCommon(\n        quic::StreamId streamId,\n        const HTTPMessage& headers,\n        bool includeEOM,\n        HTTPHeaderSize* size) noexcept;\n    void coalesceEOM(size_t encodedBodySize);\n    void handleHeadersAcked(uint64_t streamOffset);\n    void handleBodyEvent(uint64_t streamOffset, quic::ByteEvent::Type type);\n    void handleBodyEventCancelled(uint64_t streamOffset,\n                                  quic::ByteEvent::Type type);\n    uint64_t bodyBytesEgressed_{0};\n    folly::Optional<uint64_t> egressHeadersAckOffset_;\n    struct BodyByteOffset {\n      uint64_t bodyOffset;\n      uint64_t callbacks;\n      BodyByteOffset(uint64_t bo, uint64_t c) : bodyOffset(bo), callbacks(c) {\n      }\n    };\n    // We allow random insert/removal in this map, but removal should be\n    // sequential\n    folly::F14FastMap<uint64_t, BodyByteOffset> egressBodyByteEventOffsets_;\n    // Track number of armed QUIC delivery callbacks.\n    uint64_t numActiveDeliveryCallbacks_{0};\n\n    // Used to store last seen ingress push ID between\n    // the invocations of onPushPromiseBegin / onHeadersComplete.\n    // It is being reset by\n    //  - \"onNewMessage\" (in which case the push promise is being abandoned),\n    //  - \"onPushMessageBegin\" (which may be abandonned / duplicate message id)\n    //  - \"onHeadersComplete\" (not pending anymore)\n    folly::Optional<hq::PushId> ingressPushId_;\n  }; // HQStreamTransportBase\n\n  void dispatchUniWTStream(quic::StreamId /* streamId */,\n                           quic::StreamId /* sessionId */,\n                           size_t /* to consume */) override;\n\n  void dispatchBidiWTStream(quic::StreamId /* streamId */,\n                            quic::StreamId /* sessionId */,\n                            size_t /* to consume */) override;\n\n protected:\n  class HQStreamTransport\n      : public detail::singlestream::SSBidir\n      , public HQStreamTransportBase {\n   public:\n    HQStreamTransport(\n        HQSession& session,\n        TransportDirection direction,\n        quic::StreamId streamId,\n        uint32_t seqNo,\n        std::unique_ptr<HTTPCodec> codec,\n        const WheelTimerInstance& wheelTimer,\n        HTTPSessionStats* stats = nullptr,\n        http2::PriorityUpdate priority = hqDefaultPriority,\n        folly::Optional<HTTPCodec::StreamID> parentTxnId = HTTPCodec::NoStream)\n        : detail::singlestream::SSBidir(streamId),\n          HQStreamTransportBase(session,\n                                direction,\n                                streamId,\n                                seqNo,\n                                wheelTimer,\n                                stats,\n                                priority,\n                                parentTxnId) {\n      // Request streams are eagerly initialized\n      initCodec(std::move(codec), __func__);\n      initIngress(__func__);\n    }\n\n    HTTPTransaction* newPushedTransaction(\n        HTTPCodec::StreamID /* parentTxnId */,\n        HTTPTransaction::PushHandler* /* handler */,\n        ProxygenError* error = nullptr) noexcept override;\n\n    void sendPushPromise(HTTPTransaction* /* txn */,\n                         folly::Optional<hq::PushId> /* pushId */,\n                         const HTTPMessage& /* headers */,\n                         HTTPHeaderSize* /* outSize */,\n                         bool /* includeEOM */) override;\n\n    void onPushPromiseHeadersComplete(\n        hq::PushId /* pushID */,\n        HTTPCodec::StreamID /* assoc streamID */,\n        std::unique_ptr<HTTPMessage> /* promise */) override;\n\n    uint16_t getDatagramSizeLimit() const noexcept override;\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n        std::unique_ptr<folly::IOBuf> datagram) override;\n\n    uint64_t getWTInitialSendWindow() const override {\n      return session_.wtInitialSendWindow_;\n    }\n\n    bool isPeerInitiatedStream(HTTPCodec::StreamID id) override {\n      bool isClientStream = session_.sock_->isClientStream(id);\n      return (isDownstream(session_.direction_) && isClientStream) ||\n             (isUpstream(session_.direction_) && !isClientStream);\n    }\n\n    [[nodiscard]] bool supportsWebTransport() const override {\n      return session_.supportsWebTransport();\n    }\n    folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportBidiStream() override;\n    folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportUniStream() override;\n\n    bool canCreateUniStream() override;\n    bool canCreateBidiStream() override;\n\n    folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n    sendWebTransportStreamData(\n        HTTPCodec::StreamID /*id*/,\n        std::unique_ptr<folly::IOBuf> /*data*/,\n        bool /*eof*/,\n        WebTransport::ByteEventCallback* /* deliveryCallback */) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n        uint64_t maxData) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    notifyPendingWriteOnStream(HTTPCodec::StreamID,\n                               quic::StreamWriteCallback* wcb) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    resetWebTransportEgress(HTTPCodec::StreamID /*id*/,\n                            uint32_t /*errorCode*/) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    setWebTransportStreamPriority(HTTPCodec::StreamID /*id*/,\n                                  quic::PriorityQueue::Priority pri) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    setWebTransportPriorityQueue(\n        std::unique_ptr<quic::PriorityQueue> /*queue*/) noexcept override {\n      // HQSession uses HTTP priorities which cannot be merged with a custom\n      // priority queue\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n\n    folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                    WebTransport::ErrorCode>\n    readWebTransportData(HTTPCodec::StreamID id, size_t max) override {\n      auto res = session_.sock_->read(id, max);\n      if (res) {\n        return std::move(res.value());\n      } else {\n        return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n      }\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    initiateReadOnBidiStream(HTTPCodec::StreamID id,\n                             quic::StreamReadCallback* readCallback) override {\n      auto res = session_.sock_->setReadCallback(id, readCallback);\n      if (res) {\n        return folly::unit;\n      } else {\n        return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n      }\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        pauseWebTransportIngress(HTTPCodec::StreamID /*id*/) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        resumeWebTransportIngress(HTTPCodec::StreamID /*id*/) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        stopReadingWebTransportIngress(\n            HTTPCodec::StreamID /*id*/,\n            folly::Optional<uint32_t> /*errorCode*/) override;\n\n  }; // HQStreamTransport\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n  std::unique_ptr<HTTPCodec> createCodec(quic::StreamId id);\n  bool maybeRejectRequestAfterGoaway(quic::StreamId id);\n\n private:\n  void sendGoaway();\n\n  std::unique_ptr<hq::HQUnidirectionalCodec> createControlCodec(\n      hq::UnidirectionalStreamType type, HQControlStream& controlStream);\n\n  void headersComplete(HTTPMessage* /*msg*/);\n\n  void readDataProcessed();\n\n  void abortStream(quic::StreamId /*id*/);\n\n  uint32_t getMaxConcurrentOutgoingStreamsRemote() const override {\n    // need transport API\n    return 100;\n  }\n\n  using HTTPCodecPtr = std::unique_ptr<HTTPCodec>;\n  struct CodecStackEntry {\n    HTTPCodecPtr* codecPtr;\n    HTTPCodecPtr codec;\n    HTTPCodec::Callback* callback;\n    CodecStackEntry(HTTPCodecPtr* p, HTTPCodecPtr c, HTTPCodec::Callback* cb)\n        : codecPtr(p), codec(std::move(c)), callback(cb) {\n    }\n  };\n  std::vector<CodecStackEntry> codecStack_;\n  quic::HTTPPriorityQueue httpPriorityQueue_;\n\n  // Cleanup all pending streams. Invoked in session timeout\n  size_t cleanupPendingStreams();\n\n  // Remove all callbacks from a stream during cleanup\n  void clearStreamCallbacks(quic::StreamId /* id */);\n\n  void controlStreamReadAvailable(quic::StreamId id);\n  void controlStreamReadError(quic::StreamId id, const quic::QuicError& error);\n\n  using ControlStreamsKey = std::pair<quic::StreamId, hq::StreamDirection>;\n  std::unordered_map<hq::UnidirectionalStreamType, HQControlStream>\n      controlStreams_;\n  HQUniStreamDispatcher unidirectionalReadDispatcher_;\n  HQBidiStreamDispatcher bidirectionalReadDispatcher_;\n  ControlStreamReadCallback controlStreamReadCallback_;\n  QPACKCodec qpackCodec_;\n\n  // Min Stream ID we haven't seen so far\n  quic::StreamId minUnseenIncomingStreamId_{0};\n  // Whether SETTINGS have been received\n  bool receivedSettings_{false};\n  enum class SettingEnabled : uint8_t { SELF = 0, PEER = 1 };\n  std::bitset<2> supportsWebTransport_;\n\n  std::unique_ptr<WebTransportFilter> wtFilter_{};\n\n  /**\n   * The maximum number of concurrent transactions that this session's peer\n   * may create.\n   */\n  uint32_t maxConcurrentIncomingStreams_{100};\n  folly::Optional<uint32_t> receiveStreamWindowSize_;\n\n  uint64_t maxToSend_{0};\n  bool scheduledWrite_{false};\n\n  bool forceUpstream1_1_{true};\n  // Default to false for now to match existing behavior\n  bool strictValidation_{false};\n  bool datagramEnabled_{false};\n\n  /** Reads in the current loop iteration */\n  uint16_t readsPerLoop_{0};\n  std::unordered_set<quic::StreamId> pendingProcessReadSet_;\n  std::shared_ptr<QuicProtocolInfo> quicInfo_;\n  folly::Optional<HQVersion> version_;\n  std::string alpn_;\n\n protected:\n  HTTPSettings egressSettings_{\n      {SettingsId::HEADER_TABLE_SIZE, hq::kDefaultEgressHeaderTableSize},\n      {SettingsId::MAX_HEADER_LIST_SIZE, hq::kDefaultEgressMaxHeaderListSize},\n      {SettingsId::_HQ_QPACK_BLOCKED_STREAMS,\n       hq::kDefaultEgressQpackBlockedStream},\n      {SettingsId::WT_INITIAL_MAX_DATA, quic::kMaxVarInt},\n  };\n  HTTPSettings ingressSettings_;\n  uint64_t wtInitialSendWindow_{quic::kMaxVarInt};\n  // Maximum Stream/Push ID that we are allowed to open, from GOAWAY\n  quic::StreamId peerMinUnseenId_{hq::kMaxClientBidiStreamId};\n  uint64_t minUnseenIncomingPushId_{0};\n\n  ReadyGate versionUtilsReady_;\n\n  // NOTE: introduce better decoupling between the streams\n  // and the containing session, then remove the friendship.\n  friend class HQStreamBase;\n\n  // To let the operator<< access DrainState which is private\n  friend std::ostream& operator<<(std::ostream&, DrainState);\n\n  // Bidirectional transport streams\n  std::unordered_map<quic::StreamId, HQStreamTransport> streams_;\n\n  // Buffer for datagrams waiting for a stream to be assigned to\n  folly::EvictingCacheMap<\n      quic::StreamId,\n      folly::small_vector<\n          std::unique_ptr<folly::IOBuf>,\n          kDefaultMaxBufferedDatagrams,\n          folly::small_vector_policy::policy_in_situ_only<true>>>\n      datagramsBuffer_{kMaxStreamsWithBufferedDatagrams};\n\n  // Buffer for priority updates without an active stream\n  folly::EvictingCacheMap<quic::StreamId, HTTPPriority> priorityUpdatesBuffer_{\n      kMaxBufferedPriorityUpdates};\n\n  // Lookup maps for matching PushIds to StreamIds\n  folly::F14FastMap<hq::PushId, quic::StreamId> pushIdToStreamId_;\n  // Lookup maps for matching ingress push streams to push ids\n  folly::F14FastMap<quic::StreamId, hq::PushId> streamIdToPushId_;\n  std::string userAgent_;\n\n  /**\n   * Accessor implementation for HTTPSessionObserver.\n   */\n  class ObserverAccessor : public HTTPSessionObserverAccessor {\n   public:\n    explicit ObserverAccessor(HTTPSessionBase* sessionBasePtr)\n        : sessionBasePtr_(sessionBasePtr) {\n      (void)sessionBasePtr_; // silence unused variable warnings\n    }\n    ~ObserverAccessor() override = default;\n\n    size_t sendPing(uint64_t data) override {\n      if (sessionBasePtr_) {\n        return sessionBasePtr_->sendPing(data);\n      }\n      return 0;\n    }\n\n   private:\n    HTTPSessionBase* sessionBasePtr_{nullptr};\n  };\n\n  ObserverAccessor sessionObserverAccessor_;\n\n  // Container of observers for a HTTP session\n  //\n  // This member MUST be last in the list of members to ensure it is destroyed\n  // first, before any other members are destroyed. This ensures that observers\n  // can inspect any session state available through public methods\n  // when destruction of the session begins.\n  HTTPSessionObserverContainer sessionObserverContainer_;\n\n  HTTPSessionObserverContainer* getHTTPSessionObserverContainer()\n      const override {\n    return const_cast<HTTPSessionObserverContainer*>(\n        &sessionObserverContainer_);\n  }\n\n  class WriteScheduler : public folly::EventBase::LoopCallback {\n   public:\n    explicit WriteScheduler(HQSession& session) : session_(session) {\n    }\n    ~WriteScheduler() override = default;\n    void runLoopCallback() noexcept override;\n\n   private:\n    HQSession& session_;\n  };\n  WriteScheduler writeScheduler_{*this};\n}; // HQSession\n\nstd::ostream& operator<<(std::ostream& os, HQSession::DrainState drainState);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQStreamBase.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQStreamBase.h>\n\n#include <proxygen/lib/http/session/HQSession.h>\n\nnamespace proxygen {\n\nHQStreamBase::HQStreamBase(\n    HQSession& session,\n    HTTPCodecFilterChain& codecFilterChain,\n    folly::Optional<hq::UnidirectionalStreamType> streamType)\n    : codecFilterChain(codecFilterChain),\n      createdTime(std::chrono::steady_clock::now()),\n      type_(streamType),\n      session_(session) {\n}\n\nconst HTTPCodec& HQStreamBase::getCodec() const noexcept {\n  if (realCodec_) {\n    // it's not in the codec stack\n    return *realCodec_;\n  }\n  if (*realCodecPtr_) {\n    // it's in the codec stack, but not current\n    return *realCodecPtr_->get();\n  }\n  // must be the current codec\n  return *CHECK_NOTNULL(&codecFilterChain.getChainEnd());\n}\n\nHQSession& HQStreamBase::getSession() const noexcept {\n  return session_;\n}\n\n/**\n * The session maintains a codecStack which contains the history of\n * setActiveCodec calls.  The stack has a pointer to where the active codec\n * needs to be restored, and the actual unique_ptr to the codec if a newer one\n * gets pushed on.  The stack will always have at least depth 1.\n *\n * So if setActiveCodec is called 3 times recursively with {c1, c2, c1}, the\n * state will look like this:\n *\n *  codecFilterChain->chainEnd: codec1\n *\n *  [ &codecStack[1].second, nullptr      txn1    ]\n *  [ &txn2->realCodec_,     codec2,      txn2    ]\n *  [ &txn1->realCodec_,     nullptr,     txn1    ]\n *  [ nullptr,               dummy codec, nullptr ]\n *\n * That said, I can't imagine how depth could be greater than 3:\n *  dummy codec, ingress codec, egress codec.\n */\nfolly::Function<void()> HQStreamBase::setActiveCodec(const std::string& where) {\n  if (!realCodecPtr_->get()) {\n    // already the active codec, no-op\n    CHECK(!realCodec_);\n    return [] {};\n  }\n  VLOG(5) << \"Pushing active codec from \" << where;\n  CHECK_LT(session_.codecStack_.size(), HQSession::kMaxCodecStackDepth);\n  CHECK(!session_.codecStack_.back().codec);\n  // Set the requested codec as the chain destination, and store the previous\n  // codec to its spot in the codecStack\n  session_.codecStack_.back().codec =\n      codecFilterChain.setDestination(std::move(*realCodecPtr_));\n  // push a new entry for the current codec\n  session_.codecStack_.emplace_back(realCodecPtr_, nullptr, this);\n  // update a ptr to where to find the codec if needed\n  realCodecPtr_ = &session_.codecStack_.back().codec;\n  codecFilterChain.setCallback(this);\n  return [this, where] {\n    // put this codec back where it belongs\n    VLOG(5) << \"Popping active codec from \" << where;\n    auto codecPtr = session_.codecStack_.back().codecPtr;\n    CHECK(!session_.codecStack_.back().codec);\n    // pop the stack\n    session_.codecStack_.pop_back();\n    // move previous codec from the stack to active\n    *codecPtr = codecFilterChain.setDestination(\n        std::move(session_.codecStack_.back().codec));\n    codecFilterChain.setCallback(session_.codecStack_.back().callback);\n    realCodecPtr_ = codecPtr;\n  };\n}\n\nsize_t HQStreamBase::generateStreamPreface() {\n  // Request (aka HQStreamTransport) streams do not type set.\n  // If \"generateStreamPreface\" is invoked on those, its a bug\n  CHECK(type_.has_value())\n      << \"Can not generate preface on streams without a type\";\n  VLOG(4) << \"generating stream preface for \" << type_.value()\n          << \" stream streamID=\" << getEgressStreamId() << \" sess=\" << session_;\n  auto res = hq::writeStreamPreface(\n      writeBuf_, static_cast<hq::StreamTypeType>(type_.value()));\n  CHECK(!res.hasError());\n  return res.value();\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQStreamBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <folly/lang/Assume.h>\n#include <proxygen/lib/http/HTTPException.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <quic/codec/Types.h>\n\n/**\n *\n *\n * Building blocks for the different types of HTTP/3 streams.\n *\n * The streams in HQSession.h can be classified by several\n * axes:\n *   - Kind: Can carry requests / can carry control\n *   - Direction: Unidirectional / bidirectional\n *   - Transport Cardinality:\n *       Use a single transport stream / use 2 transport streams\n *\n * The above is achieved via the base/trait inheritance scheme:\n *    - Codec management is encapsulated in HQStreamBase class\n *    - Direction and cardinality are encapsulated in\n *      HQStreamMapping hierarchy\n *    - Kind is represented by\n *        Requests - HQStreamTransportBase (HQSession.h)\n *        Control  - HQControlStream (HQSession.h)\n *\n * Different streams use different combinations of the above:\n *\n *  - Request streams (HQStreamTransport):\n *     - Kind: requests\n *     - Direction: bidir\n *     - Cardinality: single bidirectional transport stream\n *\n *  - Control streams (HQControlStream)\n *     - Kind: control\n *     - Direction: unidir\n *     - Cardinality: 2 unidirectional transport streams\n *\n *  - Incoming push streams (HQIngressPushStream)\n *    - Kind: requests\n *    - Direction: unidir\n *    - Cardinality: single unidirectional ingress transport stream\n *\n *  - Incoming push streams (HQEgressPushStream)\n *    - Kind: requests\n *    - Direction: unidir\n *    - Cardinality: single unidirectional egress transport stream\n *\n *\n *  The above puts a challenge: The codec management code needs to access\n *    different streams, but should not be aware of the cardinailty.\n *\n *  This challenge is resolved via a diamond inheritance:\n *\n *\n *\n *     +---------------------+\n *     |   HQStreamMapping   |\n *     |    virtual base     |\n *     +---------------------+\n *                |\n *                |\n *                |\n *                |\n *                |\n *                +-------------------------------+\n *                V                               |\n *     +---------------------+                    |\n *     |    HQStreamBase     |                    |\n *     |  codec management   |                    |\n *     +---------------------+                    V\n *                |                    +---------------------+\n *                |                    |  Concrete mapping   |\n *                V                    |   (see below)       |\n *  +----------------------------+     +---------------------+\n *  |    Base implementation     |                |\n *  |          of Kind           |                |\n *  |     (requests/control)     |                |\n *  | Requests:                  |                |\n *  |   HQStreamTransportBase    |                |\n *  | Control                    |                |\n *  |   HQControlStream          |                |\n *  | (in HQSession.h)           |                |\n *  +----------------------------+                |\n *                |                               |\n *                +-------------------------------+\n *                |\n *                |\n *                |\n *                |\n *                V\n *  +----------------------------+\n *  |      Concrete stream       |   See HQStreamTransport\n *  |       implementation       |       HQControlStream\n *  |   combines the Kind base   |       HQIngressPushStream\n *  +----------------------------+       HQEgressPushStream\n *\n *\n */\n\nnamespace proxygen {\n\nclass HTTPSessionController;\nclass HQSession;\nclass VersionUtils;\n\n/**\n * the HQStreamMapping trait defines how logical stream directions are\n * mapped to physical transport streams, and which of the directions are not\n * implemented.\n *\n *\n *                            +-----------------+\n *                            | HQStreamMapping |\n *              +-------------|  -------------  |----------+\n *              V             | L4 - L5 mapping |          |\n *    +------------------+    +-----------------+          |\n *    |     SSBidir      |             |                   |\n *    |  --------------  |             |                   |\n * +--|  egress/ingress  |             |                   |\n * |  | 1 bidirectional  |             |                   |\n * |  |    L4 stream     |             |                   |\n * |  +------------------+             |                   |\n * |                                   |                   |\n * +------------+                      |                   |\n * |            |                      |                   |\n * |            V                      |                   |\n * |  +------------------+             V                   |\n * |  |     SSEgress     |  +---------------------+        |\n * |  |  --------------  |  |    HQStreamBase     |        |\n * |  |   egress only    |  |     ----------      |        |\n * |  | 1 unidirectional |  |  Codec stack mgmt   |        |\n * |  |    L4 stream     |  +---------------------+        |\n * |  +------------------+                                 |\n * |                                                       V\n * |                                             +------------------+\n * |  +------------------+                       |     CSBidir      |\n * |  |    SSIngress     |                       |  --------------  |\n * |  |  --------------  |                       |  egress/ingress  |\n * +->|   ingress only   |                       | 2 unidirectional |\n *    | 1 unidirectional |                       |    L4 streams    |\n *    |    L4 stream     |                       +------------------+\n *    +------------------+\n *\n */\nclass HQStreamMapping {\n public:\n  // NOTE: do not put any non-trivial logic in\n  // the destructor of HQStreamMapping.\n  virtual ~HQStreamMapping() = default;\n\n  // Accessors for ingress/egress transport streams\n  virtual quic::StreamId getEgressStreamId() const = 0;\n\n  virtual quic::StreamId getIngressStreamId() const = 0;\n\n  virtual bool hasIngressStreamId() const = 0;\n\n  virtual bool hasEgressStreamId() const = 0;\n\n  bool hasStreamId() const {\n    return hasIngressStreamId() || hasEgressStreamId();\n  }\n\n  // Safe way to check whether this HQStream is using that quicStream\n  // see usage in HQSession::findControlStream\n  virtual bool isUsing(quic::StreamId /* streamId */) const = 0;\n\n  virtual quic::StreamId getStreamId() const = 0;\n\n  virtual void setIngressStreamId(quic::StreamId /* streamId */) = 0;\n\n  virtual void setEgressStreamId(quic::StreamId /* streamId */) = 0;\n\n  virtual HTTPException::Direction getStreamDirection() const = 0;\n};\n\n/**\n * The lowest common denominator of HQ streams.\n * This class defines codec stacking and holds the session object.\n */\nclass HQStreamBase\n    : public virtual HQStreamMapping\n    , public HTTPCodec::Callback {\n public:\n  HQStreamBase() = delete;\n  HQStreamBase(\n      HQSession& /* session */,\n      HTTPCodecFilterChain& /* codecFilterChain */,\n      folly::Optional<hq::UnidirectionalStreamType> streamType = folly::none);\n\n  ~HQStreamBase() override = default;\n\n  const HTTPCodec& getCodec() const noexcept;\n\n  HQSession& getSession() const noexcept;\n\n  folly::Function<void()> setActiveCodec(const std::string& /* where */);\n\n  HTTPCodecFilterChain& codecFilterChain;\n\n  const std::chrono::steady_clock::time_point createdTime;\n\n  size_t generateStreamPreface();\n\n  /* Stream type (for streams with prefaces only) */\n  folly::Optional<hq::UnidirectionalStreamType> type_;\n\n  /** Chain of ingress IOBufs */\n  folly::IOBufQueue readBuf_{folly::IOBufQueue::cacheChainLength()};\n\n  /** Queue of egress IOBufs */\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n\n  uint64_t bytesWritten_{0}; // Accounts for egress bytes written on the\n                             // stream.\n\n protected:\n  /** parent session **/\n  HQSession& session_;\n\n  std::unique_ptr<HTTPCodec> realCodec_;\n  // realCodecPtr points to wherever this txn's codec is UNLESS it is the\n  // active codec, in which case it is nullptr, and the codec is at the end\n  // of the filter chain\n  std::unique_ptr<HTTPCodec>* realCodecPtr_{&realCodec_};\n};\n\nnamespace detail {\n\n/**\n * Transport configurations that use a single transport stream.\n * Intended usage: Request streams (bidirectional), Push streams (single\n * direction)\n */\nnamespace singlestream {\n\n/**\n * Bidirectional single transport stream\n */\nclass SSBidir : public virtual HQStreamMapping {\n public:\n  ~SSBidir() override = default;\n\n  explicit SSBidir(folly::Optional<quic::StreamId> streamId)\n      : streamId_(streamId) {\n  }\n\n  quic::StreamId getEgressStreamId() const override {\n    return getStreamId();\n  }\n\n  quic::StreamId getIngressStreamId() const override {\n    return getStreamId();\n  }\n\n  bool isUsing(quic::StreamId streamId) const override {\n    if (streamId_) {\n      return streamId_.value() == streamId;\n    }\n    return false;\n  }\n\n  bool hasIngressStreamId() const override {\n    return streamId_.has_value();\n  }\n\n  bool hasEgressStreamId() const override {\n    return streamId_.has_value();\n  }\n\n  quic::StreamId getStreamId() const override {\n    CHECK(streamId_) << \"Stream MUST be assigned before being accessed\";\n    return streamId_.value();\n  }\n\n  void setIngressStreamId(quic::StreamId streamId) override {\n    streamId_ = streamId;\n  }\n\n  void setEgressStreamId(quic::StreamId streamId) override {\n    streamId_ = streamId;\n  }\n\n  HTTPException::Direction getStreamDirection() const override {\n    return HTTPException::Direction::INGRESS_AND_EGRESS;\n  }\n\n protected:\n  folly::Optional<quic::StreamId> streamId_;\n}; // proxygen::detail::singlestream::Bidir\n\n/**\n * Egress only transport streams\n */\nclass SSEgress : public SSBidir {\n public:\n  ~SSEgress() override = default;\n\n  explicit SSEgress(folly::Optional<quic::StreamId> streamId)\n      : SSBidir(streamId) {\n  }\n\n  quic::StreamId getIngressStreamId() const override {\n    LOG(FATAL) << \"Egress only stream can not be used for ingress\";\n    folly::assume_unreachable();\n  }\n\n  void setIngressStreamId(quic::StreamId /* streamId */) override {\n    LOG(FATAL) << \"Egress only stream can not be used for ingress\";\n  }\n\n  bool hasIngressStreamId() const override {\n    return false;\n  }\n\n  HTTPException::Direction getStreamDirection() const override {\n    return HTTPException::Direction::EGRESS;\n  }\n\n}; // proxygen::detail::singlestream::Egress\n\nclass SSIngress : public SSBidir {\n public:\n  ~SSIngress() override = default;\n\n  explicit SSIngress(folly::Optional<quic::StreamId> streamId)\n      : SSBidir(streamId) {\n  }\n\n  quic::StreamId getEgressStreamId() const override {\n    LOG(FATAL) << \"Ingress only stream can not be used for egress\";\n    folly::assume_unreachable();\n  }\n\n  void setEgressStreamId(quic::StreamId /* streamId */) override {\n    LOG(FATAL) << \"Ingress only stream can not be used for egress\";\n  }\n\n  bool hasEgressStreamId() const override {\n    return false;\n  }\n\n  HTTPException::Direction getStreamDirection() const override {\n    return HTTPException::Direction::INGRESS;\n  }\n\n}; // proxygen::detail::singlestream::Ingres\n} // namespace singlestream\n\n/**\n * Composite stream bases.\n */\nnamespace composite {\n/**\n * Bidirectional composite base\n */\nclass CSBidir : public virtual HQStreamMapping {\n public:\n  ~CSBidir() override = default;\n\n  explicit CSBidir(folly::Optional<quic::StreamId> egressStreamId,\n                   folly::Optional<quic::StreamId> ingressStreamId)\n      : egressStreamId_(egressStreamId), ingressStreamId_(ingressStreamId) {\n  }\n\n  quic::StreamId getEgressStreamId() const override {\n    CHECK(egressStreamId_)\n        << \"Egress stream MUST be assigned before being accessed\";\n    return egressStreamId_.value();\n  }\n\n  quic::StreamId getIngressStreamId() const override {\n    CHECK(ingressStreamId_)\n        << \"Ingress stream MUST be assigned before being accessed\";\n    return ingressStreamId_.value();\n  }\n\n  bool isUsing(quic::StreamId streamId) const override {\n    if (ingressStreamId_) {\n      if (ingressStreamId_.value() == streamId) {\n        return true;\n      }\n    }\n    if (egressStreamId_) {\n      if (egressStreamId_.value() == streamId) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  bool hasIngressStreamId() const override {\n    return ingressStreamId_.has_value();\n  }\n\n  bool hasEgressStreamId() const override {\n    return egressStreamId_.has_value();\n  }\n\n  quic::StreamId getStreamId() const override {\n    LOG(FATAL) << \"Ambiguous call 'getStreamId' on a composite stream\";\n    folly::assume_unreachable();\n  }\n\n  void setIngressStreamId(quic::StreamId streamId) override {\n    ingressStreamId_ = streamId;\n  }\n\n  void setEgressStreamId(quic::StreamId streamId) override {\n    egressStreamId_ = streamId;\n  }\n\n  HTTPException::Direction getStreamDirection() const override {\n    return HTTPException::Direction::INGRESS_AND_EGRESS;\n  }\n\n private:\n  folly::Optional<quic::StreamId> egressStreamId_;\n  folly::Optional<quic::StreamId> ingressStreamId_;\n}; // proxygen::detail::composite::Bidir\n\n} // namespace composite\n} // namespace detail\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQStreamDispatcher.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQStreamDispatcher.h>\n\nusing namespace proxygen;\n\nHQStreamDispatcherBase::HQStreamDispatcherBase(\n    CallbackBase& callback, proxygen::TransportDirection direction)\n    : callback_(callback), direction_(direction) {\n}\n\nvoid HQStreamDispatcherBase::peekError(quic::StreamId id,\n                                       quic::QuicError error) noexcept {\n  VLOG(4) << __func__ << \": peekError streamID=\" << id << \" error: \" << error;\n\n  switch (error.code.type()) {\n    case quic::QuicErrorCode::Type::ApplicationErrorCode: {\n      auto errorCode =\n          static_cast<HTTP3::ErrorCode>(*error.code.asApplicationErrorCode());\n      VLOG(4) << \"peekError: QUIC Application Error: \" << toString(errorCode)\n              << \" streamID=\" << id;\n      break;\n    }\n    case quic::QuicErrorCode::Type::LocalErrorCode: {\n      quic::LocalErrorCode& errorCode = *error.code.asLocalErrorCode();\n      VLOG(4) << \"peekError: QUIC Local Error: \" << errorCode\n              << \" streamID=\" << id;\n      break;\n    }\n    case quic::QuicErrorCode::Type::TransportErrorCode: {\n      quic::TransportErrorCode& errorCode = *error.code.asTransportErrorCode();\n      VLOG(4) << \"peekError: QUIC Transport Error: \" << errorCode\n              << \" streamID=\" << id;\n      break;\n    }\n  }\n}\n\nvoid HQStreamDispatcherBase::onDataAvailable(\n    quic::StreamId id, const CallbackBase::PeekData& peekData) noexcept {\n  if (peekData.empty()) {\n    return;\n  }\n\n  auto& peekFirst = peekData.front();\n  // if not at offset 0, ignore\n  if (peekFirst.offset != 0) {\n    return;\n  }\n\n  auto maybeClearPeekCallback = folly::makeGuard([&] {\n    if (peekFirst.eof) {\n      // Chunk offset 0 with EOF means we must dispatch, or clear the cb\n      VLOG(4) << \"Undispatchable stream (EOF before preface complete)\";\n      callback_.rejectStream(releaseOwnership(id));\n    }\n  });\n  // empty buffer, just EOF\n  auto dataBuf = peekFirst.data.front();\n  if (!dataBuf) {\n    return;\n  }\n\n  // Look for a stream preface in the first read buffer\n  VLOG(4) << \"Attempting peek dispatch stream=\" << id\n          << \" len=\" << dataBuf->computeChainDataLength();\n  folly::io::Cursor cursor(dataBuf);\n  auto preface = quic::follyutils::decodeQuicInteger(cursor);\n  if (!preface) {\n    return;\n  }\n\n  auto result = handleStream(id, cursor, preface->first, preface->second);\n  if (result != HandleStreamResult::PENDING) {\n    maybeClearPeekCallback.dismiss();\n    if (result == HandleStreamResult::REJECT) {\n      callback_.rejectStream(releaseOwnership(id));\n    }\n  }\n}\n\nHQStreamDispatcherBase::HandleStreamResult HQUniStreamDispatcher::handleStream(\n    quic::StreamId id,\n    folly::io::Cursor& cursor,\n    uint64_t preface,\n    size_t consumed) {\n  auto type = callback_.parseUniStreamPreface(preface);\n\n  if (!type) {\n    // Failed to identify the preface,\n    return HandleStreamResult::REJECT;\n  }\n\n  switch (type.value()) {\n    case hq::UnidirectionalStreamType::CONTROL:\n    case hq::UnidirectionalStreamType::QPACK_ENCODER:\n    case hq::UnidirectionalStreamType::QPACK_DECODER: {\n      // This is a control stream, and it needs a read callback\n      // Pass ownership back to the callback\n      callback_.dispatchControlStream(\n          releaseOwnership(id), type.value(), consumed);\n      return HandleStreamResult::DISPATCHED;\n    }\n    case hq::UnidirectionalStreamType::PUSH: {\n      // ingress push streams are not allowed on the server\n      if (direction_ == proxygen::TransportDirection::DOWNSTREAM) {\n        return HandleStreamResult::REJECT;\n      }\n      // Try to read the push id from the stream\n      auto pushId = quic::follyutils::decodeQuicInteger(cursor);\n      // If successfully read the push id, call callback\n      // which will reassign the peek callback\n      // Otherwise, continue using this callback\n      if (pushId) {\n        consumed += pushId->second;\n        callback_.dispatchPushStream(\n            releaseOwnership(id), pushId->first, consumed);\n        return HandleStreamResult::DISPATCHED;\n      } else {\n        return HandleStreamResult::PENDING;\n      }\n    }\n    case hq::UnidirectionalStreamType::WEBTRANSPORT: {\n      // Try to read the session id from the stream\n      auto sessionID = quic::follyutils::decodeQuicInteger(cursor);\n      // If successfully read the session id, call sink\n      // which will reassign the peek callback\n      // Otherwise, continue using this callback\n      if (sessionID) {\n        consumed += sessionID->second;\n        callback_.dispatchUniWTStream(\n            releaseOwnership(id), sessionID->first, consumed);\n        return HandleStreamResult::DISPATCHED;\n      } else {\n        return HandleStreamResult::PENDING;\n      }\n    }\n    case hq::UnidirectionalStreamType::GREASE:\n      VLOG(4) << \"Hey, a grease stream id=\" << id;\n      break;\n    default:\n      LOG(ERROR) << \"Unrecognized type=\" << folly::to_underlying(*type);\n  }\n  return HandleStreamResult::REJECT;\n}\n\nHQStreamDispatcherBase::HandleStreamResult HQBidiStreamDispatcher::handleStream(\n    quic::StreamId id,\n    folly::io::Cursor& cursor,\n    uint64_t preface,\n    size_t consumed) {\n  auto type = callback_.parseBidiStreamPreface(preface);\n\n  if (!type) {\n    // Failed to identify the preface,\n    return HandleStreamResult::REJECT;\n  }\n\n  switch (type.value()) {\n    case hq::BidirectionalStreamType::REQUEST:\n      callback_.dispatchRequestStream(releaseOwnership(id));\n      return HandleStreamResult::DISPATCHED;\n    case hq::BidirectionalStreamType::WEBTRANSPORT: {\n      // Try to read the session id from the stream\n      auto sessionID = quic::follyutils::decodeQuicInteger(cursor);\n      // If successfully read the session id, call sink\n      // which will reassign the peek callback\n      // Otherwise, continue using this callback\n      if (sessionID) {\n        consumed += sessionID->second;\n        callback_.dispatchBidiWTStream(\n            releaseOwnership(id), sessionID->first, consumed);\n        return HandleStreamResult::DISPATCHED;\n      } else {\n        return HandleStreamResult::PENDING;\n      }\n    }\n    default: {\n      LOG(ERROR) << \"Unrecognized type=\" << static_cast<uint64_t>(type.value());\n    }\n  }\n  return HandleStreamResult::REJECT;\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/HQStreamDispatcher.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <quic/api/QuicSocket.h>\n\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n\n#include <folly/io/async/EventBase.h>\n\nnamespace proxygen {\n\n// Base class for the unidirectional stream callbacks\n// holds the session and defines the shared error messages\nclass HQStreamDispatcherBase : public quic::QuicSocket::PeekCallback {\n public:\n  // Receiver interface for the dispatched callbacks\n  struct CallbackBase {\n    using ReadError = quic::QuicError;\n    // Avoid pulling dependent names into ostensibly innocent templates...\n    using PeekData = folly::Range<quic::QuicSocket::PeekIterator>;\n\n    [[nodiscard]] virtual folly::EventBase* getEventBase() const = 0;\n\n    [[nodiscard]] virtual std::chrono::milliseconds getDispatchTimeout()\n        const = 0;\n\n    // Called by the dispatcher when a stream can not be recognized\n    virtual void rejectStream(quic::StreamId /* id */) = 0;\n\n   protected:\n    virtual ~CallbackBase() = default;\n  }; // Callback\n\n  explicit HQStreamDispatcherBase(CallbackBase& callback,\n                                  proxygen::TransportDirection direction);\n\n  ~HQStreamDispatcherBase() override = default;\n\n  void onDataAvailable(\n      quic::StreamId /* id */,\n      const CallbackBase::PeekData& /* data */) noexcept override;\n\n  void peekError(quic::StreamId /* id */, quic::QuicError\n                 /* error */) noexcept override;\n\n  // Take the temporary ownership of the stream.\n  // The ownership is released when the stream is passed\n  // to the callback\n  void takeTemporaryOwnership(quic::StreamId id) {\n    auto res = pendingStreams_.emplace(std::piecewise_construct,\n                                       std::forward_as_tuple(id),\n                                       std::forward_as_tuple(*this, id));\n    callback_.getEventBase()->timer().scheduleTimeout(\n        &res.first->second, callback_.getDispatchTimeout());\n  }\n\n  bool hasOwnership(quic::StreamId id) const {\n    return pendingStreams_.count(id) > 0;\n  }\n\n  quic::StreamId releaseOwnership(quic::StreamId id) {\n    LOG_IF(DFATAL, !hasOwnership(id))\n        << \"Can not release ownership on unowned streamID=\" << id;\n    bool found = pendingStreams_.erase(id);\n    LOG_IF(DFATAL, !found) << \"Inconstency detected streamID=\" << id;\n    return id;\n  }\n\n  size_t numberOfStreams() const {\n    return pendingStreams_.size();\n  }\n\n  void invokeOnPendingStreamIDs(const std::function<void(quic::StreamId)>& fn) {\n    for (auto& pendingStream : pendingStreams_) {\n      fn(pendingStream.first);\n    }\n  }\n\n  void dispatchTimeoutExpired(quic::StreamId id) {\n    callback_.rejectStream(releaseOwnership(id));\n  }\n\n  void cleanup() {\n    for (auto& pendingStream : pendingStreams_) {\n      callback_.rejectStream(pendingStream.first);\n    }\n    pendingStreams_.clear();\n  }\n\n private:\n  class DispatchTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit DispatchTimeout(HQStreamDispatcherBase& dispatcher,\n                             quic::StreamId id)\n        : dispatcher_(dispatcher), id_(id) {\n    }\n\n    ~DispatchTimeout() override = default;\n    void timeoutExpired() noexcept override {\n      dispatcher_.dispatchTimeoutExpired(id_);\n    }\n\n   private:\n    HQStreamDispatcherBase& dispatcher_;\n    quic::StreamId id_;\n  };\n\n  std::unordered_map<quic::StreamId, DispatchTimeout> pendingStreams_;\n\n protected:\n  enum class HandleStreamResult { DISPATCHED, REJECT, PENDING };\n  virtual HandleStreamResult handleStream(quic::StreamId id,\n                                          folly::io::Cursor& cursor,\n                                          uint64_t preface,\n                                          size_t consumed) = 0;\n\n  CallbackBase& callback_;\n  proxygen::TransportDirection direction_;\n};\n\nclass HQUniStreamDispatcher : public HQStreamDispatcherBase {\n public:\n  struct Callback : public HQStreamDispatcherBase::CallbackBase {\n    ~Callback() override = default;\n\n    // Called by the dispatcher to identify a stream preface\n    virtual folly::Optional<hq::UnidirectionalStreamType> parseUniStreamPreface(\n        uint64_t preface) = 0;\n\n    // Called by the dispatcher when a correct *peek* callback is identified\n    // for the stream id.\n    virtual void dispatchControlStream(quic::StreamId /* id */,\n                                       hq::UnidirectionalStreamType /* type */,\n                                       size_t /* to consume */) = 0;\n\n    // Called by the dispatcher when a push stream is identified by\n    // the dispatcher.\n    virtual void dispatchPushStream(quic::StreamId /* streamId */,\n                                    hq::PushId /* pushId */,\n                                    size_t /* to consume */) = 0;\n    virtual void dispatchUniWTStream(quic::StreamId /* streamId */,\n                                     quic::StreamId /* sessionId */,\n                                     size_t /* to consume */) = 0;\n  };\n\n  HQUniStreamDispatcher(Callback& callback,\n                        proxygen::TransportDirection direction)\n      : HQStreamDispatcherBase(callback, direction), callback_(callback) {\n  }\n\n private:\n  HandleStreamResult handleStream(quic::StreamId id,\n                                  folly::io::Cursor& cursor,\n                                  uint64_t preface,\n                                  size_t consumed) override;\n\n  Callback& callback_;\n};\n\nclass HQBidiStreamDispatcher : public HQStreamDispatcherBase {\n public:\n  struct Callback : public HQStreamDispatcherBase::CallbackBase {\n    ~Callback() override = default;\n\n    // Called by the dispatcher to identify a stream preface\n    virtual folly::Optional<hq::BidirectionalStreamType> parseBidiStreamPreface(\n        uint64_t preface) = 0;\n\n    virtual void dispatchRequestStream(quic::StreamId /* streamId */) = 0;\n\n    virtual void dispatchBidiWTStream(quic::StreamId /* streamId */,\n                                      quic::StreamId /* sessionId */,\n                                      size_t /* to consume */) = 0;\n  };\n\n  HQBidiStreamDispatcher(Callback& callback,\n                         proxygen::TransportDirection direction)\n      : HQStreamDispatcherBase(callback, direction), callback_(callback) {\n  }\n\n private:\n  HandleStreamResult handleStream(quic::StreamId id,\n                                  folly::io::Cursor& cursor,\n                                  uint64_t preface,\n                                  size_t consumed) override;\n  Callback& callback_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQUpstreamSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\nnamespace proxygen {\n\nHQUpstreamSession::~HQUpstreamSession() {\n  CHECK_EQ(getNumStreams(), 0);\n}\n\nvoid HQUpstreamSession::startNow() {\n  HQSession::startNow();\n  if (connectCb_ && connectTimeoutMs_.count() > 0) {\n    // Start a timer in case the connection takes too long.\n    getEventBase()->timer().scheduleTimeout(&connectTimeout_,\n                                            connectTimeoutMs_);\n  }\n}\n\nvoid HQUpstreamSession::connectTimeoutExpired() noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this << \": connection failed\";\n  if (connectCb_) {\n    onConnectionError(quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                                      \"connect timeout\"));\n  }\n}\n\nvoid HQUpstreamSession::onTransportReady() noexcept {\n  HQUpstreamSession::DestructorGuard dg(this);\n  if (!HQSession::onTransportReadyCommon()) {\n    // Something went wrong in onTransportReady, e.g. the ALPN is not supported\n    return;\n  }\n  connectSuccess();\n}\n\nvoid HQUpstreamSession::onFirstPeerPacketProcessed() noexcept {\n  HQUpstreamSession::DestructorGuard dg(this);\n  if (connectCb_) {\n    connectCb_->onFirstPeerPacketProcessed();\n  }\n}\n\nvoid HQUpstreamSession::connectSuccess() noexcept {\n  HQUpstreamSession::DestructorGuard dg(this);\n  if (connectCb_) {\n    connectCb_->connectSuccess();\n  }\n  if (connCbState_ == ConnCallbackState::REPLAY_SAFE) {\n    handleReplaySafe();\n    connCbState_ = ConnCallbackState::DONE;\n  } else {\n    connCbState_ = ConnCallbackState::CONNECT_SUCCESS;\n  }\n}\n\nvoid HQUpstreamSession::onReplaySafe() noexcept {\n  HQUpstreamSession::DestructorGuard dg(this);\n  if (connCbState_ == ConnCallbackState::CONNECT_SUCCESS) {\n    handleReplaySafe();\n    connCbState_ = ConnCallbackState::DONE;\n  } else {\n    connCbState_ = ConnCallbackState::REPLAY_SAFE;\n  }\n}\n\nvoid HQUpstreamSession::handleReplaySafe() noexcept {\n  HQSession::onReplaySafe();\n  // In the case that zero rtt, onTransportReady is almost called\n  // immediately without proof of network reachability, and onReplaySafe is\n  // expected to be called in 1 rtt time (if success).\n  if (connectCb_) {\n    auto cb = connectCb_;\n    connectCb_ = nullptr;\n    connectTimeout_.cancelTimeout();\n    cb->onReplaySafe();\n  }\n}\n\nvoid HQUpstreamSession::onConnectionEnd() noexcept {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n\n  HQSession::DestructorGuard dg(this);\n  if (connectCb_) {\n    onConnectionSetupErrorHandler(quic::QuicError(\n        quic::LocalErrorCode::CONNECT_FAILED, \"session destroyed\"));\n  }\n  HQSession::onConnectionEnd();\n}\n\nvoid HQUpstreamSession::onConnectionSetupErrorHandler(\n    quic::QuicError code) noexcept {\n  // For an upstream connection, any error before onTransportReady gets\n  // notified as a connect error.\n  if (connectCb_) {\n    HQSession::DestructorGuard dg(this);\n    auto cb = connectCb_;\n    connectCb_ = nullptr;\n    cb->connectError(std::move(code));\n    connectTimeout_.cancelTimeout();\n  }\n}\n\nbool HQUpstreamSession::isDetachable(bool checkSocket) const {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  // TODO: deal with control streams in h2q\n  if (checkSocket && sock_ && !sock_->isDetachable()) {\n    return false;\n  }\n  return getNumOutgoingStreams() == 0 && getNumIncomingStreams() == 0;\n}\n\nvoid HQUpstreamSession::attachThreadLocals(\n    folly::EventBase* eventBase,\n    std::shared_ptr<const folly::SSLContext>,\n    const WheelTimerInstance& timeout,\n    HTTPSessionStats* stats,\n    FilterIteratorFn fn,\n    HeaderCodec::Stats* headerCodecStats,\n    HTTPSessionController* controller) {\n  // TODO: deal with control streams in h2q\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  setController(controller);\n  setSessionStats(stats);\n  auto qEvbWrapper = std::make_shared<quic::FollyQuicEventBase>(eventBase);\n  if (sock_) {\n    sock_->attachEventBase(std::move(qEvbWrapper));\n  }\n  codec_.foreach (fn);\n  setHeaderCodecStats(headerCodecStats);\n  getEventBase()->runInLoop(this);\n  // The caller MUST re-add the connection to a new connection manager.\n}\n\nvoid HQUpstreamSession::detachThreadLocals(bool) {\n  VLOG(4) << __func__ << \" sess=\" << *this;\n  // TODO: deal with control streams in h2q\n  CHECK_EQ(getNumOutgoingStreams(), 0);\n  cancelLoopCallback();\n\n  // TODO: Pause reads and invoke infocallback\n  // pauseReadsImpl();\n  if (sock_) {\n    sock_->detachEventBase();\n  }\n\n  setController(nullptr);\n  setSessionStats(nullptr);\n  // The codec filters *shouldn't* be accessible while the socket is detached,\n  // I hope\n  setHeaderCodecStats(nullptr);\n  auto cm = getConnectionManager();\n  if (cm) {\n    cm->removeConnection(this);\n  }\n}\n\nbool HQUpstreamSession::tryBindIngressStreamToTxn(\n    quic::StreamId streamId,\n    hq::PushId pushId,\n    HQIngressPushStream* pushStream) {\n  // lookup pending nascent stream id\n  CHECK(pushStream);\n\n  VLOG(4) << __func__ << \" attempting to bind streamID=\" << streamId\n          << \" to pushID=\" << pushId;\n  pushStream->bindTo(streamId);\n\n#if DEBUG\n  // Check postconditions - the ingress push stream\n  // should own both the push id and the stream id.\n  // No nascent stream should own the stream id\n  auto streamById = findIngressPushStream(streamId);\n  auto streamByPushId = findIngressPushStreamByPushId(pushId);\n\n  DCHECK_EQ(streamId, pushStream->getIngressStreamId());\n  DCHECK(streamById) << \"Ingress stream must be bound to the streamID=\"\n                     << streamId;\n  DCHECK(streamByPushId) << \"Ingress stream must be found by the pushID=\"\n                         << pushId;\n  DCHECK_EQ(streamById, streamByPushId) << \"Must be same stream\";\n#endif\n\n  VLOG(4) << __func__ << \" successfully bound streamID=\" << streamId\n          << \" to pushID=\" << pushId;\n  return true;\n}\n\n// Called when we receive a push promise\nHQUpstreamSession::HQStreamTransportBase*\nHQUpstreamSession::createIngressPushStream(HTTPCodec::StreamID parentId,\n                                           hq::PushId pushId) {\n\n  // Check that a stream with this ID has not been created yet\n  DCHECK(!findIngressPushStreamByPushId(pushId))\n      << \"Ingress stream with this push ID already exists pushID=\" << pushId;\n\n  auto matchPair = ingressPushStreams_.emplace(\n      std::piecewise_construct,\n      std::forward_as_tuple(pushId),\n      std::forward_as_tuple(\n          *this,\n          pushId,\n          parentId,\n          getNumTxnServed(),\n          WheelTimerInstance(transactionsTimeout_, getEventBase())));\n\n  CHECK(matchPair.second) << \"Emplacement failed, despite earlier \"\n                             \"existence check.\";\n\n  auto newIngressPushStream = &matchPair.first->second;\n\n  // If there is a nascent stream ready to be bound to the newly\n  // created ingress stream, do it now.\n  bool bound = false;\n  auto res = pushIdToStreamId_.find(pushId);\n  if (res == pushIdToStreamId_.end()) {\n    VLOG(4)\n        << __func__ << \" pushID=\" << pushId\n        << \" not found in the lookup table, size=\" << pushIdToStreamId_.size();\n  } else {\n    bound =\n        tryBindIngressStreamToTxn(res->second, pushId, newIngressPushStream);\n  }\n\n  VLOG(4) << \"Successfully created new ingress push stream\"\n          << \" pushID=\" << pushId << \" parentStreamID=\" << parentId\n          << \" bound=\" << bound << \" streamID=\"\n          << (bound ? newIngressPushStream->getIngressStreamId()\n                    : static_cast<unsigned long>(-1));\n\n  return newIngressPushStream;\n}\n\nHQSession::HQStreamTransportBase* HQUpstreamSession::findPushStream(\n    quic::StreamId streamId) {\n  return findIngressPushStream(streamId);\n}\n\nHQUpstreamSession::HQIngressPushStream* FOLLY_NULLABLE\nHQUpstreamSession::findIngressPushStream(quic::StreamId streamId) {\n  auto res = streamIdToPushId_.find(streamId);\n  if (res == streamIdToPushId_.end()) {\n    return nullptr;\n  } else {\n    return findIngressPushStreamByPushId(res->second);\n  }\n}\n\nHQUpstreamSession::HQIngressPushStream* FOLLY_NULLABLE\nHQUpstreamSession::findIngressPushStreamByPushId(hq::PushId pushId) {\n  VLOG(4) << __func__ << \" looking up ingress push stream by pushID=\" << pushId;\n  auto it = ingressPushStreams_.find(pushId);\n  if (it == ingressPushStreams_.end()) {\n    return nullptr;\n  } else {\n    return &it->second;\n  }\n}\n\nbool HQUpstreamSession::erasePushStream(quic::StreamId streamId) {\n  auto res = streamIdToPushId_.find(streamId);\n  if (res != streamIdToPushId_.end()) {\n    auto pushId = res->second;\n    // Ingress push stream may be using the push id\n    // erase it as well if present\n    ingressPushStreams_.erase(pushId);\n\n    // Unconditionally erase the lookup entry tables\n    streamIdToPushId_.erase(res);\n    pushIdToStreamId_.erase(pushId);\n    return true;\n  }\n  return false;\n}\n\nuint32_t HQUpstreamSession::numberOfIngressPushStreams() const {\n  return ingressPushStreams_.size();\n}\n\nvoid HQUpstreamSession::dispatchPushStream(quic::StreamId pushStreamId,\n                                           hq::PushId pushId,\n                                           size_t toConsume) {\n  VLOG(4) << __func__ << \" streamID=\" << pushStreamId << \" pushId=\" << pushId;\n\n  // TODO: if/when we support client goaway, reject stream if\n  // pushId >= minUnseenIncomingPushId_ after the GOAWAY is sent\n  minUnseenIncomingPushId_ = std::max(minUnseenIncomingPushId_, pushId);\n  DCHECK_GT(toConsume, 0);\n\n  bool eom = false;\n  if (serverPushLifecycleCb_) {\n    serverPushLifecycleCb_->onNascentPushStreamBegin(pushStreamId, eom);\n  }\n\n  auto consumeRes = sock_->consume(pushStreamId, toConsume);\n  CHECK(!consumeRes.hasError())\n      << \"Unexpected error \" << consumeRes.error() << \" while consuming \"\n      << toConsume << \" bytes from stream=\" << pushStreamId\n      << \" pushId=\" << pushId;\n\n  // Replace the peek callback with a read callback and pause the read callback\n  sock_->setReadCallback(pushStreamId, this);\n  sock_->setPeekCallback(pushStreamId, nullptr);\n  sock_->pauseRead(pushStreamId);\n\n  // Increment the sequence no to account for the new transport-like stream\n  incrementSeqNo();\n\n  pushIdToStreamId_.emplace(pushId, pushStreamId);\n  streamIdToPushId_.emplace(pushStreamId, pushId);\n\n  VLOG(4) << __func__ << \" assigned lookup from pushID=\" << pushId\n          << \" to streamID=\" << pushStreamId;\n\n  // We have successfully read the push id. Notify the testing callbacks\n  if (serverPushLifecycleCb_) {\n    serverPushLifecycleCb_->onNascentPushStream(pushStreamId, pushId, eom);\n  }\n\n  // If the transaction for the incoming push stream has been created\n  // already, bind the new stream to the transaction\n  auto ingressPushStream = findIngressPushStreamByPushId(pushId);\n\n  if (ingressPushStream) {\n    auto bound =\n        tryBindIngressStreamToTxn(pushStreamId, pushId, ingressPushStream);\n    VLOG(4) << __func__ << \" bound=\" << bound << \" pushID=\" << pushId\n            << \" pushStreamID=\" << pushStreamId << \" to txn \";\n  }\n}\n\nvoid HQUpstreamSession::HQIngressPushStream::bindTo(quic::StreamId streamId) {\n  // Ensure the nascent push stream is in correct state\n  // and that its push id matches this stream's push id\n  DCHECK(txn_.getAssocTxnId().has_value());\n  VLOG(4) << __func__ << \" Binding streamID=\" << streamId\n          << \" to txn=\" << txn_.getID();\n#if DEBUG\n  // will throw bad-cast\n  HQUpstreamSession& session = dynamic_cast<HQUpstreamSession&>(session_);\n#else\n  auto& session = static_cast<HQUpstreamSession&>(session_);\n#endif\n  // Initialize this stream's codec with the id of the transport stream\n  auto codec = session.createCodec(streamId);\n  initCodec(std::move(codec), __func__);\n  DCHECK_EQ(*codecStreamId_, streamId);\n\n  // Now that the codec is initialized, set the stream ID\n  // of the push stream\n  setIngressStreamId(streamId);\n  DCHECK_EQ(getIngressStreamId(), streamId);\n\n  // Enable ingress on this stream. Read callback for the stream's\n  // id will be transferred to the HQSession\n  initIngress(__func__);\n\n  // Re-enable reads\n  session.resumeReadsForPushStream(streamId);\n\n  // Notify testing callbacks that a full push transaction\n  // has been successfully initialized\n  if (session.serverPushLifecycleCb_) {\n    session.serverPushLifecycleCb_->onPushedTxn(&txn_,\n                                                streamId,\n                                                getPushId(),\n                                                txn_.getAssocTxnId().value(),\n                                                false /* eof */);\n  }\n}\n\n// This can only be unbound in that it has not received a stream ID yet\nvoid HQUpstreamSession::eraseUnboundStream(HQStreamTransportBase* hqStream) {\n  auto hqPushIngressStream = dynamic_cast<HQIngressPushStream*>(hqStream);\n  CHECK(hqPushIngressStream)\n      << \"Only HQIngressPushStream streams are allowed to be non-bound\";\n  // This is what makes it unbound, it also cannot be in the map\n  DCHECK(!hqStream->hasIngressStreamId());\n  auto pushId = hqPushIngressStream->getPushId();\n  DCHECK(pushIdToStreamId_.find(pushId) == pushIdToStreamId_.end());\n  ingressPushStreams_.erase(pushId);\n}\n\nvoid HQUpstreamSession::cleanupUnboundPushStreams(\n    std::vector<quic::StreamId>& streamsToCleanup) {\n  for (auto& it : streamIdToPushId_) {\n    auto streamId = it.first;\n    auto pushId = it.second;\n    if (!ingressPushStreams_.contains(pushId)) {\n      streamsToCleanup.push_back(streamId);\n    }\n  }\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HQUpstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <proxygen/lib/http/session/HQSession.h>\n\n#include <folly/io/async/HHWheelTimer.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n\nnamespace proxygen {\n\nclass HQUpstreamSession : public HQSession {\n  class ConnectTimeout;\n\n  enum class ConnCallbackState { NONE, CONNECT_SUCCESS, REPLAY_SAFE, DONE };\n\n public:\n  HQUpstreamSession(const std::chrono::milliseconds transactionsTimeout,\n                    const std::chrono::milliseconds connectTimeoutMs,\n                    HTTPSessionController* controller,\n                    const wangle::TransportInfo& tinfo,\n                    InfoCallback* sessionInfoCb)\n      : HQSession(transactionsTimeout,\n                  controller,\n                  proxygen::TransportDirection::UPSTREAM,\n                  tinfo,\n                  sessionInfoCb),\n        connectTimeoutMs_(connectTimeoutMs),\n        connectTimeout_(*this) {\n  }\n\n  void setConnectCallback(ConnectCallback* connectCb) noexcept {\n    connectCb_ = connectCb;\n  }\n\n  void connectSuccess() noexcept override;\n\n  /**\n   * Returns true if the underlying transport has completed full handshake.\n   */\n  bool isReplaySafe() const override {\n    return sock_ ? sock_->replaySafe() : false;\n  }\n\n  void onConnectionEnd() noexcept override;\n\n  void startNow() override;\n\n  void onTransportReady() noexcept override;\n\n  void onReplaySafe() noexcept override;\n\n  void onFirstPeerPacketProcessed() noexcept override;\n\n  void handleReplaySafe() noexcept;\n\n  HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* /* txn */) override {\n    // No special handler for upstream requests that time out\n    return nullptr;\n  }\n\n  void setupOnHeadersComplete(HTTPTransaction* /* txn */,\n                              HTTPMessage* /* msg */) override {\n  }\n\n  void onConnectionSetupErrorHandler(quic::QuicError code) noexcept override;\n\n  bool isDetachable(bool checkSocket) const override;\n\n  void attachThreadLocals(folly::EventBase*,\n                          std::shared_ptr<const folly::SSLContext>,\n                          const WheelTimerInstance&,\n                          HTTPSessionStats*,\n                          FilterIteratorFn,\n                          HeaderCodec::Stats*,\n                          HTTPSessionController*) override;\n\n  void detachThreadLocals(bool) override;\n\n  /**\n   * Returns true iff a new outgoing transaction can be made on this session\n   */\n  bool supportsMoreTransactions() const override {\n    return sock_ && sock_->getNumOpenableBidirectionalStreams() &&\n           HTTPSessionBase::supportsMoreTransactions();\n  }\n\n  uint32_t getNumOutgoingStreams() const override {\n    // need transport API\n    return static_cast<uint32_t>(streams_.size());\n  }\n\n  uint32_t getNumIncomingStreams() const override {\n    // need transport API\n    return static_cast<uint32_t>(numberOfIngressPushStreams());\n  }\n\n protected:\n  ~HQUpstreamSession() override;\n\n private:\n  void connectTimeoutExpired() noexcept;\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4250) // inherits 'proxygen::detail::..' via dominance\n#endif\n\n  /**\n   * Client-side representation of a push stream.\n   * Does not support egress operations.\n   */\n  class HQIngressPushStream\n      : public detail::singlestream::SSIngress\n      , public HQSession::HQStreamTransportBase {\n   public:\n    HQIngressPushStream(HQSession& session,\n                        hq::PushId pushId,\n                        folly::Optional<HTTPCodec::StreamID> parentTxnId,\n                        uint32_t seqNo,\n                        const WheelTimerInstance& timeout,\n                        HTTPSessionStats* stats = nullptr,\n                        http2::PriorityUpdate priority = hqDefaultPriority)\n        : detail::singlestream::SSIngress(folly::none),\n          HQStreamTransportBase(session,\n                                TransportDirection::UPSTREAM,\n                                static_cast<HTTPCodec::StreamID>(pushId),\n                                seqNo,\n                                timeout,\n                                stats,\n                                priority,\n                                parentTxnId,\n                                hq::UnidirectionalStreamType::PUSH),\n          pushId_(pushId) {\n      // Ingress push streams are not initialized\n      // until after the nascent push stream\n      // has been received\n      // notify the testing callbacks that a half-opened push transaction\n      // has been created\n\n      // NOTE: change the API to avoid accepting parent txn ids\n      // as optional\n      CHECK(parentTxnId.has_value());\n      auto cb = ((HQUpstreamSession&)session_).serverPushLifecycleCb_;\n      if (cb) {\n        cb->onHalfOpenPushedTxn(&txn_, pushId, *parentTxnId, false);\n      }\n    }\n\n    // Bind this stream to a transport stream\n    void bindTo(quic::StreamId transportStreamId);\n\n    void onPushMessageBegin(HTTPCodec::StreamID pushId,\n                            HTTPCodec::StreamID parentTxnId,\n                            HTTPMessage* /* msg */) override {\n      LOG(ERROR) << \"Push promise on push stream\" << \" txn=\" << txn_\n                 << \" pushID=\" << pushId << \" parentTxnId=\" << parentTxnId;\n      session_.dropConnectionAsync(\n          quic::QuicError(HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED,\n                          \"Push promise on push stream\"),\n          kErrorConnection);\n    }\n\n    // Abort procedure that is specific to ingress push streams.\n    size_t sendAbort(HTTPTransaction* txn,\n                     ErrorCode errorCode) noexcept override {\n      // TBD: send \"cancel push\" here.\n\n      return sendAbortImpl(\n          toHTTP3ErrorCode(errorCode),\n          folly::to<std::string>(\"Application aborts pushed txn,\"\n                                 \" errorCode=\",\n                                 getErrorCodeString(errorCode),\n                                 \" pushID=\",\n                                 getPushId(),\n                                 \" txn=\",\n                                 txn->getID(),\n                                 \" hasIngressStream=\",\n                                 hasIngressStreamId()));\n    }\n\n    hq::PushId getPushId() const {\n      return pushId_;\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n        uint64_t /* maxData */) override {\n      // Push streams are ingress-only, so sendMaxData is not applicable\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n\n   private:\n    hq::PushId pushId_; // The push id in context of which this stream is\n                        // received\n  }; // HQIngressPushStream\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n  // Find an ingress push stream\n  HQIngressPushStream* findIngressPushStream(quic::StreamId);\n  HQIngressPushStream* findIngressPushStreamByPushId(hq::PushId);\n\n  uint32_t numberOfIngressPushStreams() const;\n\n  /**\n   * Attempt to bind an ingress push stream object (which has the txn)\n   * to a nascent stream (which has the transport/codec).\n   * returns true if binding was successful\n   */\n  bool tryBindIngressStreamToTxn(quic::StreamId streamID,\n                                 hq::PushId pushId,\n                                 HQIngressPushStream* pushStream = nullptr);\n\n  // Create ingress push stream.\n  HQStreamTransportBase* createIngressPushStream(quic::StreamId parentStreamId,\n                                                 hq::PushId pushId) override;\n\n  HQStreamTransportBase* findPushStream(quic::StreamId id) override;\n\n  void findPushStreams(\n      std::unordered_set<HQStreamTransportBase*>& streams) override {\n    for (auto& pstream : ingressPushStreams_) {\n      streams.insert(&pstream.second);\n    }\n  }\n\n  bool erasePushStream(quic::StreamId streamId) override;\n\n  void eraseUnboundStream(HQStreamTransportBase*) override;\n\n  void cleanupUnboundPushStreams(std::vector<quic::StreamId>&) override;\n\n  void dispatchPushStream(quic::StreamId /* pushStreamId */,\n                          hq::PushId /* pushId */,\n                          size_t /* toConsume */) override;\n\n  // Incoming server push streams. Since the incoming push streams\n  // can be created before transport stream\n  std::unordered_map<hq::PushId, HQIngressPushStream> ingressPushStreams_;\n\n  /**\n   * The Session notifies when an upstream 'connection' has been established\n   * and it is possible to start creating new streams / sending data\n   * The connection callback only expects either success or error\n   * so it gets automatically reset to nullptr after the first invocation\n   */\n  ConnectCallback* connectCb_{nullptr};\n\n  class ConnectTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit ConnectTimeout(HQUpstreamSession& session) : session_(session) {\n    }\n\n    void timeoutExpired() noexcept override {\n      session_.connectTimeoutExpired();\n    }\n\n   private:\n    HQUpstreamSession& session_;\n  };\n\n  std::chrono::milliseconds connectTimeoutMs_;\n  ConnectTimeout connectTimeout_;\n  ConnCallbackState connCbState_{ConnCallbackState::NONE};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTP2PriorityQueue.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTP2PriorityQueue.h>\n\nusing std::list;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nHTTP2PriorityQueue::Node* HTTP2PriorityQueue::nodeFromBaseNode(\n    HTTP2PriorityQueue::BaseNode* bnode) {\n  return\n#if DEBUG\n      CHECK_NOTNULL(dynamic_cast<HTTP2PriorityQueue::Node*>(bnode));\n#else\n      static_cast<HTTP2PriorityQueue::Node*>(bnode);\n#endif\n}\n\nuint32_t HTTP2PriorityQueue::kMaxRebuilds_ = 3;\nstd::chrono::milliseconds HTTP2PriorityQueue::kNodeLifetime_ =\n    std::chrono::seconds(30);\n\nHTTP2PriorityQueue::Node::Node(HTTP2PriorityQueue& queue,\n                               HTTP2PriorityQueue::Node* inParent,\n                               HTTPCodec::StreamID id,\n                               uint8_t weight,\n                               HTTPTransaction* txn)\n    : queue_(queue),\n      parent_(inParent),\n      id_(id),\n      weight_(weight + 1),\n      txn_(txn) {\n  auto result = queue_.nodes_.emplace(id_, this);\n  DCHECK(result.second);\n}\n\nHTTP2PriorityQueue::Node::~Node() {\n  if (!txn_) {\n    queue_.numVirtualNodes_--;\n  }\n  queue_.nodes_.erase(id_);\n}\n\n// Add a new node as a child of this node\nHTTP2PriorityQueue::Node* HTTP2PriorityQueue::Node::emplaceNode(\n    unique_ptr<HTTP2PriorityQueue::Node> node, bool exclusive) {\n  CHECK(!node->isEnqueued());\n  list<unique_ptr<Node>> children;\n  CHECK_NE(id_, node->id_) << \"Tried to create a loop in the tree\";\n  if (exclusive) {\n    // this->children become new node's children\n    std::swap(children, children_);\n    totalChildWeight_ = 0;\n    bool wasInEgressTree = inEgressTree();\n    totalEnqueuedWeight_ = 0;\n#ifndef NDEBUG\n    totalEnqueuedWeightCheck_ = 0;\n#endif\n    if (wasInEgressTree && !inEgressTree()) {\n      propagatePendingEgressClear(this);\n    }\n  }\n  auto res = addChild(std::move(node));\n  res->addChildren(std::move(children));\n  return res;\n}\n\nvoid HTTP2PriorityQueue::Node::addChildren(list<unique_ptr<Node>>&& children) {\n  list<unique_ptr<Node>> emptyChilden;\n  uint64_t totalEnqueuedWeight = 0;\n  for (auto& child : children) {\n    if (child->inEgressTree()) {\n      totalEnqueuedWeight += child->weight_;\n      child->parent_->removeEnqueuedChild(child.get());\n      CHECK(!child->enqueuedHook_.is_linked());\n      addEnqueuedChild(child.get());\n    } else {\n      CHECK(!child->enqueuedHook_.is_linked());\n    }\n    addChild(std::move(child));\n  }\n  std::swap(children, emptyChilden);\n  if (totalEnqueuedWeight > 0) {\n    if (!inEgressTree()) {\n      propagatePendingEgressSignal(this);\n    }\n    totalEnqueuedWeight_ += totalEnqueuedWeight;\n  }\n}\n\nHTTP2PriorityQueue::Node* HTTP2PriorityQueue::Node::addChild(\n    unique_ptr<HTTP2PriorityQueue::Node> child) {\n  CHECK_NE(id_, child->id_) << \"Tried to create a loop in the tree\";\n  child->parent_ = this;\n  totalChildWeight_ += child->weight_;\n  Node* raw = child.get();\n  raw->self_ = children_.insert(children_.end(), std::move(child));\n  cancelTimeout();\n  return raw;\n}\n\nunique_ptr<HTTP2PriorityQueue::Node> HTTP2PriorityQueue::Node::detachChild(\n    Node* node) {\n  CHECK(!node->isEnqueued());\n  totalChildWeight_ -= node->weight_;\n  auto it = node->self_;\n  auto res = std::move(*node->self_);\n  children_.erase(it);\n  node->parent_ = nullptr;\n  if (children_.empty() && !txn_ && !isPermanent_) {\n    queue_.scheduleNodeExpiration(this);\n  }\n  return res;\n}\n\nHTTP2PriorityQueue::Node* HTTP2PriorityQueue::Node::reparent(\n    HTTP2PriorityQueue::Node* newParent, bool exclusive) {\n  // Save enqueued_ and totalEnqueuedWeight_, clear them and restore\n  // after reparenting\n  bool wasInEgressTree = inEgressTree();\n  bool enqueued = enqueued_;\n  uint64_t totalEnqueuedWeight = totalEnqueuedWeight_;\n  totalEnqueuedWeight_ = 0;\n  enqueued_ = false;\n  if (wasInEgressTree) {\n    propagatePendingEgressClear(this);\n  }\n\n  auto self = parent_->detachChild(this);\n  (void)newParent->emplaceNode(std::move(self), exclusive);\n\n  // Restore state\n  enqueued_ = enqueued;\n  if (wasInEgressTree) {\n    propagatePendingEgressSignal(this);\n  }\n  totalEnqueuedWeight_ += totalEnqueuedWeight;\n\n  return this;\n}\n\n// Returns true if this is a descendant of node\nbool HTTP2PriorityQueue::Node::isDescendantOf(\n    HTTP2PriorityQueue::Node* node) const {\n  Node* cur = parent_;\n  while (cur) {\n    if (cur->id_ == node->id_) {\n      return true;\n    }\n    cur = cur->parent_;\n  }\n  return false;\n}\n\n// Here \"enqueued\" means enqueued or enqueued descendent - part of the\n// nextEgress computation.\n\nvoid HTTP2PriorityQueue::Node::addEnqueuedChild(\n    HTTP2PriorityQueue::Node* node) {\n  CHECK(!node->enqueuedHook_.is_linked());\n  enqueuedChildren_.push_back(*node);\n}\n\nvoid HTTP2PriorityQueue::Node::removeEnqueuedChild(\n    HTTP2PriorityQueue::Node* node) {\n  CHECK(node->enqueuedHook_.is_linked());\n  enqueuedChildren_.erase(enqueuedChildren_.iterator_to(*node));\n}\n\nvoid HTTP2PriorityQueue::Node::signalPendingEgress() {\n  enqueued_ = true;\n  propagatePendingEgressSignal(this);\n}\n\nvoid HTTP2PriorityQueue::Node::propagatePendingEgressSignal(\n    HTTP2PriorityQueue::Node* node) {\n  Node* parent = node->parent_;\n  bool stop = node->totalEnqueuedWeight_ > 0;\n  // Continue adding node->weight_ to parent_->totalEnqueuedWeight_ as\n  // long as node state changed from no-egress-in-subtree to\n  // egress-in-subtree\n  while (parent && !stop) {\n    stop = parent->inEgressTree();\n    parent->totalEnqueuedWeight_ += node->weight_;\n    parent->addEnqueuedChild(node);\n    node = parent;\n    parent = parent->parent_;\n  }\n}\n\nvoid HTTP2PriorityQueue::Node::clearPendingEgress() {\n  CHECK(enqueued_);\n  enqueued_ = false;\n  propagatePendingEgressClear(this);\n}\n\nvoid HTTP2PriorityQueue::Node::propagatePendingEgressClear(\n    HTTP2PriorityQueue::Node* node) {\n  Node* parent = node->parent_;\n  bool stop = node->inEgressTree();\n  // Continue subtracting node->weight_ from parent_->totalEnqueuedWeight_\n  // as long as node state changes from egress-in-subtree to\n  // no-egress-in-subtree\n  while (parent && !stop) {\n    CHECK_GE(parent->totalEnqueuedWeight_, node->weight_);\n    parent->totalEnqueuedWeight_ -= node->weight_;\n    parent->removeEnqueuedChild(node);\n    stop = parent->inEgressTree();\n    node = parent;\n    parent = parent->parent_;\n  }\n}\n\n// Set a new weight for this node\nvoid HTTP2PriorityQueue::Node::updateWeight(uint8_t weight) {\n  int16_t delta = weight - weight_ + 1;\n  weight_ = weight + 1;\n  parent_->totalChildWeight_ += delta;\n  if (inEgressTree()) {\n    parent_->totalEnqueuedWeight_ += delta;\n  }\n  refreshTimeout();\n}\n\n// Removes the node from the tree\nvoid HTTP2PriorityQueue::Node::removeFromTree() {\n  if (!children_.empty()) {\n    // update child weights so they sum to (approximately) this node's weight.\n    double r = double(weight_) / totalChildWeight_;\n    for (auto& child : children_) {\n      uint64_t newWeight = std::max(uint64_t(child->weight_ * r), uint64_t(1));\n      CHECK_LE(newWeight, 256);\n      child->updateWeight(uint8_t(newWeight) - 1);\n    }\n  }\n\n  CHECK(!isEnqueued());\n  if (inEgressTree()) {\n    // Gah this is tricky.\n    // The children of this node are moving to this node's parent.  We need the\n    // tree in a consistent state before calling addChildren, so mark the\n    // current node's totalEnqueuedWeight_ as 0 and propagate the clear upwards.\n    // addChildren will handle re-signalling egress.\n    totalEnqueuedWeight_ = 0;\n    propagatePendingEgressClear(this);\n  }\n\n  // move my children to my parent\n  parent_->addChildren(std::move(children_));\n  (void)parent_->detachChild(this);\n}\n\nbool HTTP2PriorityQueue::Node::iterate(\n    const std::function<bool(HTTPCodec::StreamID, HTTPTransaction*, double)>&\n        fn,\n    const std::function<bool()>& stopFn,\n    bool all) {\n  bool stop = false;\n  if (stopFn()) {\n    return true;\n  }\n#ifndef NDEBUG\n  CHECK_EQ(totalEnqueuedWeight_, totalEnqueuedWeightCheck_);\n#endif\n  if (parent_ /* exclude root */ && (all || isEnqueued())) {\n    stop = fn(id_, txn_, getRelativeWeight());\n  }\n  for (auto& child : children_) {\n    if (stop || stopFn()) {\n      return true;\n    }\n    stop = child->iterate(fn, stopFn, all);\n  }\n  return stop;\n}\n\nbool HTTP2PriorityQueue::Node::visitBFS(\n    double relativeParentWeight,\n    const std::function<bool(HTTP2PriorityQueue& queue,\n                             HTTPCodec::StreamID,\n                             HTTPTransaction*,\n                             double)>& fn,\n    bool all,\n    PendingList& pendingNodes,\n    bool enqueuedChildren) {\n  bool invoke = (parent_ != nullptr && (all || isEnqueued()));\n  auto relativeEnqueuedWeight = getRelativeEnqueuedWeight();\n\n#ifndef NDEBUG\n  CHECK_EQ(totalEnqueuedWeight_, totalEnqueuedWeightCheck_);\n#endif\n  // Add children when all==true, or for any not invoked node with\n  // pending children\n  if (all || (!invoke && totalEnqueuedWeight_ > 0)) {\n    double newRelWeight = relativeParentWeight * relativeEnqueuedWeight;\n    if (enqueuedChildren) {\n      for (auto child = enqueuedChildren_.begin();\n           child != enqueuedChildren_.end();\n           child++) {\n        pendingNodes.emplace_back(child->id_, &(*child), newRelWeight);\n      }\n    } else {\n      for (auto& child : children_) {\n        pendingNodes.emplace_back(child->id_, child.get(), newRelWeight);\n      }\n    }\n  }\n\n  // Invoke fn last in case it deletes this node\n  if (invoke &&\n      fn(queue_, id_, txn_, relativeParentWeight * relativeEnqueuedWeight)) {\n    return true;\n  }\n\n  return false;\n}\n\n#ifndef NDEBUG\nvoid HTTP2PriorityQueue::Node::updateEnqueuedWeight(bool activeNodes) {\n  totalEnqueuedWeightCheck_ = totalChildWeight_;\n  for (auto& child : children_) {\n    child->updateEnqueuedWeight(activeNodes);\n  }\n  if (activeNodes) {\n    if (totalEnqueuedWeightCheck_ == 0 && !isEnqueued()) {\n      // Must only be called with activeCount_ > 0, root cannot be dequeued\n      CHECK_NOTNULL(parent_)->totalEnqueuedWeightCheck_ -= weight_;\n    } else {\n      CHECK(parent_ == nullptr || enqueuedHook_.is_linked());\n    }\n  } else {\n    totalEnqueuedWeightCheck_ = 0;\n  }\n}\n#endif\n\nvoid HTTP2PriorityQueue::Node::dropPriorityNodes() {\n  for (auto it = children_.begin(); it != children_.end();) {\n    auto& child = *it++;\n    child->dropPriorityNodes();\n  }\n  if (!txn_ && !isPermanent_) {\n    removeFromTree();\n  }\n}\n\nvoid HTTP2PriorityQueue::Node::convertVirtualNode(HTTPTransaction* txn) {\n  CHECK(!txn_);\n  CHECK(!isPermanent_);\n  CHECK_GT(queue_.numVirtualNodes_, 0);\n  queue_.numVirtualNodes_--;\n  txn_ = txn;\n  cancelTimeout();\n}\n\nuint64_t HTTP2PriorityQueue::Node::calculateDepth(bool includeVirtual) const {\n  uint64_t depth = 0;\n  const Node* cur = this;\n  while (cur->getParent() != nullptr) {\n    if (cur->txn_ || includeVirtual) {\n      depth += 1;\n    }\n    cur = cur->getParent();\n  }\n  return depth;\n}\n\nvoid HTTP2PriorityQueue::Node::flattenSubtree() {\n  std::list<std::unique_ptr<Node>> oldChildren_;\n  // Move the old children to a temporary list\n  std::swap(oldChildren_, children_);\n  // Reparent the children\n  for (auto& child : oldChildren_) {\n    child->flattenSubtreeDFS(this);\n    addChildToNewSubtreeRoot(std::move(child), this);\n  }\n  // Update the weights\n  totalEnqueuedWeight_ = 0;\n#ifndef NDEBUG\n  totalEnqueuedWeightCheck_ = 0;\n#endif\n  totalChildWeight_ = 0;\n  std::for_each(children_.begin(),\n                children_.end(),\n                [this](const std::unique_ptr<Node>& child) {\n                  totalChildWeight_ += child->weight_;\n                  if (child->enqueued_) {\n                    totalEnqueuedWeight_ += child->weight_;\n#ifndef NDEBUG\n                    totalEnqueuedWeightCheck_ += child->weight_;\n#endif\n                  }\n                });\n}\n\nvoid HTTP2PriorityQueue::Node::flattenSubtreeDFS(Node* subtreeRoot) {\n  for (auto& child : children_) {\n    child->flattenSubtreeDFS(subtreeRoot);\n    addChildToNewSubtreeRoot(std::move(child), subtreeRoot);\n  }\n}\n\nvoid HTTP2PriorityQueue::Node::addChildToNewSubtreeRoot(\n    std::unique_ptr<Node> child, Node* subtreeRoot) {\n  child->children_.clear();\n  child->parent_ = subtreeRoot;\n  child->weight_ = kDefaultWeight;\n  child->totalChildWeight_ = 0;\n  child->totalEnqueuedWeight_ = 0;\n#ifndef NDEBUG\n  child->totalEnqueuedWeightCheck_ = 0;\n#endif\n  Node* raw = child.get();\n  raw->self_ = subtreeRoot->children_.insert(subtreeRoot->children_.end(),\n                                             std::move(child));\n}\n\n/// class HTTP2PriorityQueue\nvoid HTTP2PriorityQueue::attachThreadLocals(const WheelTimerInstance& timeout) {\n  timeout_ = timeout;\n}\n\nvoid HTTP2PriorityQueue::detachThreadLocals() {\n  // a bit harsh, we could cancel and reschedule the timeout\n  dropPriorityNodes();\n  timeout_ = WheelTimerInstance();\n}\n\nvoid HTTP2PriorityQueue::addOrUpdatePriorityNode(HTTPCodec::StreamID id,\n                                                 http2::PriorityUpdate pri) {\n  auto handle = find(id);\n  if (handle) {\n    // already added\n    CHECK(handle->getTransaction() == nullptr);\n    updatePriority(handle, pri);\n  } else {\n    // brand new\n    addTransaction(id, pri, nullptr, false /* not permanent */);\n  }\n}\n\nHTTP2PriorityQueue::Handle HTTP2PriorityQueue::addTransaction(\n    HTTPCodec::StreamID id,\n    http2::PriorityUpdate pri,\n    HTTPTransaction* txn,\n    bool permanent,\n    uint64_t* depth) {\n  CHECK_NE(id, rootNodeId_);\n  CHECK_NE(id, pri.streamDependency) << \"Tried to create a loop in the tree\";\n  CHECK(!txn || !permanent);\n  if (largestId_ && id <= *largestId_) {\n    Node* existingNode = find(id, depth);\n    if (existingNode) {\n      CHECK(!permanent);\n      existingNode->convertVirtualNode(CHECK_NOTNULL(txn));\n      updatePriority(existingNode, pri);\n      return existingNode;\n    }\n  } else {\n    largestId_ = id;\n  }\n  if (!txn) {\n    if (numVirtualNodes_ >= maxVirtualNodes_) {\n      return nullptr;\n    }\n    numVirtualNodes_++;\n  }\n\n  Node* parent = &root_;\n  if (depth) {\n    *depth = 1;\n  }\n  if (pri.streamDependency != rootNodeId_) {\n    Node* dep = find(pri.streamDependency, depth);\n    if (dep == nullptr) {\n      // specified a missing parent (timed out an idle node)?\n      VLOG(4) << \"assigning default priority to txn=\" << id;\n      // No point to try to instantiate one more virtual node\n      // if we already reached the virtual node limit\n      if (numVirtualNodes_ < maxVirtualNodes_) {\n        // The parent node hasn't arrived yet. For now setting\n        // its priority fields to default.\n        parent = nodeFromBaseNode(\n            addTransaction(pri.streamDependency,\n                           {.streamDependency = rootNodeId_,\n                            .exclusive = http2::DefaultPriority.exclusive,\n                            .weight = http2::DefaultPriority.weight},\n                           nullptr,\n                           permanent,\n                           depth));\n        if (depth) {\n          *depth += 1;\n        }\n      } else {\n        VLOG(4) << \"Virtual node limit reached, ignoring stream dependency \"\n                << pri.streamDependency << \" for new node ID \" << id;\n      }\n    } else {\n      parent = dep;\n      if (depth) {\n        *depth += 1;\n      }\n    }\n  }\n  VLOG(4) << \"Adding id=\" << id << \" with parent=\" << parent->getID()\n          << \" and weight=\" << ((uint16_t)pri.weight + 1);\n  auto node = std::make_unique<Node>(*this, parent, id, pri.weight, txn);\n  if (permanent) {\n    node->setPermanent();\n  } else if (!txn) {\n    scheduleNodeExpiration(node.get());\n  }\n  auto result = parent->emplaceNode(std::move(node), pri.exclusive);\n  pendingWeightChange_ = true;\n  return result;\n}\n\nHTTP2PriorityQueue::Handle HTTP2PriorityQueue::updatePriority(\n    HTTP2PriorityQueue::Handle handle,\n    http2::PriorityUpdate pri,\n    uint64_t* depth) {\n  Node* node = nodeFromBaseNode(handle);\n  pendingWeightChange_ = true;\n  VLOG(4) << \"Updating id=\" << node->getID()\n          << \" with parent=\" << pri.streamDependency\n          << \" and weight=\" << ((uint16_t)pri.weight + 1);\n  node->updateWeight(pri.weight);\n  CHECK_NE(pri.streamDependency, node->getID())\n      << \"Tried to create a loop in the tree\";\n  if (pri.streamDependency == node->parentID() && !pri.exclusive) {\n    // no move\n    if (depth) {\n      *depth = handle->calculateDepth();\n    }\n    return handle;\n  }\n\n  Node* newParent = find(pri.streamDependency);\n  if (!newParent) {\n    if (pri.streamDependency == rootNodeId_ ||\n        numVirtualNodes_ >= maxVirtualNodes_) {\n      newParent = &root_;\n    } else {\n      // allocate a virtual node for non-existing parent in my depenency tree\n      // then do normal priority processing\n      newParent = nodeFromBaseNode(\n          addTransaction(pri.streamDependency,\n                         {.streamDependency = rootNodeId_,\n                          .exclusive = http2::DefaultPriority.exclusive,\n                          .weight = http2::DefaultPriority.weight},\n                         nullptr,\n                         false));\n\n      VLOG(4) << \"updatePriority missing parent, creating virtual parent=\"\n              << newParent->getID() << \" for txn=\" << node->getID();\n    }\n  }\n\n  if (newParent->isDescendantOf(node)) {\n    newParent = newParent->reparent(node->getParent(), false);\n  }\n  node = node->reparent(newParent, pri.exclusive);\n  if (depth) {\n    *depth = node->calculateDepth();\n  }\n  return node;\n}\n\nvoid HTTP2PriorityQueue::removeTransaction(HTTP2PriorityQueue::Handle handle) {\n  Node* node = nodeFromBaseNode(handle);\n  pendingWeightChange_ = true;\n  // TODO: or require the node to do it?\n  if (node->isEnqueued()) {\n    clearPendingEgress(handle);\n  }\n  if (allowDanglingNodes() && numVirtualNodes_ < maxVirtualNodes_) {\n    node->clearTransaction();\n    numVirtualNodes_++;\n    scheduleNodeExpiration(node);\n  } else {\n    VLOG(5) << \"Deleting dangling node over max id=\" << node->getID();\n    node->removeFromTree();\n  }\n}\n\nvoid HTTP2PriorityQueue::signalPendingEgress(Handle handle) {\n  if (!handle->isEnqueued()) {\n    nodeFromBaseNode(handle)->signalPendingEgress();\n    activeCount_++;\n    pendingWeightChange_ = true;\n  }\n}\n\nvoid HTTP2PriorityQueue::clearPendingEgress(Handle handle) {\n  CHECK_GT(activeCount_, 0);\n  // clear does a CHECK on handle->isEnqueued()\n  nodeFromBaseNode(handle)->clearPendingEgress();\n  activeCount_--;\n  pendingWeightChange_ = true;\n}\n\nvoid HTTP2PriorityQueue::iterateBFS(\n    const std::function<bool(\n        HTTP2PriorityQueue&, HTTPCodec::StreamID, HTTPTransaction*, double)>&\n        fn,\n    const std::function<bool()>& stopFn,\n    bool all) {\n  Node::PendingList pendingNodes{{rootNodeId_, &root_, 1.0}};\n  Node::PendingList newPendingNodes;\n  bool stop = false;\n\n  updateEnqueuedWeight();\n  while (!stop && !stopFn() && !pendingNodes.empty()) {\n    CHECK(newPendingNodes.empty());\n    while (!stop && !pendingNodes.empty()) {\n      Node* node = findInternal(pendingNodes.front().id);\n      if (node) {\n        stop = node->visitBFS(pendingNodes.front().ratio,\n                              fn,\n                              all,\n                              newPendingNodes,\n                              false /* all children */);\n      }\n      pendingNodes.pop_front();\n    }\n    std::swap(pendingNodes, newPendingNodes);\n  }\n}\n\nbool HTTP2PriorityQueue::nextEgressResult(HTTP2PriorityQueue& queue,\n                                          HTTPCodec::StreamID,\n                                          HTTPTransaction* txn,\n                                          double r) {\n  queue.nextEgressResults_->emplace_back(txn, r);\n  return false;\n}\n\nvoid HTTP2PriorityQueue::nextEgress(\n    HTTP2PriorityQueue::NextEgressResult& result) {\n  struct WeightCmp {\n    bool operator()(const std::pair<HTTPTransaction*, double>& t1,\n                    const std::pair<HTTPTransaction*, double>& t2) {\n      return t1.second > t2.second;\n    }\n  };\n\n  result.reserve(activeCount_);\n  nextEgressResults_ = &result;\n\n  updateEnqueuedWeight();\n  Node::PendingList pendingNodes;\n  Node::PendingList pendingNodesTmp;\n  pendingNodes.emplace_back(rootNodeId_, &root_, 1.0);\n  bool stop = false;\n  do {\n    while (!stop && !pendingNodes.empty()) {\n      Node* node = pendingNodes.front().node;\n      if (node) {\n        stop = node->visitBFS(pendingNodes.front().ratio,\n                              nextEgressResult,\n                              false,\n                              pendingNodesTmp,\n                              true /* enqueued children */);\n      }\n      pendingNodes.pop_front();\n    }\n    std::swap(pendingNodes, pendingNodesTmp);\n  } while (!stop && !pendingNodes.empty());\n  std::sort(result.begin(), result.end(), WeightCmp());\n  nextEgressResults_ = nullptr;\n}\n\nHTTP2PriorityQueue::Node* HTTP2PriorityQueue::find(HTTPCodec::StreamID id,\n                                                   uint64_t* depth) {\n  if (id == rootNodeId_) {\n    return nullptr;\n  }\n  auto it = nodes_.find(id);\n  if (it == nodes_.end()) {\n    return nullptr;\n  }\n  if (depth) {\n    *depth = it->second->calculateDepth();\n  }\n  return it->second;\n}\n\nvoid HTTP2PriorityQueue::updateEnqueuedWeight() {\n#ifndef NDEBUG\n  if (pendingWeightChange_) {\n    root_.updateEnqueuedWeight(activeCount_ > 0);\n    pendingWeightChange_ = false;\n  }\n#endif\n}\n\n// Internal error handling\n\nvoid HTTP2PriorityQueue::rebuildTree() {\n  CHECK_LE(rebuildCount_ + 1, kMaxRebuilds_);\n  root_.flattenSubtree();\n  rebuildCount_++;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTP2PriorityQueue.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/IntrusiveList.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <proxygen/lib/http/codec/HTTP2Framer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n\n#include <deque>\n#include <list>\n#include <unordered_map>\n\nnamespace proxygen {\n\nclass HTTPTransaction;\n\nclass HTTP2PriorityQueueBase : public HTTPCodec::PriorityQueue {\n public:\n  class BaseNode {\n   public:\n    virtual ~BaseNode() = default;\n    virtual bool isEnqueued() const = 0;\n    [[nodiscard]] virtual uint64_t calculateDepth(\n        bool includeVirtual = true) const = 0;\n  };\n\n  using Handle = BaseNode*;\n\n  explicit HTTP2PriorityQueueBase(HTTPCodec::StreamID rootNodeId)\n      : rootNodeId_(rootNodeId) {\n  }\n\n  virtual Handle addTransaction(HTTPCodec::StreamID id,\n                                http2::PriorityUpdate pri,\n                                HTTPTransaction* txn,\n                                bool permanent = false,\n                                uint64_t* depth = nullptr) = 0;\n\n  // update the priority of an existing node\n  virtual Handle updatePriority(Handle handle,\n                                http2::PriorityUpdate pri,\n                                uint64_t* depth = nullptr) = 0;\n\n  // Remove the transaction from the priority tree\n  virtual void removeTransaction(Handle handle) = 0;\n\n  // Notify the queue when a transaction has egress\n  virtual void signalPendingEgress(Handle h) = 0;\n\n  // Notify the queue when a transaction no longer has egress\n  virtual void clearPendingEgress(Handle h) = 0;\n\n  HTTPCodec::StreamID getRootId() {\n    return rootNodeId_;\n  }\n\n protected:\n  HTTPCodec::StreamID rootNodeId_{0};\n};\n\nclass HTTP2PriorityQueue : public HTTP2PriorityQueueBase {\n\n private:\n  class Node;\n\n  static const size_t kNumBuckets = 100;\n\n public:\n  HTTP2PriorityQueue(HTTPCodec::StreamID rootNodeId = 0)\n      : HTTP2PriorityQueueBase(rootNodeId),\n        root_(*this, nullptr, rootNodeId, 1, nullptr) {\n    root_.setPermanent();\n  }\n\n  explicit HTTP2PriorityQueue(const WheelTimerInstance& timeout,\n                              HTTPCodec::StreamID rootNodeId = 0)\n      : HTTP2PriorityQueueBase(rootNodeId),\n        root_(*this, nullptr, rootNodeId, 1, nullptr),\n        timeout_(timeout) {\n    root_.setPermanent();\n  }\n\n  void attachThreadLocals(const WheelTimerInstance& timeout);\n\n  void detachThreadLocals();\n\n  void setMaxVirtualNodes(uint32_t maxVirtualNodes) {\n    maxVirtualNodes_ = maxVirtualNodes;\n  }\n\n  // Notify the queue when a transaction has egress\n  void signalPendingEgress(Handle h) override;\n\n  // Notify the queue when a transaction no longer has egress\n  void clearPendingEgress(Handle h) override;\n\n  void addPriorityNode(HTTPCodec::StreamID id,\n                       HTTPCodec::StreamID parent) override {\n    addTransaction(\n        id,\n        {.streamDependency = parent, .exclusive = false, .weight = 0},\n        nullptr,\n        true);\n  }\n\n  void addOrUpdatePriorityNode(HTTPCodec::StreamID id,\n                               http2::PriorityUpdate pri);\n\n  void dropPriorityNodes() {\n    root_.dropPriorityNodes();\n  }\n\n  // adds new transaction (possibly nullptr) to the priority tree\n  Handle addTransaction(HTTPCodec::StreamID id,\n                        http2::PriorityUpdate pri,\n                        HTTPTransaction* txn,\n                        bool permanent = false,\n                        uint64_t* depth = nullptr) override;\n\n  // update the priority of an existing node\n  Handle updatePriority(Handle handle,\n                        http2::PriorityUpdate pri,\n                        uint64_t* depth = nullptr) override;\n\n  // Remove the transaction from the priority tree\n  void removeTransaction(Handle handle) override;\n\n  // Returns true if there are no transaction with pending egress\n  bool empty() const {\n    return activeCount_ == 0;\n  }\n\n  // The number with pending egress\n  uint64_t numPendingEgress() const {\n    return activeCount_;\n  }\n\n  uint64_t numVirtualNodes() const {\n    return numVirtualNodes_;\n  }\n\n  void iterate(const std::function<\n                   bool(HTTPCodec::StreamID, HTTPTransaction*, double)>& fn,\n               const std::function<bool()>& stopFn,\n               bool all) {\n    updateEnqueuedWeight();\n    root_.iterate(fn, stopFn, all);\n  }\n\n  // stopFn is only evaluated once per level\n  void iterateBFS(\n      const std::function<bool(\n          HTTP2PriorityQueue&, HTTPCodec::StreamID, HTTPTransaction*, double)>&\n          fn,\n      const std::function<bool()>& stopFn,\n      bool all);\n\n  using NextEgressResult = std::vector<std::pair<HTTPTransaction*, double>>;\n\n  void nextEgress(NextEgressResult& result);\n\n  static void setNodeLifetime(std::chrono::milliseconds lifetime) {\n    kNodeLifetime_ = lifetime;\n  }\n\n  /// Error handling code\n  // Rebuilds tree by making all non-root nodes direct children of the root and\n  // weight reset to the default 16\n  void rebuildTree();\n  uint32_t getRebuildCount() const {\n    return rebuildCount_;\n  }\n  bool isRebuilt() const {\n    return rebuildCount_ > 0;\n  }\n\n private:\n  static Node* nodeFromBaseNode(BaseNode* bnode);\n\n  // Find the node in priority tree\n  Node* find(HTTPCodec::StreamID id, uint64_t* depth = nullptr);\n\n  Node* findInternal(HTTPCodec::StreamID id) {\n    if (id == rootNodeId_) {\n      return &root_;\n    }\n    return find(id);\n  }\n\n  bool allowDanglingNodes() const {\n    return timeout_ && kNodeLifetime_.count() > 0;\n  }\n\n  void scheduleNodeExpiration(Node* node) {\n    if (timeout_) {\n      VLOG(5) << \"scheduling expiration for node=\" << node->getID();\n      DCHECK_GT(kNodeLifetime_.count(), 0);\n      timeout_.scheduleTimeout(node, kNodeLifetime_);\n    }\n  }\n\n  static bool nextEgressResult(HTTP2PriorityQueue& queue,\n                               HTTPCodec::StreamID id,\n                               HTTPTransaction* txn,\n                               double r);\n\n  void updateEnqueuedWeight();\n\n private:\n  class Node\n      : public BaseNode\n      , public folly::HHWheelTimer::Callback {\n   public:\n    static const uint16_t kDefaultWeight = 16;\n\n    Node(HTTP2PriorityQueue& queue,\n         Node* inParent,\n         HTTPCodec::StreamID id,\n         uint8_t weight,\n         HTTPTransaction* txn);\n\n    ~Node() override;\n\n    void setPermanent() {\n      isPermanent_ = true;\n    }\n\n    Node* getParent() const {\n      return parent_;\n    }\n\n    HTTPCodec::StreamID getID() const {\n      return id_;\n    }\n\n    HTTPCodec::StreamID parentID() const {\n      if (parent_) {\n        return parent_->id_;\n      }\n      return queue_.getRootId();\n    }\n\n    HTTPTransaction* getTransaction() const {\n      return txn_;\n    }\n\n    void clearTransaction() {\n      txn_ = nullptr;\n    }\n\n    // Add a new node as a child of this node\n    Node* emplaceNode(std::unique_ptr<Node> node, bool exclusive);\n\n    // Removes the node from the tree\n    void removeFromTree();\n\n    void signalPendingEgress();\n\n    void clearPendingEgress();\n\n    uint16_t getWeight() const {\n      return weight_;\n    }\n\n    // Set a new weight for this node\n    void updateWeight(uint8_t weight);\n\n    Node* reparent(Node* newParent, bool exclusive);\n\n    // Returns true if this is a descendant of node\n    bool isDescendantOf(Node* node) const;\n\n    // True if this Node is in the egress queue\n    bool isEnqueued() const override {\n      return (txn_ != nullptr && enqueued_);\n    }\n\n    // True if this Node is in the egress tree even if the node itself is\n    // virtual but has enqueued descendants.\n    bool inEgressTree() const {\n      return isEnqueued() || totalEnqueuedWeight_ > 0;\n    }\n\n    double getRelativeWeight() const {\n      if (!parent_) {\n        return 1.0;\n      }\n\n      return static_cast<double>(weight_) /\n             static_cast<double>(parent_->totalChildWeight_);\n    }\n\n    double getRelativeEnqueuedWeight() const {\n      if (!parent_) {\n        return 1.0;\n      }\n\n      if (parent_->totalEnqueuedWeight_ == 0) {\n        return 0.0;\n      }\n\n      return static_cast<double>(weight_) /\n             static_cast<double>(parent_->totalEnqueuedWeight_);\n    }\n\n    /* Execute the given function on this node and all child nodes presently\n     * enqueued, until one of them asks to stop, or the stop function returns\n     * true.\n     *\n     * The all parameter visits every node, even the ones not currently\n     * enqueued.\n     *\n     * The arguments to the function are\n     *   txn - HTTPTransaction for the node\n     *   ratio - weight of this txn relative to all peers (not just enequeued)\n     */\n    bool iterate(const std::function<\n                     bool(HTTPCodec::StreamID, HTTPTransaction*, double)>& fn,\n                 const std::function<bool()>& stopFn,\n                 bool all);\n\n    struct PendingNode {\n      HTTPCodec::StreamID id;\n      Node* node;\n      double ratio;\n      PendingNode(HTTPCodec::StreamID i, Node* n, double r)\n          : id(i), node(n), ratio(r) {\n      }\n    };\n\n    using PendingList = std::deque<PendingNode>;\n    bool visitBFS(double relativeParentWeight,\n                  const std::function<bool(HTTP2PriorityQueue& queue,\n                                           HTTPCodec::StreamID,\n                                           HTTPTransaction*,\n                                           double)>& fn,\n                  bool all,\n                  PendingList& pendingNodes,\n                  bool enqueuedChildren);\n\n    void updateEnqueuedWeight(bool activeNodes);\n\n    void dropPriorityNodes();\n\n    void convertVirtualNode(HTTPTransaction* txn);\n\n    [[nodiscard]] uint64_t calculateDepth(\n        bool includeVirtual = true) const override;\n\n    // Internal error recovery\n    void flattenSubtree();\n    void flattenSubtreeDFS(Node* subtreeRoot);\n    static void addChildToNewSubtreeRoot(std::unique_ptr<Node> child,\n                                         Node* subtreeRoot);\n\n   private:\n    Node* addChild(std::unique_ptr<Node> child);\n\n    void addChildren(std::list<std::unique_ptr<Node>>&& children);\n\n    std::unique_ptr<Node> detachChild(Node* node);\n\n    void addEnqueuedChild(HTTP2PriorityQueue::Node* node);\n\n    void removeEnqueuedChild(HTTP2PriorityQueue::Node* node);\n\n    static void propagatePendingEgressSignal(Node* node);\n\n    static void propagatePendingEgressClear(Node* node);\n\n    void timeoutExpired() noexcept override {\n      VLOG(5) << \"Node=\" << id_ << \" expired\";\n      CHECK(txn_ == nullptr);\n      queue_.pendingWeightChange_ = true;\n      removeFromTree();\n    }\n\n    void refreshTimeout() {\n      if (!txn_ && !isPermanent_ && isScheduled()) {\n        queue_.scheduleNodeExpiration(this);\n      }\n    }\n\n    HTTP2PriorityQueue& queue_;\n    Node* parent_{nullptr};\n    HTTPCodec::StreamID id_;\n    uint16_t weight_{kDefaultWeight};\n    HTTPTransaction* txn_{nullptr};\n    bool isPermanent_{false};\n    bool enqueued_{false};\n#ifndef NDEBUG\n    uint64_t totalEnqueuedWeightCheck_{0};\n#endif\n    uint64_t totalEnqueuedWeight_{0};\n    uint64_t totalChildWeight_{0};\n    std::list<std::unique_ptr<Node>> children_;\n    std::list<std::unique_ptr<Node>>::iterator self_;\n    // enqueuedChildren_ includes all children that are themselves enqueued_\n    // or have enqueued descendants. Therefore, enqueuedChildren_ may contain\n    // direct children that have enqueued_ == false\n    folly::IntrusiveListHook enqueuedHook_;\n    folly::IntrusiveList<Node, &Node::enqueuedHook_> enqueuedChildren_;\n  };\n\n  using NodeMap = folly::F14FastMap<HTTPCodec::StreamID, Node*>;\n\n  NodeMap nodes_;\n  Node root_;\n  uint32_t rebuildCount_{0};\n  static uint32_t kMaxRebuilds_;\n  uint64_t activeCount_{0};\n  uint32_t maxVirtualNodes_{50};\n  uint32_t numVirtualNodes_{0};\n  folly::Optional<HTTPCodec::StreamID> largestId_;\n  bool pendingWeightChange_{false};\n  WheelTimerInstance timeout_;\n\n  NextEgressResult* nextEgressResults_{nullptr};\n  static std::chrono::milliseconds kNodeLifetime_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/DefaultHTTPCodecFactory.h>\n#include <proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.h>\n\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n\nnamespace {\nproxygen::HTTPCodecFactory::CodecConfig getCodecConfigFromAcceptorConfig(\n    const proxygen::AcceptorConfiguration& accConfig) {\n  proxygen::HTTPCodecFactory::CodecConfig config;\n  config.h1.forceHTTP1xCodecTo1_1 = accConfig.forceHTTP1_0_to_1_1;\n  config.h2.headerIndexingStrategy = accConfig.headerIndexingStrategy;\n  return config;\n}\n} // namespace\n\nnamespace proxygen {\n\nHTTPDefaultSessionCodecFactory::HTTPDefaultSessionCodecFactory(\n    std::shared_ptr<const AcceptorConfiguration> accConfig)\n    : HTTPCodecFactory(getCodecConfigFromAcceptorConfig(*accConfig)),\n      accConfig_{std::move(accConfig)} {\n}\n\nstd::unique_ptr<HTTPCodec> HTTPDefaultSessionCodecFactory::getCodec(\n    const std::string& nextProtocol, TransportDirection direction, bool isTLS) {\n  DefaultHTTPCodecFactory factory(configFn_());\n  if (!isTLS &&\n      (accConfig_->plaintextProtocol == http2::kProtocolCleartextString)) {\n    return factory.getCodec(http2::kProtocolString, direction, isTLS);\n  }\n  return factory.getCodec(nextProtocol, direction, isTLS);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/services/AcceptorConfiguration.h>\n\nnamespace proxygen {\n\n/**\n * This factory is for an HTTP server to create codecs for new connections.\n *\n * Though this factory cannot modify the passed in accConfig, the owner can\n * change parameters at runtime which affects new codecs.\n */\nclass HTTPDefaultSessionCodecFactory : public HTTPCodecFactory {\n public:\n  explicit HTTPDefaultSessionCodecFactory(\n      std::shared_ptr<const AcceptorConfiguration> accConfig);\n  ~HTTPDefaultSessionCodecFactory() override = default;\n\n  /**\n   * Get a codec instance\n   */\n  std::unique_ptr<HTTPCodec> getCodec(const std::string& nextProtocol,\n                                      TransportDirection direction,\n                                      bool isTLS) override;\n\n protected:\n  std::shared_ptr<const AcceptorConfiguration> accConfig_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDirectResponseHandler.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPDirectResponseHandler.h>\n\n#include <folly/Conv.h>\n#include <proxygen/lib/http/session/HTTPErrorPage.h>\n\nusing folly::IOBuf;\nusing std::string;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nHTTPDirectResponseHandler::HTTPDirectResponseHandler(\n    unsigned statusCode, std::string statusMsg, const HTTPErrorPage* errorPage)\n    : txn_(nullptr),\n      errorPage_(errorPage),\n      statusMessage_(std::move(statusMsg)),\n      statusCode_(statusCode),\n      headersSent_(false),\n      eomSent_(false),\n      forceConnectionClose_(true) {\n}\n\nHTTPDirectResponseHandler::~HTTPDirectResponseHandler() = default;\n\nvoid HTTPDirectResponseHandler::setTransaction(HTTPTransaction* txn) noexcept {\n  txn_ = txn;\n}\n\nvoid HTTPDirectResponseHandler::detachTransaction() noexcept {\n  delete this;\n}\n\nvoid HTTPDirectResponseHandler::onHeadersComplete(\n    std::unique_ptr<HTTPMessage> /*msg*/) noexcept {\n  VLOG(4) << \"processing request\";\n  headersSent_ = true;\n  HTTPMessage response;\n  std::unique_ptr<folly::IOBuf> responseBody;\n  response.setHTTPVersion(1, 1);\n  response.setStatusCode(statusCode_);\n  if (!statusMessage_.empty()) {\n    response.setStatusMessage(statusMessage_);\n  } else {\n    response.setStatusMessage(HTTPMessage::getDefaultReason(statusCode_));\n  }\n  if (forceConnectionClose_) {\n    response.getHeaders().add(HTTP_HEADER_CONNECTION, \"close\");\n  }\n  if (errorPage_) {\n    HTTPErrorPage::Page page = errorPage_->generate(\n        0, statusCode_, statusMessage_, nullptr, empty_string, err_);\n    VLOG(4) << \"sending error page with type \" << page.contentType;\n    response.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, page.contentType);\n    responseBody = std::move(page.content);\n    page.headers.copyTo(response.getHeaders());\n  }\n  response.getHeaders().add(\n      HTTP_HEADER_CONTENT_LENGTH,\n      folly::to<string>(responseBody ? responseBody->computeChainDataLength()\n                                     : 0));\n  txn_->sendHeaders(response);\n  if (responseBody) {\n    txn_->sendBody(std::move(responseBody));\n  }\n}\n\nvoid HTTPDirectResponseHandler::onBody(unique_ptr<IOBuf> /*chain*/) noexcept {\n  VLOG(4) << \"discarding request body\";\n}\n\nvoid HTTPDirectResponseHandler::onTrailers(\n    unique_ptr<HTTPHeaders> /*trailers*/) noexcept {\n  VLOG(4) << \"discarding request trailers\";\n}\n\nvoid HTTPDirectResponseHandler::onEOM() noexcept {\n  eomSent_ = true;\n  txn_->sendEOM();\n}\n\nvoid HTTPDirectResponseHandler::onUpgrade(\n    UpgradeProtocol /*protocol*/) noexcept {\n}\n\nvoid HTTPDirectResponseHandler::onError(const HTTPException& error) noexcept {\n  if (error.hasProxygenError()) {\n    err_ = error.getProxygenError();\n  }\n\n  if (error.getDirection() == HTTPException::Direction::INGRESS) {\n    if (error.getProxygenError() == kErrorTimeout) {\n      VLOG(4) << \"processing ingress timeout\";\n      if (!headersSent_) {\n        onHeadersComplete(nullptr);\n      }\n      if (!eomSent_) {\n        onEOM();\n      }\n    } else {\n      VLOG(4) << \"processing ingress error\";\n      if (!headersSent_) {\n        onHeadersComplete(nullptr);\n      }\n      if (!eomSent_) {\n        onEOM();\n      }\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDirectResponseHandler.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass HTTPErrorPage;\n\nclass HTTPDirectResponseHandler : public HTTPTransaction::Handler {\n public:\n  HTTPDirectResponseHandler(unsigned statusCode,\n                            std::string statusMsg,\n                            const HTTPErrorPage* errorPage = nullptr);\n\n  void forceConnectionClose(bool close) {\n    forceConnectionClose_ = close;\n  }\n  // HTTPTransaction::Handler methods\n  void setTransaction(HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(UpgradeProtocol protocol) noexcept override;\n  void onError(const HTTPException& error) noexcept override;\n  // These are no-ops since the direct response is already in memory\n  void onEgressPaused() noexcept override {\n  }\n  void onEgressResumed() noexcept override {\n  }\n\n private:\n  ~HTTPDirectResponseHandler() override;\n\n  HTTPTransaction* txn_;\n  const HTTPErrorPage* errorPage_;\n  std::string statusMessage_;\n  unsigned statusCode_;\n  ProxygenError err_{kErrorNone};\n  bool headersSent_ : 1;\n  bool eomSent_ : 1;\n  bool forceConnectionClose_ : 1;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDownstreamSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nHTTPDownstreamSession::~HTTPDownstreamSession() = default;\n\nvoid HTTPDownstreamSession::startNow() {\n  HTTPSession::startNow();\n}\n\nvoid HTTPDownstreamSession::setupOnHeadersComplete(HTTPTransaction* txn,\n                                                   HTTPMessage* msg) {\n  VLOG(5) << \"setupOnHeadersComplete txn=\" << txn << \", id=\" << txn->getID()\n          << \", handler=\" << txn->getHandler() << \", msg=\" << msg;\n  // We need to find a Handler to process the transaction.\n  // Note: The handler is responsible for freeing itself\n  // when it has finished processing the transaction.  The\n  // transaction is responsible for freeing itself when both the\n  // ingress and egress messages have completed (or failed).\n\n  // In the general case, delegate to the handler factory to generate\n  // a handler for the transaction.\n  auto* handler = CHECK_NOTNULL(getController()->getRequestHandler(*txn, msg));\n\n  DestructorGuard dg(this);\n  txn->setHandler(handler);\n}\n\nHTTPTransaction::Handler* HTTPDownstreamSession::getTransactionTimeoutHandler(\n    HTTPTransaction* txn) {\n  return getController()->getTransactionTimeoutHandler(txn, getLocalAddress());\n}\n\nvoid HTTPDownstreamSession::onHeadersSent(const HTTPMessage& headers,\n                                          bool codecWasReusable) {\n  if (!codec_->isReusable()) {\n    // If the codec turned unreusable, some thing wrong must have happened.\n    // Basically, the proxy decides the connection is not reusable.\n    // e.g, an error message is being sent with Connection: close\n    if (codecWasReusable) {\n      uint32_t statusCode = headers.getStatusCode();\n      if (statusCode >= 500) {\n        setCloseReason(ConnectionCloseReason::REMOTE_ERROR);\n      } else {\n        if (statusCode >= 400) {\n          setCloseReason(ConnectionCloseReason::ERR_RESP);\n        } else {\n          // should not be here\n          setCloseReason(ConnectionCloseReason::UNKNOWN);\n        }\n      }\n    } else {\n      // shouldn't happen... this case is detected by REQ_NOTREUSABLE\n      setCloseReason(ConnectionCloseReason::UNKNOWN);\n    }\n  }\n}\n\nbool HTTPDownstreamSession::allTransactionsStarted() const {\n  for (const auto& txn : transactions_) {\n    if (txn.second.isPushed() && !txn.second.isEgressStarted()) {\n      return false;\n    }\n  }\n  return true;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPDownstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n\nnamespace proxygen {\n\nclass HTTPSessionStats;\nclass HTTPDownstreamSession final : public HTTPSession {\n public:\n  /**\n   * @param sock       An open socket on which any applicable TLS handshaking\n   *                     has been completed already.\n   * @param localAddr  Address and port of the local end of the socket.\n   * @param peerAddr   Address and port of the remote end of the socket.\n   * @param codec      A codec with which to parse/generate messages in\n   *                     whatever HTTP-like wire format this session needs.\n   */\n  HTTPDownstreamSession(const WheelTimerInstance& timeout,\n                        folly::AsyncTransport::UniquePtr&& sock,\n                        const folly::SocketAddress& localAddr,\n                        const folly::SocketAddress& peerAddr,\n                        HTTPSessionController* controller,\n                        std::unique_ptr<HTTPCodec> codec,\n                        const wangle::TransportInfo& tinfo,\n                        InfoCallback* infoCallback)\n      : HTTPSession(timeout,\n                    std::move(sock),\n                    localAddr,\n                    peerAddr,\n                    CHECK_NOTNULL(controller),\n                    std::move(codec),\n                    tinfo,\n                    infoCallback) {\n    CHECK_EQ(codec_->getTransportDirection(), TransportDirection::DOWNSTREAM);\n  }\n\n  // allows using HTTPDownstreamSession with HHWheelTimer when it is not shared\n  HTTPDownstreamSession(folly::HHWheelTimer* timer,\n                        folly::AsyncTransport::UniquePtr&& sock,\n                        const folly::SocketAddress& localAddr,\n                        const folly::SocketAddress& peerAddr,\n                        HTTPSessionController* controller,\n                        std::unique_ptr<HTTPCodec> codec,\n                        const wangle::TransportInfo& tinfo,\n                        InfoCallback* infoCallback)\n      : HTTPDownstreamSession(WheelTimerInstance(timer),\n                              std::move(sock),\n                              localAddr,\n                              peerAddr,\n                              CHECK_NOTNULL(controller),\n                              std::move(codec),\n                              tinfo,\n                              infoCallback) {\n  }\n\n  void startNow() override;\n\n private:\n  ~HTTPDownstreamSession() override;\n\n  /**\n   * Called by onHeadersComplete().\n   */\n  void setupOnHeadersComplete(HTTPTransaction* txn, HTTPMessage* msg) override;\n\n  /**\n   * Called by transactionTimeout() in the downstream case. This function\n   * ensures that a handler is set for the transaction.\n   */\n  HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn) override;\n\n  /**\n   * Invoked when headers have been sent.\n   */\n  void onHeadersSent(const HTTPMessage& headers,\n                     bool codecWasReusable) override;\n\n  bool allTransactionsStarted() const override;\n\n  // Upstream methods.  Can implement when servers support making request\n  bool isDetachable(bool) const override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  void attachThreadLocals(folly::EventBase*,\n                          std::shared_ptr<const folly::SSLContext>,\n                          const WheelTimerInstance&,\n                          HTTPSessionStats*,\n                          FilterIteratorFn,\n                          HeaderCodec::Stats*,\n                          HTTPSessionController*) override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  void detachThreadLocals(bool) override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  HTTPTransaction* newTransaction(HTTPTransaction::Handler*) override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  bool isReplaySafe() const override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  bool isReusable() const override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n\n  bool isClosing() const override {\n    LOG(FATAL) << __func__ << \" is an upstream interface\";\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPErrorPage.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPErrorPage.h>\n\n#include <folly/io/IOBuf.h>\n\nusing std::string;\n\nnamespace proxygen {\n\nHTTPStaticErrorPage::HTTPStaticErrorPage(std::unique_ptr<folly::IOBuf> content,\n                                         const string& contentType)\n    : content_(std::move(content)), contentType_(contentType) {\n}\n\nHTTPErrorPage::Page HTTPStaticErrorPage::generate(\n    uint64_t /*requestID*/,\n    unsigned /*httpStatusCode*/,\n    const std::string& /*reason*/,\n    std::unique_ptr<folly::IOBuf> /*body*/,\n    const std::string& /*detailReason*/,\n    ProxygenError err) const {\n\n  HTTPHeaders headers;\n  VLOG(4) << \"adding server-status header for proxygen error\";\n  headers.set(\"Server-Status\", folly::to<std::string>(static_cast<int>(err)));\n  return {contentType_, content_->clone(), std::move(headers)};\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPErrorPage.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <memory>\n#include <proxygen/lib/http/HTTPHeaders.h>\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <string>\n\nnamespace folly {\nclass IOBuf;\n}\n\nnamespace proxygen {\n\n/**\n * An HTTPErrorPage generates the content for a web page that\n * Proxygen can return in response to various error conditions.\n */\nclass HTTPErrorPage {\n public:\n  struct Page {\n    Page(const std::string& pageContentType,\n         std::unique_ptr<folly::IOBuf> pageContent)\n        : contentType(pageContentType), content(std::move(pageContent)) {\n    }\n\n    Page(std::string pageContentType,\n         std::unique_ptr<folly::IOBuf> pageContent,\n         HTTPHeaders pageHeaders)\n        : contentType(std::move(pageContentType)),\n          content(std::move(pageContent)),\n          headers(std::move(pageHeaders)) {\n    }\n\n    Page(Page&& other) noexcept = default;\n\n    const std::string contentType;\n    std::unique_ptr<folly::IOBuf> content;\n    HTTPHeaders headers;\n  };\n\n  virtual ~HTTPErrorPage() {\n  }\n\n  virtual Page generate(uint64_t requestID,\n                        unsigned httpStatusCode,\n                        const std::string& reason,\n                        std::unique_ptr<folly::IOBuf> body,\n                        const std::string& detailReason,\n                        ProxygenError err) const = 0;\n};\n\n/**\n * Static error page generator.\n */\nclass HTTPStaticErrorPage : public HTTPErrorPage {\n public:\n  explicit HTTPStaticErrorPage(\n      std::unique_ptr<folly::IOBuf> content,\n      const std::string& contentType = \"text/html; charset=utf-8\");\n\n  Page generate(uint64_t requestID,\n                unsigned httpStatusCode,\n                const std::string& reason,\n                std::unique_ptr<folly::IOBuf> body,\n                const std::string& detailReason,\n                ProxygenError err) const override;\n\n protected:\n  std::unique_ptr<folly::IOBuf> content_;\n  std::string contentType_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPEvent.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPEvent.h>\n\n#include <ostream>\n\nnamespace proxygen {\n\nstd::ostream& operator<<(std::ostream& os, HTTPEvent::Type e) {\n  switch (e) {\n    case HTTPEvent::Type::MESSAGE_BEGIN:\n      os << \"message_begin\";\n      break;\n    case HTTPEvent::Type::HEADERS_COMPLETE:\n      os << \"headers_complete\";\n      break;\n    case HTTPEvent::Type::BODY:\n      os << \"body\";\n      break;\n    case HTTPEvent::Type::CHUNK_HEADER:\n      os << \"chunk_header\";\n      break;\n    case HTTPEvent::Type::CHUNK_COMPLETE:\n      os << \"chunk_complete\";\n      break;\n    case HTTPEvent::Type::TRAILERS_COMPLETE:\n      os << \"trailers_complete\";\n      break;\n    case HTTPEvent::Type::MESSAGE_COMPLETE:\n      os << \"message_complete\";\n      break;\n    case HTTPEvent::Type::UPGRADE:\n      os << \"uprade\";\n      break;\n    case HTTPEvent::Type::ERROR:\n      os << \"error\";\n      break;\n  }\n\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPEvent.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n#include <proxygen/lib/http/HTTPConstants.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\nnamespace proxygen {\n\n/**\n * Helper class that holds some event in the lifecycle\n * of an HTTP request or response.\n *\n * The main use for this class is to queue up events in\n * situations where the code handling events isn't able\n * to process them and the thing generating events isn't\n * able to stop.\n */\nclass HTTPEvent {\n public:\n  enum class Type : uint8_t {\n    // Ingress events\n    MESSAGE_BEGIN,\n    HEADERS_COMPLETE,\n    BODY,\n    CHUNK_HEADER,\n    CHUNK_COMPLETE,\n    TRAILERS_COMPLETE,\n    MESSAGE_COMPLETE,\n    UPGRADE,\n    ERROR,\n  };\n\n  HTTPEvent(HTTPCodec::StreamID streamID, Type event, bool upgrade = false)\n      : streamID_(streamID), length_(0), event_(event), upgrade_(upgrade) {\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID, Type event, size_t length)\n      : streamID_(streamID), length_(length), event_(event), upgrade_(false) {\n    // This constructor should only be used for CHUNK_HEADER.\n    // (Ideally we would take the event type as a template parameter\n    // so we could enforce this check at compile time.  Unfortunately,\n    // that would prevent us from using this constructor with\n    // deferredCallbacks_.emplace().)\n    CHECK(event == Type::CHUNK_HEADER);\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID,\n            Type event,\n            std::unique_ptr<HTTPMessage> headers)\n      : headers_(std::move(headers)),\n        streamID_(streamID),\n        length_(0),\n        event_(event),\n        upgrade_(false) {\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID,\n            Type event,\n            std::unique_ptr<folly::IOBuf> body)\n      : streamID_(streamID), length_(0), event_(event), upgrade_(false) {\n    body_.append(std::move(body));\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID,\n            Type event,\n            std::unique_ptr<HTTPHeaders> trailers)\n      : trailers_(std::move(trailers)),\n        streamID_(streamID),\n        length_(0),\n        event_(event),\n        upgrade_(false) {\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID, std::unique_ptr<HTTPException> error)\n      : error_(std::move(error)),\n        streamID_(streamID),\n        length_(0),\n        event_(Type::ERROR),\n        upgrade_(false) {\n    CHECK(error_);\n  }\n\n  HTTPEvent(HTTPCodec::StreamID streamID, Type event, UpgradeProtocol protocol)\n      : streamID_(streamID),\n        length_(0),\n        event_(event),\n        upgrade_(false),\n        protocol_(protocol) {\n  }\n\n  Type getEvent() const {\n    return event_;\n  }\n\n  HTTPCodec::StreamID getStreamID() const {\n    return streamID_;\n  }\n\n  std::unique_ptr<HTTPMessage> getHeaders() {\n    return std::move(headers_);\n  }\n\n  std::unique_ptr<folly::IOBuf> getBody() {\n    return body_.move();\n  }\n\n  size_t getBodyLength() {\n    return body_.chainLength();\n  }\n\n  void appendChunk(std::unique_ptr<folly::IOBuf>&& chain) {\n    body_.append(std::move(chain));\n  }\n\n  std::unique_ptr<HTTPException> getError() {\n    return std::move(error_);\n  }\n\n  bool isUpgrade() const {\n    CHECK(event_ == Type::MESSAGE_COMPLETE);\n    return upgrade_;\n  }\n\n  size_t getChunkLength() const {\n    CHECK(event_ == Type::CHUNK_HEADER);\n    return length_;\n  }\n\n  std::unique_ptr<HTTPHeaders> getTrailers() {\n    return std::move(trailers_);\n  }\n\n  UpgradeProtocol getUpgradeProtocol() {\n    return protocol_;\n  }\n\n private:\n  std::unique_ptr<HTTPMessage> headers_;\n  folly::IOBufQueue body_{folly::IOBufQueue::cacheChainLength()};\n  std::unique_ptr<HTTPHeaders> trailers_;\n  std::unique_ptr<HTTPException> error_;\n  HTTPCodec::StreamID streamID_;\n  size_t length_; // Only valid when event_ == CHUNK_HEADER\n  Type event_;\n  bool upgrade_; // Only valid when event_ == MESSAGE_COMPLETE\n  UpgradeProtocol protocol_;\n};\n\nstd::ostream& operator<<(std::ostream& os, HTTPEvent::Type e);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPSession.h>\n\n#include <chrono>\n#include <fizz/protocol/AsyncFizzBase.h>\n#include <folly/Conv.h>\n#include <folly/CppAttributes.h>\n#include <folly/Random.h>\n#include <folly/io/Cursor.h>\n#include <folly/tracing/ScopedTraceSection.h>\n#include <memory>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/HTTPChecks.h>\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\nusing fizz::AsyncFizzBase;\nusing folly::AsyncSocket;\nusing folly::AsyncSocketException;\nusing folly::AsyncTransport;\nusing folly::IOBuf;\nusing folly::SocketAddress;\nusing std::pair;\nusing std::string;\nusing std::unique_ptr;\nusing wangle::TransportInfo;\n\nnamespace {\nstatic const uint32_t kMinReadSize = 1460;\nstatic const uint32_t kWriteReadyMax = 65536;\n\n// Lower = higher latency, better prioritization\n// Higher = lower latency, less prioritization\nstatic const uint32_t kMaxWritesPerLoop = 32;\n\nstatic constexpr folly::StringPiece kClientLabel =\n    \"EXPORTER HTTP CERTIFICATE client\";\nstatic constexpr folly::StringPiece kServerLabel =\n    \"EXPORTER HTTP CERTIFICATE server\";\n} // anonymous namespace\n\nnamespace proxygen {\n\nHTTPSession::HTTPSession(folly::HHWheelTimer* wheelTimer,\n                         AsyncTransport::UniquePtr sock,\n                         const SocketAddress& localAddr,\n                         const SocketAddress& peerAddr,\n                         HTTPSessionController* controller,\n                         unique_ptr<HTTPCodec> codec,\n                         const TransportInfo& tinfo,\n                         InfoCallback* infoCallback)\n    : HTTPSession(WheelTimerInstance(wheelTimer),\n                  std::move(sock),\n                  localAddr,\n                  peerAddr,\n                  controller,\n                  std::move(codec),\n                  tinfo,\n                  infoCallback) {\n}\n\nHTTPSession::HTTPSession(const WheelTimerInstance& wheelTimer,\n                         AsyncTransport::UniquePtr sock,\n                         const SocketAddress& localAddr,\n                         const SocketAddress& peerAddr,\n                         HTTPSessionController* controller,\n                         unique_ptr<HTTPCodec> codec,\n                         const TransportInfo& tinfo,\n                         InfoCallback* infoCallback)\n    : HTTPSessionBase(localAddr,\n                      peerAddr,\n                      controller,\n                      tinfo,\n                      infoCallback,\n                      std::move(codec),\n                      wheelTimer,\n                      HTTPCodec::StreamID(0)),\n      writeTimeout_(this),\n      sock_(std::move(sock)),\n      wheelTimer_(wheelTimer),\n      txnEgressQueue_(isHTTP2CodecProtocol(codec_->getProtocol())\n                          ? WheelTimerInstance(wheelTimer)\n                          : WheelTimerInstance(),\n                      HTTPCodec::StreamID(0)),\n      draining_(false),\n      started_(false),\n      writesDraining_(false),\n      resetAfterDrainingWrites_(false),\n      ingressError_(false),\n      flowControlTimeout_(this),\n      drainTimeout_(this),\n      reads_(SocketState::PAUSED),\n      writes_(SocketState::UNPAUSED),\n      ingressUpgraded_(false),\n      resetSocketOnShutdown_(false),\n      inLoopCallback_(false),\n      pendingPause_(false),\n      writeBufSplit_(false),\n      sessionObserverAccessor_(this),\n      sessionObserverContainer_(&sessionObserverAccessor_) {\n  setByteEventTracker(std::make_shared<ByteEventTracker>(this));\n  initialReceiveWindow_ = receiveStreamWindowSize_ = receiveSessionWindowSize_ =\n      codec_->getDefaultWindowSize();\n\n  codec_.add<HTTPChecks>();\n\n  setupCodec();\n\n  nextEgressResults_.reserve(maxConcurrentIncomingStreams_);\n\n  if (infoCallback_) {\n    infoCallback_->onCreate(*this);\n  }\n\n  auto controllerPtr = getController();\n  if (controllerPtr) {\n    flowControlTimeout_.setTimeoutDuration(\n        controllerPtr->getSessionFlowControlTimeout());\n  }\n  attachToSessionController();\n  informSessionControllerTransportReady();\n\n  if (!sock_->isReplaySafe()) {\n    sock_->setReplaySafetyCallback(this);\n  }\n  initCodecHeaderIndexingStrategy();\n}\n\nuint32_t HTTPSession::getCertAuthSettingVal() {\n  uint32_t certAuthSettingVal = 0;\n  constexpr uint16_t settingLen = 4;\n  std::unique_ptr<folly::IOBuf> ekm;\n  folly::StringPiece label;\n  if (isUpstream()) {\n    label = kClientLabel;\n  } else {\n    label = kServerLabel;\n  }\n  auto fizzBase = getTransport()->getUnderlyingTransport<AsyncFizzBase>();\n  if (fizzBase) {\n    ekm = fizzBase->getExportedKeyingMaterial(label, nullptr, settingLen);\n  } else {\n    VLOG(4) << \"Underlying transport does not support secondary \"\n               \"authentication.\";\n    return certAuthSettingVal;\n  }\n  if (ekm && ekm->computeChainDataLength() == settingLen) {\n    folly::io::Cursor cursor(ekm.get());\n    auto ekmVal = cursor.readBE<uint32_t>();\n    certAuthSettingVal = (ekmVal & 0x3fffffff) | 0x80000000;\n  }\n  return certAuthSettingVal;\n}\n\nbool HTTPSession::verifyCertAuthSetting(uint32_t value) {\n  uint32_t certAuthSettingVal = 0;\n  constexpr uint16_t settingLen = 4;\n  std::unique_ptr<folly::IOBuf> ekm;\n  folly::StringPiece label;\n  if (isUpstream()) {\n    label = kServerLabel;\n  } else {\n    label = kClientLabel;\n  }\n  auto fizzBase = getTransport()->getUnderlyingTransport<AsyncFizzBase>();\n  if (fizzBase) {\n    ekm = fizzBase->getExportedKeyingMaterial(label, nullptr, settingLen);\n  } else {\n    VLOG(4) << \"Underlying transport does not support secondary \"\n               \"authentication.\";\n    return false;\n  }\n  if (ekm && ekm->computeChainDataLength() == settingLen) {\n    folly::io::Cursor cursor(ekm.get());\n    auto ekmVal = cursor.readBE<uint32_t>();\n    certAuthSettingVal = (ekmVal & 0x3fffffff) | 0x80000000;\n  } else {\n    return false;\n  }\n  if (certAuthSettingVal == value) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nvoid HTTPSession::setupCodec() {\n  if (!codec_->supportsParallelRequests()) {\n    // until we support upstream pipelining\n    maxConcurrentIncomingStreams_ = 1;\n    maxConcurrentOutgoingStreamsRemote_ = isDownstream() ? 0 : 1;\n  }\n\n  // If a secondary authentication manager is configured for this session, set\n  // the SETTINGS_HTTP_CERT_AUTH to indicate support for HTTP-layer certificate\n  // authentication.\n  uint32_t certAuthSettingVal = 0;\n  if (secondAuthManager_) {\n    certAuthSettingVal = getCertAuthSettingVal();\n  }\n  HTTPSettings* settings = codec_->getEgressSettings();\n  if (settings) {\n    settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS,\n                         maxConcurrentIncomingStreams_);\n    if (certAuthSettingVal != 0) {\n      settings->setSetting(SettingsId::SETTINGS_HTTP_CERT_AUTH,\n                           certAuthSettingVal);\n    }\n  }\n  codec_->generateConnectionPreface(writeBuf_);\n\n  if (codec_->supportsSessionFlowControl() && !connFlowControl_) {\n    connFlowControl_ = new FlowControlFilter(*this, writeBuf_, codec_.call());\n    codec_.addFilters(std::unique_ptr<FlowControlFilter>(connFlowControl_));\n  }\n  if (codec_->supportsParallelRequests() && sock_ && isDownstream()) {\n    auto rateLimitFilter = std::make_unique<RateLimitFilter>(\n        &getEventBase()->timer(), sessionStats_);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::HEADERS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::DIRECT_ERROR_HANDLING);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::MISC_CONTROL_MSGS);\n    rateLimitFilter->addRateLimiter(RateLimiter::Type::RSTS);\n    rateLimitFilter_ = rateLimitFilter.get();\n    codec_.addFilters(std::move(rateLimitFilter));\n  }\n\n  codec_.setCallback(this);\n}\n\nHTTPSession::~HTTPSession() {\n  VLOG(4) << *this << \" closing\";\n\n  CHECK(transactions_.empty());\n  txnEgressQueue_.dropPriorityNodes();\n  CHECK(txnEgressQueue_.empty());\n  DCHECK(!sock_->getReadCallback());\n\n  if (writeTimeout_.isScheduled()) {\n    writeTimeout_.cancelTimeout();\n  }\n\n  if (flowControlTimeout_.isScheduled()) {\n    flowControlTimeout_.cancelTimeout();\n  }\n\n  if (pingProber_ && pingProber_->isScheduled()) {\n    pingProber_->cancelProbes();\n  }\n\n  runDestroyCallbacks();\n}\n\nstd::chrono::milliseconds HTTPSession::getDrainTimeout() const {\n  static constexpr std::chrono::milliseconds kDefaultDrainTimeout{\n      std::chrono::seconds(5)};\n  auto controller = getController();\n  if (controller) {\n    auto controllerTimeout = controller->getGracefulShutdownTimeout();\n    if (controllerTimeout < kDefaultDrainTimeout) {\n      return controllerTimeout;\n    }\n  }\n  return kDefaultDrainTimeout;\n}\n\nvoid HTTPSession::startNow() {\n  CHECK(!started_);\n  started_ = true;\n  codec_->generateSettings(writeBuf_);\n  if (connFlowControl_) {\n    connFlowControl_->setReceiveWindowSize(writeBuf_,\n                                           receiveSessionWindowSize_);\n  }\n  // For HTTP/2 if we are currently draining it means we got notified to\n  // shutdown before we sent a SETTINGS frame, so we defer sending a GOAWAY\n  // util we've started and sent SETTINGS.\n  if (draining_) {\n    codec_->generateGoaway(writeBuf_);\n    if (codec_->isWaitingToDrain()) {\n      wheelTimer_.scheduleTimeout(&drainTimeout_, getDrainTimeout());\n    } else if (isDownstream()) {\n      // transactions must be empty and reads cannot be shutdown\n      VLOG(4) << \"Starting drain timer\";\n      resetTimeoutTo(getDrainTimeout());\n    } // it's upstream, let the client close it when they want\n  }\n  scheduleWrite();\n  resumeReads();\n}\n\nvoid HTTPSession::setByteEventTracker(\n    std::shared_ptr<ByteEventTracker> byteEventTracker) {\n  if (byteEventTracker && byteEventTracker_) {\n    byteEventTracker->absorb(std::move(*byteEventTracker_));\n  }\n  byteEventTracker_ = byteEventTracker;\n  if (byteEventTracker_) {\n    byteEventTracker_->setCallback(this);\n    byteEventTracker_->setTTLBAStats(sessionStats_);\n  }\n}\n\nvoid HTTPSession::setSessionStats(HTTPSessionStats* stats) {\n  HTTPSessionBase::setSessionStats(stats);\n  if (byteEventTracker_) {\n    byteEventTracker_->setTTLBAStats(stats);\n  }\n\n  if (rateLimitFilter_) {\n    rateLimitFilter_->setSessionStats(stats);\n  }\n}\n\nvoid HTTPSession::setFlowControl(size_t initialReceiveWindow,\n                                 size_t receiveStreamWindowSize,\n                                 size_t receiveSessionWindowSize) {\n  CHECK(!started_);\n  initialReceiveWindow_ = initialReceiveWindow;\n  receiveStreamWindowSize_ = receiveStreamWindowSize;\n  receiveSessionWindowSize_ = receiveSessionWindowSize;\n  HTTPSessionBase::setReadBufferLimit(receiveSessionWindowSize);\n  HTTPSettings* settings = codec_->getEgressSettings();\n  if (settings) {\n    settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                         initialReceiveWindow_);\n  }\n}\n\nvoid HTTPSession::setEgressSettings(const SettingsList& inSettings) {\n  VLOG_IF(4, started_) << \"Must flush egress settings to peer\";\n  HTTPSettings* settings = codec_->getEgressSettings();\n  if (settings) {\n    for (const auto& setting : inSettings) {\n      settings->setSetting(setting.id, setting.value);\n      if (setting.id == SettingsId::MAX_CONCURRENT_STREAMS) {\n        maxConcurrentIncomingStreams_ = setting.value;\n      }\n    }\n  }\n}\n\nvoid HTTPSession::setMaxConcurrentIncomingStreams(uint32_t num) {\n  CHECK(!started_);\n  if (codec_->supportsParallelRequests()) {\n    maxConcurrentIncomingStreams_ = num;\n    HTTPSettings* settings = codec_->getEgressSettings();\n    if (settings) {\n      settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS,\n                           maxConcurrentIncomingStreams_);\n    }\n  }\n}\n\nvoid HTTPSession::setEgressBytesLimit(uint64_t bytesLimit) {\n  CHECK(!started_);\n  egressBytesLimit_ = bytesLimit;\n}\n\nvoid HTTPSession::readTimeoutExpired() noexcept {\n  VLOG(3) << \"session-level timeout on \" << *this;\n\n  DestructorGuard g(this);\n  setCloseReason(ConnectionCloseReason::TIMEOUT);\n  if (!codec_->isReusable() && transactions_.empty()) {\n    LOG_IF(DFATAL, readsShutdown()) << \"Why did we have a read timer running?\";\n    // Shutdown reads (uninstall read callback, etc).  Session will close\n    VLOG(4) << \"Shutdown from readTimeoutExpired sess=\" << *this;\n    shutdownTransport(true, false);\n  } // otherwise\n  //    1. The codec is re-usable - we will notifyPendingShutdown to start drain\n  //    2. There's an active txn; the FIN timer will start from\n  //         onEgressMessageFinished/ShutdownTransportCallback.\n  notifyPendingShutdown();\n  checkForShutdown();\n}\n\nvoid HTTPSession::writeTimeoutExpired() noexcept {\n  VLOG(4) << \"Write timeout for \" << *this;\n\n  CHECK(pendingWrite_.hasValue());\n  DestructorGuard g(this);\n\n  setCloseReason(ConnectionCloseReason::TIMEOUT);\n  shutdownTransportWithReset(kErrorWriteTimeout);\n}\n\nvoid HTTPSession::flowControlTimeoutExpired() noexcept {\n  VLOG(4) << \"Flow control timeout for \" << *this;\n\n  DestructorGuard g(this);\n\n  setCloseReason(ConnectionCloseReason::TIMEOUT);\n  shutdownTransport(true, true);\n}\n\nvoid HTTPSession::describe(std::ostream& os) const {\n  os << \"proto=\" << getCodecProtocolString(codec_->getProtocol());\n  if (isDownstream()) {\n    os << \", UA=\" << codec_->getUserAgent()\n       << \", downstream=\" << getPeerAddress() << \", \" << getLocalAddress()\n       << \"=local\";\n  } else {\n    os << \", local=\" << getLocalAddress() << \", \" << getPeerAddress()\n       << \"=upstream\";\n  }\n}\n\nbool HTTPSession::isBusy() const {\n  return !transactions_.empty() || codec_->isBusy();\n}\n\nvoid HTTPSession::notifyPendingEgress() noexcept {\n  scheduleWrite();\n}\n\nvoid HTTPSession::notifyPendingShutdown() {\n  VLOG(4) << *this << \" notified pending shutdown\";\n  drain();\n}\n\nvoid HTTPSession::closeWhenIdle() {\n  // If drain() already called, this is a noop\n  drain();\n  // Generate the second GOAWAY now. No-op if second GOAWAY already sent.\n  if (codec_->generateGoaway(writeBuf_)) {\n    scheduleWrite();\n  }\n  if (!isBusy() && !hasMoreWrites()) {\n    // if we're already idle, close now\n    dropConnection();\n  } else if (isDownstream() && !readsShutdown()) {\n    auto to = getDrainTimeout();\n    VLOG(4) << \"Starting drain timer t=\" << to.count();\n    resetTimeoutTo(to);\n  }\n}\n\nvoid HTTPSession::immediateShutdown() {\n  if (isLoopCallbackScheduled()) {\n    cancelLoopCallback();\n  }\n  if (shutdownTransportCb_) {\n    shutdownTransportCb_.reset();\n  }\n  // checkForShutdown only closes the connection if these conditions are true\n  DCHECK(writesShutdown());\n  DCHECK(transactions_.empty());\n  checkForShutdown();\n}\n\nvoid HTTPSession::dropConnection(const std::string& errorMsg) {\n  VLOG(4) << \"dropping \" << *this;\n  if (!sock_ || (readsShutdown() && writesShutdown())) {\n    VLOG(4) << *this << \" already shutdown\";\n    DCHECK(!shutdownTransportCb_) << \"Why is there a shutdownTransportCb_?\";\n    if (isLoopCallbackScheduled()) {\n      immediateShutdown();\n    }\n    return;\n  }\n\n  setCloseReason(ConnectionCloseReason::SHUTDOWN);\n  if (transactions_.empty() && !hasMoreWrites()) {\n    DestructorGuard dg(this);\n    shutdownTransport(true, true);\n    // shutdownTransport might have generated a write (goaway)\n    // If so, writes will not be shutdown, so fall through to\n    // shutdownTransportWithReset.\n    if (readsShutdown() && writesShutdown()) {\n      immediateShutdown();\n      return;\n    }\n  }\n  shutdownTransportWithReset(kErrorDropped, errorMsg);\n}\n\nvoid HTTPSession::dumpConnectionState(uint8_t /*loglevel*/) {\n}\n\nbool HTTPSession::isUpstream() const {\n  return proxygen::isUpstream(codec_->getTransportDirection());\n}\n\nbool HTTPSession::isDownstream() const {\n  return proxygen::isDownstream(codec_->getTransportDirection());\n}\n\nvoid HTTPSession::getReadBuffer(void** buf, size_t* bufSize) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPSession - getReadBuffer\");\n  pair<void*, uint32_t> readSpace =\n      readBuf_.preallocate(kMinReadSize, HTTPSessionBase::maxReadBufferSize_);\n  *buf = readSpace.first;\n  *bufSize = readSpace.second;\n}\n\nvoid HTTPSession::readDataAvailable(size_t readSize) noexcept {\n  FOLLY_SCOPED_TRACE_SECTION(\n      \"HTTPSession - readDataAvailable\", \"readSize\", readSize);\n  VLOG(10) << \"read completed on \" << *this << \", bytes=\" << readSize;\n\n  DestructorGuard dg(this);\n  if (pingProber_) {\n    pingProber_->refreshTimeout(/*onIngress=*/true);\n  }\n  resetTimeout();\n\n  if (ingressError_) {\n    VLOG(3) << \"discarding readBuf due to ingressError_ sess=\" << *this\n            << \" bytes=\" << readSize;\n    return;\n  }\n  readBuf_.postallocate(readSize);\n\n  if (infoCallback_) {\n    infoCallback_->onRead(*this, readSize, HTTPCodec::NoStream);\n  }\n\n  processReadData();\n}\n\nbool HTTPSession::isBufferMovable() noexcept {\n  return true;\n}\n\nvoid HTTPSession::readBufferAvailable(std::unique_ptr<IOBuf> readBuf) noexcept {\n  size_t readSize = readBuf->computeChainDataLength();\n  FOLLY_SCOPED_TRACE_SECTION(\n      \"HTTPSession - readBufferAvailable\", \"readSize\", readSize);\n  VLOG(5) << \"read completed on \" << *this << \", bytes=\" << readSize;\n\n  if (pingProber_) {\n    pingProber_->refreshTimeout(/*onIngress=*/true);\n  }\n\n  DestructorGuard dg(this);\n  resetTimeout();\n\n  if (ingressError_) {\n    VLOG(3) << \"discarding readBuf due to ingressError_ sess=\" << *this\n            << \" bytes=\" << readSize;\n    return;\n  }\n  readBuf_.append(std::move(readBuf));\n\n  if (infoCallback_) {\n    infoCallback_->onRead(*this, readSize, HTTPCodec::NoStream);\n  }\n\n  processReadData();\n}\n\nvoid HTTPSession::processReadData() {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPSession - processReadData\");\n\n  // Pass the ingress data through the codec to parse it. The codec\n  // will invoke various methods of the HTTPSession as callbacks.\n  while (!ingressError_ && readsUnpaused() && !readBuf_.empty()) {\n    // Skip any 0 length buffers before invoking the codec. Since readBuf_ is\n    // not empty, we are guaranteed to find a non-empty buffer.\n    while (readBuf_.front()->length() == 0) {\n      readBuf_.pop_front();\n    }\n\n    // We're about to parse, make sure the parser is not paused\n    codec_->setParserPaused(false);\n    size_t bytesParsed = codec_->onIngress(*readBuf_.front());\n    if (bytesParsed == 0) {\n      // If the codec didn't make any progress with current input, we\n      // better get more.\n      break;\n    }\n    readBuf_.trimStart(bytesParsed);\n  }\n}\n\nvoid HTTPSession::readEOF() noexcept {\n  DestructorGuard guard(this);\n  VLOG(4) << \"EOF on \" << *this;\n  // for SSL only: error without any bytes from the client might happen\n  // due to client-side issues with the SSL cert. Note that it can also\n  // happen if the client sends a H2 frame header but no body.\n  if (infoCallback_ && transportInfo_.secure && getNumTxnServed() == 0 &&\n      readBuf_.empty()) {\n    infoCallback_->onIngressError(*this, kErrorClientSilent);\n  }\n\n  // Shut down reads, and also shut down writes if there are no\n  // transactions.  (If there are active transactions, leave the\n  // write side of the socket open so those transactions can\n  // finish generating responses.)\n  setCloseReason(ConnectionCloseReason::READ_EOF);\n  shutdownTransport(true, transactions_.empty());\n}\n\nvoid HTTPSession::readErr(const AsyncSocketException& ex) noexcept {\n  DestructorGuard guard(this);\n  VLOG(4) << \"read error on \" << *this << \": \" << ex.what();\n\n  auto sslEx = dynamic_cast<const folly::SSLException*>(&ex);\n  if (infoCallback_ && sslEx) {\n    if (sslEx->getSSLError() == folly::SSLError::CLIENT_RENEGOTIATION) {\n      infoCallback_->onIngressError(*this, kErrorClientRenegotiation);\n    }\n  }\n\n  // We're definitely finished reading. Don't close the write side\n  // of the socket if there are outstanding transactions, though.\n  // Instead, give the transactions a chance to produce any remaining\n  // output.\n  if (sslEx && sslEx->getSSLError() == folly::SSLError::SSL_ERROR) {\n    transportInfo_.sslError = ex.what();\n  }\n  setCloseReason(ConnectionCloseReason::IO_READ_ERROR);\n  shutdownTransport(true, transactions_.empty(), ex.what());\n}\n\nHTTPTransaction* HTTPSession::newPushedTransaction(\n    HTTPCodec::StreamID assocStreamId,\n    HTTPTransaction::PushHandler* handler,\n    ProxygenError* error) noexcept {\n  if (!codec_->supportsPushTransactions()) {\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorPushNotSupported);\n    return nullptr;\n  }\n  CHECK(isDownstream());\n  CHECK_NOTNULL(handler);\n  if (draining_) {\n    // This session doesn't support any more push transactions\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorTransportIsDraining);\n    return nullptr;\n  }\n\n  if (outgoingStreams_ >= maxConcurrentOutgoingStreamsRemote_) {\n    // This session doesn't support any more push transactions\n    SET_PROXYGEN_ERROR_IF(\n        error, ProxygenError::kErrorMaxConcurrentOutgoingStreamLimitReached);\n    return nullptr;\n  }\n\n  HTTPTransaction* txn = createTransaction(\n      codec_->createStream(), assocStreamId, http2::DefaultPriority, error);\n  if (!txn) {\n    return nullptr;\n  }\n  DestructorGuard dg(this);\n  txn->setHandler(handler);\n  return txn;\n}\n\nsize_t HTTPSession::getCodecSendWindowSize() const {\n  const HTTPSettings* settings = codec_->getIngressSettings();\n  if (settings) {\n    return settings->getSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                codec_->getDefaultWindowSize());\n  }\n  return codec_->getDefaultWindowSize();\n}\n\nvoid HTTPSession::onMessageBegin(HTTPCodec::StreamID streamID,\n                                 HTTPMessage* msg) {\n  VLOG(4) << \"processing new msg streamID=\" << streamID << \" \" << *this;\n\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (txn) {\n    if (isDownstream() && txn->isPushed()) {\n      // Push streams are unidirectional (half-closed). If the downstream\n      // attempts to send ingress, abort with STREAM_CLOSED error.\n      HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                       \"Downstream attempts to send ingress, abort.\");\n      ex.setCodecStatusCode(ErrorCode::STREAM_CLOSED);\n      txn->onError(ex);\n    }\n    return; // If this transaction is already registered, no need to add it now\n  }\n\n  if (infoCallback_) {\n    infoCallback_->onRequestBegin(*this);\n  }\n\n  txn = createTransaction(streamID, HTTPCodec::NoStream);\n  if (!txn) {\n    return; // This could happen if the socket is bad.\n  }\n\n  if (!codec_->supportsParallelRequests() && getPipelineStreamCount() > 1) {\n    // The previous transaction hasn't completed yet. Pause reads until\n    // it completes; this requires pausing both transactions.\n\n    // HTTP/1.1 pipeline is detected, and which is incompactible with\n    // ByteEventTracker. Drain all the ByteEvents\n    CHECK(byteEventTracker_);\n    byteEventTracker_->drainByteEvents();\n\n    // drainByteEvents() may detach txn(s). Don't pause read if one txn left\n    if (getPipelineStreamCount() < 2) {\n      DCHECK(readsUnpaused());\n      return;\n    }\n\n    // There must be at least two transactions (we just checked). The previous\n    // txns haven't completed yet. Pause reads until they complete\n    DCHECK_GE(transactions_.size(), 2);\n    std::map<HTTPCodec::StreamID, HTTPTransaction*> sortedTxns;\n    for (auto& x_2 : transactions_) {\n      sortedTxns.emplace(x_2.first, &x_2.second);\n    }\n    for (auto it = ++sortedTxns.rbegin(); it != sortedTxns.rend(); ++it) {\n      DCHECK(it->second->isIngressEOMSeen());\n      it->second->pauseIngress();\n    }\n    sortedTxns.rbegin()->second->pauseIngress();\n    DCHECK_EQ(liveTransactions_, 0);\n    DCHECK(readsPaused());\n  }\n}\n\nvoid HTTPSession::onPushMessageBegin(HTTPCodec::StreamID streamID,\n                                     HTTPCodec::StreamID assocStreamID,\n                                     HTTPMessage* msg) {\n  VLOG(4) << \"processing new push promise streamID=\" << streamID\n          << \" on assocStreamID=\" << assocStreamID << \" \" << *this;\n  if (infoCallback_) {\n    infoCallback_->onRequestBegin(*this);\n  }\n  if (assocStreamID == 0) {\n    VLOG(2) << \"push promise \" << streamID << \" should be associated with \"\n            << \"an active stream=\" << assocStreamID << \" \" << *this;\n    invalidStream(streamID, ErrorCode::PROTOCOL_ERROR);\n    return;\n  }\n\n  if (isDownstream()) {\n    VLOG(2) << \"push promise cannot be sent to upstream \" << *this;\n    invalidStream(streamID, ErrorCode::PROTOCOL_ERROR);\n    return;\n  }\n\n  HTTPTransaction* assocTxn = findTransaction(assocStreamID);\n  if (!assocTxn || assocTxn->isIngressEOMSeen()) {\n    VLOG(2) << \"cannot find the assocTxn=\" << assocTxn\n            << \", or assoc stream is already closed by upstream\" << *this;\n    invalidStream(streamID, ErrorCode::PROTOCOL_ERROR);\n    return;\n  }\n\n  auto txn = createTransaction(streamID, assocStreamID);\n  if (!txn) {\n    return; // This could happen if the socket is bad.\n  }\n\n  if (!assocTxn->onPushedTransaction(txn)) {\n    VLOG(1) << \"Failed to add pushed txn \" << streamID << \" to assoc txn \"\n            << assocStreamID << \" on \" << *this;\n    HTTPException ex(\n        HTTPException::Direction::INGRESS_AND_EGRESS,\n        folly::to<std::string>(\"Failed to add pushed transaction \", streamID));\n    ex.setCodecStatusCode(ErrorCode::REFUSED_STREAM);\n    onError(streamID, ex, true);\n  }\n}\n\nvoid HTTPSession::onHeadersComplete(HTTPCodec::StreamID streamID,\n                                    unique_ptr<HTTPMessage> msg) {\n  // The codec's parser detected the end of an ingress message's\n  // headers.\n  VLOG(4) << \"processing ingress headers complete for \" << *this\n          << \", streamID=\" << streamID;\n\n  if (!codec_->isReusable()) {\n    setCloseReason(ConnectionCloseReason::REQ_NOTREUSABLE);\n  }\n\n  if (infoCallback_) {\n    infoCallback_->onIngressMessage(*this, *msg.get());\n  }\n\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    invalidStream(streamID);\n    return;\n  }\n\n  // Inform observers when request headers (i.e. ingress, from downstream\n  // client) are processed.\n  //\n  // TODO(T227264326) remove the `isRequest` check since it is strictly not\n  // needed. It was added as a work-around to ensure the `requestStarted`\n  // callback will never be invoked for a response, especially in certain\n  // scenario such as H2 pubsub. See D76341533 for details.\n  if (isDownstream() && msg->isRequest()) {\n    if (msg.get()) {\n      const auto event =\n          HTTPSessionObserverInterface::RequestStartedEvent::Builder()\n              .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n              .setRequest(*msg)\n              .setTxnObserverAccessor(txn->getObserverAccessor())\n              .build();\n      sessionObserverContainer_.invokeInterfaceMethod<\n          HTTPSessionObserverInterface::Events::RequestStarted>(\n          [&event](auto observer, auto observed) {\n            observer->requestStarted(observed, event);\n          });\n    }\n  }\n\n  HTTPTransaction::DestructorGuard dg(txn);\n\n  const char* sslCipher =\n      transportInfo_.sslCipher ? transportInfo_.sslCipher->c_str() : nullptr;\n  msg->setSecureInfo(transportInfo_.sslVersion, sslCipher);\n  msg->setSecure(transportInfo_.secure);\n\n  setupOnHeadersComplete(txn, msg.get());\n\n  // The txn may have already been aborted by the handler.\n  // Verify that the txn is not done.\n  if (txn->isIngressComplete() && txn->isEgressComplete()) {\n    return;\n  }\n\n  if (!txn->getHandler()) {\n    txn->sendAbort();\n    return;\n  }\n\n  // Tell the Transaction to start processing the message now\n  // that the full ingress headers have arrived.\n  txn->onIngressHeadersComplete(std::move(msg));\n  if (httpSessionActivityTracker_) {\n    httpSessionActivityTracker_->reportActivity();\n  }\n}\n\nvoid HTTPSession::onBody(HTTPCodec::StreamID streamID,\n                         unique_ptr<IOBuf> chain,\n                         uint16_t padding) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPSession - onBody\");\n  DestructorGuard dg(this);\n  // The codec's parser detected part of the ingress message's\n  // entity-body.\n  uint64_t length = chain->computeChainDataLength();\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    if (connFlowControl_ &&\n        connFlowControl_->ingressBytesProcessed(writeBuf_, length)) {\n      scheduleWrite();\n    }\n    invalidStream(streamID);\n    return;\n  }\n\n  if (HTTPSessionBase::onBodyImpl(std::move(chain), length, padding, txn)) {\n    VLOG(4) << *this << \" pausing due to read limit exceeded.\";\n    pauseReads();\n  }\n}\n\nvoid HTTPSession::onChunkHeader(HTTPCodec::StreamID streamID, size_t length) {\n  // The codec's parser detected a chunk header (meaning that this\n  // connection probably is HTTP/1.1).\n  //\n  // After calling onChunkHeader(), the codec will call onBody() zero\n  // or more times and then call onChunkComplete().\n  //\n  // The reason for this callback on the chunk header is to support\n  // an optimization.  In general, the job of the codec is to present\n  // the HTTPSession with an abstract view of a message,\n  // with all the details of wire formatting hidden.  However, there's\n  // one important case where we want to know about chunking: reverse\n  // proxying where both the client and server streams are HTTP/1.1.\n  // In that scenario, we preserve the server's chunk boundaries when\n  // sending the response to the client, in order to avoid possibly\n  // making the egress packetization worse by rechunking.\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    invalidStream(streamID);\n    return;\n  }\n  txn->onIngressChunkHeader(length);\n}\n\nvoid HTTPSession::onChunkComplete(HTTPCodec::StreamID streamID) {\n  // The codec's parser detected the end of the message body chunk\n  // associated with the most recent call to onChunkHeader().\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    invalidStream(streamID);\n    return;\n  }\n  txn->onIngressChunkComplete();\n}\n\nvoid HTTPSession::onTrailersComplete(HTTPCodec::StreamID streamID,\n                                     unique_ptr<HTTPHeaders> trailers) {\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    invalidStream(streamID);\n    return;\n  }\n  txn->onIngressTrailers(std::move(trailers));\n}\n\nvoid HTTPSession::onMessageComplete(HTTPCodec::StreamID streamID,\n                                    bool upgrade) {\n  DestructorGuard dg(this);\n  // The codec's parser detected the end of the ingress message for\n  // this transaction.\n  VLOG(4) << \"processing ingress message complete for \" << *this\n          << \", streamID=\" << streamID;\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    invalidStream(streamID);\n    return;\n  }\n\n  if (upgrade) {\n    /* Send the upgrade callback to the transaction and the handler. Only\n     * relevant for HTTP/1.1.\n     */\n    ingressUpgraded_ = true;\n    txn->onIngressUpgrade(UpgradeProtocol::TCP);\n    return;\n  }\n\n  decrementTransactionCount(txn, true, false);\n  txn->onIngressEOM();\n\n  // The codec knows, based on the semantics of whatever protocol it\n  // supports, whether it's valid for any more ingress messages to arrive\n  // after this one.  For example, an HTTP/1.1 request containing\n  // \"Connection: close\" indicates the end of the ingress.\n  //\n  // If the connection is not reusable, we close the read side of it\n  // but not the write side.  There are two reasons why more writes\n  // may occur after this point:\n  //   * If there are previous writes buffered up in the\n  //     queue, we need to attempt to complete them.\n  //   * The Handler associated with the transaction may want to\n  //     produce more egress data when the ingress message is fully\n  //     complete.  (As a common example, an application that handles\n  //     form POSTs may not be able to even start generating a response\n  //     until it has received the full request body.)\n  //\n  // There may be additional checks that need to be performed that are\n  // specific to requests or responses, so we call the subclass too.\n  if (!codec_->isReusable() && !codec_->supportsParallelRequests()) {\n    VLOG(4) << *this << \" cannot reuse ingress\";\n    shutdownTransport(true, false);\n  }\n}\n\nvoid HTTPSession::onError(HTTPCodec::StreamID streamID,\n                          const HTTPException& error,\n                          bool newTxn) {\n  DestructorGuard dg(this);\n  // The codec detected an error in the ingress stream, possibly bad\n  // syntax, a truncated message, or bad semantics in the frame.  If reads\n  // are paused, queue up the event; otherwise, process it now.\n  VLOG(4) << \"Error on \" << *this << \", streamID=\" << streamID << \", \" << error;\n\n  if (ingressError_) {\n    return;\n  }\n  if (!codec_->supportsParallelRequests()) {\n    // this error should only prevent us from reading/handling more errors\n    // on serial streams\n    ingressError_ = true;\n    setCloseReason(ConnectionCloseReason::SESSION_PARSE_ERROR);\n  }\n  if ((streamID == 0) && infoCallback_) {\n    infoCallback_->onIngressError(*this, kErrorMessage);\n  }\n\n  if (!streamID) {\n    ingressError_ = true;\n    onSessionParseError(error);\n    return;\n  }\n\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    if (error.hasHttpStatusCode() && streamID != 0) {\n      // If the error has an HTTP code, then parsing was fine, it just was\n      // illegal in a higher level way\n      txn = createTransaction(streamID, HTTPCodec::NoStream);\n      if (infoCallback_) {\n        infoCallback_->onRequestBegin(*this);\n      }\n      if (txn) {\n        handleErrorDirectly(txn, error);\n      }\n    } else if (newTxn) {\n      onNewTransactionParseError(streamID, error);\n    } else {\n      VLOG(4) << *this << \" parse error with invalid transaction\";\n      invalidStream(streamID);\n    }\n    return;\n  }\n\n  if (!txn->getHandler() &&\n      txn->getEgressState() == HTTPTransactionEgressSM::State::Start) {\n    handleErrorDirectly(txn, error);\n    return;\n  }\n\n  txn->onError(error);\n  if (!codec_->isReusable() && transactions_.empty()) {\n    VLOG(4) << *this << \"shutdown from onError\";\n    setCloseReason(ConnectionCloseReason::SESSION_PARSE_ERROR);\n    shutdownTransport(true, true);\n  }\n}\n\nvoid HTTPSession::onAbort(HTTPCodec::StreamID streamID, ErrorCode code) {\n  VLOG(4) << \"stream abort on \" << *this << \", streamID=\" << streamID\n          << \", code=\" << getErrorCodeString(code);\n\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    VLOG(4) << *this\n            << \" abort for unrecognized transaction, streamID= \" << streamID;\n    return;\n  }\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                   folly::to<std::string>(\"Stream aborted, streamID=\",\n                                          streamID,\n                                          \", code=\",\n                                          getErrorCodeString(code)));\n  ex.setProxygenError(kErrorStreamAbort);\n  ex.setCodecStatusCode(code);\n  DestructorGuard dg(this);\n\n  if (abortPushesOnRST_ && isDownstream() && !txn->getAssocTxnId() &&\n      code == ErrorCode::CANCEL) {\n    VLOG(4) << \"Cancel all push txns because assoc txn has been cancelled.\";\n    for (auto assocPushId : txn->getPushedTransactions()) {\n      auto* pushTxn = findTransaction(assocPushId);\n      DCHECK(pushTxn != nullptr);\n      pushTxn->onError(ex);\n    }\n  }\n\n  txn->onError(ex);\n}\n\nvoid HTTPSession::onGoaway(uint64_t lastGoodStreamID,\n                           ErrorCode code,\n                           std::unique_ptr<folly::IOBuf> debugData) {\n  DestructorGuard g(this);\n  VLOG(4) << \"GOAWAY on \" << *this << \", code=\" << getErrorCodeString(code);\n\n  setCloseReason(ConnectionCloseReason::GOAWAY);\n\n  // Drain active transactions and prevent new transactions\n  drain();\n\n  // We give the less-forceful onGoaway() first so that transactions have\n  // a chance to do stat tracking before potentially getting a forceful\n  // onError().\n  invokeOnAllTransactions(\n      [code](HTTPTransaction* txn) { txn->onGoaway(code); });\n\n  // Abort transactions which have been initiated but not created\n  // successfully at the remote end. Upstream transactions are created\n  // with odd transaction IDs and downstream transactions with even IDs.\n  std::vector<HTTPCodec::StreamID> refusedIds;\n  std::vector<HTTPCodec::StreamID> errorIds;\n  std::vector<HTTPCodec::StreamID> pubSubControlIds;\n\n  for (const auto& id : transactionIds_) {\n    if (((bool)(id & 0x01) == isUpstream()) && (id > lastGoodStreamID)) {\n      refusedIds.push_back(id);\n    } else if (code != ErrorCode::NO_ERROR) {\n      // Error goaway -> error all streams\n      errorIds.push_back(id);\n    } else if (lastGoodStreamID < http2::kMaxStreamID &&\n               (controlStreamIds_.find(id) != controlStreamIds_.end())) {\n      // Final (non-error) goaway -> error control streams\n      pubSubControlIds.push_back(id);\n    }\n  }\n  errorOnTransactionIds(refusedIds, kErrorStreamUnacknowledged);\n\n  if (code != ErrorCode::NO_ERROR) {\n    string debugStr;\n    if (debugData) {\n      debugData->coalesce();\n      debugStr = debugData->moveToFbString();\n    }\n    auto msg = folly::to<std::string>(\"GOAWAY error with codec error: \",\n                                      getErrorCodeString(code),\n                                      \" with debug info: \",\n                                      debugStr);\n    errorOnTransactionIds(errorIds, kErrorConnectionReset, msg);\n  }\n\n  errorOnTransactionIds(pubSubControlIds, kErrorStreamAbort);\n}\n\nvoid HTTPSession::onPingRequest(uint64_t data) {\n  VLOG(4) << *this << \" got ping request with data=\" << data;\n\n  TimePoint timestamp = getCurrentTime();\n\n  uint64_t bytesScheduledBeforePing = 0;\n  size_t pingSize = 0;\n  if (writeBufSplit_) {\n    // Stick the ping at the end, we don't know that writeBuf_ begins on a\n    // frame boundary anymore\n    bytesScheduledBeforePing = sessionByteOffset();\n    pingSize = codec_->generatePingReply(writeBuf_, data);\n  } else {\n    // Insert the ping reply to the head of writeBuf_\n    folly::IOBufQueue pingBuf(folly::IOBufQueue::cacheChainLength());\n    pingSize = codec_->generatePingReply(pingBuf, data);\n    pingBuf.append(writeBuf_.move());\n    writeBuf_.append(pingBuf.move());\n    bytesScheduledBeforePing = bytesScheduled_;\n  }\n\n  if (byteEventTracker_) {\n    // addPingByteEvent has logic to shift all ByteEvents after\n    // 'bytesScheduledBeforePing'.  In the case where we're putting it at the\n    // end, there will be no events with an offset as high - so it will be a\n    // no-op.\n    byteEventTracker_->addPingByteEvent(\n        pingSize, timestamp, bytesScheduledBeforePing);\n  }\n  scheduleWrite();\n}\n\nvoid HTTPSession::onPingReply(uint64_t data) {\n  VLOG(4) << *this << \" got ping reply with id=\" << data;\n  if (pingProber_) {\n    pingProber_->onPingReply(data);\n  }\n  if (infoCallback_) {\n    infoCallback_->onPingReplyReceived();\n  }\n\n  const auto pingReplyEvent =\n      HTTPSessionObserverInterface::PingReplyEvent::Builder()\n          .setId(data)\n          .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n          .build();\n  sessionObserverContainer_\n      .invokeInterfaceMethod<HTTPSessionObserverInterface::Events::PingReply>(\n          [&](auto observer, auto observed) {\n            observer->pingReply(observed, pingReplyEvent);\n          });\n}\n\nvoid HTTPSession::onWindowUpdate(HTTPCodec::StreamID streamID,\n                                 uint32_t amount) {\n  VLOG(4) << *this << \" got window update on streamID=\" << streamID << \" for \"\n          << amount << \" bytes.\";\n  HTTPTransaction* txn = findTransaction(streamID);\n  if (!txn) {\n    return;\n  }\n  txn->onIngressWindowUpdate(amount);\n}\n\nvoid HTTPSession::onSettings(const SettingsList& settings) {\n  DestructorGuard g(this);\n  for (auto& setting : settings) {\n    if (setting.id == SettingsId::INITIAL_WINDOW_SIZE) {\n      onSetSendWindow(setting.value);\n    } else if (setting.id == SettingsId::MAX_CONCURRENT_STREAMS) {\n      onSetMaxInitiatedStreams(setting.value);\n    } else if (setting.id == SettingsId::SETTINGS_HTTP_CERT_AUTH) {\n      if (!(verifyCertAuthSetting(setting.value))) {\n        return;\n      }\n    }\n  }\n  if (codec_->generateSettingsAck(writeBuf_) > 0) {\n    scheduleWrite();\n  }\n  if (infoCallback_) {\n    infoCallback_->onSettings(*this, settings);\n  }\n}\n\nvoid HTTPSession::onSettingsAck() {\n  VLOG(4) << *this << \" received settings ack\";\n  if (infoCallback_) {\n    infoCallback_->onSettingsAck(*this);\n  }\n}\n\nvoid HTTPSession::onPriority(HTTPCodec::StreamID streamID,\n                             const HTTPPriority&) {\n  if (getNumIncomingStreams() >= codec_->getEgressSettings()->getSetting(\n                                     SettingsId::MAX_CONCURRENT_STREAMS,\n                                     std::numeric_limits<int32_t>::max())) {\n    invalidStream(streamID, ErrorCode::PROTOCOL_ERROR);\n  }\n}\n\nvoid HTTPSession::onCertificateRequest(uint16_t requestId,\n                                       std::unique_ptr<IOBuf> authRequest) {\n  DestructorGuard dg(this);\n  VLOG(4) << \"CERTIFICATE_REQUEST on\" << *this << \", requestId=\" << requestId;\n\n  if (!secondAuthManager_) {\n    return;\n  }\n\n  std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> authenticator;\n  auto fizzBase = getTransport()->getUnderlyingTransport<AsyncFizzBase>();\n  if (fizzBase) {\n    if (isUpstream()) {\n      authenticator =\n          secondAuthManager_->getAuthenticator(*fizzBase,\n                                               TransportDirection::UPSTREAM,\n                                               requestId,\n                                               std::move(authRequest));\n    } else {\n      authenticator =\n          secondAuthManager_->getAuthenticator(*fizzBase,\n                                               TransportDirection::DOWNSTREAM,\n                                               requestId,\n                                               std::move(authRequest));\n    }\n  } else {\n    VLOG(4) << \"Underlying transport does not support secondary \"\n               \"authentication.\";\n    return;\n  }\n  if (codec_->generateCertificate(writeBuf_,\n                                  authenticator.first,\n                                  std::move(authenticator.second)) > 0) {\n    scheduleWrite();\n  }\n}\n\nvoid HTTPSession::onCertificate(uint16_t certId,\n                                std::unique_ptr<IOBuf> authenticator) {\n  DestructorGuard dg(this);\n  VLOG(4) << \"CERTIFICATE on\" << *this << \", certId=\" << certId;\n\n  if (!secondAuthManager_) {\n    return;\n  }\n\n  bool isValid = false;\n  auto fizzBase = getTransport()->getUnderlyingTransport<AsyncFizzBase>();\n  if (fizzBase) {\n    if (isUpstream()) {\n      isValid = secondAuthManager_->validateAuthenticator(\n          *fizzBase,\n          TransportDirection::UPSTREAM,\n          certId,\n          std::move(authenticator));\n    } else {\n      isValid = secondAuthManager_->validateAuthenticator(\n          *fizzBase,\n          TransportDirection::DOWNSTREAM,\n          certId,\n          std::move(authenticator));\n    }\n  } else {\n    VLOG(4) << \"Underlying transport does not support secondary \"\n               \"authentication.\";\n    return;\n  }\n  if (isValid) {\n    VLOG(4) << \"Successfully validated the authenticator provided by the peer.\";\n  } else {\n    VLOG(4) << \"Failed to validate the authenticator provided by the peer\";\n  }\n}\n\nvoid HTTPSession::onSetSendWindow(uint32_t windowSize) {\n  VLOG(4) << *this << \" got send window size adjustment. new=\" << windowSize;\n  invokeOnAllTransactions([windowSize](HTTPTransaction* txn) {\n    txn->onIngressSetSendWindow(windowSize);\n  });\n}\n\nvoid HTTPSession::onSetMaxInitiatedStreams(uint32_t maxTxns) {\n  VLOG(4) << *this << \" got new maximum number of concurrent txns \"\n          << \"we can initiate: \" << maxTxns;\n  const bool didSupport = supportsMoreTransactions();\n  maxConcurrentOutgoingStreamsRemote_ = maxTxns;\n  if (infoCallback_ && didSupport != supportsMoreTransactions()) {\n    if (didSupport) {\n      infoCallback_->onSettingsOutgoingStreamsFull(*this);\n    } else {\n      infoCallback_->onSettingsOutgoingStreamsNotFull(*this);\n    }\n  }\n}\n\nsize_t HTTPSession::sendSettings() {\n  size_t size = codec_->generateSettings(writeBuf_);\n  scheduleWrite();\n  return size;\n}\n\nvoid HTTPSession::pauseIngress(HTTPTransaction* txn) noexcept {\n  VLOG(4) << *this << \" pausing streamID=\" << txn->getID()\n          << \", liveTransactions_ was \" << liveTransactions_;\n  CHECK_GT(liveTransactions_, 0);\n  --liveTransactions_;\n\n  if (liveTransactions_ == 0) {\n    pauseReads();\n  }\n}\n\nvoid HTTPSession::resumeIngress(HTTPTransaction* txn) noexcept {\n  VLOG(4) << *this << \" resuming streamID=\" << txn->getID()\n          << \", liveTransactions_ was \" << liveTransactions_;\n  ++liveTransactions_;\n\n  // This function can be called from detach(), in which case liveTransactions_\n  // may go to 1 briefly, even though we are still anit-pipelining.\n  if (liveTransactions_ == 1 &&\n      (codec_->supportsParallelRequests() || getPipelineStreamCount() <= 1)) {\n    resumeReads();\n  }\n}\n\nvoid HTTPSession::transactionTimeout(HTTPTransaction* txn) noexcept {\n  // A transaction has timed out.  If the transaction does not have\n  // a Handler yet, because we haven't yet received the full request\n  // headers, we give it a DirectResponseHandler that generates an\n  // error page.\n  VLOG(3) << \"Transaction timeout for streamID=\" << txn->getID();\n  if (!codec_->supportsParallelRequests()) {\n    // this error should only prevent us from reading/handling more errors\n    // on serial streams\n    ingressError_ = true;\n  }\n\n  if (!txn->getHandler() &&\n      txn->getEgressState() == HTTPTransactionEgressSM::State::Start) {\n    VLOG(4) << *this << \" Timed out receiving headers\";\n    if (infoCallback_) {\n      infoCallback_->onIngressError(*this, kErrorTimeout);\n    }\n    if (codec_->supportsParallelRequests()) {\n      // This can only happen with HTTP/2 where the HEADERS frame is incomplete\n      // and we time out waiting for the CONTINUATION.  Abort the request.\n      //\n      // It would maybe be a little nicer to use the timeout handler for these\n      // also.\n      txn->sendAbort();\n      return;\n    }\n\n    VLOG(4) << *this << \" creating direct error handler\";\n    auto handler = getTransactionTimeoutHandler(txn);\n    txn->setHandler(handler);\n  }\n\n  // Tell the transaction about the timeout.  The transaction will\n  // communicate the timeout to the handler, and the handler will\n  // decide how to proceed.\n  txn->onIngressTimeout();\n}\n\nvoid HTTPSession::sendHeaders(HTTPTransaction* txn,\n                              const HTTPMessage& headers,\n                              HTTPHeaderSize* size,\n                              bool includeEOM) noexcept {\n  CHECK(started_);\n  unique_ptr<IOBuf> goawayBuf;\n  if (draining_ && isUpstream() && codec_->isReusable() &&\n      allTransactionsStarted()) {\n    // For HTTP/1.1, add Connection: close\n    // For H2, save the goaway for AFTER the request\n    auto writeBuf = writeBuf_.move();\n    drainImpl();\n    goawayBuf = writeBuf_.move();\n    writeBuf_.append(std::move(writeBuf));\n  }\n\n  const bool wasReusable = codec_->isReusable();\n  const uint64_t oldOffset = sessionByteOffset();\n  auto assocStream = txn->getAssocTxnId();\n  if (headers.isRequest() && assocStream) {\n    // Only PUSH_PROMISE (not push response) has an associated stream\n    codec_->generatePushPromise(\n        writeBuf_, txn->getID(), headers, *assocStream, includeEOM, size);\n  } else {\n    codec_->generateHeader(writeBuf_, txn->getID(), headers, includeEOM, size);\n  }\n  const uint64_t newOffset = sessionByteOffset();\n\n  // for push response count towards the MAX_CONCURRENT_STREAMS limit\n  if (isDownstream() && headers.isResponse() && txn->isPushed()) {\n    incrementOutgoingStreams(txn);\n  }\n\n  // For all upstream headers, addFirstHeaderByteEvent should be added\n  // For all downstream, only response headers need addFirstHeaderByteEvent\n  bool shouldAddFirstHeaderByteEvent =\n      isUpstream() || (isDownstream() && headers.isResponse());\n  if (shouldAddFirstHeaderByteEvent && newOffset > oldOffset &&\n      !txn->testAndSetFirstHeaderByteSent() && byteEventTracker_) {\n    byteEventTracker_->addFirstHeaderByteEvent(newOffset, txn);\n  }\n\n  if (size) {\n    VLOG(4) << *this << \" sending headers, size=\" << size->compressed\n            << \", uncompressedSize=\" << size->uncompressed;\n  }\n  if (goawayBuf) {\n    VLOG(4) << *this << \" moved GOAWAY to end of writeBuf\";\n    writeBuf_.append(std::move(goawayBuf));\n  }\n  if (includeEOM) {\n    CHECK_GE(newOffset, oldOffset);\n    commonEom(txn, newOffset - oldOffset, true);\n  }\n  scheduleWrite();\n  onHeadersSent(headers, wasReusable);\n\n  // If this is a client sending request headers to upstream\n  // invoke requestStarted event for attached observers.\n  //\n  // TODO(T227264326) remove the `isRequest` check since it is strictly not\n  // needed. It was added as a work-around to ensure the `requestStarted`\n  // callback will never be invoked for a response, especially in certain\n  // scenario such as H2 pubsub. See D76341533 for details.\n  if (isUpstream() && headers.isRequest()) {\n    const auto event =\n        HTTPSessionObserverInterface::RequestStartedEvent::Builder()\n            .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n            .setRequest(headers)\n            .setTxnObserverAccessor(txn->getObserverAccessor())\n            .build();\n    sessionObserverContainer_.invokeInterfaceMethod<\n        HTTPSessionObserverInterface::Events::RequestStarted>(\n        [&event](auto observer, auto observed) {\n          observer->requestStarted(observed, event);\n        });\n  }\n}\n\nvoid HTTPSession::commonEom(HTTPTransaction* txn,\n                            size_t encodedSize,\n                            bool piggybacked) noexcept {\n  HTTPSessionBase::handleLastByteEvents(byteEventTracker_.get(),\n                                        txn,\n                                        encodedSize,\n                                        sessionByteOffset(),\n                                        piggybacked);\n  onEgressMessageFinished(txn);\n}\n\nsize_t HTTPSession::sendBody(HTTPTransaction* txn,\n                             std::unique_ptr<folly::IOBuf> body,\n                             bool includeEOM,\n                             bool trackLastByteFlushed) noexcept {\n  uint64_t offset = sessionByteOffset();\n  size_t bodyLen = body ? body->computeChainDataLength() : 0;\n  size_t encodedSize = codec_->generateBody(writeBuf_,\n                                            txn->getID(),\n                                            std::move(body),\n                                            HTTPCodec::NoPadding,\n                                            includeEOM);\n  CHECK(inLoopCallback_);\n  bodyBytesPerWriteBuf_ += bodyLen;\n  if (httpSessionActivityTracker_) {\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        offset, encodedSize, byteEventTracker_.get(), txn);\n  }\n  if (encodedSize > 0 && !txn->testAndSetFirstByteSent() && byteEventTracker_) {\n    byteEventTracker_->addFirstBodyByteEvent(offset + 1, txn);\n  }\n\n  if (trackLastByteFlushed && encodedSize > 0 && byteEventTracker_) {\n    byteEventTracker_->addTrackedByteEvent(txn, offset + encodedSize);\n  }\n\n  if (includeEOM) {\n    VLOG(5) << *this << \" sending EOM in body for streamID=\" << txn->getID();\n    commonEom(txn, encodedSize, true);\n  }\n  return encodedSize;\n}\n\nsize_t HTTPSession::sendChunkHeader(HTTPTransaction* txn,\n                                    size_t length) noexcept {\n  size_t encodedSize =\n      codec_->generateChunkHeader(writeBuf_, txn->getID(), length);\n  scheduleWrite();\n  return encodedSize;\n}\n\nsize_t HTTPSession::sendChunkTerminator(HTTPTransaction* txn) noexcept {\n  size_t encodedSize = codec_->generateChunkTerminator(writeBuf_, txn->getID());\n  scheduleWrite();\n  return encodedSize;\n}\n\nvoid HTTPSession::onEgressMessageFinished(HTTPTransaction* txn, bool withRST) {\n  // If the semantics of the protocol don't permit more messages\n  // to be read or sent on this connection, close the socket in one or\n  // more directions.\n  CHECK(!transactions_.empty());\n\n  if (infoCallback_) {\n    infoCallback_->onRequestEnd(*this, txn->getMaxDeferredSize());\n  }\n  auto oldStreamCount = getPipelineStreamCount();\n  decrementTransactionCount(txn, false, true);\n\n  // We should shutdown reads if we are closing with RST or we aren't\n  // interested in any further messages (ie if we are a downstream session).\n  // Upgraded sessions have independent ingress and egress, and the reads\n  // need not be shutdown on egress finish.\n  if (withRST) {\n    // Let any queued writes complete, but send a RST when done.\n    VLOG(4) << *this << \" resetting egress after this message\";\n    resetAfterDrainingWrites_ = true;\n    setCloseReason(ConnectionCloseReason::TRANSACTION_ABORT);\n    shutdownTransport(true, true);\n  } else if (!codec_->isReusable() || readsShutdown()) {\n    if (transactions_.size() == 1) {\n      // the reason is already set (either not reusable or readshutdown).\n\n      // Defer normal shutdowns until after the end of the loop.  This\n      // handles an edge case with direct responses with Connection:\n      // close served before ingress EOM.  The remainder of the ingress\n      // message may be in the parse loop, so give it a chance to\n      // finish out and avoid a kErrorEOF\n\n      // we can get here during shutdown, in that case do not schedule a\n      // shutdown callback again\n      if (!shutdownTransportCb_) {\n        // Just for safety, the following bumps the refcount on this session\n        // to keep it live until the loopCb runs\n        shutdownTransportCb_ =\n            std::make_unique<ShutdownTransportCallback>(this);\n        sock_->getEventBase()->runInLoop(shutdownTransportCb_.get());\n      }\n    } else {\n      // Parsing of new transactions is not going to work anymore, but we can't\n      // shutdown immediately because there are outstanding transactions.\n      // This sets the draining_ flag, which will trigger shutdown checks as\n      // existing transactions detach.\n      drain();\n    }\n  } else {\n    maybeResumePausedPipelinedTransaction(oldStreamCount,\n                                          txn->getSequenceNumber());\n  }\n}\n\nvoid HTTPSession::ShutdownTransportCallback::runLoopCallback() noexcept {\n  VLOG(4) << *session_ << \" shutdown from onEgressMessageFinished\";\n  // Continuation of the above logic.  Writes will be shutdown, reads are\n  // forced shutdown in specific cases:\n  //  1. There's an ingress error\n  //  2. This is a downstream (server) HTTP/1x session that has not been\n  //     upgraded via CONNECT.\n  //     For upstream, the response may be pending\n  //     For upgrade, the connection nominally supports half-close\n  //     For H2, we should wait for the peer FIN, or timeout\n  //\n  //     H1 shutdowns in this case are justified because we've sent a full\n  //      response and the codec is not re-usable.  There may be more request\n  //      data coming, but maybe not.\n  bool shutdownReads = session_->ingressError_ ||\n                       (session_->isDownstream() &&\n                        !session_->codec_->supportsParallelRequests() &&\n                        !session_->ingressUpgraded_);\n  if (session_->isDownstream() && !session_->readsShutdown() &&\n      !shutdownReads) {\n    auto to = session_->getDrainTimeout();\n    VLOG(4) << \"Starting drain timer t=\" << to.count();\n    session_->resetTimeoutTo(to);\n  }\n  auto dg = dg_.release();\n  session_->shutdownTransport(shutdownReads, true);\n  delete dg;\n}\n\nsize_t HTTPSession::sendPadding(HTTPTransaction* txn, uint16_t bytes) noexcept {\n  auto encodedSize = codec_->generatePadding(writeBuf_, txn->getID(), bytes);\n  VLOG(4) << *this << \" sending \" << bytes\n          << \" bytes of padding, encodedSize=\" << encodedSize\n          << \" for streamID=\" << txn->getID();\n  if (encodedSize > 0) {\n    scheduleWrite();\n  }\n  return encodedSize;\n}\n\nsize_t HTTPSession::sendEOM(HTTPTransaction* txn,\n                            const HTTPHeaders* trailers) noexcept {\n\n  VLOG(4) << *this << \" sending EOM for streamID=\" << txn->getID()\n          << \" trailers=\" << (trailers ? \"yes\" : \"no\");\n\n  size_t encodedSize = 0;\n  if (trailers) {\n    encodedSize = codec_->generateTrailers(writeBuf_, txn->getID(), *trailers);\n  } else {\n    encodedSize = codec_->generateEOM(writeBuf_, txn->getID());\n  }\n\n  commonEom(txn, encodedSize, false);\n  return encodedSize;\n}\n\nsize_t HTTPSession::sendAbort(HTTPTransaction* txn,\n                              ErrorCode statusCode) noexcept {\n  // Ask the codec to generate an abort indicator for the transaction.\n  // Depending on the protocol, this may be a no-op.\n  // Schedule a network write to send out whatever egress we might\n  // have queued up.\n  VLOG(4) << *this << \" sending abort for streamID=\" << txn->getID();\n  // drain this transaction's writeBuf instead of flushing it\n  // then enqueue the abort directly into the Session buffer,\n  // hence with max priority.\n  size_t rstStreamSize =\n      codec_->generateRstStream(writeBuf_, txn->getID(), statusCode);\n\n  if (!codec_->isReusable()) {\n    setCloseReason(ConnectionCloseReason::TRANSACTION_ABORT);\n  }\n\n  scheduleWrite();\n\n  bool sendTcpRstFallback = !rstStreamSize;\n  onEgressMessageFinished(txn, sendTcpRstFallback);\n  return rstStreamSize;\n}\n\nsize_t HTTPSession::changePriority(HTTPTransaction* /*txn*/,\n                                   HTTPPriority /*pri*/) noexcept {\n  return 0;\n}\n\nvoid HTTPSession::setSecondAuthManager(\n    std::unique_ptr<SecondaryAuthManagerBase> secondAuthManager) {\n  secondAuthManager_ = std::move(secondAuthManager);\n}\n\nSecondaryAuthManagerBase* HTTPSession::getSecondAuthManager() const {\n  return secondAuthManager_.get();\n}\n\n/**\n * Send a CERTIFICATE_REQUEST frame. If the underlying protocol doesn't\n * support secondary authentication, this is a no-op and 0 is returned.\n */\nsize_t HTTPSession::sendCertificateRequest(\n    std::unique_ptr<folly::IOBuf> certificateRequestContext,\n    std::vector<fizz::Extension> extensions) {\n  // Check if both sending and receiving peer have advertised valid\n  // SETTINGS_HTTP_CERT_AUTH setting. Otherwise, the frames for secondary\n  // authentication should not be sent.\n  auto ingressSettings = codec_->getIngressSettings();\n  auto egressSettings = codec_->getEgressSettings();\n  if (ingressSettings && egressSettings) {\n    if (ingressSettings->getSetting(SettingsId::SETTINGS_HTTP_CERT_AUTH, 0) ==\n            0 ||\n        egressSettings->getSetting(SettingsId::SETTINGS_HTTP_CERT_AUTH, 0) ==\n            0) {\n      VLOG(4) << \"Secondary certificate authentication is not supported.\";\n      return 0;\n    }\n  }\n  if (!secondAuthManager_) {\n    return 0;\n  }\n  auto authRequest = secondAuthManager_->createAuthRequest(\n      std::move(certificateRequestContext), std::move(extensions));\n  auto encodedSize = codec_->generateCertificateRequest(\n      writeBuf_, authRequest.first, std::move(authRequest.second));\n  if (encodedSize > 0) {\n    scheduleWrite();\n  } else {\n    VLOG(4) << \"Failed to generate CERTIFICATE_REQUEST frame.\";\n  }\n  return encodedSize;\n}\n\nvoid HTTPSession::decrementTransactionCount(HTTPTransaction* txn,\n                                            bool ingressEOM,\n                                            bool egressEOM) {\n  if ((isUpstream() && !txn->isPushed()) ||\n      (isDownstream() && txn->isPushed())) {\n    if (ingressEOM && txn->testAndClearIsCountedTowardsStreamLimit()) {\n      DCHECK_NE(outgoingStreams_, 0);\n      outgoingStreams_--;\n    }\n  } else {\n    if (egressEOM && txn->testAndClearIsCountedTowardsStreamLimit()) {\n      DCHECK_NE(incomingStreams_, 0);\n      incomingStreams_--;\n    }\n  }\n}\n\n// This is a kludgy function because it requires the caller to remember\n// the old value of pipelineStreamCount from before it calls\n// decrementTransactionCount.  I'm trying to avoid yet more state in\n// HTTPSession.  If decrementTransactionCount actually closed a stream\n// and there is still a pipelinable stream, then it was pipelining\nbool HTTPSession::maybeResumePausedPipelinedTransaction(size_t oldStreamCount,\n                                                        uint32_t txnSeqn) {\n  if (!codec_->supportsParallelRequests() && !transactions_.empty()) {\n    auto pipelineStreamCount = getPipelineStreamCount();\n    if (pipelineStreamCount < oldStreamCount && pipelineStreamCount == 1) {\n      // For H1, StreamID = txnSeqn + 1\n      auto curStreamId = txnSeqn + 1;\n      auto txnIt = transactions_.find(curStreamId + 1);\n      CHECK(txnIt != transactions_.end());\n      DCHECK(transactionIds_.contains(curStreamId + 1));\n      auto& nextTxn = txnIt->second;\n      DCHECK_EQ(nextTxn.getSequenceNumber(), txnSeqn + 1);\n      DCHECK(!nextTxn.isIngressComplete());\n      DCHECK(nextTxn.isIngressPaused());\n      VLOG(4) << \"Resuming paused pipelined txn \" << nextTxn;\n      nextTxn.resumeIngress();\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid HTTPSession::detach(HTTPTransaction* txn) noexcept {\n  DestructorGuard guard(this);\n  HTTPCodec::StreamID streamID = txn->getID();\n  auto txnSeqn = txn->getSequenceNumber();\n  auto it = transactions_.find(txn->getID());\n  DCHECK(it != transactions_.end());\n  DCHECK(transactionIds_.contains(txn->getID()));\n\n  if (txn->isIngressPaused()) {\n    // Someone detached a transaction that was paused.  Make the resumeIngress\n    // call to keep liveTransactions_ in order\n    VLOG(4) << *this << \" detached paused transaction=\" << streamID;\n    resumeIngress(txn);\n  }\n\n  VLOG(4) << *this << \" removing streamID=\" << streamID\n          << \", liveTransactions was \" << liveTransactions_;\n  CHECK_GT(liveTransactions_, 0);\n  liveTransactions_--;\n\n  if (txn->isPushed()) {\n    auto assocTxn = findTransaction(*txn->getAssocTxnId());\n    if (assocTxn) {\n      assocTxn->removePushedTransaction(streamID);\n    }\n  }\n\n  // do not track a detached control stream\n  controlStreamIds_.erase(txn->getID());\n\n  auto oldStreamCount = getPipelineStreamCount();\n  decrementTransactionCount(txn, true, true);\n  if (lastTxn_ == txn) {\n    lastTxn_ = nullptr;\n  }\n  DCHECK(transactionIds_.contains(it->first));\n  transactionIds_.erase(it->first);\n  transactions_.erase(it);\n\n  if (transactions_.empty()) {\n    HTTPSessionBase::setLatestActive();\n    if (pingProber_) {\n      pingProber_->cancelProbes();\n    }\n    if (infoCallback_) {\n      infoCallback_->onDeactivateConnection(*this);\n    }\n    if (getConnectionManager()) {\n      getConnectionManager()->onDeactivated(*this);\n    }\n  }\n\n  if (infoCallback_) {\n    infoCallback_->onTransactionDetached(*this);\n  }\n\n  if (!readsShutdown()) {\n    if (maybeResumePausedPipelinedTransaction(oldStreamCount, txnSeqn)) {\n      return;\n    } else {\n      // this will resume reads if they were paused (eg: 0 HTTP transactions)\n      resumeReads();\n    }\n  }\n\n  if (liveTransactions_ == 0 && transactions_.empty() && !isScheduled() &&\n      codec_->isReusable()) {\n    // Start the idle timer again\n    resetTimeout();\n    // if transactions empty and codec is not re-usable, fin timeout scheduled\n    // from onEgressMessageFinished/ShutdownTransportCallback\n  }\n\n  // It's possible that this is the last transaction in the session,\n  // so check whether the conditions for shutdown are satisfied.\n  if (transactions_.empty()) {\n    if (shouldShutdown()) {\n      writesDraining_ = true;\n    }\n    // Handle the case where we are draining writes but all remaining\n    // transactions terminated with no egress.\n    if (writesDraining_ && !writesShutdown() && !hasMoreWrites()) {\n      shutdownTransport(false, true);\n      return;\n    }\n  }\n  checkForShutdown();\n}\n\nsize_t HTTPSession::sendWindowUpdate(HTTPTransaction* txn,\n                                     uint32_t bytes) noexcept {\n  size_t sent = codec_->generateWindowUpdate(writeBuf_, txn->getID(), bytes);\n  if (sent) {\n    scheduleWrite();\n  }\n  return sent;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHTTPSession::sendWTMaxData(uint64_t /*maxData*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHTTPSession::sendWTMaxStreams(uint64_t /*maxStreams*/, bool /*isBidi*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHTTPSession::sendWTStreamsBlocked(uint64_t /*maxStreams*/, bool /*isBidi*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nHTTPSession::sendWTDataBlocked(uint64_t /*maxData*/) {\n  return folly::unit;\n}\n\nbool HTTPSession::usesEncodedApplicationErrorCodes() {\n  return true;\n}\n\nvoid HTTPSession::notifyIngressBodyProcessed(uint32_t bytes) noexcept {\n  if (HTTPSessionBase::notifyBodyProcessed(bytes)) {\n    resumeReads();\n  }\n  if (connFlowControl_ &&\n      connFlowControl_->ingressBytesProcessed(writeBuf_, bytes)) {\n    scheduleWrite();\n  }\n}\n\nvoid HTTPSession::notifyEgressBodyBuffered(int64_t bytes) noexcept {\n  if (HTTPSessionBase::notifyEgressBodyBuffered(bytes, true) &&\n      !inLoopCallback_ && !isLoopCallbackScheduled()) {\n    sock_->getEventBase()->runInLoop(this);\n  }\n}\n\nbool HTTPSession::getCurrentTransportInfoWithoutUpdate(\n    TransportInfo* tinfo) const {\n  auto sock = sock_->getUnderlyingTransport<AsyncSocket>();\n  if (sock) {\n    tinfo->initWithSocket(sock);\n#if defined(__linux__) || defined(__FreeBSD__)\n    tinfo->readTcpCongestionControl(sock);\n    tinfo->readMaxPacingRate(sock);\n#endif // defined(__linux__) || defined(__FreeBSD__)\n    tinfo->totalBytes = sock->getRawBytesWritten();\n    return true;\n  }\n  return false;\n}\n\nbool HTTPSession::getCurrentTransportInfo(TransportInfo* tinfo) {\n  if (getCurrentTransportInfoWithoutUpdate(tinfo)) {\n    // some fields are the same with the setup transport info\n    tinfo->setupTime = transportInfo_.setupTime;\n    tinfo->secure = transportInfo_.secure;\n    tinfo->sslSetupTime = transportInfo_.sslSetupTime;\n    tinfo->sslVersion = transportInfo_.sslVersion;\n    tinfo->sslCipher = transportInfo_.sslCipher;\n    tinfo->sslResume = transportInfo_.sslResume;\n    tinfo->appProtocol = transportInfo_.appProtocol;\n    tinfo->sslError = transportInfo_.sslError;\n#if defined(__linux__) || defined(__FreeBSD__)\n    tinfo->recvwnd = tinfo->tcpinfo.tcpi_rcv_space\n                     << tinfo->tcpinfo.tcpi_rcv_wscale;\n    // update connection transport info with the latest RTT\n    if (tinfo->tcpinfo.tcpi_rtt > 0) {\n      transportInfo_.tcpinfo.tcpi_rtt = tinfo->tcpinfo.tcpi_rtt;\n      transportInfo_.rtt = std::chrono::microseconds(tinfo->tcpinfo.tcpi_rtt);\n    }\n    transportInfo_.rtx = tinfo->rtx;\n#elif defined(__APPLE__)\n    tinfo->recvwnd = tinfo->tcpinfo.tcpi_rcv_wnd\n                     << tinfo->tcpinfo.tcpi_rcv_wscale;\n#endif\n    return true;\n  }\n  return false;\n}\n\nvoid HTTPSession::setHeaderIndexingStrategy(\n    const HeaderIndexingStrategy* strat) {\n  if (isHTTP2CodecProtocol(codec_->getProtocol())) {\n    auto* h2Codec = dynamic_cast<HTTP2Codec*>(codec_.getChainEndPtr());\n    if (h2Codec) {\n      h2Codec->setHeaderIndexingStrategy(strat);\n    }\n  }\n}\n\nvoid HTTPSession::getFlowControlInfo(HTTPTransaction::FlowControlInfo* info) {\n  info->sessionWritesPaused_ = writesPaused();\n  info->sessionReadsPaused_ = readsPaused();\n  info->flowControlEnabled_ = connFlowControl_ != nullptr;\n  if (connFlowControl_) {\n    info->sessionRecvWindow_ = connFlowControl_->getRecvWindow().getCapacity();\n    info->sessionSendWindow_ = connFlowControl_->getSendWindow().getSize();\n    info->sessionRecvOutstanding_ =\n        connFlowControl_->getRecvWindow().getOutstanding();\n    info->sessionSendOutstanding_ =\n        connFlowControl_->getSendWindow().getOutstanding();\n  }\n}\n\nHTTPTransaction::Transport::Type HTTPSession::getSessionType() const noexcept {\n  return getType();\n}\n\nunique_ptr<IOBuf> HTTPSession::getNextToSend(bool* cork,\n                                             bool* timestampTx,\n                                             bool* timestampAck) {\n  // limit ourselves to one outstanding write at a time (onWriteSuccess calls\n  // scheduleWrite)\n  if (numActiveWrites_ > 0 || writesShutdown()) {\n    VLOG(4) << \"skipping write during this loop, numActiveWrites_=\"\n            << numActiveWrites_ << \" writesShutdown()=\" << writesShutdown();\n    return nullptr;\n  }\n\n  // We always tack on at least one body packet to the current write buf\n  // This ensures that a short HTTPS response will go out in a single SSL record\n  while (!txnEgressQueue_.empty()) {\n    uint32_t toSend = kWriteReadyMax;\n    if (connFlowControl_) {\n      if (connFlowControl_->getAvailableSend() == 0) {\n        VLOG(4) << \"Session-level send window is full, skipping remaining \"\n                << \"body writes this loop\";\n        break;\n      }\n      toSend = std::min(toSend, connFlowControl_->getAvailableSend());\n    }\n    txnEgressQueue_.nextEgress(nextEgressResults_);\n    CHECK(!nextEgressResults_.empty()); // Queue was non empty, so this must be\n    // The maximum we will send for any transaction in this loop\n    uint32_t txnMaxToSend = toSend * nextEgressResults_.front().second;\n    if (txnMaxToSend == 0) {\n      // toSend is smaller than the number of transactions.  Give all egress\n      // to the first transaction\n      nextEgressResults_.erase(++nextEgressResults_.begin(),\n                               nextEgressResults_.end());\n      txnMaxToSend = std::min(toSend, egressBodySizeLimit_);\n      nextEgressResults_.front().second = 1;\n    }\n    if (nextEgressResults_.size() > 1 && txnMaxToSend > egressBodySizeLimit_) {\n      // Cap the max to egressBodySizeLimit_, and recompute toSend accordingly\n      txnMaxToSend = egressBodySizeLimit_;\n      toSend = txnMaxToSend / nextEgressResults_.front().second;\n    }\n    // split allowed by relative weight, with some minimum\n    for (auto txnPair : nextEgressResults_) {\n      uint32_t txnAllowed = txnPair.second * toSend;\n      if (nextEgressResults_.size() > 1) {\n        CHECK_LE(txnAllowed, egressBodySizeLimit_);\n      }\n      if (connFlowControl_) {\n        CHECK_LE(txnAllowed, connFlowControl_->getAvailableSend());\n      }\n      if (txnAllowed == 0) {\n        // The ratio * toSend was so small this txn gets nothing.\n        VLOG(4) << *this << \" breaking egress loop on 0 txnAllowed\";\n        break;\n      }\n\n      VLOG(4) << *this << \" egressing txnID=\" << txnPair.first->getID()\n              << \" allowed=\" << txnAllowed;\n      txnPair.first->onWriteReady(txnAllowed, txnPair.second);\n    }\n    nextEgressResults_.clear();\n    // it can be empty because of HTTPTransaction rate limiting.  We should\n    // change rate limiting to clearPendingEgress while waiting.\n    if (!writeBuf_.empty()) {\n      break;\n    }\n  }\n  *timestampTx = false;\n  *timestampAck = false;\n  if (byteEventTracker_) {\n    uint64_t needed = byteEventTracker_->preSend(\n        cork, timestampTx, timestampAck, bytesWritten_);\n    if (needed > 0) {\n      VLOG(5) << *this\n              << \" writeBuf_.chainLength(): \" << writeBuf_.chainLength()\n              << \" txnEgressQueue_.empty(): \" << txnEgressQueue_.empty();\n\n      if (needed < writeBuf_.chainLength()) {\n        // split the next SOM / EOM chunk\n        VLOG(5) << *this << \" splitting \" << needed << \" bytes out of a \"\n                << writeBuf_.chainLength() << \" bytes IOBuf\";\n        *cork = true;\n        if (sessionStats_) {\n          sessionStats_->recordPresendIOSplit();\n        }\n        writeBufSplit_ = true;\n        return writeBuf_.split(needed);\n      } else {\n        CHECK_EQ(needed, writeBuf_.chainLength());\n      }\n    }\n  }\n\n  // cork if there are txns with pending egress and room to send them\n  *cork = !txnEgressQueue_.empty() && !isConnWindowFull();\n  return writeBuf_.move();\n}\n\nvoid HTTPSession::runLoopCallback() noexcept {\n  // We schedule this callback to run at the end of an event\n  // loop iteration if either of two conditions has happened:\n  //   * The session has generated some egress data (see scheduleWrite())\n  //   * Reads have become unpaused (see resumeReads())\n  DestructorGuard dg(this);\n  inLoopCallback_ = true;\n  auto scopeg = folly::makeGuard([this] {\n    inLoopCallback_ = false;\n    // This ScopeGuard needs to be under the above DestructorGuard\n    updatePendingWrites();\n    checkForShutdown();\n  });\n  VLOG(5) << *this << \" in loop callback\";\n\n  if (isDownstream() && !txnEgressQueue_.empty()) {\n    const auto event =\n        HTTPSessionObserverInterface::PreWriteEvent::Builder()\n            .setPendingEgressBytes(getPendingWriteSize())\n            .setTimestamp(HTTPSessionObserverInterface::Clock::now())\n            .build();\n    sessionObserverContainer_\n        .invokeInterfaceMethod<HTTPSessionObserverInterface::Events::PreWrite>(\n            [&event](auto observer, auto observed) {\n              observer->preWrite(observed, event);\n            });\n  }\n\n  uint64_t bytesWritten = 0;\n  for (uint32_t i = 0; i < kMaxWritesPerLoop; ++i) {\n    bodyBytesPerWriteBuf_ = 0;\n    bool cork = true;\n    bool timestampTx = false;\n    bool timestampAck = false;\n    unique_ptr<IOBuf> writeBuf =\n        getNextToSend(&cork, &timestampTx, &timestampAck);\n\n    if (!writeBuf) {\n      break;\n    }\n    uint64_t len = writeBuf->computeChainDataLength();\n    VLOG(11) << *this << \" bytes of egress to be written: \" << len\n             << \" cork:\" << cork << \" timestampTx:\" << timestampTx\n             << \" timestampAck:\" << timestampAck;\n    if (len == 0) {\n      checkForShutdown();\n      return;\n    }\n\n    folly::WriteFlags flags = folly::WriteFlags::NONE;\n    flags |= (cork) ? folly::WriteFlags::CORK : folly::WriteFlags::NONE;\n    flags |= (timestampTx) ? folly::WriteFlags::TIMESTAMP_TX\n                           : folly::WriteFlags::NONE;\n    flags |= (timestampAck) ? folly::WriteFlags::EOR : folly::WriteFlags::NONE;\n    CHECK(!pendingWrite_.hasValue());\n    pendingWrite_.emplace(len, DestructorGuard(this));\n\n    if (!writeTimeout_.isScheduled()) {\n      // Any performance concern here?\n      wheelTimer_.scheduleTimeout(&writeTimeout_);\n    }\n    numActiveWrites_++;\n    VLOG(4) << *this << \" writing \" << len\n            << \", activeWrites=\" << numActiveWrites_ << \" cork:\" << cork\n            << \" timestampTx:\" << timestampTx\n            << \" timestampAck:\" << timestampAck;\n    bytesScheduled_ += len;\n    sock_->writeChain(this, std::move(writeBuf), flags);\n    if (numActiveWrites_ > 0) {\n      updateWriteCount();\n      HTTPSessionBase::notifyEgressBodyBuffered(len, false);\n      // updateWriteBufSize called in scope guard\n      break;\n    }\n    bytesWritten += len;\n    // writeChain can result in a writeError and trigger the shutdown code path\n  }\n  if (infoCallback_ && bytesWritten > 0) {\n    infoCallback_->onWrite(*this, bytesWritten);\n  }\n\n  if (numActiveWrites_ == 0 && !writesShutdown() && hasMoreWrites() &&\n      (!connFlowControl_ || connFlowControl_->getAvailableSend())) {\n    scheduleWrite();\n  }\n\n  if (readsUnpaused()) {\n    processReadData();\n\n    // Install the read callback if necessary\n    if (readsUnpaused() && !sock_->getReadCallback()) {\n      sock_->setReadCB(this);\n    }\n  }\n  // checkForShutdown is now in ScopeGuard\n}\n\nvoid HTTPSession::scheduleWrite() {\n  // Do all the network writes for this connection in one batch at\n  // the end of the current event loop iteration.  Writing in a\n  // batch helps us packetize the network traffic more efficiently,\n  // as well as saving a few system calls.\n  if (!isLoopCallbackScheduled() &&\n      (writeBuf_.front() || !txnEgressQueue_.empty())) {\n    VLOG(5) << *this << \" scheduling write callback\";\n    sock_->getEventBase()->runInLoop(this);\n  }\n}\n\nvoid HTTPSession::updateWriteCount() {\n  if (numActiveWrites_ > 0 && writesUnpaused()) {\n    // Exceeded limit. Pause reading on the incoming stream.\n    VLOG(3) << \"Pausing egress for \" << *this;\n    writes_ = SocketState::PAUSED;\n  } else if (numActiveWrites_ == 0 && writesPaused()) {\n    // Dropped below limit. Resume reading on the incoming stream if needed.\n    VLOG(3) << \"Resuming egress for \" << *this;\n    writes_ = SocketState::UNPAUSED;\n  }\n}\n\nvoid HTTPSession::shutdownTransport(bool shutdownReads,\n                                    bool shutdownWrites,\n                                    const std::string& errorMsg,\n                                    ProxygenError error) {\n  DestructorGuard guard(this);\n\n  // shutdowns not accounted for, shouldn't see any\n  setCloseReason(ConnectionCloseReason::UNKNOWN);\n\n  VLOG(4) << \"shutdown request for \" << *this << \": reads=\" << shutdownReads\n          << \" (currently \" << readsShutdown() << \"), writes=\" << shutdownWrites\n          << \" (currently \" << writesShutdown() << \")\";\n\n  bool notifyEgressShutdown = false;\n  bool notifyIngressShutdown = false;\n\n  if (!transportInfo_.sslError.empty()) {\n    error = kErrorSSL;\n  } else if (sock_->error()) {\n    VLOG(3) << \"shutdown request for \" << *this\n            << \" on bad socket. Shutting down writes too.\";\n    if (getConnectionCloseReason() == ConnectionCloseReason::IO_WRITE_ERROR) {\n      error = kErrorWrite;\n    } else {\n      error = kErrorConnectionReset;\n    }\n    shutdownWrites = true;\n  } else if (getConnectionCloseReason() == ConnectionCloseReason::TIMEOUT) {\n    error = kErrorTimeout;\n  }\n\n  if (shutdownReads && !shutdownWrites && flowControlTimeout_.isScheduled()) {\n    // reads are dead and writes are blocked on a window update that will never\n    // come.  shutdown writes too.\n    VLOG(4) << *this\n            << \" Converting read shutdown to read/write due to\"\n               \" flow control\";\n    shutdownWrites = true;\n  }\n\n  if (shutdownWrites && !writesShutdown()) {\n    // Need to shutdown, bypass double GOAWAY\n    if (codec_->generateImmediateGoaway(writeBuf_)) {\n      scheduleWrite();\n    }\n    if (!hasMoreWrites() &&\n        (transactions_.empty() || codec_->closeOnEgressComplete())) {\n      writes_ = SocketState::SHUTDOWN;\n      if (byteEventTracker_) {\n        byteEventTracker_->drainByteEvents();\n      }\n      if (resetAfterDrainingWrites_) {\n        VLOG(4) << *this << \" writes drained, sending RST\";\n        resetSocketOnShutdown_ = true;\n        shutdownReads = true;\n      } else {\n        VLOG(4) << *this << \" writes drained, closing\";\n        if (isUpstream() || !codec_->supportsParallelRequests()) {\n          sock_->shutdownWriteNow();\n        }\n      }\n      notifyEgressShutdown = true;\n    } else if (!writesDraining_) {\n      writesDraining_ = true;\n      notifyEgressShutdown = true;\n    } // else writes are already draining; don't double notify\n  }\n\n  if (shutdownReads && !readsShutdown()) {\n    notifyIngressShutdown = true;\n    // TODO: send an RST if readBuf_ is non empty?\n    shutdownRead();\n    if (!transactions_.empty() && error == kErrorConnectionReset) {\n      if (infoCallback_) {\n        infoCallback_->onIngressError(*this, error);\n      }\n    } else if (error == kErrorEOF) {\n      // Report to the codec that the ingress stream has ended\n      codec_->onIngressEOF();\n      if (infoCallback_) {\n        infoCallback_->onIngressEOF();\n      }\n    }\n    // Once reads are shutdown the parser should stop processing\n    codec_->setParserPaused(true);\n  }\n\n  if (notifyIngressShutdown || notifyEgressShutdown) {\n    auto dir = (notifyIngressShutdown && notifyEgressShutdown)\n                   ? HTTPException::Direction::INGRESS_AND_EGRESS\n                   : (notifyIngressShutdown ? HTTPException::Direction::INGRESS\n                                            : HTTPException::Direction::EGRESS);\n    HTTPException ex(dir,\n                     folly::to<std::string>(\"Shutdown transport: \",\n                                            getErrorString(error),\n                                            errorMsg.empty() ? \"\" : \" \",\n                                            errorMsg,\n                                            \", \",\n                                            getPeerAddress().describe()));\n    ex.setProxygenError(error);\n    invokeOnAllTransactions([&ex](HTTPTransaction* txn) { txn->onError(ex); });\n  }\n\n  if (readsShutdown() && writesShutdown()) {\n    // No need to defer shutdown\n    shutdownTransportCb_.reset();\n  }\n\n  // Close the socket only after the onError() callback on the txns\n  // and handler has been detached.\n  checkForShutdown();\n}\n\nvoid HTTPSession::shutdownTransportWithReset(ProxygenError errorCode,\n                                             const std::string& errorMsg) {\n  DestructorGuard guard(this);\n  VLOG(4) << \"shutdownTransportWithReset\";\n\n  if (!readsShutdown()) {\n    shutdownRead();\n  }\n\n  if (!writesShutdown()) {\n    writes_ = SocketState::SHUTDOWN;\n    IOBuf::destroy(writeBuf_.move());\n    if (pendingWrite_.hasValue()) {\n      numActiveWrites_--;\n    }\n    VLOG(4) << *this << \" cancel write timer\";\n    writeTimeout_.cancelTimeout();\n    resetSocketOnShutdown_ = true;\n  }\n\n  errorOnAllTransactions(errorCode, errorMsg);\n  // drainByteEvents() can call detach(txn), which can in turn call\n  // shutdownTransport if we were already draining. To prevent double\n  // calling onError() to the transactions, we call drainByteEvents()\n  // after we've given the explicit error.\n  if (byteEventTracker_) {\n    byteEventTracker_->drainByteEvents();\n  }\n\n  // HTTPTransaction::onError could theoretically schedule more callbacks,\n  // so do this last.\n  if (isLoopCallbackScheduled()) {\n    cancelLoopCallback();\n  }\n\n  // If there was a pending transport shutdown, we don't need it anymore\n  shutdownTransportCb_.reset();\n\n  // onError() callbacks or drainByteEvents() could result in txns detaching\n  // due to CallbackGuards going out of scope. Close the socket only after\n  // the txns are detached.\n  checkForShutdown();\n}\n\nvoid HTTPSession::shutdownRead() {\n  VLOG(10) << *this << \" shutting down reads\";\n  sock_->setReadCB(nullptr);\n  reads_ = SocketState::SHUTDOWN;\n  // disable socket timestamp events as we're shutting down reads\n  // (once reads are disabled, we cannot receive socket timestamp events)\n  if (byteEventTracker_) {\n    const auto numEventDrained =\n        byteEventTracker_->disableSocketTimestampEvents();\n    VLOG(10) << *this << \" drained \" << numEventDrained\n             << \" pending socket timestamp events when shutting down reads\";\n  }\n}\n\nvoid HTTPSession::checkForShutdown() {\n  VLOG(10) << *this\n           << \" checking for shutdown, readShutdown=\" << readsShutdown()\n           << \", writesShutdown=\" << writesShutdown()\n           << \", transaction set empty=\" << transactions_.empty();\n\n  // Two conditions are required to destroy the HTTPSession:\n  //   * All writes have been finished.\n  //   * There are no transactions remaining on the session.\n  if (writesShutdown() && transactions_.empty() && !isLoopCallbackScheduled() &&\n      (isUpstream() || readsShutdown())) {\n    VLOG(4) << \"destroying \" << *this;\n    shutdownRead();\n    auto asyncSocket = sock_->getUnderlyingTransport<folly::AsyncSocket>();\n    if (asyncSocket) {\n      asyncSocket->setBufferCallback(nullptr);\n    }\n    if (resetSocketOnShutdown_) {\n      sock_->closeWithReset();\n    } else {\n      sock_->closeNow();\n    }\n    destroy();\n  }\n}\n\nvoid HTTPSession::drain() {\n  if (!draining_) {\n    VLOG(4) << *this << \" draining\";\n    draining_ = true;\n    setCloseReason(ConnectionCloseReason::SHUTDOWN);\n\n    if (allTransactionsStarted()) {\n      drainImpl();\n    }\n    if (transactions_.empty() && isUpstream()) {\n      // We don't do this for downstream since we need to wait for\n      // inflight requests to arrive\n      VLOG(4) << *this << \" shutdown from drain\";\n      shutdownTransport(true, true);\n    }\n  } else {\n    VLOG(4) << *this << \" already draining\";\n  }\n}\n\nvoid HTTPSession::drainImpl() {\n  setCloseReason(ConnectionCloseReason::SHUTDOWN);\n  // For HTTP/2, if we haven't started yet then we cannot send a GOAWAY frame\n  // since we haven't sent the initial SETTINGS frame. Defer sending that\n  // GOAWAY until the initial SETTINGS is sent.\n  if (started_) {\n    if (codec_->generateGoaway(writeBuf_) > 0) {\n      scheduleWrite();\n    }\n    if (codec_->isWaitingToDrain()) {\n      // Schedule another goaway\n      wheelTimer_.scheduleTimeout(&drainTimeout_, getDrainTimeout());\n    } else if (transactions_.empty() && isDownstream() && !readsShutdown()) {\n      // Codec is not reusable, but we need to wait for peer FIN\n      VLOG(4) << \"Starting drain timer sess=\" << *this;\n      resetTimeoutTo(getDrainTimeout());\n    } // else\n    //   1. there's an txn - FIN timeout set from\n    //       onEgressMessageFinished/ShutdownTransportCallback\n    //   2. Client session - let don't need to wait for peer FIN\n    //   3. reads were already shutdown, nothing to wait for\n  }\n}\n\nbool HTTPSession::shouldShutdown() const {\n  return draining_ && allTransactionsStarted() &&\n         (!codec_->supportsParallelRequests() || isUpstream() ||\n          !codec_->isReusable());\n}\n\nsize_t HTTPSession::sendPing() {\n  return sendPing(folly::Random::rand64());\n}\n\nsize_t HTTPSession::sendPing(uint64_t data) {\n  const size_t bytes = codec_->generatePingRequest(writeBuf_, data);\n  if (bytes) {\n    scheduleWrite();\n  }\n  return bytes;\n}\n\nvoid HTTPSession::enablePingProbes(std::chrono::seconds interval,\n                                   std::chrono::seconds timeout,\n                                   bool extendIntervalOnIngress,\n                                   bool immediate) {\n  if (isHTTP2CodecProtocol(codec_->getProtocol())) {\n    pingProber_ = std::make_unique<PingProber>(\n        *this, interval, timeout, extendIntervalOnIngress, immediate);\n  }\n}\n\nHTTPSession::PingProber::PingProber(HTTPSession& session,\n                                    std::chrono::seconds interval,\n                                    std::chrono::seconds timeout,\n                                    bool extendIntervalOnIngress,\n                                    bool immediate)\n    : session_(session),\n      interval_(interval),\n      timeout_(timeout),\n      extendIntervalOnIngress_(extendIntervalOnIngress) {\n  if (immediate) {\n    timeoutExpired();\n  } else if (session_.getNumStreams() > 0) {\n    startProbes();\n  } // else session will start them when a stream is created\n}\n\nvoid HTTPSession::PingProber::startProbes() {\n  refreshTimeout(/*onIngress=*/false);\n}\n\nvoid HTTPSession::PingProber::cancelProbes() {\n  if (pingVal_) {\n    VLOG(4) << \"Canceling active probe sess=\" << session_;\n    pingVal_.reset();\n  }\n  cancelTimeout();\n}\n\nvoid HTTPSession::PingProber::refreshTimeout(bool onIngress) {\n  if (!pingVal_ && (!onIngress || extendIntervalOnIngress_)) {\n    VLOG(4) << \"Scheduling next ping probe for sess=\" << session_;\n    session_.getEventBase()->timer().scheduleTimeout(this, interval_);\n  }\n}\n\nvoid HTTPSession::PingProber::timeoutExpired() noexcept {\n  if (pingVal_) {\n    VLOG(3) << \"Ping probe timed out, dropping connection sess=\" << session_;\n    if (auto sessionStats = session_.sessionStats_) {\n      sessionStats->recordSessionPeriodicPingProbeTimeout();\n    }\n    session_.dropConnection(\"Ping probe timed out\");\n  } else {\n    pingVal_ = folly::Random::rand64();\n    VLOG(4) << \"Sending ping probe with value=\" << *pingVal_\n            << \" sess=\" << session_;\n    session_.sendPing(*pingVal_);\n    session_.getEventBase()->timer().scheduleTimeout(this, timeout_);\n  }\n}\n\nvoid HTTPSession::PingProber::onPingReply(uint64_t pingVal) {\n  if (!pingVal_ || *pingVal_ != pingVal) {\n    // This can happen if someone calls sendPing() manually\n    VLOG(3) << \"Received unexpected PING reply=\" << pingVal << \" expecting=\"\n            << ((pingVal_) ? folly::to<std::string>(*pingVal_)\n                           : std::string(\"none\"));\n    return;\n  }\n  VLOG(4) << \"Received expected ping, rescheduling\";\n  pingVal_.reset();\n  refreshTimeout(/*onIngress=*/false);\n}\n\nHTTPTransaction* HTTPSession::findTransaction(HTTPCodec::StreamID streamID) {\n  if (lastTxn_ && streamID == lastTxn_->getID()) {\n    return lastTxn_;\n  }\n  auto it = transactions_.find(streamID);\n  if (it == transactions_.end()) {\n    DCHECK(!transactionIds_.contains(streamID));\n    return nullptr;\n  } else {\n    DCHECK(transactionIds_.contains(streamID));\n    lastTxn_ = &it->second;\n    return lastTxn_;\n  }\n}\n\nHTTPTransaction* HTTPSession::createTransaction(\n    HTTPCodec::StreamID streamID,\n    const folly::Optional<HTTPCodec::StreamID>& assocStreamID,\n    const http2::PriorityUpdate& priority,\n    ProxygenError* error) {\n  if (!sock_->good() || writesShutdown()) {\n    // Refuse to add a transaction on a closing session\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorBadSocket);\n    return nullptr;\n  }\n\n  if (transactions_.contains(streamID)) {\n    // Refuse to add a transaction if a transaction of that ID already exists.\n    SET_PROXYGEN_ERROR_IF(error, ProxygenError::kErrorDuplicatedStreamId);\n    return nullptr;\n  }\n\n  if (transactions_.empty()) {\n    if (pingProber_) {\n      pingProber_->startProbes();\n    }\n    if (infoCallback_) {\n      infoCallback_->onActivateConnection(*this);\n    }\n    if (getConnectionManager()) {\n      getConnectionManager()->onActivated(*this);\n    }\n    HTTPSessionBase::onCreateTransaction();\n  }\n\n  auto matchPair = transactions_.emplace(\n      std::piecewise_construct,\n      std::forward_as_tuple(streamID),\n      std::forward_as_tuple(codec_->getTransportDirection(),\n                            streamID,\n                            getNumTxnServed(),\n                            *this,\n                            txnEgressQueue_,\n                            wheelTimer_.getWheelTimer(),\n                            wheelTimer_.getDefaultTimeout(),\n                            sessionStats_,\n                            codec_->supportsStreamFlowControl(),\n                            initialReceiveWindow_,\n                            getCodecSendWindowSize(),\n                            priority,\n                            assocStreamID,\n                            setIngressTimeoutAfterEom_));\n\n  CHECK(matchPair.second) << \"Emplacement failed, despite earlier \"\n                             \"existence check.\";\n  transactionIds_.emplace(streamID);\n\n  HTTPTransaction* txn = &matchPair.first->second;\n\n  if (getNumTxnServed() > 0) {\n    auto stats = txn->getSessionStats();\n    if (stats != nullptr) {\n      stats->recordSessionReused();\n    }\n  }\n\n  VLOG(5) << *this << \" adding streamID=\" << txn->getID()\n          << \", liveTransactions_ was \" << liveTransactions_;\n\n  ++liveTransactions_;\n  incrementSeqNo();\n  txn->setReceiveWindow(receiveStreamWindowSize_);\n\n  if (isUpstream() && !txn->isPushed()) {\n    incrementOutgoingStreams(txn);\n    // do not count towards MAX_CONCURRENT_STREAMS for PUSH_PROMISE\n  } else if (!(isDownstream() && txn->isPushed())) {\n    incrementIncomingStreams(txn);\n  }\n  // Downstream push is counted in HTTPSession::sendHeaders\n\n  if (infoCallback_) {\n    infoCallback_->onTransactionAttached(*this);\n  }\n\n  return txn;\n}\n\nvoid HTTPSession::incrementOutgoingStreams(HTTPTransaction* txn) {\n  DCHECK(txn);\n  outgoingStreams_++;\n  txn->setIsCountedTowardsStreamLimit();\n  HTTPSessionBase::onNewOutgoingStream(outgoingStreams_);\n}\n\nvoid HTTPSession::incrementIncomingStreams(HTTPTransaction* txn) {\n  DCHECK(txn);\n  incomingStreams_++;\n  txn->setIsCountedTowardsStreamLimit();\n}\n\nvoid HTTPSession::writeSuccess() noexcept {\n  CHECK(pendingWrite_.hasValue());\n  DestructorGuard dg(this);\n  auto bytesWritten = pendingWrite_->first;\n  bytesWritten_ += bytesWritten;\n  transportInfo_.totalBytes += bytesWritten;\n  CHECK(writeTimeout_.isScheduled());\n  VLOG(10) << \"Cancel write timer on last successful write\";\n  writeTimeout_.cancelTimeout();\n  pendingWrite_.reset();\n\n  if (infoCallback_ && !inLoopCallback_) {\n    infoCallback_->onWrite(*this, bytesWritten);\n  }\n\n  VLOG(5) << \"total bytesWritten_: \" << bytesWritten_;\n\n  // processByteEvents will return true if it has been replaced with another\n  // tracker in the middle and needs to be re-run.  Should happen at most\n  // once.  while with no body is intentional\n  while (byteEventTracker_ && byteEventTracker_->processByteEvents(\n                                  byteEventTracker_, bytesWritten_)) {\n  } // pass\n\n  if ((!codec_->isReusable() || readsShutdown()) && (transactions_.empty())) {\n    if (!codec_->isReusable()) {\n      // Shouldn't happen unless there is a bug. This can only happen when\n      // someone calls shutdownTransport, but did not specify a reason before.\n      setCloseReason(ConnectionCloseReason::UNKNOWN);\n    }\n    VLOG(4) << *this << \" shutdown from onWriteSuccess\";\n    // downstream needs to listen for fin to shutdown reads\n    shutdownTransport(isUpstream(), true);\n  }\n  numActiveWrites_--;\n  if (!inLoopCallback_) {\n    updateWriteCount();\n    // safe to resume here:\n    updateWriteBufSize(-folly::to<int64_t>(bytesWritten));\n    // PRIO_FIXME: this is done because of the corking business...\n    //             in the future we may want to have a pull model\n    //             whereby the socket asks us for a given amount of\n    //             data to send...\n    if (numActiveWrites_ == 0 && hasMoreWrites()) {\n      runLoopCallback();\n    }\n  }\n  onWriteCompleted();\n\n  if (egressBytesLimit_ > 0 && bytesWritten_ >= egressBytesLimit_) {\n    VLOG(4) << \"Egress limit reached, shutting down \"\n               \"session (egressed \"\n            << bytesWritten_ << \", limit set to \" << egressBytesLimit_ << \")\";\n    shutdownTransport(true, true);\n  }\n}\n\nvoid HTTPSession::writeErr(size_t bytesWritten,\n                           const AsyncSocketException& ex) noexcept {\n  VLOG(4) << *this << \" write error: \" << ex.what();\n  DestructorGuard dg(this);\n  DCHECK(pendingWrite_.hasValue());\n  pendingWrite_.reset();\n  if (infoCallback_) {\n    infoCallback_->onWrite(*this, bytesWritten);\n  }\n\n  auto sslEx = dynamic_cast<const folly::SSLException*>(&ex);\n  // Save the SSL error, if there was one.  It will be recorded later\n  if (sslEx && sslEx->getSSLError() == folly::SSLError::SSL_ERROR) {\n    transportInfo_.sslError = ex.what();\n  }\n\n  setCloseReason(ConnectionCloseReason::IO_WRITE_ERROR);\n  shutdownTransportWithReset(kErrorWrite, ex.what());\n}\n\nvoid HTTPSession::onWriteCompleted() {\n  if (!writesDraining_) {\n    return;\n  }\n\n  if (numActiveWrites_) {\n    return;\n  }\n\n  // Don't shutdown if there might be more writes\n  if (pendingWrite_.hasValue()) {\n    return;\n  }\n\n  // All finished draining writes, so shut down the egress\n  shutdownTransport(false, true);\n}\n\nvoid HTTPSession::onSessionParseError(const HTTPException& error) {\n  VLOG(4) << *this << \" session layer parse error. Terminate the session.\";\n  if (error.hasCodecStatusCode()) {\n    std::unique_ptr<folly::IOBuf> errorMsg =\n        folly::IOBuf::copyBuffer(error.what());\n    if (codec_->generateImmediateGoaway(\n            writeBuf_, error.getCodecStatusCode(), std::move(errorMsg))) {\n      scheduleWrite();\n    }\n  }\n  if (error.hasProxygenError() && error.getProxygenError() == kErrorDropped) {\n    // Codec is requesting a connection drop\n    dropConnection();\n  } else {\n    setCloseReason(ConnectionCloseReason::SESSION_PARSE_ERROR);\n    shutdownTransport(true,\n                      true,\n                      \"\",\n                      error.hasProxygenError() ? error.getProxygenError()\n                                               : kErrorMalformedInput);\n  }\n}\n\nvoid HTTPSession::onNewTransactionParseError(HTTPCodec::StreamID streamID,\n                                             const HTTPException& error) {\n  VLOG(4) << *this << \" parse error with new transaction\";\n  if (error.hasCodecStatusCode()) {\n    codec_->generateRstStream(writeBuf_, streamID, error.getCodecStatusCode());\n    scheduleWrite();\n  }\n  if (!codec_->isReusable()) {\n    // HTTP 1x codec does not support per stream abort so this will\n    // render the codec not reusable\n    setCloseReason(ConnectionCloseReason::SESSION_PARSE_ERROR);\n  }\n}\n\nvoid HTTPSession::pauseReads() {\n  // Make sure the parser is paused.  Note that if reads are shutdown\n  // before they are paused, we never make it past the if.\n  codec_->setParserPaused(true);\n  if (!readsUnpaused() ||\n      (codec_->supportsParallelRequests() && !ingressLimitExceeded())) {\n    return;\n  }\n  pauseReadsImpl();\n}\n\nvoid HTTPSession::pauseReadsImpl() {\n  VLOG(4) << *this << \": pausing reads\";\n  if (infoCallback_) {\n    infoCallback_->onIngressPaused(*this);\n  }\n  cancelTimeout();\n  sock_->setReadCB(nullptr);\n  reads_ = SocketState::PAUSED;\n}\n\nvoid HTTPSession::resumeReads() {\n  if (!readsPaused() ||\n      (codec_->supportsParallelRequests() && ingressLimitExceeded())) {\n    return;\n  }\n  resumeReadsImpl();\n}\n\nvoid HTTPSession::resumeReadsImpl() {\n  VLOG(4) << *this << \": resuming reads\";\n  resetTimeout();\n  reads_ = SocketState::UNPAUSED;\n  codec_->setParserPaused(false);\n  if (!isLoopCallbackScheduled()) {\n    sock_->getEventBase()->runInLoop(this);\n  }\n}\n\nbool HTTPSession::hasMoreWrites() const {\n  VLOG(10) << __PRETTY_FUNCTION__ << \" numActiveWrites_: \" << numActiveWrites_\n           << \" pendingWrite_.hasValue(): \" << pendingWrite_.hasValue()\n           << \" txnEgressQueue_.empty(): \" << txnEgressQueue_.empty();\n\n  return (numActiveWrites_ != 0) || pendingWrite_.hasValue() ||\n         writeBuf_.front() || !txnEgressQueue_.empty();\n}\n\nvoid HTTPSession::errorOnAllTransactions(ProxygenError err,\n                                         const std::string& errorMsg) {\n  std::vector<HTTPCodec::StreamID> ids;\n  ids.reserve(transactionIds_.size());\n  std::copy(\n      transactionIds_.begin(), transactionIds_.end(), std::back_inserter(ids));\n  errorOnTransactionIds(ids, err, errorMsg);\n}\n\nvoid HTTPSession::errorOnTransactionIds(\n    const std::vector<HTTPCodec::StreamID>& ids,\n    ProxygenError err,\n    const std::string& errorMsg) {\n  std::string extraErrorMsg;\n  if (!errorMsg.empty()) {\n    extraErrorMsg = folly::to<std::string>(\". \", errorMsg);\n  }\n\n  for (auto id : ids) {\n    HTTPException ex(\n        HTTPException::Direction::INGRESS_AND_EGRESS,\n        folly::to<std::string>(\n            getErrorString(err), \" on transaction id: \", id, extraErrorMsg));\n    ex.setProxygenError(err);\n    errorOnTransactionId(id, ex);\n  }\n}\n\nvoid HTTPSession::errorOnTransactionId(HTTPCodec::StreamID id,\n                                       const HTTPException& ex) {\n  auto txn = findTransaction(id);\n  if (txn != nullptr) {\n    txn->onError(ex);\n  }\n}\n\nvoid HTTPSession::onConnectionSendWindowOpen() {\n  flowControlTimeout_.cancelTimeout();\n  // We can write more now. Schedule a write.\n  scheduleWrite();\n}\n\nvoid HTTPSession::onConnectionSendWindowClosed() {\n  if (!txnEgressQueue_.empty()) {\n    VLOG(4) << *this << \" session stalled by flow control\";\n    if (sessionStats_) {\n      sessionStats_->recordSessionStalled();\n    }\n  }\n  DCHECK(!flowControlTimeout_.isScheduled());\n  if (infoCallback_) {\n    infoCallback_->onFlowControlWindowClosed(*this);\n  }\n  auto timeout = flowControlTimeout_.getTimeoutDuration();\n  if (timeout != std::chrono::milliseconds(0)) {\n    wheelTimer_.scheduleTimeout(&flowControlTimeout_, timeout);\n  } else {\n    wheelTimer_.scheduleTimeout(&flowControlTimeout_);\n  }\n}\n\nvoid HTTPSession::invalidStream(HTTPCodec::StreamID stream, ErrorCode code) {\n  if (!codec_->supportsParallelRequests()) {\n    LOG(ERROR) << \"Invalid stream on non-parallel codec.\";\n    return;\n  }\n\n  HTTPException err(HTTPException::Direction::INGRESS_AND_EGRESS,\n                    folly::to<std::string>(\"invalid stream=\", stream));\n  // TODO: Below line will change for HTTP/2 -- just call a const getter\n  // function for the status code.\n  err.setCodecStatusCode(code);\n  onError(stream, err, true);\n}\n\nvoid HTTPSession::onPingReplyLatency(int64_t latency) noexcept {\n  if (infoCallback_ && latency >= 0) {\n    infoCallback_->onPingReplySent(latency);\n  }\n}\n\nvoid HTTPSession::onDeleteTxnByteEvent() noexcept {\n  if (readsShutdown()) {\n    shutdownTransport(true, transactions_.empty());\n  }\n}\n\nvoid HTTPSession::onEgressBuffered() {\n  if (infoCallback_) {\n    infoCallback_->onEgressBuffered(*this);\n  }\n}\n\nvoid HTTPSession::onEgressBufferCleared() {\n  if (infoCallback_) {\n    infoCallback_->onEgressBufferCleared(*this);\n  }\n}\n\nvoid HTTPSession::onReplaySafe() noexcept {\n  CHECK(sock_);\n  sock_->setReplaySafetyCallback(nullptr);\n\n  if (infoCallback_) {\n    infoCallback_->onFullHandshakeCompletion(*this);\n  }\n\n  for (auto callback : waitingForReplaySafety_) {\n    callback->onReplaySafe();\n  }\n  waitingForReplaySafety_.clear();\n}\n\nvoid HTTPSession::onTxnByteEventWrittenToBuf(const ByteEvent& event) noexcept {\n  if (!sock_->isEorTrackingEnabled() || event.getTransaction() == nullptr ||\n      event.byteOffset_ != sock_->getAppBytesWritten()) {\n    return;\n  }\n\n  // by default, we're going to add TX and ACK events whenever they're available\n  const auto& txn = event.getTransaction();\n  if (event.timestampTx_) {\n    byteEventTracker_->addTxByteEvent(\n        sock_->getRawBytesWritten(), event.eventType_, txn);\n  }\n  if (event.timestampAck_) {\n    byteEventTracker_->addAckByteEvent(\n        sock_->getRawBytesWritten(), event.eventType_, txn);\n  }\n}\n\nbool HTTPSession::isDetachable(bool checkSocket) const {\n  if (checkSocket && sock_ && !sock_->isDetachable()) {\n    return false;\n  }\n  return transactions_.size() == 0 && getNumIncomingStreams() == 0 &&\n         !writesPaused() && !flowControlTimeout_.isScheduled() &&\n         !writeTimeout_.isScheduled() && !drainTimeout_.isScheduled();\n}\n\nvoid HTTPSession::invokeOnAllTransactions(\n    folly::Function<void(HTTPTransaction*)> fn) {\n  DestructorGuard g(this);\n  std::vector<HTTPCodec::StreamID> ids;\n  ids.reserve(transactionIds_.size());\n  std::copy(\n      transactionIds_.begin(), transactionIds_.end(), std::back_inserter(ids));\n  for (auto idit = ids.begin(); idit != ids.end() && !transactions_.empty();\n       ++idit) {\n    auto txn = findTransaction(*idit);\n    if (txn != nullptr) {\n      fn(txn);\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/record/Types.h>\n#include <folly/IntrusiveList.h>\n#include <folly/Optional.h>\n#include <folly/container/F14Map.h>\n#include <folly/container/F14Set.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <proxygen/lib/http/HTTPConstants.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/FlowControlFilter.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n#include <proxygen/lib/http/session/HTTPEvent.h>\n#include <proxygen/lib/http/session/HTTPSessionActivityTracker.h>\n#include <proxygen/lib/http/session/HTTPSessionBase.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/SecondaryAuthManagerBase.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n#include <vector>\n\nnamespace proxygen {\n\nclass HTTPSessionController;\nclass HTTPSessionStats;\n\n#define PROXYGEN_HTTP_SESSION_USES_BASE 1\nconstexpr uint32_t kDefaultMaxConcurrentOutgoingStreamsRemote = 100000;\nconstexpr uint32_t kDefaultMaxConcurrentIncomingStreams = 100;\n\nclass HTTPSession\n    : public HTTPSessionBase\n    , public HTTPTransaction::Transport\n    , public ByteEventTracker::Callback\n    , protected folly::AsyncTransport::BufferCallback\n    , private FlowControlFilter::Callback\n    , private HTTPCodec::Callback\n    , private folly::EventBase::LoopCallback\n    , private folly::AsyncTransport::ReadCallback\n    , private folly::AsyncTransport::ReplaySafetyCallback\n    , private folly::AsyncTransport::WriteCallback {\n public:\n  using UniquePtr = std::unique_ptr<HTTPSession, Destructor>;\n\n  HTTPTransaction::Transport::Type getType() const noexcept override {\n    return HTTPTransaction::Transport::Type::TCP;\n  }\n\n  void setHTTPSessionActivityTracker(std::unique_ptr<HTTPSessionActivityTracker>\n                                         httpSessionActivityTracker) override {\n    if (!getByteEventTracker()) {\n      setByteEventTracker(std::make_shared<ByteEventTracker>(this));\n    }\n    HTTPSessionBase::setHTTPSessionActivityTracker(\n        std::move(httpSessionActivityTracker));\n  }\n\n  folly::AsyncTransport* getTransport() override {\n    return sock_.get();\n  }\n\n  folly::EventBase* getEventBase() const override {\n    if (sock_) {\n      return sock_->getEventBase();\n    }\n    return nullptr;\n  }\n\n  const folly::AsyncTransport* getTransport() const override {\n    return sock_.get();\n  }\n\n  bool hasActiveTransactions() const override {\n    return !transactions_.empty();\n  }\n\n  uint32_t getNumStreams() const override {\n    return static_cast<uint32_t>(transactions_.size());\n  }\n\n  uint32_t getNumOutgoingStreams() const override {\n    return outgoingStreams_;\n  }\n\n  uint32_t getNumIncomingStreams() const override {\n    return incomingStreams_;\n  }\n\n  ByteEventTracker* getByteEventTracker() {\n    return byteEventTracker_.get();\n  }\n\n  HTTPSessionBase* getHTTPSessionBase() override {\n    return this;\n  }\n\n  void setByteEventTracker(std::shared_ptr<ByteEventTracker> byteEventTracker);\n\n  void setSessionStats(HTTPSessionStats* stats) override;\n  /**\n   * Set flow control properties on the session.\n   *\n   * @param initialReceiveWindow      size of initial receive window\n   *                                  for all ingress streams; set via\n   *                                  the initial SETTINGS frame\n   * @param receiveStreamWindowSize   per-stream receive window for NEW streams;\n   *                                  sent via a WINDOW_UPDATE frame\n   * @param receiveSessionWindowSize  per-session receive window; sent\n   *                                  via a WINDOW_UPDATE frame\n   */\n  void setFlowControl(size_t initialReceiveWindow,\n                      size_t receiveStreamWindowSize,\n                      size_t receiveSessionWindowSize) override;\n\n  /**\n   * Set outgoing settings for this session\n   */\n  void setEgressSettings(const SettingsList& inSettings) override;\n\n  bool getHTTP2PrioritiesEnabled() const override {\n    return HTTPSessionBase::getHTTP2PrioritiesEnabled();\n  }\n\n  void setHTTP2PrioritiesEnabled(bool enabled) override {\n    HTTPSessionBase::setHTTP2PrioritiesEnabled(enabled);\n  }\n\n  folly::Optional<HTTPTransaction::ConnectionToken> getConnectionToken()\n      const noexcept override {\n    return connectionToken_;\n  }\n\n  const folly::SocketAddress& getLocalAddress() const noexcept override {\n    return HTTPSessionBase::getLocalAddress();\n  }\n\n  const folly::SocketAddress& getPeerAddress() const noexcept override {\n    return HTTPSessionBase::getPeerAddress();\n  }\n\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept override {\n    return HTTPSessionBase::getSetupTransportInfo();\n  }\n\n  [[nodiscard]] std::chrono::seconds getLatestIdleTime() const override {\n    return HTTPSessionBase::getLatestIdleTime();\n  }\n\n  bool getCurrentTransportInfo(wangle::TransportInfo* tinfo) override;\n\n  void getFlowControlInfo(HTTPTransaction::FlowControlInfo* info) override;\n\n  HTTPTransaction::Transport::Type getSessionType() const noexcept override;\n\n  /**\n   * Set the maximum number of transactions the remote can open at once.\n   */\n  void setMaxConcurrentIncomingStreams(uint32_t num) override;\n\n  /**\n   * Set the maximum number of bytes allowed to be egressed in the session\n   * before cutting it off\n   */\n  void setEgressBytesLimit(uint64_t bytesLimit);\n\n  /**\n   * If set to true, HTTPSession will abort the push streams when receiving\n   * a STREAM_RST on the associated stream.\n   *\n   * This applies to HTTP/2, and it's temporarily available to perform an\n   * experiment.\n   */\n  void setAbortPushesOnRST(bool value) {\n    abortPushesOnRST_ = value;\n  }\n\n  /**\n   * Start reading from the transport and send any introductory messages\n   * to the remote side. This function must be called once per session to\n   * begin reads.\n   */\n  void startNow() override;\n\n  /**\n   * Send a settings frame\n   */\n  size_t sendSettings() override;\n\n  /**\n   * Causes a ping to be sent on the session. If the underlying protocol\n   * doesn't support pings, this will return 0. Otherwise, it will return\n   * the number of bytes written on the transport to send the ping.\n   */\n  size_t sendPing() override;\n\n  /**\n   * Send a CERTIFICATE_REQUEST frame. If the underlying protocol doesn't\n   * support secondary authentication, this is a no-op and 0 is returned.\n   */\n  size_t sendCertificateRequest(\n      std::unique_ptr<folly::IOBuf> certificateRequestContext,\n      std::vector<fizz::Extension> extensions) override;\n\n  // public ManagedConnection methods\n  void timeoutExpired() noexcept override {\n    readTimeoutExpired();\n  }\n\n  void describe(std::ostream& os) const override;\n  bool isBusy() const override;\n  void notifyPendingShutdown() override;\n  void closeWhenIdle() override;\n  void dropConnection(const std::string& errorMsg = \"\") override;\n  void dumpConnectionState(uint8_t loglevel) override;\n\n  bool getCurrentTransportInfoWithoutUpdate(\n      wangle::TransportInfo* tinfo) const override;\n\n  void setHeaderIndexingStrategy(const HeaderIndexingStrategy* strat) override;\n\n  void setHeaderCodecStats(HeaderCodec::Stats* stats) override {\n    codec_->setHeaderCodecStats(stats);\n  }\n\n  void enableDoubleGoawayDrain() override {\n    codec_->enableDoubleGoawayDrain();\n  }\n\n  /**\n   * If the connection is closed by remote end\n   */\n  bool connCloseByRemote() override {\n    auto sock = getTransport()->getUnderlyingTransport<folly::AsyncSocket>();\n    if (sock) {\n      return sock->isClosedByPeer();\n    }\n    return false;\n  }\n\n  /**\n   * Attach a SecondaryAuthManager to this session to control secondary\n   * certificate authentication in HTTP/2.\n   */\n  void setSecondAuthManager(\n      std::unique_ptr<SecondaryAuthManagerBase> secondAuthManager);\n\n  /**\n   * Get the SecondaryAuthManager attached to this session.\n   */\n  SecondaryAuthManagerBase* getSecondAuthManager() const;\n\n  bool isDetachable(bool checkSocket = true) const override;\n\n  /**\n   * Returns true if this session is draining. This can happen if drain()\n   * is called explicitly, if a GOAWAY frame is received, or during shutdown.\n   */\n  bool isDraining() const override {\n    return draining_;\n  }\n\n  bool readsUnpaused() const {\n    return reads_ == SocketState::UNPAUSED;\n  }\n\n  bool readsPaused() const {\n    return reads_ == SocketState::PAUSED;\n  }\n\n  bool readsShutdown() const {\n    return reads_ == SocketState::SHUTDOWN;\n  }\n\n  bool writesUnpaused() const {\n    return writes_ == SocketState::UNPAUSED;\n  }\n\n  bool writesPaused() const {\n    return writes_ == SocketState::PAUSED;\n  }\n\n  bool writesShutdown() const {\n    return writes_ == SocketState::SHUTDOWN;\n  }\n\n  void enablePingProbes(std::chrono::seconds interval,\n                        std::chrono::seconds timeout,\n                        bool extendIntervalOnIngress,\n                        bool immediate = false) override;\n\n protected:\n  /**\n   * HTTPSession is an abstract base class and cannot be instantiated\n   * directly. If you want to handle requests and send responses (act as a\n   * server), construct a HTTPDownstreamSession. If you want to make\n   * requests and handle responses (act as a client), construct a\n   * HTTPUpstreamSession.\n   *\n   * @param wheelTimer  Shared HHWheel Timer instance for scheduling timeouts.\n   * @param sock                 An open socket on which any applicable TLS\n   *                               handshaking has been completed already.\n   * @param localAddr            Address and port of the local end of\n   *                               the socket.\n   * @param peerAddr             Address and port of the remote end of\n   *                               the socket.\n   * @param controller           Controller which can create the handler for\n   *                               a new transaction.\n   * @param codec                A codec with which to parse/generate messages\n   *                               in whatever HTTP-like wire format this\n   *                               session needs.\n   * @param tinfo                Struct containing the transport's TCP/SSL\n   *                               level info.\n   * @param InfoCallback         Optional callback to be informed of session\n   *                               lifecycle events.\n   */\n  HTTPSession(const WheelTimerInstance& wheelTimer,\n              folly::AsyncTransport::UniquePtr sock,\n              const folly::SocketAddress& localAddr,\n              const folly::SocketAddress& peerAddr,\n              HTTPSessionController* controller,\n              std::unique_ptr<HTTPCodec> codec,\n              const wangle::TransportInfo& tinfo,\n              InfoCallback* infoCallback);\n\n  // thrift uses WheelTimer\n  HTTPSession(folly::HHWheelTimer* wheelTimer,\n              folly::AsyncTransport::UniquePtr sock,\n              const folly::SocketAddress& localAddr,\n              const folly::SocketAddress& peerAddr,\n              HTTPSessionController* controller,\n              std::unique_ptr<HTTPCodec> codec,\n              const wangle::TransportInfo& tinfo,\n              InfoCallback* infoCallback);\n\n  ~HTTPSession() override;\n\n  /**\n   * Called by onHeadersComplete(). This function allows downstream and\n   * upstream to do any setup (like preparing a handler) when headers are\n   * first received from the remote side on a given transaction.\n   */\n  virtual void setupOnHeadersComplete(HTTPTransaction* txn,\n                                      HTTPMessage* msg) = 0;\n\n  /**\n   * Called by transactionTimeout if the transaction has no handler.\n   */\n  virtual HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn) = 0;\n\n  /**\n   * Invoked when headers have been sent.\n   */\n  virtual void onHeadersSent(const HTTPMessage& /* headers */,\n                             bool /* codecWasReusable */) {\n  }\n\n  virtual bool allTransactionsStarted() const = 0;\n\n  /**\n   * Invoked when the transaction finishes sending a message and\n   * appropriately shuts down reads and/or writes with respect to\n   * downstream or upstream semantics.\n   */\n  void onEgressMessageFinished(HTTPTransaction* txn, bool withRST = false);\n\n  /**\n   * Gets the next IOBuf to send (either writeBuf_ or new egress from\n   * the priority queue), and sets cork appropriately\n   */\n  std::unique_ptr<folly::IOBuf> getNextToSend(bool* cork,\n                                              bool* timestampTx,\n                                              bool* timestampAck);\n\n  void decrementTransactionCount(HTTPTransaction* txn,\n                                 bool ingressEOM,\n                                 bool egressEOM);\n\n  size_t getCodecSendWindowSize() const;\n\n  using HTTPTransaction::Transport::getHTTPPriority;\n  folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n      uint8_t) override {\n    return folly::none;\n  }\n\n  void readTimeoutExpired() noexcept;\n  void writeTimeoutExpired() noexcept;\n  void flowControlTimeoutExpired() noexcept;\n\n  // AsyncTransport::ReadCallback methods\n  void getReadBuffer(void** buf, size_t* bufSize) override;\n  void readDataAvailable(size_t readSize) noexcept override;\n  bool isBufferMovable() noexcept override;\n  void readBufferAvailable(std::unique_ptr<folly::IOBuf>) noexcept override;\n  void processReadData();\n  void readEOF() noexcept override;\n  void readErr(const folly::AsyncSocketException&) noexcept override;\n\n  std::string getSecurityProtocol() const override {\n    return sock_->getSecurityProtocol();\n  }\n\n  // HTTPCodec::Callback methods\n  void onMessageBegin(HTTPCodec::StreamID streamID, HTTPMessage* msg) override;\n  void onPushMessageBegin(HTTPCodec::StreamID streamID,\n                          HTTPCodec::StreamID assocStreamID,\n                          HTTPMessage* msg) override;\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         std::unique_ptr<HTTPMessage> msg) override;\n  void onBody(HTTPCodec::StreamID streamID,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override;\n  void onChunkHeader(HTTPCodec::StreamID stream, size_t length) override;\n  void onChunkComplete(HTTPCodec::StreamID stream) override;\n  void onTrailersComplete(HTTPCodec::StreamID streamID,\n                          std::unique_ptr<HTTPHeaders> trailers) override;\n  void onMessageComplete(HTTPCodec::StreamID streamID, bool upgrade) override;\n  void onError(HTTPCodec::StreamID streamID,\n               const HTTPException& error,\n               bool newTxn) override;\n  void onAbort(HTTPCodec::StreamID streamID, ErrorCode code) override;\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode code,\n                std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n  void onPingRequest(uint64_t data) override;\n  void onPingReply(uint64_t data) override;\n  void onWindowUpdate(HTTPCodec::StreamID stream, uint32_t amount) override;\n  void onSettings(const SettingsList& settings) override;\n  void onSettingsAck() override;\n  void onPriority(HTTPCodec::StreamID, const HTTPPriority&) override;\n  void onCertificateRequest(uint16_t requestId,\n                            std::unique_ptr<folly::IOBuf> authRequest) override;\n  void onCertificate(uint16_t certId,\n                     std::unique_ptr<folly::IOBuf> authenticator) override;\n  uint32_t numOutgoingStreams() const override {\n    return outgoingStreams_;\n  }\n  uint32_t numIncomingStreams() const override {\n    return incomingStreams_;\n  }\n\n  // HTTPTransaction::Transport methods\n  void pauseIngress(HTTPTransaction* txn) noexcept override;\n  void resumeIngress(HTTPTransaction* txn) noexcept override;\n  void transactionTimeout(HTTPTransaction* txn) noexcept override;\n  void sendHeaders(HTTPTransaction* txn,\n                   const HTTPMessage& headers,\n                   HTTPHeaderSize* size,\n                   bool includeEOM) noexcept override;\n  size_t sendBody(HTTPTransaction* txn,\n                  std::unique_ptr<folly::IOBuf>,\n                  bool includeEOM,\n                  bool trackLastByteFlushed) noexcept override;\n  size_t sendChunkHeader(HTTPTransaction* txn, size_t length) noexcept override;\n  size_t sendChunkTerminator(HTTPTransaction* txn) noexcept override;\n  size_t sendPadding(HTTPTransaction* txn, uint16_t bytes) noexcept override;\n  size_t sendEOM(HTTPTransaction* txn,\n                 const HTTPHeaders* trailers) noexcept override;\n  size_t sendAbort(HTTPTransaction* txn,\n                   ErrorCode statusCode) noexcept override;\n  size_t changePriority(HTTPTransaction* /*txn*/,\n                        HTTPPriority /* pri */) noexcept override;\n  void detach(HTTPTransaction* txn) noexcept override;\n  size_t sendWindowUpdate(HTTPTransaction* txn,\n                          uint32_t bytes) noexcept override;\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n      uint64_t maxData) override;\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxStreams(\n      uint64_t maxStreams, bool isBidi) override;\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTStreamsBlocked(\n      uint64_t maxStreams, bool isBidi) override;\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTDataBlocked(\n      uint64_t maxData) override;\n  bool usesEncodedApplicationErrorCodes() override;\n  void notifyPendingEgress() noexcept override;\n  void notifyIngressBodyProcessed(uint32_t bytes) noexcept override;\n  void notifyEgressBodyBuffered(int64_t bytes) noexcept override;\n  HTTPTransaction* newPushedTransaction(\n      HTTPCodec::StreamID assocStreamId,\n      HTTPTransaction::PushHandler* handler,\n      ProxygenError* error = nullptr) noexcept override;\n  bool serverEarlyResponseEnabled() const noexcept override {\n    return HTTPSessionBase::getServerEarlyResponseEnabled();\n  }\n\n  const HTTPCodec& getCodec() const noexcept override {\n    return codec_.getChainEnd();\n  }\n\n  /**\n   * Returns the underlying AsyncTransport.\n   * Overrides HTTPTransaction::Transport::getUnderlyingTransport().\n   */\n  const folly::AsyncTransport* getUnderlyingTransport()\n      const noexcept override {\n    return sock_.get();\n  }\n\n  /**\n   * Drains the current transactions and prevents new transactions from being\n   * created on this session. If this is an upstream session and the\n   * number of transactions reaches zero, this session will shutdown the\n   * transport and delete itself. For downstream sessions, an explicit\n   * call to dropConnection() or shutdownTransport() is required.\n   */\n  void drain() override;\n\n  /**\n   * Start closing the socket.\n   * @param shutdownReads  Whether to close the read side of the\n   * socket. All transactions which are not ingress complete will receive\n   * an error.\n   * @param shutdownWrites Whether to close the write side of the\n   * socket. All transactions which are not egress complete will receive\n   * an error.\n   * @param errorMsg additional error information to pass to each transaction\n   */\n  void shutdownTransport(bool shutdownReads = true,\n                         bool shutdownWrites = true,\n                         const std::string& errorMsg = \"\",\n                         ProxygenError error = kErrorEOF);\n\n  /**\n   * Immediately close the socket in both directions, discarding any\n   * queued writes that haven't yet been transferred to the kernel,\n   * and send a RST to the client.\n   * All transactions receive onWriteError.\n   *\n   * @param errorCode  Error code sent with the onWriteError to transactions.\n   * @param errorMsg   Error string included in the final error msg.\n   */\n  void shutdownTransportWithReset(ProxygenError errorCode,\n                                  const std::string& errorMsg = \"\");\n\n  /**\n   * Immediately shuts down read events by unhooking the read callback.\n   *\n   * Since read events must be enabled to receive socket error messages (which\n   * include timestamps), disables and drains socket timestamping events in byte\n   * tracker to prevent connection shutdown from stalling due to pending\n   * timestamp events.\n   */\n  void shutdownRead();\n\n  // EventBase::LoopCallback methods\n  void runLoopCallback() noexcept override;\n\n  /**\n   * Schedule a write to occur at the end of this event loop.\n   */\n  void scheduleWrite();\n\n  /**\n   * Update the size of the unwritten egress data and invoke\n   * callbacks if the size has crossed the buffering limit.\n   */\n  void updateWriteCount();\n\n  /**\n   * Tells us what would be the offset of the next byte to be\n   * enqueued within the whole session.\n   */\n  inline uint64_t sessionByteOffset() {\n    return bytesScheduled_ + writeBuf_.chainLength();\n  }\n\n  /**\n   * Immediately shut down the session, by deleting the loop callbacks first\n   */\n  void immediateShutdown();\n\n  /**\n   * Check whether the socket is shut down in both directions; if it is,\n   * initiate the destruction of this HTTPSession.\n   */\n  void checkForShutdown();\n\n  /**\n   * Get the HTTPTransaction for the given transaction ID, or nullptr if that\n   * transaction ID does not exist within this HTTPSession.\n   */\n  HTTPTransaction* findTransaction(HTTPCodec::StreamID streamID);\n\n  /**\n   * Create a new transaction.\n   * @return pointer to the transaction on success, or else nullptr if it\n   * already exists\n   */\n  HTTPTransaction* createTransaction(\n      HTTPCodec::StreamID streamID,\n      const folly::Optional<HTTPCodec::StreamID>& assocStreamID,\n      const http2::PriorityUpdate& priority = http2::DefaultPriority,\n      ProxygenError* error = nullptr);\n\n  /** Invoked by WriteSegment on completion of a write. */\n  void writeSuccess() noexcept override;\n\n  /** Invoked by WriteSegment on write failure. */\n  void writeErr(size_t bytesWritten,\n                const folly::AsyncSocketException& ex) noexcept override;\n\n  /** Check whether to shut down the transport after a write completes. */\n  void onWriteCompleted();\n\n  /** Stop reading from the transport until resumeReads() is called */\n  void pauseReads();\n\n  /**\n   * Send a session layer abort and shutdown the transport for reads and\n   * writes.\n   */\n  void onSessionParseError(const HTTPException& error);\n\n  /**\n   * Send a transaction abort and leave the session and transport intact.\n   */\n  void onNewTransactionParseError(HTTPCodec::StreamID streamID,\n                                  const HTTPException& error);\n\n  /**\n   * Unpause reading from the transport.\n   * @note If any codec callbacks arrived while reads were paused,\n   * they will be processed before network reads resume.\n   */\n  void resumeReads();\n\n  /** Check whether the session has any writes in progress or upcoming */\n  bool hasMoreWrites() const;\n\n  /**\n   * This function invokes a callback on all transactions. It is safe,\n   * but runs in O(n*log n) and if the callback *adds* transactions,\n   * they will not get the callback.\n   */\n  void invokeOnAllTransactions(folly::Function<void(HTTPTransaction*)> fn);\n\n  /**\n   * This function invokes a callback on all transactions. It is safe,\n   * but runs in O(n*log n) and if the callback *adds* transactions,\n   * they will not get the callback.\n   */\n  void errorOnAllTransactions(ProxygenError err, const std::string& errorMsg);\n\n  void errorOnTransactionIds(const std::vector<HTTPCodec::StreamID>& ids,\n                             ProxygenError err,\n                             const std::string& extraErrorMsg = \"\");\n\n  void errorOnTransactionId(HTTPCodec::StreamID id, const HTTPException& ex);\n\n  /**\n   * Returns true iff this session should shutdown at this time. Default\n   * behavior is to not shutdown.\n   */\n  bool shouldShutdown() const;\n\n  void drainImpl();\n\n  void pauseReadsImpl();\n  void resumeReadsImpl();\n\n  void rescheduleLoopCallbacks() {\n    if (!isLoopCallbackScheduled()) {\n      sock_->getEventBase()->runInLoop(this);\n    }\n\n    if (shutdownTransportCb_ &&\n        !shutdownTransportCb_->isLoopCallbackScheduled()) {\n      sock_->getEventBase()->runInLoop(shutdownTransportCb_.get(), true);\n    }\n  }\n\n  void cancelLoopCallbacks() {\n    if (isLoopCallbackScheduled()) {\n      cancelLoopCallback();\n    }\n    if (shutdownTransportCb_) {\n      shutdownTransportCb_->cancelLoopCallback();\n    }\n  }\n\n  // protected members\n  class WriteTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit WriteTimeout(HTTPSession* session) : session_(session) {\n    }\n    ~WriteTimeout() override = default;\n\n    void timeoutExpired() noexcept override {\n      session_->writeTimeoutExpired();\n    }\n\n   private:\n    HTTPSession* session_;\n  };\n  WriteTimeout writeTimeout_;\n\n  /** Queue of egress IOBufs */\n  folly::IOBufQueue writeBuf_{folly::IOBufQueue::cacheChainLength()};\n\n  /** Chain of ingress IOBufs */\n  folly::IOBufQueue readBuf_{folly::IOBufQueue::cacheChainLength()};\n\n  folly::F14NodeMap<HTTPCodec::StreamID, HTTPTransaction> transactions_;\n  folly::F14FastSet<HTTPCodec::StreamID> transactionIds_;\n\n  /**\n   * Track all current known control streams we have within this session. A\n   *stream is considered as a control stream, after some ExStream is associated\n   *with it.\n   **/\n  folly::F14FastSet<HTTPCodec::StreamID> controlStreamIds_;\n\n  /** Count of transactions awaiting input */\n  uint32_t liveTransactions_{0};\n\n  folly::AsyncTransport::UniquePtr sock_;\n\n  WheelTimerInstance wheelTimer_;\n\n  HTTP2PriorityQueue txnEgressQueue_;\n\n  /**\n   * Number of writes submitted to the transport for which we haven't yet\n   * received completion or failure callbacks.\n   */\n  unsigned numActiveWrites_{0};\n\n  /**\n   * Indicates if the session is waiting for existing transactions to close.\n   * Once all transactions close, the session will be deleted.\n   */\n  bool draining_ : 1;\n\n  bool started_ : 1;\n\n  bool writesDraining_ : 1;\n  bool resetAfterDrainingWrites_ : 1;\n  bool ingressError_ : 1;\n\n private:\n  uint32_t getMaxConcurrentOutgoingStreamsRemote() const override {\n    return maxConcurrentOutgoingStreamsRemote_;\n  }\n\n  bool isUpstream() const;\n  bool isDownstream() const;\n\n  // from folly::AsyncTransport::BufferCallback\n  void onEgressBuffered() override;\n  void onEgressBufferCleared() override;\n\n  void setupCodec();\n  void onSetSendWindow(uint32_t windowSize);\n  void onSetMaxInitiatedStreams(uint32_t maxTxns);\n\n  uint32_t getCertAuthSettingVal();\n\n  bool verifyCertAuthSetting(uint32_t value);\n\n  void addLastByteEvent(HTTPTransaction* txn, uint64_t byteNo) noexcept;\n\n  void addAckToLastByteEvent(HTTPTransaction* txn,\n                             const ByteEvent& lastByteEvent);\n\n  /**\n   * Callback function from the flow control filter if the full window\n   * becomes not full.\n   */\n  void onConnectionSendWindowOpen() override;\n  void onConnectionSendWindowClosed() override;\n\n  /**\n   * Invoked when the codec processes callbacks for a stream we are no\n   * longer tracking.\n   */\n  void invalidStream(HTTPCodec::StreamID stream,\n                     ErrorCode code = ErrorCode::STREAM_CLOSED);\n\n  bool isConnWindowFull() const {\n    return connFlowControl_ && connFlowControl_->getAvailableSend() == 0;\n  }\n\n  // ByteEventTracker::Callback functions\n  void onPingReplyLatency(int64_t latency) noexcept override;\n  void onTxnByteEventWrittenToBuf(const ByteEvent& event) noexcept override;\n  void onDeleteTxnByteEvent() noexcept override;\n\n  /**\n   * Common EOM process shared by sendHeaders, sendBody and sendEOM\n   *\n   * @param txn             the transaction that's sending request\n   * @param encodedSize     size of data frame generated by codec\n   * @param piggybacked     whether this eom is a separate sendEOM or\n   *                          piggybacked in sendHeaders and sendBody\n   */\n  void commonEom(HTTPTransaction* txn,\n                 size_t encodedSize,\n                 bool piggybacked) noexcept;\n\n  /**\n   * Add a ReplaySafetyCallback requesting notification when the transport has\n   * replay protection.\n   *\n   * Most transport-layer security protocols (like TLS) provide protection\n   * against an eavesdropper capturing data, and later replaying it to the\n   * server. However, 0-RTT security protocols allow initial data to be sent\n   * without replay protection before the security handshake completes. This\n   * function can be used when a HTTP session is in that initial non-replay safe\n   * stage, but a request requires a replay safe transport. Will trigger\n   * callback synchronously if the transport is already replay safe.\n   */\n  void addWaitingForReplaySafety(\n      ReplaySafetyCallback* callback) noexcept override {\n    if (sock_->isReplaySafe()) {\n      callback->onReplaySafe();\n    } else {\n      waitingForReplaySafety_.push_back(callback);\n    }\n  }\n\n  /**\n   * Remove a ReplaySafetyCallback that had been waiting for replay safety\n   * (eg if a transaction waiting for replay safety is canceled).\n   */\n  void removeWaitingForReplaySafety(\n      ReplaySafetyCallback* callback) noexcept override {\n    waitingForReplaySafety_.remove(callback);\n  }\n\n  /**\n   * This is a temporary workaround until we have a better way to allocate\n   * stream IDs to waiting transactions.\n   */\n  bool needToBlockForReplaySafety() const override {\n    return !waitingForReplaySafety_.empty();\n  }\n\n  /**\n   * Callback from the transport to this HTTPSession to signal when the\n   * transport has become replay safe.\n   */\n  void onReplaySafe() noexcept override;\n\n  // Returns the number of streams that count against a pipelining limit.\n  // Upstreams can't really pipleine (send responses before requests), so\n  // count ANY stream against the limit.\n  size_t getPipelineStreamCount() const {\n    return isDownstream() ? incomingStreams_ : transactions_.size();\n  }\n\n  bool maybeResumePausedPipelinedTransaction(size_t oldStreamCount,\n                                             uint32_t txnSeqn);\n\n  void incrementOutgoingStreams(HTTPTransaction* txn);\n  void incrementIncomingStreams(HTTPTransaction* txn);\n\n  // returns true if the threshold has been exceeded\n  bool incrementNumControlMsgsInCurInterval(http2::FrameType frameType);\n\n  // returns true if the rate limiting threshold has been exceeded\n  bool incrementDirectErrorHandlingInCurInterval();\n\n  void scheduleResetNumControlMsgs();\n\n  void scheduleResetDirectErrorHandling();\n\n  size_t sendPing(uint64_t data) override;\n\n  // private members\n\n  std::list<ReplaySafetyCallback*> waitingForReplaySafety_;\n\n  folly::Optional<std::pair<uint64_t, HTTPSession::DestructorGuard>>\n      pendingWrite_;\n\n  /**\n   * Connection level flow control for HTTP/2\n   */\n  FlowControlFilter* connFlowControl_{nullptr};\n\n  /**\n   * The received setting for the maximum number of concurrent\n   * transactions that this session may create. We may assume the\n   * remote allows unlimited transactions until we get a SETTINGS frame,\n   * but to be reasonable, assume the remote doesn't allow more than 100K\n   * concurrent transactions on one connection.\n   */\n  uint32_t maxConcurrentOutgoingStreamsRemote_{\n      kDefaultMaxConcurrentOutgoingStreamsRemote};\n\n  /**\n   * The maximum number of concurrent transactions that this session's peer\n   * may create.\n   */\n  uint32_t maxConcurrentIncomingStreams_{kDefaultMaxConcurrentIncomingStreams};\n\n  /**\n   * The number concurrent transactions initiated by this session\n   */\n  uint32_t outgoingStreams_{0};\n\n  /**\n   * The number of concurrent transactions initiated by this sessions's peer\n   */\n  uint32_t incomingStreams_{0};\n\n  /**\n   * Number of bytes written so far.\n   */\n  uint64_t bytesWritten_{0};\n\n  /**\n   * Number of bytes scheduled so far.\n   */\n  uint64_t bytesScheduled_{0};\n\n  /**\n   * Number of body un-encoded bytes in the write buffer per write iteration.\n   */\n  uint64_t bodyBytesPerWriteBuf_{0};\n\n  /**\n   * Container to hold the results of HTTP2PriorityQueue::nextEgress\n   */\n  HTTP2PriorityQueue::NextEgressResult nextEgressResults_;\n\n  std::shared_ptr<ByteEventTracker> byteEventTracker_{nullptr};\n\n  std::unique_ptr<HTTPSessionActivityTracker> httpSessionActivityTracker_;\n\n  HTTPTransaction* lastTxn_{nullptr};\n\n  /**\n   * Max number of bytes to egress per session\n   */\n  uint64_t egressBytesLimit_{0};\n\n  // Flow control settings\n  size_t initialReceiveWindow_{0};\n  size_t receiveStreamWindowSize_{0};\n  size_t receiveSessionWindowSize_{0};\n\n  class ShutdownTransportCallback : public folly::EventBase::LoopCallback {\n   public:\n    explicit ShutdownTransportCallback(HTTPSession* session)\n        : session_(session), dg_(std::make_unique<DestructorGuard>(session)) {\n    }\n\n    ~ShutdownTransportCallback() override = default;\n\n    void runLoopCallback() noexcept override;\n\n   private:\n    HTTPSession* session_;\n    std::unique_ptr<DestructorGuard> dg_;\n  };\n  std::unique_ptr<ShutdownTransportCallback> shutdownTransportCb_;\n\n  class FlowControlTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit FlowControlTimeout(HTTPSession* session) : session_(session) {\n    }\n    ~FlowControlTimeout() override = default;\n\n    void timeoutExpired() noexcept override {\n      session_->flowControlTimeoutExpired();\n    }\n\n    [[nodiscard]] std::chrono::milliseconds getTimeoutDuration() const {\n      return duration_;\n    }\n\n    void setTimeoutDuration(std::chrono::milliseconds duration) {\n      duration_ = duration;\n    }\n\n   private:\n    HTTPSession* session_;\n    std::chrono::milliseconds duration_{std::chrono::milliseconds(0)};\n  };\n  FlowControlTimeout flowControlTimeout_;\n\n  class DrainTimeout : public folly::HHWheelTimer::Callback {\n   public:\n    explicit DrainTimeout(HTTPSession* session) : session_(session) {\n    }\n    ~DrainTimeout() override = default;\n\n    void timeoutExpired() noexcept override {\n      session_->closeWhenIdle();\n    }\n\n   private:\n    HTTPSession* session_;\n  };\n  DrainTimeout drainTimeout_;\n  std::chrono::milliseconds getDrainTimeout() const;\n\n  class PingProber : public folly::HHWheelTimer::Callback {\n   public:\n    PingProber(HTTPSession& session,\n               std::chrono::seconds interval,\n               std::chrono::seconds timeout,\n               bool extendIntervalOnIngress,\n               bool immediate);\n\n    void startProbes();\n\n    void cancelProbes();\n\n    void refreshTimeout(bool onIngress);\n\n    void timeoutExpired() noexcept override;\n\n    void callbackCanceled() noexcept override {\n    }\n\n    void onPingReply(uint64_t data);\n\n   private:\n    HTTPSession& session_;\n    std::chrono::seconds interval_;\n    std::chrono::seconds timeout_;\n    folly::Optional<uint64_t> pingVal_;\n    bool extendIntervalOnIngress_{true};\n  };\n  std::unique_ptr<PingProber> pingProber_;\n\n  // secondary authentication manager\n  std::unique_ptr<SecondaryAuthManagerBase> secondAuthManager_;\n\n  enum class SocketState : uint8_t {\n    UNPAUSED = 0,\n    PAUSED = 1,\n    SHUTDOWN = 2,\n  };\n\n  SocketState reads_;\n  SocketState writes_;\n\n  /**\n   * Indicates whether an upgrade request has been received from the codec.\n   */\n  bool ingressUpgraded_ : 1;\n  bool resetSocketOnShutdown_ : 1;\n  // indicates a fatal error that prevents further ingress data processing\n  bool inLoopCallback_ : 1;\n  bool pendingPause_ : 1;\n  bool writeBufSplit_ : 1;\n\n  bool abortPushesOnRST_{false};\n\n  /**\n   * Accessor implementation for HTTPSessionObserver.\n   */\n  class ObserverAccessor : public HTTPSessionObserverAccessor {\n   public:\n    explicit ObserverAccessor(HTTPSessionBase* sessionBasePtr)\n        : sessionBasePtr_(sessionBasePtr) {\n      (void)sessionBasePtr_; // silence unused variable warnings\n    }\n    ~ObserverAccessor() override = default;\n\n    size_t sendPing(uint64_t data) override {\n      if (sessionBasePtr_) {\n        return sessionBasePtr_->sendPing(data);\n      }\n      return 0;\n    }\n\n   private:\n    HTTPSessionBase* sessionBasePtr_{nullptr};\n  };\n\n  ObserverAccessor sessionObserverAccessor_;\n\n  // Container of observers for a HTTP session\n  //\n  // This member MUST be last in the list of members to ensure it is destroyed\n  // first, before any other members are destroyed. This ensures that observers\n  // can inspect any session state available through public methods\n  // when destruction of the session begins.\n  HTTPSessionObserverContainer sessionObserverContainer_;\n\n  HTTPSessionObserverContainer* getHTTPSessionObserverContainer()\n      const override {\n    return const_cast<HTTPSessionObserverContainer*>(\n        &sessionObserverContainer_);\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionAcceptor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPSessionAcceptor.h>\n\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.h>\n#include <proxygen/lib/http/session/HTTPDirectResponseHandler.h>\n\nusing folly::SocketAddress;\nusing std::string;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nconst SocketAddress HTTPSessionAcceptor::unknownSocketAddress_(\"0.0.0.0\", 0);\n\nHTTPSessionAcceptor::HTTPSessionAcceptor(\n    std::shared_ptr<const AcceptorConfiguration> accConfig)\n    : HTTPSessionAcceptor(std::move(accConfig), nullptr) {\n}\n\nHTTPSessionAcceptor::HTTPSessionAcceptor(\n    std::shared_ptr<const AcceptorConfiguration> accConfig,\n    std::shared_ptr<HTTPCodecFactory> codecFactory)\n    : HTTPAcceptor(std::move(accConfig)),\n      codecFactory_(codecFactory),\n      simpleController_(std::make_shared<SimpleController>(this)) {\n  if (!codecFactory_) {\n    codecFactory_ =\n        std::make_shared<HTTPDefaultSessionCodecFactory>(getConfig());\n  }\n}\n\nHTTPSessionAcceptor::~HTTPSessionAcceptor() = default;\n\nconst HTTPErrorPage* HTTPSessionAcceptor::getErrorPage(\n    const SocketAddress& addr) const {\n  return defaultErrorPage_.get();\n}\n\nvoid HTTPSessionAcceptor::onNewConnection(folly::AsyncTransport::UniquePtr sock,\n                                          const SocketAddress* peerAddress,\n                                          const string& nextProtocol,\n                                          wangle::SecureTransportType,\n                                          const wangle::TransportInfo& tinfo) {\n\n  unique_ptr<HTTPCodec> codec = codecFactory_->getCodec(\n      nextProtocol,\n      TransportDirection::DOWNSTREAM,\n      // we assume if security protocol isn't empty, then it's TLS\n      !sock->getSecurityProtocol().empty());\n\n  if (!codec) {\n    VLOG(2) << \"codecFactory_ failed to provide codec\";\n    onSessionCreationError(ProxygenError::kErrorUnsupportedScheme);\n    return;\n  }\n  auto egressSettings = codec->getEgressSettings();\n  if (egressSettings && setEnableConnectProtocol_) {\n    egressSettings->setSetting(SettingsId::ENABLE_CONNECT_PROTOCOL, 1);\n  }\n\n  auto controller = getController();\n  SocketAddress localAddress;\n  try {\n    sock->getLocalAddress(&localAddress);\n  } catch (...) {\n    VLOG(3) << \"couldn't get local address for socket\";\n    localAddress = unknownSocketAddress_;\n  }\n\n  // overwrite address if the socket has no IP, e.g. Unix domain socket\n  if (!localAddress.isFamilyInet()) {\n    if (getConfig()->bindAddress.isFamilyInet()) {\n      localAddress = getConfig()->bindAddress;\n    } else {\n      localAddress = unknownSocketAddress_;\n    }\n    VLOG(4) << \"set localAddress=\" << localAddress.describe();\n  }\n\n  auto sessionInfoCb = sessionInfoCb_ ? sessionInfoCb_ : this;\n  VLOG(4) << \"Created new \" << nextProtocol << \" session for peer \"\n          << *peerAddress;\n  auto codecProtocol = codec->getProtocol();\n  auto* session = new HTTPDownstreamSession(getTransactionTimeoutSet(),\n                                            std::move(sock),\n                                            localAddress,\n                                            *peerAddress,\n                                            controller.get(),\n                                            std::move(codec),\n                                            tinfo,\n                                            sessionInfoCb);\n  if (getConfig()->maxConcurrentIncomingStreams) {\n    session->setMaxConcurrentIncomingStreams(\n        getConfig()->maxConcurrentIncomingStreams);\n  }\n  session->setEgressSettings(getConfig()->egressSettings);\n\n  // set HTTP2 priorities flag on session object\n  auto HTTP2PrioritiesEnabled = getHttp2PrioritiesEnabled();\n  session->setHTTP2PrioritiesEnabled(HTTP2PrioritiesEnabled);\n\n  // set flow control parameters\n  session->setFlowControl(getConfig()->initialReceiveWindow,\n                          getConfig()->receiveStreamWindowSize,\n                          getConfig()->receiveSessionWindowSize);\n  // TODO(@damlaj): support server early resp for http/3\n  if (getConfig()->serverEarlyResponseEnabled &&\n      codecProtocol == CodecProtocol::HTTP_2) {\n    session->enableServerEarlyResponse();\n  }\n  if (getConfig()->writeBufferLimit > 0) {\n    session->setWriteBufferLimit(getConfig()->writeBufferLimit);\n  }\n  session->setSessionStats(downstreamSessionStats_);\n  Acceptor::addConnection(session);\n  startSession(*session);\n}\n\nsize_t HTTPSessionAcceptor::dropIdleConnections(size_t num) {\n  // release in batch for more efficiency\n  VLOG(6) << \"attempt to drop downstream idle connections\";\n  return downstreamConnectionManager_->dropIdleConnections(num);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionAcceptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <proxygen/lib/http/session/HTTPErrorPage.h>\n#include <proxygen/lib/http/session/SimpleController.h>\n#include <proxygen/lib/services/HTTPAcceptor.h>\n\nnamespace proxygen {\n\nclass HTTPSessionStats;\n\n/**\n * Specialization of Acceptor that serves as an abstract base for\n * acceptors that support HTTP and related protocols.\n */\nclass HTTPSessionAcceptor\n    : public HTTPAcceptor\n    , private HTTPSessionBase::InfoCallback {\n public:\n  explicit HTTPSessionAcceptor(\n      std::shared_ptr<const AcceptorConfiguration> accConfig);\n  explicit HTTPSessionAcceptor(\n      std::shared_ptr<const AcceptorConfiguration> accConfig,\n      std::shared_ptr<HTTPCodecFactory> codecFactory);\n  ~HTTPSessionAcceptor() override;\n\n  /**\n   * Set the default error page generator.\n   */\n  void setDefaultErrorPage(std::unique_ptr<HTTPErrorPage> generator) {\n    defaultErrorPage_ = std::move(generator);\n  }\n\n  /**\n   * Access the default error page generator.\n   */\n  [[nodiscard]] const HTTPErrorPage* getDefaultErrorPage() const {\n    return defaultErrorPage_.get();\n  }\n\n  /**\n   * Access the right error page generator for a connection.\n   * @param   localAddr  Address of the local end of the connection.\n   * @return  The diagnostic error page generator if one has been\n   *          set AND the connection is to an internal VIP, or\n   *          else the default error page generator if one has\n   *          been set, or else nullptr.\n   */\n  [[nodiscard]] virtual const HTTPErrorPage* getErrorPage(\n      const folly::SocketAddress& addr) const;\n\n  /**\n   * Set the codec factory for this session\n   */\n  void setCodecFactory(std::shared_ptr<HTTPCodecFactory> codecFactory) {\n    codecFactory_ = codecFactory;\n  }\n\n  /**\n   * return the codec factory for this session\n   */\n  std::shared_ptr<HTTPCodecFactory> getCodecFactory() {\n    return codecFactory_;\n  }\n\n  /**\n   * Create a Handler for a new transaction.  The transaction and HTTP message\n   * (request) are passed so the implementation can construct different\n   * handlers based on these.  The transaction will be explicitly set on the\n   * handler later via setTransaction.  The request message will be passed\n   * in onHeadersComplete.\n   */\n  virtual HTTPTransaction::Handler* newHandler(HTTPTransaction& txn,\n                                               HTTPMessage* msg) noexcept = 0;\n\n  /**\n   * Set an HTTPSession::InfoCallback to use for each session instead of the\n   * acceptor object.\n   */\n  void setSessionInfoCallback(HTTPSession::InfoCallback* cb) {\n    sessionInfoCb_ = cb;\n  }\n\n  virtual bool getHttp2PrioritiesEnabled() {\n    return getConfig()->HTTP2PrioritiesEnabled;\n  }\n\n protected:\n  /**\n   * This function is invoked when a new session is created to get the\n   * controller to associate with the new session. Child classes may\n   * override this function to provide their own more sophisticated\n   * controller here.\n   */\n  virtual std::shared_ptr<HTTPSessionController> getController() {\n    return simpleController_;\n  }\n\n  HTTPSessionStats* downstreamSessionStats_{nullptr};\n\n  bool setEnableConnectProtocol_{false};\n\n  HTTPSession::InfoCallback* getSessionInfoCallback() {\n    return sessionInfoCb_ ? sessionInfoCb_ : this;\n  }\n\n  // Acceptor methods\n  void onNewConnection(folly::AsyncTransport::UniquePtr sock,\n                       const folly::SocketAddress* address,\n                       const std::string& nextProtocol,\n                       wangle::SecureTransportType secureTransportType,\n                       const wangle::TransportInfo& tinfo) override;\n\n  virtual void startSession(HTTPSessionBase& session) {\n    session.startNow();\n  }\n  virtual size_t dropIdleConnections(size_t num);\n\n  virtual void onSessionCreationError(ProxygenError /*error*/) {\n  }\n\n private:\n  HTTPSessionAcceptor(const HTTPSessionAcceptor&) = delete;\n  HTTPSessionAcceptor& operator=(const HTTPSessionAcceptor&) = delete;\n\n  /** General-case error page generator */\n  std::unique_ptr<HTTPErrorPage> defaultErrorPage_;\n\n  std::shared_ptr<HTTPCodecFactory> codecFactory_{};\n\n  std::shared_ptr<SimpleController> simpleController_;\n\n  HTTPSession::InfoCallback* sessionInfoCb_{nullptr};\n\n  /**\n   * 0.0.0.0:0, a valid address to use if getsockname() or getpeername() fails\n   */\n  static const folly::SocketAddress unknownSocketAddress_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionActivityTracker.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n#include <proxygen/lib/http/session/HTTPSessionActivityTracker.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nHTTPSessionActivityTracker::HTTPSessionActivityTracker(\n    wangle::ManagedConnection* managedConnection,\n    size_t ingressThreshold,\n    size_t egressThreshold)\n    : managedConnection_(managedConnection),\n      ingressThreshold_(ingressThreshold),\n      egressThreshold_(egressThreshold) {\n}\n\nvoid HTTPSessionActivityTracker::reportActivity() {\n  if (managedConnection_->getConnectionManager()) {\n    managedConnection_->getConnectionManager()->reportActivity(\n        *managedConnection_);\n  }\n}\n\nbool HTTPSessionActivityTracker::onIngressBody(size_t bytes) {\n  ingressSize_ += bytes;\n  if (ingressSize_ >= ingressThreshold_) {\n    ingressSize_ = ingressSize_ % ingressThreshold_;\n    reportActivity();\n    return true;\n  }\n  return false;\n}\n\n// Note that the provided offset could be either session offset, by HTTPSession,\n// or stream offset, by HQSession. To support both cases, we keep track\n// internally of the session byte Offset, sessionBodyOffset_, and use to\n// calculate the required byte event tracker offset.\nvoid HTTPSessionActivityTracker::addTrackedEgressByteEvent(\n    const size_t offset,\n    const size_t bodyLen,\n    ByteEventTracker* byteEventTracker,\n    HTTPTransaction* txn) {\n  SCOPE_EXIT {\n    sessionBodyOffset_ += bodyLen;\n  };\n  if (!byteEventTracker || !txn ||\n      sessionBodyOffset_ / egressThreshold_ ==\n          (sessionBodyOffset_ + bodyLen) / egressThreshold_) {\n    return;\n  }\n  auto lastTrackedEgressByteEvent =\n      (sessionBodyOffset_ / egressThreshold_) * egressThreshold_;\n  auto reportActivityCb = [this](ByteEvent&) { reportActivity(); };\n  // Note that a single we might need to add multiple tracking events\n  while (sessionBodyOffset_ + bodyLen >=\n         lastTrackedEgressByteEvent + egressThreshold_) {\n    lastTrackedEgressByteEvent += egressThreshold_;\n    auto trackOffset = offset + lastTrackedEgressByteEvent - sessionBodyOffset_;\n    byteEventTracker->addTrackedByteEvent(txn, trackOffset, reportActivityCb);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionActivityTracker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <wangle/acceptor/ConnectionManager.h>\n\nnamespace proxygen {\n\nclass ByteEventTracker;\nclass HTTPTransaction;\n\n/*\n * HTTPSessionActivityTracker tracks activities, reads/writes, on active\n * sessions. The primary usage for this is to support Slowloris mitigation by\n * closing active sessions with low activity when the host is under memory\n * constraints. HTTPSessionActivityTracker is hooked to a session and tracks\n * significant activities. Once significant activity is detected, the connection\n * manager is notified of the event. Significant activities could be in the form\n * of read or write above threshold amount of bytes.\n */\nclass HTTPSessionActivityTracker {\n public:\n  HTTPSessionActivityTracker(wangle::ManagedConnection* managedConnection,\n                             size_t ingressThreshold,\n                             size_t egressThreshold);\n\n  virtual void reportActivity();\n\n  bool onIngressBody(size_t bytes);\n\n  void addTrackedEgressByteEvent(const size_t offset,\n                                 const size_t bodyLen,\n                                 ByteEventTracker* byteEventTracker,\n                                 HTTPTransaction* txn);\n  virtual ~HTTPSessionActivityTracker() = default;\n\n private:\n  wangle::ManagedConnection* managedConnection_;\n  size_t ingressSize_{0};\n  size_t sessionBodyOffset_{0};\n  const size_t ingressThreshold_;\n  const size_t egressThreshold_;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionBase.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPSessionBase.h>\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n\nusing folly::SocketAddress;\nusing wangle::TransportInfo;\n\nnamespace proxygen {\nstd::atomic<uint32_t> HTTPSessionBase::kDefaultReadBufLimit{65536};\nuint32_t HTTPSessionBase::maxReadBufferSize_ = 4000;\nuint32_t HTTPSessionBase::egressBodySizeLimit_ = 4096;\nuint32_t HTTPSessionBase::kDefaultWriteBufLimit = 65536;\n\nHTTPSessionBase::HTTPSessionBase(const SocketAddress& localAddr,\n                                 const SocketAddress& peerAddr,\n                                 HTTPSessionController* controller,\n                                 const TransportInfo& tinfo,\n                                 InfoCallback* infoCallback,\n                                 std::unique_ptr<HTTPCodec> codec,\n                                 const WheelTimerInstance& wheelTimer,\n                                 HTTPCodec::StreamID rootNodeId)\n    : infoCallback_(infoCallback),\n      transportInfo_(tinfo),\n      codec_(std::move(codec)),\n      localAddr_(localAddr),\n      peerAddr_(peerAddr),\n      controller_(controller),\n      h2PrioritiesEnabled_(true) {\n\n  // If we receive IPv4-mapped IPv6 addresses, convert them to IPv4.\n  localAddr_.tryConvertToIPv4();\n  peerAddr_.tryConvertToIPv4();\n}\n\nHTTPSessionBase::~HTTPSessionBase() {\n  if (sessionStats_) {\n    sessionStats_->recordPendingBufferedWriteBytes(-1 *\n                                                   (int64_t)pendingWriteSize_);\n    sessionStats_->recordPendingBufferedReadBytes(-1 *\n                                                  (int64_t)pendingReadSize_);\n  }\n}\n\nvoid HTTPSessionBase::setSessionStats(HTTPSessionStats* stats) {\n  if (sessionStats_ != stats && sessionStats_ != nullptr) {\n    sessionStats_->recordPendingBufferedWriteBytes(-1 *\n                                                   (int64_t)pendingWriteSize_);\n    sessionStats_->recordPendingBufferedReadBytes(-1 *\n                                                  (int64_t)pendingReadSize_);\n  }\n  sessionStats_ = stats;\n  if (sessionStats_) {\n    sessionStats_->recordPendingBufferedWriteBytes(pendingWriteSize_);\n    sessionStats_->recordPendingBufferedReadBytes(pendingReadSize_);\n  }\n}\n\nvoid HTTPSessionBase::setRateLimitParams(\n    RateLimiter::Type type,\n    uint32_t maxEventsPerInterval,\n    std::chrono::milliseconds intervalDuration) {\n  if (rateLimitFilter_) {\n    rateLimitFilter_->setRateLimitParams(\n        type, maxEventsPerInterval, intervalDuration);\n  }\n}\n\nvoid HTTPSessionBase::runDestroyCallbacks() {\n  if (infoCallback_) {\n    infoCallback_->onDestroy(*this);\n  }\n  if (controller_) {\n    controller_->detachSession(this);\n    controller_ = nullptr;\n  }\n}\n\nvoid HTTPSessionBase::onCodecChanged() {\n  if (controller_) {\n    controller_->onSessionCodecChange(this);\n  }\n\n  initCodecHeaderIndexingStrategy();\n}\n\nvoid HTTPSessionBase::initCodecHeaderIndexingStrategy() {\n  if (controller_) {\n    setHeaderIndexingStrategy(controller_->getHeaderIndexingStrategy());\n  }\n}\n\nbool HTTPSessionBase::onBodyImpl(std::unique_ptr<folly::IOBuf> chain,\n                                 size_t length,\n                                 uint16_t padding,\n                                 HTTPTransaction* txn) {\n  DestructorGuard dg(this);\n  auto oldSize = pendingReadSize_;\n  CHECK_LE(pendingReadSize_,\n           std::numeric_limits<uint32_t>::max() - length - padding);\n  pendingReadSize_ += length + padding;\n  if (httpSessionActivityTracker_) {\n    httpSessionActivityTracker_->onIngressBody(length + padding);\n  }\n  if (sessionStats_) {\n    sessionStats_->recordPendingBufferedReadBytes(length + padding);\n  }\n  txn->onIngressBody(std::move(chain), padding);\n  if (oldSize < pendingReadSize_) {\n    // Transaction must have buffered something and not called\n    // notifyBodyProcessed() on it.\n    VLOG(4) << *this << \" Enqueued ingress. Ingress buffer uses \"\n            << pendingReadSize_ << \" of \" << readBufLimit_ << \" bytes.\";\n    if (ingressLimitExceeded() && oldSize <= readBufLimit_) {\n      if (infoCallback_) {\n        infoCallback_->onIngressLimitExceeded(*this);\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\nbool HTTPSessionBase::notifyBodyProcessed(uint32_t bytes) {\n  CHECK_GE(pendingReadSize_, bytes);\n  auto oldSize = pendingReadSize_;\n  pendingReadSize_ -= bytes;\n  if (sessionStats_) {\n    sessionStats_->recordPendingBufferedReadBytes(-1 * (int64_t)bytes);\n  }\n\n  VLOG(4) << *this << \" Dequeued \" << bytes << \" bytes of ingress. \"\n          << \"Ingress buffer uses \" << pendingReadSize_ << \" of \"\n          << readBufLimit_ << \" bytes.\";\n  if (oldSize > readBufLimit_ && pendingReadSize_ <= readBufLimit_) {\n    return true;\n  }\n  return false;\n}\n\nbool HTTPSessionBase::notifyEgressBodyBuffered(int64_t bytes, bool update) {\n  pendingWriteSizeDelta_ += bytes;\n  VLOG(4) << __func__ << \" pwsd=\" << pendingWriteSizeDelta_;\n  // any net change requires us to update pause/resume state in the\n  // loop callback\n  if (pendingWriteSizeDelta_ >= 0 && update) {\n    // pause inline, resume in loop\n    updateWriteBufSize(0);\n    return false;\n  }\n  return true;\n}\n\nvoid HTTPSessionBase::updateWriteBufSize(int64_t delta) {\n  // This is the sum of body bytes buffered within transactions_ and in\n  // the sock_'s write buffer.\n  delta += pendingWriteSizeDelta_;\n  pendingWriteSizeDelta_ = 0;\n  DCHECK(delta >= 0 || uint64_t(-delta) <= pendingWriteSize_);\n  if (sessionStats_) {\n    sessionStats_->recordPendingBufferedWriteBytes(delta);\n  }\n  pendingWriteSize_ += delta;\n}\n\nvoid HTTPSessionBase::updatePendingWrites() {\n  if (pendingWriteSizeDelta_) {\n    updateWriteBufSize(0);\n  }\n}\n\nvoid HTTPSessionBase::handleErrorDirectly(HTTPTransaction* txn,\n                                          const HTTPException& error) {\n  VLOG(4) << *this << \" creating direct error handler\";\n  DCHECK(txn);\n  auto handler = getParseErrorHandler(txn, error);\n  if (!handler) {\n    txn->sendAbort();\n    return;\n  }\n  txn->setHandler(handler);\n  if (infoCallback_) {\n    infoCallback_->onIngressError(*this, error.getProxygenError());\n  }\n  txn->onError(error);\n}\n\nHTTPTransaction::Handler* HTTPSessionBase::getParseErrorHandler(\n    HTTPTransaction* txn, const HTTPException& error) {\n  // we encounter an error before we finish reading the ingress headers.\n  if (codec_->getTransportDirection() == TransportDirection::UPSTREAM) {\n    // do not return the parse error handler for upstreams, since all we\n    // can do in that direction is abort.\n    return nullptr;\n  }\n  return controller_->getParseErrorHandler(txn, error, getLocalAddress());\n}\n\nvoid HTTPSessionBase::attachToSessionController() {\n  auto controllerPtr = getController();\n  if (controllerPtr) {\n    controllerPtr->attachSession(this);\n  }\n}\n\nvoid HTTPSessionBase::informSessionControllerTransportReady() {\n  auto controllerPtr = getController();\n  if (controllerPtr) {\n    controllerPtr->onTransportReady(this);\n  }\n}\n\nvoid HTTPSessionBase::handleLastByteEvents(ByteEventTracker* byteEventTracker,\n                                           HTTPTransaction* txn,\n                                           size_t encodedSize,\n                                           size_t byteOffset,\n                                           bool piggybacked) {\n  // TODO: sort out the TransportCallback for all the EOM handling cases.\n  //  Current code has the same behavior as before when there wasn't commonEom.\n  //  The issue here is onEgressBodyLastByte can be called twice, depending on\n  //  the encodedSize. E.g., when codec actually write to buffer in sendEOM.\n  if (!txn->testAndSetFirstByteSent()) {\n    txn->onEgressBodyFirstByte();\n  }\n  if (!piggybacked) {\n    txn->onEgressBodyLastByte();\n  }\n  // in case encodedSize == 0 we won't get TTLBA which is acceptable\n  // noting the fact that we don't have a response body\n  if (byteEventTracker && (encodedSize > 0)) {\n    byteEventTracker->addLastByteEvent(txn, byteOffset);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/record/Types.h>\n#include <folly/io/IOBuf.h>\n#include <folly/io/async/AsyncUDPSocket.h>\n#include <folly/io/async/SSLContext.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n#include <proxygen/lib/http/codec/RateLimitFilter.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n#include <proxygen/lib/http/session/HTTPSessionActivityTracker.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/utils/Time.h>\n#include <wangle/acceptor/ManagedConnection.h>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\nclass HTTPSessionController;\nclass HTTPSessionStats;\nclass HTTPTransaction;\nclass ByteEventTracker;\n\nconstexpr uint32_t kDefaultMaxConcurrentOutgoingStreams = 100;\n\nclass HTTPSessionBase : public wangle::ManagedConnection {\n public:\n  /**\n   * Optional callback interface that the HTTPSessionBase\n   * notifies of connection lifecycle events.\n   */\n  class InfoCallback {\n   public:\n    virtual ~InfoCallback() = default;\n\n    // Note: you must not start any asynchronous work from onCreate()\n    virtual void onCreate(const HTTPSessionBase&) {\n    }\n    virtual void onTransportReady(const HTTPSessionBase&) {\n    }\n    virtual void onConnectionError(const HTTPSessionBase&) {\n    }\n    virtual void onFullHandshakeCompletion(const HTTPSessionBase&) {\n    }\n    virtual void onIngressError(const HTTPSessionBase&, ProxygenError) {\n    }\n    virtual void onIngressEOF() {\n    }\n    virtual void onRead(const HTTPSessionBase&, size_t /*bytesRead*/) {\n    }\n    /**\n     * New version of the API.  Includes the stream these bytes belong to,\n     * or HTTPCodec::NoStream if unknown.  bytesRead can be 0 if the stream\n     * ended.\n     *\n     * If onRead is currently implemented with the old signature (without stream\n     * ID), safest path is to keep it and change it to call onRead with the new\n     * signature that includes the stream ID with folly::none as the Stream ID.\n     */\n    virtual void onRead(const HTTPSessionBase& sess,\n                        size_t bytesRead,\n                        folly::Optional<HTTPCodec::StreamID> /*stream id*/) {\n      onRead(sess, bytesRead);\n    }\n    virtual void onWrite(const HTTPSessionBase&, size_t /*bytesWritten*/) {\n    }\n    virtual void onRequestBegin(const HTTPSessionBase&) {\n    }\n    virtual void onRequestEnd(const HTTPSessionBase&,\n                              uint32_t /*maxIngressQueueSize*/) {\n    }\n    virtual void onActivateConnection(const HTTPSessionBase&) {\n    }\n    virtual void onDeactivateConnection(const HTTPSessionBase&) {\n    }\n    // Note: you must not start any asynchronous work from onDestroy()\n    virtual void onDestroy(const HTTPSessionBase&) {\n    }\n    virtual void onIngressMessage(const HTTPSessionBase&, const HTTPMessage&) {\n    }\n    virtual void onIngressLimitExceeded(const HTTPSessionBase&) {\n    }\n    virtual void onIngressPaused(const HTTPSessionBase&) {\n    }\n    virtual void onTransactionAttached(const HTTPSessionBase&) {\n    }\n    virtual void onTransactionDetached(const HTTPSessionBase&) {\n    }\n    virtual void onPingReplySent(int64_t /*latency*/) {\n    }\n    virtual void onPingReplyReceived() {\n    }\n    virtual void onSettingsOutgoingStreamsFull(const HTTPSessionBase&) {\n    }\n    virtual void onSettingsOutgoingStreamsNotFull(const HTTPSessionBase&) {\n    }\n    virtual void onFlowControlWindowClosed(const HTTPSessionBase&) {\n    }\n    virtual void onEgressBuffered(const HTTPSessionBase&) {\n    }\n    virtual void onEgressBufferCleared(const HTTPSessionBase&) {\n    }\n    virtual void onSettings(const HTTPSessionBase&, const SettingsList&) {\n    }\n    virtual void onSettingsAck(const HTTPSessionBase&) {\n    }\n  };\n\n  HTTPSessionBase(const folly::SocketAddress& localAddr,\n                  const folly::SocketAddress& peerAddr,\n                  HTTPSessionController* controller,\n                  const wangle::TransportInfo& tinfo,\n                  InfoCallback* infoCallback,\n                  std::unique_ptr<HTTPCodec> codec,\n                  const WheelTimerInstance& wheelTimer,\n                  HTTPCodec::StreamID rootNodeId);\n\n  ~HTTPSessionBase() override;\n\n  virtual void setHTTPSessionActivityTracker(\n      std::unique_ptr<HTTPSessionActivityTracker> httpSessionActivityTracker) {\n    httpSessionActivityTracker_ = std::move(httpSessionActivityTracker);\n  }\n\n  [[nodiscard]] HTTPSessionActivityTracker* getHTTPSessionActivityTracker()\n      const {\n    return httpSessionActivityTracker_.get();\n  }\n  /**\n   * Set the read buffer limit to be used for all new HTTPSessionBase objects.\n   */\n  static void setDefaultReadBufferLimit(uint32_t limit) {\n    kDefaultReadBufLimit = limit;\n    VLOG(3) << \"read buffer limit: \" << int(limit / 1000) << \"KB\";\n  }\n\n  static void setMaxReadBufferSize(uint32_t bytes) {\n    maxReadBufferSize_ = bytes;\n  }\n\n  /**\n   * Set the maximum egress body size for any outbound body bytes per loop,\n   * when there are > 1 transactions.\n   */\n  static void setFlowControlledBodySizeLimit(uint32_t limit) {\n    egressBodySizeLimit_ = limit;\n  }\n\n  /**\n   * Set the default number of egress bytes this session will buffer before\n   * pausing all transactions' egress.\n   */\n  static void setDefaultWriteBufferLimit(uint32_t max) {\n    kDefaultWriteBufLimit = max;\n  }\n\n  void setInfoCallback(InfoCallback* callback) {\n    infoCallback_ = callback;\n  }\n\n  void setRateLimitParams(RateLimiter::Type type,\n                          uint32_t maxEventsPerInterval,\n                          std::chrono::milliseconds intervalDuration);\n\n  [[nodiscard]] InfoCallback* getInfoCallback() const {\n    return infoCallback_;\n  }\n\n  virtual void setHeaderIndexingStrategy(\n      const HeaderIndexingStrategy* strat) = 0;\n\n  virtual void setSessionStats(HTTPSessionStats* stats);\n\n  [[nodiscard]] virtual HTTPTransaction::Transport::Type getType()\n      const noexcept = 0;\n\n  virtual folly::AsyncTransport* getTransport() = 0;\n\n  [[nodiscard]] virtual const folly::AsyncTransport* getTransport() const = 0;\n\n  [[nodiscard]] virtual folly::EventBase* getEventBase() const = 0;\n\n  /**\n   * Called by handleErrorDirectly (when handling parse errors) if the\n   * transaction has no handler.\n   */\n  HTTPTransaction::Handler* getParseErrorHandler(HTTPTransaction* txn,\n                                                 const HTTPException& error);\n\n  [[nodiscard]] virtual bool hasActiveTransactions() const = 0;\n\n  /**\n   * Returns true iff a new outgoing transaction can be made on this session\n   */\n  [[nodiscard]] virtual bool supportsMoreTransactions() const {\n    return (getNumOutgoingStreams() < getMaxConcurrentOutgoingStreams());\n  }\n\n  [[nodiscard]] virtual uint32_t getNumStreams() const = 0;\n\n  [[nodiscard]] virtual uint32_t getNumOutgoingStreams() const = 0;\n\n  // SimpleSessionPool\n  [[nodiscard]] uint32_t getHistoricalMaxOutgoingStreams() const {\n    return historicalMaxOutgoingStreams_;\n  }\n\n  [[nodiscard]] virtual uint32_t getNumIncomingStreams() const = 0;\n\n  [[nodiscard]] virtual uint32_t getMaxConcurrentOutgoingStreamsRemote()\n      const = 0;\n\n  [[nodiscard]] uint32_t getMaxConcurrentOutgoingStreams() const {\n    return std::min(maxConcurrentOutgoingStreamsConfig_,\n                    getMaxConcurrentOutgoingStreamsRemote());\n  }\n\n  [[nodiscard]] HTTPSessionController* getController() const {\n    return controller_;\n  }\n\n  void setController(HTTPSessionController* controller) {\n    controller_ = controller;\n\n    // Controller controlled settings\n    initCodecHeaderIndexingStrategy();\n  }\n\n  [[nodiscard]] ConnectionCloseReason getConnectionCloseReason() const {\n    return closeReason_;\n  }\n\n  template <typename Filter, typename... Args>\n  void addCodecFilter(Args&&... args) {\n    codec_.add<Filter>(std::forward<Args>(args)...);\n  }\n\n  [[nodiscard]] virtual CodecProtocol getCodecProtocol() const {\n    return codec_->getProtocol();\n  }\n\n  /**\n   * Set flow control properties on the session.\n   *\n   * @param initialReceiveWindow      size of initial receive window\n   *                                  for all ingress streams; set via\n   *                                  the initial SETTINGS frame\n   * @param receiveStreamWindowSize   per-stream receive window for NEW streams;\n   *                                  sent via a WINDOW_UPDATE frame\n   * @param receiveSessionWindowSize  per-session receive window; sent\n   *                                  via a WINDOW_UPDATE frame\n   */\n  virtual void setFlowControl(size_t initialReceiveWindow,\n                              size_t receiveStreamWindowSize,\n                              size_t receiveSessionWindowSize) = 0;\n\n  /**\n   * Set outgoing settings for this session\n   */\n  virtual void setEgressSettings(const SettingsList& inSettings) = 0;\n\n  /**\n   * Global flag for turning HTTP2 priorities off\n   **/\n  void setHTTP2PrioritiesEnabled(bool enabled) /*override*/ {\n    h2PrioritiesEnabled_ = enabled;\n  }\n\n  [[nodiscard]] virtual bool getHTTP2PrioritiesEnabled() const {\n    return h2PrioritiesEnabled_;\n  }\n\n  /**\n   * Set the maximum number of outgoing transactions this session can open\n   * at once. Note: you can only call function before startNow() is called\n   * since the remote side can change this value.\n   */\n  void setMaxConcurrentOutgoingStreams(uint32_t num) {\n    // TODO: CHECK(started_);\n    maxConcurrentOutgoingStreamsConfig_ = num;\n  }\n\n  /**\n   * Set the maximum number of transactions the remote can open at once.\n   */\n  virtual void setMaxConcurrentIncomingStreams(uint32_t num) = 0;\n\n  /**\n   * Get/Set the number of egress bytes this session will buffer before\n   * pausing all transactions' egress.\n   */\n  [[nodiscard]] uint32_t getWriteBufferLimit() const {\n    return writeBufLimit_;\n  }\n\n  void setWriteBufferLimit(uint32_t limit) {\n    writeBufLimit_ = limit;\n    VLOG(4) << \"write buffer limit: \" << int(limit / 1000) << \"KB\";\n  }\n\n  void setReadBufferLimit(uint32_t limit) {\n    readBufLimit_ = limit;\n  }\n\n  /**\n   * Start reading from the transport and send any introductory messages\n   * to the remote side. This function must be called once per session to\n   * begin reads.\n   */\n  virtual void startNow() = 0;\n\n  /**\n   * Send a settings frame\n   */\n  virtual size_t sendSettings() = 0;\n\n  /**\n   * Causes a ping to be sent on the session. If the underlying protocol\n   * doesn't support pings, this will return 0. Otherwise, it will return\n   * the number of bytes written on the transport to send the ping.\n   */\n  virtual size_t sendPing() = 0;\n\n  virtual size_t sendPing(uint64_t data) = 0;\n\n  /**\n   * Send a CERTIFICATE_REQUEST frame. If the underlying protocol doesn't\n   * support secondary authentication, this is a no-op and 0 is returned.\n   */\n  virtual size_t sendCertificateRequest(\n      std::unique_ptr<folly::IOBuf> /* certificateRequestContext */,\n      std::vector<fizz::Extension> /* extensions */) {\n    return 0;\n  }\n\n  [[nodiscard]] uint64_t getNumTxnServed() const {\n    return transactionSeqNo_;\n  }\n\n  [[nodiscard]] bool getStreamLimitExceeded() const {\n    return streamLimitExceeded_;\n  }\n\n  [[nodiscard]] std::chrono::seconds getLatestIdleTime() const /*override*/ {\n    DCHECK_GT(transactionSeqNo_, 0u)\n        << \"No idle time for the first transaction\";\n    DCHECK(latestActive_ > TimePoint::min());\n    return latestIdleDuration_;\n  }\n\n  // public HTTPTransaction::Transport overrides\n  [[nodiscard]] virtual const folly::SocketAddress& getLocalAddress()\n      const noexcept {\n    return localAddr_;\n  }\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress()\n      const noexcept override {\n    return peerAddr_;\n  }\n  [[nodiscard]] const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept\n  /*override*/ {\n    return transportInfo_;\n  }\n  virtual bool getCurrentTransportInfo(\n      wangle::TransportInfo* tinfo) /*override*/\n      = 0;\n\n  virtual bool getCurrentTransportInfoWithoutUpdate(\n      wangle::TransportInfo* tinfo) const = 0;\n\n  virtual void setHeaderCodecStats(HeaderCodec::Stats* stats) = 0;\n\n  virtual void enableDoubleGoawayDrain() = 0;\n\n  wangle::TransportInfo& getSetupTransportInfo() noexcept {\n    return transportInfo_;\n  }\n\n  /**\n   * If the connection is closed by remote end\n   */\n  virtual bool connCloseByRemote() = 0;\n\n  // Upstream API\n\n  // The interfaces defined below update the virtual stream based priority\n  // scheme from the current system which allows only strict priorities to a\n  // flexible system allowing an arbitrary tree of virtual streams, subject only\n  // to the limitations in the HTTP/2 specification. An arbitrary prioritization\n  // scheme can be implemented by constructing virtual streams corresponding to\n  // the desired prioritization and attaching new streams as dependencies of the\n  // appropriate virtual stream.\n  //\n  // The user must define a map from an opaque integer priority level to an\n  // HTTP/2 priority corresponding to the virtual stream. This map is\n  // implemented by the user in a class that extends\n  // HTTPUpstreamSession::PriorityMapFactory. A shared pointer to this class is\n  // passed into the constructor of HTTPUpstreamSession. This method will send\n  // the virtual streams and return a unique pointer to a class that extends\n  // HTTPUpstreamSession::PriorityAdapter. This class implements the map between\n  // the user defined priority level and the HTTP/2 priority level.\n  //\n  // When the session is started, the createVirtualStreams method of\n  // PriorityMapFactory is called by HTTPUpstreamSession::startNow. The returned\n  // pointer to the PriorityAdapter map is cached in HTTPUpstreamSession. The\n  // HTTP/2 priority that should be used for a new stream dependent on a virtual\n  // stream corresponding to a given priority level is then retrieved by calling\n  // the HTTPUpstreamSession::getHTTPPriority(uint8_t level) method.\n  //\n  // The prior strict priority system has been left in place for now, but if\n  // both maxLevels and PriorityMapFactory are passed into the\n  // HTTPUpstreamSession constructor, the maxLevels parameter will be ignored.\n\n  // Implments a map from generic priority level to HTTP/2 priority.\n  class PriorityAdapter {\n   public:\n    virtual folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n        uint8_t level) = 0;\n    virtual ~PriorityAdapter() = default;\n  };\n\n  using FilterIteratorFn = std::function<void(HTTPCodecFilter*)>;\n\n  [[nodiscard]] virtual bool isDetachable(bool checkSocket) const = 0;\n\n  virtual void attachThreadLocals(\n      folly::EventBase* eventBase,\n      std::shared_ptr<const folly::SSLContext> sslContext,\n      const WheelTimerInstance& wheelTimer,\n      HTTPSessionStats* stats,\n      FilterIteratorFn fn,\n      HeaderCodec::Stats* headerCodecStats,\n      HTTPSessionController* controller) = 0;\n\n  virtual void detachThreadLocals(bool detachSSLContext = false) = 0;\n\n  /**\n   * Creates a new transaction on this upstream session. Invoking this function\n   * also has the side-affect of starting reads after this event loop completes.\n   *\n   * @param handler The request handler to attach to this transaction. It must\n   *                not be null.\n   */\n  virtual HTTPTransaction* newTransaction(\n      HTTPTransaction::Handler* handler) = 0;\n\n  [[nodiscard]] virtual bool isReplaySafe() const = 0;\n\n  /**\n   * Returns true if the underlying transport can be used again in a new\n   * request.\n   */\n  [[nodiscard]] virtual bool isReusable() const = 0;\n\n  /**\n   * Returns true if the session is shutting down\n   */\n  [[nodiscard]] virtual bool isClosing() const = 0;\n\n  /**\n   * Drains the current transactions and prevents new transactions from being\n   * created on this session. When the number of transactions reaches zero, this\n   * session will shutdown the transport and delete itself.\n   */\n  virtual void drain() = 0;\n\n  virtual folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n      uint8_t level) = 0;\n\n  void setConnectionToken(\n      const HTTPTransaction::ConnectionToken& token) noexcept {\n    connectionToken_ = token;\n  }\n\n  // Use the protocol's ping feature to test liveness of the peer.  Send a ping\n  // every interval seconds.  If the ping is not returned by timeout, drop the\n  // connection.\n  // If extendIntervalOnIngress is true, then any ingress data will reset the\n  // timer until the next PING.\n  // If immediate is true, send a ping immediately.  Otherwise, wait one\n  // interval.\n  virtual void enablePingProbes(std::chrono::seconds interval,\n                                std::chrono::seconds timeout,\n                                bool extendIntervalOnIngress,\n                                bool immediate = false) = 0;\n\n  void setIngressTimeoutAfterEom(bool setIngressTimeoutAfterEom) noexcept {\n    setIngressTimeoutAfterEom_ = setIngressTimeoutAfterEom;\n  }\n\n  /**\n   * Adds an observer.\n   *\n   * If the observer is already added, this is a no-op.\n   *\n   * @param observer     Observer to add.\n   */\n  bool addObserver(HTTPSessionObserverContainer::Observer* observer) {\n    if (auto list = getHTTPSessionObserverContainer()) {\n      list->addObserver(observer);\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Adds an observer.\n   *\n   * If the observer is already added, this is a no-op.\n   *\n   * @param observer     <shared_ptr> Observer to add\n   * @return             Whether the observer was added (fails if no list).\n   */\n  bool addObserver(\n      std::shared_ptr<HTTPSessionObserverContainer::Observer> observer) {\n    if (auto list = getHTTPSessionObserverContainer()) {\n      list->addObserver(std::move(observer));\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Removes an observer.\n   *\n   * @param observer     Observer to remove.\n   * @return             Whether the observer was found and removed.\n   */\n  bool removeObserver(HTTPSessionObserverContainer::Observer* observer) {\n    if (auto list = getHTTPSessionObserverContainer()) {\n      list->removeObserver(observer);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  /**\n   * Removes an observer.\n   *\n   * @param observer     Observer to remove.\n   * @return             Whether the observer was found and removed.\n   */\n  bool removeObserver(\n      std::shared_ptr<HTTPSessionObserverContainer::Observer> observer) {\n    if (auto list = getHTTPSessionObserverContainer()) {\n      return list->removeObserver(std::move(observer));\n    }\n    return false;\n  }\n\n  void enableServerEarlyResponse() noexcept {\n    CHECK_EQ(codec_->getTransportDirection(), TransportDirection::DOWNSTREAM);\n    enableServerEarlyResponse_ = codec_->supportsParallelRequests();\n  }\n  [[nodiscard]] bool getServerEarlyResponseEnabled() const {\n    return enableServerEarlyResponse_;\n  }\n\n protected:\n  bool notifyEgressBodyBuffered(int64_t bytes, bool update);\n\n  void updateWriteBufSize(int64_t delta);\n\n  void updatePendingWrites();\n\n  bool hasPendingEgress() {\n    return pendingWriteSize_ + pendingWriteSizeDelta_ > 0;\n  }\n\n  [[nodiscard]] uint64_t getPendingWriteSize() const {\n    return pendingWriteSize_;\n  }\n\n  /**\n   * Install a direct response handler for the transaction based on the\n   * error.\n   */\n  void handleErrorDirectly(HTTPTransaction* txn, const HTTPException& error);\n\n  bool onBodyImpl(std::unique_ptr<folly::IOBuf> chain,\n                  size_t length,\n                  uint16_t padding,\n                  HTTPTransaction* txn);\n\n  bool notifyBodyProcessed(uint32_t bytes);\n\n  void setLatestActive() {\n    latestActive_ = getCurrentTime();\n  }\n\n  [[nodiscard]] bool ingressLimitExceeded() const {\n    return pendingReadSize_ > readBufLimit_;\n  }\n  void onCreateTransaction() {\n    if (transactionSeqNo_ >= 1) {\n      // idle duration only exists since the 2nd transaction in the session\n      latestIdleDuration_ = secondsSince(latestActive_);\n    }\n  }\n\n  void incrementSeqNo() {\n    ++transactionSeqNo_;\n  }\n\n  void setStreamLimitExceeded(bool streamLimitExceeded) {\n    streamLimitExceeded_ = streamLimitExceeded;\n  }\n\n  void onNewOutgoingStream(uint32_t outgoingStreams) {\n    if (outgoingStreams > historicalMaxOutgoingStreams_) {\n      historicalMaxOutgoingStreams_ = outgoingStreams;\n    }\n  }\n\n  void setCloseReason(ConnectionCloseReason reason) {\n    if (closeReason_ == ConnectionCloseReason::kMAX_REASON) {\n      closeReason_ = reason;\n    }\n  }\n\n  static void handleLastByteEvents(ByteEventTracker* byteEventTracker,\n                                   HTTPTransaction* txn,\n                                   size_t encodedSize,\n                                   size_t byteOffset,\n                                   bool piggybacked);\n\n  void runDestroyCallbacks();\n\n  /*\n   * Invoked by children upon updating the actual codec wrapped by the filter\n   * chain.\n   */\n  void onCodecChanged();\n\n  /**\n   * Initializes the underlying codec's header indexing strategy, if applicable,\n   * by retrieving the requisite strategy from the bound controller.\n   * This methods exists as some sessions, notably HTTPUpstreamSessions, have\n   # their parent controller set after instantiation\n   */\n  void initCodecHeaderIndexingStrategy();\n\n  /**\n   * Attaches session to HTTPSessionController.\n   */\n  void attachToSessionController();\n\n  /**\n   * Informs HTTPSessionController that transport is ready.\n   */\n  void informSessionControllerTransportReady();\n\n  /**\n   * Returns the HTTPSessionObserverContainer or nullptr if not available.\n   *\n   * HTTPSession implementations that support observers should override this\n   * function and return the session observer container that they hold in\n   * memory.\n   *\n   * We have a default implementation to ensure that there is no risk of a\n   * pure-virtual function being called during constructon or destruction of\n   * the session. If this was to occur the derived class which implements this\n   * function may be unavailable leading to undefined behavior. While this is\n   * true for any pure-virtual function, the potential for this issue is\n   * greater for observers.\n   */\n  [[nodiscard]] virtual HTTPSessionObserverContainer*\n  getHTTPSessionObserverContainer() const {\n    return nullptr;\n  }\n\n  HTTPSessionStats* sessionStats_{nullptr};\n\n  InfoCallback* infoCallback_{nullptr}; // maybe can move to protected\n\n  wangle::TransportInfo transportInfo_;\n\n  HTTPCodecFilterChain codec_;\n\n  /**\n   * Maximum number of ingress body bytes that can be buffered across all\n   * transactions for this single session/connection.\n   * While changing this value from multiple threads is supported,\n   * it is not correct since a thread can use a value changed by another thread\n   * We should change the code to avoid this\n   */\n  static std::atomic<uint32_t> kDefaultReadBufLimit;\n\n  /**\n   * The maximum size of the read buffer from the socket.\n   */\n  static uint32_t maxReadBufferSize_;\n\n  /**\n   * Maximum number of bytes that can be buffered across all transactions before\n   * this session will start applying backpressure to its transactions.\n   */\n  static uint32_t kDefaultWriteBufLimit;\n  /**\n   * Maximum number of bytes to egress per loop when there are > 1\n   * transactions.  Otherwise defaults to kDefaultWriteBufLimit.\n   */\n  static uint32_t egressBodySizeLimit_;\n\n  /** Address of this end of the connection */\n  folly::SocketAddress localAddr_;\n\n  /** Address of the remote end of the connection */\n  folly::SocketAddress peerAddr_;\n\n  /**\n   * Optional connection token associated with this session.\n   */\n  folly::Optional<HTTPTransaction::ConnectionToken> connectionToken_;\n\n  /**\n   * Indicates whether ingress timeout has to be scheduled after EOM is sent.\n   */\n  bool setIngressTimeoutAfterEom_{false};\n\n  std::unique_ptr<HTTPSessionActivityTracker> httpSessionActivityTracker_;\n\n  RateLimitFilter* rateLimitFilter_{nullptr};\n\n private:\n  // Underlying controller_ is marked as private so that callers must\n  // utilize getController/setController protected methods.  This ensures we\n  // have a single path to update controller_\n  HTTPSessionController* controller_{nullptr};\n\n  // private ManagedConnection methods\n  [[nodiscard]] std::chrono::milliseconds getIdleTime() const override {\n    if (timePointInitialized(latestActive_)) {\n      return millisecondsSince(latestActive_);\n    } else {\n      return std::chrono::milliseconds(0);\n    }\n  }\n\n  /**\n   * The latest time when this session became idle status\n   */\n  TimePoint latestActive_{};\n\n  /**\n   * The idle duration between latest two consecutive active status\n   */\n  std::chrono::seconds latestIdleDuration_{};\n\n  /** Transaction sequence number */\n  uint32_t transactionSeqNo_{0};\n\n  /**\n   * The root cause reason this connection was closed.\n   */\n  ConnectionCloseReason closeReason_{ConnectionCloseReason::kMAX_REASON};\n\n  /**\n   * The maximum number concurrent transactions in the history of this session\n   */\n  uint32_t historicalMaxOutgoingStreams_{0};\n\n  /**\n   * The maximum number of concurrent transactions that this session may\n   * create, as configured locally.\n   */\n  uint32_t maxConcurrentOutgoingStreamsConfig_{\n      kDefaultMaxConcurrentOutgoingStreams};\n\n  /**\n   * Maximum number of cumulative bytes that can be buffered by the\n   * transactions in this session before applying backpressure.\n   *\n   * Note readBufLimit_ is settable via setFlowControl\n   */\n  uint32_t readBufLimit_{kDefaultReadBufLimit};\n  uint32_t writeBufLimit_{kDefaultWriteBufLimit};\n\n  /**\n   * Bytes of egress data sent to the socket but not yet written\n   * to the network.\n   */\n  uint64_t pendingWriteSize_{0};\n\n  /**\n   * The net change this event loop in the amount of buffered bytes\n   * for all this session's txns and socket write buffer.\n   */\n  int64_t pendingWriteSizeDelta_{0};\n\n  /**\n   * Bytes of ingress data read from the socket, but not yet sent to a\n   * transaction.\n   */\n  uint32_t pendingReadSize_{0};\n\n  bool h2PrioritiesEnabled_ : 1;\n\n  /**\n   * Indicates whether quic stream ID limit is exceeded.\n   */\n  bool streamLimitExceeded_{false};\n\n  /**\n   * When the server sends a complete response prior to the client sending an\n   * entire request (for non-upgraded streams), the server will subsequently\n   * write RST_STREAM/NO_ERROR (h2) or STOP_SENDING/NO_ERROR (h3). This has no\n   * effect in http/1.1.\n   */\n  bool enableServerEarlyResponse_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionController.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <glog/logging.h>\n#include <proxygen/lib/http/codec/compress/HeaderIndexingStrategy.h>\n\nnamespace folly {\nclass SocketAddress;\n}\n\nnamespace proxygen {\n\nclass HTTPException;\nclass HTTPMessage;\nclass HTTPSessionBase;\nclass HTTPTransaction;\nclass HTTPTransactionHandler;\n\nclass HTTPSessionController {\n public:\n  virtual ~HTTPSessionController() = default;\n\n  /**\n   * Will be invoked whenever HTTPSession successfully parses a\n   * request\n   *\n   * The controller creates a Handler for a new transaction.  The\n   * transaction and HTTP message (request) are passed so the\n   * implementation can construct different handlers based on these.\n   * The transaction will be explicitly set on the handler later via\n   * setTransaction.  The request message will be passed in\n   * onHeadersComplete.\n   */\n  virtual HTTPTransactionHandler* getRequestHandler(HTTPTransaction& txn,\n                                                    HTTPMessage* msg) = 0;\n\n  /**\n   * Will be invoked when HTTPSession is unable to parse a new request\n   * on the connection because of bad input.\n   *\n   * error contains specific information about what went wrong\n   */\n  virtual HTTPTransactionHandler* getParseErrorHandler(\n      HTTPTransaction* txn,\n      const HTTPException& error,\n      const folly::SocketAddress& localAddress) = 0;\n\n  /**\n   * Will be invoked when HTTPSession times out parsing a new request.\n   */\n  virtual HTTPTransactionHandler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn, const folly::SocketAddress& localAddress) = 0;\n\n  /**\n   * Inform the controller it is associated with this particular session.\n   */\n  virtual void attachSession(HTTPSessionBase* session) = 0;\n\n  /**\n   * Informed at the end when the given HTTPSession is going away.\n   */\n  virtual void detachSession(const HTTPSessionBase* session) = 0;\n\n  /**\n   * Inform the controller that the session's codec changed.\n   */\n  virtual void onSessionCodecChange(HTTPSessionBase* /*session*/) {\n  }\n\n  /**\n   * Invoked when the underlying transport is ready.\n   *\n   * On invocation, the controller can perform operations that depend on access\n   * to the transport (socket), such as setting up instrumentation or looking up\n   * configuration that depends on the peer's address.\n\n   * For HQ/QUIC, attachSession() is called before the underlying transport is\n   * initialized, so transport related operations must be performed here.\n   */\n  virtual void onTransportReady(HTTPSessionBase* /*session*/) {\n  }\n\n  /**\n   * Optionally allow the session to query custom graceful shutdown timeout.\n   */\n  [[nodiscard]] virtual std::chrono::milliseconds getGracefulShutdownTimeout()\n      const {\n    return std::chrono::milliseconds(0);\n  }\n\n  /**\n   * Optionally allow the session to query custom flow control timeout.\n   */\n  [[nodiscard]] virtual std::chrono::milliseconds getSessionFlowControlTimeout()\n      const {\n    return std::chrono::milliseconds(0);\n  }\n\n  /**\n   * Returns the H2 header indexing strategy to be employed by the session\n   */\n  [[nodiscard]] virtual const HeaderIndexingStrategy*\n  getHeaderIndexingStrategy() const {\n    return HeaderIndexingStrategy::getDefaultInstance();\n  }\n};\n\nclass HTTPUpstreamSessionController : public HTTPSessionController {\n  HTTPTransactionHandler* getRequestHandler(HTTPTransaction& /*txn*/,\n                                            HTTPMessage* /*msg*/) final {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  /**\n   * Will be invoked when HTTPSession is unable to parse a new request\n   * on the connection because of bad input.\n   *\n   * error contains specific information about what went wrong\n   */\n  HTTPTransactionHandler* getParseErrorHandler(\n      HTTPTransaction* /*txn*/,\n      const HTTPException& /*error*/,\n      const folly::SocketAddress& /*localAddress*/) final {\n    LOG(FATAL) << \"Unreachable\";\n  }\n\n  /**\n   * Will be invoked when HTTPSession times out parsing a new request.\n   */\n  HTTPTransactionHandler* getTransactionTimeoutHandler(\n      HTTPTransaction* /*txn*/,\n      const folly::SocketAddress& /*localAddress*/) final {\n    LOG(FATAL) << \"Unreachable\";\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPSessionStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <inttypes.h>\n#include <proxygen/lib/http/session/TTLBAStats.h>\n\nnamespace proxygen {\n\n// This may be retired with a byte events refactor\nclass HTTPSessionStats : public TTLBAStats {\n public:\n  HTTPSessionStats() = default;\n  HTTPSessionStats(const HTTPSessionStats&) = delete;\n  HTTPSessionStats& operator=(const HTTPSessionStats&) = delete;\n  HTTPSessionStats(HTTPSessionStats&&) = delete;\n  HTTPSessionStats& operator=(HTTPSessionStats&&) = delete;\n  ~HTTPSessionStats() noexcept override = default;\n\n  virtual void recordTransactionOpened() noexcept = 0;\n  virtual void recordTransactionClosed() noexcept = 0;\n  virtual void recordTransactionsServed(uint64_t) noexcept = 0;\n  virtual void recordSessionReused() noexcept = 0;\n  virtual void recordSessionIdleTime(std::chrono::seconds) noexcept {\n  }\n  virtual void recordTransactionStalled() noexcept = 0;\n  virtual void recordSessionStalled() noexcept = 0;\n  virtual void recordPendingBufferedReadBytes(int64_t) noexcept = 0;\n  virtual void recordPendingBufferedWriteBytes(int64_t) noexcept {\n  }\n  virtual void recordEgressContentLengthMismatches() noexcept = 0;\n  virtual void recordSessionPeriodicPingProbeTimeout() noexcept = 0;\n\n  virtual void recordControlMsgsInInterval(int64_t) noexcept {\n  }\n  virtual void recordControlMsgRateLimited() noexcept {\n  }\n  virtual void recordHeadersInInterval(int64_t) noexcept {\n  }\n  virtual void recordHeadersRateLimited() noexcept {\n  }\n  virtual void recordResetsInInterval(int64_t) noexcept {\n  }\n  virtual void recordResetsRateLimited() noexcept {\n  }\n  virtual void recordReadPerLoopLimitExceeded() noexcept {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransaction.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\n#include <algorithm>\n#include <folly/Conv.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/tracing/ScopedTraceSection.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/RFC2616.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/http/webtransport/HTTPWebTransport.h>\n#include <sstream>\n\nusing folly::IOBuf;\nusing std::unique_ptr;\n\nnamespace proxygen {\n\nnamespace {\nconst int64_t kApproximateMTU = 1400;\nconst std::chrono::seconds kRateLimitMaxDelay(10);\nconst uint64_t kMaxBufferPerTxn = 65536;\nconstexpr uint32_t kMinThreshold = 128 * 1024;\n\nusing namespace proxygen;\nHTTPException stateMachineError(HTTPException::Direction dir, std::string msg) {\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, msg);\n  // Sadly, ProxygenErrorEnum is maxed out at 6 bits, so I cannot add\n  // kErrorEgressStateTransition.  Instead, set to ingress and record the\n  // exception direction in the 'errno' field of the exception.\n  ex.setProxygenError(kErrorIngressStateTransition);\n  ex.setCodecStatusCode(ErrorCode::INTERNAL_ERROR);\n  ex.setErrno(uint32_t(dir));\n  return ex;\n}\n\ninline ErrorCode getDefaultAbortErrorCode(bool isUpstream) {\n  return isUpstream ? ErrorCode::CANCEL : ErrorCode::INTERNAL_ERROR;\n}\n\n} // namespace\n\n#define INVARIANT_RETURN(X, Y)                                            \\\n  if (!(X)) {                                                             \\\n    invariantViolation(                                                   \\\n        HTTPException(HTTPException::Direction::INGRESS_AND_EGRESS, #X)); \\\n    return Y;                                                             \\\n  }                                                                       \\\n  static_assert(true, \"semicolon required\")\n\n#define INVARIANT(X)                                                      \\\n  if (!(X)) {                                                             \\\n    invariantViolation(                                                   \\\n        HTTPException(HTTPException::Direction::INGRESS_AND_EGRESS, #X)); \\\n    return;                                                               \\\n  }                                                                       \\\n  static_assert(true, \"semicolon required\")\n\nuint64_t HTTPTransaction::egressBufferLimit_ = kMaxBufferPerTxn;\n\nHTTPTransaction::HTTPTransaction(\n    TransportDirection direction,\n    HTTPCodec::StreamID id,\n    uint32_t seqNo,\n    Transport& transport,\n    HTTP2PriorityQueueBase& egressQueue,\n    folly::HHWheelTimer* timer,\n    const folly::Optional<std::chrono::milliseconds>& defaultIdleTimeout,\n    HTTPSessionStats* stats,\n    bool useFlowControl,\n    uint32_t receiveInitialWindowSize,\n    uint32_t sendInitialWindowSize,\n    http2::PriorityUpdate priority,\n    folly::Optional<HTTPCodec::StreamID> assocId,\n    bool setIngressTimeoutAfterEom)\n    : deferredEgressBody_(folly::IOBufQueue::cacheChainLength()),\n      direction_(direction),\n      id_(id),\n      seqNo_(seqNo),\n      transport_(transport),\n      wtTransportProvider_(&transport),\n      stats_(stats),\n      recvWindow_(receiveInitialWindowSize),\n      sendWindow_(sendInitialWindowSize),\n      egressQueue_(egressQueue),\n      assocStreamId_(assocId),\n      priority_(priority),\n      ingressPaused_(false),\n      egressPaused_(false),\n      flowControlPaused_(false),\n      handlerEgressPaused_(false),\n      egressRateLimited_(false),\n      useFlowControl_(useFlowControl),\n      aborted_(false),\n      deleting_(false),\n      firstByteSent_(false),\n      firstHeaderByteSent_(false),\n      inResume_(false),\n      isCountedTowardsStreamLimit_(false),\n      ingressErrorSeen_(false),\n      priorityFallback_(false),\n      headRequest_(false),\n      enableLastByteFlushedTracking_(false),\n      wtConnectStream_(false),\n      egressHeadersDelivered_(false),\n      has1xxResponse_(false),\n      deferredNoError_(false),\n      upgraded_(false),\n      idleTimeout_(defaultIdleTimeout),\n      timer_(timer),\n      setIngressTimeoutAfterEom_(setIngressTimeoutAfterEom),\n      txnObserverAccessor_(this),\n      txnObserverContainer_(&txnObserverAccessor_) {\n  if (assocStreamId_) {\n    if (isUpstream()) {\n      egressState_ = HTTPTransactionEgressSM::State::SendingDone;\n    } else {\n      ingressState_ = HTTPTransactionIngressSM::State::ReceivingDone;\n    }\n  }\n\n  updateReadTimeout();\n  if (stats_) {\n    stats_->recordTransactionOpened();\n  }\n\n  if (isDownstream() || !isPushed()) {\n    queueHandle_ =\n        egressQueue_.addTransaction(id_, priority, this, false, &insertDepth_);\n  }\n  if (priority.streamDependency != egressQueue_.getRootId() &&\n      insertDepth_ == 1) {\n    priorityFallback_ = true;\n  }\n\n  currentDepth_ = insertDepth_;\n}\n\nvoid HTTPTransaction::onDelayedDestroy(bool delayed) {\n  if (!isEgressComplete() || !isIngressComplete() || isEnqueued() ||\n      pendingByteEvents_ > 0 || deleting_) {\n    return;\n  }\n  VLOG(4) << \"destroying transaction \" << *this;\n  deleting_ = true;\n  if (webTransportImpl_) {\n    webTransportImpl_->destroy();\n  }\n  if (handler_) {\n    // TODO: call onWebTransportSessionClose?\n    handler_->detachTransaction();\n    handler_ = nullptr;\n  }\n  transportCallback_ = nullptr;\n  const auto bytesBuffered = recvWindow_.getOutstanding();\n  if (bytesBuffered) {\n    transport_.notifyIngressBodyProcessed(bytesBuffered);\n  }\n  transport_.detach(this);\n  (void)delayed; // prevent unused variable warnings\n}\n\nHTTPTransaction::~HTTPTransaction() {\n  // Cancel transaction timeout if still scheduled.\n  if (isScheduled()) {\n    cancelTimeout();\n  }\n\n  if (stats_) {\n    stats_->recordTransactionClosed();\n  }\n  if (isEnqueued()) {\n    dequeue();\n  }\n  // TODO: handle the case where the priority node hangs out longer than\n  // the transaction\n  if (queueHandle_) {\n    egressQueue_.removeTransaction(queueHandle_);\n  }\n}\n\nvoid HTTPTransaction::reset(bool useFlowControl,\n                            uint32_t receiveInitialWindowSize,\n                            uint32_t receiveStreamWindowSize,\n                            uint32_t sendInitialWindowSize) {\n  useFlowControl_ = useFlowControl;\n  recvWindow_.setCapacity(receiveInitialWindowSize);\n  setReceiveWindow(receiveStreamWindowSize);\n  sendWindow_.setCapacity(sendInitialWindowSize);\n}\n\nvoid HTTPTransaction::onIngressHeadersComplete(\n    std::unique_ptr<HTTPMessage> msg) {\n  DestructorGuard g(this);\n  msg->setSeqNo(seqNo_);\n  if (isUpstream() && !isPushed() && msg->isResponse()) {\n    lastResponseStatus_ = msg->getStatusCode();\n  }\n  bool nonFinalPushHeaders = isPushed() && msg->isRequest();\n  if (!validateIngressStateTransition(\n          msg->isFinal() && !nonFinalPushHeaders\n              ? HTTPTransactionIngressSM::Event::onFinalHeaders\n              : HTTPTransactionIngressSM::Event::onNonFinalHeaders)) {\n    return;\n  }\n  if (msg->isRequest()) {\n    auto method = msg->getMethod();\n    headRequest_ = (method == HTTPMethod::HEAD);\n    upgraded_ = (method == HTTPMethod::CONNECT);\n    wtConnectStream_ = HTTPWebTransport::isConnectMessage(*msg);\n    connectUdpStream_ = msg->getMethod() == HTTPMethod::CONNECT &&\n                        msg->getUpgradeProtocol() &&\n                        *msg->getUpgradeProtocol() == \"connect-udp\";\n  }\n\n  if ((msg->isRequest() && msg->getMethod() != HTTPMethod::CONNECT) ||\n      (msg->isResponse() && !headRequest_ &&\n       !RFC2616::responseBodyMustBeEmpty(msg->getStatusCode()))) {\n    // CONNECT payload has no defined semantics\n    const auto& contentLen =\n        msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH);\n    if (!contentLen.empty()) {\n      try {\n        expectedIngressContentLengthRemaining_ =\n            folly::to<uint64_t>(contentLen);\n      } catch (const folly::ConversionError& ex) {\n        LOG(ERROR) << \"Invalid content-length: \" << contentLen\n                   << \", ex=\" << ex.what() << \" \" << *this;\n      }\n      if (expectedIngressContentLengthRemaining_) {\n        expectedIngressContentLength_ =\n            expectedIngressContentLengthRemaining_.value();\n      }\n    }\n  }\n  if (transportCallback_) {\n    transportCallback_->headerBytesReceived(msg->getIngressHeaderSize());\n  }\n\n  if (hasBytesEventObservers()) {\n    const auto& size = msg->getIngressHeaderSize();\n    const auto e =\n        HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n            .setTimestamp(proxygen::SteadyClock::now())\n            .setType(HTTPTransactionObserverInterface::TxnBytesEvent::Type::\n                         LAST_HEADER_BYTE_READ)\n            .setHeaders(*msg)\n            .setNumBytes(size.compressed ? size.compressed : size.uncompressed)\n            .build();\n    emitBytesEvent(e);\n  }\n\n  updateIngressCompressionInfo(transport_.getCodec().getCompressionInfo());\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(\n        id_, HTTPEvent::Type::HEADERS_COMPLETE, std::move(msg));\n    VLOG(4) << \"Queued ingress event of type \"\n            << HTTPEvent::Type::HEADERS_COMPLETE << \" \" << *this;\n  } else {\n    processIngressHeadersComplete(std::move(msg));\n  }\n}\n\nvoid HTTPTransaction::processIngressHeadersComplete(\n    std::unique_ptr<HTTPMessage> msg) {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  refreshTimeout();\n  if (handler_ && !isIngressComplete()) {\n    handler_->onHeadersComplete(std::move(msg));\n  }\n}\n\nbool HTTPTransaction::updateContentLengthRemaining(size_t len) {\n  if (expectedIngressContentLengthRemaining_.has_value()) {\n    if (expectedIngressContentLengthRemaining_.value() >= len) {\n      expectedIngressContentLengthRemaining_ =\n          expectedIngressContentLengthRemaining_.value() - len;\n    } else {\n      auto errorMsg = folly::to<std::string>(\n          \"Content-Length/body mismatch onIngressBody: received=\",\n          len,\n          \" expecting no more than \",\n          expectedIngressContentLengthRemaining_.value());\n      LOG(ERROR) << errorMsg << \" \" << *this;\n      if (handler_) {\n        HTTPException ex(HTTPException::Direction::INGRESS, errorMsg);\n        ex.setProxygenError(kErrorParseBody);\n        onError(ex);\n      }\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid HTTPTransaction::onIngressBody(unique_ptr<IOBuf> chain, uint16_t padding) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - onIngressBody\");\n  DestructorGuard g(this);\n  VLOG(6) << __func__ << \" chain_length=\" << chain->computeChainDataLength();\n  if (isIngressEOMSeen()) {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for this\n    ss << \"onIngressBody after ingress closed \" << *this;\n    VLOG(4) << ss.str();\n    abortAndDeliverError(ErrorCode::STREAM_CLOSED, ss.str());\n    return;\n  }\n  auto len = chain->computeChainDataLength();\n  if (len == 0) {\n    return;\n  }\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onBody)) {\n    return;\n  }\n  if (!updateContentLengthRemaining(len)) {\n    return;\n  }\n\n  if (transportCallback_) {\n    transportCallback_->bodyBytesReceived(len);\n  }\n  if (hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::BODY_BYTES_READ)\n                       .setNumBytes(len)\n                       .build();\n    emitBytesEvent(e);\n  }\n  // register the bytes in the receive window\n  if (!recvWindow_.reserve(len + padding, useFlowControl_)) {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for this\n    ss << \"recvWindow_.reserve failed with len=\" << len\n       << \" padding=\" << padding << \" capacity=\" << recvWindow_.getCapacity()\n       << \" outstanding=\" << recvWindow_.getOutstanding() << \" \" << *this;\n    LOG(ERROR) << ss.str();\n    abortAndDeliverError(ErrorCode::FLOW_CONTROL_ERROR, ss.str());\n    return;\n  } else {\n    INVARIANT(recvWindow_.free(padding));\n    recvToAck_ += padding;\n  }\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n\n    HTTPEvent* tailEvent =\n        deferredIngress_->empty() ? nullptr : &deferredIngress_->back();\n    bool shouldCoalesce =\n        tailEvent && tailEvent->getEvent() == HTTPEvent::Type::BODY &&\n        (tailEvent->getBodyLength() + len <= kMaxBufferPerTxn);\n\n    if (shouldCoalesce) {\n      VLOG(4) << \"Coalesced ingress event of type \" << HTTPEvent::Type::BODY\n              << \" size=\" << len << \" \" << *this;\n      tailEvent->appendChunk(std::move(chain));\n    } else {\n      VLOG(4) << \"Queued ingress event of type \" << HTTPEvent::Type::BODY\n              << \" size=\" << len << \" \" << *this;\n      deferredIngress_->emplace(id_, HTTPEvent::Type::BODY, std::move(chain));\n    }\n  } else {\n    INVARIANT(recvWindow_.free(len));\n    processIngressBody(std::move(chain), len);\n  }\n}\n\nvoid HTTPTransaction::processIngressBody(unique_ptr<IOBuf> chain, size_t len) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - processIngressBody\");\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  refreshTimeout();\n  transport_.notifyIngressBodyProcessed(len);\n  auto chainLen = chain->computeChainDataLength();\n  if (handler_) {\n    if (!isIngressComplete()) {\n      handler_->onBodyWithOffset(ingressBodyOffset_, std::move(chain));\n    }\n\n    if (useFlowControl_ && !isIngressEOMSeen()) {\n      recvToAck_ += len;\n      if (recvToAck_ > 0) {\n        uint32_t divisor = 2;\n        if (transport_.isDraining()) {\n          // only send window updates for draining transports when window is\n          // closed\n          divisor = 1;\n        }\n        if (uint32_t(recvToAck_) >= kMinThreshold ||\n            uint32_t(recvToAck_) >= (recvWindow_.getCapacity() / divisor)) {\n          flushWindowUpdate();\n        }\n      }\n    } // else don't care about window updates\n  }\n  ingressBodyOffset_ += chainLen;\n}\n\nvoid HTTPTransaction::onIngressChunkHeader(size_t length) {\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onChunkHeader)) {\n    return;\n  }\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(id_, HTTPEvent::Type::CHUNK_HEADER, length);\n    VLOG(4) << \"Queued ingress event of type \" << HTTPEvent::Type::CHUNK_HEADER\n            << \" size=\" << length << \" \" << *this;\n  } else {\n    processIngressChunkHeader(length);\n  }\n}\n\nvoid HTTPTransaction::processIngressChunkHeader(size_t length) {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  refreshTimeout();\n  if (handler_ && !isIngressComplete()) {\n    handler_->onChunkHeader(length);\n  }\n}\n\nvoid HTTPTransaction::onIngressChunkComplete() {\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onChunkComplete)) {\n    return;\n  }\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(id_, HTTPEvent::Type::CHUNK_COMPLETE);\n    VLOG(4) << \"Queued ingress event of type \"\n            << HTTPEvent::Type::CHUNK_COMPLETE << \" \" << *this;\n  } else {\n    processIngressChunkComplete();\n  }\n}\n\nvoid HTTPTransaction::processIngressChunkComplete() {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  refreshTimeout();\n  if (handler_ && !isIngressComplete()) {\n    handler_->onChunkComplete();\n  }\n}\n\nvoid HTTPTransaction::onIngressTrailers(unique_ptr<HTTPHeaders> trailers) {\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onTrailers)) {\n    return;\n  }\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(\n        id_, HTTPEvent::Type::TRAILERS_COMPLETE, std::move(trailers));\n    VLOG(4) << \"Queued ingress event of type \"\n            << HTTPEvent::Type::TRAILERS_COMPLETE << \" \" << *this;\n  } else {\n    processIngressTrailers(std::move(trailers));\n  }\n}\n\nvoid HTTPTransaction::processIngressTrailers(unique_ptr<HTTPHeaders> trailers) {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  refreshTimeout();\n  if (handler_ && !isIngressComplete()) {\n    handler_->onTrailers(std::move(trailers));\n  }\n}\n\nvoid HTTPTransaction::onIngressUpgrade(UpgradeProtocol protocol) {\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onUpgrade)) {\n    return;\n  }\n  upgraded_ = true;\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(id_, HTTPEvent::Type::UPGRADE, protocol);\n    VLOG(4) << \"Queued ingress event of type \" << HTTPEvent::Type::UPGRADE\n            << \" \" << *this;\n  } else {\n    processIngressUpgrade(protocol);\n  }\n}\n\nvoid HTTPTransaction::processIngressUpgrade(UpgradeProtocol protocol) {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  if (handler_ && !isIngressComplete()) {\n    handler_->onUpgrade(protocol);\n  }\n}\n\nvoid HTTPTransaction::onIngressEOM() {\n  if (isIngressEOMSeen()) {\n    // This can happen when HTTPSession calls onIngressEOF()\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for this\n    ss << \"onIngressEOM after ingress closed \" << *this;\n    VLOG(4) << ss.str();\n    abortAndDeliverError(ErrorCode::STREAM_CLOSED, ss.str());\n    return;\n  }\n  if (expectedIngressContentLengthRemaining_.has_value() &&\n      expectedIngressContentLengthRemaining_.value() > 0) {\n    auto errorMsg = folly::to<std::string>(\n        \"Content-Length/body mismatch onIngressEOM: expecting another \",\n        expectedIngressContentLengthRemaining_.value());\n    LOG(ERROR) << errorMsg << \" \" << *this;\n    if (handler_) {\n      HTTPException ex(HTTPException::Direction::INGRESS, errorMsg);\n      ex.setProxygenError(kErrorParseBody);\n      onError(ex);\n    }\n    return;\n  }\n\n  if (!validateIngressStateTransition(HTTPTransactionIngressSM::Event::onEOM)) {\n    return;\n  }\n  // We need to update the read timeout here.  We're not likely to be\n  // expecting any more ingress, and the timer should be cancelled\n  // immediately.  If we are expecting more, this will reset the timer.\n  updateReadTimeout();\n  if (mustQueueIngress()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(id_, HTTPEvent::Type::MESSAGE_COMPLETE);\n    VLOG(4) << \"Queued ingress event of type \"\n            << HTTPEvent::Type::MESSAGE_COMPLETE << \" \" << *this;\n  } else {\n    processIngressEOM();\n  }\n}\n\nvoid HTTPTransaction::processIngressEOM() {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  VLOG(4) << \"ingress EOM on \" << *this;\n  const bool wasComplete = isIngressComplete();\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::eomFlushed)) {\n    return;\n  }\n  if (handler_) {\n    if (!wasComplete) {\n      handler_->onEOM();\n    }\n  } else {\n    markEgressComplete();\n  }\n  updateReadTimeout();\n}\n\nbool HTTPTransaction::isExpectingWindowUpdate() const {\n  return egressState_ != HTTPTransactionEgressSM::State::SendingDone &&\n         useFlowControl_ && sendWindow_.getSize() <= 0;\n}\n\nbool HTTPTransaction::isExpectingIngress() const {\n  bool upstreamSendingDone = true;\n  if (setIngressTimeoutAfterEom_) {\n    upstreamSendingDone = isDownstream() || isEgressComplete();\n  }\n  return isExpectingWindowUpdate() ||\n         (!ingressPaused_ && !isIngressEOMSeen() && upstreamSendingDone);\n}\n\nvoid HTTPTransaction::updateReadTimeout() {\n  if (isExpectingIngress()) {\n    refreshTimeout();\n  } else {\n    cancelTimeout();\n  }\n}\n\nvoid HTTPTransaction::markIngressComplete() {\n  VLOG(4) << \"Marking ingress complete on \" << *this;\n  ingressState_ = HTTPTransactionIngressSM::State::ReceivingDone;\n  deferredIngress_.reset();\n  cancelTimeout();\n}\n\nvoid HTTPTransaction::markEgressComplete() {\n  VLOG(4) << \"Marking egress complete on \" << *this;\n  auto pendingBytes = getOutstandingEgressBodyBytes();\n  if (pendingBytes) {\n    int64_t deferredEgressBodyBytes = folly::to<int64_t>(pendingBytes);\n    transport_.notifyEgressBodyBuffered(-deferredEgressBodyBytes);\n  }\n  deferredEgressBody_.move();\n  if (isEnqueued()) {\n    dequeue();\n  }\n  egressState_ = HTTPTransactionEgressSM::State::SendingDone;\n}\n\nbool HTTPTransaction::validateIngressStateTransition(\n    HTTPTransactionIngressSM::Event event) {\n  DestructorGuard g(this);\n\n  if (!HTTPTransactionIngressSM::transit(ingressState_, event)) {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for state machine\n    ss << \"Invalid ingress state transition, state=\" << ingressState_\n       << \", event=\" << event << \", streamID=\" << id_;\n    auto ex = stateMachineError(HTTPException::Direction::INGRESS, ss.str());\n    // This will invoke sendAbort() and also inform the handler of the\n    // error and detach the handler.\n    onError(ex);\n    return false;\n  }\n  return true;\n}\n\nbool HTTPTransaction::validateEgressStateTransition(\n    HTTPTransactionEgressSM::Event event) {\n  DestructorGuard g(this);\n\n  if (!HTTPTransactionEgressSM::transit(egressState_, event)) {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for state machine\n    ss << \"Invalid egress state transition, state=\" << egressState_\n       << \", event=\" << event << \", streamID=\" << id_;\n    LOG(ERROR) << ss.str() << \" \" << *this;\n    invariantViolation(\n        stateMachineError(HTTPException::Direction::EGRESS, ss.str()));\n    return false;\n  }\n  return true;\n}\n\nvoid HTTPTransaction::invariantViolation(HTTPException ex) {\n  DestructorGuard g(this);\n  LOG(ERROR) << \"invariantViolation msg=\" << ex.what()\n             << \" aborted_=\" << uint32_t(aborted_) << \" \" << *this;\n  if (handler_) {\n    handler_->onInvariantViolation(ex);\n  } else {\n    LOG(FATAL) << \"Invariant violation with no handler; ex=\" << ex.what();\n  }\n  // In http/1.1, this will send TCP reset and ungracefully terminate the\n  // connection. In h2, this will send stream reset but keep the connection\n  // open. Should this be INTERNAL_ERROR?\n  sendAbort(ErrorCode::NO_ERROR);\n}\n\nvoid HTTPTransaction::abortAndDeliverError(ErrorCode codecError,\n                                           const std::string& msg) {\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, msg);\n  ex.setCodecStatusCode(codecError);\n  // onError will call sendAbort if there is a codec status code and no\n  // proxygen error code.  It will *also* notify the handler.\n  onError(ex);\n}\n\nvoid HTTPTransaction::onError(const HTTPException& error) {\n  /**\n   * Defer ingress RST_STREAM w/ NO_ERROR if ingress EOM is queued. Downstream\n   * may send full response prior to upstream sending entire request.\n   */\n  if (isUpstream() && error.hasCodecStatusCode() &&\n      error.getCodecStatusCode() == ErrorCode::NO_ERROR &&\n      isIngressEOMQueued()) {\n    checkCreateDeferredIngress();\n    deferredIngress_->emplace(id_, std::make_unique<HTTPException>(error));\n    return;\n  }\n\n  processIngressError(error);\n}\n\nvoid HTTPTransaction::processIngressError(const HTTPException& error) {\n  DestructorGuard g(this);\n\n  const bool wasAborted = aborted_; // see comment below\n  const bool wasEgressComplete = isEgressComplete();\n  const bool wasIngressComplete = isIngressComplete();\n  bool notify = (handler_);\n  HTTPException::Direction direction = error.getDirection();\n\n  if (direction == HTTPException::Direction::INGRESS && isIngressEOMSeen() &&\n      isExpectingWindowUpdate()) {\n    // we got an ingress error, we've seen the entire message, but we're\n    // expecting more (window updates).  These aren't coming, convert to\n    // INGRESS_AND_EGRESS\n    VLOG(4) << \"Converting ingress error to ingress+egress due to\"\n               \" flow control, and aborting \"\n            << *this;\n    direction = HTTPException::Direction::INGRESS_AND_EGRESS;\n    sendAbort(ErrorCode::FLOW_CONTROL_ERROR);\n  }\n\n  if (error.getProxygenError() == kErrorStreamAbort) {\n    DCHECK(error.getDirection() ==\n           HTTPException::Direction::INGRESS_AND_EGRESS);\n    aborted_ = true;\n  } else if (error.hasCodecStatusCode()) {\n    DCHECK(error.getDirection() ==\n           HTTPException::Direction::INGRESS_AND_EGRESS);\n    sendAbort(error.getCodecStatusCode());\n  }\n\n  switch (direction) {\n    case HTTPException::Direction::INGRESS_AND_EGRESS:\n      markEgressComplete();\n      markIngressComplete();\n      if (wasEgressComplete && wasIngressComplete &&\n          // We mark egress complete before we get acknowledgement of the\n          // write segment finishing successfully.\n          // TODO: instead of using DestructorGuard hacks to keep txn around,\n          // use an explicit callback function and set egress complete after\n          // last byte flushes (or egress error occurs), see #3912823\n          (error.getProxygenError() != kErrorWriteTimeout || wasAborted)) {\n        notify = false;\n      }\n      break;\n    case HTTPException::Direction::EGRESS:\n      markEgressComplete();\n      if (!wasEgressComplete && isIngressEOMSeen() && ingressErrorSeen_) {\n        // we've already seen an ingress error but we ignored it, hoping the\n        // handler would resume and read our queued EOM.  Now both sides are\n        // dead and we need to kill this transaction.\n        markIngressComplete();\n      }\n      if (wasEgressComplete) {\n        notify = false;\n      }\n      break;\n    case HTTPException::Direction::INGRESS:\n      if (isIngressEOMSeen()) {\n        // Not an error, for now\n        ingressErrorSeen_ = true;\n        return;\n      }\n      markIngressComplete();\n      if (wasIngressComplete) {\n        notify = false;\n      }\n      break;\n  }\n  if (notify && handler_) {\n    // mark egress complete may result in handler detaching\n    handler_->onError(error);\n  }\n}\n\nvoid HTTPTransaction::onGoaway(ErrorCode code) {\n  DestructorGuard g(this);\n  VLOG(4) << \"received GOAWAY notification on \" << *this;\n  // This callback can be received at any time and does not affect this\n  // transaction's ingress or egress state machines. If it would have\n  // affected this transaction's state, we would have received onError()\n  // instead.\n  if (handler_) {\n    handler_->onGoaway(code);\n  }\n}\n\nvoid HTTPTransaction::onIngressTimeout() {\n  DestructorGuard g(this);\n  VLOG(4) << \"ingress timeout on \" << *this;\n  pauseIngress();\n  bool windowUpdateTimeout = !isEgressComplete() && isExpectingWindowUpdate();\n  if (handler_) {\n    if (windowUpdateTimeout) {\n      HTTPException ex(\n          HTTPException::Direction::INGRESS_AND_EGRESS,\n          folly::to<std::string>(\n              \"ingress timeout, streamID=\",\n              id_,\n              \", timeout=\",\n              idleTimeout_.has_value()\n                  ? std::chrono::duration_cast<std::chrono::milliseconds>(\n                        idleTimeout_.value())\n                        .count()\n                  : -1,\n              \"ms\"));\n      ex.setProxygenError(kErrorWriteTimeout);\n      // This is a protocol error\n      ex.setCodecStatusCode(ErrorCode::PROTOCOL_ERROR);\n      onError(ex);\n    } else {\n      HTTPException ex(\n          HTTPException::Direction::INGRESS,\n          folly::to<std::string>(\n              \"ingress timeout, streamID=\",\n              id_,\n              \", timeout=\",\n              idleTimeout_.has_value()\n                  ? std::chrono::duration_cast<std::chrono::milliseconds>(\n                        idleTimeout_.value())\n                        .count()\n                  : -1,\n              \"ms\"));\n      ex.setProxygenError(kErrorTimeout);\n      onError(ex);\n    }\n  } else {\n    markIngressComplete();\n    markEgressComplete();\n  }\n}\n\nvoid HTTPTransaction::onIngressWindowUpdate(const uint32_t amount) {\n  if (!useFlowControl_) {\n    return;\n  }\n  DestructorGuard g(this);\n  VLOG(4) << \"Remote side ack'd \" << amount << \" bytes \" << *this;\n  updateReadTimeout();\n  if (sendWindow_.free(amount)) {\n    notifyTransportPendingEgress();\n  } else {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for this\n    ss << \"sendWindow_.free failed with amount=\" << amount\n       << \" capacity=\" << sendWindow_.getCapacity()\n       << \" outstanding=\" << sendWindow_.getOutstanding() << \" \" << *this;\n    LOG(ERROR) << ss.str();\n    abortAndDeliverError(ErrorCode::FLOW_CONTROL_ERROR, ss.str());\n  }\n}\n\nvoid HTTPTransaction::onIngressSetSendWindow(const uint32_t newWindowSize) {\n  if (!useFlowControl_) {\n    return;\n  }\n  updateReadTimeout();\n  if (sendWindow_.setCapacity(newWindowSize)) {\n    notifyTransportPendingEgress();\n  } else {\n    std::stringstream ss;\n    // Use stringstream to invoke operator << for this\n    ss << \"sendWindow_.setCapacity failed with newWindowSize=\" << newWindowSize\n       << \" capacity=\" << sendWindow_.getCapacity()\n       << \" outstanding=\" << sendWindow_.getOutstanding() << \" \" << *this;\n    LOG(ERROR) << ss.str();\n    abortAndDeliverError(ErrorCode::FLOW_CONTROL_ERROR, ss.str());\n  }\n}\n\nvoid HTTPTransaction::onEgressTimeout() {\n  DestructorGuard g(this);\n  VLOG(4) << \"egress timeout on \" << *this;\n  if (handler_) {\n    HTTPException ex(HTTPException::Direction::EGRESS,\n                     folly::to<std::string>(\"egress timeout, streamID=\", id_));\n    ex.setProxygenError(kErrorTimeout);\n    onError(ex);\n  } else {\n    markEgressComplete();\n  }\n}\n\nvoid HTTPTransaction::onEgressHeaderFirstByte() {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->firstHeaderByteFlushed();\n  }\n\n  if (hasBytesEventObservers()) {\n    const auto e =\n        HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n            .setTimestamp(proxygen::SteadyClock::now())\n            .setType(HTTPTransactionObserverInterface::TxnBytesEvent::Type::\n                         FIRST_HEADER_BYTE_WRITE)\n            .build();\n    emitBytesEvent(e);\n  }\n}\n\nvoid HTTPTransaction::onEgressBodyFirstByte() {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->firstByteFlushed();\n  }\n\n  if (hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::FIRST_BODY_BYTE_WRITE)\n                       .build();\n    emitBytesEvent(e);\n  }\n}\n\nvoid HTTPTransaction::onEgressBodyLastByte() {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->lastByteFlushed();\n  }\n\n  if (hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::LAST_BODY_BYTE_WRITE)\n                       .build();\n    emitBytesEvent(e);\n  }\n}\n\nvoid HTTPTransaction::onEgressTrackedByte() {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->trackedByteFlushed();\n  }\n}\n\nvoid HTTPTransaction::onEgressLastByteAck(std::chrono::milliseconds latency) {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->lastByteAcked(latency);\n  }\n\n  if (hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::LAST_BODY_BYTE_ACK)\n                       .build();\n    emitBytesEvent(e);\n  }\n}\n\nvoid HTTPTransaction::onLastEgressHeaderByteAcked() {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - onLastEgressHeaderByteAcked\");\n  egressHeadersDelivered_ = true;\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->lastEgressHeaderByteAcked();\n  }\n}\n\nvoid HTTPTransaction::onEgressBodyBytesAcked(uint64_t bodyOffset) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - onEgressBodyBytesAcked\");\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->bodyBytesDelivered(bodyOffset);\n  }\n}\n\nvoid HTTPTransaction::onEgressBodyBytesTx(uint64_t bodyOffset) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - onEgressBodyBytesTx\");\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->bodyBytesTx(bodyOffset);\n  }\n}\n\nvoid HTTPTransaction::onEgressBodyDeliveryCanceled(uint64_t bodyOffset) {\n  FOLLY_SCOPED_TRACE_SECTION(\"HTTPTransaction - onEgressBodyDeliveryCanceled\");\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->bodyBytesDeliveryCancelled(bodyOffset);\n  }\n}\n\nvoid HTTPTransaction::onEgressTrackedByteEventTX(const ByteEvent& event) {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->trackedByteEventTX(event);\n  }\n}\n\nvoid HTTPTransaction::onEgressTrackedByteEventAck(const ByteEvent& event) {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->trackedByteEventAck(event);\n  }\n\n  if (ByteEvent::FIRST_BYTE == event.eventType_ && hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::FIRST_BODY_BYTE_ACK)\n                       .build();\n    emitBytesEvent(e);\n  }\n}\n\nvoid HTTPTransaction::onEgressTransportAppRateLimited() {\n  DestructorGuard g(this);\n  if (transportCallback_) {\n    transportCallback_->transportAppRateLimited();\n  }\n}\n\nvoid HTTPTransaction::sendHeadersWithOptionalEOM(const HTTPMessage& headers,\n                                                 bool eom) {\n  if (!validateEgressStateTransition(\n          HTTPTransactionEgressSM::Event::sendHeaders)) {\n    return;\n  }\n  DCHECK(!isEgressComplete());\n  if (!headers.isRequest() && !isPushed()) {\n    lastResponseStatus_ = headers.getStatusCode();\n  }\n  if (headers.isRequest()) {\n    headRequest_ = (headers.getMethod() == HTTPMethod::HEAD);\n    wtConnectStream_ = HTTPWebTransport::isConnectMessage(headers);\n    connectUdpStream_ = headers.getMethod() == HTTPMethod::CONNECT &&\n                        headers.getUpgradeProtocol() &&\n                        *headers.getUpgradeProtocol() == \"connect-udp\";\n  } else {\n    has1xxResponse_ = headers.is1xxResponse();\n  }\n\n  if (headers.isResponse() && !headRequest_) {\n    const auto& contentLen =\n        headers.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH);\n    if (!contentLen.empty()) {\n      try {\n        expectedResponseLength_ = folly::to<uint64_t>(contentLen);\n      } catch (const folly::ConversionError& ex) {\n        LOG(ERROR) << \"Invalid content-length: \" << contentLen\n                   << \", ex=\" << ex.what() << \" \" << *this;\n      }\n    }\n  }\n  HTTPHeaderSize size;\n  transport_.sendHeaders(this, headers, &size, eom);\n  if (transportCallback_) {\n    transportCallback_->headerBytesGenerated(size);\n  }\n  if (hasBytesEventObservers()) {\n    const auto e =\n        HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n            .setTimestamp(proxygen::SteadyClock::now())\n            .setType(HTTPTransactionObserverInterface::TxnBytesEvent::Type::\n                         HEADER_BYTES_GENERATED)\n            .setNumBytes(size.compressed ? size.compressed : size.uncompressed)\n            .setHeaders(headers)\n            .build();\n    emitBytesEvent(e);\n  }\n  updateEgressCompressionInfo(transport_.getCodec().getCompressionInfo());\n  if (eom) {\n    if (!validateEgressStateTransition(\n            HTTPTransactionEgressSM::Event::sendEOM)) {\n      return;\n    }\n    // trailers are supported in this case:\n    // trailers are for chunked encoding-transfer of a body\n    if (transportCallback_) {\n      transportCallback_->bodyBytesGenerated(0);\n    }\n    if (hasBytesEventObservers()) {\n      const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                         .setTimestamp(proxygen::SteadyClock::now())\n                         .setType(HTTPTransactionObserverInterface::\n                                      TxnBytesEvent::Type::BODY_BYTES_GENERATED)\n                         .setNumBytes(0)\n                         .build();\n      emitBytesEvent(e);\n    }\n    if (!validateEgressStateTransition(\n            HTTPTransactionEgressSM::Event::eomFlushed)) {\n      return;\n    }\n    updateReadTimeout();\n  }\n  flushWindowUpdate();\n}\n\nvoid HTTPTransaction::sendHeadersWithEOM(const HTTPMessage& header) {\n  sendHeadersWithOptionalEOM(header, true);\n}\n\nvoid HTTPTransaction::sendHeaders(const HTTPMessage& header) {\n  sendHeadersWithOptionalEOM(header, false);\n}\n\nvoid HTTPTransaction::sendBody(std::unique_ptr<folly::IOBuf> body) {\n  DestructorGuard guard(this);\n  bool chunking =\n      ((egressState_ == HTTPTransactionEgressSM::State::ChunkHeaderSent) &&\n       !transport_.getCodec().supportsParallelRequests()); // see\n                                                           // sendChunkHeader\n\n  if (!validateEgressStateTransition(\n          HTTPTransactionEgressSM::Event::sendBody)) {\n    return;\n  }\n\n  if (body) {\n    size_t bodyLen = body->computeChainDataLength();\n    actualResponseLength_ = actualResponseLength_.value() + bodyLen;\n\n    if (chunking) {\n      // Note, this check doesn't account for cases where sendBody is called\n      // multiple times for a single chunk, and the total length exceeds the\n      // header.\n      DCHECK(!chunkHeaders_.empty());\n      DCHECK_LE(bodyLen, chunkHeaders_.back().length)\n          << \"Sent body longer than chunk header \";\n    }\n    deferredEgressBody_.append(std::move(body));\n    transport_.notifyEgressBodyBuffered(bodyLen);\n  }\n  notifyTransportPendingEgress();\n}\n\nbool HTTPTransaction::onWriteReady(const uint32_t maxEgress, double ratio) {\n  DestructorGuard g(this);\n  DCHECK(isEnqueued());\n  cumulativeRatio_ += ratio;\n  egressCalls_++;\n  sendDeferredBody(maxEgress);\n  return isEnqueued();\n}\n\n// Send up to maxEgress body bytes, including pendingEOM if appropriate\nsize_t HTTPTransaction::sendDeferredBody(uint32_t maxEgress) {\n  const int32_t windowAvailable = sendWindow_.getSize();\n  const uint32_t sendWindow =\n      useFlowControl_\n          ? std::min<uint32_t>(maxEgress,\n                               windowAvailable > 0 ? windowAvailable : 0)\n          : maxEgress;\n\n  // We shouldn't be called if we have no pending body/EOM, egress is paused,\n  // or the send window is closed\n  const size_t bytesLeft = getOutstandingEgressBodyBytes();\n  INVARIANT_RETURN((bytesLeft > 0 || isEgressEOMQueued()) && sendWindow > 0, 0);\n\n  size_t canSend = std::min<size_t>(sendWindow, bytesLeft);\n  if (maybeDelayForRateLimit()) {\n    // Timeout will call notifyTransportPendingEgress again\n    return 0;\n  }\n\n  size_t nbytes = 0;\n  bool willSendEOM = false;\n\n  if (chunkHeaders_.empty()) {\n    if (deferredEgressBody_.chainLength() > 0) {\n      std::unique_ptr<IOBuf> body = deferredEgressBody_.split(canSend);\n      nbytes = sendBodyNow(std::move(body), canSend, hasPendingEOM());\n    }\n  } else {\n    size_t curLen = 0;\n    // This body is expliticly chunked\n    while (!chunkHeaders_.empty() && canSend > 0) {\n      Chunk& chunk = chunkHeaders_.front();\n      if (!chunk.headerSent) {\n        nbytes += transport_.sendChunkHeader(this, chunk.length);\n        chunk.headerSent = true;\n      }\n      curLen = std::min<size_t>(chunk.length, canSend);\n      std::unique_ptr<folly::IOBuf> cur = deferredEgressBody_.split(curLen);\n      VLOG(4) << \"sending \" << curLen << \" fin=false\";\n      nbytes += sendBodyNow(std::move(cur), curLen, false);\n      canSend -= curLen;\n      chunk.length -= curLen;\n      if (chunk.length == 0) {\n        nbytes += transport_.sendChunkTerminator(this);\n        chunkHeaders_.pop_front();\n      } else {\n        DCHECK_EQ(canSend, 0);\n      }\n    }\n    willSendEOM = hasPendingEOM();\n  }\n  // Send any queued eom\n  if (willSendEOM) {\n    nbytes += sendEOMNow();\n  }\n\n  // Update the handler's pause state\n  notifyTransportPendingEgress();\n\n  // Outer check is a minor perf optimization — avoids the virtual call into\n  // checkIfEgressRateLimitedByUpstream() when the buffer is non-empty (common\n  // case), even though checkIfEgressRateLimitedByUpstream also checks this.\n  if (getOutstandingEgressBodyBytes() == 0) {\n    checkIfEgressRateLimitedByUpstream();\n  }\n\n  if (transportCallback_) {\n    transportCallback_->bodyBytesGenerated(nbytes);\n  }\n  if (hasBytesEventObservers()) {\n    const auto e = HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                       .setTimestamp(proxygen::SteadyClock::now())\n                       .setType(HTTPTransactionObserverInterface::\n                                    TxnBytesEvent::Type::BODY_BYTES_GENERATED)\n                       .setNumBytes(nbytes)\n                       .build();\n    emitBytesEvent(e);\n  }\n  return nbytes;\n}\n\nbool HTTPTransaction::maybeDelayForRateLimit() {\n  if (egressLimitBytesPerMs_ <= 0) {\n    // No rate limiting\n    return false;\n  }\n\n  if (numLimitedBytesEgressed_ == 0) {\n    // If we haven't egressed any bytes yet, don't delay.\n    return false;\n  }\n\n  int64_t limitedDurationMs =\n      (int64_t)millisecondsBetween(getCurrentTime(), startRateLimit_).count();\n\n  // Algebra!  Try to figure out the next time send where we'll\n  // be allowed to send at least 1 full packet's worth.  The\n  // formula we're using is:\n  //   (bytesSoFar + packetSize) / (timeSoFar + delay) == targetRateLimit\n  std::chrono::milliseconds requiredDelay(\n      (((int64_t)numLimitedBytesEgressed_ + kApproximateMTU) -\n       ((int64_t)egressLimitBytesPerMs_ * limitedDurationMs)) /\n      (int64_t)egressLimitBytesPerMs_);\n\n  if (requiredDelay.count() <= 0) {\n    // No delay required\n    return false;\n  }\n\n  if (requiredDelay > kRateLimitMaxDelay) {\n    // The delay should never be this long\n    VLOG(4) << \"ratelim: Required delay too long (\" << requiredDelay.count()\n            << \"ms), ignoring\";\n    return false;\n  }\n\n  // Delay required\n\n  egressRateLimited_ = true;\n\n  if (timer_) {\n    timer_->scheduleTimeout(&rateLimitCallback_, requiredDelay);\n  }\n\n  notifyTransportPendingEgress();\n  return true;\n}\n\nvoid HTTPTransaction::rateLimitTimeoutExpired() {\n  egressRateLimited_ = false;\n  notifyTransportPendingEgress();\n}\n\nsize_t HTTPTransaction::maybeSendDeferredNoError() {\n  size_t bytes = 0;\n  if (deferredNoError_) {\n    deferredNoError_ = false;\n    VLOG(4) << \"sending deferred NO_ERROR\";\n    bytes += sendAbortImpl(ErrorCode::NO_ERROR);\n  }\n  return bytes;\n}\n\nvoid HTTPTransaction::sendPadding(uint16_t bytes) {\n  VLOG(4) << \"egress padding=\" << bytes << \" on \" << *this;\n  // Padding is allowed at any time, even before headers and after EOM\n  transport_.sendPadding(this, bytes);\n}\n\nsize_t HTTPTransaction::sendEOMNow() {\n  DestructorGuard g(this);\n  VLOG(4) << \"egress EOM on \" << *this;\n  // TODO: with ByteEvent refactor, we will have to delay changing this\n  // state until later\n  if (!validateEgressStateTransition(\n          HTTPTransactionEgressSM::Event::eomFlushed)) {\n    return 0;\n  }\n  size_t nbytes = transport_.sendEOM(this, trailers_.get());\n  trailers_.reset();\n  updateReadTimeout();\n  // rst_stream/no_error if downstream egresses eom before ingress eom seen\n  deferredNoError_ = transport_.serverEarlyResponseEnabled() &&\n                     isDownstream() && !isIngressEOMSeen() && !isUpgraded();\n\n  nbytes += maybeSendDeferredNoError();\n\n  return nbytes;\n}\n\nsize_t HTTPTransaction::sendBodyNow(std::unique_ptr<folly::IOBuf> body,\n                                    size_t bodyLen,\n                                    bool sendEom) {\n  constexpr std::string_view kNoneStr = \"None\";\n  DCHECK(body);\n  DCHECK_GT(bodyLen, 0);\n  size_t nbytes = 0;\n  if (useFlowControl_) {\n    // Because of how sendBodyNow is embedded in HTTPTransaction code flow,\n    // calling INVARIANT here is not safe\n    CHECK(sendWindow_.reserve(bodyLen));\n  }\n  VLOG(4) << \"Sending \" << bodyLen\n          << \" bytes of body. eom=\" << ((sendEom) ? \"yes\" : \"no\")\n          << \" send_window is \"\n          << (useFlowControl_\n                  ? folly::to<std::string>(\n                        sendWindow_.getSize(), \" / \", sendWindow_.getCapacity())\n                  : kNoneStr)\n          << \" trailers=\" << ((trailers_) ? \"yes\" : \"no\") << \" \" << *this;\n  DCHECK_LT(bodyLen, std::numeric_limits<int64_t>::max());\n  transport_.notifyEgressBodyBuffered(-static_cast<int64_t>(bodyLen));\n  const bool sendDataFin = sendEom && !trailers_;\n  if (sendDataFin) {\n    if (!validateEgressStateTransition(\n            HTTPTransactionEgressSM::Event::eomFlushed)) {\n      return 0;\n    }\n  } else if (ingressErrorSeen_ && isExpectingWindowUpdate()) {\n    // I don't know how we got here but we're in trouble.  We need a window\n    // update to continue but we've already seen an ingress error.\n    HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,\n                     folly::to<std::string>(\"window blocked with ingress error,\"\n                                            \" streamID=\",\n                                            id_));\n    ex.setProxygenError(kErrorEOF);\n    ex.setCodecStatusCode(ErrorCode::FLOW_CONTROL_ERROR);\n    onError(ex);\n    return 0;\n  }\n  updateReadTimeout();\n  nbytes = transport_.sendBody(\n      this, std::move(body), sendDataFin, enableLastByteFlushedTracking_);\n\n  bodyBytesEgressed_ += bodyLen;\n  for (auto it = egressBodyOffsetsToTrack_.begin();\n       it != egressBodyOffsetsToTrack_.end() && it->first < bodyBytesEgressed_;\n       it = egressBodyOffsetsToTrack_.begin()) {\n    transport_.trackEgressBodyOffset(it->first, it->second);\n    egressBodyOffsetsToTrack_.erase(it);\n  }\n  if (sendEom && trailers_) {\n    nbytes += sendEOMNow();\n  }\n  if (egressLimitBytesPerMs_ > 0) {\n    numLimitedBytesEgressed_ += nbytes;\n  }\n\n  if (isEgressComplete()) {\n    nbytes += maybeSendDeferredNoError();\n  }\n  return nbytes;\n}\n\nvoid HTTPTransaction::sendEOM() {\n  DestructorGuard g(this);\n  if (!validateEgressStateTransition(HTTPTransactionEgressSM::Event::sendEOM)) {\n    return;\n  }\n  if (expectedResponseLength_ && actualResponseLength_ &&\n      (*expectedResponseLength_ != *actualResponseLength_)) {\n    if (stats_) {\n      stats_->recordEgressContentLengthMismatches();\n    }\n    auto errorMsg = folly::to<std::string>(\n        \"Content-Length/body mismatch sendEOM: expected=\",\n        *expectedResponseLength_,\n        \", actual= \",\n        *actualResponseLength_);\n    LOG(ERROR) << errorMsg << \" \" << *this;\n  }\n\n  if (getOutstandingEgressBodyBytes() == 0 && chunkHeaders_.empty()) {\n    // there is nothing left to send, egress the EOM directly.\n    if (!isEnqueued()) {\n      size_t nbytes = sendEOMNow();\n      transport_.notifyPendingEgress();\n      if (transportCallback_) {\n        transportCallback_->bodyBytesGenerated(nbytes);\n      }\n      if (hasBytesEventObservers()) {\n        const auto e =\n            HTTPTransactionObserverInterface::TxnBytesEvent::Builder()\n                .setTimestamp(proxygen::SteadyClock::now())\n                .setType(HTTPTransactionObserverInterface::TxnBytesEvent::Type::\n                             BODY_BYTES_GENERATED)\n                .setNumBytes(nbytes)\n                .build();\n        emitBytesEvent(e);\n      }\n    } else {\n      // If the txn is enqueued, sendDeferredBody()\n      // should take care of sending the EOM.\n      // This can happen for some uses of the egress queue\n      VLOG(4) << \"Queued egress EOM with no body\"\n              << \"[egressState=\" << egressState_ << \", \"\n              << \"ingressState=\" << ingressState_ << \", \"\n              << \"egressPaused=\" << egressPaused_ << \", \"\n              << \"ingressPaused=\" << ingressPaused_ << \", \"\n              << \"aborted=\" << aborted_ << \", \"\n              << \"enqueued=\" << isEnqueued() << \", \"\n              << \"chainLength=\" << deferredEgressBody_.chainLength() << \"]\"\n              << \" on \" << *this;\n    }\n  } else {\n    VLOG(4) << \"Queued egress EOM on \" << *this;\n    notifyTransportPendingEgress();\n  }\n}\n\nvoid HTTPTransaction::sendAbort() {\n  sendAbort(getDefaultAbortErrorCode(isUpstream()));\n}\n\nvoid HTTPTransaction::sendAbort(ErrorCode statusCode) {\n  if (statusCode == ErrorCode::NO_ERROR) {\n    // we can only send RST_STREAM or STOP_SENDING w/ NO_ERROR if downstream and\n    // eom is either queued or flushed\n    const bool canSendNoError =\n        isDownstream() &&\n        getEgressState() >= HTTPTransactionEgressSMData::State::EOMQueued;\n\n    // we defer sending abort only if eom is queued\n    deferredNoError_ =\n        canSendNoError &&\n        getEgressState() == HTTPTransactionEgressSMData::State::EOMQueued;\n\n    if (deferredNoError_) {\n      VLOG(4) << \"deferring abort ErrorCode=NO_ERROR\";\n      return;\n    }\n\n    // if statusCode == NO_ERROR and eom has not been flushed; default to CANCEL\n    // for upstream & INTERNAL_ERROR for downstream\n    if (!canSendNoError) {\n      VLOG(4) << \"cannot send NO_ERROR; falling back to default ErrorCode\";\n      statusCode = getDefaultAbortErrorCode(isUpstream());\n    }\n  }\n  sendAbortImpl(statusCode);\n}\n\nsize_t HTTPTransaction::sendAbortImpl(ErrorCode statusCode) {\n  DestructorGuard g(this);\n  markIngressComplete();\n  markEgressComplete();\n  if (aborted_) {\n    // This can happen in cases where the abort is sent before notifying the\n    // handler, but its logic also wants to abort\n    VLOG(4) << \"skipping redundant abort\";\n    return 0;\n  }\n  VLOG(4) << \"aborting transaction \" << *this;\n  aborted_ = true;\n  size_t nbytes = transport_.sendAbort(this, statusCode);\n  if (transportCallback_) {\n    HTTPHeaderSize size;\n    size.uncompressed = nbytes;\n    transportCallback_->headerBytesGenerated(size);\n  }\n  return nbytes;\n}\n\nbool HTTPTransaction::trackEgressBodyOffset(uint64_t offset,\n                                            ByteEvent::EventFlags flags) {\n  if (transport_.getSessionType() != Transport::Type::QUIC) {\n    // for now\n    return false;\n  }\n  if (offset < bodyBytesEgressed_) {\n    // we've egressed this byte already, ask transport to track it\n    transport_.trackEgressBodyOffset(offset, flags);\n  } else {\n    egressBodyOffsetsToTrack_.emplace(offset, flags);\n  }\n  return true;\n}\n\nuint16_t HTTPTransaction::getDatagramSizeLimit() const noexcept {\n  return transport_.getDatagramSizeLimit();\n}\n\nbool HTTPTransaction::sendDatagram(std::unique_ptr<folly::IOBuf> datagram) {\n  if (!validateEgressStateTransition(\n          HTTPTransactionEgressSM::Event::sendDatagram)) {\n    return false;\n  }\n\n  auto size = datagram->computeChainDataLength();\n  if (size > getDatagramSizeLimit()) {\n    return false;\n  }\n\n  auto sent = wtTransportProvider_->sendDatagram(std::move(datagram));\n\n  if (sent && transportCallback_) {\n    transportCallback_->datagramBytesGenerated(size);\n  }\n\n  return sent.hasValue();\n}\n\nfolly::Optional<HTTPTransaction::ConnectionToken>\nHTTPTransaction::getConnectionToken() const noexcept {\n  return transport_.getConnectionToken();\n}\n\nvoid HTTPTransaction::pauseIngress() {\n  VLOG(4) << \"pauseIngress request \" << *this;\n  DestructorGuard g(this);\n  if (ingressPaused_) {\n    VLOG(4) << \"can't pause ingress; ingressPaused=\" << ingressPaused_;\n    return;\n  }\n  ingressPaused_ = true;\n  cancelTimeout();\n  transport_.pauseIngress(this);\n}\n\nvoid HTTPTransaction::resumeIngress() {\n  VLOG(4) << \"resumeIngress request \" << *this;\n  DestructorGuard g(this);\n  if (!ingressPaused_ || isIngressComplete()) {\n    VLOG(4) << \"can't resume ingress, ingressPaused=\" << ingressPaused_\n            << \", ingressComplete=\" << isIngressComplete()\n            << \", inResume_=\" << inResume_ << \" \" << *this;\n    return;\n  }\n  ingressPaused_ = false;\n  transport_.resumeIngress(this);\n  if (inResume_) {\n    VLOG(4) << \"skipping recursive resume loop \" << *this;\n    return;\n  }\n  inResume_ = true;\n  SCOPE_EXIT {\n    updateReadTimeout();\n    inResume_ = false;\n  };\n\n  if (deferredIngress_ && (maxDeferredIngress_ <= deferredIngress_->size())) {\n    maxDeferredIngress_ = deferredIngress_->size();\n  }\n\n  // Process any deferred ingress callbacks\n  // Note: we recheck the ingressPaused_ state because a callback\n  // invoked by the resumeIngress() call above could have re-paused\n  // the transaction.\n  while (!ingressPaused_ && deferredIngress_ && !deferredIngress_->empty()) {\n    HTTPEvent& callback(deferredIngress_->front());\n    VLOG(5) << \"Processing deferred ingress callback of type \"\n            << callback.getEvent() << \" \" << *this;\n    SCOPE_EXIT {\n      if (deferredIngress_) {\n        deferredIngress_->pop();\n      }\n    };\n    switch (callback.getEvent()) {\n      case HTTPEvent::Type::MESSAGE_BEGIN:\n        LOG(FATAL) << \"unreachable\";\n      case HTTPEvent::Type::HEADERS_COMPLETE:\n        processIngressHeadersComplete(callback.getHeaders());\n        break;\n      case HTTPEvent::Type::BODY: {\n        auto len = callback.getBodyLength();\n        INVARIANT(recvWindow_.free(len));\n        processIngressBody(callback.getBody(), len);\n      } break;\n      case HTTPEvent::Type::CHUNK_HEADER:\n        processIngressChunkHeader(callback.getChunkLength());\n        break;\n      case HTTPEvent::Type::CHUNK_COMPLETE:\n        processIngressChunkComplete();\n        break;\n      case HTTPEvent::Type::TRAILERS_COMPLETE:\n        processIngressTrailers(callback.getTrailers());\n        break;\n      case HTTPEvent::Type::MESSAGE_COMPLETE:\n        processIngressEOM();\n        break;\n      case HTTPEvent::Type::UPGRADE:\n        processIngressUpgrade(callback.getUpgradeProtocol());\n        break;\n      case HTTPEvent::Type::ERROR:\n        processIngressError(*callback.getError());\n        break;\n    }\n  }\n}\n\nvoid HTTPTransaction::pauseEgress() {\n  VLOG(4) << \"asked to pause egress \" << *this;\n  DestructorGuard g(this);\n  if (egressPaused_) {\n    VLOG(4) << \"egress already paused \" << *this;\n    return;\n  }\n  egressPaused_ = true;\n  updateHandlerPauseState();\n}\n\nvoid HTTPTransaction::resumeEgress() {\n  VLOG(4) << \"asked to resume egress \" << *this;\n  DestructorGuard g(this);\n  if (!egressPaused_) {\n    VLOG(4) << \"egress already not paused \" << *this;\n    return;\n  }\n  egressPaused_ = false;\n  updateHandlerPauseState();\n}\n\nvoid HTTPTransaction::setEgressRateLimit(uint64_t bitsPerSecond) {\n  egressLimitBytesPerMs_ = bitsPerSecond / 8000;\n  if (bitsPerSecond > 0 && egressLimitBytesPerMs_ == 0) {\n    VLOG(4) << \"ratelim: Limit too low (\" << bitsPerSecond << \"), ignoring\";\n  }\n  startRateLimit_ = getCurrentTime();\n  numLimitedBytesEgressed_ = 0;\n}\n\nvoid HTTPTransaction::notifyTransportPendingEgress() {\n  DestructorGuard guard(this);\n  CHECK(queueHandle_);\n  if (!egressRateLimited_ &&\n      (getOutstandingEgressBodyBytes() > 0 || isEgressEOMQueued()) &&\n      (!useFlowControl_ || sendWindow_.getSize() > 0)) {\n    // Egress isn't paused, we have something to send, and flow\n    // control isn't blocking us.\n    if (!isEnqueued()) {\n      // Insert into the queue and let the session know we've got something\n      egressQueue_.signalPendingEgress(queueHandle_);\n      transport_.notifyPendingEgress();\n    }\n  } else if (isEnqueued()) {\n    // Nothing to send, or not allowed to send right now.\n    egressQueue_.clearPendingEgress(queueHandle_);\n  }\n  updateHandlerPauseState();\n}\n\nvoid HTTPTransaction::updateHandlerPauseState() {\n  if (isEgressEOMSeen()) {\n    VLOG(4) << \"transaction already egress complete, not updating pause state \"\n            << *this;\n    return;\n  }\n  int64_t availWindow = sendWindow_.getSize() - getOutstandingEgressBodyBytes();\n  // do not count transaction stalled if no more bytes to send,\n  // i.e. when availWindow == 0\n  if (useFlowControl_ && availWindow < 0 && !flowControlPaused_) {\n    VLOG(4) << \"transaction stalled by flow control txn=\" << *this;\n    if (stats_) {\n      stats_->recordTransactionStalled();\n    }\n  }\n  flowControlPaused_ = useFlowControl_ && availWindow <= 0;\n  bool bufferFull = getOutstandingEgressBodyBytes() > egressBufferLimit_;\n  bool handlerShouldBePaused =\n      egressPaused_ || flowControlPaused_ || egressRateLimited_ || bufferFull;\n\n  if (!egressPaused_ && bufferFull) {\n    VLOG(4) << \"Not resuming handler, buffer full, txn=\" << *this;\n  }\n\n  if (handler_ && handlerShouldBePaused != handlerEgressPaused_) {\n    if (handlerShouldBePaused) {\n      if (canSendHeaders()) {\n        VLOG(4) << \"txn hasn't egressed headers, not updating pause state \"\n                << *this;\n        return;\n      }\n      handlerEgressPaused_ = true;\n      VLOG(4) << \"egress paused txn=\" << *this;\n      handler_->onEgressPaused();\n    } else {\n      handlerEgressPaused_ = false;\n      VLOG(4) << \"egress resumed txn=\" << *this;\n      handler_->onEgressResumed();\n    }\n  }\n}\n\nvoid HTTPTransaction::updateIngressCompressionInfo(\n    const CompressionInfo& tableInfo) {\n  tableInfo_.ingress = tableInfo.ingress;\n}\n\nvoid HTTPTransaction::updateEgressCompressionInfo(\n    const CompressionInfo& tableInfo) {\n  tableInfo_.egress = tableInfo.egress;\n}\n\nconst CompressionInfo& HTTPTransaction::getCompressionInfo() const {\n  return tableInfo_;\n}\n\nbool HTTPTransaction::mustQueueIngress() const {\n  return ingressPaused_ || (deferredIngress_ && !deferredIngress_->empty());\n}\n\nvoid HTTPTransaction::checkCreateDeferredIngress() {\n  if (!deferredIngress_) {\n    deferredIngress_ = std::make_unique<std::queue<HTTPEvent>>();\n  }\n}\n\nbool HTTPTransaction::onPushedTransaction(HTTPTransaction* pushTxn) {\n  DestructorGuard g(this);\n  INVARIANT_RETURN(*pushTxn->assocStreamId_ == id_, false);\n  if (!handler_) {\n    VLOG(4) << \"Cannot add a pushed txn to an unhandled txn\";\n    return false;\n  }\n  refreshTimeout();\n  handler_->onPushedTransaction(pushTxn);\n  if (!pushTxn->getHandler()) {\n    VLOG(4) << \"Failed to create a handler for push transaction\";\n    return false;\n  }\n  pushedTransactions_.insert(pushTxn->getID());\n  return true;\n}\n\nvoid HTTPTransaction::setIdleTimeout(std::chrono::milliseconds idleTimeout) {\n  idleTimeout_ = idleTimeout;\n  VLOG(4) << \"HTTPTransaction: idle timeout is set to  \"\n          << std::chrono::duration_cast<std::chrono::milliseconds>(idleTimeout)\n                 .count();\n  updateReadTimeout();\n}\n\nvoid HTTPTransaction::describe(std::ostream& os) const {\n  transport_.describe(os);\n  os << \", streamID=\" << id_;\n}\n\n/*\n * TODO: when HTTPSession sends a SETTINGS frame indicating a\n * different initial window, it should call this function on all its\n * transactions.\n */\nvoid HTTPTransaction::setReceiveWindow(uint32_t capacity) {\n  // Depending on whether delta is positive or negative it will cause the\n  // window to either increase or decrease.\n  if (!useFlowControl_) {\n    return;\n  }\n  int32_t delta = capacity - recvWindow_.getCapacity();\n  if (delta < 0) {\n    // For now, we're disallowing shrinking the window, since it can lead\n    // to FLOW_CONTROL_ERRORs if there is data in flight.\n    VLOG(4) << \"Refusing to shrink the recv window\";\n    return;\n  }\n  if (!recvWindow_.setCapacity(capacity)) {\n    return;\n  }\n  recvToAck_ += delta;\n  flushWindowUpdate();\n}\n\nvoid HTTPTransaction::flushWindowUpdate() {\n  if (recvToAck_ > 0 && useFlowControl_ && !isIngressEOMSeen() &&\n      (isDownstream() ||\n       egressState_ != HTTPTransactionEgressSM::State::Start ||\n       ingressState_ != HTTPTransactionIngressSM::State::Start)) {\n    // Down egress upstream window updates until after headers\n    VLOG(4) << \"recv_window is \" << recvWindow_.getSize() << \" / \"\n            << recvWindow_.getCapacity() << \" after acking \" << recvToAck_\n            << \" \" << *this;\n    transport_.sendWindowUpdate(this, recvToAck_);\n    recvToAck_ = 0;\n  }\n}\n\nint32_t HTTPTransaction::getRecvToAck() const {\n  return recvToAck_;\n}\n\nstd::ostream& operator<<(std::ostream& os, const HTTPTransaction& txn) {\n  txn.describe(os);\n  return os;\n}\n\nvoid HTTPTransaction::updateAndSendPriority(HTTPPriority pri) {\n  pri.urgency = HTTPMessage::normalizePriority((int8_t)pri.urgency);\n  // Note we no longer want to play with the egressQueue_ with the new API.\n  transport_.changePriority(this, pri);\n}\n\nvoid HTTPTransaction::checkIfEgressRateLimitedByUpstream() {\n  if (transportCallback_ && !isEgressEOMQueued() &&\n      getOutstandingEgressBodyBytes() == 0) {\n    transportCallback_->egressBufferEmpty();\n  }\n}\n\nvoid HTTPTransaction::onDatagram(\n    std::unique_ptr<folly::IOBuf> datagram) noexcept {\n  DestructorGuard g(this);\n  if (aborted_) {\n    return;\n  }\n  VLOG(4) << \"datagram received on \" << *this;\n  if (!validateIngressStateTransition(\n          HTTPTransactionIngressSM::Event::onDatagram)) {\n    return;\n  }\n  refreshTimeout();\n  auto size = datagram->computeChainDataLength();\n\n  if (transportCallback_) {\n    transportCallback_->datagramBytesReceived(size);\n  }\n\n  if (handler_ && !isIngressComplete()) {\n    handler_->onDatagram(std::move(datagram));\n  }\n}\n\nWebTransportImpl::BidiStreamHandle HTTPTransaction::onWebTransportBidiStream(\n    HTTPCodec::StreamID id) {\n  if (!handler_) {\n    wtTransportProvider_->resetWebTransportEgress(id,\n                                                  WebTransport::kInternalError);\n    wtTransportProvider_->stopReadingWebTransportIngress(\n        id, WebTransport::kInternalError);\n    return {.readHandle = nullptr, .writeHandle = nullptr};\n  }\n  refreshTimeout();\n  auto handle = webTransportImpl_->onWebTransportBidiStream(id);\n  handler_->onWebTransportBidiStream(\n      id, {.readHandle = handle.readHandle, .writeHandle = handle.writeHandle});\n  // what if the handler killed handle (stop sending/fin/rst)?\n  return handle;\n}\n\nWebTransportImpl::StreamReadHandle* HTTPTransaction::onWebTransportUniStream(\n    HTTPCodec::StreamID id) {\n  if (!handler_) {\n    LOG(ERROR) << \"Handler not set\";\n    wtTransportProvider_->stopReadingWebTransportIngress(\n        id, WebTransport::kInternalError);\n    return nullptr;\n  }\n  refreshTimeout();\n  auto handle = webTransportImpl_->onWebTransportUniStream(id);\n\n  handler_->onWebTransportUniStream(id, handle);\n  // what if the handler killed handle (stop sending)?\n  return handle;\n}\n\nbool HTTPTransaction::onWebTransportStopSending(HTTPCodec::StreamID id,\n                                                uint32_t errorCode) {\n  webTransportImpl_->onWebTransportStopSending(id, errorCode);\n  return true;\n}\n\nvoid HTTPTransaction::sendCloseWebTransportSessionCapsule(\n    uint32_t errorCode, const std::string& errorMessage) {\n  if (isEgressComplete()) {\n    VLOG(4) << \"Ignoring sendCloseWebTransportSessionCapsule: egress already \"\n               \"complete for streamID=\"\n            << id_;\n    return;\n  }\n  CloseWebTransportSessionCapsule capsule{.applicationErrorCode = errorCode,\n                                          .applicationErrorMessage =\n                                              errorMessage};\n  folly::IOBufQueue queue;\n  if (auto res = writeCloseWebTransportSession(queue, capsule);\n      !res.has_value()) {\n    VLOG(4) << \"Failed to write CLOSE_WEBTRANSPORT_SESSION capsule\";\n    return;\n  }\n\n  auto capsuleData = queue.move();\n  if (capsuleData && capsuleData->computeChainDataLength() > 0) {\n    sendBody(std::move(capsuleData));\n  }\n}\n\nbool HTTPTransaction::hasBytesEventObservers() {\n  return txnObserverContainer_.hasObserversForEvent<\n      HTTPTransactionObserverInterface::Events::TxnBytes>();\n}\n\nvoid HTTPTransaction::emitBytesEvent(\n    const HTTPTransactionObserverInterface::TxnBytesEvent& event) {\n  txnObserverContainer_.invokeInterfaceMethod<\n      HTTPTransactionObserverInterface::Events::TxnBytes>(\n      [&event](auto observer, auto observed) {\n        observer->onBytesEvent(observed, event);\n      });\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransaction.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/async/AsyncTransport.h>\n#include <folly/io/async/DelayedDestructionBase.h>\n#include <folly/io/async/HHWheelTimer.h>\n#include <folly/lang/Assume.h>\n#include <iosfwd>\n#include <memory>\n#include <proxygen/lib/http/HTTPConstants.h>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <proxygen/lib/http/Window.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverInterface.h>\n#include <proxygen/lib/http/session/ByteEvents.h>\n#include <proxygen/lib/http/session/HTTP2PriorityQueue.h>\n#include <proxygen/lib/http/session/HTTPEvent.h>\n#include <proxygen/lib/http/session/HTTPTransactionEgressSM.h>\n#include <proxygen/lib/http/session/HTTPTransactionIngressSM.h>\n#include <proxygen/lib/http/sink/FlowControlInfo.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <proxygen/lib/http/webtransport/WebTransportImpl.h>\n#include <proxygen/lib/utils/Time.h>\n#include <proxygen/lib/utils/TraceEvent.h>\n#include <proxygen/lib/utils/TraceEventObserver.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n#include <set>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\n\n/**\n * An HTTPTransaction represents a single request/response pair\n * for some HTTP-like protocol.  It works with a Transport that\n * performs the network processing and wire-protocol formatting\n * and a Handler that implements some sort of application logic.\n *\n * The typical sequence of events for a simple application is:\n *\n *   * The application accepts a connection and creates a Transport.\n *   * The Transport reads from the connection, parses whatever\n *     protocol the client is speaking, and creates a Transaction\n *     to represent the first request.\n *   * Once the Transport has received the full request headers,\n *     it creates a Handler, plugs the handler into the Transaction,\n *     and calls the Transaction's onIngressHeadersComplete() method.\n *   * The Transaction calls the Handler's onHeadersComplete() method\n *     and the Handler begins processing the request.\n *   * If there is a request body, the Transport streams it through\n *     the Transaction to the Handler.\n *   * When the Handler is ready to produce a response, it streams\n *     the response through the Transaction to the Transport.\n *   * When the Transaction has seen the end of both the request\n *     and the response, it detaches itself from the Handler and\n *     Transport and deletes itself.\n *   * The Handler deletes itself at some point after the Transaction\n *     has detached from it.\n *   * The Transport may, depending on the protocol, process other\n *     requests after -- or even in parallel with -- that first\n *     request.  Each request gets its own Transaction and Handler.\n *\n * For some applications, like proxying, a Handler implementation\n * may obtain one or more upstream connections, each represented\n * by another Transport, and create outgoing requests on the upstream\n * connection(s), with each request represented as a new Transaction.\n *\n * With a multiplexing protocol like H2 on both sides of a proxy,\n * the cardinality relationship can be:\n *\n *                 +-----------+     +-----------+     +-------+\n *   (Client-side) | Transport |1---*|Transaction|1---1|Handler|\n *                 +-----------+     +-----------+     +-------+\n *                                                         1\n *                                                         |\n *                                                         |\n *                                                         1\n *                                   +---------+     +-----------+\n *                (Server-side)      |Transport|1---*|Transaction|\n *                                   +---------+     +-----------+\n *\n * A key design goal of HTTPTransaction is to serve as a protocol-\n * independent abstraction that insulates Handlers from the semantics\n * different of HTTP-like protocols.\n */\n\n/** Info about Transaction running on this session */\nclass TransactionInfo {\n public:\n  TransactionInfo() = default;\n\n  TransactionInfo(std::chrono::milliseconds ttfb,\n                  std::chrono::milliseconds ttlb,\n                  uint64_t eHeader,\n                  uint64_t inHeader,\n                  uint64_t eBody,\n                  uint64_t inBody,\n                  bool completed)\n      : timeToFirstByte(ttfb),\n        timeToLastByte(ttlb),\n        egressHeaderBytes(eHeader),\n        ingressHeaderBytes(inHeader),\n        egressBodyBytes(eBody),\n        ingressBodyBytes(inBody),\n        isCompleted(completed) {\n  }\n\n  /** Time to first byte */\n  std::chrono::milliseconds timeToFirstByte{0};\n  /** Time to last byte */\n  std::chrono::milliseconds timeToLastByte{0};\n\n  /** Number of bytes send in headers */\n  uint64_t egressHeaderBytes{0};\n  /** Number of bytes receive headers */\n  uint64_t ingressHeaderBytes{0};\n  /** Number of bytes send in body */\n  uint64_t egressBodyBytes{0};\n  /** Number of bytes receive in body */\n  uint64_t ingressBodyBytes{0};\n\n  /** Is the transaction was completed without error */\n  bool isCompleted{false};\n};\n\nclass HTTPSessionStats;\nclass HTTPTransaction;\nclass HTTPTransactionHandler : public TraceEventObserver {\n public:\n  /**\n   * Called once per transaction. This notifies the handler of which\n   * transaction it should talk to and will receive callbacks from.\n   */\n  virtual void setTransaction(HTTPTransaction* txn) noexcept = 0;\n\n  /**\n   * Called once after a transaction successfully completes. It\n   * will be called even if a read or write error happened earlier.\n   * This is a terminal callback, which means that the HTTPTransaction\n   * object that gives this call will be invalid after this function\n   * completes.\n   */\n  virtual void detachTransaction() noexcept = 0;\n\n  /**\n   * Called at most once per transaction. This is usually the first\n   * ingress callback. It is possible to get a read error before this\n   * however. If you had previously called pauseIngress(), this callback\n   * will be delayed until you call resumeIngress().\n   */\n  virtual void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept = 0;\n\n  /**\n   * Can be called multiple times per transaction. If you had previously\n   * called pauseIngress(), this callback will be delayed until you call\n   * resumeIngress().\n   */\n  virtual void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept = 0;\n\n  /**\n   * Same as onBody() but with additional offset parameter.\n   */\n  virtual void onBodyWithOffset(uint64_t /* bodyOffset */,\n                                std::unique_ptr<folly::IOBuf> chain) {\n    onBody(std::move(chain));\n  }\n\n  /**\n   * Can be called multiple times per transaction. If you had previously\n   * called pauseIngress(), this callback will be delayed until you call\n   * resumeIngress(). This signifies the beginning of a chunk of length\n   * 'length'. You will receive onBody() after this. Also, the length will\n   * be greater than zero.\n   */\n  virtual void onChunkHeader(size_t /* length */) noexcept {\n  }\n\n  /**\n   * Can be called multiple times per transaction. If you had previously\n   * called pauseIngress(), this callback will be delayed until you call\n   * resumeIngress(). This signifies the end of a chunk.\n   */\n  virtual void onChunkComplete() noexcept {\n  }\n\n  /**\n   * Can be called any number of times per transaction. If you had\n   * previously called pauseIngress(), this callback will be delayed until\n   * you call resumeIngress(). Trailers can be received once right before\n   * the EOM of a chunked HTTP/1.1 reponse or multiple times per\n   * transaction from HTTP/2 HEADERS frames.\n   */\n  virtual void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept = 0;\n\n  /**\n   * Can be called once per transaction. If you had previously called\n   * pauseIngress(), this callback will be delayed until you call\n   * resumeIngress(). After this callback is received, there will be no\n   * more normal ingress callbacks received (onEgress*() and onError()\n   * may still be invoked). The Handler should consider\n   * ingress complete after receiving this message. This Transaction is\n   * still valid, and work may still occur on it until detachTransaction\n   * is called.\n   */\n  virtual void onEOM() noexcept = 0;\n\n  /**\n   * Can be called once per transaction. If you had previously called\n   * pauseIngress(), this callback will be delayed until you call\n   * resumeIngress(). After this callback is invoked, further data\n   * will be forwarded using the onBody() callback. Once the data transfer\n   * is completed (EOF recevied in case of CONNECT), onEOM() callback will\n   * be invoked.\n   */\n  virtual void onUpgrade(UpgradeProtocol protocol) noexcept = 0;\n\n  /**\n   * Can be called at any time before detachTransaction(). This callback\n   * implies that an error has occurred. To determine if ingress or egress\n   * is affected, check the direciont on the HTTPException. If the\n   * direction is INGRESS, it MAY still be possible to send egress.\n   */\n  virtual void onError(const HTTPException& error) noexcept = 0;\n\n  /**\n   * Can be called at any time before detachTransaction(). This callback is\n   * invoked in cases that violate an internal invariant that is fatal to the\n   * transaction but can be recoverable for the session or library.  One such\n   * example is mis-use of the egress APIs (sendBody() before sendHeaders()).\n   */\n  virtual void onInvariantViolation(const HTTPException& error) noexcept {\n    LOG(FATAL) << error.what();\n  }\n  /**\n   * If the remote side's receive buffer fills up, this callback will be\n   * invoked so you can attempt to stop sending to the remote side.\n   */\n  virtual void onEgressPaused() noexcept = 0;\n\n  /**\n   * This callback lets you know that the remote side has resumed reading\n   * and you can now continue to send data.\n   */\n  virtual void onEgressResumed() noexcept = 0;\n\n  /**\n   * Ask the handler to construct a handler for a pushed transaction associated\n   * with its transaction.\n   *\n   * TODO: Reconsider default implementation here. If the handler\n   * does not implement, better set max initiated to 0 in a settings frame?\n   */\n  virtual void onPushedTransaction(HTTPTransaction* /* txn */) noexcept {\n  }\n\n  /**\n   * Inform the handler that a GOAWAY has been received on the\n   * transport. This callback will only be invoked if the transport is\n   * HTTP/2 or HTTP/3. It may be invoked multiple times.\n   *\n   * @param code The error code received in the GOAWAY frame\n   */\n  virtual void onGoaway(ErrorCode /* code */) noexcept {\n  }\n\n  /**\n   * Can be called multiple times per transaction after onHeadersComplete and\n   * before detachTransaction()\n   *\n   * It does not obey pauseIngress/resumeIngress it is up to the handler\n   * to decide whether to buffer/drop datagrams\n   */\n  virtual void onDatagram(std::unique_ptr<folly::IOBuf> /*datagram*/) noexcept {\n  }\n\n  /**\n   * Invoked when the peer initiates a new bidirectional WebTransport stream.\n   * This can only be invoked when the transaction has successfully negotiated\n   * WebTransport (CONNECT(webtransport) + 2xx).\n   *\n   * Once it is called the handler is responsible for disposing of the stream\n   * until the transaction detaches, at which point it will be automatically\n   * reset.\n   */\n  virtual void onWebTransportBidiStream(\n      HTTPCodec::StreamID /*id*/,\n      WebTransport::BidiStreamHandle /*stream*/) noexcept {\n  }\n\n  /**\n   * Invoked when the peer initiates a new unidirectional WebTransport stream.\n   * This can only be invoked when the transaction has successfully negotiated\n   * WebTransport (CONNECT(webtransport) + 2xx).\n   *\n   * Once it is called the handler is responsible for disposing of the stream\n   * until the transaction detaches, at which point it will be automatically\n   * abandoned.\n   */\n  virtual void onWebTransportUniStream(\n      HTTPCodec::StreamID /*id*/,\n      WebTransport::StreamReadHandle* /*stream*/) noexcept {\n  }\n\n  /**\n   * Invoked when the peer closes the WebTransport session.\n   * This can only be invoked when the transaction has successfully negotiated\n   * WebTransport (CONNECT(webtransport) + 2xx).\n   *\n   * Before this is called, all outstanding stream reads and writes will return\n   * an error, and all stream handles will be cancelled and invalidated.\n   *\n   * This will be called prior to detachTransaction.  An error code will be\n   * passed if the peer sent a DRAIN_WEBTRANSPORT_SESSION capsule.\n   */\n  virtual void onWebTransportSessionClose(\n      folly::Optional<uint32_t> /*error*/) noexcept {\n  }\n\n  ~HTTPTransactionHandler() override = default;\n};\n\nclass HTTPPushTransactionHandler : public HTTPTransactionHandler {\n public:\n  ~HTTPPushTransactionHandler() override = default;\n\n  void onHeadersComplete(std::unique_ptr<HTTPMessage>) noexcept final {\n    LOG(FATAL) << \"push txn received headers\";\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf>) noexcept final {\n    LOG(FATAL) << \"push txn received body\";\n  }\n\n  void onBodyWithOffset(uint64_t,\n                        std::unique_ptr<folly::IOBuf>) noexcept final {\n    LOG(FATAL) << \"push txn received body with offset\";\n  }\n\n  void onChunkHeader(size_t /* length */) noexcept final {\n    LOG(FATAL) << \"push txn received chunk header\";\n  }\n\n  void onChunkComplete() noexcept final {\n    LOG(FATAL) << \"push txn received chunk complete\";\n  }\n\n  void onTrailers(std::unique_ptr<HTTPHeaders>) noexcept final {\n    LOG(FATAL) << \"push txn received trailers\";\n  }\n\n  void onEOM() noexcept final {\n    LOG(FATAL) << \"push txn received EOM\";\n  }\n\n  void onUpgrade(UpgradeProtocol) noexcept final {\n    LOG(FATAL) << \"push txn received upgrade\";\n  }\n\n  void onPushedTransaction(HTTPTransaction*) noexcept final {\n    LOG(FATAL) << \"push txn received push txn\";\n  }\n};\n\n/**\n * Callback interface to be notified of events on the byte stream.\n */\nclass HTTPTransactionTransportCallback {\n public:\n  virtual void firstHeaderByteFlushed() noexcept = 0;\n\n  virtual void firstByteFlushed() noexcept = 0;\n\n  virtual void lastByteFlushed() noexcept = 0;\n\n  virtual void trackedByteFlushed() noexcept {\n  }\n\n  virtual void lastByteAcked(std::chrono::milliseconds latency) noexcept = 0;\n\n  virtual void trackedByteEventTX(const ByteEvent& /* event */) noexcept {\n  }\n\n  virtual void trackedByteEventAck(const ByteEvent& /* event */) noexcept {\n  }\n\n  virtual void egressBufferEmpty() noexcept {\n  }\n\n  virtual void headerBytesGenerated(HTTPHeaderSize& size) noexcept = 0;\n\n  virtual void headerBytesReceived(const HTTPHeaderSize& size) noexcept = 0;\n\n  // May include extra bytes for EOF/trailers\n  virtual void bodyBytesGenerated(size_t nbytes) noexcept = 0;\n\n  virtual void bodyBytesReceived(size_t size) noexcept = 0;\n\n  virtual void lastEgressHeaderByteAcked() noexcept {\n  }\n\n  virtual void bodyBytesTx(uint64_t /* bodyOffset */) noexcept {\n  }\n\n  virtual void bodyBytesDelivered(uint64_t /* bodyOffset */) noexcept {\n  }\n\n  virtual void bodyBytesDeliveryCancelled(uint64_t /* bodyOffset */) noexcept {\n  }\n\n  virtual void transportAppRateLimited() noexcept {\n  }\n\n  virtual void datagramBytesGenerated(size_t /* nbytes */) noexcept {\n  }\n  virtual void datagramBytesReceived(size_t /* size */) noexcept {\n  }\n\n  virtual ~HTTPTransactionTransportCallback() = default;\n};\n\nclass HTTPSessionBase;\nclass HTTPTransaction\n    : public folly::HHWheelTimer::Callback\n    , public folly::DelayedDestructionBase\n    , public WebTransportImpl::SessionProvider {\n public:\n  using Handler = HTTPTransactionHandler;\n  using PushHandler = HTTPPushTransactionHandler;\n\n  using FlowControlInfo = proxygen::FlowControlInfo;\n\n  /**\n   * Opaque token that identifies the underlying connection.\n   * Transaction handlers can use this token to group different\n   * Transport instances by the distinct underlying connections.\n   * Its uniqueness is not enforced by the Transport.\n   */\n  using ConnectionToken = std::string;\n\n  class Transport : public WebTransportImpl::TransportProvider {\n   public:\n    enum class Type : uint8_t { TCP, QUIC };\n\n    virtual void pauseIngress(HTTPTransaction* txn) noexcept = 0;\n\n    virtual void resumeIngress(HTTPTransaction* txn) noexcept = 0;\n\n    virtual void transactionTimeout(HTTPTransaction* txn) noexcept = 0;\n\n    virtual void sendHeaders(HTTPTransaction* txn,\n                             const HTTPMessage& headers,\n                             HTTPHeaderSize* size,\n                             bool eom) noexcept = 0;\n\n    virtual size_t sendBody(HTTPTransaction* txn,\n                            std::unique_ptr<folly::IOBuf> iobuf,\n                            bool eom,\n                            bool trackLastByteFlushed) noexcept = 0;\n\n    virtual size_t sendChunkHeader(HTTPTransaction* txn,\n                                   size_t length) noexcept = 0;\n\n    virtual size_t sendChunkTerminator(HTTPTransaction* txn) noexcept = 0;\n\n    virtual size_t sendPadding(HTTPTransaction* txn,\n                               uint16_t bytes) noexcept = 0;\n\n    virtual size_t sendEOM(HTTPTransaction* txn,\n                           const HTTPHeaders* trailers) noexcept = 0;\n\n    virtual size_t sendAbort(HTTPTransaction* txn,\n                             ErrorCode statusCode) noexcept = 0;\n    /*\n     * Updates the Local priority for the transaction.\n     * For an upstream transaction it also sends the priority update to the peer\n     */\n    virtual size_t changePriority(HTTPTransaction* txn,\n                                  HTTPPriority pri) noexcept = 0;\n\n    virtual size_t sendWindowUpdate(HTTPTransaction* txn,\n                                    uint32_t bytes) noexcept = 0;\n\n    virtual void notifyPendingEgress() noexcept = 0;\n\n    virtual void detach(HTTPTransaction* txn) noexcept = 0;\n\n    virtual void notifyIngressBodyProcessed(uint32_t bytes) noexcept = 0;\n\n    virtual void notifyEgressBodyBuffered(int64_t bytes) noexcept = 0;\n\n    [[nodiscard]] const folly::SocketAddress& getLocalAddress()\n        const noexcept override = 0;\n\n    [[nodiscard]] const folly::SocketAddress& getPeerAddress()\n        const noexcept override = 0;\n\n    [[nodiscard]] virtual std::chrono::seconds getLatestIdleTime() const = 0;\n\n    virtual void describe(std::ostream&) const = 0;\n\n    [[nodiscard]] virtual const wangle::TransportInfo& getSetupTransportInfo()\n        const noexcept = 0;\n\n    virtual bool getCurrentTransportInfo(wangle::TransportInfo* tinfo) = 0;\n\n    virtual void getFlowControlInfo(FlowControlInfo* info) = 0;\n\n    [[nodiscard]] virtual HTTPTransaction::Transport::Type getSessionType()\n        const noexcept = 0;\n\n    [[nodiscard]] virtual const HTTPCodec& getCodec() const noexcept = 0;\n\n    /*\n     * Drain the underlying session. This will affect other transactions\n     * running on the same session and is discouraged unless you are confident\n     * that the session is broken.\n     */\n    virtual void drain() = 0;\n\n    [[nodiscard]] virtual bool isDraining() const = 0;\n\n    virtual HTTPTransaction* newPushedTransaction(\n        HTTPCodec::StreamID assocStreamId,\n        HTTPTransaction::PushHandler* handler,\n        ProxygenError* error = nullptr) noexcept = 0;\n\n    [[nodiscard]] virtual std::string getSecurityProtocol() const = 0;\n\n    virtual void addWaitingForReplaySafety(\n        folly::AsyncTransport::ReplaySafetyCallback* callback) noexcept = 0;\n\n    virtual void removeWaitingForReplaySafety(\n        folly::AsyncTransport::ReplaySafetyCallback* callback) noexcept = 0;\n\n    [[nodiscard]] virtual bool needToBlockForReplaySafety() const = 0;\n\n    [[nodiscard]] virtual const folly::AsyncTransport* getUnderlyingTransport()\n        const noexcept = 0;\n\n    /**\n     * Returns true if the underlying transport has completed full handshake.\n     */\n    [[nodiscard]] virtual bool isReplaySafe() const = 0;\n\n    virtual void setHTTP2PrioritiesEnabled(bool enabled) = 0;\n    [[nodiscard]] virtual bool getHTTP2PrioritiesEnabled() const = 0;\n\n    virtual HTTPSessionBase* getHTTPSessionBase() = 0;\n\n    virtual folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n        uint8_t level) = 0;\n\n    virtual folly::Optional<HTTPPriority> getHTTPPriority() {\n      return folly::none;\n    }\n\n    [[nodiscard]] virtual uint16_t getDatagramSizeLimit() const noexcept {\n      return 0;\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n        std::unique_ptr<folly::IOBuf> /*datagram*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    bool usesEncodedApplicationErrorCodes() override {\n      return true;\n    }\n\n    bool isPeerInitiatedStream(HTTPCodec::StreamID /*id*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    [[nodiscard]] virtual bool supportsWebTransport() const {\n      return false;\n    }\n\n    folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportBidiStream() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportUniStream() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::SemiFuture<folly::Unit> awaitUniStreamCredit() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    bool canCreateUniStream() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    bool canCreateBidiStream() override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n    sendWebTransportStreamData(\n        HTTPCodec::StreamID /*id*/,\n        std::unique_ptr<folly::IOBuf> /*data*/,\n        bool /*eof*/,\n        WebTransport::ByteEventCallback* /* deliveryCallback */) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    notifyPendingWriteOnStream(HTTPCodec::StreamID,\n                               quic::StreamWriteCallback*) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    resetWebTransportEgress(HTTPCodec::StreamID /*id*/,\n                            uint32_t /*errorCode*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                    WebTransport::ErrorCode>\n    readWebTransportData(HTTPCodec::StreamID /*id*/, size_t /*max*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    initiateReadOnBidiStream(\n        HTTPCodec::StreamID /*id*/,\n        quic::StreamReadCallback* /*readCallback*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    setWebTransportStreamPriority(\n        HTTPCodec::StreamID /*id*/,\n        quic::PriorityQueue::Priority /*pri*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    setWebTransportPriorityQueue(\n        std::unique_ptr<quic::PriorityQueue> /*queue*/) noexcept override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    pauseWebTransportIngress(HTTPCodec::StreamID /*id*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    resumeWebTransportIngress(HTTPCodec::StreamID /*id*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    stopReadingWebTransportIngress(\n        HTTPCodec::StreamID /*id*/,\n        folly::Optional<uint32_t> /*errorCode*/) override {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    /**\n     * Ask transport to track and ack body delivery.\n     */\n    virtual void trackEgressBodyOffset(uint64_t /* bodyOffset */,\n                                       ByteEvent::EventFlags /*flags*/) {\n      LOG(FATAL) << __func__ << \" not supported\";\n      folly::assume_unreachable();\n    }\n\n    [[nodiscard]] virtual folly::Optional<HTTPTransaction::ConnectionToken>\n    getConnectionToken() const noexcept = 0;\n\n    [[nodiscard]] virtual bool serverEarlyResponseEnabled() const noexcept {\n      return false;\n    }\n  };\n\n  using TransportCallback = HTTPTransactionTransportCallback;\n\n  /**\n   * readBufLimit and sendWindow are only used if useFlowControl is\n   * true. Furthermore, if flow control is enabled, no guarantees can be\n   * made on the borders of the L7 chunking/data frames of the outbound\n   * messages.\n   *\n   * priority is not used by HTTP/1.1. The -1 default makes sure that all\n   * plain HTTP transactions land up in the same queue as the control data.\n   */\n  HTTPTransaction(\n      TransportDirection direction,\n      HTTPCodec::StreamID id,\n      uint32_t seqNo,\n      Transport& transport,\n      HTTP2PriorityQueueBase& egressQueue,\n      folly::HHWheelTimer* timer = nullptr,\n      const folly::Optional<std::chrono::milliseconds>& defaultIdleTimeout =\n          folly::Optional<std::chrono::milliseconds>(),\n      HTTPSessionStats* stats = nullptr,\n      bool useFlowControl = false,\n      uint32_t receiveInitialWindowSize = 0,\n      uint32_t sendInitialWindowSize = 0,\n      http2::PriorityUpdate = http2::DefaultPriority,\n      folly::Optional<HTTPCodec::StreamID> assocStreamId = HTTPCodec::NoStream,\n      bool setIngressTimeoutAfterEom = false);\n\n  ~HTTPTransaction() override;\n\n  void reset(bool useFlowControl,\n             uint32_t receiveInitialWindowSize,\n             uint32_t receiveStreamWindowSize,\n             uint32_t sendInitialWindowSize);\n\n  HTTPCodec::StreamID getID() const {\n    return id_;\n  }\n\n  uint32_t getSequenceNumber() const {\n    return seqNo_;\n  }\n\n  const Transport& getTransport() const {\n    return transport_;\n  }\n\n  Transport& getTransport() {\n    return transport_;\n  }\n\n  virtual void setHandler(Handler* handler) {\n    if (handler_ != handler) {\n      handlerEgressPaused_ = false;\n    }\n    handler_ = handler;\n    if (handler_) {\n      DestructorGuard dg(this);\n      handler_->setTransaction(this);\n      updateHandlerPauseState();\n    }\n  }\n\n  const Handler* getHandler() const {\n    return handler_;\n  }\n\n  Handler* getHandler() {\n    return handler_;\n  }\n\n  http2::PriorityUpdate getPriority() const {\n    return priority_;\n  }\n\n  std::tuple<uint64_t, uint64_t, double> getPrioritySummary() const {\n    return std::make_tuple(\n        insertDepth_,\n        currentDepth_,\n        egressCalls_ > 0 ? cumulativeRatio_ / static_cast<double>(egressCalls_)\n                         : 0);\n  }\n\n  folly::Optional<HTTPPriority> getHTTPPriority() const {\n    return transport_.getHTTPPriority();\n  }\n\n  bool getPriorityFallback() const {\n    return priorityFallback_;\n  }\n\n  HTTPTransactionEgressSM::State getEgressState() const {\n    return egressState_;\n  }\n\n  HTTPTransactionIngressSM::State getIngressState() const {\n    return ingressState_;\n  }\n\n  bool isUpstream() const {\n    return proxygen::isUpstream(direction_);\n  }\n\n  bool isDownstream() const {\n    return proxygen::isDownstream(direction_);\n  }\n\n  void getLocalAddress(folly::SocketAddress& addr) const {\n    addr = transport_.getLocalAddress();\n  }\n\n  void getPeerAddress(folly::SocketAddress& addr) const {\n    addr = transport_.getPeerAddress();\n  }\n\n  const folly::SocketAddress& getLocalAddress() const noexcept {\n    return transport_.getLocalAddress();\n  }\n\n  const folly::SocketAddress& getPeerAddress() const noexcept {\n    return transport_.getPeerAddress();\n  }\n\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept {\n    return transport_.getSetupTransportInfo();\n  }\n\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const {\n    transport_.getCurrentTransportInfo(tinfo);\n  }\n\n  void getCurrentFlowControlInfo(FlowControlInfo* info) const {\n    transport_.getFlowControlInfo(info);\n    info->streamSendWindow_ = sendWindow_.getCapacity();\n    info->streamSendOutstanding_ = sendWindow_.getOutstanding();\n    info->streamRecvWindow_ = recvWindow_.getCapacity();\n    info->streamRecvOutstanding_ = recvWindow_.getOutstanding();\n  }\n\n  HTTPSessionStats* getSessionStats() const {\n    return stats_;\n  }\n\n  /**\n   * Check whether more response is expected. One or more 1xx status\n   * responses can be received prior to the regular response.\n   * Note: 101 is handled by the codec using a separate onUpgrade callback\n   */\n  virtual bool extraResponseExpected() const {\n    return (lastResponseStatus_ >= 100 && lastResponseStatus_ < 200) &&\n           lastResponseStatus_ != 101;\n  }\n\n  /**\n   * Change the size of the receive window and propagate the change to the\n   * remote end using a window update.\n   *\n   * TODO: when HTTPSession sends a SETTINGS frame indicating a\n   * different initial window, it should call this function on all its\n   * transactions.\n   */\n  virtual void setReceiveWindow(uint32_t capacity);\n\n  /**\n   * Get the receive window of the transaction\n   */\n  virtual const Window& getReceiveWindow() const {\n    return recvWindow_;\n  }\n\n  uint32_t getMaxDeferredSize() {\n    return maxDeferredIngress_;\n  }\n\n  /**\n   * Invoked by the session when the ingress headers are complete\n   */\n  void onIngressHeadersComplete(std::unique_ptr<HTTPMessage> msg);\n\n  /**\n   * Invoked by the session when some or all of the ingress entity-body has\n   * been parsed.\n   */\n  void onIngressBody(std::unique_ptr<folly::IOBuf> chain, uint16_t padding);\n\n  /**\n   * Invoked by the session when a chunk header has been parsed.\n   */\n  void onIngressChunkHeader(size_t length);\n\n  /**\n   * Invoked by the session when the CRLF terminating a chunk has been parsed.\n   */\n  void onIngressChunkComplete();\n\n  /**\n   * Invoked by the session when the ingress trailers have been parsed.\n   */\n  void onIngressTrailers(std::unique_ptr<HTTPHeaders> trailers);\n\n  /**\n   * Invoked by the session when the session and transaction need to be\n   * upgraded to a different protocol\n   */\n  void onIngressUpgrade(UpgradeProtocol protocol);\n\n  /**\n   * Invoked by the session when the ingress message is complete.\n   */\n  void onIngressEOM();\n\n  /**\n   * Invoked by the session when there is an error (e.g., invalid syntax,\n   * TCP RST) in either the ingress or egress stream. Note that this\n   * message is processed immediately even if this transaction normally\n   * would queue ingress.\n   *\n   * @param error Details for the error. This exception also has\n   * information about whether the error applies to the ingress, egress,\n   * or both directions of the transaction\n   */\n  void onError(const HTTPException& error);\n\n  /**\n   * Invoked by the session when a GOAWAY frame is received.\n   * TODO: we may consider exposing the additional debug data here in the\n   * future.\n   *\n   * @param code The error code received in the GOAWAY frame\n   */\n  void onGoaway(ErrorCode code);\n\n  /**\n   * Invoked by the session when there is a timeout on the ingress stream.\n   * Note that each transaction has its own timer but the session\n   * is the effective target of the timer.\n   */\n  void onIngressTimeout();\n\n  /**\n   * Invoked by the session when the remote endpoint of this transaction\n   * signals that it has consumed 'amount' bytes. This is only for\n   * versions of HTTP that support per transaction flow control.\n   */\n  void onIngressWindowUpdate(uint32_t amount);\n\n  /**\n   * Invoked by the session when the remote endpoint signals that we\n   * should change our send window. This is only for\n   * versions of HTTP that support per transaction flow control.\n   */\n  void onIngressSetSendWindow(uint32_t newWindowSize);\n\n  /**\n   * Notify this transaction that it is ok to egress.  Returns true if there\n   * is additional pending egress\n   */\n  bool onWriteReady(uint32_t maxEgress, double ratio);\n\n  /**\n   * Invoked by the session when there is a timeout on the egress stream.\n   */\n  void onEgressTimeout();\n\n  /**\n   * Invoked by the session when the first header byte is flushed.\n   */\n  void onEgressHeaderFirstByte();\n\n  /**\n   * Invoked by the session when the first byte is flushed.\n   */\n  void onEgressBodyFirstByte();\n\n  /**\n   * Invoked by the session when the last byte is flushed.\n   */\n  void onEgressBodyLastByte();\n\n  /**\n   * Invoked by the session when the tracked byte is flushed.\n   */\n  void onEgressTrackedByte();\n\n  /**\n   * Invoked when the ACK_LATENCY event is delivered\n   *\n   * @param latency the time between the moment when the last byte was sent\n   *        and the moment when we received the ACK from the client\n   */\n  void onEgressLastByteAck(std::chrono::milliseconds latency);\n\n  /**\n   * Invoked by the session when last egress headers have been acked by the\n   * peer.\n   */\n  void onLastEgressHeaderByteAcked();\n\n  /**\n   * Invoked by the session when egress body has been transmitted to the\n   * peer. Called for each sendBody() call if body bytes tracking is enabled.\n   */\n  void onEgressBodyBytesTx(uint64_t bodyOffset);\n\n  /**\n   * Invoked by the session when egress body has been acked by the\n   * peer. Called for each sendBody() call if body bytes tracking is enabled.\n   */\n  void onEgressBodyBytesAcked(uint64_t bodyOffset);\n\n  /**\n   * Invoked by the session when egress body tx or delivery has been cancelled\n   * by the peer.\n   */\n  void onEgressBodyDeliveryCanceled(uint64_t bodyOffset);\n\n  /**\n   * Invoked by the session when a tracked ByteEvent is transmitted by NIC.\n   */\n  void onEgressTrackedByteEventTX(const ByteEvent& event);\n\n  /**\n   * Invoked by the session when a tracked ByteEvent is ACKed by remote peer.\n   *\n   * LAST_BYTE events are processed by legacy functions.\n   */\n  void onEgressTrackedByteEventAck(const ByteEvent& event);\n\n  /**\n   * Invoked if the egress transport becomes app rate limited.\n   *\n   * TODO(bschlinker): Add support for QUIC.\n   */\n  void onEgressTransportAppRateLimited();\n\n  /**\n   * Can be called multiple times per transaction after onHeadersComplete and\n   * before detachTransaction()\n   *\n   * It does not obey pauseIngress/resumeIngress it is up to the handler\n   * to decide whether to buffer/drop datagrams\n   */\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept;\n\n  // Calls from Transport\n  WebTransportImpl::BidiStreamHandle onWebTransportBidiStream(\n      HTTPCodec::StreamID id);\n\n  WebTransportImpl::StreamReadHandle* onWebTransportUniStream(\n      HTTPCodec::StreamID id);\n\n  bool onWebTransportStopSending(HTTPCodec::StreamID id, uint32_t errorCode);\n\n  /**\n   * Invoked by the handlers that are interested in tracking\n   * performance stats.\n   */\n  virtual void setTransportCallback(TransportCallback* cb) {\n    transportCallback_ = cb;\n  }\n\n  /**\n   * @return true if ingress has started on this transaction.\n   */\n  bool isIngressStarted() const {\n    return ingressState_ != HTTPTransactionIngressSM::State::Start;\n  }\n\n  /**\n   * @return true iff the ingress EOM has been queued in HTTPTransaction\n   * but the handler has not yet been notified of this event.\n   */\n  bool isIngressEOMQueued() const {\n    return ingressState_ == HTTPTransactionIngressSM::State::EOMQueued;\n  }\n\n  /**\n   * @return true iff the handler has been notified of the ingress EOM.\n   */\n  bool isIngressComplete() const {\n    return ingressState_ == HTTPTransactionIngressSM::State::ReceivingDone;\n  }\n\n  /**\n   * @return true iff onIngressEOM() has been called.\n   */\n  bool isIngressEOMSeen() const {\n    return isIngressEOMQueued() || isIngressComplete();\n  }\n\n  /**\n   * @return true if egress has started on this transaction.\n   */\n  bool isEgressStarted() const {\n    return egressState_ != HTTPTransactionEgressSM::State::Start;\n  }\n\n  /**\n   * @return true iff sendEOM() has been called, but the eom has not been\n   * flushed to the socket yet.\n   */\n  bool isEgressEOMQueued() const {\n    return egressState_ == HTTPTransactionEgressSM::State::EOMQueued;\n  }\n\n  /**\n   * @return true iff the egress EOM has been flushed to the socket.\n   */\n  bool isEgressComplete() const {\n    return egressState_ == HTTPTransactionEgressSM::State::SendingDone;\n  }\n\n  /**\n   * @return true iff the remote side initiated this transaction.\n   */\n  bool isRemoteInitiated() const {\n    return (isDownstream() && id_ % 2 == 1) || (isUpstream() && id_ % 2 == 0);\n  }\n\n  /**\n   * @return true iff sendEOM() has been called.\n   */\n  bool isEgressEOMSeen() const {\n    return isEgressEOMQueued() || isEgressComplete();\n  }\n\n  /**\n   * @return true if we can send headers on this transaction\n   *\n   * Here's the logic:\n   *  1) In start state (TODO: egress sm needs final/non-final)\n   *  2) This is downstream AND state machine says sendHeaders is OK AND\n   *   2a) this downstream has not sent a response\n   *   2b) this downstream has only sent 1xx responses\n   */\n  virtual bool canSendHeaders() const {\n    return (egressState_ == HTTPTransactionEgressSM::State::Start) ||\n           (isDownstream() &&\n            HTTPTransactionEgressSM::canTransit(\n                egressState_, HTTPTransactionEgressSM::Event::sendHeaders) &&\n            (lastResponseStatus_ == 0 || extraResponseExpected()));\n  }\n\n  /**\n   * Send the egress message headers to the Transport. This method does\n   * not actually write the message out on the wire immediately. All\n   * writes happen at the end of the event loop at the earliest.\n   * Note: This method should be called once per message unless the first\n   * headers sent indicate a 1xx status.\n   *\n   * sendHeaders will not set EOM flag in header frame, whereas\n   * sendHeadersWithEOM will. sendHeadersWithOptionalEOM backs both of them.\n   *\n   * @param headers  Message headers\n   */\n  virtual void sendHeaders(const HTTPMessage& headers);\n  virtual void sendHeadersWithEOM(const HTTPMessage& headers);\n  virtual void sendHeadersWithOptionalEOM(const HTTPMessage& headers, bool eom);\n\n  /**\n   * Send part or all of the egress message body to the Transport. If flow\n   * control is enabled, the chunk boundaries may not be respected.\n   * This method does not actually write the message out on the wire\n   * immediately. All writes happen at the end of the event loop at the\n   * earliest.\n   * Note: This method may be called zero or more times per message.\n   *\n   * @param body Message body data; the Transport will take care of\n   *             applying any necessary protocol framing, such as\n   *             chunk headers.\n   */\n  virtual void sendBody(std::unique_ptr<folly::IOBuf> body);\n\n  /**\n   * Send padding bytes that the receiving application layer will ignore. This\n   * is currently implemented only for HTTP/2 and HTTP/3 and will do nothing on\n   * HTTP/1 connections.\n   *\n   * sendPadding() may be called at any time, even before headers and after EOM.\n   *\n   * @param bytes The number of bytes of padding to send on this transaction.\n   * The actual serialized size of the padding will be greater than this number\n   * by some O(1) amount, depending on the exact framing and later transport\n   *              encryption.\n   */\n  virtual void sendPadding(uint16_t bytes);\n\n  /**\n   * Returns the cumulative size of body passed to sendBody so far\n   */\n  size_t bodyBytesSent() const {\n    return actualResponseLength_.value_or(0);\n  }\n\n  /**\n   * Write any protocol framing required for the subsequent call(s)\n   * to sendBody(). This method does not actually write the message out on\n   * the wire immediately. All writes happen at the end of the event loop\n   * at the earliest.\n   * @param length  Length in bytes of the body data to follow.\n   */\n  virtual void sendChunkHeader(size_t length) {\n    if (!validateEgressStateTransition(\n            HTTPTransactionEgressSM::Event::sendChunkHeader)) {\n      return;\n    }\n    // TODO: move this logic down to session/codec\n    if (!transport_.getCodec().supportsParallelRequests()) {\n      chunkHeaders_.emplace_back(length);\n    }\n  }\n\n  /**\n   * Write any protocol syntax needed to terminate the data. This method\n   * does not actually write the message out on the wire immediately. All\n   * writes happen at the end of the event loop at the earliest.\n   * Frame begun by the last call to sendChunkHeader().\n   */\n  virtual void sendChunkTerminator() {\n    validateEgressStateTransition(\n        HTTPTransactionEgressSM::Event::sendChunkTerminator);\n  }\n\n  /**\n   * Send message trailers to the Transport. This method does\n   * not actually write the message out on the wire immediately. All\n   * writes happen at the end of the event loop at the earliest.\n   * Note: This method may be called at most once per message.\n   *\n   * @param trailers  Message trailers.\n   */\n  virtual void sendTrailers(const HTTPHeaders& trailers) {\n    if (!validateEgressStateTransition(\n            HTTPTransactionEgressSM::Event::sendTrailers)) {\n      return;\n    }\n    trailers_ = std::make_unique<HTTPHeaders>(trailers);\n  }\n\n  /**\n   * Finalize the egress message; depending on the protocol used\n   * by the Transport, this may involve sending an explicit \"end\n   * of message\" indicator. This method does not actually write the\n   * message out on the wire immediately. All writes happen at the end\n   * of the event loop at the earliest.\n   *\n   * If the ingress message also is complete, the transaction may\n   * detach itself from the Handler and Transport and delete itself\n   * as part of this method.\n   *\n   * Note: Either this method or sendAbort() should be called once\n   *       per message.\n   */\n  virtual void sendEOM();\n\n  /**\n   * Terminate the transaction. Depending on the underlying protocol, this\n   * may cause the connection to close or write egress bytes. This method\n   * does not actually write the message out on the wire immediately. All\n   * writes happen at the end of the event loop at the earliest.\n   *\n   * This function may also cause additional callbacks such as\n   * detachTransaction() to the handler either immediately or after it returns.\n   */\n  void sendAbort();\n\n  /**\n   * Identical to above, but user may supply an error code.\n   *\n   * Note:\n   * Downstream sessions invoking ::sendAbort(NO_ERROR) after egressing eom will\n   * queue the RST_STREAM/STOP_SENDING NO_ERROR (if the protocol supports such\n   * sematics). This is different from the typical behaviour of *immediately*\n   * terminating both ingress and egress (and therefore dropping any buffered\n   * data) with other ErrorCodes.\n   */\n  virtual void sendAbort(ErrorCode statusCode);\n\n  /**\n   * Pause ingress processing.  Upon pause, the HTTPTransaction\n   * will call its Transport's pauseIngress() method.  The Transport\n   * should make a best effort to stop invoking the HTTPTransaction's\n   * onIngress* callbacks.  If the Transport does invoke any of those\n   * methods while the transaction is paused, however, the transaction\n   * will queue the ingress events and data and delay delivery to the\n   * Handler until the transaction is unpaused.\n   */\n  virtual void pauseIngress();\n\n  /**\n   * Resume ingress processing. Only useful after a call to pauseIngress().\n   */\n  virtual void resumeIngress();\n\n  /**\n   * @return true iff ingress processing is paused for the handler\n   */\n  bool isIngressPaused() const {\n    return ingressPaused_;\n  }\n\n  /**\n   * Pause egress generation. HTTPTransaction may call its Handler's\n   * onEgressPaused() method if there is a state change as a result of\n   * this call.\n   *\n   * On receiving onEgressPaused(), the Handler should make a best effort\n   * to stop invoking the HTTPTransaction's egress generating methods.  If\n   * the Handler does invoke any of those methods while the transaction is\n   * paused, however, the transaction will forward them anyway, unless it\n   * is a body event. If flow control is enabled, body events will be\n   * buffered for later transmission when egress is unpaused.\n   */\n  void pauseEgress();\n\n  /**\n   * Resume egress generation. The Handler's onEgressResumed() will not be\n   * invoked if the HTTP/2 send window is full or there is too much\n   * buffered egress data on this transaction already. In that case,\n   * once the send window is not full or the buffer usage decreases, the\n   * handler will finally get onEgressResumed().\n   */\n  void resumeEgress();\n\n  /**\n   * Specify a rate limit for egressing bytes.\n   * The transaction will buffer extra bytes if doing so would cause it to go\n   * over the specified rate limit.  Setting to a value of 0 will cause no\n   * rate-limiting to occur.\n   */\n  void setEgressRateLimit(uint64_t bitsPerSecond);\n\n  /**\n   * @return true iff egress processing is paused for the handler\n   */\n  bool isEgressPaused() const {\n    return handlerEgressPaused_;\n  }\n\n  /**\n   * @return true iff egress processing is paused due to flow control\n   * to the handler\n   */\n  bool isFlowControlPaused() const {\n    return flowControlPaused_;\n  }\n\n  /**\n   * @return true iff this transaction can be used to push resources to\n   * the remote side.\n   */\n  bool supportsPushTransactions() const {\n    return isDownstream() && transport_.getCodec().supportsPushTransactions();\n  }\n\n  /**\n   * Create a new pushed transaction associated with this transaction,\n   * and assign the given handler and priority.\n   *\n   * @return the new transaction for the push, or nullptr if a new push\n   * transaction is impossible right now.\n   */\n  virtual HTTPTransaction* newPushedTransaction(\n      HTTPPushTransactionHandler* handler, ProxygenError* error = nullptr) {\n    if (isEgressEOMSeen()) {\n      SET_PROXYGEN_ERROR_IF(error,\n                            ProxygenError::kErrorEgressEOMSeenOnParentStream);\n      return nullptr;\n    }\n    auto txn = transport_.newPushedTransaction(id_, handler, error);\n    if (txn) {\n      pushedTransactions_.insert(txn->getID());\n    }\n    return txn;\n  }\n\n  /**\n   * Invoked by the session (upstream only) when a new pushed transaction\n   * arrives.  The txn's handler will be notified and is responsible for\n   * installing a handler.  If no handler is installed in the callback,\n   * the pushed transaction will be aborted.\n   */\n  bool onPushedTransaction(HTTPTransaction* txn);\n\n  /**\n   * True if this transaction is a server push transaction\n   */\n  bool isPushed() const {\n    return assocStreamId_.has_value();\n  }\n\n  /**\n   * Overrides the default idle timeout value.\n   */\n  void setIdleTimeout(std::chrono::milliseconds idleTimeout);\n\n  bool hasIdleTimeout() const {\n    return idleTimeout_.has_value();\n  }\n\n  /**\n   * Returns the associated transaction ID for pushed transactions, 0 otherwise\n   */\n  folly::Optional<HTTPCodec::StreamID> getAssocTxnId() const {\n    return assocStreamId_;\n  }\n\n  /**\n   * Get a set of server-pushed transactions associated with this transaction.\n   */\n  const std::set<HTTPCodec::StreamID>& getPushedTransactions() const {\n    return pushedTransactions_;\n  }\n\n  /**\n   * Remove the pushed txn ID from the set of pushed txns\n   * associated with this txn.\n   */\n  void removePushedTransaction(HTTPCodec::StreamID pushStreamId) {\n    pushedTransactions_.erase(pushStreamId);\n  }\n\n  /**\n   * Schedule or refresh the idle timeout for this transaction\n   */\n  void refreshTimeout() override {\n    // TODO(T121147568): Remove the zero-check after the experiment is complete.\n    if (timer_ && hasIdleTimeout() &&\n        idleTimeout_.value() != std::chrono::milliseconds::zero()) {\n      timer_->scheduleTimeout(this, idleTimeout_.value());\n    }\n  }\n\n  /**\n   * Tests if the first byte has already been sent, and if it\n   * hasn't yet then it marks it as sent.\n   */\n  bool testAndSetFirstByteSent() {\n    bool ret = firstByteSent_;\n    firstByteSent_ = true;\n    return ret;\n  }\n\n  bool testAndClearIsCountedTowardsStreamLimit() {\n    bool ret = isCountedTowardsStreamLimit_;\n    isCountedTowardsStreamLimit_ = false;\n    return ret;\n  }\n\n  void setIsCountedTowardsStreamLimit() {\n    isCountedTowardsStreamLimit_ = true;\n  }\n\n  /**\n   * Tests if the very first byte of Header has already been set.\n   * If it hasn't yet, it marks it as sent.\n   */\n  bool testAndSetFirstHeaderByteSent() {\n    bool ret = firstHeaderByteSent_;\n    firstHeaderByteSent_ = true;\n    return ret;\n  }\n\n  /**\n   * HTTPTransaction will not detach until it has 0 pending byte events.  If\n   * you call incrementPendingByteEvents, you must make a corresponding call\n   * to decrementPendingByteEvents or the transaction will never be destroyed.\n   */\n  void incrementPendingByteEvents() {\n    CHECK_LT(pendingByteEvents_,\n             std::numeric_limits<decltype(pendingByteEvents_)>::max());\n    pendingByteEvents_++;\n  }\n\n  void decrementPendingByteEvents() {\n    DestructorGuard dg(this);\n    CHECK_GT(pendingByteEvents_, 0);\n    pendingByteEvents_--;\n  }\n\n  uint64_t getNumPendingByteEvents() const {\n    return pendingByteEvents_;\n  }\n\n  /**\n   * Timeout callback for this transaction.  The timer is active\n   * until the ingress message is complete or terminated by error.\n   */\n  void timeoutExpired() noexcept override {\n    transport_.transactionTimeout(this);\n  }\n\n  /**\n   * Override callback cancellation to no-op. This will trigger on destruction\n   * of the timer.\n   */\n  void callbackCanceled() noexcept override {\n  }\n\n  /**\n   * Write a description of the transaction to a stream\n   */\n  void describe(std::ostream& os) const;\n\n  /**\n   * Change the priority of this transaction, may generate a PRIORITY frame.\n   */\n  virtual void updateAndSendPriority(HTTPPriority pri);\n\n  /**\n   * Add a callback waiting for this transaction to have a transport with\n   * replay protection.\n   */\n  virtual void addWaitingForReplaySafety(\n      folly::AsyncTransport::ReplaySafetyCallback* callback) {\n    transport_.addWaitingForReplaySafety(callback);\n  }\n\n  /**\n   * Remove a callback waiting for replay protection (if it was canceled).\n   */\n  virtual void removeWaitingForReplaySafety(\n      folly::AsyncTransport::ReplaySafetyCallback* callback) {\n    transport_.removeWaitingForReplaySafety(callback);\n  }\n\n  virtual bool needToBlockForReplaySafety() const {\n    return transport_.needToBlockForReplaySafety();\n  }\n\n  int32_t getRecvToAck() const;\n\n  void checkIfEgressRateLimitedByUpstream();\n\n  const CompressionInfo& getCompressionInfo() const;\n\n  bool hasPendingBody() const {\n    return getOutstandingEgressBodyBytes() > 0;\n  }\n\n  size_t getOutstandingEgressBodyBytes() const {\n    return deferredEgressBody_.chainLength();\n  }\n\n  void setLastByteFlushedTrackingEnabled(bool enabled) {\n    enableLastByteFlushedTracking_ = enabled;\n  }\n\n  // Use this API to track TX or Ack for a particular offset of the HTTP body,\n  // if the underlying transport is capable of tracking.\n  // It will generate a callback to HTTPTransactionTransportCallback either\n  // trackedByteEventTx or trackedByteEventAck.  The the event does not happen,\n  // there is no cancellation callback.\n  //\n  // You can call this API before you have egressed the given bodyOffset.\n  // Last byte ack is already tracked implicity and delivered via lastByteAcked.\n  bool trackEgressBodyOffset(\n      uint64_t bodyOffset,\n      ByteEvent::EventFlags flags = ByteEvent::EventFlags::ACK);\n\n  uint16_t getDatagramSizeLimit() const noexcept;\n  virtual bool sendDatagram(std::unique_ptr<folly::IOBuf> datagram);\n\n  folly::Optional<ConnectionToken> getConnectionToken() const noexcept;\n\n  bool isWebTransportConnectStream() {\n    return transport_.supportsWebTransport() && wtConnectStream_;\n  }\n\n  bool isConnectUdpStream() const {\n    return connectUdpStream_;\n  }\n\n  WebTransportImpl* getWebTransport() {\n    if (!isWebTransportConnectStream()) {\n      return nullptr;\n    }\n    if (!webTransportImpl_) {\n      CHECK(wtTransportProvider_);\n      webTransportImpl_ =\n          std::make_unique<WebTransportImpl>(*wtTransportProvider_, *this);\n    }\n    return webTransportImpl_.get();\n  }\n\n  void setWTTransportProvider(WebTransportImpl::TransportProvider* tp) {\n    wtTransportProvider_ = tp;\n  }\n\n  WebTransportImpl::TransportProvider* getWTTransportProvider() const {\n    return wtTransportProvider_;\n  }\n\n  static void setEgressBufferLimit(uint64_t limit) {\n    egressBufferLimit_ = limit;\n  }\n\n  bool addObserver(HTTPTransactionObserverContainer::Observer* observer) {\n    txnObserverContainer_.addObserver(observer);\n    return true;\n  }\n\n  bool addObserver(\n      std::shared_ptr<HTTPTransactionObserverContainer::Observer> observer) {\n    txnObserverContainer_.addObserver(std::move(observer));\n    return true;\n  }\n\n  bool removeObserver(HTTPTransactionObserverContainer::Observer* observer) {\n    txnObserverContainer_.removeObserver(observer);\n    return true;\n  }\n\n  bool removeObserver(\n      std::shared_ptr<HTTPTransactionObserverContainer::Observer> observer) {\n    txnObserverContainer_.removeObserver(std::move(observer));\n    return true;\n  }\n\n  HTTPTransactionObserverAccessor* getObserverAccessor() {\n    return &txnObserverAccessor_;\n  }\n\n private:\n  HTTPTransaction(const HTTPTransaction&) = delete;\n  HTTPTransaction& operator=(const HTTPTransaction&) = delete;\n\n  void dequeue() {\n    CHECK(isEnqueued());\n    egressQueue_.clearPendingEgress(queueHandle_);\n  }\n\n  void abortAndDeliverError(ErrorCode codecErorr, const std::string& msg);\n\n  void onDelayedDestroy(bool delayed) override;\n\n  /**\n   * Invokes the handler's onEgressPaused/Resumed if the handler's pause\n   * state needs updating\n   */\n  void updateHandlerPauseState();\n\n  /**\n   * Update the CompressionInfo (tableInfo_) struct\n   */\n  void updateEgressCompressionInfo(const CompressionInfo&);\n\n  void updateIngressCompressionInfo(const CompressionInfo&);\n\n  bool mustQueueIngress() const;\n\n  /**\n   * Check if deferredIngress_ points to some queue before pushing HTTPEvent\n   * to it.\n   */\n  void checkCreateDeferredIngress();\n  // Implementation of sending an abort for this transaction.\n  size_t sendAbortImpl(ErrorCode statusCode);\n\n  // Internal implementations of the ingress-related callbacks\n  // that work whether the ingress events are immediate or deferred.\n  void processIngressHeadersComplete(std::unique_ptr<HTTPMessage> msg);\n  void processIngressBody(std::unique_ptr<folly::IOBuf> chain, size_t len);\n  void processIngressChunkHeader(size_t length);\n  void processIngressChunkComplete();\n  void processIngressTrailers(std::unique_ptr<HTTPHeaders> trailers);\n  void processIngressUpgrade(UpgradeProtocol protocol);\n  void processIngressEOM();\n  void processIngressError(const HTTPException& ex);\n\n  void sendBodyFlowControlled(std::unique_ptr<folly::IOBuf> body = nullptr);\n  size_t sendBodyNow(std::unique_ptr<folly::IOBuf> body,\n                     size_t bodyLen,\n                     bool eom);\n  size_t sendEOMNow();\n  void onDeltaSendWindowSize(int32_t windowDelta);\n\n  void notifyTransportPendingEgress();\n\n  size_t sendDeferredBody(uint32_t maxEgress);\n\n  bool maybeDelayForRateLimit();\n\n  bool isEnqueued() const {\n    return queueHandle_ ? queueHandle_->isEnqueued() : false;\n  }\n\n  // Whther the txn has a pending EOM that can be send out (i.e., no more body\n  // bytes need to go before it.)\n  bool hasPendingEOM() const {\n    return isEgressEOMQueued() && getOutstandingEgressBodyBytes() == 0;\n  }\n\n  bool isExpectingIngress() const;\n\n  bool isExpectingWindowUpdate() const;\n\n  void updateReadTimeout();\n\n  /**\n   * Causes isIngressComplete() to return true, removes any queued\n   * ingress, and cancels the read timeout.\n   */\n  void markIngressComplete();\n\n  /**\n   * Causes isEgressComplete() to return true, removes any queued egress,\n   * and cancels the write timeout.\n   */\n  void markEgressComplete();\n\n  /**\n   * Validates the ingress state transition. Returns false and sends an\n   * abort with INTERNAL_ERROR if the transition fails. Otherwise it\n   * returns true.\n   */\n  bool validateIngressStateTransition(HTTPTransactionIngressSM::Event);\n\n  /**\n   * Validates the egress state transition.\n   *\n   * If the transition fails, it will invoke onInvariantViolation, and the\n   * default implementation is to CHECK/crash.  If you have a custom\n   * onInvariantViolation handler, this function can return false.\n   */\n  bool validateEgressStateTransition(HTTPTransactionEgressSM::Event);\n\n  void invariantViolation(HTTPException ex);\n\n  /**\n   * Flushes any pending window updates.  This can happen from setReceiveWindow\n   * or sendHeaders depending on transaction state.\n   */\n  void flushWindowUpdate();\n\n  bool updateContentLengthRemaining(size_t len);\n\n  void rateLimitTimeoutExpired();\n\n  // If deferredNoError_ is set to true, it invokes ::sendAbort() w/ NO_ERROR\n  // and returns the number of bytes written to the transport\n  size_t maybeSendDeferredNoError();\n\n  // Whether the stream has been upgraded to some other protocol\n  bool isUpgraded() const {\n    return upgraded_ || wtConnectStream_;\n  }\n\n  // Whether we have bytes event observers\n  bool hasBytesEventObservers();\n\n  // Emit bytes event to all observers\n  void emitBytesEvent(\n      const HTTPTransactionObserverInterface::TxnBytesEvent& event);\n\n  class RateLimitCallback : public folly::HHWheelTimer::Callback {\n   public:\n    explicit RateLimitCallback(HTTPTransaction& txn) : txn_(txn) {\n    }\n\n    void timeoutExpired() noexcept override {\n      txn_.rateLimitTimeoutExpired();\n    }\n    void callbackCanceled() noexcept override {\n      // no op\n    }\n\n   private:\n    HTTPTransaction& txn_;\n  };\n\n  class ObserverAccessor : public HTTPTransactionObserverAccessor {\n   public:\n    explicit ObserverAccessor(HTTPTransaction* txn) : txn_(txn) {\n      (void)txn_; // silence unused variable warning\n    }\n    ~ObserverAccessor() override = default;\n\n    bool addObserver(\n        HTTPTransactionObserverContainer::Observer* observer) override {\n      return txn_->addObserver(observer);\n    }\n\n    bool addObserver(std::shared_ptr<HTTPTransactionObserverContainer::Observer>\n                         observer) override {\n      return txn_->addObserver(std::move(observer));\n    }\n\n    [[nodiscard]] uint64_t getTxnId() const override {\n      return txn_->getID();\n    }\n\n   private:\n    HTTPTransaction* txn_;\n  };\n\n  RateLimitCallback rateLimitCallback_{*this};\n\n  /**\n   * Queue to hold any events that we receive from the Transaction\n   * while the ingress is supposed to be paused.\n   */\n  std::unique_ptr<std::queue<HTTPEvent>> deferredIngress_;\n\n  /**\n   * Queue to hold any body bytes to be sent out\n   * while egress to the remote is supposed to be paused.\n   */\n  folly::IOBufQueue deferredEgressBody_{folly::IOBufQueue::cacheChainLength()};\n\n  const TransportDirection direction_;\n  HTTPTransactionEgressSM::State egressState_{\n      HTTPTransactionEgressSM::getNewInstance()};\n  HTTPTransactionIngressSM::State ingressState_{\n      HTTPTransactionIngressSM::getNewInstance()};\n  /**\n   * bytes we need to acknowledge to the remote end using a window update\n   */\n  int32_t recvToAck_{0};\n\n  HTTPCodec::StreamID id_;\n  uint32_t seqNo_;\n  uint32_t maxDeferredIngress_{0};\n  Handler* handler_{nullptr};\n  Transport& transport_;\n  WebTransportImpl::TransportProvider* wtTransportProvider_;\n\n  HTTPSessionStats* stats_{nullptr};\n\n  CompressionInfo tableInfo_;\n\n  /**\n   * The recv window and associated data. This keeps track of how many\n   * bytes we are allowed to buffer.\n   */\n  Window recvWindow_;\n\n  /**\n   * The send window and associated data. This keeps track of how many\n   * bytes we are allowed to send and have outstanding.\n   */\n  Window sendWindow_;\n\n  TransportCallback* transportCallback_{nullptr};\n\n  /**\n   * Trailers to send, if any.\n   */\n  std::unique_ptr<HTTPHeaders> trailers_;\n\n  struct Chunk {\n    explicit Chunk(size_t inLength) : length(inLength), headerSent(false) {\n    }\n    size_t length;\n    bool headerSent;\n  };\n  std::list<Chunk> chunkHeaders_;\n\n  /**\n   * Reference to our priority queue\n   */\n  HTTP2PriorityQueueBase& egressQueue_;\n\n  /**\n   * Handle to our position in the priority queue.\n   */\n  HTTP2PriorityQueueBase::Handle queueHandle_{nullptr};\n\n  /**\n   * ID of request transaction (for pushed txns only)\n   */\n  folly::Optional<HTTPCodec::StreamID> assocStreamId_;\n\n  /**\n   * Set of all push transactions IDs associated with this transaction.\n   */\n  std::set<HTTPCodec::StreamID> pushedTransactions_;\n\n  /**\n   * Priority of this transaction\n   */\n  http2::PriorityUpdate priority_;\n\n  /**\n   * Information about this transaction's priority.\n   *\n   * insertDepth_ is the depth of this node in the tree when the txn was created\n   * currentDepth_ is the depth of this node in the tree after the last\n   *               onPriorityUpdate. It may not reflect its real position in\n   *               realtime, since after the last onPriorityUpdate, it may get\n   *               reparented as parent transactions complete.\n   * cumulativeRatio_ / egressCalls_ is the average relative weight of this\n   *                                 txn during egress\n   */\n  uint64_t insertDepth_{0};\n  uint64_t currentDepth_{0};\n  double cumulativeRatio_{0};\n  uint64_t egressCalls_{0};\n\n  uint64_t pendingByteEvents_{0};\n  folly::Optional<uint64_t> expectedIngressContentLength_;\n  folly::Optional<uint64_t> expectedIngressContentLengthRemaining_;\n  folly::Optional<uint64_t> expectedResponseLength_;\n  folly::Optional<uint64_t> actualResponseLength_{0};\n  uint64_t bodyBytesEgressed_{0};\n  std::map<uint64_t, ByteEvent::EventFlags> egressBodyOffsetsToTrack_;\n\n  bool ingressPaused_ : 1;\n  bool egressPaused_ : 1;\n  bool flowControlPaused_ : 1;\n  bool handlerEgressPaused_ : 1;\n  bool egressRateLimited_ : 1;\n  bool useFlowControl_ : 1;\n  bool aborted_ : 1;\n  bool deleting_ : 1;\n  bool firstByteSent_ : 1;\n  bool firstHeaderByteSent_ : 1;\n  bool inResume_ : 1;\n  bool isCountedTowardsStreamLimit_ : 1;\n  bool ingressErrorSeen_ : 1;\n  bool priorityFallback_ : 1;\n  bool headRequest_ : 1;\n  bool enableLastByteFlushedTracking_ : 1;\n  bool wtConnectStream_ : 1;\n  bool connectUdpStream_ : 1 = false;\n\n  // Prevents the application from calling skipBodyTo() before egress\n  // headers have been delivered.\n  bool egressHeadersDelivered_ : 1;\n\n  // Whether the HTTPTransaction has sent out a 1xx response HTTPMessage.\n  bool has1xxResponse_ : 1;\n\n  /**\n   * If set, the transaction will flush a RST_STREAM/NO_ERROR (h2) or\n   * STOP_SENDING/NO_ERROR (h3) after eom. This is only set if ::abort() is\n   * invoked with ErrorCode::NO_ERROR on a downstream transaction after eom has\n   * been queued.\n   */\n  bool deferredNoError_ : 1;\n\n  bool upgraded_ : 1;\n\n  /**\n   * If this transaction represents a request (ie, it is backed by an\n   * HTTPUpstreamSession) , this field indicates the last response status\n   * received from the server. If this transaction represents a response,\n   * this field indicates the last status we've sent. For instances, this\n   * could take on multiple 1xx values, and then take on 200.\n   */\n  uint16_t lastResponseStatus_{0};\n\n  // Maximum size of egress buffer before invoking onEgressPaused\n  static uint64_t egressBufferLimit_;\n\n  uint64_t egressLimitBytesPerMs_{0};\n  proxygen::TimePoint startRateLimit_;\n  uint64_t numLimitedBytesEgressed_{0};\n\n  folly::Optional<std::chrono::milliseconds> idleTimeout_;\n\n  folly::HHWheelTimer* timer_;\n\n  // Keeps track for body offset processed so far.\n  uint64_t ingressBodyOffset_{0};\n\n  bool setIngressTimeoutAfterEom_{false};\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> closeSession(\n      folly::Optional<uint32_t> error) override {\n    if (error.has_value()) {\n      sendCloseWebTransportSessionCapsule(error.value(), \"\");\n    }\n    webTransportImpl_->terminateSession(error);\n\n    if (!isEgressEOMSeen()) {\n      sendEOM();\n    }\n\n    return folly::unit;\n  }\n\n  void sendCloseWebTransportSessionCapsule(uint32_t errorCode,\n                                           const std::string& errorMessage);\n\n  std::unique_ptr<WebTransportImpl> webTransportImpl_;\n\n  ObserverAccessor txnObserverAccessor_;\n\n  // Container of observers for an HTTP transaction.\n  //\n  // This member MUST be last in the list of members to ensure it is destroyed\n  // first, before any other members are destroyed. This ensures that observers\n  // can inspect any session state available through public methods\n  // when destruction of the session begins.\n  HTTPTransactionObserverContainer txnObserverContainer_;\n};\n\n/**\n * Write a description of an HTTPTransaction to an ostream\n */\nstd::ostream& operator<<(std::ostream& os, const HTTPTransaction& txn);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransactionEgressSM.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPTransactionEgressSM.h>\n\n#include <folly/Indestructible.h>\n\nnamespace proxygen {\n\nstd::pair<HTTPTransactionEgressSMData::State, bool>\nHTTPTransactionEgressSMData::find(HTTPTransactionEgressSMData::State s,\n                                  HTTPTransactionEgressSMData::Event e) {\n  using State = HTTPTransactionEgressSMData::State;\n  using Event = HTTPTransactionEgressSMData::Event;\n\n  //             +--> ChunkHeaderSent -> ChunkBodySent\n  //             |      ^                    v\n  //             |      |   ChunkTerminatorSent -> TrailersSent\n  //             |      |__________|        |          |\n  //             |                          |          v\n  // Start -> HeadersSent                   +----> EOMQueued --> SendingDone\n  //             |                                     ^\n  //             +------------> RegularBodySent -------+\n\n  static const folly::Indestructible<TransitionTable<State, Event>> transitions{\n      TransitionTable<State, Event>{\n          static_cast<uint64_t>(State::NumStates),\n          static_cast<uint64_t>(Event::NumEvents),\n          {{{State::Start, Event::sendHeaders}, State::HeadersSent},\n\n           // For HTTP sending 100 response, then a regular response\n           {{State::HeadersSent, Event::sendHeaders}, State::HeadersSent},\n\n           {{State::HeadersSent, Event::sendBody}, State::RegularBodySent},\n           {{State::HeadersSent, Event::sendTrailers}, State::TrailersSent},\n           {{State::HeadersSent, Event::sendChunkHeader},\n            State::ChunkHeaderSent},\n           {{State::HeadersSent, Event::sendEOM}, State::EOMQueued},\n\n           {{State::RegularBodySent, Event::sendBody}, State::RegularBodySent},\n           {{State::RegularBodySent, Event::sendTrailers}, State::TrailersSent},\n           {{State::RegularBodySent, Event::sendEOM}, State::EOMQueued},\n\n           {{State::ChunkHeaderSent, Event::sendBody}, State::ChunkBodySent},\n\n           {{State::ChunkBodySent, Event::sendBody}, State::ChunkBodySent},\n           {{State::ChunkBodySent, Event::sendChunkTerminator},\n            State::ChunkTerminatorSent},\n\n           {{State::ChunkTerminatorSent, Event::sendChunkHeader},\n            State::ChunkHeaderSent},\n           {{State::ChunkTerminatorSent, Event::sendTrailers},\n            State::TrailersSent},\n           {{State::ChunkTerminatorSent, Event::sendEOM}, State::EOMQueued},\n\n           {{State::TrailersSent, Event::sendEOM}, State::EOMQueued},\n\n           {{State::HeadersSent, Event::sendDatagram}, State::DatagramSent},\n           {{State::DatagramSent, Event::sendDatagram}, State::DatagramSent},\n           {{State::DatagramSent, Event::sendTrailers}, State::TrailersSent},\n           {{State::DatagramSent, Event::sendEOM}, State::EOMQueued},\n\n           {{State::EOMQueued, Event::eomFlushed}, State::SendingDone}}}};\n\n  return transitions->find(s, e);\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionEgressSMData::State s) {\n  switch (s) {\n    case HTTPTransactionEgressSMData::State::Start:\n      os << \"Start\";\n      break;\n    case HTTPTransactionEgressSMData::State::HeadersSent:\n      os << \"HeadersSent\";\n      break;\n    case HTTPTransactionEgressSMData::State::DatagramSent:\n      os << \"DatagramSent\";\n      break;\n    case HTTPTransactionEgressSMData::State::RegularBodySent:\n      os << \"RegularBodySent\";\n      break;\n    case HTTPTransactionEgressSMData::State::ChunkHeaderSent:\n      os << \"ChunkHeaderSent\";\n      break;\n    case HTTPTransactionEgressSMData::State::ChunkBodySent:\n      os << \"ChunkBodySent\";\n      break;\n    case HTTPTransactionEgressSMData::State::ChunkTerminatorSent:\n      os << \"ChunkTerminatorSent\";\n      break;\n    case HTTPTransactionEgressSMData::State::TrailersSent:\n      os << \"TrailersSent\";\n      break;\n    case HTTPTransactionEgressSMData::State::EOMQueued:\n      os << \"EOMQueued\";\n      break;\n    case HTTPTransactionEgressSMData::State::SendingDone:\n      os << \"SendingDone\";\n      break;\n    case HTTPTransactionEgressSMData::State::NumStates:\n      CHECK(false) << \"Bad state\";\n  }\n\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionEgressSMData::Event e) {\n  switch (e) {\n    case HTTPTransactionEgressSMData::Event::sendHeaders:\n      os << \"sendHeaders\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendDatagram:\n      os << \"sendDatagram\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendBody:\n      os << \"sendBody\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendChunkHeader:\n      os << \"sendChunkHeader\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendChunkTerminator:\n      os << \"sendChunkTerminator\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendTrailers:\n      os << \"sendTrailers\";\n      break;\n    case HTTPTransactionEgressSMData::Event::sendEOM:\n      os << \"sendEOM\";\n      break;\n    case HTTPTransactionEgressSMData::Event::eomFlushed:\n      os << \"eomFlushed\";\n      break;\n    case HTTPTransactionEgressSMData::Event::NumEvents:\n      CHECK(false) << \"Bad event\";\n  }\n\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransactionEgressSM.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <iosfwd>\n#include <proxygen/lib/utils/StateMachine.h>\n\nnamespace proxygen {\n\nclass HTTPTransactionEgressSMData {\n public:\n  enum class State : uint8_t {\n    Start,\n    HeadersSent,\n    DatagramSent,\n    RegularBodySent,\n    ChunkHeaderSent,\n    ChunkBodySent,\n    ChunkTerminatorSent,\n    TrailersSent,\n    EOMQueued,\n    SendingDone,\n\n    // Must be last\n    NumStates\n  };\n\n  enum class Event : uint8_t {\n    // API accessible transitions\n    sendHeaders,\n    sendDatagram,\n    sendBody,\n    sendChunkHeader,\n    sendChunkTerminator,\n    sendTrailers,\n    sendEOM,\n    // Internal state transitions\n    eomFlushed,\n\n    // Must be last\n    NumEvents\n  };\n\n  static State getInitialState() {\n    return State::Start;\n  }\n\n  static std::pair<State, bool> find(State s, Event e);\n\n  static const std::string getName() {\n    return \"HTTPTransactionEgress\";\n  }\n};\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionEgressSMData::State s);\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionEgressSMData::Event e);\n\nusing HTTPTransactionEgressSM = StateMachine<HTTPTransactionEgressSMData>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransactionIngressSM.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPTransactionIngressSM.h>\n\n#include <folly/Indestructible.h>\n\nnamespace proxygen {\n\n//             +--> ChunkHeaderReceived -> ChunkBodyReceived\n//             |        ^                     v\n//             |        |          ChunkCompleted -> TrailersReceived\n//             |        |_______________|     |      |\n//             |                              v      v\n// Start -> HeadersReceived ---------------> EOMQueued ---> ReceivingDone\n//             |  |                             ^  ^\n//             |  +-----> RegularBodyReceived --+  |\n//             |                                   |\n//             +---------> UpgradeComplete --------+\n\nstd::pair<HTTPTransactionIngressSMData::State, bool>\nHTTPTransactionIngressSMData::find(HTTPTransactionIngressSMData::State s,\n                                   HTTPTransactionIngressSMData::Event e) {\n  using State = HTTPTransactionIngressSMData::State;\n  using Event = HTTPTransactionIngressSMData::Event;\n\n  static const folly::Indestructible<TransitionTable<State, Event>> transitions{\n      TransitionTable<State, Event>{\n          static_cast<uint64_t>(State::NumStates),\n          static_cast<uint64_t>(Event::NumEvents),\n          {{{State::Start, Event::onFinalHeaders}, State::FinalHeadersReceived},\n\n           {{State::Start, Event::onNonFinalHeaders},\n            State::NonFinalHeadersReceived},\n           {{State::NonFinalHeadersReceived, Event::onNonFinalHeaders},\n            State::NonFinalHeadersReceived},\n           {{State::NonFinalHeadersReceived, Event::onFinalHeaders},\n            State::FinalHeadersReceived},\n           {{State::NonFinalHeadersReceived, Event::onUpgrade},\n            State::UpgradeComplete},\n\n           {{State::FinalHeadersReceived, Event::onBody},\n            State::RegularBodyReceived},\n           {{State::FinalHeadersReceived, Event::onDatagram},\n            State::FinalHeadersReceived},\n           {{State::FinalHeadersReceived, Event::onChunkHeader},\n            State::ChunkHeaderReceived},\n           // special case - 0 byte body with trailers\n           {{State::FinalHeadersReceived, Event::onTrailers},\n            State::TrailersReceived},\n           {{State::FinalHeadersReceived, Event::onUpgrade},\n            State::UpgradeComplete},\n           {{State::FinalHeadersReceived, Event::onEOM}, State::EOMQueued},\n\n           {{State::RegularBodyReceived, Event::onBody},\n            State::RegularBodyReceived},\n           {{State::RegularBodyReceived, Event::onDatagram},\n            State::RegularBodyReceived},\n           // HTTP2 supports trailers and doesn't handle body as chunked events\n           {{State::RegularBodyReceived, Event::onTrailers},\n            State::TrailersReceived},\n           {{State::RegularBodyReceived, Event::onEOM}, State::EOMQueued},\n\n           {{State::ChunkHeaderReceived, Event::onBody},\n            State::ChunkBodyReceived},\n\n           {{State::ChunkBodyReceived, Event::onBody},\n            State::ChunkBodyReceived},\n           {{State::ChunkBodyReceived, Event::onChunkComplete},\n            State::ChunkCompleted},\n\n           {{State::ChunkCompleted, Event::onChunkHeader},\n            State::ChunkHeaderReceived},\n           {{State::ChunkCompleted, Event::onTrailers},\n            State::TrailersReceived},\n           {{State::ChunkCompleted, Event::onEOM}, State::EOMQueued},\n\n           {{State::TrailersReceived, Event::onEOM}, State::EOMQueued},\n\n           {{State::UpgradeComplete, Event::onBody}, State::UpgradeComplete},\n           {{State::UpgradeComplete, Event::onEOM}, State::EOMQueued},\n\n           {{State::EOMQueued, Event::eomFlushed}, State::ReceivingDone}}}};\n\n  return transitions->find(s, e);\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionIngressSMData::State s) {\n  switch (s) {\n    case HTTPTransactionIngressSMData::State::Start:\n      os << \"Start\";\n      break;\n    case HTTPTransactionIngressSMData::State::NonFinalHeadersReceived:\n      os << \"NonFinalHeadersReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::FinalHeadersReceived:\n      os << \"FinalHeadersReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::RegularBodyReceived:\n      os << \"RegularBodyReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::ChunkHeaderReceived:\n      os << \"ChunkHeaderReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::ChunkBodyReceived:\n      os << \"ChunkBodyReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::ChunkCompleted:\n      os << \"ChunkCompleted\";\n      break;\n    case HTTPTransactionIngressSMData::State::TrailersReceived:\n      os << \"TrailersReceived\";\n      break;\n    case HTTPTransactionIngressSMData::State::UpgradeComplete:\n      os << \"UpgradeComplete\";\n      break;\n    case HTTPTransactionIngressSMData::State::EOMQueued:\n      os << \"EOMQueued\";\n      break;\n    case HTTPTransactionIngressSMData::State::ReceivingDone:\n      os << \"ReceivingDone\";\n      break;\n    case HTTPTransactionIngressSMData::State::NumStates:\n      CHECK(false) << \"Bad state\";\n  }\n\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionIngressSMData::Event e) {\n  switch (e) {\n    case HTTPTransactionIngressSMData::Event::onNonFinalHeaders:\n      os << \"onNonFinalHeaders\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onFinalHeaders:\n      os << \"onFinalHeaders\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onDatagram:\n      os << \"onDatagram\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onBody:\n      os << \"onBody\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onChunkHeader:\n      os << \"onChunkHeader\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onChunkComplete:\n      os << \"onChunkComplete\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onTrailers:\n      os << \"onTrailers\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onUpgrade:\n      os << \"onUpgrade\";\n      break;\n    case HTTPTransactionIngressSMData::Event::onEOM:\n      os << \"onEOM\";\n      break;\n    case HTTPTransactionIngressSMData::Event::eomFlushed:\n      os << \"eomFlushed\";\n      break;\n    case HTTPTransactionIngressSMData::Event::NumEvents:\n      CHECK(false) << \"Bad event\";\n  }\n\n  return os;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPTransactionIngressSM.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <iosfwd>\n#include <map>\n#include <proxygen/lib/utils/StateMachine.h>\n\nnamespace proxygen {\n\nclass HTTPTransactionIngressSMData {\n public:\n  enum class State : uint8_t {\n    Start,\n    NonFinalHeadersReceived,\n    FinalHeadersReceived,\n    RegularBodyReceived,\n    ChunkHeaderReceived,\n    ChunkBodyReceived,\n    ChunkCompleted,\n    TrailersReceived,\n    UpgradeComplete,\n    EOMQueued,\n    ReceivingDone,\n\n    // Must be last\n    NumStates\n  };\n\n  enum class Event : uint8_t {\n    // API accessible transitions\n    onNonFinalHeaders,\n    onFinalHeaders,\n    onDatagram,\n    onBody,\n    onChunkHeader,\n    onChunkComplete,\n    onTrailers,\n    onUpgrade,\n    onEOM,\n    // Internal state transitions\n    eomFlushed,\n\n    // Must be last\n    NumEvents\n  };\n\n  static State getInitialState() {\n    return State::Start;\n  }\n\n  static std::pair<State, bool> find(State s, Event e);\n\n  static const std::string getName() {\n    return \"HTTPTransactionIngress\";\n  }\n};\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionIngressSMData::State s);\n\nstd::ostream& operator<<(std::ostream& os,\n                         HTTPTransactionIngressSMData::Event e);\n\nusing HTTPTransactionIngressSM = StateMachine<HTTPTransactionIngressSMData>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPUpstreamSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\nnamespace proxygen {\n\nHTTPUpstreamSession::HTTPUpstreamSession(\n    const WheelTimerInstance& wheelTimer,\n    folly::AsyncTransport::UniquePtr&& sock,\n    const folly::SocketAddress& localAddr,\n    const folly::SocketAddress& peerAddr,\n    std::unique_ptr<HTTPCodec> codec,\n    const wangle::TransportInfo& tinfo,\n    InfoCallback* infoCallback)\n    : HTTPSession(wheelTimer,\n                  std::move(sock),\n                  localAddr,\n                  peerAddr,\n                  nullptr,\n                  std::move(codec),\n                  tinfo,\n                  infoCallback) {\n  if (sock_) {\n    auto asyncSocket = sock_->getUnderlyingTransport<folly::AsyncSocket>();\n    if (asyncSocket) {\n      asyncSocket->setBufferCallback(this);\n    }\n  }\n  CHECK_EQ(codec_->getTransportDirection(), TransportDirection::UPSTREAM);\n}\n\n// uses folly::HHWheelTimer instance which is used on client side & thrift\nHTTPUpstreamSession::HTTPUpstreamSession(\n    folly::HHWheelTimer* wheelTimer,\n    folly::AsyncTransport::UniquePtr&& sock,\n    const folly::SocketAddress& localAddr,\n    const folly::SocketAddress& peerAddr,\n    std::unique_ptr<HTTPCodec> codec,\n    const wangle::TransportInfo& tinfo,\n    InfoCallback* infoCallback)\n    : HTTPUpstreamSession(WheelTimerInstance(wheelTimer),\n                          std::move(sock),\n                          localAddr,\n                          peerAddr,\n                          std::move(codec),\n                          tinfo,\n                          infoCallback) {\n}\n\nHTTPUpstreamSession::~HTTPUpstreamSession() = default;\n\nbool HTTPUpstreamSession::isReplaySafe() const {\n  return sock_ ? sock_->isReplaySafe() : false;\n}\n\nbool HTTPUpstreamSession::isReusable() const {\n  VLOG(4) << \"isReusable: \" << *this\n          << \", liveTransactions_=\" << liveTransactions_\n          << \", isClosing()=\" << isClosing()\n          << \", sock_->connecting()=\" << sock_->connecting()\n          << \", codec_->isReusable()=\" << codec_->isReusable()\n          << \", codec_->isBusy()=\" << codec_->isBusy()\n          << \", numActiveWrites_=\" << numActiveWrites_\n          << \", writeTimeout_.isScheduled()=\" << writeTimeout_.isScheduled()\n          << \", ingressError_=\" << ingressError_\n          << \", hasMoreWrites()=\" << hasMoreWrites()\n          << \", codec_->supportsParallelRequests()=\"\n          << codec_->supportsParallelRequests();\n  return !isClosing() && !sock_->connecting() && codec_->isReusable() &&\n         !codec_->isBusy() && !ingressError_ &&\n         (codec_->supportsParallelRequests() ||\n          (\n              // These conditions only apply to serial codec sessions\n              !hasMoreWrites() && liveTransactions_ == 0 &&\n              !writeTimeout_.isScheduled()));\n}\n\nbool HTTPUpstreamSession::isClosing() const {\n  VLOG(5) << \"isClosing: \" << *this << \", sock_->good()=\" << sock_->good()\n          << \", draining_=\" << draining_\n          << \", readsShutdown()=\" << readsShutdown()\n          << \", writesShutdown()=\" << writesShutdown()\n          << \", writesDraining_=\" << writesDraining_\n          << \", resetAfterDrainingWrites_=\" << resetAfterDrainingWrites_;\n  return !sock_->good() || draining_ || readsShutdown() || writesShutdown() ||\n         writesDraining_ || resetAfterDrainingWrites_;\n}\n\nvoid HTTPUpstreamSession::startNow() {\n  // startNow in base class CHECKs this session has not started.\n  HTTPSession::startNow();\n}\n\nHTTPTransaction* HTTPUpstreamSession::newTransaction(\n    HTTPTransaction::Handler* handler) {\n  auto txn = newTransactionWithError(handler);\n  if (txn.hasError()) {\n    return nullptr;\n  }\n  return txn.value();\n}\n\nfolly::Expected<HTTPTransaction*, HTTPUpstreamSession::NewTransactionError>\nHTTPUpstreamSession::newTransactionWithError(\n    HTTPTransaction::Handler* handler) {\n  if (!supportsMoreTransactions()) {\n    // This session doesn't support any more parallel transactions\n    return folly::makeUnexpected<NewTransactionError>(\n        \"Number of HTTP outgoing transactions reaches limit in the session\");\n  } else if (draining_) {\n    return folly::makeUnexpected<NewTransactionError>(\"Connection is draining\");\n  }\n\n  if (!started_) {\n    startNow();\n  }\n\n  ProxygenError error = kErrorNone;\n  auto txn = createTransaction(codec_->createStream(),\n                               HTTPCodec::NoStream,\n                               http2::DefaultPriority,\n                               &error);\n\n  if (!txn) {\n    switch (error) {\n      case ProxygenError::kErrorBadSocket:\n        return folly::makeUnexpected<NewTransactionError>(\n            \"Socket connection is closing\");\n      case ProxygenError::kErrorDuplicatedStreamId:\n        return folly::makeUnexpected<NewTransactionError>(\n            \"HTTP Stream ID already exists\");\n      default:\n        return folly::makeUnexpected<NewTransactionError>(\n            \"Unknown error when creating HTTP transaction\");\n    }\n  }\n\n  DestructorGuard dg(this);\n  txn->setHandler(CHECK_NOTNULL(handler));\n  return txn;\n}\n\nHTTPTransaction::Handler* HTTPUpstreamSession::getTransactionTimeoutHandler(\n    HTTPTransaction* /*txn*/) {\n  // No special handler for upstream requests that time out\n  return nullptr;\n}\n\nbool HTTPUpstreamSession::allTransactionsStarted() const {\n  for (const auto& txn : transactions_) {\n    if (!txn.second.isPushed() && !txn.second.isEgressStarted()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid HTTPUpstreamSession::attachThreadLocals(\n    folly::EventBase* eventBase,\n    std::shared_ptr<const folly::SSLContext> sslContext,\n    const WheelTimerInstance& wheelTimer,\n    HTTPSessionStats* stats,\n    FilterIteratorFn fn,\n    HeaderCodec::Stats* headerCodecStats,\n    HTTPSessionController* controller) {\n  txnEgressQueue_.attachThreadLocals(wheelTimer);\n  if (rateLimitFilter_) {\n    rateLimitFilter_->attachThreadLocals(&eventBase->timer());\n  }\n  wheelTimer_ = wheelTimer;\n  setController(controller);\n  setSessionStats(stats);\n  if (sock_) {\n    sock_->attachEventBase(eventBase);\n    maybeAttachSSLContext(sslContext);\n  }\n  codec_.foreach (fn);\n  codec_->setHeaderCodecStats(headerCodecStats);\n  resumeReadsImpl();\n  rescheduleLoopCallbacks();\n}\n\nvoid HTTPUpstreamSession::maybeAttachSSLContext(\n    std::shared_ptr<const folly::SSLContext> sslContext) const {\n#ifndef NO_ASYNCSSLSOCKET\n  auto sslSocket = sock_->getUnderlyingTransport<folly::AsyncSSLSocket>();\n  if (sslSocket && sslContext) {\n    sslSocket->attachSSLContext(sslContext);\n  }\n#endif\n}\n\nvoid HTTPUpstreamSession::detachThreadLocals(bool detachSSLContext) {\n  CHECK(transactions_.empty());\n  cancelLoopCallbacks();\n  pauseReadsImpl();\n  if (sock_) {\n    if (detachSSLContext) {\n      maybeDetachSSLContext();\n    }\n    sock_->detachEventBase();\n  }\n  txnEgressQueue_.detachThreadLocals();\n  if (rateLimitFilter_) {\n    rateLimitFilter_->detachThreadLocals();\n  }\n  setController(nullptr);\n  setSessionStats(nullptr);\n  // The codec filters *shouldn't* be accessible while the socket is detached,\n  // I hope\n  codec_->setHeaderCodecStats(nullptr);\n  auto cm = getConnectionManager();\n  if (cm) {\n    cm->removeConnection(this);\n  }\n}\n\nvoid HTTPUpstreamSession::maybeDetachSSLContext() const {\n#ifndef NO_ASYNCSSLSOCKET\n  auto sslSocket = sock_->getUnderlyingTransport<folly::AsyncSSLSocket>();\n  if (sslSocket) {\n    sslSocket->detachSSLContext();\n  }\n#endif\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/HTTPUpstreamSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/SSLContext.h>\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n\nnamespace proxygen {\n\nclass HTTPSessionStats;\n\nclass HTTPUpstreamSession : public HTTPSession {\n  using NewTransactionError = std::string;\n\n public:\n  /**\n   * @param sock           An open socket on which any applicable TLS\n   *                         handshaking has been completed already.\n   * @param localAddr      Address and port of the local end of the socket.\n   * @param peerAddr       Address and port of the remote end of the socket.\n   * @param codec          A codec with which to parse/generate messages in\n   *                         whatever HTTP-like wire format this session needs.\n   */\n  HTTPUpstreamSession(const WheelTimerInstance& wheelTimer,\n                      folly::AsyncTransport::UniquePtr&& sock,\n                      const folly::SocketAddress& localAddr,\n                      const folly::SocketAddress& peerAddr,\n                      std::unique_ptr<HTTPCodec> codec,\n                      const wangle::TransportInfo& tinfo,\n                      InfoCallback* infoCallback);\n\n  // uses folly::HHWheelTimer instance which is used on client side & thrift\n  HTTPUpstreamSession(folly::HHWheelTimer* wheelTimer,\n                      folly::AsyncTransport::UniquePtr&& sock,\n                      const folly::SocketAddress& localAddr,\n                      const folly::SocketAddress& peerAddr,\n                      std::unique_ptr<HTTPCodec> codec,\n                      const wangle::TransportInfo& tinfo,\n                      InfoCallback* infoCallback);\n\n  using FilterIteratorFn = std::function<void(HTTPCodecFilter*)>;\n\n  void attachThreadLocals(folly::EventBase* eventBase,\n                          std::shared_ptr<const folly::SSLContext> sslContext,\n                          const WheelTimerInstance& wheelTimer,\n                          HTTPSessionStats* stats,\n                          FilterIteratorFn fn,\n                          HeaderCodec::Stats* headerCodecStats,\n                          HTTPSessionController* controller) override;\n\n  void detachThreadLocals(bool detachSSLContext = false) override;\n\n  void startNow() override;\n\n  /**\n   * Creates a new transaction on this upstream session. Invoking this function\n   * also has the side-affect of starting reads after this event loop completes.\n   *\n   * @param handler The request handler to attach to this transaction. It must\n   *                not be null.\n   */\n  HTTPTransaction* newTransaction(HTTPTransaction::Handler* handler) override;\n\n  /**\n   * Returns true if the underlying transport has completed full handshake.\n   */\n  bool isReplaySafe() const override;\n\n  /**\n   * Returns true if this session has no open transactions and the underlying\n   * transport can be used again in a new request.\n   */\n  bool isReusable() const override;\n\n  /**\n   * Returns true if the session is shutting down\n   */\n  bool isClosing() const override;\n\n  /**\n   * Drains the current transactions and prevents new transactions from being\n   * created on this session. When the number of transactions reaches zero, this\n   * session will shutdown the transport and delete itself.\n   */\n  void drain() override {\n    HTTPSession::drain();\n  }\n\n  folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n      uint8_t level) override {\n    if (!priorityAdapter_) {\n      return HTTPSession::getHTTPPriority(level);\n    }\n    return priorityAdapter_->getHTTPPriority(level);\n  }\n\n  folly::Expected<HTTPTransaction*, NewTransactionError>\n  newTransactionWithError(HTTPTransaction::Handler* handler);\n\n protected:\n  ~HTTPUpstreamSession() override;\n\n private:\n  /**\n   * Called by onHeadersComplete(). Currently a no-op for upstream.\n   */\n  void setupOnHeadersComplete(HTTPTransaction* /* txn */,\n                              HTTPMessage* /* msg */) override {\n  }\n\n  /**\n   * Called by transactionTimeout() if the transaction has no handler.\n   */\n  HTTPTransaction::Handler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn) override;\n\n  bool allTransactionsStarted() const override;\n\n  void maybeAttachSSLContext(\n      std::shared_ptr<const folly::SSLContext> sslContext) const;\n  void maybeDetachSSLContext() const;\n\n  std::unique_ptr<PriorityAdapter> priorityAdapter_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/QuicProtocolInfo.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <quic/api/QuicSocket.h>\n#include <quic/common/Optional.h>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\n\n/**\n * Session-level protocol info.\n */\nstruct QuicProtocolInfo : public wangle::ProtocolInfo {\n  QuicProtocolInfo& operator=(const QuicProtocolInfo&) = default;\n\n  ~QuicProtocolInfo() override = default;\n\n  quic::Optional<quic::ConnectionId> clientChosenDestConnectionId;\n  quic::Optional<quic::ConnectionId> clientConnectionId;\n  quic::Optional<quic::ConnectionId> serverConnectionId;\n  quic::Optional<quic::TransportSettings> transportSettings;\n  quic::Optional<std::string> fingerprint;\n  quic::Optional<quic::QuicVersion> quicVersion;\n\n  uint32_t ptoCount{0};\n  uint32_t totalPTOCount{0};\n  uint64_t totalTransportBytesSent{0};\n  uint64_t totalTransportBytesRecvd{0};\n  bool usedZeroRtt{false};\n};\n\ninline void initQuicProtocolInfo(QuicProtocolInfo& quicInfo,\n                                 const quic::QuicSocket& sock) {\n  quicInfo.clientChosenDestConnectionId =\n      sock.getClientChosenDestConnectionId();\n  quicInfo.clientConnectionId = sock.getClientConnectionId();\n  quicInfo.serverConnectionId = sock.getServerConnectionId();\n  quicInfo.quicVersion = sock.getState() ? sock.getState()->version\n                                         : quic::Optional<quic::QuicVersion>();\n}\n\ninline void updateQuicProtocolInfo(QuicProtocolInfo& quicInfo,\n                                   const quic::QuicSocket& sock) {\n  auto curQuicInfo = sock.getTransportInfo();\n  quicInfo.ptoCount = curQuicInfo.ptoCount;\n  quicInfo.totalPTOCount = curQuicInfo.totalPTOCount;\n  quicInfo.totalTransportBytesSent = curQuicInfo.bytesSent;\n  quicInfo.totalTransportBytesRecvd = curQuicInfo.bytesRecvd;\n  quicInfo.transportSettings = sock.getTransportSettings();\n  quicInfo.usedZeroRtt = curQuicInfo.usedZeroRtt;\n}\n\n/**\n *  Stream level protocol info. Contains all data from\n *  the sessinon info, plus stream-specific information.\n *  This structure is owned by each individual stream,\n *  and is updated when requested.\n *  If instance of HQ Transport Stream outlives the corresponding QUIC socket,\n *  has been destroyed, this structure will contain the last snapshot\n *  of the data received from the QUIC socket.\n *\n * Usage:\n *   TransportInfo tinfo;\n *   txn.getCurrentTransportInfo(&tinfo); // txn is the HTTP transaction object\n *   auto streamInfo = dynamic_cast<QuicStreamProtocolInfo>(tinfo.protocolInfo);\n *   if (streamInfo) {\n *      // stream level AND connection level info is available\n *   };\n *   auto connectionInfo = dynamic_cast<QuicProtocolInfo>(tinfo.protocolInfo);\n *   if (connectionInfo) {\n *     // ONLY connection level info is available. No stream level info.\n *   }\n *\n */\nstruct QuicStreamProtocolInfo : public QuicProtocolInfo {\n\n  // Slicing assignment operator to initialize the per-stream protocol info\n  // with the values of the per-session protocol info.\n  QuicStreamProtocolInfo& operator=(const QuicProtocolInfo& other) {\n    if (this != &other) {\n      *(static_cast<QuicProtocolInfo*>(this)) = other;\n    }\n    return *this;\n  }\n\n  quic::QuicSocket::StreamTransportInfo streamTransportInfo;\n  // NOTE: when the control stream latency stats will be reintroduced,\n  // collect it here.\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/SecondaryAuthManager.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/SecondaryAuthManager.h>\n\n#include <fizz/extensions/exportedauth/ExportedAuthenticator.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBufQueue.h>\nusing folly::io::QueueAppender;\n\nnamespace proxygen {\n\nSecondaryAuthManager::SecondaryAuthManager(\n    std::unique_ptr<fizz::SelfCert> cert) {\n  cert_ = std::move(cert);\n}\n\nSecondaryAuthManager::~SecondaryAuthManager() = default;\n\nstd::pair<uint16_t, std::unique_ptr<folly::IOBuf>>\nSecondaryAuthManager::createAuthRequest(\n    std::unique_ptr<folly::IOBuf> certRequestContext,\n    std::vector<fizz::Extension> extensions) {\n  // The certificate_request_context has to include the two octets Request-ID.\n  uint16_t requestId = requestIdCounter_++;\n  folly::IOBufQueue contextQueue{folly::IOBufQueue::cacheChainLength()};\n  auto contextLen =\n      sizeof(requestId) + certRequestContext->computeChainDataLength();\n  QueueAppender appender(&contextQueue, contextLen);\n  appender.writeBE<uint16_t>(requestId);\n  contextQueue.append(std::move(certRequestContext));\n  auto secureContext = contextQueue.move();\n  auto authRequest = fizz::ExportedAuthenticator::getAuthenticatorRequest(\n      std::move(secureContext), std::move(extensions));\n  auto authRequestClone = authRequest->clone();\n  outstandingRequests_.insert(\n      std::make_pair(requestId, std::move(authRequest)));\n  return std::make_pair(requestId, std::move(authRequestClone));\n}\n\nstd::pair<uint16_t, std::unique_ptr<folly::IOBuf>>\nSecondaryAuthManager::getAuthenticator(\n    const fizz::AsyncFizzBase& transport,\n    TransportDirection dir,\n    uint16_t requestId,\n    std::unique_ptr<folly::IOBuf> authRequest) {\n  uint16_t certId = certIdCounter_++;\n  std::unique_ptr<folly::IOBuf> authenticator;\n  if (dir == TransportDirection::UPSTREAM) {\n    authenticator = fizz::ExportedAuthenticator::getAuthenticator(\n        transport, fizz::Direction::UPSTREAM, *cert_, std::move(authRequest));\n  } else {\n    authenticator = fizz::ExportedAuthenticator::getAuthenticator(\n        transport, fizz::Direction::DOWNSTREAM, *cert_, std::move(authRequest));\n  }\n  requestCertMap_.insert(std::make_pair(requestId, certId));\n  return std::make_pair(certId, std::move(authenticator));\n}\n\nbool SecondaryAuthManager::validateAuthenticator(\n    const fizz::AsyncFizzBase& transport,\n    TransportDirection dir,\n    uint16_t certId,\n    std::unique_ptr<folly::IOBuf> authenticator) {\n  // Verify the certificate_request_context contains the Request-ID of a\n  // previously-sent \"CERTIFICATE_REQUEST\".\n  auto authClone = authenticator->clone();\n  auto authRequest = verifyContext(std::move(authClone));\n  if (!authRequest) {\n    return false;\n  }\n  // Validate the authenticator with regard to the authenticator request.\n  folly::Optional<std::vector<fizz::CertificateEntry>> certs;\n  if (dir == TransportDirection::UPSTREAM) {\n    certs = fizz::ExportedAuthenticator::validateAuthenticator(\n        transport,\n        fizz::Direction::DOWNSTREAM,\n        std::move(*authRequest),\n        std::move(authenticator));\n  } else {\n    certs = fizz::ExportedAuthenticator::validateAuthenticator(\n        transport,\n        fizz::Direction::UPSTREAM,\n        std::move(*authRequest),\n        std::move(authenticator));\n  }\n  if (!certs) {\n    return false;\n  } else if ((*certs).size() == 0) {\n    VLOG(4) << \"Peer does not have appropriate certificate or does not want to \"\n               \"provide one, empty authenticator received\";\n  } else {\n    receivedCerts_.insert(std::make_pair(certId, std::move(*certs)));\n  }\n  return true;\n}\n\nfolly::Optional<std::unique_ptr<folly::IOBuf>>\nSecondaryAuthManager::verifyContext(\n    std::unique_ptr<folly::IOBuf> authenticator) {\n  auto certRequestContext =\n      fizz::ExportedAuthenticator::getAuthenticatorContext(\n          std::move(authenticator));\n  folly::io::Cursor cursor(certRequestContext.get());\n  auto requestId = cursor.readBE<uint16_t>();\n  if (outstandingRequests_.find(requestId) == outstandingRequests_.end()) {\n    VLOG(4) << \"No previous CERTIFICATE_REQUEST matches the the CERTIFICATE \"\n               \"with Request-ID=\"\n            << requestId;\n    return folly::none;\n  }\n  auto authRequest = std::move(outstandingRequests_[requestId]);\n  return authRequest;\n}\n\nfolly::Optional<uint16_t> SecondaryAuthManager::getCertId(uint16_t requestId) {\n  if (requestCertMap_.find(requestId) == requestCertMap_.end()) {\n    return folly::none;\n  } else {\n    folly::Optional<uint16_t> certId = requestCertMap_[requestId];\n    return certId;\n  }\n}\n\nfolly::Optional<std::vector<fizz::CertificateEntry>>\nSecondaryAuthManager::getPeerCert(uint16_t certId) {\n  folly::Optional<std::vector<fizz::CertificateEntry>> certChain;\n  if (receivedCerts_.find(certId) == receivedCerts_.end()) {\n    return folly::none;\n  } else {\n    certChain = std::move(receivedCerts_[certId]);\n    return certChain;\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/SecondaryAuthManager.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <fizz/backend/openssl/certificate/OpenSSLPeerCertImpl.h>\n#include <fizz/backend/openssl/certificate/OpenSSLSelfCertImpl.h>\n#include <fizz/protocol/Certificate.h>\n#include <proxygen/lib/http/session/SecondaryAuthManagerBase.h>\n\nnamespace proxygen {\n\nclass SecondaryAuthManager : public SecondaryAuthManagerBase {\n public:\n  explicit SecondaryAuthManager(std::unique_ptr<fizz::SelfCert> cert);\n\n  SecondaryAuthManager() = default;\n\n  ~SecondaryAuthManager() override;\n\n  std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> createAuthRequest(\n      std::unique_ptr<folly::IOBuf> certRequestContext,\n      std::vector<fizz::Extension> extensions) override;\n\n  std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> getAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> authRequest) override;\n\n  bool validateAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t certId,\n      std::unique_ptr<folly::IOBuf> authenticator) override;\n\n  /**\n   * Retrieve a Cert-ID given the corresponding Request-ID.\n   */\n  folly::Optional<uint16_t> getCertId(uint16_t requestId);\n\n  /**\n   * Retrieve the peer certificate chain given the corresponding Cert-ID.\n   */\n  folly::Optional<std::vector<fizz::CertificateEntry>> getPeerCert(\n      uint16_t certId);\n\n private:\n  uint16_t requestIdCounter_{0};\n  uint16_t certIdCounter_{0};\n\n  /**\n   * Verify if the certificate_request_context of the authenticator contains\n   * a Request-ID of a previous CERTIFICATE_REQUEST.\n   * @param authenticator The received exported authenticator.\n   * @return The authenticator request if verification passes.\n   */\n  folly::Optional<std::unique_ptr<folly::IOBuf>> verifyContext(\n      std::unique_ptr<folly::IOBuf> authenticator);\n\n  // Locally cached authenticator requests, used for authenticator validation\n  // and the CERTIFICATE_NEEDED frame.\n  std::map<uint16_t, std::unique_ptr<folly::IOBuf>> outstandingRequests_;\n\n  // Secondary certificate possessed by the local endpoint.\n  std::unique_ptr<fizz::SelfCert> cert_;\n\n  // Caching the Request-ID:Cert-ID mapping which guides the use of\n  // USE_CERTIFICATE frame.\n  std::map<uint16_t, uint16_t> requestCertMap_;\n\n  // Locally cached certificates which authenticates the secondary identity of\n  // the peer.\n  std::map<uint16_t, std::vector<fizz::CertificateEntry>> receivedCerts_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/SecondaryAuthManagerBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/protocol/AsyncFizzBase.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n\nnamespace proxygen {\n\nclass SecondaryAuthManagerBase {\n public:\n  virtual ~SecondaryAuthManagerBase() = default;\n\n  /**\n   * Generate an authenticator request given a certificate_request_context and\n   * a set of extensions.\n   * @return (request ID, encoded authenticator request)\n   */\n  virtual std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> createAuthRequest(\n      std::unique_ptr<folly::IOBuf> certRequestContext,\n      std::vector<fizz::Extension> extensions) = 0;\n\n  /**\n   * Generate an authenticator request given the Request-ID and authenticator\n   * request..\n   * @return (cert ID, encoded authenticator)\n   */\n  virtual std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> getAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> authRequest) = 0;\n\n  /**\n   * Validate an authenticator and cache the received certificate along with the\n   * Cert-ID if it is valid.\n   */\n  virtual bool validateAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t certId,\n      std::unique_ptr<folly::IOBuf> authenticator) = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/ServerPushLifecycle.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n\nnamespace proxygen {\n\nclass HTTPMessage;\nclass HTTPTransaction;\n\n//  A callback class for observing pushed transactions.\n//  It is built for HTTP3 state transitions, but is backward\n//  compatible with HTTP2\n//\n//  The state transition diagram (states unique to HTTP3\n//  are marked with \"h3\")\n//\n//  The sole purpose of this callback is tesing the correct\n//  behavior of the server push.\n//\n//\n//                        .-----------------.\n//                      (       Start       )\n//                       `-----------------'\n//                                |\n//                +---------------+--------------+\n//                v                              v\n//     +---------------------+       +---------------------h3\n// +---| onPushPromiseBegin  |       |    onNascentBegin    |---+\n// |   +---------------------+       +----------------------+   |\n// |              |                              |              |\n// |              v                              v              |\n// |   +---------------------+         +------------------h3    |\n// |   |    onPushPromise    |-------+ |  onNascentStream  |----+\n// |   +---------------------+       | +-------------------+    |\n// |              |                  |           |              |\n// |              +---+              |           |              |\n// |                  v              |           |              |\n// |     +------------------------h3 |           |              |\n// +--+--| onHalfPushedTransaction | |           +---+          |\n// |  |  +-------------------------+ |               |          |\n// |  |               |              |               |          |\n// |  |               +--------------+               |          |\n// |  |                              v               |          |\n// |  +----------+      +-------------------------+  |          |\n// |             +------| onFullPushedTransaction |<-+          |\n// |             v      +-------------------------+             |\n// |    .----------------h3          |                          |\n// |   (onPushedTxnTimeout )         |  .-----------------h3    |\n// |    `-----------------'          | ( onOrphanedNascent  )<--+\n// |                              +--+  `------------------'    |\n// |   .-----------------h3       |           .------------h3   |\n// +->( onOrphanedHfOpnTxn )      |          ( onNascentEof  )<-+\n//     `------------------'       |           `-------------'\n//                                v\n//                          .-----------.\n//                         (  onTxnEof   )\n//                          `-----------'\n//\n//\nclass ServerPushLifecycleCallback {\n public:\n  virtual ~ServerPushLifecycleCallback() = default;\n\n  /**\n   * A push promise has arrived, but has not been fully parsed yet\n   */\n  virtual void onPushPromiseBegin(HTTPCodec::StreamID /* parent streamID */,\n                                  hq::PushId /* pushID */) = 0;\n\n  /**\n   * A push promise has arrived. Headers have been parsed.\n   */\n  virtual void onPushPromise(HTTPCodec::StreamID /* parent streamID */,\n                             hq::PushId /* pushID */,\n                             HTTPMessage* /* msg */) = 0;\n\n  /**\n   * A push stream has arrived, but the pushId has not been received yet\n   */\n  virtual void onNascentPushStreamBegin(\n      HTTPCodec::StreamID /* push stream ID */, bool /* eom */) = 0;\n\n  /**\n   * A push stream has arrived, and the pushId has been received.\n   */\n  virtual void onNascentPushStream(HTTPCodec::StreamID /* push stream ID */,\n                                   hq::PushId /* pushId */,\n                                   bool /* eom */) = 0;\n\n  /**\n   * A push stream has ended before being converted to to full\n   * ingress stream\n   */\n  virtual void onNascentEof(HTTPCodec::StreamID /* push stream ID */,\n                            folly::Optional<hq::PushId> /* pushId */) = 0;\n\n  /**\n   * A nascent push stream has timed out. For more details, see\n   * \"The Personal History, Adventures, Experience and Observation\n   * of David Copperfield the Younger of Blunderstone Rookery\n   * (Which He Never Meant to Publish on Any Account).\", Dickens C.\n   */\n  virtual void onOrphanedNascentStream(\n      HTTPCodec::StreamID /* push stream ID */,\n      folly::Optional<hq::PushId> /* pushId */) = 0;\n\n  /**\n   * A push promise has arrived, the push id and the assoc (parent) id\n   * are known.\n   */\n  virtual void onHalfOpenPushedTxn(const HTTPTransaction* /* txn */,\n                                   hq::PushId /* server push id */,\n                                   HTTPCodec::StreamID /* assoc stream id */,\n                                   bool /* eom */) = 0;\n\n  /**\n   * Both the promise and the stream have arrived. The newly created transport\n   * can be created\n   */\n  virtual void onPushedTxn(const HTTPTransaction* /* txn */,\n                           HTTPCodec::StreamID /* push ingress stream */,\n                           hq::PushId /* server push id */,\n                           HTTPCodec::StreamID /* assoc stream id */,\n                           bool /* eom */) = 0;\n\n  /**\n   * A fully opened pushed transaction has timed out\n   */\n  virtual void onPushedTxnTimeout(const HTTPTransaction* /* txn */) = 0;\n\n  /**\n   * A half-opened push transaction has timed out\n   */\n  virtual void onOrphanedHalfOpenPushedTxn(\n      const HTTPTransaction* /* txn */) = 0;\n\n  /**\n   * Push ID limit exceeded, possibly closing the stream\n   */\n  virtual void onPushIdLimitExceeded(\n      hq::PushId /* incoming push id */,\n      folly::Optional<hq::PushId> /* maximal allowed push id */,\n      folly::Optional<HTTPCodec::StreamID> /* possible push stream */) = 0;\n\n}; // ServerPushLifecycleCallback\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/SimpleController.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/SimpleController.h>\n\n#include <proxygen/lib/http/session/CodecErrorResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPDirectResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPSessionAcceptor.h>\n\nnamespace proxygen {\n\nSimpleController::SimpleController(HTTPSessionAcceptor* acceptor)\n    : acceptor_(acceptor) {\n}\n\nHTTPTransactionHandler* SimpleController::getRequestHandler(\n    HTTPTransaction& txn, HTTPMessage* msg) {\n  CHECK(acceptor_) << \"Requires an acceptor, or override this method\";\n  return acceptor_->newHandler(txn, msg);\n}\n\nHTTPTransactionHandler* SimpleController::getParseErrorHandler(\n    HTTPTransaction* /*txn*/,\n    const HTTPException& error,\n    const folly::SocketAddress& localAddress) {\n\n  if (error.hasCodecStatusCode()) {\n    return new CodecErrorResponseHandler(error.getCodecStatusCode());\n  }\n\n  auto errorPage = acceptor_ ? acceptor_->getErrorPage(localAddress) : nullptr;\n  return createErrorHandler(\n      error.hasHttpStatusCode() ? error.getHttpStatusCode() : 400,\n      \"Bad Request\",\n      errorPage);\n}\n\nHTTPTransactionHandler* SimpleController::getTransactionTimeoutHandler(\n    HTTPTransaction* /*txn*/, const folly::SocketAddress& localAddress) {\n\n  auto errorPage = acceptor_ ? acceptor_->getErrorPage(localAddress) : nullptr;\n  return createErrorHandler(408, \"Client timeout\", errorPage);\n}\n\nvoid SimpleController::attachSession(HTTPSessionBase* /*sess*/) {\n}\n\nvoid SimpleController::detachSession(const HTTPSessionBase* /*sess*/) {\n}\n\nHTTPTransactionHandler* SimpleController::createErrorHandler(\n    uint32_t statusCode,\n    const std::string& statusMessage,\n    const HTTPErrorPage* errorPage) {\n\n  return new HTTPDirectResponseHandler(statusCode, statusMessage, errorPage);\n}\n\nstd::chrono::milliseconds SimpleController::getGracefulShutdownTimeout() const {\n  return acceptor_ ? acceptor_->getGracefulShutdownTimeout()\n                   : std::chrono::milliseconds(0);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/SimpleController.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <string>\n\nnamespace proxygen {\n\nclass HTTPErrorPage;\nclass HTTPSessionAcceptor;\n\n/**\n * This simple controller provides some basic default behaviors. When\n * errors occur, it will install an appropriate handler. Otherwise, it\n * will install the acceptor's default handler.\n */\nclass SimpleController : public HTTPSessionController {\n public:\n  explicit SimpleController(HTTPSessionAcceptor* acceptor);\n\n  /**\n   * Will be invoked whenever HTTPSession successfully parses a\n   * request\n   */\n  HTTPTransactionHandler* getRequestHandler(HTTPTransaction& txn,\n                                            HTTPMessage* msg) override;\n\n  /**\n   * Will be invoked when HTTPSession is unable to parse a new request\n   * on the connection because of bad input.\n   *\n   * error contains specific information about what went wrong\n   */\n  HTTPTransactionHandler* getParseErrorHandler(\n      HTTPTransaction* txn,\n      const HTTPException& error,\n      const folly::SocketAddress& localAddress) override;\n\n  /**\n   * Will be invoked when HTTPSession times out parsing a new request.\n   */\n  HTTPTransactionHandler* getTransactionTimeoutHandler(\n      HTTPTransaction* txn, const folly::SocketAddress& localAddress) override;\n\n  void attachSession(HTTPSessionBase*) override;\n  void detachSession(const HTTPSessionBase*) override;\n\n  [[nodiscard]] std::chrono::milliseconds getGracefulShutdownTimeout()\n      const override;\n\n protected:\n  HTTPTransactionHandler* createErrorHandler(uint32_t statusCode,\n                                             const std::string& statusMessage,\n                                             const HTTPErrorPage* errorPage);\n\n  HTTPSessionAcceptor* const acceptor_{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/TTLBAStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\nnamespace proxygen {\n\nclass TTLBAStats {\n public:\n  virtual ~TTLBAStats() noexcept = default;\n\n  TTLBAStats() = default;\n  TTLBAStats(const TTLBAStats&) = delete;\n  TTLBAStats& operator=(const TTLBAStats&) = delete;\n  TTLBAStats(TTLBAStats&&) = delete;\n  TTLBAStats& operator=(TTLBAStats&&) = delete;\n\n  virtual void recordPresendIOSplit() noexcept = 0;\n  virtual void recordPresendExceedLimit() noexcept = 0;\n  virtual void recordTTLBAExceedLimit() noexcept = 0;\n  virtual void recordTTLBANotFound() noexcept = 0;\n  virtual void recordTTLBAReceived() noexcept = 0;\n  virtual void recordTTLBATimeout() noexcept = 0;\n  virtual void recordTTLBATracked() noexcept = 0;\n  virtual void recordTTBTXExceedLimit() noexcept = 0;\n  virtual void recordTTBTXReceived() noexcept = 0;\n  virtual void recordTTBTXTimeout() noexcept = 0;\n  virtual void recordTTBTXNotFound() noexcept = 0;\n  virtual void recordTTBTXTracked() noexcept = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/TransactionByteEvents.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/ByteEvents.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\nnamespace proxygen {\n\nclass TransactionByteEvent : public ByteEvent {\n public:\n  TransactionByteEvent(uint64_t byteNo,\n                       EventType eventType,\n                       HTTPTransaction* txn,\n                       ByteEvent::Callback callback = nullptr)\n      : ByteEvent(byteNo, eventType, callback), txn_(txn) {\n    txn_->incrementPendingByteEvents();\n  }\n\n  ~TransactionByteEvent() override {\n    txn_->decrementPendingByteEvents();\n  }\n\n  [[nodiscard]] HTTPTransaction* getTransaction() const override {\n    return txn_;\n  }\n\n  HTTPTransaction* txn_;\n};\n\n/**\n * TimestampByteEvents are used to wait for TX and ACK timestamps.\n *\n * Contain a timeout that determines when the timestamp event expires (e.g., we\n * stop waiting to receive the timestamp from the system).\n */\nclass TimestampByteEvent\n    : public TransactionByteEvent\n    , public folly::HHWheelTimer::Callback {\n public:\n  enum TimestampType {\n    TX,\n    ACK,\n  };\n  /**\n   * The instances of TimestampByteEvent::Callback *MUST* outlive the ByteEvent\n   * it is registered on.\n   */\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void timeoutExpired(TimestampByteEvent* event) noexcept = 0;\n  };\n\n  TimestampByteEvent(TimestampByteEvent::Callback* asyncTimeoutCallback,\n                     TimestampType timestampType,\n                     uint64_t byteNo,\n                     EventType eventType,\n                     HTTPTransaction* txn,\n                     ByteEvent::Callback callback = nullptr)\n      : TransactionByteEvent(byteNo, eventType, txn, callback),\n        timestampType_(timestampType),\n        asyncTimeoutCallback_(asyncTimeoutCallback) {\n  }\n\n  void timeoutExpired() noexcept override {\n    asyncTimeoutCallback_->timeoutExpired(this);\n  }\n\n  const TimestampType timestampType_;\n\n private:\n  Callback* asyncTimeoutCallback_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/WebTransportFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/HTTPMessageFilters.h>\n#include <proxygen/lib/http/codec/HQUtils.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n\nnamespace proxygen {\n\n/**\n * WebTransportFilter class definition.\n *\n * HTTPSession/HQSession will install this filter on a WebTransport transaction.\n * The filter holds some session state like the flow controller. The onBody\n * callbacks will pass in the ingress to the\n * H2WebTransportCodec/H3WebTransportCodec, which then call the appropriate\n * capsule callbacks.\n */\nclass WebTransportFilter\n    : public HTTPMessageFilter\n    , public WebTransportImpl::TransportProvider\n    , public HTTPTransactionTransportCallback\n    , public WebTransportCapsuleCodec::Callback {\n public:\n  explicit WebTransportFilter(HTTPTransaction* txn, CodecVersion version)\n      : txn_(txn) {\n    codec_ = std::make_unique<WebTransportCapsuleCodec>(this, version);\n    if (version == CodecVersion::H3) {\n      h3Tp_ = txn_->getWTTransportProvider();\n    } else {\n      // For H2, start with 0 streams allowed until we receive max stream\n      // capsules\n      maxWTBidiStreams_ = 0;\n      maxWTUniStreams_ = 0;\n    }\n    txn_->setWTTransportProvider(\n        static_cast<WebTransportImpl::TransportProvider*>(this));\n    txn_->setTransportCallback(\n        static_cast<HTTPTransactionTransportCallback*>(this));\n    if (txn->isDownstream()) {\n      nextNewWTBidiStream_++;\n      nextNewWTUniStream_++;\n    }\n  }\n\n  static std::unique_ptr<WebTransportFilter> make(HTTPTransaction* txn,\n                                                  CodecVersion version) {\n    auto filter = std::make_unique<WebTransportFilter>(txn, version);\n    if (auto* nextTxnHandler = txn->getHandler()) {\n      filter->setNextTransactionHandler(nextTxnHandler);\n    }\n    txn->setHandler(filter.get());\n    return filter;\n  }\n\n  void clearTransaction() {\n    txn_ = nullptr;\n  }\n\n  void setHandler(WebTransportHandler* handler) {\n    handler_ = handler;\n  }\n\n  void setWebTransportImpl(WebTransportImpl* wtImpl) {\n    wtImpl_ = wtImpl;\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    if (sessionClosed_) {\n      XLOG(ERR) << \"Received additional data after WT_CLOSE_SESSION capsule, \"\n                << \"resetting CONNECT stream with H3_MESSAGE_ERROR\";\n      if (txn_) {\n        auto errorCode =\n            hq::hqToHttpErrorCode(HTTP3::ErrorCode::HTTP_MESSAGE_ERROR);\n        txn_->sendAbort(errorCode);\n      }\n      return;\n    }\n    codec_->onIngress(std::move(chain), false);\n  }\n\n  void onEOM() noexcept override {\n    codec_->onIngress(nullptr, true);\n\n    if (nextTransactionHandler_) {\n      nextTransactionHandler_->onEOM();\n    }\n  }\n\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept override {\n    if (nextTransactionHandler_) {\n      nextTransactionHandler_->onDatagram(std::move(datagram));\n    }\n  }\n\n  void onWebTransportBidiStream(\n      HTTPCodec::StreamID id,\n      WebTransport::BidiStreamHandle stream) noexcept override {\n    if (nextTransactionHandler_) {\n      nextTransactionHandler_->onWebTransportBidiStream(id, std::move(stream));\n    }\n  }\n\n  void onWebTransportUniStream(\n      HTTPCodec::StreamID id,\n      WebTransport::StreamReadHandle* stream) noexcept override {\n    if (nextTransactionHandler_) {\n      nextTransactionHandler_->onWebTransportUniStream(id, stream);\n    }\n  }\n\n  std::unique_ptr<HTTPMessageFilter> clone() noexcept override {\n    return nullptr;\n  }\n\n  void trackedByteEventTX(const ByteEvent& /*event*/) noexcept override {\n  }\n  void firstHeaderByteFlushed() noexcept override {\n  }\n  void firstByteFlushed() noexcept override {\n  }\n  void lastByteFlushed() noexcept override {\n  }\n  void lastByteAcked(std::chrono::milliseconds /*latency*/) noexcept override {\n  }\n  void headerBytesGenerated(HTTPHeaderSize& /*size*/) noexcept override {\n  }\n  void headerBytesReceived(const HTTPHeaderSize& /*size*/) noexcept override {\n  }\n  void bodyBytesGenerated(size_t /*nbytes*/) noexcept override {\n  }\n  void bodyBytesReceived(size_t /*size*/) noexcept override {\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) override {\n    if (h3Tp_) {\n      return h3Tp_->sendDatagram(std::move(datagram));\n    }\n    return folly::unit;\n  }\n\n  folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n  newWebTransportBidiStream() override {\n    // TODO: Flow control\n    if (h3Tp_) {\n      return h3Tp_->newWebTransportBidiStream();\n    }\n    auto res = nextNewWTBidiStream_;\n    nextNewWTBidiStream_ += 4;\n    return res;\n  }\n\n  folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n  newWebTransportUniStream() override {\n    // TODO: Flow control\n    if (h3Tp_) {\n      return h3Tp_->newWebTransportUniStream();\n    }\n    auto res = nextNewWTUniStream_;\n    nextNewWTUniStream_ += 4;\n    return res;\n  }\n\n  folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n  sendWebTransportStreamData(HTTPCodec::StreamID id,\n                             std::unique_ptr<folly::IOBuf> data,\n                             bool eof,\n                             WebTransport::ByteEventCallback* wcb) override {\n    if (h3Tp_) {\n      return h3Tp_->sendWebTransportStreamData(id, std::move(data), eof, wcb);\n    }\n    return WebTransport::FCState::UNBLOCKED;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n      uint64_t maxData) override {\n    WTMaxDataCapsule capsule{maxData};\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    if (auto res = writeWTMaxData(buf, capsule); !res.has_value()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n    }\n    if (txn_) {\n      txn_->sendBody(buf.move());\n    }\n    return folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxStreams(\n      uint64_t maxStreams, bool isBidi) override {\n    WTMaxStreamsCapsule capsule{maxStreams};\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    if (auto res = writeWTMaxStreams(buf, capsule, isBidi); !res.has_value()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n    }\n    if (txn_) {\n      txn_->sendBody(buf.move());\n    }\n    return folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTStreamsBlocked(\n      uint64_t maxStreams, bool isBidi) override {\n    WTStreamsBlockedCapsule capsule{maxStreams};\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    auto writeRes = writeWTStreamsBlocked(buf, capsule, isBidi);\n    if (!writeRes) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n    }\n    if (txn_) {\n      txn_->sendBody(buf.move());\n    }\n    return folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTDataBlocked(\n      uint64_t maxData) override {\n    WTDataBlockedCapsule capsule{maxData};\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    auto writeRes = writeWTDataBlocked(buf, capsule);\n    if (!writeRes) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n    }\n    if (txn_) {\n      txn_->sendBody(buf.move());\n    }\n    return folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> resetWebTransportEgress(\n      HTTPCodec::StreamID id, uint32_t errorCode) override {\n    return h3Tp_ ? h3Tp_->resetWebTransportEgress(id, errorCode) : folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  setWebTransportStreamPriority(HTTPCodec::StreamID id,\n                                quic::PriorityQueue::Priority pri) override {\n    return h3Tp_ ? h3Tp_->setWebTransportStreamPriority(id, pri) : folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  setWebTransportPriorityQueue(\n      std::unique_ptr<quic::PriorityQueue> queue) noexcept override {\n    return h3Tp_ ? h3Tp_->setWebTransportPriorityQueue(std::move(queue))\n                 : folly::unit;\n  }\n\n  folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                  WebTransport::ErrorCode>\n  readWebTransportData(HTTPCodec::StreamID id, size_t max) override {\n    return h3Tp_\n               ? h3Tp_->readWebTransportData(id, max)\n               : folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  initiateReadOnBidiStream(HTTPCodec::StreamID id,\n                           quic::StreamReadCallback* readCallback) override {\n    return h3Tp_ ? h3Tp_->initiateReadOnBidiStream(id, readCallback)\n                 : folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  pauseWebTransportIngress(HTTPCodec::StreamID id) override {\n    return h3Tp_ ? h3Tp_->pauseWebTransportIngress(id) : folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  resumeWebTransportIngress(HTTPCodec::StreamID id) override {\n    return h3Tp_ ? h3Tp_->resumeWebTransportIngress(id) : folly::unit;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  stopReadingWebTransportIngress(HTTPCodec::StreamID id,\n                                 folly::Optional<uint32_t> errorCode) override {\n    return h3Tp_ ? h3Tp_->stopReadingWebTransportIngress(id, errorCode)\n                 : folly::unit;\n  }\n\n  folly::SemiFuture<folly::Unit> awaitUniStreamCredit() override {\n    return folly::makeFuture(folly::unit);\n  }\n\n  folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() override {\n    return folly::makeSemiFuture(folly::unit);\n  }\n\n  bool canCreateUniStream() override {\n    if ((nextNewWTUniStream_ >> 2) >= maxWTUniStreams_) {\n      return false;\n    }\n    if (h3Tp_ && !h3Tp_->canCreateUniStream()) {\n      return false;\n    }\n    return true;\n  }\n\n  bool canCreateBidiStream() override {\n    if ((nextNewWTBidiStream_ >> 2) >= maxWTBidiStreams_) {\n      return false;\n    }\n    if (h3Tp_ && !h3Tp_->canCreateBidiStream()) {\n      return false;\n    }\n    return true;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  notifyPendingWriteOnStream(HTTPCodec::StreamID id,\n                             quic::StreamWriteCallback* wcb) override {\n    return h3Tp_ ? h3Tp_->notifyPendingWriteOnStream(id, wcb) : folly::unit;\n  }\n\n  bool usesEncodedApplicationErrorCodes() override {\n    return h3Tp_ ? h3Tp_->usesEncodedApplicationErrorCodes()\n                 : true; // For H2, we use encoded application error codes\n  }\n\n  [[nodiscard]] uint64_t getWTInitialSendWindow() const override {\n    return h3Tp_ ? h3Tp_->getWTInitialSendWindow() : quic::kMaxVarInt;\n  }\n\n  bool isPeerInitiatedStream(HTTPCodec::StreamID id) override {\n    if (h3Tp_) {\n      return h3Tp_->isPeerInitiatedStream(id);\n    }\n    bool isClientStream = (id & 0b01) == 0;\n    return txn_->isDownstream() ? isClientStream : !isClientStream;\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return txn_->getLocalAddress();\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return txn_->getPeerAddress();\n  }\n\n  void onConnectionError(CapsuleCodec::ErrorCode error) noexcept override {\n    XLOG(DBG1) << __func__ << \" error=\" << static_cast<int>(error);\n  }\n  void onPaddingCapsule(PaddingCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTResetStreamCapsule(\n      WTResetStreamCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTStopSendingCapsule(\n      WTStopSendingCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTStreamCapsule(WTStreamCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTMaxDataCapsule(WTMaxDataCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    if (wtImpl_) {\n      wtImpl_->onMaxData(capsule.maximumData);\n    }\n  }\n  void onWTMaxStreamDataCapsule(\n      WTMaxStreamDataCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTMaxStreamsBidiCapsule(\n      WTMaxStreamsCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    maxWTBidiStreams_ = capsule.maximumStreams;\n    if (wtImpl_) {\n      wtImpl_->onMaxStreams(capsule.maximumStreams, true);\n    }\n  }\n  void onWTMaxStreamsUniCapsule(WTMaxStreamsCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    maxWTUniStreams_ = capsule.maximumStreams;\n    if (wtImpl_) {\n      wtImpl_->onMaxStreams(capsule.maximumStreams, false);\n    }\n  }\n  void onWTDataBlockedCapsule(WTDataBlockedCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    if (wtImpl_) {\n      wtImpl_->onDataBlocked(capsule.maximumData);\n    }\n  }\n  void onWTStreamDataBlockedCapsule(\n      WTStreamDataBlockedCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onWTStreamsBlockedBidiCapsule(\n      WTStreamsBlockedCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    if (wtImpl_) {\n      wtImpl_->onStreamsBlocked(capsule.maximumStreams, true);\n    }\n  }\n  void onWTStreamsBlockedUniCapsule(\n      WTStreamsBlockedCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__;\n    if (wtImpl_) {\n      wtImpl_->onStreamsBlocked(capsule.maximumStreams, false);\n    }\n  }\n  void onDatagramCapsule(DatagramCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n  }\n  void onCloseWTSessionCapsule(\n      CloseWebTransportSessionCapsule capsule) noexcept override {\n    XLOG(DBG1) << __func__ << \" errorCode=\" << capsule.applicationErrorCode\n               << \" message=\" << capsule.applicationErrorMessage;\n\n    if (wtImpl_) {\n      wtImpl_->terminateSession(capsule.applicationErrorCode);\n      sessionClosed_ = true;\n      closeErrorCode_ = capsule.applicationErrorCode;\n      closeErrorMessage_ = capsule.applicationErrorMessage;\n    }\n\n    if (handler_) {\n      handler_->onSessionEnd(capsule.applicationErrorCode);\n    }\n\n    if (txn_ && !txn_->isEgressComplete()) {\n      txn_->sendEOM();\n    }\n  }\n  void onDrainWTSessionCapsule(\n      DrainWebTransportSessionCapsule /*capsule*/) noexcept override {\n    XLOG(DBG1) << __func__;\n    if (handler_) {\n      handler_->onSessionDrain();\n    }\n  }\n\n private:\n  HTTPTransaction* txn_;\n  std::unique_ptr<CapsuleCodec> codec_;\n  uint64_t nextNewWTBidiStream_{0};\n  uint64_t nextNewWTUniStream_{2};\n  uint64_t maxWTBidiStreams_{quic::kMaxVarInt};\n  uint64_t maxWTUniStreams_{quic::kMaxVarInt};\n  folly::F14FastMap<HTTPCodec::StreamID, WebTransportImpl::StreamReadHandle*>\n      readCallbacks_;\n  struct WriteCallback {\n    HTTPCodec::StreamID id;\n    uint64_t bodyOffset;\n    quic::StreamWriteCallback* wcb;\n  };\n  std::list<WriteCallback> writeCallbacks_;\n  WebTransportHandler* handler_{nullptr};\n  WebTransportImpl* wtImpl_{nullptr};\n  [[maybe_unused]] TransportProvider* h3Tp_{nullptr};\n\n  bool sessionClosed_{false};\n  uint32_t closeErrorCode_{0};\n  std::string closeErrorMessage_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/ByteEventTrackerMocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n\nclass MockByteEventTrackerCallback\n    : public proxygen::ByteEventTracker::Callback {\n public:\n  MOCK_METHOD((void), onPingReplyLatency, (int64_t), (noexcept));\n  MOCK_METHOD((void),\n              onTxnByteEventWrittenToBuf,\n              (const proxygen::ByteEvent&),\n              (noexcept));\n  MOCK_METHOD((void), onDeleteTxnByteEvent, (), (noexcept));\n};\n"
  },
  {
    "path": "proxygen/lib/http/session/test/ByteEventTrackerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <proxygen/lib/http/session/test/ByteEventTrackerMocks.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\n#include <chrono>\n\nusing namespace testing;\nusing namespace proxygen;\n\nclass ByteEventTrackerTest : public Test {\n public:\n  void SetUp() override {\n    txn_.setTransportCallback(&transportCallback_);\n  }\n\n protected:\n  folly::EventBase eventBase_;\n  WheelTimerInstance transactionTimeouts_{std::chrono::milliseconds(500),\n                                          &eventBase_};\n  NiceMock<MockHTTPTransactionTransport> transport_;\n  StrictMock<MockHTTPHandler> handler_;\n  HTTP2PriorityQueue txnEgressQueue_;\n  HTTPTransaction txn_{TransportDirection::DOWNSTREAM,\n                       HTTPCodec::StreamID(1),\n                       1,\n                       transport_,\n                       txnEgressQueue_,\n                       transactionTimeouts_.getWheelTimer(),\n                       transactionTimeouts_.getDefaultTimeout()};\n  MockHTTPTransactionTransportCallback transportCallback_;\n  MockByteEventTrackerCallback callback_;\n  std::shared_ptr<ByteEventTracker> byteEventTracker_{\n      new ByteEventTracker(&callback_)};\n};\n\nTEST_F(ByteEventTrackerTest, Ping) {\n  auto pingByteEventCb = [](ByteEvent& event) {\n    EXPECT_EQ(event.getType(), ByteEvent::PING_REPLY_SENT);\n  };\n  byteEventTracker_->addPingByteEvent(\n      10, proxygen::getCurrentTime(), 0, pingByteEventCb);\n  EXPECT_CALL(callback_, onPingReplyLatency(_));\n  byteEventTracker_->processByteEvents(byteEventTracker_, 10);\n}\n\nTEST_F(ByteEventTrackerTest, Ttlb) {\n  auto lastBodyByteEventCb = [](ByteEvent& event) {\n    EXPECT_EQ(event.getType(), ByteEvent::LAST_BYTE);\n  };\n  byteEventTracker_->addLastByteEvent(&txn_, 10, lastBodyByteEventCb);\n  EXPECT_CALL(transportCallback_, headerBytesGenerated(_)); // sendAbort calls?\n  txn_.sendAbort(); // put it in a state for detach\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  EXPECT_CALL(\n      callback_,\n      onTxnByteEventWrittenToBuf(AllOf(\n          Property(&ByteEvent::getByteOffset, 10),\n          Property(&ByteEvent::getType, ByteEvent::EventType::LAST_BYTE))));\n  EXPECT_CALL(transport_, detach(_));\n  byteEventTracker_->processByteEvents(byteEventTracker_, 10);\n}\n\nclass MockTrackedByteEventCb {\n public:\n  MockTrackedByteEventCb() = default;\n  MOCK_METHOD(void, onEventByteCallback, (const ByteEvent&));\n};\n\nTEST_F(ByteEventTrackerTest, TrackBytes) {\n  MockTrackedByteEventCb mockCB;\n  EXPECT_CALL(mockCB, onEventByteCallback(_)).Times(2);\n  auto trackedByteEventCb = [&](ByteEvent& event) {\n    EXPECT_EQ(event.getType(), ByteEvent::TRACKED_BYTE);\n    mockCB.onEventByteCallback(event);\n  };\n  byteEventTracker_->addTrackedByteEvent(&txn_, 10, trackedByteEventCb);\n  byteEventTracker_->addTrackedByteEvent(\n      &txn_, 10, trackedByteEventCb); // same offset\n  byteEventTracker_->processByteEvents(byteEventTracker_, 10);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Install MockQuicSocketDriver.h for downstream test targets\ninstall(FILES MockQuicSocketDriver.h DESTINATION include/proxygen/lib/http/session/test)\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\n# Header-only library for MockQuicSocketDriver\nadd_library(mock_quic_socket_driver INTERFACE)\ntarget_include_directories(mock_quic_socket_driver INTERFACE\n    $<BUILD_INTERFACE:${PROXYGEN_FBCODE_ROOT}>\n    $<INSTALL_INTERFACE:include>\n)\ntarget_link_libraries(mock_quic_socket_driver INTERFACE\n    mvfst::mvfst_api_test_mocks\n    mvfst::mvfst_common_events_folly_eventbase\n    Folly::folly\n)\n\nadd_library(sessiontestutils TestUtils.cpp)\ntarget_include_directories(\n    sessiontestutils PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIR}\n)\ntarget_compile_options(\n    sessiontestutils PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(sessiontestutils PUBLIC proxygen)\n\n\nproxygen_add_test(TARGET SessionTests\n  SOURCES\n    ByteEventTrackerTest.cpp\n    DownstreamTransactionTest.cpp\n    HTTPDownstreamSessionTest.cpp\n    HTTPSessionAcceptorTest.cpp\n    HTTPUpstreamSessionTest.cpp\n    MockCodecDownstreamTest.cpp\n    HTTP2PriorityQueueTest.cpp\n    HTTPDefaultSessionCodecFactoryTest.cpp\n    HTTPTransactionSMTest.cpp\n    HTTPTransactionTest.cpp\n    HTTPTransactionWebTransportTest.cpp\n  DEPENDS\n    codectestutils\n    sessiontestutils\n    testtransport\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET HQSessionTests\n  SOURCES\n    HQByteEventTrackerTest.cpp\n    HQDownstreamSessionTest.cpp\n    HQSessionMocksTest.cpp\n    HQSessionTestCommon.cpp\n    HQStreamBaseTest.cpp\n    HQStreamDispatcherTest.cpp\n    HQUpstreamSessionTest.cpp\n  DEPENDS\n    codectestutils\n    sessiontestutils\n    testtransport\n    proxygen\n    testmain\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_state_machine\n)\n\nadd_executable(http_session_benchmark HTTPSessionBenchmark.cpp)\ntarget_include_directories(http_session_benchmark PUBLIC\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIRS}\n)\ntarget_compile_definitions(http_session_benchmark PUBLIC\n    ${LIBGMOCK_DEFINES}\n)\ntarget_compile_options(http_session_benchmark PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(http_session_benchmark PUBLIC\n    codectestutils\n    sessiontestutils\n    proxygen\n    Folly::folly\n    Folly::follybenchmark\n    ${LIBGMOCK_LIBRARIES}\n)\n"
  },
  {
    "path": "proxygen/lib/http/session/test/DownstreamTransactionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing std::unique_ptr;\n\nclass DownstreamTransactionTest : public testing::Test {\n public:\n  DownstreamTransactionTest() = default;\n\n  void SetUp() override {\n    EXPECT_CALL(transport_, describe(_)).WillRepeatedly(Return());\n  }\n\n  void setupRequestResponseFlow(HTTPTransaction* txn,\n                                uint32_t size,\n                                bool delayResponse = false) {\n    EXPECT_CALL(handler_, _setTransaction(txn));\n    EXPECT_CALL(handler_, _detachTransaction());\n    EXPECT_CALL(transport_, detach(txn));\n    if (delayResponse) {\n      EXPECT_CALL(handler_, _onHeadersComplete(_));\n    } else {\n      EXPECT_CALL(handler_, _onHeadersComplete(_))\n          .WillOnce(Invoke([=](std::shared_ptr<HTTPMessage> /*msg*/) {\n            auto response = makeResponse(200);\n            txn->sendHeaders(*response.get());\n            txn->sendBody(makeBuf(size));\n            txn->sendEOM();\n          }));\n    }\n    EXPECT_CALL(transport_, sendHeaders(txn, _, _, _))\n        .WillOnce(\n            Invoke([=](Unused, const HTTPMessage& headers, Unused, Unused) {\n              EXPECT_EQ(headers.getStatusCode(), 200);\n            }));\n    EXPECT_CALL(transport_, sendBody(txn, _, false, false))\n        .WillRepeatedly(Invoke(\n            [=,\n             this](Unused, std::shared_ptr<folly::IOBuf> body, Unused, Unused) {\n              auto cur = body->computeChainDataLength();\n              sent_ += cur;\n              return cur;\n            }));\n    if (delayResponse) {\n      EXPECT_CALL(transport_, sendEOM(txn, _));\n    } else {\n      EXPECT_CALL(transport_, sendEOM(txn, _))\n          .WillOnce(InvokeWithoutArgs([=, this]() {\n            CHECK_EQ(sent_, size);\n            txn->onIngressBody(makeBuf(size), 0);\n            txn->onIngressEOM();\n            return 5;\n          }));\n    }\n    EXPECT_CALL(handler_, _onBodyWithOffset(_, _))\n        .WillRepeatedly(\n            Invoke([=, this](uint64_t, std::shared_ptr<folly::IOBuf> body) {\n              received_ += body->computeChainDataLength();\n            }));\n    EXPECT_CALL(handler_, _onEOM()).WillOnce(InvokeWithoutArgs([=, this] {\n      CHECK_EQ(received_, size);\n    }));\n    EXPECT_CALL(transport_, notifyPendingEgress())\n        .WillOnce(InvokeWithoutArgs([=] { txn->onWriteReady(size, 1); }))\n        .WillOnce(DoDefault()); // The second call is for sending the eom\n\n    txn->setHandler(&handler_);\n  }\n\n protected:\n  folly::EventBase eventBase_;\n  MockHTTPTransactionTransport transport_;\n  StrictMock<MockHTTPHandler> handler_;\n  HTTP2PriorityQueue txnEgressQueue_;\n  uint32_t received_{0};\n  uint32_t sent_{0};\n  std::unique_ptr<HTTPTransaction> txn_;\n\n  HTTPTransaction& makeTxn(bool useFlowControl = false,\n                           uint32_t receiveInitialWindowSize = 0,\n                           uint32_t sendInitialWindowSize = 0) {\n    txn_ = std::make_unique<HTTPTransaction>(TransportDirection::DOWNSTREAM,\n                                             HTTPCodec::StreamID(1),\n                                             1,\n                                             transport_,\n                                             txnEgressQueue_,\n                                             &eventBase_.timer(),\n                                             std::chrono::milliseconds(500),\n                                             nullptr,\n                                             useFlowControl,\n                                             receiveInitialWindowSize,\n                                             sendInitialWindowSize);\n    return *txn_;\n  }\n\n  HTTPTransaction& makeExTxn() {\n    txn_ = std::make_unique<HTTPTransaction>(TransportDirection::DOWNSTREAM,\n                                             HTTPCodec::StreamID(2),\n                                             1,\n                                             transport_,\n                                             txnEgressQueue_,\n                                             &eventBase_.timer(),\n                                             std::chrono::milliseconds(500),\n                                             nullptr,\n                                             false,\n                                             0,\n                                             0,\n                                             http2::DefaultPriority,\n                                             HTTPCodec::NoStream);\n    return *txn_;\n  }\n};\n\n/**\n * Test that the the transaction properly forwards callbacks to the\n * handler and that it interacts with its transport as expected.\n */\nTEST_F(DownstreamTransactionTest, SimpleCallbackForwarding) {\n  // flow control is disabled\n  auto& txn = makeTxn();\n  setupRequestResponseFlow(&txn, 100);\n\n  txn.onIngressHeadersComplete(makeGetRequest());\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, InvariantViolationHandler) {\n  auto& txn = makeTxn();\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  EXPECT_CALL(handler_, _onInvariantViolation(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_EQ(ex.getErrno(), (int)HTTPException::Direction::EGRESS);\n        EXPECT_EQ(std::string(ex.what()),\n                  \"Invalid egress state transition, state=Start, \"\n                  \"event=sendBody, streamID=1\");\n      }));\n  EXPECT_CALL(transport_, sendAbort(_, _));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n\n  txn.setHandler(&handler_);\n  // Send body before headers -- oops;\n  txn.sendBody(makeBuf(10));\n  eventBase_.loop();\n}\n\n/**\n * Testing that we're sending a window update for simple requests\n */\nTEST_F(DownstreamTransactionTest, RegularWindowUpdate) {\n  auto& txn = makeTxn(true, // flow control enabled\n                      400,\n                      http2::kInitialWindow);\n  uint32_t reqBodySize = 220;\n  setupRequestResponseFlow(&txn, reqBodySize);\n\n  // test that the window update is generated\n  EXPECT_CALL(transport_, sendWindowUpdate(_, reqBodySize));\n\n  // run the test\n  txn.onIngressHeadersComplete(makeGetRequest());\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, NoWindowUpdate) {\n  auto& txn = makeTxn(true, // flow control enabled\n                      450,  // more than 2x req size\n                      http2::kInitialWindow);\n  uint32_t reqBodySize = 220;\n  setupRequestResponseFlow(&txn, reqBodySize, true);\n\n  EXPECT_CALL(transport_, sendWindowUpdate(_, reqBodySize)).Times(0);\n\n  // run the test\n  txn.onIngressHeadersComplete(makeGetRequest());\n  txn.onIngressBody(makeBuf(reqBodySize), 0);\n  txn.onIngressEOM();\n  auto response = makeResponse(200);\n  txn.sendHeaders(*response.get());\n  txn.sendBody(makeBuf(reqBodySize));\n  txn.sendEOM();\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, FlowControlInfoCorrect) {\n  auto& txn = makeTxn(true, 450, 100);\n\n  EXPECT_CALL(transport_, getFlowControlInfo(_))\n      .WillOnce(Invoke([=](HTTPTransaction::FlowControlInfo* info) {\n        info->flowControlEnabled_ = true;\n        info->sessionSendWindow_ = 1;\n        info->sessionRecvWindow_ = 2;\n      }));\n  HTTPTransaction::FlowControlInfo info;\n  txn.getCurrentFlowControlInfo(&info);\n\n  EXPECT_EQ(info.flowControlEnabled_, true);\n  EXPECT_EQ(info.sessionSendWindow_, 1);\n  EXPECT_EQ(info.sessionRecvWindow_, 2);\n  EXPECT_EQ(info.streamRecvWindow_, 450);\n  EXPECT_EQ(info.streamSendWindow_, 100);\n}\n\nTEST_F(DownstreamTransactionTest, ExpectingWindowUpdate) {\n  auto& txn = makeTxn(true, 450, 100);\n  uint32_t reqBodySize = 220;\n\n  // Get a request, pause ingress, fill up the sendWindow, then expect for a\n  // timeout to be scheduled.\n  txn.onIngressHeadersComplete(makeGetRequest());\n  txn.pauseIngress();\n  txn.onIngressBody(makeBuf(reqBodySize), 0);\n  txn.onIngressEOM();\n  auto response = makeResponse(200);\n  txn.sendHeaders(*response.get());\n  txn.sendBody(makeBuf(reqBodySize));\n  txn.sendEOM();\n  txn.onWriteReady(1000, 1);\n  EXPECT_EQ(eventBase_.timer().count(), 1);\n}\n\nTEST_F(DownstreamTransactionTest, NoWindowUpdateAfterDoneSending) {\n  auto& txn = makeTxn(true, 450, 220);\n  uint32_t reqBodySize = 220;\n\n  // Ensure that after flushing an EOM we are not expecting window update.\n  txn.onIngressHeadersComplete(makeGetRequest());\n  txn.onIngressBody(makeBuf(reqBodySize), 0);\n  auto response = makeResponse(200);\n  txn.sendHeaders(*response.get());\n  txn.sendBody(makeBuf(reqBodySize));\n  txn.sendEOM();\n  txn.onWriteReady(1000, 1);\n  txn.pauseIngress();\n  txn.onIngressEOM();\n  EXPECT_EQ(eventBase_.timer().count(), 0);\n}\n\n/**\n * Testing window increase using window update; we're actually using this in\n * production to avoid bumping the window using the SETTINGS frame\n */\nTEST_F(DownstreamTransactionTest, WindowIncrease) {\n  // set initial window size higher than per-stream window\n  auto& txn = makeTxn(true, // flow control enabled\n                      http2::kInitialWindow,\n                      http2::kInitialWindow);\n  uint32_t reqSize = 500;\n  setupRequestResponseFlow(&txn, reqSize);\n\n  // we expect the difference from the per stream window and the initial window,\n  // together with the bytes sent in the request\n  uint32_t perStreamWindow = http2::kInitialWindow + 1024 * 1024;\n  uint32_t expectedWindowUpdate = perStreamWindow - http2::kInitialWindow;\n  EXPECT_CALL(transport_, sendWindowUpdate(_, expectedWindowUpdate));\n\n  // use a higher window\n  txn.setReceiveWindow(perStreamWindow);\n\n  txn.onIngressHeadersComplete(makeGetRequest());\n  eventBase_.loop();\n}\n\n/**\n * Testing that we're not sending window update when per-stream window size is\n * smaller than the initial window size\n */\nTEST_F(DownstreamTransactionTest, WindowDecrease) {\n  // set initial window size higher than per-stream window\n  auto& txn = makeTxn(true, // flow control enabled\n                      http2::kInitialWindow,\n                      http2::kInitialWindow);\n  setupRequestResponseFlow(&txn, 500);\n\n  // in this case, there should be no window update, as we decrease the window\n  // below the number of bytes we're sending\n  EXPECT_CALL(transport_, sendWindowUpdate(_, _)).Times(0);\n\n  // use a smaller window\n  uint32_t perStreamWindow = http2::kInitialWindow - 1000;\n  txn.setReceiveWindow(perStreamWindow);\n\n  txn.onIngressHeadersComplete(makeGetRequest());\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, ParseErrorCbs) {\n  // Test where the transaction gets on parse error and then a body\n  // callback. This is possible because codecs are stateless between\n  // frames.\n\n  auto& txn = makeTxn();\n\n  HTTPException err(HTTPException::Direction::INGRESS, \"test\");\n  err.setHttpStatusCode(400);\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        ASSERT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS);\n        ASSERT_EQ(std::string(ex.what()), \"test\");\n      }));\n  // onBody() is suppressed since ingress is complete after ingress onError()\n  // onEOM() is suppressed since ingress is complete after ingress onError()\n  EXPECT_CALL(transport_, sendAbort(_, _));\n\n  // New-ish: onError can be delivered more than once if the first error was\n  // unidirectional and the other direction isn't closed before the second error\n  // occurs.  Is this OK?\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_NE(\n            std::string(ex.what()).find(\"onIngressBody after ingress closed\"),\n            std::string::npos);\n      }));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n\n  txn.setHandler(&handler_);\n  txn.onError(err);\n  // Since the transaction is already closed for ingress, giving it\n  // ingress body causes the transaction to be aborted and closed\n  // immediately.\n  txn.onIngressBody(makeBuf(10), 0);\n\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, DetachFromNotify) {\n  unique_ptr<StrictMock<MockHTTPHandler>> handler(\n      new StrictMock<MockHTTPHandler>);\n\n  auto& txn = makeTxn();\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(*handler, _setTransaction(&txn));\n  EXPECT_CALL(*handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage> /*msg*/) {\n        auto response = makeResponse(200);\n        txn.sendHeaders(*response.get());\n        txn.sendBody(makeBuf(10));\n      }));\n  EXPECT_CALL(transport_, sendHeaders(&txn, _, _, _))\n      .WillOnce(Invoke([&](Unused, const HTTPMessage& headers, Unused, Unused) {\n        EXPECT_EQ(headers.getStatusCode(), 200);\n      }));\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(10));\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(-10))\n      .WillOnce(InvokeWithoutArgs([&]() {\n        txn.setHandler(nullptr);\n        handler.reset();\n      }));\n  EXPECT_CALL(transport_, detach(&txn));\n\n  HTTPException err(HTTPException::Direction::INGRESS_AND_EGRESS, \"test\");\n\n  txn.setHandler(handler.get());\n  txn.onIngressHeadersComplete(makeGetRequest());\n  txn.onError(err);\n}\n\nTEST_F(DownstreamTransactionTest, DeferredEgress) {\n  EXPECT_CALL(transport_, describe(_)).WillRepeatedly(Return());\n  EXPECT_CALL(transport_, notifyPendingEgress()).WillRepeatedly(Return());\n\n  auto& txn = makeTxn(true, 10, 10);\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  EXPECT_CALL(handler_, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage> /*msg*/) {\n        auto response = makeResponse(200);\n        txn.sendHeaders(*response.get());\n        txn.sendBody(makeBuf(10));\n        txn.sendBody(makeBuf(20));\n        txn.sendBody(makeBuf(30));\n      }));\n  EXPECT_CALL(transport_, sendHeaders(&txn, _, _, _))\n      .WillOnce(Invoke([&](Unused, const HTTPMessage& headers, Unused, Unused) {\n        EXPECT_EQ(headers.getStatusCode(), 200);\n      }));\n\n  // sendBody\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(10));\n  EXPECT_CALL(handler_, _onEgressPaused());\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(20));\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(30));\n\n  txn.setHandler(&handler_);\n  txn.onIngressHeadersComplete(makeGetRequest());\n\n  // onWriteReady, send, then dequeue (window now full)\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(-30));\n\n  txn.onIngressWindowUpdate(20);\n  EXPECT_EQ(txn.onWriteReady(30, 1), false);\n\n  // Buffer released on error\n  EXPECT_CALL(transport_, notifyEgressBodyBuffered(-30));\n  EXPECT_CALL(handler_, _onError(_));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n\n  HTTPException err(HTTPException::Direction::INGRESS_AND_EGRESS, \"test\");\n  txn.onError(err);\n}\n\nTEST_F(DownstreamTransactionTest, InternalError) {\n  unique_ptr<StrictMock<MockHTTPHandler>> handler(\n      new StrictMock<MockHTTPHandler>);\n\n  auto& txn = makeTxn();\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(*handler, _setTransaction(&txn));\n  EXPECT_CALL(*handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage> /*msg*/) {\n        auto response = makeResponse(200);\n        txn.sendHeaders(*response.get());\n      }));\n  EXPECT_CALL(transport_, sendHeaders(&txn, _, _, _))\n      .WillOnce(Invoke([&](Unused, const HTTPMessage& headers, Unused, Unused) {\n        EXPECT_EQ(headers.getStatusCode(), 200);\n      }));\n  EXPECT_CALL(transport_, sendAbort(&txn, ErrorCode::INTERNAL_ERROR));\n  EXPECT_CALL(*handler, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n\n  HTTPException err(HTTPException::Direction::INGRESS_AND_EGRESS, \"test\");\n\n  txn.setHandler(handler.get());\n  txn.onIngressHeadersComplete(makeGetRequest());\n  txn.sendAbort();\n}\n\nTEST_F(DownstreamTransactionTest, UnpausedFlowControlViolation) {\n  InSequence enforceOrder;\n  auto& txn = makeTxn(true, // flow control enabled\n                      400,\n                      http2::kInitialWindow);\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  EXPECT_CALL(handler_, _onHeadersComplete(_));\n  EXPECT_CALL(transport_, sendAbort(&txn, ErrorCode::FLOW_CONTROL_ERROR));\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_NE(std::string(ex.what()).find(\"reserve failed\"),\n                  std::string::npos);\n      }));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n\n  txn.setHandler(&handler_);\n  txn.onIngressHeadersComplete(makePostRequest(401));\n  txn.onIngressBody(makeBuf(401), 0);\n}\n\nTEST_F(DownstreamTransactionTest, ParseIngressErrorExTxnUnidirectional) {\n  // Test where the ex transaction using QoS0 gets Ingress error\n  auto& exTxn = makeExTxn();\n  HTTPException err(HTTPException::Direction::INGRESS, \"test\");\n  err.setHttpStatusCode(400);\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(handler_, _setTransaction(&exTxn));\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        ASSERT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS);\n        ASSERT_EQ(std::string(ex.what()), \"test\");\n      }));\n  // onBody() is suppressed since ingress is complete after ingress onError()\n  // onEOM() is suppressed since ingress is complete after ingress onError()\n  EXPECT_CALL(transport_, sendAbort(_, _));\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_NE(\n            std::string(ex.what()).find(\"onIngressBody after ingress closed\"),\n            std::string::npos);\n      }));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&exTxn));\n\n  exTxn.setHandler(&handler_);\n  exTxn.onError(err);\n  // Since the transaction is already closed for ingress, giving it\n  // ingress body causes the transaction to be aborted and closed\n  // immediately.\n  exTxn.onIngressBody(makeBuf(10), 0);\n\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, ParseIngressErrorExTxnNonUnidirectional) {\n  // Test where the ex transaction using QoS0 gets Ingress error\n  auto& exTxn = makeExTxn();\n  HTTPException err(HTTPException::Direction::INGRESS, \"test\");\n  err.setHttpStatusCode(400);\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(handler_, _setTransaction(&exTxn));\n  // Ingress error will propagate\n  // even if INGRESS state is completed for unidrectional ex_txn\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        ASSERT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS);\n        ASSERT_EQ(std::string(ex.what()), \"test\");\n      }));\n\n  EXPECT_CALL(transport_, sendAbort(_, _));\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_NE(\n            std::string(ex.what()).find(\"onIngressBody after ingress closed\"),\n            std::string::npos);\n      }));\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&exTxn));\n\n  exTxn.setHandler(&handler_);\n  exTxn.onError(err);\n  // Since the transaction is already closed for ingress, giving it\n  // ingress body causes the transaction to be aborted and closed\n  // immediately.\n  exTxn.onIngressBody(makeBuf(10), 0);\n\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, IngressStateViolationWithByteEvents) {\n  auto& txn = makeTxn();\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  txn.setHandler(&handler_);\n  EXPECT_CALL(handler_, _onHeadersComplete(_));\n  txn.onIngressHeadersComplete(makeGetRequest());\n  EXPECT_CALL(handler_, _onEOM());\n  txn.onIngressEOM();\n\n  // When headers are sent, hold a byte event\n  EXPECT_CALL(transport_, sendHeaders(&txn, _, _, _))\n      .WillOnce(\n          InvokeWithoutArgs([&txn] { txn.incrementPendingByteEvents(); }));\n  txn.sendHeaders(getResponse(200));\n\n  EXPECT_CALL(transport_, sendAbort(_, _));\n  // The error is delivered immediately after abort is sent\n  EXPECT_CALL(handler_, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& ex) {\n        EXPECT_EQ(ex.getDirection(),\n                  HTTPException::Direction::INGRESS_AND_EGRESS);\n        EXPECT_NE(\n            std::string(ex.what()).find(\"onIngressEOM after ingress closed\"),\n            std::string::npos);\n      }));\n\n  // Double EOM\n  txn.onIngressEOM();\n\n  // The transaction detaches after the byte event is released\n  EXPECT_CALL(handler_, _detachTransaction());\n  EXPECT_CALL(transport_, detach(&txn));\n  txn.decrementPendingByteEvents();\n  eventBase_.loop();\n}\n\nTEST_F(DownstreamTransactionTest, SetHandlerWhenPaused) {\n  auto& txn = makeTxn(true, 10, 10);\n\n  EXPECT_CALL(handler_, _setTransaction(&txn));\n  txn.setHandler(&handler_);\n  txn.sendHeaders(getResponse(200));\n  EXPECT_CALL(handler_, _onEgressPaused());\n  txn.sendBody(makeBuf(20));\n  StrictMock<MockHTTPHandler> handler2;\n  EXPECT_CALL(handler2, _setTransaction(&txn));\n  EXPECT_CALL(handler2, _onEgressPaused());\n  txn.setHandler(&handler2);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQByteEventTrackerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <proxygen/lib/http/session/HQByteEventTracker.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <quic/api/test/Mocks.h>\n\n#include <chrono>\n\nusing namespace testing;\nusing namespace proxygen;\n\nusing QuicByteEvent = quic::ByteEvent;\nusing QuicByteEventType = quic::ByteEvent::Type;\n\nclass HQByteEventTrackerTest : public Test {\n public:\n  void SetUp() override {\n    socket_ = std::make_shared<quic::MockQuicSocket>(\n        nullptr, &connectionSetupCallback_, &connectionCallback_);\n    byteEventTracker_ =\n        std::make_shared<HQByteEventTracker>(nullptr, socket_.get(), streamId_);\n    txn_.setTransportCallback(&transportCallback_);\n  }\n\n  /**\n   * Setup an EXPECT_CALL for QuicSocket::registerTxCallback.\n   */\n  std::unique_ptr<quic::ByteEventCallback*> expectRegisterTxCallback(\n      const uint64_t offset,\n      quic::Expected<void, quic::LocalErrorCode> returnVal = {}) const {\n    auto capturedCallbackPtr =\n        std::make_unique<quic::ByteEventCallback*>(nullptr);\n    EXPECT_CALL(*socket_, registerTxCallback(streamId_, offset, _))\n        .WillOnce(\n            DoAll(SaveArg<2>(&*capturedCallbackPtr.get()), Return(returnVal)));\n    return capturedCallbackPtr;\n  }\n\n  /**\n   * Setup an EXPECT_CALL for QuicSocket::registerDeliveryCallback.\n   *\n   * Returns a unique_ptr<ptr*> where the inner pointer will be populated to\n   * point to the callback handler passed to registerDeliveryCallback.\n   */\n  std::unique_ptr<quic::ByteEventCallback*> expectRegisterDeliveryCallback(\n      const uint64_t offset,\n      quic::Expected<void, quic::LocalErrorCode> returnVal = {}) const {\n    auto capturedCallbackPtr =\n        std::make_unique<quic::ByteEventCallback*>(nullptr);\n    EXPECT_CALL(*socket_, registerDeliveryCallback(streamId_, offset, _))\n        .WillOnce(\n            DoAll(SaveArg<2>(&*capturedCallbackPtr.get()), Return(returnVal)));\n    return capturedCallbackPtr;\n  }\n\n  /**\n   * Get a matcher for a proxygen ByteEvent.\n   */\n  static auto getByteEventMatcher(ByteEvent::EventType eventType,\n                                  uint64_t offset) {\n    return AllOf(testing::Property(&ByteEvent::getType, eventType),\n                 testing::Property(&ByteEvent::getByteOffset, offset));\n  }\n\n protected:\n  const HTTPCodec::StreamID streamId_{1};\n  quic::MockConnectionSetupCallback connectionSetupCallback_;\n  quic::MockConnectionCallback connectionCallback_;\n  std::shared_ptr<quic::MockQuicSocket> socket_;\n  folly::EventBase eventBase_;\n  WheelTimerInstance transactionTimeouts_{std::chrono::milliseconds(500),\n                                          &eventBase_};\n  NiceMock<MockHTTPTransactionTransport> transport_;\n  StrictMock<MockHTTPHandler> handler_;\n  HTTP2PriorityQueue txnEgressQueue_;\n  HTTPTransaction txn_{TransportDirection::DOWNSTREAM,\n                       HTTPCodec::StreamID(1),\n                       1,\n                       transport_,\n                       txnEgressQueue_,\n                       transactionTimeouts_.getWheelTimer(),\n                       transactionTimeouts_.getDefaultTimeout()};\n  MockHTTPTransactionTransportCallback transportCallback_;\n  std::shared_ptr<ByteEventTracker> byteEventTracker_;\n};\n\n/**\n * Test when first and last body byte are written in the same buffer.\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteSingleWrite) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = 10;\n\n  InSequence s;\n\n  // first and last byte events created\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n\n  // first and last byte written\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(4, txn_.getNumPendingByteEvents());\n\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when first and last body byte are written in separate buffers.\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteSeparateWrites) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = 10;\n  const uint64_t firstWriteBytes = 5; // bytes written on first write\n\n  InSequence s;\n\n  // first and last byte events created\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n\n  // first byte written\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_, firstWriteBytes);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(3, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n\n  // last byte written\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(4, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n\n  // TX of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n\n  // ACK of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when first and last body byte are written in separate buffers.\n *\n * TX and ACK events are interleaved and extra bytes beyond the last byte\n * are reported written.\n */\nTEST_F(HQByteEventTrackerTest,\n       FirstLastBodyByteSeparateWritesInterleavedExtraBytes) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = 10;\n  const uint64_t firstWriteBytes = 5; // bytes written on first write\n\n  // first byte event created and byte written\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_, firstWriteBytes);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n\n  // TX and ACK of first byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  // last byte event created and byte written\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_,\n                                       lastBodyByteOffset + 40);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n\n  // TX and ACK of last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when the first and last body byte have the same offset (single byte txn)\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteSingleByte) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = firstBodyByteOffset;\n\n  InSequence s; // required due to same EXPECT_CALL triggered twice\n\n  // first byte event created and byte written\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  EXPECT_EQ(4, txn_.getNumPendingByteEvents());\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n\n  // TX of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n\n  // ACK of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when the first and last body byte have the same offset (single byte txn)\n *\n * Offset is zero.\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteSingleByteZeroOffset) {\n  const uint64_t firstBodyByteOffset = 0;\n  const uint64_t lastBodyByteOffset = firstBodyByteOffset;\n\n  InSequence s; // required due to same EXPECT_CALL triggered twice\n\n  // first byte event created and byte written\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  EXPECT_EQ(4, txn_.getNumPendingByteEvents());\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n\n  // TX of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventTX(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbTxCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::TX});\n\n  // ACK of first and last byte\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::FIRST_BYTE, firstBodyByteOffset)));\n  (*fbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = firstBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n  EXPECT_CALL(transportCallback_,\n              trackedByteEventAck(getByteEventMatcher(\n                  ByteEvent::EventType::LAST_BYTE, lastBodyByteOffset)));\n  (*lbbAckCbHandler)\n      ->onByteEvent(QuicByteEvent{.id = streamId_,\n                                  .offset = lastBodyByteOffset,\n                                  .type = QuicByteEventType::ACK});\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when the QUIC byte events are canceled after registration\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteCancellation) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = 10;\n\n  InSequence s;\n\n  // first byte event created and byte written\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  EXPECT_EQ(1, txn_.getNumPendingByteEvents());\n\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(firstBodyByteOffset);\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(firstBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_, firstBodyByteOffset);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(fbbTxCbHandler, NotNull());\n  ASSERT_THAT(fbbAckCbHandler, NotNull());\n\n  // last byte event created and byte written\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(3, txn_.getNumPendingByteEvents());\n\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(lastBodyByteOffset);\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(lastBodyByteOffset);\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(4, txn_.getNumPendingByteEvents());\n  ASSERT_THAT(lbbTxCbHandler, NotNull());\n  ASSERT_THAT(lbbAckCbHandler, NotNull());\n\n  EXPECT_CALL(transportCallback_, trackedByteEventTX(_)).Times(0);\n  (*fbbTxCbHandler)\n      ->onByteEventCanceled(QuicByteEvent{.id = streamId_,\n                                          .offset = firstBodyByteOffset,\n                                          .type = QuicByteEventType::TX});\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n\n  EXPECT_CALL(transportCallback_, trackedByteEventTX(_)).Times(0);\n  (*lbbTxCbHandler)\n      ->onByteEventCanceled(QuicByteEvent{.id = streamId_,\n                                          .offset = lastBodyByteOffset,\n                                          .type = QuicByteEventType::TX});\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n\n  EXPECT_CALL(transportCallback_, trackedByteEventAck(_)).Times(0);\n  (*fbbAckCbHandler)\n      ->onByteEventCanceled(QuicByteEvent{.id = streamId_,\n                                          .offset = firstBodyByteOffset,\n                                          .type = QuicByteEventType::ACK});\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n\n  EXPECT_CALL(transportCallback_, trackedByteEventAck(_)).Times(0);\n  (*lbbAckCbHandler)\n      ->onByteEventCanceled(QuicByteEvent{.id = streamId_,\n                                          .offset = lastBodyByteOffset,\n                                          .type = QuicByteEventType::ACK});\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n\n/**\n * Test when registration of QUIC byte events fails.\n *\n * Callbacks should be deleted, and thus there should be no error about leaks.\n */\nTEST_F(HQByteEventTrackerTest, FirstLastBodyByteErrorOnRegistration) {\n  const uint64_t firstBodyByteOffset = 1;\n  const uint64_t lastBodyByteOffset = 10;\n\n  // first and last byte events created\n  byteEventTracker_->addFirstBodyByteEvent(firstBodyByteOffset, &txn_);\n  byteEventTracker_->addLastByteEvent(&txn_, lastBodyByteOffset);\n  EXPECT_EQ(2, txn_.getNumPendingByteEvents());\n\n  // first and last byte written\n  EXPECT_CALL(transportCallback_, firstByteFlushed());\n  auto fbbTxCbHandler = expectRegisterTxCallback(\n      firstBodyByteOffset, quic::make_unexpected(quic::LocalErrorCode()));\n  auto fbbAckCbHandler = expectRegisterDeliveryCallback(\n      firstBodyByteOffset, quic::make_unexpected(quic::LocalErrorCode()));\n  EXPECT_CALL(transportCallback_, lastByteFlushed());\n  auto lbbTxCbHandler = expectRegisterTxCallback(\n      lastBodyByteOffset, quic::make_unexpected(quic::LocalErrorCode()));\n  auto lbbAckCbHandler = expectRegisterDeliveryCallback(\n      lastBodyByteOffset, quic::make_unexpected(quic::LocalErrorCode()));\n\n  byteEventTracker_->processByteEvents(byteEventTracker_, lastBodyByteOffset);\n  Mock::VerifyAndClearExpectations(&socket_);\n  Mock::VerifyAndClearExpectations(&transportCallback_);\n  EXPECT_EQ(0, txn_.getNumPendingByteEvents());\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQDownstreamSessionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Expected.h>\n#include <proxygen/lib/http/session/HQDownstreamSession.h>\n\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/session/test/HQDownstreamSessionTest.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\n#include <folly/futures/Future.h>\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\nusing namespace proxygen::hq;\nusing namespace quic;\nusing namespace testing;\nusing namespace std::chrono;\nusing Priority = quic::HTTPPriorityQueue::Priority;\n\nusing HQDownstreamSessionTestDeliveryAck = HQDownstreamSessionTest;\n\n// Use this test class for h3 server push tests\nusing HQDownstreamSessionTestPush = HQDownstreamSessionTest;\n\nnamespace {\nHTTPMessage getProgressiveGetRequest() {\n  auto req = proxygen::getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=1, i\");\n  return req;\n}\n} // namespace\n\nHTTPCodec::StreamID HQDownstreamSessionTest::sendRequest(const std::string& url,\n                                                         int8_t priority,\n                                                         bool eom) {\n  auto req = proxygen::getGetRequest();\n  req.setURL(url);\n  req.setPriority(priority);\n  return sendRequest(req, eom);\n}\n\nquic::StreamId HQDownstreamSessionTest::nextStreamId() {\n  auto id = nextStreamId_;\n  nextStreamId_ += 4;\n  return id;\n}\n\nquic::StreamId HQDownstreamSessionTest::sendRequest(\n    const proxygen::HTTPMessage& req, bool eom, quic::StreamId id) {\n  if (id == quic::kEightByteLimit) {\n    id = nextStreamId();\n  }\n  auto res = requests_.emplace(std::piecewise_construct,\n                               std::forward_as_tuple(id),\n                               std::forward_as_tuple(makeCodec(id)));\n  auto& request = res.first->second;\n  request.id = request.codec->createStream();\n  request.readEOF = eom;\n  request.codec->generateHeader(request.buf, request.id, req, eom);\n  return id;\n}\n\nquic::StreamId HQDownstreamSessionTest::sendHeader() {\n  return sendRequest(\"/\", 0, false);\n}\n\nfolly::Promise<folly::Unit> HQDownstreamSessionTest::sendRequestLater(\n    proxygen::HTTPMessage req, bool eof) {\n  folly::Promise<folly::Unit> reqp;\n  reqp.getSemiFuture().via(&eventBase_).thenValue([=, this](auto&&) {\n    auto id = sendRequest(req, eof);\n    socketDriver_->addReadEvent(\n        id, getStream(id).buf.move(), std::chrono::milliseconds(0));\n    socketDriver_->addReadEOF(id, std::chrono::milliseconds(0));\n    // note that eof=true used to terminate the connection and now it\n    // no longer does\n  });\n  return reqp;\n}\n\nvoid HQDownstreamSessionTest::SetUp() {\n  SetUpBase();\n  SetUpOnTransportReady();\n}\n\nvoid HQDownstreamSessionTest::TearDown() {\n  // with these versions we need to wait for GOAWAY delivery on the control\n  // stream\n  eventBase_.loop();\n}\n\nvoid HQDownstreamSessionTest::SetUpBase() {\n  HQSessionTest::SetUp();\n  streamTransInfo_ = {.totalHeadOfLineBlockedTime =\n                          std::chrono::milliseconds(100),\n                      .holbCount = 2,\n                      .isHolb = true};\n\n  EXPECT_CALL(*socketDriver_->getSocket(), getStreamTransportInfo(testing::_))\n      .WillRepeatedly(testing::Return(streamTransInfo_));\n}\n\nvoid HQDownstreamSessionTest::SetUpOnTransportReady() {\n  hqSession_->onTransportReady();\n\n  if (createControlStreams()) {\n    eventBase_.loopOnce();\n    EXPECT_EQ(httpCallbacks_.settings, 1);\n  }\n}\n\ntemplate <class HandlerType>\nstd::unique_ptr<testing::StrictMock<HandlerType>>\nHQDownstreamSessionTest::addSimpleStrictHandlerBase() {\n  auto handler = std::make_unique<testing::StrictMock<HandlerType>>();\n\n  // The ownership model here is suspect, but assume the callers won't destroy\n  // handler before it's requested\n  auto rawHandler = handler.get();\n  EXPECT_CALL(getMockController(), getRequestHandler(testing::_, testing::_))\n      .WillOnce(testing::Return(rawHandler))\n      .RetiresOnSaturation();\n\n  EXPECT_CALL(*handler, _setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&handler->txn_));\n\n  return handler;\n}\n\nstd::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>\nHQDownstreamSessionTest::addSimpleStrictHandler() {\n  return addSimpleStrictHandlerBase<proxygen::MockHTTPHandler>();\n}\n\nstd::pair<quic::StreamId,\n          std::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>>\nHQDownstreamSessionTest::checkRequest(proxygen::HTTPMessage req) {\n  auto id = sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM(\n      [hdlr = handler.get()] { hdlr->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  return {id, std::move(handler)};\n}\n\nvoid HQDownstreamSessionTest::flushRequestsAndWaitForReads(\n    bool eof,\n    std::chrono::milliseconds eofDelay,\n    std::chrono::milliseconds initialDelay,\n    std::function<void()> extraEventsFn) {\n  while (!flushRequests(eof, eofDelay, initialDelay, extraEventsFn)) {\n    CHECK(eventBase_.loop());\n  }\n  CHECK(eventBase_.loop());\n}\n\nvoid HQDownstreamSessionTest::flushRequestsAndLoop(\n    bool eof,\n    std::chrono::milliseconds eofDelay,\n    std::chrono::milliseconds initialDelay,\n    std::function<void()> extraEventsFn) {\n  flushRequests(eof, eofDelay, initialDelay, extraEventsFn);\n  CHECK(eventBase_.loop());\n}\n\nvoid HQDownstreamSessionTest::flushRequestsAndLoopN(\n    uint64_t n,\n    bool eof,\n    std::chrono::milliseconds eofDelay,\n    std::chrono::milliseconds initialDelay,\n    std::function<void()> extraEventsFn) {\n  flushRequests(eof, eofDelay, initialDelay, extraEventsFn);\n  for (uint64_t i = 0; i < n; i++) {\n    eventBase_.loopOnce();\n  }\n}\n\nbool HQDownstreamSessionTest::flushRequests(\n    bool eof,\n    std::chrono::milliseconds eofDelay,\n    std::chrono::milliseconds initialDelay,\n    std::function<void()> extraEventsFn) {\n  bool done = true;\n\n  if (!encoderWriteBuf_.empty()) {\n    socketDriver_->addReadEvent(\n        kQPACKEncoderIngressStreamId, encoderWriteBuf_.move(), initialDelay);\n    initialDelay = std::chrono::milliseconds(0);\n  }\n  for (auto& req : requests_) {\n    if (socketDriver_->isStreamIdle(req.first)) {\n      continue;\n    }\n    if (req.second.buf.chainLength() > 0) {\n      socketDriver_->addReadEvent(\n          req.first, req.second.buf.move(), initialDelay);\n      done = false;\n    }\n    // EOM -> stream EOF\n    if (req.second.readEOF) {\n      socketDriver_->addReadEOF(req.first, eofDelay);\n      done = false;\n    }\n  }\n  if (extraEventsFn) {\n    extraEventsFn();\n  }\n  if (eof || eofDelay.count() > 0) {\n    /*  wonkiness.  Should somehow close the connection?\n     * socketDriver_->addReadEOF(1, eofDelay);\n     */\n  }\n  return done;\n}\n\ntesting::StrictMock<proxygen::MockController>&\nHQDownstreamSessionTest::getMockController() {\n  return controllerContainer_.mockController;\n}\n\nstd::unique_ptr<proxygen::HTTPCodec> HQDownstreamSessionTest::makeCodec(\n    proxygen::HTTPCodec::StreamID id) {\n  return std::make_unique<proxygen::hq::HQStreamCodec>(\n      id,\n      proxygen::TransportDirection::UPSTREAM,\n      qpackCodec_,\n      encoderWriteBuf_,\n      decoderWriteBuf_,\n      [] { return std::numeric_limits<uint64_t>::max(); },\n      ingressSettings_);\n}\n\nHQDownstreamSessionTest::ClientStream& HQDownstreamSessionTest::getStream(\n    proxygen::HTTPCodec::StreamID id) {\n  auto it = requests_.find(id);\n  CHECK(it != requests_.end());\n  return it->second;\n}\n\nvoid HQDownstreamSessionTest::expectTransactionTimeout(\n    testing::StrictMock<proxygen::MockHTTPHandler>& handler,\n    folly::Function<void()> fn) {\n  EXPECT_CALL(getMockController(),\n              getTransactionTimeoutHandler(testing::_, testing::_))\n      .WillOnce(testing::Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&handler.txn_));\n  handler.expectError(\n      [&handler, &fn](const proxygen::HTTPException& ex) mutable {\n        if (fn) {\n          fn();\n        }\n        EXPECT_FALSE(ex.hasHttpStatusCode());\n        handler.sendHeaders(408, 100);\n        handler.sendBody(100);\n        handler.sendEOM();\n      });\n  handler.expectDetachTransaction();\n}\n\nstd::unique_ptr<MockSessionObserver>\nHQDownstreamSessionTest::addMockSessionObserver(\n    MockSessionObserver::EventSet eventSet) {\n  auto observer = std::make_unique<NiceMock<MockSessionObserver>>(eventSet);\n  EXPECT_CALL(*observer, attached(_));\n  hqSession_->addObserver(observer.get());\n  return observer;\n}\n\nstd::shared_ptr<MockSessionObserver>\nHQDownstreamSessionTest::addMockSessionObserverShared(\n    MockSessionObserver::EventSet eventSet) {\n  auto observer = std::make_shared<NiceMock<MockSessionObserver>>(eventSet);\n  EXPECT_CALL(*observer, attached(_));\n  hqSession_->addObserver(observer);\n  return observer;\n}\n\nTEST_P(HQDownstreamSessionTest, GetMaxPushIdOK) {\n  folly::Optional<hq::PushId> expectedId = hqSession_->getMaxAllowedPushId();\n  EXPECT_EQ(expectedId, folly::none);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SimpleGet) {\n  auto idh = checkRequest();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[idh.first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[idh.first].writeEOF);\n  // Checks that the server response is sent using the QPACK dynamic table\n  CHECK_GE(qpackCodec_.getCompressionInfo().ingress.headerTableSize_, 0);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, PriorityUpdateIntoTransport) {\n  auto request = getProgressiveGetRequest();\n  auto id = sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  socketDriver_->expectSetPriority(id, Priority(1, true));\n  handler->expectHeaders();\n  handler->expectEOM([&]() {\n    auto resp = makeResponse(200, 0);\n    std::get<0>(resp)->getHeaders().add(HTTP_HEADER_PRIORITY, \"u=2\");\n    socketDriver_->expectSetPriority(id, Priority(2, false));\n    handler->sendRequest(*std::get<0>(resp));\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, DisableEgressPrioritization) {\n  hqSession_->setEnableEgressPrioritization(false);\n  auto request = getProgressiveGetRequest();\n  sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders(\n      [&]() { handler->txn_->updateAndSendPriority(HTTPPriority(2, false)); });\n  handler->expectEOM([&]() {\n    auto resp = makeResponse(200, 0);\n    std::get<0>(resp)->getHeaders().add(HTTP_HEADER_PRIORITY, \"u=2\");\n    handler->sendRequest(*std::get<0>(resp));\n  });\n\n  EXPECT_CALL(*socketDriver_->getSocket(), setStreamPriority(_, _)).Times(0);\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, ReplyResponsePriority) {\n  auto request = getProgressiveGetRequest();\n  auto id = sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  socketDriver_->expectSetPriority(id, Priority(1, true));\n  handler->expectHeaders();\n  handler->expectEOM([&]() {\n    auto resp = makeResponse(200, 0);\n    EXPECT_CALL(*socketDriver_->getSocket(), getStreamPriority(_))\n        .Times(1)\n        .WillOnce(Return(quic::HTTPPriorityQueue::Priority(1, true)));\n    handler->sendRequest(*std::get<0>(resp));\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SetLocalPriorityOnHeadersComplete) {\n  uint8_t urgency = 5;\n  bool incremental = false;\n  auto request = getProgressiveGetRequest();\n  auto id = sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  // Check that the priority is set from the request headers\n  {\n    InSequence enforceOrder;\n    socketDriver_->expectSetPriority(id, Priority(1, true));\n    socketDriver_->expectSetPriority(id, Priority(urgency, incremental));\n  }\n  handler->expectHeaders([&]() {\n    handler->txn_->updateAndSendPriority(HTTPPriority(urgency, incremental));\n  });\n  handler->expectEOM([&]() {\n    auto resp = makeResponse(200, 0);\n    EXPECT_CALL(*socketDriver_->getSocket(), getStreamPriority(_))\n        .Times(1)\n        .WillOnce(\n            Return(quic::HTTPPriorityQueue::Priority(urgency, incremental)));\n    handler->sendRequest(*std::get<0>(resp));\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SetLocalPriorityAfterClientEOM) {\n  uint8_t urgency = 5;\n  bool incremental = false;\n  auto request = getProgressiveGetRequest();\n  auto id = sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  {\n    InSequence enforceOrder;\n    // Check that the priority is set from the request headers\n    socketDriver_->expectSetPriority(id, Priority(1, true));\n    // Check that the priority is set from the local update\n    socketDriver_->expectSetPriority(id, Priority(urgency, incremental));\n  }\n  handler->expectHeaders();\n  handler->expectEOM([&]() {\n    handler->txn_->updateAndSendPriority(HTTPPriority(urgency, incremental));\n    auto resp = makeResponse(200, 0);\n    EXPECT_CALL(*socketDriver_->getSocket(), getStreamPriority(_))\n        .Times(1)\n        .WillOnce(\n            Return(quic::HTTPPriorityQueue::Priority(urgency, incremental)));\n    handler->sendRequest(*std::get<0>(resp));\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestPush, PushPriority) {\n  sendRequest(\"/\", 1);\n  HTTPMessage promiseReq, parentResp;\n  promiseReq.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  promiseReq.setURL(\"/\");\n  promiseReq.setHTTPPriority(0, false);\n\n  parentResp.setStatusCode(200);\n  parentResp.setStatusMessage(\"Ohai\");\n\n  HTTPMessage pushResp(parentResp);\n  pushResp.setHTTPPriority(1, false);\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n  handler->expectHeaders();\n  HTTPCodec::StreamID pushStreamId = 0;\n  handler->expectEOM([&] {\n    EXPECT_CALL(*socketDriver_->getSocket(),\n                setStreamPriority(handler->txn_->getID(), _))\n        .Times(0);\n    handler->txn_->sendHeaders(parentResp);\n    handler->txn_->sendBody(makeBuf(100));\n\n    auto outgoingStreams = hqSession_->getNumOutgoingStreams();\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    EXPECT_EQ(hqSession_->getNumOutgoingStreams(), outgoingStreams + 1);\n    // PushPromise doesn't update parent streram's priority. It does update push\n    // stream priority\n    EXPECT_CALL(*socketDriver_->getSocket(),\n                setStreamPriority(handler->txn_->getID(), _))\n        .Times(0);\n    socketDriver_->expectSetPriority(pushTxn->getID(), Priority(0, false));\n    pushTxn->sendHeaders(promiseReq);\n    pushStreamId = pushTxn->getID();\n    socketDriver_->expectSetPriority(pushStreamId, Priority(1, false));\n    pushTxn->sendHeaders(pushResp);\n    pushTxn->sendBody(makeBuf(200));\n    pushTxn->sendEOM();\n  });\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  flushRequestsAndLoopN(1);\n  handler->txn_->sendEOM();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, OnPriorityCallback) {\n  // Simulate priority arriving too early, connection still valid\n  // this is going to be stored and applied when receiving headers\n  hqSession_->onPriority(0, HTTPPriority(3, false));\n  socketDriver_->expectSetPriority(0, Priority(3, false));\n  auto id = sendRequest(getProgressiveGetRequest());\n  CHECK_EQ(id, 0);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(200, 1000);\n    // Priority update on the stream\n    socketDriver_->expectSetPriority(0, Priority(2, true));\n    hqSession_->onPriority(id, HTTPPriority(2, true));\n    handler->sendBody(1000);\n    handler->sendEOM();\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  // Priority on a stream we can't find - no-op\n  hqSession_->onPriority(id, HTTPPriority(4, true));\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, GetStopSending) {\n  auto id = sendRequest(getGetRequest());\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([hdlr = handler.get()] { hdlr->sendHeaders(200, 100); });\n  handler->expectError([](const proxygen::HTTPException& ex) {\n    EXPECT_EQ(ex.getCodecStatusCode(), ErrorCode::CANCEL);\n    EXPECT_EQ(ex.getProxygenError(), kErrorStreamAbort);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  socketDriver_->addStopSending(id, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, HttpRateLimitNormal) {\n  // The rate-limiting code grabs the event base from the EventBaseManager,\n  // so we need to set it.\n  folly::EventBaseManager::get()->setEventBase(&eventBase_, false);\n  uint32_t rspLengthBytes = 100000;\n\n  // make sure we are not limited by connection flow control\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(rspLengthBytes *\n                                                             2);\n  // Create a request\n  auto id = sendRequest();\n\n  // Set a low rate-limit on the transaction\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders([&] {\n    uint32_t rateLimit_kbps = 640;\n    handler1->txn_->setEgressRateLimit(rateLimit_kbps * 1024);\n  });\n  // Send a somewhat big response that we know will get rate-limited\n  handler1->expectEOM([&] {\n    // At 640kbps, this should take slightly over 800ms\n    handler1->sendHeaders(200, rspLengthBytes);\n    handler1->sendBody(rspLengthBytes);\n  });\n  EXPECT_CALL(*handler1, _onEgressPaused()).Times(AtLeast(1));\n  handler1->expectEgressResumed([&handler1] { handler1->txn_->sendEOM(); });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  // Check that the write side got blocked\n  socketDriver_->expectStreamWritesPaused(id);\n  // Open flow control again\n  socketDriver_->getSocket()->setStreamFlowControlWindow(id,\n                                                         rspLengthBytes * 2);\n  flushRequestsAndLoop();\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SimplePost) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto& request = getStream(id);\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(10), HTTPCodec::NoPadding, true);\n  request.readEOF = true;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectBody(); // should check length too but meh\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SimplePostWithPadding) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto& request = getStream(id);\n  // Include padding on the request\n  request.codec->generatePadding(request.buf, request.id, 10'000);\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(10), HTTPCodec::NoPadding, true);\n  request.readEOF = true;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectBody([](uint64_t, std::shared_ptr<folly::IOBuf> body) {\n    EXPECT_EQ(body->computeChainDataLength(), 10);\n  });\n  handler->expectEOM([&handler] {\n    // Include padding on the reply too\n    handler->sendReplyWithBody(200, 100, true, true, false, 200);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 315);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, PaddingSM) {\n  auto id = nextStreamId();\n  auto res = requests_.emplace(std::piecewise_construct,\n                               std::forward_as_tuple(id),\n                               std::forward_as_tuple(makeCodec(id)));\n  auto& request = res.first->second;\n  request.id = request.codec->createStream();\n  request.readEOF = false;\n  request.codec->generatePadding(request.buf, request.id, 1'000);\n  request.codec->generateHeader(\n      request.buf, request.id, getPostRequest(100), false);\n  request.codec->generatePadding(request.buf, request.id, 2'000);\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(100), HTTPCodec::NoPadding, true);\n  request.codec->generatePadding(request.buf, request.id, 4'000);\n  request.readEOF = true;\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectBody([](uint64_t, std::shared_ptr<folly::IOBuf> body) {\n    EXPECT_EQ(body->computeChainDataLength(), 100);\n  });\n  handler->expectEOM([&handler] {\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendHeaders(getResponse(200));\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendChunkHeader(10);\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendBody(makeBuf(10));\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendChunkTerminator();\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendEOM();\n    handler->txn_->sendPadding(100);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].readOffset, 7'100);\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 610);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SimpleGetEofDelay) {\n  auto idh = checkRequest();\n  flushRequestsAndLoop(false, std::chrono::milliseconds(10));\n  EXPECT_GT(socketDriver_->streams_[idh.first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[idh.first].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, UnfinishedPost) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto& request = getStream(id);\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(9), HTTPCodec::NoPadding, true);\n  request.readEOF = true;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectError([&handler](const proxygen::HTTPException& ex) {\n    // The HTTP/1.1 parser tracks content-length and 400's if it is short\n    // The HQStreamCodec does no such thing, and it's caught by\n    // HTTPTransaction, with a different error.\n    EXPECT_EQ(ex.getProxygenError(), kErrorParseBody);\n    handler->sendReplyWithBody(400, 100);\n    // afrind: this logic is in HTTPSession so should move to base or\n    // duplicate in HQSession (see also custom error handlers)\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, Multiplexing) {\n  std::vector<std::unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n  for (auto n = 0; n < 10; n++) {\n    auto idh = checkRequest();\n    handlers.emplace_back(std::move(idh.second));\n  }\n  flushRequestsAndWaitForReads();\n  for (auto& req : requests_) {\n    EXPECT_GT(socketDriver_->streams_[req.first].writeBuf.chainLength(), 110);\n    EXPECT_TRUE(socketDriver_->streams_[req.first].writeEOF);\n  }\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, Maxreadsperloop) {\n  std::vector<std::unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n  for (auto n = 0; n < 20; n++) {\n    auto idh = checkRequest();\n    handlers.emplace_back(std::move(idh.second));\n  }\n\n  flushRequestsAndLoopN(1);\n  // After one loop, reads on some streams will be idle\n  // while on some other they will not\n  int idleCount = 0;\n  int nonIdleCount = 0;\n  for (auto& req : requests_) {\n    if (socketDriver_->isStreamIdle(req.first)) {\n      idleCount++;\n    } else {\n      nonIdleCount++;\n    }\n  }\n  EXPECT_GT(idleCount, 0);\n  EXPECT_GT(nonIdleCount, 0);\n\n  // Now finish all the reads\n  eventBase_.loop();\n  for (auto& req : requests_) {\n    EXPECT_GT(socketDriver_->streams_[req.first].writeBuf.chainLength(), 110);\n    EXPECT_TRUE(socketDriver_->streams_[req.first].writeEOF);\n  }\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, OnFlowControlUpdate) {\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    handler->sendHeaders(200, 100);\n    handler->txn_->sendBody(makeBuf(100));\n  });\n  handler->expectEgressPaused();\n  handler->expectEgressResumed([&handler] { handler->txn_->sendEOM(); });\n  handler->expectDetachTransaction();\n\n  // Initialize the flow control window to less than the response body\n  socketDriver_->setStreamFlowControlWindow(id, 10);\n  flushRequestsAndLoop();\n  // Check that the write side got blocked\n  socketDriver_->expectStreamWritesPaused(id);\n  // Open the flow control window\n  socketDriver_->getSocket()->setStreamFlowControlWindow(id, 200);\n  CHECK(eventBase_.loop());\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, OnFlowControlUpdateOnUnknownStream) {\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n\n  // Call flowControlUpdate on a stream the Application doesn't know\n  socketDriver_->sock_->connCb_->onFlowControlUpdate(id + 4);\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\n// This test does not work with header compression\nTEST_P(HQDownstreamSessionTest, OnConnectionWindowPartialHeaders) {\n  // Only enough conn window to send headers initially.\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n\n  // Initialize the flow control window to less than the response body\n  socketDriver_->setConnectionFlowControlWindow(10 + numCtrlStreams_);\n  flushRequestsAndLoop();\n  // Check that the write side got blocked\n  socketDriver_->expectConnWritesPaused();\n  // We should have some bytes pending to be written out in the QPACK Encoder\n  // stream\n  EXPECT_GT(socketDriver_->streams_[kQPACKEncoderEgressStreamId]\n                .writeBuf.chainLength(),\n            0);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n  // Open the flow control window\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(200);\n  CHECK(eventBase_.loop());\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, OnConnectionWindowPartialBody) {\n  flushRequestsAndLoop(); // loop once for SETTINGS, etc\n  // Only enough conn window to send headers initially.\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  // TODO: we should probably pause egress on conn limited.\n  // handler->expectEgressPaused();\n  // handler->expectEgressResumed();\n  handler->expectDetachTransaction();\n\n  // Initialize the flow control window to less than the response body\n  socketDriver_->setConnectionFlowControlWindow(110 + numCtrlStreams_);\n  flushRequestsAndLoop();\n  // Check that the write side got blocked\n  socketDriver_->expectConnWritesPaused();\n  // We should have some bytes pending to be written out in the QPACK Encoder\n  // stream\n  EXPECT_GT(socketDriver_->streams_[kQPACKEncoderEgressStreamId]\n                .writeBuf.chainLength(),\n            0);\n  EXPECT_GT(qpackCodec_.getCompressionInfo().egress.headerTableSize_, 0);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n  // Open the flow control window\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(200 +\n                                                             numCtrlStreams_);\n  CHECK(eventBase_.loop());\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SeparateEom) {\n  // Only enough conn window to send headers initially.\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    handler->sendHeaders(200, 100);\n    handler->sendBody(100);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n\n  handler->sendEOM();\n  // Open the flow control window\n  CHECK(eventBase_.loop());\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nstd::unique_ptr<folly::IOBuf> getSimpleRequestData() {\n  std::string req(\"GET / HTTP/1.1\\nHost: www.facebook.com\\n\\n\");\n  return folly::IOBuf::copyBuffer(req);\n}\n\nstd::tuple<size_t, size_t, size_t> estimateResponseSize(bool isHq,\n                                                        HTTPMessage msg,\n                                                        size_t contentLength,\n                                                        size_t chunkSize) {\n  folly::IOBufQueue estimateSizeBuf{folly::IOBufQueue::cacheChainLength()};\n  std::unique_ptr<HTTPCodec> codec;\n  QPACKCodec qpackCodec;\n  folly::IOBufQueue encoderWriteBuf{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue decoderWriteBuf{folly::IOBufQueue::cacheChainLength()};\n  HTTPSettings dummySettings;\n  qpackCodec.setEncoderHeaderTableSize(kQPACKTestDecoderMaxTableSize);\n  if (isHq) {\n    codec = std::make_unique<hq::HQStreamCodec>(\n        0,\n        TransportDirection::DOWNSTREAM,\n        qpackCodec,\n        encoderWriteBuf,\n        decoderWriteBuf,\n        [] { return std::numeric_limits<uint64_t>::max(); },\n        dummySettings);\n  } else {\n    codec = std::make_unique<HTTP1xCodec>(TransportDirection::DOWNSTREAM, true);\n  }\n\n  MockHTTPCodecCallback callback;\n  codec->setCallback(&callback);\n  auto txn = codec->createStream();\n\n  if (!isHq) {\n    EXPECT_CALL(callback, onHeadersComplete(_, _));\n    EXPECT_CALL(callback, onMessageBegin(_, _));\n    codec->onIngress(*getSimpleRequestData());\n  }\n\n  codec->generateHeader(estimateSizeBuf, txn, msg);\n  size_t currentLength = contentLength;\n\n  bool chunking = (chunkSize != 0);\n  if (!chunking) {\n    chunkSize = std::numeric_limits<size_t>::max();\n  }\n  auto currentSize = estimateSizeBuf.chainLength();\n  while (currentLength > 0) {\n    uint32_t toSend = std::min(currentLength, chunkSize);\n    std::vector<uint8_t> buf;\n    buf.resize(toSend, 'a');\n    if (chunking) {\n      codec->generateChunkHeader(estimateSizeBuf, txn, toSend);\n    }\n    codec->generateBody(estimateSizeBuf,\n                        txn,\n                        folly::IOBuf::copyBuffer(buf),\n                        HTTPCodec::NoPadding,\n                        false);\n    if (chunking) {\n      codec->generateChunkTerminator(estimateSizeBuf, txn);\n    }\n    currentLength -= toSend;\n  }\n  size_t framingOverhead =\n      estimateSizeBuf.chainLength() - currentSize - contentLength;\n  currentSize = estimateSizeBuf.chainLength();\n  codec->generateEOM(estimateSizeBuf, txn);\n\n  size_t eomSize = estimateSizeBuf.chainLength() - currentSize;\n  size_t estimatedSize = estimateSizeBuf.chainLength();\n  return std::tuple<size_t, size_t, size_t>(\n      estimatedSize, framingOverhead, eomSize);\n}\n\nTEST_P(HQDownstreamSessionTest, PendingEomBuffered) {\n  size_t contentLength = 100;\n  size_t chunkSize = 5;\n\n  auto reply = makeResponse(200);\n  reply->setIsChunked(true);\n  size_t estimatedSize = 0;\n  size_t framingOverhead = 0;\n  size_t eomSize = 0;\n  std::tie(estimatedSize, framingOverhead, eomSize) =\n      estimateResponseSize(true, *reply, contentLength, chunkSize);\n  // EOMs are 0 bytes in H3, but there is framing overhead of at least two\n  // bytes.\n  auto bytesWithheld = 2;\n\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler, contentLength, chunkSize] {\n    handler->sendChunkedReplyWithBody(\n        200, contentLength, chunkSize, false, true);\n  });\n\n  // Set the flow control window to be less than the EOM overhead added by\n  // the codec\n  socketDriver_->setStreamFlowControlWindow(id, estimatedSize - bytesWithheld);\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(),\n            estimatedSize - bytesWithheld);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n\n  handler->expectDetachTransaction();\n  socketDriver_->getSocket()->setStreamFlowControlWindow(id, estimatedSize);\n\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(), estimatedSize);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, PendingEomQueuedNotFlushed) {\n  auto reply = makeResponse(200);\n  reply->setWantsKeepalive(true);\n  reply->getHeaders().add(HTTP_HEADER_CONTENT_LENGTH,\n                          folly::to<std::string>(1));\n  size_t estimatedSize = 0;\n  size_t framingOverhead = 0;\n  size_t eomSize = 0;\n  std::tie(estimatedSize, framingOverhead, eomSize) =\n      estimateResponseSize(true, *reply, 1, 0);\n  CHECK_EQ(eomSize, 0);\n  auto bytesWithheld = framingOverhead;\n\n  auto id = sendRequest(getGetRequest());\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler, this, id, estimatedSize, bytesWithheld] {\n    // Initialize the flow control window to just less than the\n    // estimated size of the eom codec which the codec generates..\n    socketDriver_->setStreamFlowControlWindow(id,\n                                              estimatedSize - bytesWithheld);\n    handler->sendReplyWithBody(200, 1);\n  });\n\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(),\n            estimatedSize - bytesWithheld);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n\n  handler->expectDetachTransaction();\n  socketDriver_->getSocket()->setStreamFlowControlWindow(id, estimatedSize);\n\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(), estimatedSize);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, PendingEomQueuedNotFlushedConn) {\n  // flush control streams first\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n\n  auto reply = makeResponse(200);\n  reply->setWantsKeepalive(true);\n  size_t estimatedSize = 0;\n  size_t framingOverhead = 0;\n  size_t eomSize = 0;\n  std::tie(estimatedSize, framingOverhead, eomSize) =\n      estimateResponseSize(true, *reply, 1, 0);\n\n  // No EOM yet\n  auto id = sendRequest(getGetRequest(), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler] { handler->sendHeaders(200, 1); });\n  flushRequestsAndLoopN(1);\n\n  socketDriver_->addReadEOF(id, std::chrono::milliseconds(0));\n  handler->expectEOM([&handler, this] {\n    handler->txn_->sendBody(makeBuf(1));\n    handler->txn_->sendEOM();\n    socketDriver_->setConnectionFlowControlWindow(1);\n  });\n\n  // Set the conn flow control to be enough for the body byte but not enough\n  // for the framing overhead\n  auto remaining = framingOverhead + eomSize;\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(),\n            estimatedSize - remaining);\n  EXPECT_FALSE(socketDriver_->streams_[id].writeEOF);\n\n  handler->expectDetachTransaction();\n  for (size_t i = 0; i < remaining; i++) {\n    socketDriver_->getSocket()->setConnectionFlowControlWindow(1);\n\n    CHECK(eventBase_.loop());\n    EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(),\n              estimatedSize - remaining + i);\n\n    EXPECT_TRUE(socketDriver_->streams_[id].writeEOF != (i < remaining - 1));\n  }\n\n  // Need flow control for goaway\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(100);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SendEomLaterChunked) {\n  size_t contentLength = 100;\n  size_t chunkSize = 10;\n\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, contentLength, chunkSize] {\n    handler->sendChunkedReplyWithBody(\n        200, contentLength, chunkSize, false, false);\n  });\n  handler->expectEOM([&handler] { handler->sendEOM(); });\n  handler->expectDetachTransaction();\n\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(), contentLength);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SendEomLater) {\n  size_t contentLength = 100;\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, contentLength] {\n    handler->sendHeaders(200, contentLength);\n    handler->sendBody(contentLength);\n  });\n  handler->expectEOM([&handler] { handler->sendEOM(); });\n  handler->expectDetachTransaction();\n\n  flushRequestsAndLoop();\n  CHECK(eventBase_.loop());\n  EXPECT_GE(socketDriver_->streams_[id].writeBuf.chainLength(), contentLength);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\n// Invoke notifyPendingShutdown, which will include an outgoing\n// Connection: close header on the next outbound headers.  The next incoming\n// request containing a Connection: close header will complete the drain state\n// machine\n\n// closeWhenIdle on an idle conn - immediate delete\nTEST_P(HQDownstreamSessionTest, ShutdownCloseIdle) {\n  EXPECT_TRUE(hqSession_->isReusable());\n  hqSession_->closeWhenIdle();\n}\n\n// closeWhenIdle invoked when a request is open, delete happens when it finishes\nTEST_P(HQDownstreamSessionTest, ShutdownCloseIdleReq) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([this] {\n    hqSession_->closeWhenIdle();\n    EXPECT_TRUE(hqSession_->isClosing());\n  });\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\n// dropConnection invoked while a request being processed, it receives an\n// error\nTEST_P(HQDownstreamSessionTest, ShutdownDropWithReq) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  hqSession_->dropConnection();\n}\n\n// dropConnection invoked while a request is partial, it receives an\n// error from the transport\nTEST_P(HQDownstreamSessionTest, ShutdownDropWithPartialReq) {\n  sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  hqSession_->dropConnection();\n}\n\n// Call drop connection while there are bytes pending to egress\nTEST_P(HQDownstreamSessionTest, DropConnectionPendingEgress) {\n  // NOTE: this test assumes that dropConnection() gets called by the handler\n  // before the session has the chance to write data.\n  // This is not true anymore when there are control streams\n  // So let's just loop a bit to give time to the Downstream Session to send the\n  // control stream preface\n  flushRequestsAndLoop();\n\n  sendRequest(getGetRequest());\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, this] {\n    handler->sendReplyWithBody(200, 1);\n    eventBase_.runInLoop([this] { hqSession_->dropConnection(); }, true);\n  });\n  handler->expectEOM();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, TestInfoCallbacks) {\n  folly::Optional<HTTPCodec::StreamID> id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  EXPECT_CALL(infoCb_, onRequestBegin(_)).Times(1);\n  EXPECT_CALL(infoCb_, onActivateConnection(_)).Times(1);\n  EXPECT_CALL(infoCb_, onIngressMessage(_, _)).Times(1);\n  EXPECT_CALL(infoCb_, onRead(_, _, id)).Times(AtLeast(2));\n  EXPECT_CALL(infoCb_, onWrite(_, _)).Times(AtLeast(1));\n  EXPECT_CALL(infoCb_, onDestroy(_)).Times(1);\n  EXPECT_CALL(infoCb_, onRequestEnd(_, _)).Times(1);\n  EXPECT_CALL(infoCb_, onDeactivateConnection(_)).Times(1);\n  flushRequestsAndLoop();\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, NotifyDropNoStreams) {\n  hqSession_->notifyPendingShutdown();\n  eventBase_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, ShutdownDropWithUnflushedResp) {\n  auto id = sendRequest();\n  // should be enough to trick HQSession into serializing the EOM into\n  // HQStreamTransport but without enough to send it.\n  // HTTP/3 has an extra grease frame on the first transaction\n  socketDriver_->setStreamFlowControlWindow(id, 209);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    handler->sendChunkedReplyWithBody(200, 100, 100, false, true);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  hqSession_->dropConnection();\n}\n\n// rst_stream while a request is partial, terminate cleanly\nTEST_P(HQDownstreamSessionTest, Cancel) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([this, id] {\n    socketDriver_->addReadError(id,\n                                HTTP3::ErrorCode::HTTP_INTERNAL_ERROR,\n                                std::chrono::milliseconds(0));\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[id].error,\n            HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n}\n\nTEST_P(HQDownstreamSessionTest, EndOfStreamWithPartialFrame) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto& request = getStream(id);\n  // generate body + EOM, but trim the last byte\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  request.codec->generateBody(\n      writeBuf, request.id, makeBuf(10), HTTPCodec::NoPadding, true);\n  request.buf.append(writeBuf.split(writeBuf.chainLength() - 1));\n  request.readEOF = true;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_FRAME_ERROR);\n}\n\n// read() returns a LocalErrorCode\nTEST_P(HQDownstreamSessionTest, ReadErrorSync) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([this, id] {\n    // mark the stream in read error and trigger a readAvailable call\n    socketDriver_->setReadError(id);\n    // This is just to trigger readAvailable\n    socketDriver_->addReadEvent(id, makeBuf(10), milliseconds(0));\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\n// Connection dies in error with an open stream\nTEST_P(HQDownstreamSessionTest, TransportErrorWithOpenStream) {\n  sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([this] {\n    eventBase_.runInLoop([this] {\n      // This should error out the stream first, then destroy the session\n      socketDriver_->deliverConnectionError(\n          quic::QuicError(quic::TransportErrorCode::PROTOCOL_VIOLATION, \"\"));\n    });\n  });\n  handler->expectError([](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorConnectionReset);\n  });\n  handler->expectDetachTransaction();\n  EXPECT_CALL(infoCb_, onConnectionError(_)).Times(0);\n  flushRequestsAndLoop();\n}\n\n// writeChain() returns a LocalErrorCode with a half-closed stream\nTEST_P(HQDownstreamSessionTest, WriteError) {\n  auto id = sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler, this, id] {\n    handler->sendHeaders(200, 100);\n    socketDriver_->setWriteError(id);\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectError([](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorWrite);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\n// writeChain() returns a LocalErrorCode with stream open both ways\nTEST_P(HQDownstreamSessionTest, WriteErrorPartialReq) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, this, id] {\n    handler->sendReplyWithBody(200, 100);\n    socketDriver_->setWriteError(id);\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\n// Test write on non writable stream\nTEST_P(HQDownstreamSessionTest, WriteNonWritableStream) {\n  auto idh = checkRequest();\n  // delay the eof event so that we won't have to loop\n  flushRequestsAndLoop(false, milliseconds(0), milliseconds(50), [&] {\n    // Force the read in the loop, so that this will trigger a write.\n    eventBase_.loop();\n    socketDriver_->flowControlAccess_.clear();\n  });\n  // Once the eof is written and no more bytes remain, we should never\n  // call flow control methods.\n  EXPECT_FALSE(socketDriver_->flowControlAccess_.contains(idh.first));\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, WriteErrorFlowControl) {\n  auto id = sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, this, id] {\n    handler->sendReplyWithBody(200, 100);\n    socketDriver_->forceStreamClose(id);\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectError();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\n// Connection error on idle connection\nTEST_P(HQDownstreamSessionTest, ConnectionErrorIdle) {\n  socketDriver_->deliverConnectionError(\n      quic::QuicError(quic::TransportErrorCode::PROTOCOL_VIOLATION, \"\"));\n  eventBase_.loopOnce();\n}\n\n// Connection End on an idle connection\nTEST_P(HQDownstreamSessionTest, ConnectionEnd) {\n  nextStreamId();\n  socketDriver_->addOnConnectionEndEvent(10);\n  CHECK(eventBase_.loop());\n}\n\nTEST_P(HQDownstreamSessionTest, BadHttpHeaders) {\n  auto id = nextStreamId();\n  std::array<uint8_t, 4> badHeaders{0x01, 0x02, 0x00, 0x81};\n  auto buf = folly::IOBuf::copyBuffer(badHeaders.data(), badHeaders.size());\n  socketDriver_->addReadEvent(id, std::move(buf), milliseconds(0));\n  socketDriver_->addReadEOF(id);\n  /* T35641532 -- Should QPACK errors be a session errors ?\n  testing::StrictMock<MockHTTPHandler> handler;\n  EXPECT_CALL(getMockController(), getParseErrorHandler(_, _, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&handler.txn_));\n  handler.expectError();\n  handler.expectDetachTransaction();\n  */\n  flushRequestsAndLoop();\n  // The QPACK error will cause the connection to get dropped\n}\n\nTEST_P(HQDownstreamSessionTest, BadHttpStrict) {\n  hqSession_->setStrictValidation(true);\n  sendRequest(getGetRequest(\"/foo\\xff\"));\n  testing::StrictMock<MockHTTPHandler> handler;\n  EXPECT_CALL(getMockController(), getParseErrorHandler(_, _, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&handler.txn_));\n  handler.expectError([&handler](const HTTPException& ex) {\n    EXPECT_TRUE(ex.hasHttpStatusCode());\n    handler.sendReplyWithBody(ex.getHttpStatusCode(), 100);\n  });\n  handler.expectDetachTransaction();\n\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, BadHttpHeadersStrict) {\n  hqSession_->setStrictValidation(true);\n  auto req = getGetRequest(\"/foo\");\n  req.getHeaders().set(\"Foo\", \"bad value\\xff\");\n  sendRequest(req);\n  testing::StrictMock<MockHTTPHandler> handler;\n  EXPECT_CALL(getMockController(), getParseErrorHandler(_, _, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&handler.txn_));\n  handler.expectError([&handler](const HTTPException& ex) {\n    EXPECT_TRUE(ex.hasHttpStatusCode());\n    handler.sendReplyWithBody(ex.getHttpStatusCode(), 100);\n  });\n  handler.expectDetachTransaction();\n\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SendFinOnly) {\n  HTTPMessage req;\n  req.setMethod(HTTPMethod::GET);\n  req.setHTTPVersion(0, 9);\n  req.setURL(\"/\");\n  sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    resp.setHTTPVersion(0, 9);\n    handler->txn_->sendHeaders(resp);\n    handler->txn_->sendEOM();\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, PauseResume) {\n  auto id = sendRequest(getPostRequest(65547), false);\n  auto& request = getStream(id);\n  auto handler = addSimpleStrictHandler();\n  // Handler pauses as soon as it receives headers.  Nothing buffered so\n  // transport continues reading\n  handler->expectHeaders([&handler] { handler->txn_->pauseIngress(); });\n  flushRequestsAndLoop();\n  EXPECT_FALSE(socketDriver_->isStreamPaused(id));\n\n  // Generate some body, but over the limit.  The session (currently) reads\n  // everything from the transport, so it will exceed the limit and pause\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(65537), HTTPCodec::NoPadding, true);\n  flushRequestsAndLoop();\n  EXPECT_TRUE(socketDriver_->isStreamPaused(id));\n  EXPECT_TRUE(socketDriver_->streams_[id].readBuf.empty());\n\n  // Now send some more data, all buffered\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(10), HTTPCodec::NoPadding, true);\n  request.readEOF = true;\n  flushRequestsAndLoop();\n  EXPECT_FALSE(socketDriver_->streams_[id].readBuf.empty());\n\n  auto id2 = sendRequest();\n  auto handler2 = addSimpleStrictHandler();\n  // stream 2 will start paused at the transport, so even headers are parsed.\n  flushRequestsAndLoopN(1);\n  EXPECT_FALSE(socketDriver_->streams_[id2].readBuf.empty());\n  EXPECT_TRUE(socketDriver_->isStreamPaused(id2));\n  hqSession_->closeWhenIdle();\n\n  // After resume, body (2 calls) and EOM delivered\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(2);\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  handler2->expectHeaders(\n      [&handler2] { handler2->sendReplyWithBody(200, 100); });\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n  handler->txn_->resumeIngress();\n  eventBase_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, EnqueuedAbort) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    handler->sendHeaders(200, 100);\n    handler->txn_->sendBody(makeBuf(100));\n    handler->txn_->sendAbort();\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, TransactionTimeout) {\n  sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler] {\n    // fire the timeout as soon as receiving the headers\n    handler->txn_->setIdleTimeout(std::chrono::milliseconds(0));\n  });\n  handler->expectError([&handler](const HTTPException& ex) {\n    EXPECT_FALSE(ex.hasHttpStatusCode());\n    handler->terminate();\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, ManagedTimeoutUnidirectionalReadReset) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  connManager->addConnection(hqSession_, true);\n  HQSession::DestructorGuard dg(hqSession_);\n\n  // Just keep sending instructions to set the dynamic table capacity\n  std::array<uint8_t, 1> data1{0b00100111};\n  auto buf1 = folly::IOBuf::copyBuffer(data1.data(), data1.size());\n  socketDriver_->addReadEvent(6, std::move(buf1), milliseconds(0));\n  std::array<uint8_t, 1> data2{0b00100110};\n  auto buf2 = folly::IOBuf::copyBuffer(data2.data(), data2.size());\n  socketDriver_->addReadEvent(6, std::move(buf2), milliseconds(100));\n  // Check that the session did not timeout, yet\n  eventBase_.runAfterDelay(\n      [&] {\n        EXPECT_NE(hqSession_->getConnectionCloseReason(),\n                  ConnectionCloseReason::TIMEOUT);\n      },\n      250);\n\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, ManagedTimeoutActiveStreams) {\n  std::chrono::milliseconds connIdleTimeout{300};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  HQSession::DestructorGuard dg(hqSession_);\n  sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  connManager->addConnection(hqSession_, true);\n  // Txn idle timer is > connIdleTimeout\n  auto lastErrorTime = std::chrono::steady_clock::now();\n  handler->expectHeaders([&handler] {\n    handler->txn_->setIdleTimeout(std::chrono::milliseconds(500));\n  });\n  handler->expectError(\n      [&handler, this, &lastErrorTime](const HTTPException& ex) {\n        // we should txn timeout\n        EXPECT_FALSE(ex.hasHttpStatusCode());\n        EXPECT_EQ(ex.getProxygenError(), kErrorTimeout);\n        EXPECT_TRUE(hqSession_->isScheduled());\n        hqSession_->cancelTimeout();\n        handler->terminate();\n        lastErrorTime = std::chrono::steady_clock::now();\n      });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  auto now = std::chrono::steady_clock::now();\n  EXPECT_GE(\n      std::chrono::duration_cast<std::chrono::milliseconds>(now - lastErrorTime)\n          .count(),\n      connIdleTimeout.count());\n  // Connection timeouts in the loop and closes.\n  EXPECT_EQ(hqSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::TIMEOUT);\n}\n\nTEST_P(HQDownstreamSessionTest, ManagedTimeoutNoStreams) {\n  std::chrono::milliseconds connIdleTimeout{300};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  HQSession::DestructorGuard dg(hqSession_);\n  connManager->addConnection(hqSession_, true);\n  eventBase_.loop();\n  EXPECT_EQ(hqSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::TIMEOUT);\n}\n\nTEST_P(HQDownstreamSessionTest, TransactionTimeoutNoCodecId) {\n  auto id = nextStreamId();\n  auto res = requests_.emplace(std::piecewise_construct,\n                               std::forward_as_tuple(id),\n                               std::forward_as_tuple(makeCodec(id)));\n  auto req = getGetRequest();\n  auto& request = res.first->second;\n  request.id = request.codec->createStream();\n  request.codec->generateHeader(request.buf, request.id, req, false);\n  // Send only a new line, so that onMessageBegin does not get called\n  request.buf.split(request.buf.chainLength() - 1);\n  testing::StrictMock<MockHTTPHandler> handler;\n  expectTransactionTimeout(handler);\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SendOnFlowControlPaused) {\n  // 106 bytes of resp headers, 1 byte of body but 5 bytes of chunk overhead\n  auto id = sendRequest();\n  // HTTP/3 has an extra grease frame on the first transaction\n  socketDriver_->setStreamFlowControlWindow(id, 103);\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] {\n    handler->sendHeaders(200, 100);\n    handler->txn_->sendBody(makeBuf(100));\n  });\n  handler->expectEgressPaused([&handler] { handler->txn_->sendEOM(); });\n  flushRequestsAndLoop();\n  socketDriver_->setStreamFlowControlWindow(id, 100);\n  handler->expectDetachTransaction();\n  eventBase_.loop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, Http_100Continue) {\n  auto req = getPostRequest(100);\n  req.getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  auto id = sendRequest(req, false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler] {\n    HTTPMessage continueResp;\n    continueResp.setStatusCode(100);\n    handler->txn_->sendHeaders(continueResp);\n  });\n  flushRequestsAndLoopN(1);\n  auto& request = getStream(id);\n  request.codec->generateBody(\n      request.buf, request.id, makeBuf(100), HTTPCodec::NoPadding, true);\n  request.readEOF = true;\n\n  handler->expectBody();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, ByteEvents) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  MockHTTPTransactionTransportCallback callback;\n  handler->expectHeaders([&handler, &callback] {\n    handler->txn_->setTransportCallback(&callback);\n  });\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  EXPECT_CALL(callback, headerBytesGenerated(_));\n  EXPECT_CALL(callback, bodyBytesGenerated(_));\n  EXPECT_CALL(callback, firstHeaderByteFlushed());\n  EXPECT_CALL(callback, firstByteFlushed());\n  EXPECT_CALL(callback, lastByteFlushed());\n  EXPECT_CALL(callback, lastByteAcked(_));\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, Observer_Attach_Detach_Destroyed) {\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    hqSession_->removeObserver(observer.get());\n  }\n\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, destroyed(_, _));\n    hqSession_->dropConnection();\n  }\n}\n\nTEST_P(HQDownstreamSessionTest, Observer_Attach_Detach_Destroyed_Shared) {\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserverShared(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    hqSession_->removeObserver(observer.get());\n  }\n\n  {\n    auto observer = addMockSessionObserverShared(eventSet);\n    EXPECT_CALL(*observer, destroyed(_, _));\n    hqSession_->dropConnection();\n  }\n}\n\nTEST_P(HQDownstreamSessionTest, Observer_RequestStarted) {\n\n  // Add an observer NOT subscribed to the RequestStarted event\n  auto observerUnsubscribed =\n      addMockSessionObserver(MockSessionObserver::EventSetBuilder().build());\n  hqSession_->addObserver(observerUnsubscribed.get());\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::RequestStarted)\n          .build());\n  hqSession_->addObserver(observerSubscribed.get());\n\n  EXPECT_CALL(*observerUnsubscribed, requestStarted(_, _)).Times(0);\n\n  HTTPTransactionObserverAccessor* actualTxnObserverAccessor;\n  // Add a request started event to the observer\n  EXPECT_CALL(*observerSubscribed, requestStarted(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPSessionObserverAccessor*,\n              const HTTPSessionObserverInterface::RequestStartedEvent& event) {\n            EXPECT_EQ(event.request.getHeaders().getSingleOrEmpty(\n                          \"x-meta-test-header\"),\n                      \"abc123\");\n            actualTxnObserverAccessor = event.txnObserverAccessor;\n          }));\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler]() {\n    handler->sendReplyWithBody(200 /* status code */,\n                               100 /* content size */,\n                               true /* keepalive */,\n                               true /* sendEOM */,\n                               false /*trailers*/);\n  });\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(hqSession_);\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(\"x-meta-test-header\", \"abc123\");\n  sendRequest(req);\n  flushRequestsAndLoop(true, milliseconds(0));\n  EXPECT_EQ(actualTxnObserverAccessor, handler->txn_->getObserverAccessor());\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, AppRateLimited) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  MockHTTPTransactionTransportCallback callback;\n  handler->expectHeaders([&handler, &callback] {\n    handler->txn_->setTransportCallback(&callback);\n  });\n  handler->expectEOM([this, &handler] {\n    handler->sendHeaders(200, 150);\n    handler->txn_->sendBody(makeBuf(100));\n    // force trigger onAppRateLimited\n    hqSession_->onAppRateLimited();\n  });\n  EXPECT_CALL(callback, headerBytesGenerated(_));\n  EXPECT_CALL(callback, bodyBytesGenerated(Ge(100))); // For HQ it's 100\n  EXPECT_CALL(callback, firstHeaderByteFlushed());\n  EXPECT_CALL(callback, firstByteFlushed());\n  EXPECT_CALL(callback, transportAppRateLimited());\n  flushRequestsAndLoop();\n\n  // send some more bytes and force trigger onAppRateLimited\n  EXPECT_CALL(callback, bodyBytesGenerated(Ge(50))); // For HQ it's 52\n  EXPECT_CALL(callback, transportAppRateLimited());\n  handler->txn_->sendBody(makeBuf(50));\n  hqSession_->onAppRateLimited();\n  flushRequestsAndLoop();\n\n  // Send the EOM, txn should not detach yet\n  EXPECT_CALL(callback, bodyBytesGenerated(0));\n  EXPECT_CALL(callback, lastByteFlushed());\n  handler->txn_->sendEOM(); // 0 length EOM\n  flushRequestsAndLoopN(1);\n\n  // Let the delivery callback fire, now it can cleanup\n  EXPECT_CALL(callback, lastByteAcked(_));\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, LastByteEventZeroSize) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  MockHTTPTransactionTransportCallback callback;\n  handler->expectHeaders([&handler, &callback] {\n    handler->txn_->setTransportCallback(&callback);\n  });\n  handler->expectEOM([&handler] {\n    handler->sendHeaders(200, 100);\n    handler->txn_->sendBody(makeBuf(100));\n  });\n  EXPECT_CALL(callback, headerBytesGenerated(_));\n  EXPECT_CALL(callback, bodyBytesGenerated(Ge(100))); // For HQ it's 103\n  EXPECT_CALL(callback, firstHeaderByteFlushed());\n  EXPECT_CALL(callback, firstByteFlushed());\n  flushRequestsAndLoop();\n\n  // Send the EOM, txn should not detach yet\n  EXPECT_CALL(callback, bodyBytesGenerated(0));\n  EXPECT_CALL(callback, lastByteFlushed());\n  handler->txn_->sendEOM(); // 0 length EOM\n  flushRequestsAndLoopN(1);\n\n  // Let the delivery callback fire, now it can cleanup\n  EXPECT_CALL(callback, lastByteAcked(_));\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, DropWithByteEvents) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  MockHTTPTransactionTransportCallback callback;\n  handler->expectHeaders([&handler, &callback] {\n    handler->txn_->setTransportCallback(&callback);\n  });\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n  EXPECT_CALL(callback, headerBytesGenerated(_));\n  EXPECT_CALL(callback, bodyBytesGenerated(_));\n  EXPECT_CALL(callback, firstHeaderByteFlushed());\n  EXPECT_CALL(callback, firstByteFlushed());\n  EXPECT_CALL(callback, lastByteFlushed());\n  flushRequestsAndLoopN(1);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, TransportInfo) {\n  wangle::TransportInfo transInfo;\n  quic::QuicSocket::TransportInfo quicInfo;\n  quicInfo.srtt = std::chrono::microseconds(135);\n  quicInfo.rttvar = std::chrono::microseconds(246);\n  quicInfo.writableBytes = 212;\n  quicInfo.congestionWindow = 5 * quic::kDefaultUDPSendPacketLen;\n  quicInfo.packetsRetransmitted = 513;\n  quicInfo.timeoutBasedLoss = 90;\n  quicInfo.pto = std::chrono::microseconds(34);\n  quicInfo.bytesSent = 23;\n  quicInfo.bytesRecvd = 123;\n  quicInfo.ptoCount = 1;\n  quicInfo.totalPTOCount = 2;\n  EXPECT_CALL(*socketDriver_->getSocket(), getTransportInfo())\n      .Times(3)\n      .WillRepeatedly(Return(quicInfo));\n  hqSession_->getCurrentTransportInfoWithoutUpdate(&transInfo);\n  EXPECT_EQ(135, transInfo.rtt.count());\n  EXPECT_EQ(246, transInfo.rtt_var);\n  EXPECT_EQ(5, transInfo.cwnd);\n  EXPECT_EQ(5 * quic::kDefaultUDPSendPacketLen, transInfo.cwndBytes);\n  EXPECT_EQ(513, transInfo.rtx);\n  EXPECT_EQ(90, transInfo.rtx_tm);\n  EXPECT_EQ(34, transInfo.rto);\n  EXPECT_EQ(23, transInfo.totalBytes);\n  auto quicProtocolInfo =\n      dynamic_cast<QuicProtocolInfo*>(transInfo.protocolInfo.get());\n  EXPECT_EQ(0, quicProtocolInfo->ptoCount);\n  EXPECT_EQ(0, quicProtocolInfo->totalPTOCount);\n  EXPECT_EQ(0, quicProtocolInfo->totalTransportBytesSent);\n  EXPECT_EQ(0, quicProtocolInfo->totalTransportBytesRecvd);\n  hqSession_->getCurrentTransportInfo(&transInfo);\n  EXPECT_EQ(1, quicProtocolInfo->ptoCount);\n  EXPECT_EQ(2, quicProtocolInfo->totalPTOCount);\n  EXPECT_EQ(23, quicProtocolInfo->totalTransportBytesSent);\n  EXPECT_EQ(123, quicProtocolInfo->totalTransportBytesRecvd);\n  hqSession_->dropConnection();\n}\n\n// Current Transport Info tests\nTEST_P(HQDownstreamSessionTest, CurrentTransportInfo) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  MockHTTPTransactionTransportCallback callback;\n  handler->expectHeaders([&handler, &callback] {\n    handler->txn_->setTransportCallback(&callback);\n  });\n\n  QuicStreamProtocolInfo resultProtocolInfo;\n  handler->expectEOM([&handler, &resultProtocolInfo] {\n    wangle::TransportInfo transInfo;\n    handler->txn_->getCurrentTransportInfo(&transInfo);\n    auto quicStreamProtocolInfo =\n        dynamic_cast<QuicStreamProtocolInfo*>(transInfo.protocolInfo.get());\n\n    if (quicStreamProtocolInfo) {\n      resultProtocolInfo.streamTransportInfo =\n          quicStreamProtocolInfo->streamTransportInfo;\n    }\n  });\n\n  handler->expectDetachTransaction();\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorDropped);\n  });\n\n  flushRequestsAndLoop();\n  hqSession_->dropConnection();\n\n  // The stream transport info field should be equal to\n  // the mock object\n  EXPECT_EQ(resultProtocolInfo.streamTransportInfo.totalHeadOfLineBlockedTime,\n            streamTransInfo_.totalHeadOfLineBlockedTime);\n  EXPECT_EQ(resultProtocolInfo.streamTransportInfo.holbCount,\n            streamTransInfo_.holbCount);\n  EXPECT_EQ(resultProtocolInfo.streamTransportInfo.isHolb,\n            streamTransInfo_.isHolb);\n}\n\nTEST_P(HQDownstreamSessionTest, GetAddresses) {\n  EXPECT_EQ(socketDriver_->localAddress_, hqSession_->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, hqSession_->getPeerAddress());\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, GetAddressesFromBase) {\n  auto* sessionBase = dynamic_cast<HTTPSessionBase*>(hqSession_);\n  EXPECT_EQ(socketDriver_->localAddress_, sessionBase->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, sessionBase->getPeerAddress());\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, GetAddressesAfterDropConnection) {\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n  EXPECT_EQ(socketDriver_->localAddress_, hqSession_->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, hqSession_->getPeerAddress());\n}\n\nTEST_P(HQDownstreamSessionTest, RstCancelled) {\n  auto id = nextStreamId();\n  auto buf = folly::IOBuf::create(3);\n  memcpy(buf->writableData(), \"GET\", 3);\n  buf->append(3);\n  socketDriver_->addReadEvent(id, std::move(buf), milliseconds(0));\n  flushRequestsAndLoopN(1);\n  socketDriver_->addReadError(id,\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n                              std::chrono::milliseconds(0));\n  hqSession_->closeWhenIdle();\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[id].error,\n            HTTP3::ErrorCode::HTTP_REQUEST_REJECTED);\n}\n\nTEST_P(HQDownstreamSessionTest, LocalErrQueuedEgress) {\n  sendRequest(getPostRequest(10), false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler, this] {\n    socketDriver_->setStreamFlowControlWindow(0, 0);\n    socketDriver_->setConnectionFlowControlWindow(0);\n    handler->sendHeaders(200, 65536 * 2);\n    handler->sendBody(65536 * 2);\n  });\n  handler->expectEgressPaused();\n  flushRequestsAndLoopN(2);\n  handler->expectError([](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorShutdown);\n  });\n  handler->expectDetachTransaction();\n  socketDriver_->deliverConnectionError(\n      quic::QuicError(quic::LocalErrorCode::CONNECTION_RESET, \"\"));\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, NoErrorNoStreams) {\n  EXPECT_CALL(infoCb_, onIngressError(_, kErrorNone));\n  socketDriver_->deliverConnectionError(\n      quic::QuicError(HTTP3::ErrorCode::HTTP_NO_ERROR, \"\"));\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, NoErrorOneStreams) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  flushRequestsAndLoopN(1);\n  // stream gets an error\n  handler->expectError([](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorEOF);\n  });\n  handler->expectDetachTransaction();\n  // This is for connection level errors, maybe should be reported a\n  EXPECT_CALL(infoCb_, onIngressError(_, kErrorEOF));\n  socketDriver_->deliverConnectionError(\n      quic::QuicError(HTTP3::ErrorCode::HTTP_NO_ERROR, \"\"));\n}\n\nTEST_P(HQDownstreamSessionTest, Connect) {\n  auto handler = addSimpleStrictHandler();\n  // Send HTTP 200 OK to accept the CONNECT request\n  handler->expectHeaders([&handler] { handler->sendHeaders(200, 100); });\n  handler->expectEOM([&] { handler->terminate(); });\n\n  // Data should be received using onBody\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"12345\"))\n      .WillOnce(ExpectString(\"abcdefg\"));\n  handler->expectDetachTransaction();\n\n  proxygen::HTTPMessage req;\n  req.setURL(\"test.net/path\");\n  req.setMethod(\"CONNECT\");\n  req.getHeaders().add(proxygen::HTTP_HEADER_HOST, \"https://test.net/path\");\n  auto id = sendRequest(req, /* eom */ false);\n  auto& request = getStream(id);\n\n  auto buf1 = folly::IOBuf::copyBuffer(\"12345\");\n  request.codec->generateBody(\n      request.buf, request.id, std::move(buf1), HTTPCodec::NoPadding, true);\n  flushRequestsAndLoopN(1);\n\n  auto buf2 = folly::IOBuf::copyBuffer(\"abcdefg\");\n  request.codec->generateBody(\n      request.buf, request.id, std::move(buf2), HTTPCodec::NoPadding, true);\n  flushRequestsAndLoopN(1);\n\n  request.readEOF = true;\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, ConnectUDP) {\n  auto handler = addSimpleStrictHandler();\n  // Send HTTP 200 OK to accept the CONNECT request\n  handler->expectHeaders([&handler] { handler->sendHeaders(200, 100); });\n  handler->expectEOM([&] { handler->terminate(); });\n\n  // Data should be received using onBody\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"12345\"))\n      .WillOnce(ExpectString(\"abcdefg\"));\n  handler->expectDetachTransaction();\n\n  proxygen::HTTPMessage req;\n  req.setURL(\"test.net/path\");\n  req.setMethod(\"CONNECT-UDP\");\n  req.getHeaders().add(proxygen::HTTP_HEADER_HOST, \"https://test.net/path\");\n  auto id = sendRequest(req, /* eom */ false);\n  auto& request = getStream(id);\n\n  auto buf1 = folly::IOBuf::copyBuffer(\"12345\");\n  request.codec->generateBody(\n      request.buf, request.id, std::move(buf1), HTTPCodec::NoPadding, true);\n  flushRequestsAndLoopN(1);\n\n  auto buf2 = folly::IOBuf::copyBuffer(\"abcdefg\");\n  request.codec->generateBody(\n      request.buf, request.id, std::move(buf2), HTTPCodec::NoPadding, true);\n  flushRequestsAndLoopN(1);\n\n  request.readEOF = true;\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\n// Just open a stream and send nothing\nTEST_P(HQDownstreamSessionTest, zeroBytes) {\n  auto id = nextStreamId();\n  socketDriver_->addReadEvent(\n      id, folly::IOBuf::copyBuffer(\"\", 0), milliseconds(0));\n  testing::StrictMock<MockHTTPHandler> handler;\n  expectTransactionTimeout(handler);\n  eventBase_.loop();\n  hqSession_->closeWhenIdle();\n}\n\n// For HQ, send an incomplete frame header\nTEST_P(HQDownstreamSessionTest, oneByte) {\n  auto id = nextStreamId();\n  socketDriver_->addReadEvent(\n      id, folly::IOBuf::copyBuffer(\"\", 1), milliseconds(0));\n  testing::StrictMock<MockHTTPHandler> handler;\n  expectTransactionTimeout(handler);\n  eventBase_.loop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, TestGoawayID) {\n  // This test check that unidirectional stream IDs are not accounted for\n  // in the Goaway Max Stream ID\n  auto req = getGetRequest();\n  // Explicitly skip some stream IDs to simulate out of order delivery\n  sendRequest(req, true, 4);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([hdlr = handler.get()] {\n    // Delay sending EOM so the streams are active when draining\n    hdlr->sendReplyWithBody(200, 100, true, false);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  hqSession_->closeWhenIdle();\n  // Give it some time to send the two goaways and receive the delivery callback\n  flushRequestsAndLoopN(3);\n  EXPECT_EQ(httpCallbacks_.goaways, 2);\n  EXPECT_THAT(httpCallbacks_.goawayStreamIds,\n              ElementsAre(kMaxClientBidiStreamId, 8));\n  handler->sendEOM();\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, TestReceiveGoaway) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  egressControlCodec_->generateGoaway(\n      writeBuf, HTTPCodec::MaxStreamID, ErrorCode::NO_ERROR, nullptr);\n  socketDriver_->addReadEvent(\n      connControlStreamId_, writeBuf.move(), std::chrono::milliseconds(0));\n  flushRequestsAndLoopN(1);\n  EXPECT_FALSE(hqSession_->isClosing());\n  hqSession_->closeWhenIdle();\n  eventBase_.loop();\n}\n\nTEST_P(HQDownstreamSessionTest, TestGetGoaway) {\n  std::vector<std::unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n  auto numStreams = 3;\n  for (auto n = 1; n <= numStreams; n++) {\n    auto req = getGetRequest();\n    // Explicitly skip some stream IDs to simulate out of order delivery\n    sendRequest(req, true, n * 8);\n    handlers.emplace_back(addSimpleStrictHandler());\n    auto handler = handlers.back().get();\n    handler->expectHeaders();\n    handler->expectEOM([hdlr = handler] {\n      // Delay sending EOM so the streams are active when draining\n      hdlr->sendReplyWithBody(200, 100, true, false);\n    });\n    handler->expectDetachTransaction();\n  }\n  flushRequestsAndLoopN(1);\n  hqSession_->closeWhenIdle();\n  // Give it some time to send the two goaways and receive the delivery callback\n  flushRequestsAndLoopN(3);\n  EXPECT_EQ(httpCallbacks_.goaways, 2);\n  EXPECT_THAT(httpCallbacks_.goawayStreamIds,\n              ElementsAre(kMaxClientBidiStreamId, numStreams * 8 + 4));\n\n  // Check that a new stream with id >= lastStreamId gets rejected\n  auto errReq = getGetRequest();\n  quic::StreamId errStreamId = numStreams * 8 + 4;\n  sendRequest(errReq, true, errStreamId);\n  flushRequestsAndLoopN(1);\n  auto& errStream = socketDriver_->streams_[errStreamId];\n  EXPECT_EQ(errStream.writeState, MockQuicSocketDriver::StateEnum::ERROR);\n  EXPECT_TRUE(errStream.error == HTTP3::ErrorCode::HTTP_REQUEST_REJECTED);\n\n  // Check that a new stream with id <= lastStreamId is instead just fine\n  auto okReq = getGetRequest();\n  sendRequest(okReq, true, numStreams * 8 - 4);\n  auto okHandler = addSimpleStrictHandler();\n  okHandler->expectHeaders();\n  okHandler->expectEOM([&] { okHandler->sendReplyWithBody(200, 100); });\n  okHandler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n\n  // now send response EOM on the pending transactions, to finish shutdown\n  for (auto& handler : handlers) {\n    handler->sendEOM();\n  }\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACK) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM(\n      [hdlr = handler.get()] { hdlr->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n\n  auto controlStream = encoderWriteBuf_.move();\n  flushRequestsAndLoopN(1);\n  encoderWriteBuf_.append(std::move(controlStream));\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, cancelQPACK) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n  auto& request = getStream(id);\n  // discard part of request, header won't get qpack-ack'd\n  request.buf.trimEnd(request.buf.chainLength() - 3);\n  request.readEOF = false;\n  flushRequestsAndLoopN(1);\n  socketDriver_->addReadError(id,\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n                              std::chrono::milliseconds(0));\n  hqSession_->closeWhenIdle();\n  flushRequestsAndLoop();\n  // this will evict all headers, which is only legal if the cancellation is\n  // emitted and processed.\n  qpackCodec_.setEncoderHeaderTableSize(0);\n  EXPECT_EQ(*socketDriver_->streams_[id].error,\n            HTTP3::ErrorCode::HTTP_REQUEST_REJECTED);\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKCanceled) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n  // This request never gets a handler\n\n  auto controlStream = encoderWriteBuf_.move();\n  // receive header block with unsatisfied dep\n  flushRequestsAndLoopN(1);\n\n  // cancel this request\n  socketDriver_->addReadError(id,\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n                              std::chrono::milliseconds(0));\n  flushRequestsAndLoopN(1);\n\n  // Now send the dependency\n  encoderWriteBuf_.append(std::move(controlStream));\n  flushRequestsAndLoop();\n\n  // This used to crash\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKTimeout) {\n  auto req = getPostRequest(10);\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req, false);\n  auto& request = getStream(id);\n  folly::IOBufQueue reqTail(folly::IOBufQueue::cacheChainLength());\n  reqTail.append(request.buf.move());\n  request.buf.append(reqTail.split(reqTail.chainLength() / 2));\n  // reqTail now has the second half of request\n\n  flushRequests();\n  testing::StrictMock<MockHTTPHandler> handler;\n  expectTransactionTimeout(handler, [&] {\n    request.buf.append(reqTail.move());\n    auto body = folly::IOBuf::wrapBuffer(\"\\3\\3\\3\\3\\3\\3\\3\\3\\3\\3\", 10);\n    request.codec->generateBody(\n        request.buf, request.id, std::move(body), HTTPCodec::NoPadding, true);\n    flushRequests();\n  });\n  eventBase_.loop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, QPACKEncoderLimited) {\n  auto req = getGetRequest();\n  socketDriver_->getSocket()->setStreamFlowControlWindow(\n      kQPACKEncoderEgressStreamId, 10);\n  auto id = sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([hdlr = handler.get()] {\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    resp.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n    hdlr->txn_->sendHeaders(resp);\n    hdlr->txn_->sendEOM();\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  // QPACK will attempt to index the header, but cannot reference it because\n  // it runs out of stream flow control\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 30);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, DelayedQPACKStopSendingReset) {\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n  // This request never gets a handler\n\n  auto controlStream = encoderWriteBuf_.move();\n  // receive header block with unsatisfied dep\n  flushRequestsAndLoopN(1);\n\n  // cancel this request\n  socketDriver_->addStopSending(id, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  socketDriver_->addReadError(id,\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n                              std::chrono::milliseconds(0));\n  flushRequestsAndLoopN(1);\n\n  // Now send the dependency\n  encoderWriteBuf_.append(std::move(controlStream));\n  flushRequestsAndLoop();\n\n  // This used to crash\n  hqSession_->closeWhenIdle();\n}\n\nusing ControlStreamsStallTest = HQDownstreamSessionBeforeTransportReadyTest;\n\nTEST_P(ControlStreamsStallTest, StalledQpackStream) {\n  // This test does not pre-setup any control streams for a reason.\n  // We want to be able to control unidirectional (QPACK) stream lifetime to\n  // perform the test.\n\n  QuicConnectionStateBase state(quic::QuicNodeType::Server);\n  EXPECT_CALL(*(socketDriver_->sock_), getState())\n      .WillRepeatedly(testing::Invoke(\n          [&state]() -> QuicConnectionStateBase* { return &state; }));\n\n  // Get a request going.\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n\n  // Add data to the control stream 6 with fake stream offset 42.\n  // This is to ensure unidirectional stream dispatcher in session does not\n  // instantiate an actual full blown control stream object, but only create a\n  // pending tmp stream placeholder and attach a peek callback to it to wait for\n  // the preface.\n  std::array<uint8_t, 1> data1{0b00100111};\n  auto buf1 = folly::IOBuf::copyBuffer(data1.data(), data1.size());\n  socketDriver_->addReadEvent(6, std::move(buf1), milliseconds(0), 42);\n\n  // Let the sesion roll.\n  hqSession_->onTransportReady();\n\n  // Now cancel the request we queded up in the beginning.\n  socketDriver_->addStopSending(id, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  socketDriver_->addReadError(id,\n                              HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED,\n                              std::chrono::milliseconds(0));\n\n  // Roll the event base. Once session loops, the expectation is that the\n  // pending control stream is alive and well and has its peek callback\n  // attached.\n  flushRequestsAndLoopN(1);\n\n  EXPECT_NE(socketDriver_->streams_[6].peekCB, nullptr);\n\n  // Close the session; all good.\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, QPACKHeadersTooLarge) {\n  hqSession_->setEgressSettings({{SettingsId::MAX_HEADER_LIST_SIZE, 60}});\n  auto req = getGetRequest();\n  req.getHeaders().add(\"X-FB-Debug\", \"rfccffgvtvnenjkbtitkfdufddnvbecu\");\n  auto id = sendRequest(req);\n  testing::StrictMock<MockHTTPHandler> errHandler;\n  EXPECT_CALL(getMockController(), getParseErrorHandler(_, _, _))\n      .WillOnce(Return(&errHandler));\n  EXPECT_CALL(errHandler, _setTransaction(testing::_))\n      .WillOnce(testing::SaveArg<0>(&errHandler.txn_));\n  errHandler.expectError([&errHandler](const HTTPException& ex) {\n    EXPECT_EQ(ex.getHttp3ErrorCode(),\n              (uint32_t)HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n    errHandler.sendReplyWithBody(400, 100);\n  });\n  errHandler.expectDetachTransaction();\n  flushRequestsAndLoopN(2);\n  // Gets a response\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 30);\n  EXPECT_EQ(*socketDriver_->streams_[id].error,\n            (uint32_t)HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED);\n  auto decoderStream = socketDriver_->streams_[kQPACKDecoderEgressStreamId]\n                           .writeBuf.front()\n                           ->clone();\n  decoderStream->coalesce();\n  // preface, cancel, ici\n  EXPECT_EQ(decoderStream->computeChainDataLength(), 3);\n  EXPECT_EQ(decoderStream->data()[1], 0x40); // stream 0 cancelled\n\n  // But the conn is still usable\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([hdlr = handler.get(), this] {\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    hdlr->txn_->sendHeaders(resp);\n    hdlr->txn_->sendEOM();\n    hqSession_->closeWhenIdle();\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionBeforeTransportReadyTest, NotifyPendingShutdown) {\n  hqSession_->notifyPendingShutdown();\n  SetUpOnTransportReady();\n  // Give it some time to send the two goaways and receive the delivery callback\n  flushRequestsAndLoopN(3);\n  // There is a check for this already for all the tests, but adding this to\n  // make it explicit that SETTINGS should be sent before GOAWAY even in this\n  // corner case, otherwise the peer will error out the session\n  EXPECT_EQ(httpCallbacks_.settings, 1);\n  EXPECT_EQ(httpCallbacks_.goaways, 2);\n  EXPECT_THAT(httpCallbacks_.goawayStreamIds,\n              ElementsAre(kMaxClientBidiStreamId, 0));\n}\n\n// NOTE: a failure for this test may cause an infinite loop in processReadData\nTEST_P(HQDownstreamSessionTest, ProcessReadDataOnDetachedStream) {\n  auto id = sendRequest(\"/\", 0, false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&] {\n    eventBase_.runAfterDelay(\n        [&] {\n          // schedule a few events to run in the eventbase back-to-back\n          // call readAvailable with just the EOF\n          auto& stream = socketDriver_->streams_[id];\n          CHECK(!stream.readEOF);\n          stream.readEOF = true;\n          CHECK(stream.readCB);\n          stream.readCB->readAvailable(id);\n          // now send an error so that the stream gets marked for detach\n          stream.readCB->readError(\n              id, quic::QuicError(HTTP3::ErrorCode::HTTP_NO_ERROR));\n          // then closeWhenIdle (like during shutdown), this calls\n          // checkForShutdown that calls checkForDetach and may detach a\n          // transaction that was added to the pendingProcessReadSet in the same\n          // loop\n          hqSession_->closeWhenIdle();\n        },\n        10);\n  });\n  flushRequestsAndLoopN(1);\n\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  flushRequestsAndLoop();\n}\n\n// Test Cases for which Settings are not sent in the test SetUp\nusing HQDownstreamSessionTestNoSettings = HQDownstreamSessionTest;\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionTestNoSettings,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.shouldSendSettings_ = false;\n                           return tp;\n                         }()),\n                         paramsToTestName);\nTEST_P(HQDownstreamSessionTestNoSettings, SimpleGet) {\n  auto idh = checkRequest();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[idh.first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[idh.first].writeEOF);\n  // Checks that the server response is sent without the QPACK dynamic table\n  CHECK_EQ(qpackCodec_.getCompressionInfo().ingress.headerTableSize_, 0);\n\n  // TODO: Check that QPACK does not use the dynamic table for the response\n  hqSession_->closeWhenIdle();\n}\n\n// This test is checking two different scenarios for different protocol\n//   - in HQ we already have sent SETTINGS in SetUp, so tests that multiple\n//     setting frames are not allowed\nTEST_P(HQDownstreamSessionTest, ExtraSettings) {\n  sendRequest();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorConnection);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n\n  // Need to use a new codec. Since generating settings twice is\n  // forbidden\n  HQControlCodec auxControlCodec_{0x0003,\n                                  TransportDirection::UPSTREAM,\n                                  StreamDirection::EGRESS,\n                                  egressSettings_};\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  auxControlCodec_.generateSettings(writeBuf);\n  socketDriver_->addReadEvent(\n      connControlStreamId_, writeBuf.move(), milliseconds(0));\n\n  flushRequestsAndLoop();\n\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\nusing HQDownstreamSessionFilterTestHQ = HQDownstreamSessionTest;\nTEST_P(HQDownstreamSessionFilterTestHQ, ControlStreamFilters) {\n  uint64_t settingsReceived = 0;\n\n  class TestFilter : public PassThroughHTTPCodecFilter {\n   public:\n    explicit TestFilter(uint64_t& settingsReceived)\n        : settingsReceived_(settingsReceived) {\n    }\n\n    void onSettings(const SettingsList& settings) override {\n      settingsReceived_++;\n    }\n    uint64_t& settingsReceived_;\n  };\n\n  hqSession_->addCodecFilter<TestFilter>(settingsReceived);\n  sendSettings();\n  flushRequestsAndLoop();\n  EXPECT_EQ(settingsReceived, 1);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, onErrorEmptyEnqueued) {\n  folly::IOBufQueue rst{folly::IOBufQueue::cacheChainLength()};\n  auto id1 = sendRequest();\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, this, id1] {\n    handler1->sendHeaders(200, 100);\n    socketDriver_->setStreamFlowControlWindow(id1, 100);\n    // After one loop, it will become stream flow control blocked, and txn\n    // will think it is enqueued, but session will not.\n    handler1->expectEgressPaused();\n    handler1->sendBody(101);\n    eventBase_.runInLoop([&handler1, this, id1] {\n      handler1->expectError();\n      handler1->expectDetachTransaction();\n      socketDriver_->addReadError(id1,\n                                  HTTP3::ErrorCode::HTTP_INTERNAL_ERROR,\n                                  std::chrono::milliseconds(0));\n    });\n  });\n  flushRequestsAndLoop();\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, dropWhilePaused) {\n  folly::IOBufQueue rst{folly::IOBufQueue::cacheChainLength()};\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, this] {\n    // pause writes\n    socketDriver_->setConnectionFlowControlWindow(0);\n    // fill session buffer\n    handler1->sendReplyWithBody(200, hqSession_->getWriteBufferLimit());\n  });\n  flushRequestsAndLoop();\n\n  handler1->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorDropped);\n  });\n  handler1->expectDetachTransaction();\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQDownstreamSessionTest, StopSendingOnUnknownUnidirectionalStreams) {\n  auto greaseStreamId = nextUnidirectionalStreamId();\n  createControlStream(socketDriver_.get(),\n                      greaseStreamId,\n                      proxygen::hq::UnidirectionalStreamType(\n                          *getGreaseId(folly::Random::rand32(16))));\n  auto idh = checkRequest();\n  flushRequestsAndLoop();\n\n  EXPECT_EQ(*socketDriver_->streams_[greaseStreamId].error,\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n  // Also check that the request completes correctly\n  EXPECT_GT(socketDriver_->streams_[idh.first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[idh.first].writeEOF);\n  // Checks that the server response is sent using the QPACK dynamic table\n  CHECK_GE(qpackCodec_.getCompressionInfo().ingress.headerTableSize_, 0);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, DataOnUnknownControlStream) {\n  auto randPreface =\n      hq::UnidirectionalStreamType(*getGreaseId(folly::Random::rand32(16)));\n  // Create  unidirectional stream with an unknown stream preface\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  generateStreamPreface(writeBuf, randPreface);\n  socketDriver_->addReadEvent(14, writeBuf.move());\n  flushRequestsAndLoop();\n\n  // Send an extra varint on the same stream, ignoring STOP_SENDING\n  folly::IOBufQueue writeBuf2{folly::IOBufQueue::cacheChainLength()};\n  generateStreamPreface(writeBuf2, randPreface);\n  socketDriver_->addReadEvent(14, writeBuf.move());\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, eofControlStream) {\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorConnection);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  socketDriver_->addReadEOF(connControlStreamId_);\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTest, resetControlStream) {\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorConnection);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  socketDriver_->addReadError(connControlStreamId_,\n                              HTTP3::ErrorCode::HTTP_INTERNAL_ERROR);\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n}\n\nTEST_P(HQDownstreamSessionTest, controlStreamWriteError) {\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendHeaders(200, 100); });\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorWrite);\n  });\n  handler->expectDetachTransaction();\n  socketDriver_->setWriteError(kQPACKEncoderEgressStreamId);\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_CLOSED_CRITICAL_STREAM);\n}\n\nTEST_P(HQDownstreamSessionTest, TooManyControlStreams) {\n  // This creates a request stream, so that we can check the HTTP3::ErrorCode\n  // at the end of the test. With no active streams we would drop the\n  // connection with no error instead.\n  sendRequest();\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorConnection);\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n\n  // Create an extra control stream, that causes the connection to get dropped\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  generateStreamPreface(writeBuf, UnidirectionalStreamType::CONTROL);\n  socketDriver_->addReadEvent(14, writeBuf.move());\n\n  flushRequestsAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_STREAM_CREATION_ERROR);\n}\n\nTEST_P(HQDownstreamSessionTest, TestGreaseFramePerSession) {\n  // a grease frame will be created in the first bidir stream\n  auto idh1 = checkRequest();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[idh1.first].writeBuf.chainLength(), 110);\n  FakeHTTPCodecCallback callback1;\n  std::unique_ptr<HQStreamCodec> upstreamCodec =\n      std::make_unique<hq::HQStreamCodec>(\n          idh1.first,\n          TransportDirection::UPSTREAM,\n          qpackCodec_,\n          encoderWriteBuf_,\n          decoderWriteBuf_,\n          [] { return std::numeric_limits<uint64_t>::max(); },\n          ingressSettings_);\n  upstreamCodec->setCallback(&callback1);\n  upstreamCodec->onIngress(\n      *socketDriver_->streams_[idh1.first].writeBuf.front());\n  EXPECT_EQ(callback1.unknownFrames, 1);\n  EXPECT_EQ(callback1.greaseFrames, 1);\n\n  // no grease frame will be created in the second bidir stream\n  auto idh2 = checkRequest();\n  flushRequestsAndLoop();\n  FakeHTTPCodecCallback callback2;\n  upstreamCodec->setCallback(&callback2);\n  upstreamCodec->onIngress(\n      *socketDriver_->streams_[idh2.first].writeBuf.front());\n  EXPECT_EQ(callback2.unknownFrames, 0);\n  EXPECT_EQ(callback2.greaseFrames, 0);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, getHTTPPriority) {\n  folly::Optional<HTTPPriority> expectedResults = HTTPPriority{1, true};\n\n  auto request = getProgressiveGetRequest();\n  sendRequest(request);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&]() {\n    auto resp = makeResponse(200, 0);\n\n    EXPECT_CALL(*socketDriver_->getSocket(),\n                getStreamPriority(handler->txn_->getID()))\n        .WillOnce(Return(quic::HTTPPriorityQueue::Priority(\n            expectedResults.value().urgency,\n            expectedResults.value().incremental)))\n        .WillOnce(\n            Return(quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS)))\n        .WillOnce(Return(quic::HTTPPriorityQueue::Priority(\n            expectedResults.value().urgency,\n            expectedResults.value().incremental)));\n\n    auto urgencyIncremental = handler->txn_->getHTTPPriority().value();\n    EXPECT_EQ(urgencyIncremental.urgency, expectedResults.value().urgency);\n    EXPECT_EQ(urgencyIncremental.incremental,\n              expectedResults.value().incremental);\n    EXPECT_EQ(handler->txn_->getHTTPPriority(), folly::none);\n    handler->sendRequest(*std::get<0>(resp));\n  });\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, IdleTimeoutNoStreams) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  connManager->addConnection(hqSession_, true);\n  // Just run the loop, the session will timeout, drain and close\n  auto start = std::chrono::steady_clock::now();\n  eventBase_.loop();\n  auto end = std::chrono::steady_clock::now();\n  EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start)\n                .count(),\n            connIdleTimeout.count());\n}\n\nTEST_P(HQDownstreamSessionTest, IdleTimeoutResetWithPing) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  connManager->addConnection(hqSession_, true);\n  for (int i = 1; i <= 4; i++) {\n    socketDriver_->addPingReceivedReadEvent(std::chrono::milliseconds(100));\n  }\n  // Just run the loop, the session will timeout, drain and close\n  auto start = std::chrono::steady_clock::now();\n  flushRequestsAndLoop();\n  auto end = std::chrono::steady_clock::now();\n  EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start)\n                .count(),\n            connIdleTimeout.count() * 2);\n}\n\nTEST_P(HQDownstreamSessionTest, IdleTimeoutResetWithPingAcknowledged) {\n  std::chrono::milliseconds connIdleTimeout{200};\n  auto connManager = wangle::ConnectionManager::makeUnique(\n      &eventBase_, connIdleTimeout, nullptr);\n  connManager->addConnection(hqSession_, true);\n  for (int i = 1; i <= 4; i++) {\n    socketDriver_->addPingAcknowledgedReadEvent(std::chrono::milliseconds(100));\n  }\n  // Just run the loop, the session will timeout, drain and close\n  auto start = std::chrono::steady_clock::now();\n  flushRequestsAndLoop();\n  auto end = std::chrono::steady_clock::now();\n  EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start)\n                .count(),\n            connIdleTimeout.count() * 2);\n}\n\n/**\n * Few tests cases here:\n *     1. Invoking ::sendAbort(NO_ERROR) on a transaction after eom has been\n * flushed will flush a RST_STREAM/NO_ERROR frame (no need to defer anything in\n * this specific case)\n *\n *     2. Invoking ::sendAbort(NO_ERROR) on a transaction after eom has been\n * queued, but not flushed, will defer a RST_STREAM/NO_ERROR frame until after\n * eom has been written to the underlying transport\n *\n *     3. Invoking ::sendAbort(NO_ERROR) on a transaction that has not queued an\n * eom will be translated to ErrorCode::CANCEL\n */\nTEST_P(HQDownstreamSessionTest, SendNoErrorAfterEomFlush) {\n  // test case #1 where eom is flushed immediately\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/0);\n    handler->txn_->sendEOM();\n    handler->txn_->sendAbort(ErrorCode::NO_ERROR);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  auto streamId = nextStreamId();\n  sendRequest(getPostRequest(), /*eom=*/false, streamId);\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  // verify that the server has invoked QuicSocket::stopSending\n  auto& streams = socketDriver_->streams_;\n  EXPECT_NE(streams.find(streamId), streams.end());\n  EXPECT_EQ(streams[streamId].error,\n            folly::make_optional(HTTP3::ErrorCode::HTTP_NO_ERROR));\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, SendDeferredNoError) {\n  // test case #2 where fin flag is set on data frame\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/200);\n    txn->pauseIngress();\n    handler->sendBody(100);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  auto streamId = nextStreamId();\n  sendRequest(getPostRequest(), /*eom=*/false, streamId);\n\n  // should not detach transaction yet since there is a pending eom and abort\n  // will not be invoked until the resp eom is flushed\n  EXPECT_CALL(*handler, _detachTransaction).Times(0);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  // resuming ingress and sending the rest of body & eom should detach the\n  // transaction\n  handler->expectDetachTransaction();\n  txn->sendBody(makeBuf(100));\n  txn->sendEOM();\n  txn->sendAbort(ErrorCode::NO_ERROR);\n  txn->resumeEgress(); // unnecessary since pending eom wil resume egress\n                       // anyways\n  evbLoopNonBlockN(2);\n\n  // verify that the server has invoked QuicSocket::stopSending\n  auto& streams = socketDriver_->streams_;\n  EXPECT_NE(streams.find(streamId), streams.end());\n  EXPECT_EQ(streams[streamId].error,\n            folly::make_optional(HTTP3::ErrorCode::HTTP_NO_ERROR));\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, InvalidNoErrorAbort) {\n  // test case #3 where NO_ERROR is transformed into INTERNAL_ERROR if there is\n  // no pending eom\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/200);\n    handler->sendBody(100);\n    txn->sendAbort(ErrorCode::NO_ERROR);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  auto streamId = nextStreamId();\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  sendRequest(getPostRequest(), /*eom=*/false, streamId);\n\n  // this should detach transaction since it is immediately aborted\n  EXPECT_CALL(*handler, _detachTransaction);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  auto& streams = socketDriver_->streams_;\n  EXPECT_NE(streams.find(streamId), streams.end());\n  EXPECT_EQ(streams[streamId].error,\n            folly::make_optional(HTTP3::ErrorCode::HTTP_INTERNAL_ERROR));\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTest, IncrementalStreamsRoundRobin) {\n  // set connection flow control window to 1500 bytes\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(1500);\n\n  auto id1 = sendRequest(getProgressiveGetRequest());\n  auto id2 = sendRequest(getProgressiveGetRequest());\n  socketDriver_->expectSetPriority(id1, Priority(1, true));\n  socketDriver_->expectSetPriority(id2, Priority(1, true));\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() {\n    handler1->sendHeaders(200, 3000);\n    handler1->sendBody(3000);\n    handler1->sendEOM();\n  });\n\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2]() {\n    handler2->sendHeaders(200, 3000);\n    handler2->sendBody(3000);\n    handler2->sendEOM();\n  });\n\n  flushRequestsAndLoop();\n\n  // after first egress, txn1 should have written data and txn2 should have none\n  // (since the connection window was consumed by txn1)\n  EXPECT_GT(socketDriver_->streams_[id1].writeBuf.chainLength(), 0);\n  EXPECT_EQ(socketDriver_->streams_[id2].writeBuf.chainLength(), 0);\n  auto txn1FirstBytes = socketDriver_->streams_[id1].writeBuf.chainLength();\n\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(1500);\n  flushRequestsAndLoop();\n\n  // after second egress, txn2 should have written data\n  // and txn1 should not have written additional data\n  EXPECT_EQ(socketDriver_->streams_[id1].writeBuf.chainLength(),\n            txn1FirstBytes);\n  EXPECT_GT(socketDriver_->streams_[id2].writeBuf.chainLength(), 0);\n  auto txn2FirstBytes = socketDriver_->streams_[id2].writeBuf.chainLength();\n\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(1500);\n  flushRequestsAndLoop();\n\n  // after third egress, txn1 should have written more data\n  EXPECT_GT(socketDriver_->streams_[id1].writeBuf.chainLength(),\n            txn1FirstBytes);\n  EXPECT_EQ(socketDriver_->streams_[id2].writeBuf.chainLength(),\n            txn2FirstBytes);\n  auto txn1SecondBytes = socketDriver_->streams_[id1].writeBuf.chainLength();\n\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(1500);\n  flushRequestsAndLoop();\n\n  // after fourth egress, txn2 should have written more data\n  EXPECT_EQ(socketDriver_->streams_[id1].writeBuf.chainLength(),\n            txn1SecondBytes);\n  EXPECT_GT(socketDriver_->streams_[id2].writeBuf.chainLength(),\n            txn2FirstBytes);\n\n  // grant enough flow control to finish both streams\n  socketDriver_->getSocket()->setConnectionFlowControlWindow(10000);\n  handler1->expectDetachTransaction();\n  handler2->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  EXPECT_GE(socketDriver_->streams_[id1].writeBuf.chainLength(), 3000);\n  EXPECT_GE(socketDriver_->streams_[id2].writeBuf.chainLength(), 3000);\n  EXPECT_TRUE(socketDriver_->streams_[id1].writeEOF);\n  EXPECT_TRUE(socketDriver_->streams_[id2].writeEOF);\n\n  hqSession_->closeWhenIdle();\n}\n\n/**\n * Instantiate the Parametrized test cases\n */\n\n// Make sure all the tests keep working with all the supported protocol versions\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionTest,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionFilterTestHQ,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.createQPACKStreams_ = true;\n                           tp.shouldSendSettings_ = false;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionBeforeTransportReadyTest,\n                         HQDownstreamSessionBeforeTransportReadyTest,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\nTEST_P(HQDownstreamSessionTestPush, SimplePush) {\n  auto id = sendRequest(\"/\", 1);\n  HTTPMessage promiseReq, res;\n  promiseReq.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  promiseReq.setURL(\"/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n  handler->expectHeaders();\n  HTTPCodec::StreamID pushStreamId = 0;\n  handler->expectEOM([&] {\n    // Generate response for the associated stream\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(100));\n\n    // Different from H2, this counts as an outgoing stream as soon as the\n    // txn is created.\n    // TODO: maybe create the stream lazily when trying to send the real\n    // headers instead?\n    auto outgoingStreams = hqSession_->getNumOutgoingStreams();\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    EXPECT_EQ(hqSession_->getNumOutgoingStreams(), outgoingStreams + 1);\n    // Generate a push request (PUSH_PROMISE)\n    pushTxn->sendHeaders(promiseReq);\n    pushStreamId = pushTxn->getID();\n    LOG(INFO) << \"pushStreamId=\" << pushStreamId;\n    pushTxn->sendHeaders(res);\n    pushTxn->sendBody(makeBuf(200));\n    pushTxn->sendEOM();\n  });\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  flushRequestsAndLoopN(1);\n  handler->txn_->sendEOM();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  auto pushIt = pushes_.find(pushStreamId);\n  ASSERT_TRUE(pushIt != pushes_.end());\n  EXPECT_GT(socketDriver_->streams_[pushIt->first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[pushIt->first].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestPush, PushPriorityCallback) {\n  auto id = sendRequest(\"/\", 1);\n  HTTPMessage promiseReq, res;\n  promiseReq.getHeaders().set(HTTP_HEADER_HOST, \"www.foo.com\");\n  promiseReq.setURL(\"/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n  handler->expectHeaders();\n  HTTPCodec::StreamID pushStreamId = 0;\n  handler->expectEOM([&] {\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(100));\n\n    auto outgoingStreams = hqSession_->getNumOutgoingStreams();\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    EXPECT_EQ(hqSession_->getNumOutgoingStreams(), outgoingStreams + 1);\n    // Generate a push request (PUSH_PROMISE)\n    pushTxn->sendHeaders(promiseReq);\n    pushStreamId = pushTxn->getID();\n    pushTxn->sendHeaders(res);\n    pushTxn->sendBody(makeBuf(200));\n    pushTxn->sendEOM();\n  });\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  flushRequestsAndLoopN(1);\n\n  // Push stream's priority can be updated either with stream id or push id:\n  auto pushId = pushes_.find(pushStreamId)->second;\n  socketDriver_->expectSetPriority(pushStreamId, Priority(6, true));\n  hqSession_->onPushPriority(pushId, HTTPPriority(6, true));\n  socketDriver_->expectSetPriority(pushStreamId, Priority(5, true));\n  hqSession_->onPriority(pushStreamId, HTTPPriority(5, true));\n\n  handler->txn_->sendEOM();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  auto pushIt = pushes_.find(pushStreamId);\n  ASSERT_TRUE(pushIt != pushes_.end());\n  EXPECT_GT(socketDriver_->streams_[pushIt->first].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[pushIt->first].writeEOF);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestPush, StopSending) {\n  auto id = sendRequest(\"/\", 1);\n  HTTPMessage req, res;\n  req.getHeaders().set(\"HOST\", \"www.foo.com\");\n  req.setURL(\"https://www.foo.com/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n  handler->expectHeaders();\n  HTTPCodec::StreamID pushStreamId = 0;\n  handler->expectEOM([&] {\n    // Generate response for the associated stream\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(100));\n\n    // Different from H2, this counts as an outgoing stream as soon as the\n    // txn is created.\n    // TODO: maybe create the stream lazily when trying to send the real\n    // headers instead?\n    auto outgoingStreams = hqSession_->getNumOutgoingStreams();\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    EXPECT_EQ(hqSession_->getNumOutgoingStreams(), outgoingStreams + 1);\n    // Generate a push request (PUSH_PROMISE)\n    pushTxn->sendHeaders(req);\n    pushStreamId = pushTxn->getID();\n    LOG(INFO) << \"pushStreamId=\" << pushStreamId;\n    pushTxn->sendHeaders(res);\n    pushTxn->sendBody(makeBuf(200));\n    // NO EOM\n  });\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _onError(_));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  flushRequestsAndLoopN(1);\n  handler->txn_->sendEOM();\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_GT(socketDriver_->streams_[id].writeBuf.chainLength(), 110);\n  EXPECT_TRUE(socketDriver_->streams_[id].writeEOF);\n  auto pushIt = pushes_.find(pushStreamId);\n  ASSERT_TRUE(pushIt != pushes_.end());\n  EXPECT_GT(socketDriver_->streams_[pushIt->first].writeBuf.chainLength(), 110);\n  EXPECT_FALSE(socketDriver_->streams_[pushIt->first].writeEOF);\n  // Cancel the push with stop sending\n  socketDriver_->addStopSending(pushIt->first,\n                                HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED);\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nusing DropConnectionInTransportReadyTest =\n    HQDownstreamSessionBeforeTransportReadyTest;\n\nINSTANTIATE_TEST_SUITE_P(ControlStreamsStallTest,\n                         ControlStreamsStallTest,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.checkUniridStreamCallbacks = false;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\nINSTANTIATE_TEST_SUITE_P(DropConnectionInTransportReadyTest,\n                         DropConnectionInTransportReadyTest,\n                         Values(\n                             [] {\n                               TestParams tp;\n                               tp.alpn_ = \"unsupported\";\n                               tp.expectOnTransportReady = false;\n                               return tp;\n                             }(),\n                             [] {\n                               TestParams tp;\n                               tp.alpn_ = \"h3\";\n                               tp.unidirectionalStreamsCredit = 1;\n                               tp.expectOnTransportReady = false;\n                               return tp;\n                             }()),\n                         paramsToTestName);\n// Instantiate hq server push tests\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionTestPush,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.unidirectionalStreamsCredit = 8;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\n// Use this test class for mismatched alpn tests\nclass HQDownstreamSessionTestUnsupportedAlpn : public HQDownstreamSessionTest {\n public:\n  void SetUp() override {\n    SetUpBase();\n  }\n};\n\nTEST_P(DropConnectionInTransportReadyTest, TransportReadyFailure) {\n  HQDownstreamSession::DestructorGuard dg(hqSession_);\n  EXPECT_CALL(infoCb_, onTransportReady(_)).Times(0);\n  EXPECT_CALL(infoCb_, onConnectionError(_))\n      .WillOnce(Invoke([](const HTTPSessionBase& session) {\n        const auto hqSession = dynamic_cast<const HQSession*>(&session);\n        ASSERT_NE(hqSession, nullptr);\n        ASSERT_NE(hqSession->getQuicSocket(), nullptr);\n      }));\n  SetUpOnTransportReady();\n  EXPECT_EQ(hqSession_->getQuicSocket(), nullptr);\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck,\n       DropConnectionWithDeliveryAckCbSetError) {\n  auto req = getGetRequest();\n  auto streamId = sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, 1723);\n  });\n\n  auto sock = socketDriver_->getSocket();\n\n  // This is a copy of the one in MockQuicSocketDriver, only hijacks data stream\n  // and forces an error.\n  EXPECT_CALL(\n      *sock,\n      registerByteEventCallback(\n          quic::ByteEvent::Type::ACK, testing::_, testing::_, testing::_))\n      .WillRepeatedly(testing::Invoke(\n          [streamId, &socketDriver = socketDriver_](quic::ByteEvent::Type,\n                                                    quic::StreamId id,\n                                                    uint64_t offset,\n                                                    quic::ByteEventCallback* cb)\n              -> quic::Expected<void, LocalErrorCode> {\n            if (id == streamId) {\n              return quic::make_unexpected(LocalErrorCode::INVALID_OPERATION);\n            }\n\n            socketDriver->checkNotReadOnlyStream(id);\n            auto it = socketDriver->streams_.find(id);\n            if (it == socketDriver->streams_.end() ||\n                it->second.nextWriteOffset >= offset) {\n              return quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS);\n            }\n            CHECK_NE(it->second.writeState,\n                     MockQuicSocketDriver::StateEnum::CLOSED);\n            it->second.deliveryCallbacks.emplace_back(offset, cb);\n            return {};\n          }));\n\n  EXPECT_CALL(*handler, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& error) {\n        EXPECT_TRUE(std::string(error.what())\n                        .find(\"failed to register byte event callback\") !=\n                    std::string::npos);\n      }));\n  handler->expectDetachTransaction();\n\n  flushRequestsAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck, TestBodyDeliveryAck) {\n  auto req = getGetRequest();\n  sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  auto length = 42;\n  auto offset = length - 1;\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, length);\n    handler->sendBody(length);\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        handler->txn_->bodyBytesSent() - 1,\n        proxygen::ByteEvent::EventFlags::ACK));\n    handler->sendEOM();\n  });\n\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_EQ(transportCallback_.numBodyBytesDeliveredCalls_, 1);\n  EXPECT_EQ(transportCallback_.bodyBytesDeliveredOffset_, offset);\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck, TestBodyTxCallback) {\n  auto req = getGetRequest();\n  sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  auto length = 42;\n  auto offset = length - 1;\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, length);\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        offset, proxygen::ByteEvent::EventFlags::TX));\n    handler->sendBody(length);\n    handler->sendEOM();\n  });\n\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_EQ(transportCallback_.numBodyBytesTxCalls_, 1);\n  EXPECT_EQ(transportCallback_.bodyBytesTxOffset_, offset);\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck, TestBodyDeliveryAckMultiple) {\n  auto req = getGetRequest();\n  sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  auto length = 42 + 17;\n  auto offset1 = 41;\n  auto offset2 = length - 1;\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, length);\n    // Test registering callbacks before sendBody\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        offset1,\n        proxygen::ByteEvent::EventFlags::TX |\n            proxygen::ByteEvent::EventFlags::ACK));\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        offset2, proxygen::ByteEvent::EventFlags::ACK));\n    handler->sendBody(42);\n    handler->sendBody(17);\n    handler->sendEOM();\n  });\n\n  handler->expectDetachTransaction();\n  flushRequestsAndLoop();\n  EXPECT_EQ(transportCallback_.numBodyBytesTxCalls_, 1);\n  EXPECT_EQ(transportCallback_.bodyBytesTxOffset_, offset1);\n  EXPECT_EQ(transportCallback_.numBodyBytesDeliveredCalls_, 2);\n  EXPECT_EQ(transportCallback_.bodyBytesDeliveredOffset_, offset2);\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck, TestBodyDeliveryErr) {\n  auto req = getGetRequest();\n  auto streamId = sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  auto length = 42;\n  auto offset = length - 1;\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, length);\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        offset, proxygen::ByteEvent::EventFlags::ACK));\n  });\n  flushRequestsAndLoop();\n  EXPECT_TRUE(transportCallback_.lastEgressHeadersByteDelivered_);\n\n  // One day, txn_->sendHeaders() will return number of bytes written, and we\n  // won't need this. For now, H3 frame headers size is 2 bytes.\n  const uint64_t frameHeaderSize = 2;\n  const uint64_t streamOffsetAfterHeaders =\n      (2 * frameHeaderSize) + transportCallback_.headerBytesGenerated_;\n\n  auto sock = socketDriver_->getSocket();\n\n  // This is a copy of the one in MockQuicSocketDriver, only hijacks data stream\n  // and forces an error.\n  EXPECT_CALL(\n      *sock,\n      registerByteEventCallback(\n          quic::ByteEvent::Type::ACK, testing::_, testing::_, testing::_))\n      .WillRepeatedly(testing::Invoke(\n          [streamId,\n           &streamOffsetAfterHeaders = streamOffsetAfterHeaders,\n           &socketDriver = socketDriver_](quic::ByteEvent::Type,\n                                          quic::StreamId id,\n                                          uint64_t offset,\n                                          quic::ByteEventCallback* cb)\n              -> quic::Expected<void, LocalErrorCode> {\n            if (id == streamId && offset > streamOffsetAfterHeaders) {\n              for (auto& it : socketDriver->streams_) {\n                auto& stream = it.second;\n                stream.readState = quic::MockQuicSocketDriver::ERROR;\n                stream.writeState = quic::MockQuicSocketDriver::ERROR;\n              }\n              return quic::make_unexpected(LocalErrorCode::INVALID_OPERATION);\n            }\n\n            socketDriver->checkNotReadOnlyStream(id);\n            auto it = socketDriver->streams_.find(id);\n            if (it == socketDriver->streams_.end() ||\n                it->second.nextWriteOffset >= offset) {\n              return quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS);\n            }\n            CHECK_NE(it->second.writeState,\n                     MockQuicSocketDriver::StateEnum::CLOSED);\n            it->second.deliveryCallbacks.emplace_back(offset, cb);\n            return {};\n          }));\n\n  EXPECT_CALL(*handler, _onError(_))\n      .WillOnce(Invoke([&handler = handler](const HTTPException& error) {\n        EXPECT_TRUE(std::string(error.what())\n                        .find(\"failed to register byte event callback\") !=\n                    std::string::npos);\n        handler->txn_->sendAbort();\n      }));\n\n  handler->expectDetachTransaction();\n\n  handler->sendBody(42);\n  flushRequestsAndLoop();\n}\n\nTEST_P(HQDownstreamSessionTestDeliveryAck, TestBodyDeliveryCancel) {\n  auto req = getGetRequest();\n  sendRequest(req);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n\n  auto length = 42;\n  auto offset = length - 1;\n  // Start the response.\n  handler->expectEOM([&]() {\n    handler->txn_->setTransportCallback(&transportCallback_);\n    handler->sendHeaders(200, length);\n    EXPECT_TRUE(handler->txn_->trackEgressBodyOffset(\n        offset,\n        proxygen::ByteEvent::EventFlags::TX |\n            proxygen::ByteEvent::EventFlags::ACK));\n    handler->sendBody(length);\n    // handler->sendEOM();\n  });\n\n  flushRequestsAndLoopN(1);\n\n  EXPECT_CALL(*handler, _onError(_)).Times(1);\n  handler->expectDetachTransaction();\n  socketDriver_->deliverErrorOnAllStreams(\n      quic::QuicError(LocalErrorCode::INVALID_OPERATION, \"fake error\"));\n  flushRequestsAndLoop();\n\n  EXPECT_EQ(transportCallback_.numBodyBytesCanceledCalls_, 2);\n  EXPECT_EQ(transportCallback_.bodyBytesCanceledOffset_, offset);\n}\n\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionTestDeliveryAck,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\nclass HQDownstreamSessionTestWebTransport : public HQDownstreamSessionTest {};\n\nTEST_P(HQDownstreamSessionTestWebTransport, WTRequestNegotiates) {\n  HTTPMessage req;\n  req.setHTTPVersion(1, 1);\n  req.setUpgradeProtocol(\"webtransport\");\n  req.setMethod(HTTPMethod::CONNECT);\n  req.setURL(\"/webtransport\");\n  req.getHeaders().set(HTTP_HEADER_HOST, \"www.facebook.com\");\n  auto sessionId = sendRequest(req, false);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  flushRequestsAndLoopN(3);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  handler->txn_->sendHeaders(resp);\n  EXPECT_NE(handler->txn_->getWebTransport(), nullptr);\n  handler->sendEOM();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  socketDriver_->addReadEOF(sessionId, std::chrono::milliseconds(0));\n  hqSession_->closeWhenIdle();\n  flushRequestsAndLoop();\n}\n\nINSTANTIATE_TEST_SUITE_P(HQDownstreamSessionTest,\n                         HQDownstreamSessionTestWebTransport,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.shouldSendSettings_ = false;\n                           tp.webTransport_ = true;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQDownstreamSessionTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/session/HQDownstreamSession.h>\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n#include <proxygen/lib/http/session/test/HQSessionTestCommon.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\n#include <folly/futures/Future.h>\n#include <folly/portability/GTest.h>\n\nconstexpr quic::StreamId kQPACKEncoderIngressStreamId = 6;\nconstexpr quic::StreamId kQPACKEncoderEgressStreamId = 7;\nconstexpr quic::StreamId kQPACKDecoderEgressStreamId = 11;\n\nclass TestTransportCallback\n    : public proxygen::HTTPTransactionTransportCallback {\n public:\n  void firstHeaderByteFlushed() noexcept override {\n  }\n\n  void firstByteFlushed() noexcept override {\n  }\n\n  void lastByteFlushed() noexcept override {\n    lastByteFlushed_ = true;\n  }\n\n  void trackedByteFlushed() noexcept override {\n  }\n\n  void lastByteAcked(\n      std::chrono::milliseconds /* latency */) noexcept override {\n    lastByteAcked_ = true;\n  }\n\n  void headerBytesGenerated(proxygen::HTTPHeaderSize& size) noexcept override {\n    headerBytesGenerated_ += size.compressedBlock;\n  }\n\n  void headerBytesReceived(\n      const proxygen::HTTPHeaderSize& /* size */) noexcept override {\n  }\n\n  void bodyBytesGenerated(size_t nbytes) noexcept override {\n    bodyBytesGenerated_ += nbytes;\n  }\n\n  void bodyBytesReceived(size_t /* size */) noexcept override {\n  }\n\n  void lastEgressHeaderByteAcked() noexcept override {\n    lastEgressHeadersByteDelivered_ = true;\n  }\n\n  void bodyBytesTx(uint64_t bodyOffset) noexcept override {\n    numBodyBytesTxCalls_++;\n    bodyBytesTxOffset_ = bodyOffset;\n  }\n\n  void bodyBytesDelivered(uint64_t bodyOffset) noexcept override {\n    numBodyBytesDeliveredCalls_++;\n    bodyBytesDeliveredOffset_ = bodyOffset;\n  }\n\n  void bodyBytesDeliveryCancelled(uint64_t bodyOffset) noexcept override {\n    numBodyBytesCanceledCalls_++;\n    bodyBytesCanceledOffset_ = bodyOffset;\n  }\n\n  uint64_t headerBytesGenerated_{0};\n  bool lastEgressHeadersByteDelivered_{false};\n  uint64_t numBodyBytesDeliveredCalls_{0};\n  uint64_t bodyBytesDeliveredOffset_{0};\n  uint64_t numBodyBytesTxCalls_{0};\n  uint64_t bodyBytesTxOffset_{0};\n  uint64_t numBodyBytesCanceledCalls_{0};\n  uint64_t bodyBytesCanceledOffset_{0};\n  uint64_t bodyBytesGenerated_{0};\n  bool lastByteFlushed_{false};\n  bool lastByteAcked_{false};\n};\n\nclass HQDownstreamSessionTest : public HQSessionTest {\n public:\n  HQDownstreamSessionTest(\n      folly::Optional<TestParams> overrideParams = folly::none)\n      : HQSessionTest(proxygen::TransportDirection::DOWNSTREAM,\n                      overrideParams) {\n  }\n\n protected:\n  proxygen::HTTPCodec::StreamID sendRequest(const std::string& url = \"/\",\n                                            int8_t priority = 0,\n                                            bool eom = true);\n\n  quic::StreamId nextStreamId();\n\n  quic::StreamId sendRequest(const proxygen::HTTPMessage& req,\n                             bool eom = true,\n                             quic::StreamId id = quic::kEightByteLimit);\n\n  quic::StreamId sendHeader();\n\n  folly::Promise<folly::Unit> sendRequestLater(proxygen::HTTPMessage req,\n                                               bool eof = false);\n\n public:\n  void SetUp() override;\n  void TearDown() override;\n\n protected:\n  void SetUpBase();\n  void SetUpOnTransportReady();\n\n  template <class HandlerType>\n  std::unique_ptr<testing::StrictMock<HandlerType>>\n  addSimpleStrictHandlerBase();\n\n  std::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>\n  addSimpleStrictHandler();\n\n  std::pair<quic::StreamId,\n            std::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>>\n  checkRequest(proxygen::HTTPMessage req = proxygen::getGetRequest());\n\n  void flushRequestsAndWaitForReads(\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  void flushRequestsAndLoop(\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  void flushRequestsAndLoopN(\n      uint64_t n,\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  bool flushRequests(\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  testing::StrictMock<proxygen::MockController>& getMockController();\n\n  std::unique_ptr<proxygen::HTTPCodec> makeCodec(\n      proxygen::HTTPCodec::StreamID id);\n\n  struct ClientStream {\n    explicit ClientStream(std::unique_ptr<proxygen::HTTPCodec> c)\n        : codec(std::move(c)) {\n    }\n\n    proxygen::HTTPCodec::StreamID id;\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    bool readEOF{false};\n    std::unique_ptr<proxygen::HTTPCodec> codec;\n  };\n\n  ClientStream& getStream(proxygen::HTTPCodec::StreamID id);\n\n  void expectTransactionTimeout(\n      testing::StrictMock<proxygen::MockHTTPHandler>& handler,\n      folly::Function<void()> fn = folly::Function<void()>());\n\n  std::unique_ptr<proxygen::MockSessionObserver> addMockSessionObserver(\n      proxygen::MockSessionObserver::EventSet eventSet);\n\n  std::shared_ptr<proxygen::MockSessionObserver> addMockSessionObserverShared(\n      proxygen::MockSessionObserver::EventSet eventSet);\n\n  std::unordered_map<quic::StreamId, ClientStream> requests_;\n  quic::StreamId nextStreamId_{0};\n  quic::QuicSocket::StreamTransportInfo streamTransInfo_;\n  TestTransportCallback transportCallback_;\n};\n\nclass HQDownstreamSessionBeforeTransportReadyTest\n    : public HQDownstreamSessionTest {\n  void SetUp() override {\n    // Just do a basic setup, but don't call onTransportReady nor create the\n    // control streams just yet, so to give the test a chance to manipulate\n    // the session before onTransportReady\n    SetUpBase();\n  }\n};\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQSessionMocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/HQSession.h>\n#include <proxygen/lib/http/session/HQStreamDispatcher.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\nnamespace proxygen {\n\nclass MockServerPushLifecycleCallback : public ServerPushLifecycleCallback {\n public:\n  ~MockServerPushLifecycleCallback() override = default;\n\n  MOCK_METHOD(void,\n              onPushPromiseBegin,\n              (HTTPCodec::StreamID /* parent streamID */,\n               hq::PushId /* pushID */));\n\n  MOCK_METHOD(void,\n              onPushPromise,\n              (HTTPCodec::StreamID /* parent streamID */,\n               hq::PushId /* pushID */,\n               HTTPMessage* /* msg */));\n\n  MOCK_METHOD(void,\n              onNascentPushStreamBegin,\n              (HTTPCodec::StreamID /* push stream ID */, bool /* eom */));\n\n  MOCK_METHOD(void,\n              onNascentPushStream,\n              (HTTPCodec::StreamID /* push stream ID */,\n               hq::PushId /* server push id */,\n               bool /* eom */));\n\n  MOCK_METHOD(void,\n              onNascentEof,\n              (HTTPCodec::StreamID /* push stream ID */,\n               folly::Optional<hq::PushId> /* push id */));\n\n  MOCK_METHOD(void,\n              onOrphanedNascentStream,\n              (HTTPCodec::StreamID /* push stream ID */,\n               folly::Optional<hq::PushId> /* push id */));\n\n  MOCK_METHOD(void,\n              onHalfOpenPushedTxn,\n              (const HTTPTransaction* /* txn */,\n               hq::PushId /* push id */,\n               HTTPCodec::StreamID /* assoc stream id */,\n               bool /* eom */));\n\n  MOCK_METHOD(void,\n              onPushedTxn,\n              (const HTTPTransaction* /* txn */,\n               HTTPCodec::StreamID /* push stream id */,\n               hq::PushId /* push id */,\n               HTTPCodec::StreamID /* assoc stream id */,\n               bool /* eom */));\n\n  MOCK_METHOD(void, onPushedTxnTimeout, (const HTTPTransaction* /* txn */));\n\n  MOCK_METHOD(void,\n              onOrphanedHalfOpenPushedTxn,\n              (const HTTPTransaction* /* txn */));\n\n  MOCK_METHOD(void,\n              onPushIdLimitExceeded,\n              (hq::PushId /* incoming push id */,\n               folly::Optional<hq::PushId> /* max allowed push id */,\n               folly::Optional<HTTPCodec::StreamID> /* stream */));\n\n  using PushPromiseBeginF =\n      std::function<void(HTTPCodec::StreamID, hq::PushId)>;\n  using PushPromiseF =\n      std::function<void(HTTPCodec::StreamID, hq::PushId, HTTPMessage*)>;\n  using NascentPushStreamBeginF =\n      std::function<void(HTTPCodec::StreamID, bool)>;\n  using NascentPushStreamF =\n      std::function<void(HTTPCodec::StreamID, hq::PushId, bool)>;\n  using NascentEofF =\n      std::function<void(HTTPCodec::StreamID, folly::Optional<hq::PushId>)>;\n  using OrphanedNascentStreamF =\n      std::function<void(HTTPCodec::StreamID, folly::Optional<hq::PushId>)>;\n  using HalfOpenPushedTxnF = std::function<void(\n      const HTTPTransaction*, hq::PushId, HTTPCodec::StreamID, bool)>;\n  using PushedTxnF = std::function<void(const HTTPTransaction*,\n                                        HTTPCodec::StreamID,\n                                        hq::PushId,\n                                        HTTPCodec::StreamID,\n                                        bool)>;\n  using PushedTxnTimeoutF = std::function<void(const HTTPTransaction*)>;\n  using OrphanedHalfOpenPushedTxnF =\n      std::function<void(const HTTPTransaction*)>;\n\n  using PushIdLimitExceededF =\n      std::function<void(hq::PushId,\n                         folly::Optional<hq::PushId>,\n                         folly::Optional<HTTPCodec::StreamID>)>;\n\n  void expectPushPromiseBegin(PushPromiseBeginF impl = nullptr) {\n    auto& exp = EXPECT_CALL(*this, onPushPromiseBegin(testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectPushPromise(PushPromiseF impl = nullptr) {\n    auto& exp =\n        EXPECT_CALL(*this, onPushPromise(testing::_, testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectNascentPushStreamBegin(NascentPushStreamBeginF impl = nullptr) {\n    auto& exp =\n        EXPECT_CALL(*this, onNascentPushStreamBegin(testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectNascentPushStream(NascentPushStreamF impl = nullptr) {\n    auto& exp = EXPECT_CALL(\n        *this, onNascentPushStream(testing::_, testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectNascentEof(NascentEofF impl = nullptr) {\n    auto& exp = EXPECT_CALL(*this, onNascentEof(testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectOrphanedNascentStream(OrphanedNascentStreamF impl = nullptr) {\n    auto& exp =\n        EXPECT_CALL(*this, onOrphanedNascentStream(testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectHalfOpenPushedTxn(HalfOpenPushedTxnF impl = nullptr) {\n    auto& exp = EXPECT_CALL(\n        *this,\n        onHalfOpenPushedTxn(testing::_, testing::_, testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectPushedTxn(PushedTxnF impl = nullptr) {\n    auto& exp = EXPECT_CALL(\n        *this,\n        onPushedTxn(\n            testing::_, testing::_, testing::_, testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectPushedTxnTimeout(PushedTxnTimeoutF impl = nullptr) {\n    auto& exp = EXPECT_CALL(*this, onPushedTxnTimeout(testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectOrphanedHalfOpenPushedTxn(\n      OrphanedHalfOpenPushedTxnF impl = nullptr) {\n    auto& exp = EXPECT_CALL(*this, onOrphanedHalfOpenPushedTxn(testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n\n  void expectPushIdLimitExceeded(PushIdLimitExceededF impl = nullptr) {\n    auto& exp = EXPECT_CALL(\n        *this, onPushIdLimitExceeded(testing::_, testing::_, testing::_));\n    if (impl) {\n      exp.WillOnce(testing::Invoke(impl));\n    }\n  }\n};\n\nclass MockConnectCallback : public HQSession::ConnectCallback {\n public:\n  MOCK_METHOD(void, connectSuccess, ());\n  MOCK_METHOD(void, onReplaySafe, ());\n  MOCK_METHOD(void, connectError, (quic::QuicError));\n  MOCK_METHOD(void, onFirstPeerPacketProcessed, ());\n};\n\nclass MockHQSession : public HQSession {\n public:\n  MockHQSession(const folly::Optional<std::chrono::milliseconds>&\n                    transactionsTimeout = folly::none,\n                HTTPSessionController* controller = nullptr,\n                const folly::Optional<proxygen::TransportDirection>& direction =\n                    folly::none)\n      : HQSession(transactionsTimeout.value_or(getDefaultTransactionTimeout()),\n                  controller,\n                  direction.value_or(getMockDefaultDirection()),\n                  wangle::TransportInfo(),\n                  nullptr),\n        transactionTimeout_(\n            transactionsTimeout.value_or(getDefaultTransactionTimeout())),\n        direction_(direction.value_or(getMockDefaultDirection())),\n        quicProtocolInfo_(std::make_shared<QuicProtocolInfo>()),\n        quicStreamProtocolInfo_(std::make_shared<QuicStreamProtocolInfo>()) {\n    LOG(INFO) << \"Creating mock transaction on stream \" << lastStreamId_;\n    makeMockTransaction(lastStreamId_++);\n\n    ON_CALL(*this, newTransaction(::testing::_))\n        .WillByDefault(::testing::DoAll(\n            ::testing::SaveArg<0>(&handler_),\n            ::testing::WithArgs<0>(\n                ::testing::Invoke([&](HTTPTransaction::Handler* handler) {\n                  CHECK(txn_);\n                  LOG(INFO) << \"Setting transaction handler to \" << handler;\n                  txn_->HTTPTransaction::setHandler(handler);\n                })),\n            ::testing::Return(txn_.get())));\n  }\n\n  static std::chrono::milliseconds getDefaultTransactionTimeout() {\n    return std::chrono::milliseconds(5000);\n  }\n\n  static proxygen::TransportDirection getMockDefaultDirection() {\n    return proxygen::TransportDirection::UPSTREAM;\n  }\n\n  bool isDetachable(bool) const override {\n    return false;\n  }\n\n  void attachThreadLocals(folly::EventBase*,\n                          std::shared_ptr<const folly::SSLContext>,\n                          const WheelTimerInstance&,\n                          HTTPSessionStats*,\n                          FilterIteratorFn,\n                          HeaderCodec::Stats*,\n                          HTTPSessionController*) override {\n  }\n\n  void detachThreadLocals(bool) override {\n  }\n\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         std::unique_ptr<HTTPMessage> msg,\n                         bool eom = false) {\n    if (handler_) {\n      handler_->onHeadersComplete(std::move(msg));\n      if (eom) {\n        handler_->onEOM();\n      }\n    }\n  };\n\n  void onHeadersComplete(HTTPCodec::StreamID streamID,\n                         int statusCode,\n                         const std::string& statusMessage,\n                         bool eom = false) {\n    auto resp = std::make_unique<HTTPMessage>();\n    resp->setStatusCode(statusCode);\n    resp->setStatusMessage(statusMessage);\n    onHeadersComplete(streamID, std::move(resp), eom);\n  }\n\n  MOCK_METHOD(bool, isReplaySafe, (), (const));\n\n  MOCK_METHOD(HTTPTransaction::Handler*,\n              getTransactionTimeoutHandler,\n              (HTTPTransaction*));\n\n  MOCK_METHOD(void, setupOnHeadersComplete, (HTTPTransaction*, HTTPMessage*));\n\n  MOCK_METHOD((void),\n              onConnectionSetupErrorHandler,\n              (quic::QuicError),\n              (noexcept));\n\n  MOCK_METHOD(HTTPTransaction*, newTransaction, (HTTPTransaction::Handler*));\n\n  MOCK_METHOD(void, drain, ());\n\n  MOCK_CONST_METHOD0(getQuicSocket, quic::QuicSocket*());\n\n  MockHTTPTransaction* makeMockTransaction(HTTPCodec::StreamID id) {\n    LOG(INFO) << \"Creating mocked transaction on stream \" << id;\n\n    txn_ = std::make_unique<::testing::StrictMock<MockHTTPTransaction>>(\n        direction_,\n        id,\n        0, /* seqNo */\n        egressQueue_,\n        nullptr, /* timer */\n        transactionTimeout_);\n\n    LOG(INFO) << \"Setting default handlers on the new transaction \"\n              << txn_.get();\n\n    EXPECT_CALL(*txn_, setHandler(::testing::_))\n        .WillRepeatedly(\n            ::testing::Invoke([txn = txn_.get()](HTTPTransactionHandler* hdlr) {\n              LOG(INFO) << \"Setting handler on \" << txn << \" to \" << hdlr;\n              txn->HTTPTransaction::setHandler(hdlr);\n            }));\n\n    EXPECT_CALL(*txn_, canSendHeaders())\n        .WillRepeatedly(::testing::Invoke([txn = txn_.get()] {\n          return txn->HTTPTransaction::canSendHeaders();\n        }));\n\n    EXPECT_CALL(txn_->mockTransport_, getCurrentTransportInfo(::testing::_))\n        .WillRepeatedly(::testing::DoAll(\n            ::testing::WithArgs<0>(\n                ::testing::Invoke([&](wangle::TransportInfo* tinfo) {\n                  if (tinfo) {\n                    tinfo->protocolInfo = quicStreamProtocolInfo_;\n                  }\n                })),\n            ::testing::Return(true)));\n\n    LOG(INFO) << \"Returning the new mocked transaction \" << txn_.get();\n\n    return txn_.get();\n  }\n\n  HQStreamTransportBase* findPushStream(quic::StreamId) override {\n    return nullptr;\n  }\n\n  void findPushStreams(std::unordered_set<HQStreamTransportBase*>&) override {\n  }\n  bool erasePushStream(quic::StreamId) override {\n    return false;\n  }\n  uint32_t getNumOutgoingStreams() const override {\n    return static_cast<uint32_t>(streams_.size());\n  }\n  uint32_t getNumIncomingStreams() const override {\n    return static_cast<uint32_t>(streams_.size());\n  }\n\n  void dispatchPushStream(quic::StreamId /* streamId */,\n                          hq::PushId /* pushId */,\n                          size_t /* to consume */) override {\n  }\n\n  const std::chrono::milliseconds transactionTimeout_;\n  const proxygen::TransportDirection direction_;\n\n  HTTP2PriorityQueue egressQueue_;\n  wangle::TransportInfo currentTransportInfo_;\n  std::shared_ptr<QuicProtocolInfo> quicProtocolInfo_;\n  std::shared_ptr<QuicStreamProtocolInfo> quicStreamProtocolInfo_;\n\n  std::unique_ptr<::testing::StrictMock<MockHTTPTransactionTransport>>\n      transport_;\n\n  std::unique_ptr<::testing::StrictMock<MockHTTPTransaction>> txn_;\n\n  HTTPCodec::StreamID lastStreamId_{1}; // streamID 0 is reserved\n  HTTPTransaction::Handler* handler_;\n};\n\nclass FakeHQHTTPCodecCallback : public FakeHTTPCodecCallback {\n public:\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQSessionMocksTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\nusing namespace proxygen;\nusing namespace testing;\n/**\n * A test case to validate that mocks exported by HQsesion\n * work correctly\n */\nclass MocksTest : public Test {\n public:\n  void SetUp() override {\n    handler_ = std::make_unique<StrictMock<MockHTTPHandler>>();\n    hqSession_ = std::make_unique<StrictMock<MockHQSession>>();\n\n    hqSession_->quicStreamProtocolInfo_->ptoCount = 1;\n    hqSession_->quicStreamProtocolInfo_->totalPTOCount = 2;\n    hqSession_->quicStreamProtocolInfo_->totalTransportBytesSent = 3;\n    hqSession_->quicStreamProtocolInfo_->streamTransportInfo.holbCount = 4;\n  }\n\n  void TearDown() override {\n  }\n\n protected:\n  std::unique_ptr<StrictMock<MockHTTPHandler>> handler_;\n  folly::HHWheelTimer::UniquePtr timeoutSet_;\n  wangle::TransportInfo tInfo_;\n  std::unique_ptr<StrictMock<MockHQSession>> hqSession_;\n};\n\n// Test that the mocked `newTransaction` does the right thing\nTEST_F(MocksTest, MockHQSessionNewTransaction) {\n  // The default implementation of `newTransaction` is provided in Mocks.h\n  // But it has to be enabled via `EXPECT_CALL` without an action.\n  EXPECT_CALL(*hqSession_, newTransaction(_));\n\n  // Capture the transaction object that HQSession will pass to the\n  // handler\n  HTTPTransaction* txn;\n  EXPECT_CALL(*handler_, _setTransaction(_)).WillOnce(SaveArg<0>(&txn));\n\n  hqSession_->newTransaction(handler_.get());\n\n  // After the `setTransaction` has been invoked, the handler and transaction\n  // must be tied.\n  EXPECT_EQ(txn, hqSession_->txn_.get())\n      << \"`newTransaction` should call `handler::setTransaction`\";\n  EXPECT_EQ(handler_.get(), hqSession_->handler_)\n      << \"`newTransaction` should set the session handler\";\n}\n\n// Test that the mocked `newTransaction` does the right thing\nTEST_F(MocksTest, MockHQSessionPropagatesQuickProtocolInfo) {\n  // The default implementation of `newTransaction` is provided in Mocks.h\n  // But it has to be enabled via `EXPECT_CALL` without an action.\n  EXPECT_CALL(*hqSession_, newTransaction(_));\n\n  // Capture the transaction object that HQSession will pass to the\n  // handler\n  HTTPTransaction* txn;\n  EXPECT_CALL(*handler_, _setTransaction(_)).WillOnce(SaveArg<0>(&txn));\n\n  hqSession_->newTransaction(handler_.get());\n\n  wangle::TransportInfo tinfo;\n\n  hqSession_->txn_->getCurrentTransportInfo(&tinfo);\n\n  auto receivedProtocolInfo =\n      dynamic_cast<QuicStreamProtocolInfo*>(tinfo.protocolInfo.get());\n\n  EXPECT_NE(receivedProtocolInfo, nullptr)\n      << \"`getCurrentTransportInfo` should return QuicStreamProtocolInfo\";\n\n  EXPECT_EQ(receivedProtocolInfo->totalPTOCount, 2)\n      << \"received protocol info should reflect the quic protocol info\";\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQSessionTestCommon.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/test/HQSessionTestCommon.h>\n#include <quic/folly_utils/Utils.h>\n\n#include <folly/Random.h>\n#include <folly/String.h>\n\nusing namespace proxygen;\nusing namespace proxygen::hq;\n\nsize_t encodeQuicIntegerWithAtLeast(uint64_t value,\n                                    uint8_t atLeast,\n                                    folly::io::QueueAppender& appender) {\n  CHECK(atLeast == 1 || atLeast == 2 || atLeast == 4 || atLeast == 8);\n\n  CHECK_LE(value, quic::kEightByteLimit);\n  uint8_t numBytes = 0;\n  if (value <= quic::kOneByteLimit) {\n    numBytes = 1;\n  } else if (value <= quic::kTwoByteLimit) {\n    numBytes = 2;\n  } else if (value <= quic::kFourByteLimit) {\n    numBytes = 4;\n  } else if (value <= quic::kEightByteLimit) {\n    numBytes = 8;\n  }\n  CHECK_NE(numBytes, 0);\n  numBytes = std::max(numBytes, atLeast);\n  CHECK(numBytes == 1 || numBytes == 2 || numBytes == 4 || numBytes == 8);\n  if (numBytes == 1) {\n    auto modified = static_cast<uint8_t>(value);\n    appender.writeBE<uint8_t>(modified);\n    return sizeof(modified);\n  } else if (numBytes == 2) {\n    auto reduced = static_cast<uint16_t>(value);\n    uint16_t modified = reduced | 0x4000;\n    appender.writeBE<uint16_t>(modified);\n    return sizeof(modified);\n  } else if (numBytes == 4) {\n    auto reduced = static_cast<uint32_t>(value);\n    uint32_t modified = reduced | 0x80000000;\n    appender.writeBE<uint32_t>(modified);\n    return sizeof(modified);\n  } else if (numBytes == 8) {\n    uint64_t modified = value | 0xC000000000000000;\n    appender.writeBE<uint64_t>(modified);\n    return sizeof(modified);\n  }\n  CHECK(false);\n}\n\nsize_t generateStreamPreface(folly::IOBufQueue& writeBuf,\n                             UnidirectionalStreamType type) {\n  folly::io::QueueAppender appender(&writeBuf, 8);\n  uint8_t size = 1 << (folly::Random::rand32() % 4);\n  auto bytesWritten = encodeQuicIntegerWithAtLeast(\n      static_cast<hq::StreamTypeType>(type), size, appender);\n  CHECK_GE(bytesWritten, size);\n  return bytesWritten;\n}\n\nstd::string paramsToTestName(const testing::TestParamInfo<TestParams>& info) {\n  std::vector<std::string> paramsV;\n  folly::split('-', info.param.alpn_, paramsV);\n  if (info.param.numBytesOnPushStream < kUnlimited) {\n    paramsV.emplace_back(\n        \"_\" + folly::to<std::string>(info.param.numBytesOnPushStream));\n  }\n  if (info.param.unidirectionalStreamsCredit != kDefaultUnidirStreamCredit) {\n    paramsV.emplace_back(\n        \"_\" + folly::to<std::string>(info.param.unidirectionalStreamsCredit));\n  }\n  if (!info.param.createQPACKStreams_) {\n    paramsV.emplace_back(\"_noqpack\");\n  }\n  if (info.param.datagrams_) {\n    paramsV.emplace_back(\"_datagrams\");\n  }\n  if (info.param.webTransport_) {\n    paramsV.emplace_back(\"_webtransport\");\n  }\n  return folly::join(\"\", paramsV);\n}\n\nfolly::Optional<std::pair<UnidirectionalStreamType, size_t>> parseStreamPreface(\n    folly::io::Cursor& cursor, std::string alpn) {\n  auto res = quic::follyutils::decodeQuicInteger(cursor);\n  if (!res) {\n    return folly::none;\n  }\n  auto prefaceEnum = UnidirectionalStreamType(res->first);\n  switch (prefaceEnum) {\n    case UnidirectionalStreamType::CONTROL:\n    case UnidirectionalStreamType::PUSH:\n    case UnidirectionalStreamType::QPACK_ENCODER:\n    case UnidirectionalStreamType::QPACK_DECODER:\n    case UnidirectionalStreamType::WEBTRANSPORT:\n      if (ALPN_HQ) {\n        return std::make_pair(prefaceEnum, res->second);\n      } else {\n        return folly::none;\n      }\n    default:\n      break;\n  }\n  return folly::none;\n}\n\nvoid parseReadData(HQUnidirectionalCodec* codec,\n                   folly::IOBufQueue& readBuf,\n                   std::unique_ptr<folly::IOBuf> buf) {\n  readBuf.append(std::move(buf));\n  auto ret = codec->onUnidirectionalIngress(readBuf.move());\n  readBuf.append(std::move(ret));\n}\n\nvoid createControlStream(quic::MockQuicSocketDriver* socketDriver,\n                         quic::StreamId id,\n                         UnidirectionalStreamType streamType) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  auto length = generateStreamPreface(writeBuf, streamType);\n  CHECK_EQ(length, writeBuf.chainLength());\n  socketDriver->sock_->setControlStream(id);\n  for (size_t i = 0; i < length; i++) {\n    socketDriver->addReadEvent(\n        id, writeBuf.splitAtMost(1), std::chrono::milliseconds(0));\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQSessionTestCommon.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <limits>\n#include <proxygen/lib/http/codec/HQFramer.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/QPACKDecoderCodec.h>\n#include <proxygen/lib/http/codec/QPACKEncoderCodec.h>\n#include <proxygen/lib/http/session/HQDownstreamSession.h>\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n\n#define ALPN_HQ (alpn.find(\"h3\") == 0)\n\nnamespace {\nconstexpr unsigned int kTransactionTimeout = 500;\nconstexpr unsigned int kConnectTimeout = 500;\nconstexpr size_t kQPACKTestDecoderMaxTableSize = 2048;\nconstexpr std::size_t kUnlimited = std::numeric_limits<std::size_t>::max();\nconst proxygen::hq::PushId kUnknownPushId =\n    std::numeric_limits<uint64_t>::max();\nconstexpr proxygen::hq::PushId kInitialPushId = 12345;\nconstexpr uint64_t kPushIdIncrement = 1;\nconstexpr uint64_t kDefaultUnidirStreamCredit = 3;\n} // namespace\n\nstruct TestParams {\n  std::string alpn_;\n  bool createQPACKStreams_{true};\n  bool shouldSendSettings_{true};\n  uint64_t unidirectionalStreamsCredit{kDefaultUnidirStreamCredit};\n  std::size_t numBytesOnPushStream{kUnlimited};\n  bool expectOnTransportReady{true};\n  bool datagrams_{false};\n  bool webTransport_{false};\n  bool checkUniridStreamCallbacks{true};\n};\n\nstd::string prBodyScriptToName(const std::vector<uint8_t>& bodyScript);\n\nsize_t encodeQuicIntegerWithAtLeast(uint64_t value,\n                                    uint8_t atLeast,\n                                    folly::io::QueueAppender& appender);\n\nstd::string paramsToTestName(const testing::TestParamInfo<TestParams>& info);\n\nsize_t generateStreamPreface(folly::IOBufQueue& writeBuf,\n                             proxygen::hq::UnidirectionalStreamType type);\n\nfolly::Optional<std::pair<proxygen::hq::UnidirectionalStreamType, size_t>>\nparseStreamPreface(folly::io::Cursor& cursor, std::string alpn);\n\nvoid parseReadData(proxygen::hq::HQUnidirectionalCodec* codec,\n                   folly::IOBufQueue& readBuf,\n                   std::unique_ptr<folly::IOBuf> buf);\n\nvoid createControlStream(quic::MockQuicSocketDriver* socketDriver,\n                         quic::StreamId id,\n                         proxygen::hq::UnidirectionalStreamType streamType);\n\nclass HQSessionTest\n    : public testing::TestWithParam<TestParams>\n    , public quic::MockQuicSocketDriver::LocalAppCallback\n    , public proxygen::hq::HQUnidirectionalCodec::Callback {\n\n public:\n  void SetUp() override {\n    folly::EventBaseManager::get()->clearEventBase();\n    proxygen::HTTPSession::setDefaultWriteBufferLimit(65536);\n    proxygen::HTTP2PriorityQueue::setNodeLifetime(std::chrono::milliseconds(2));\n    EXPECT_CALL(infoCb_, onTransactionAttached(testing::_))\n        .WillRepeatedly([this]() { onTransactionSymmetricCounter++; });\n    EXPECT_CALL(infoCb_, onTransactionAttached(testing::_))\n        .WillRepeatedly([this]() { onTransactionSymmetricCounter--; });\n  }\n\n  void TearDown() override {\n    EXPECT_EQ(onTransactionSymmetricCounter, 0);\n  }\n\n protected:\n  explicit HQSessionTest(\n      proxygen::TransportDirection direction,\n      folly::Optional<TestParams> overrideParams = folly::none)\n      : direction_(direction),\n        overrideParams_(overrideParams),\n        qpackEncoderCodec_(qpackCodec_, *this),\n        qpackDecoderCodec_(qpackCodec_, *this),\n        controllerContainer_(GetParam())\n\n  {\n    if (direction_ == proxygen::TransportDirection::DOWNSTREAM) {\n      hqSession_ = new proxygen::HQDownstreamSession(\n          std::chrono::milliseconds(kTransactionTimeout),\n          &controllerContainer_.mockController,\n          proxygen::mockTransportInfo,\n          nullptr);\n      nextUnidirectionalStreamId_ = 2;\n    } else if (direction_ == proxygen::TransportDirection::UPSTREAM) {\n      hqSession_ = new proxygen::HQUpstreamSession(\n          std::chrono::milliseconds(kTransactionTimeout),\n          std::chrono::milliseconds(kConnectTimeout),\n          &controllerContainer_.mockController,\n          proxygen::mockTransportInfo,\n          nullptr);\n      nextUnidirectionalStreamId_ = 3;\n    } else {\n      LOG(FATAL) << \"wrong TransportEnum\";\n    }\n\n    if (GetParam().datagrams_) {\n      egressSettings_.setSetting(proxygen::SettingsId::_HQ_DATAGRAM, 1);\n    }\n    if (GetParam().webTransport_) {\n      egressSettings_.setSetting(proxygen::SettingsId::_HQ_DATAGRAM, 1);\n      egressSettings_.setSetting(proxygen::SettingsId::ENABLE_CONNECT_PROTOCOL,\n                                 1);\n      egressSettings_.setSetting(proxygen::SettingsId::ENABLE_WEBTRANSPORT, 1);\n      egressSettings_.setSetting(proxygen::SettingsId::WT_INITIAL_MAX_DATA,\n                                 65536);\n    }\n\n    egressControlCodec_ = std::make_unique<proxygen::hq::HQControlCodec>(\n        nextUnidirectionalStreamId_,\n        direction_ == proxygen::TransportDirection::DOWNSTREAM\n            ? proxygen::TransportDirection::UPSTREAM\n            : proxygen::TransportDirection::DOWNSTREAM,\n        proxygen::hq::StreamDirection::EGRESS,\n        egressSettings_);\n    socketDriver_ = std::make_unique<quic::MockQuicSocketDriver>(\n        &eventBase_,\n        hqSession_,\n        hqSession_,\n        direction_ == proxygen::TransportDirection::DOWNSTREAM\n            ? quic::MockQuicSocketDriver::TransportEnum::SERVER\n            : quic::MockQuicSocketDriver::TransportEnum::CLIENT,\n        getProtocolString());\n\n    hqSession_->setSocket(socketDriver_->getSocket());\n\n    hqSession_->setEgressSettings(egressSettings_.getAllSettings());\n    qpackCodec_.setEncoderHeaderTableSize(1024);\n    qpackCodec_.setDecoderHeaderTableMaxSize(kQPACKTestDecoderMaxTableSize);\n    hqSession_->setInfoCallback(&infoCb_);\n\n    socketDriver_->setMaxUniStreams(GetParam().unidirectionalStreamsCredit);\n\n    EXPECT_CALL(infoCb_, onRead(testing::_, testing::_, testing::_))\n        .Times(testing::AnyNumber());\n\n    size_t ctrlStreamCount = 1;\n    size_t qpackStreamCount = (GetParam().createQPACKStreams_) ? 2 : 0;\n    numCtrlStreams_ = ctrlStreamCount + qpackStreamCount;\n    socketDriver_->setLocalAppCallback(this);\n\n    if (GetParam().checkUniridStreamCallbacks &&\n        GetParam().unidirectionalStreamsCredit >= numCtrlStreams_ &&\n        GetParam().alpn_.starts_with(\"h3\")) {\n      auto dirModifier =\n          (direction_ == proxygen::TransportDirection::DOWNSTREAM) ? 0 : 1;\n      EXPECT_CALL(infoCb_, onWrite(testing::_, testing::_))\n          .Times(testing::AtLeast(1));\n      for (auto i = 0; i < numCtrlStreams_; i++) {\n        folly::Optional<proxygen::HTTPCodec::StreamID> expectedStreamID =\n            i * 4 + 2 + dirModifier;\n        EXPECT_CALL(infoCb_, onRead(testing::_, testing::_, expectedStreamID))\n            .Times(testing::AtLeast(1));\n      }\n    }\n\n    quic::QuicSocket::TransportInfo transportInfo;\n    transportInfo.srtt = std::chrono::microseconds(100);\n    transportInfo.congestionWindow = 1500;\n\n    EXPECT_CALL(*socketDriver_->getSocket(), getTransportInfo())\n        .WillRepeatedly(testing::Return(transportInfo));\n  }\n\n  bool createControlStreams() {\n    // NOTE: this is NOT the stream credit advertised by the peer.\n    // this is the number of uni streams that we allow the peer to open. if that\n    // is not enough for the control streams, onTransportReady drops the\n    // connection, so don't try to create or write to new streams.\n    if (GetParam().unidirectionalStreamsCredit < numCtrlStreams_) {\n      return false;\n    }\n    if (!GetParam().alpn_.starts_with(\"h3\")) {\n      // this function can be called when alpn negotiation failed\n      return false;\n    }\n    connControlStreamId_ = nextUnidirectionalStreamId();\n    createControlStream(socketDriver_.get(),\n                        connControlStreamId_,\n                        proxygen::hq::UnidirectionalStreamType::CONTROL);\n    if (GetParam().createQPACKStreams_) {\n      createControlStream(\n          socketDriver_.get(),\n          nextUnidirectionalStreamId(),\n          proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER);\n      createControlStream(\n          socketDriver_.get(),\n          nextUnidirectionalStreamId(),\n          proxygen::hq::UnidirectionalStreamType::QPACK_DECODER);\n    }\n    if (GetParam().shouldSendSettings_) {\n      sendSettings();\n    }\n    return true;\n  }\n\n  void sendSettings() {\n    folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n    egressControlCodec_->generateSettings(writeBuf);\n    socketDriver_->addReadEvent(\n        connControlStreamId_, writeBuf.move(), std::chrono::milliseconds(0));\n  }\n\n  const std::string getProtocolString() const {\n    if (GetParam().alpn_ == \"h3\") {\n      return proxygen::kH3;\n    }\n    return GetParam().alpn_;\n  }\n\n  void readCallback(quic::StreamId id,\n                    std::unique_ptr<folly::IOBuf> buf) override {\n  }\n\n  void unidirectionalReadCallback(quic::StreamId id,\n                                  std::unique_ptr<folly::IOBuf> buf) override {\n    // check for control streams\n    if (buf->empty()) {\n      return;\n    }\n\n    auto it = controlStreams_.find(id);\n    if (it == controlStreams_.end()) {\n      folly::io::Cursor cursor(buf.get());\n      auto preface = parseStreamPreface(cursor, getProtocolString());\n      CHECK(preface) << \"Preface can not be parsed protocolString=\"\n                     << getProtocolString();\n      switch (preface->first) {\n        case proxygen::hq::UnidirectionalStreamType::CONTROL:\n          ingressControlCodec_ = std::make_unique<proxygen::hq::HQControlCodec>(\n              id,\n              proxygen::TransportDirection::UPSTREAM,\n              proxygen::hq::StreamDirection::INGRESS,\n              ingressSettings_,\n              preface->first);\n          ingressControlCodec_->setCallback(&httpCallbacks_);\n          break;\n        case proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER:\n        case proxygen::hq::UnidirectionalStreamType::QPACK_DECODER:\n          break;\n        case proxygen::hq::UnidirectionalStreamType::PUSH: {\n          auto pushIt = pushes_.find(id);\n          if (pushIt == pushes_.end()) {\n            auto pushId = quic::follyutils::decodeQuicInteger(cursor);\n            if (pushId) {\n              pushes_.emplace(id, pushId->first);\n            }\n          }\n        }\n          return;\n        case proxygen::hq::UnidirectionalStreamType::WEBTRANSPORT:\n          return;\n        default:\n          CHECK(false) << \"Unknown stream preface=\" << preface->first;\n      }\n      socketDriver_->sock_->setControlStream(id);\n      auto res = controlStreams_.emplace(id, preface->first);\n      it = res.first;\n      buf->trimStart(preface->second);\n      if (buf->empty()) {\n        return;\n      }\n    }\n\n    switch (it->second) {\n      case proxygen::hq::UnidirectionalStreamType::CONTROL:\n        parseReadData(\n            ingressControlCodec_.get(), ingressControlBuf_, std::move(buf));\n        break;\n      case proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER:\n        parseReadData(&qpackEncoderCodec_, encoderReadBuf_, std::move(buf));\n        break;\n      case proxygen::hq::UnidirectionalStreamType::QPACK_DECODER:\n        parseReadData(&qpackDecoderCodec_, decoderReadBuf_, std::move(buf));\n        break;\n      case proxygen::hq::UnidirectionalStreamType::PUSH:\n        VLOG(4) << \"Ingress push streams should not go through \"\n                << \"the unidirectional read path\";\n        break;\n      default:\n        CHECK(false) << \"Unknown stream type=\" << it->second;\n    }\n  }\n\n  void onError(proxygen::HTTPCodec::StreamID streamID,\n               const proxygen::HTTPException& error,\n               bool /*newTxn*/) override {\n    LOG(FATAL) << __func__ << \" streamID=\" << streamID\n               << \" error=\" << error.what();\n  }\n\n  quic::StreamId nextUnidirectionalStreamId() {\n    auto id = nextUnidirectionalStreamId_;\n    nextUnidirectionalStreamId_ += 4;\n    return id;\n  }\n\n  struct MockControllerContainer {\n    explicit MockControllerContainer(TestParams params) {\n      EXPECT_CALL(mockController, getHeaderIndexingStrategy())\n          .WillRepeatedly(testing::Return(\n              proxygen::HeaderIndexingStrategy::getDefaultInstance()));\n      testing::InSequence s;\n      EXPECT_CALL(mockController, attachSession(testing::_));\n      if (params.expectOnTransportReady) {\n        EXPECT_CALL(mockController, onTransportReady(testing::_));\n      }\n      EXPECT_CALL(mockController, detachSession(testing::_));\n    }\n    testing::StrictMock<proxygen::MockController> mockController;\n  };\n\n  testing::StrictMock<proxygen::MockController>& getMockController() {\n    return controllerContainer_.mockController;\n  }\n\n public:\n  quic::MockQuicSocketDriver* getSocketDriver() {\n    return socketDriver_.get();\n  }\n\n  proxygen::HQSession* getSession() {\n    return hqSession_;\n  }\n\n  void setSessionDestroyCallback(\n      folly::Function<void(const proxygen::HTTPSessionBase&)> cb) {\n    EXPECT_CALL(infoCb_, onDestroy(testing::_))\n        .WillOnce(testing::Invoke(\n            [&](const proxygen::HTTPSessionBase&) { cb(*hqSession_); }));\n  }\n\n  const TestParams& GetParam() const {\n    if (overrideParams_) {\n      return *overrideParams_;\n    } else {\n      const testing::TestWithParam<TestParams>* base = this;\n      return base->GetParam();\n    }\n  }\n\n  std::unique_ptr<folly::IOBuf> getH3Datagram(\n      uint64_t streamId,\n      std::unique_ptr<folly::IOBuf> datagram,\n      folly::Optional<uint64_t> ctxId = 0) {\n    // Prepend the H3 Datagram header to the datagram payload\n    // HTTP/3 Datagram {\n    //   Quarter Stream ID (i),\n    //   [Context ID (i)],\n    //   HTTP/3 Datagram Payload (..),\n    // }\n    quic::BufPtr headerBuf = quic::BufPtr(folly::IOBuf::create(0));\n    quic::BufAppender appender(headerBuf.get(),\n                               proxygen::kMaxDatagramHeaderSize);\n    auto streamIdRes = quic::encodeQuicInteger(\n        streamId / 4, [&](auto val) { appender.writeBE(val); });\n    if (streamIdRes.hasError()) {\n      return nullptr;\n    }\n    if (ctxId) {\n      auto ctxIdRes = quic::encodeQuicInteger(\n          *ctxId, [&](auto val) { appender.writeBE(val); });\n      if (ctxIdRes.hasError()) {\n        return nullptr;\n      }\n    }\n    quic::BufQueue queue(std::move(headerBuf));\n    queue.append(std::move(datagram));\n    return queue.move();\n  }\n\n protected:\n  // Utility to loop the evb but avoid blocking if there are no queued events.\n  void evbLoopNonBlockN(size_t count) {\n    for (; count > 0; count--) {\n      eventBase_.loopOnce(EVLOOP_NONBLOCK);\n    }\n  }\n\n  proxygen::TransportDirection direction_;\n  folly::Optional<TestParams> overrideParams_;\n  // Unidirectional Stream Codecs used for Ingress Only\n  proxygen::hq::QPACKEncoderCodec qpackEncoderCodec_;\n  proxygen::hq::QPACKDecoderCodec qpackDecoderCodec_;\n  // Read/WriteBufs for QPACKCodec, one for the encoder, one for the decoder\n  folly::IOBufQueue encoderReadBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue decoderReadBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue encoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue decoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};\n\n  folly::EventBase eventBase_;\n  proxygen::HQSession* hqSession_;\n  MockControllerContainer controllerContainer_;\n  std::unique_ptr<quic::MockQuicSocketDriver> socketDriver_;\n  // One QPACKCodec per session, handles both encoder and decoder\n  proxygen::QPACKCodec qpackCodec_;\n  std::map<quic::StreamId, proxygen::hq::UnidirectionalStreamType>\n      controlStreams_;\n  // Ingress Control Stream\n  std::unique_ptr<proxygen::hq::HQControlCodec> ingressControlCodec_;\n  folly::IOBufQueue ingressControlBuf_{folly::IOBufQueue::cacheChainLength()};\n  proxygen::HTTPSettings egressSettings_{\n      {proxygen::SettingsId::HEADER_TABLE_SIZE, kQPACKTestDecoderMaxTableSize},\n      {proxygen::SettingsId::MAX_HEADER_LIST_SIZE, 655335},\n      {proxygen::SettingsId::_HQ_QPACK_BLOCKED_STREAMS, 100}};\n  proxygen::HTTPSettings ingressSettings_;\n  proxygen::FakeHTTPCodecCallback httpCallbacks_;\n  uint8_t numCtrlStreams_{0};\n  quic::StreamId connControlStreamId_;\n  testing::NiceMock<proxygen::MockHTTPSessionInfoCallback> infoCb_;\n  quic::StreamId nextUnidirectionalStreamId_;\n  // Egress Control Stream\n  std::unique_ptr<proxygen::hq::HQControlCodec> egressControlCodec_;\n  folly::F14FastMap<quic::StreamId, proxygen::hq::PushId> pushes_;\n  uint64_t onTransactionSymmetricCounter{0};\n};\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQStreamBaseTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/HQStreamBase.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\n/**\n * A test to validate that the different stream base implementations\n * are working correctly.\n */\nnamespace {\nconstexpr quic::StreamId kDefaultIngressStream = 1;\nconstexpr quic::StreamId kDefaultEgressStream = 2;\nconstexpr quic::StreamId kDefaultBidirStream = 3;\n} // namespace\n\nclass HQStreamBaseTest : public Test {\n public:\n  void SetUp() override {\n    ssEgressMapping_ =\n        std::make_unique<detail::singlestream::SSEgress>(kDefaultEgressStream);\n    ssIngressMapping_ = std::make_unique<detail::singlestream::SSIngress>(\n        kDefaultIngressStream);\n    ssBidirMapping_ =\n        std::make_unique<detail::singlestream::SSBidir>(kDefaultBidirStream);\n    csBidirMappingEmpty_ =\n        std::make_unique<detail::composite::CSBidir>(folly::none, folly::none);\n    csBidirMappingEgressSet_ = std::make_unique<detail::composite::CSBidir>(\n        kDefaultEgressStream, folly::none);\n    csBidirMappingIngressSet_ = std::make_unique<detail::composite::CSBidir>(\n        folly::none, kDefaultIngressStream);\n    csBidirMappingBothSet_ = std::make_unique<detail::composite::CSBidir>(\n        kDefaultEgressStream, kDefaultIngressStream);\n  }\n\n  void TearDown() override {\n  }\n\n protected:\n  std::unique_ptr<detail::singlestream::SSEgress> ssEgressMapping_;\n  std::unique_ptr<detail::singlestream::SSIngress> ssIngressMapping_;\n  std::unique_ptr<detail::singlestream::SSBidir> ssBidirMapping_;\n  std::unique_ptr<detail::composite::CSBidir> csBidirMappingEmpty_;\n  std::unique_ptr<detail::composite::CSBidir> csBidirMappingEgressSet_;\n  std::unique_ptr<detail::composite::CSBidir> csBidirMappingIngressSet_;\n  std::unique_ptr<detail::composite::CSBidir> csBidirMappingBothSet_;\n};\n\nusing HQStreamDeathTest = HQStreamBaseTest;\n\nTEST_F(HQStreamBaseTest, TestSingleStreamBidir) {\n  EXPECT_EQ(kDefaultBidirStream, ssBidirMapping_->getEgressStreamId());\n  EXPECT_EQ(kDefaultBidirStream, ssBidirMapping_->getIngressStreamId());\n  EXPECT_EQ(kDefaultBidirStream, ssBidirMapping_->getStreamId());\n\n  EXPECT_TRUE(ssBidirMapping_->isUsing(kDefaultBidirStream));\n\n  EXPECT_FALSE(ssBidirMapping_->isUsing(kDefaultEgressStream));\n  EXPECT_FALSE(ssBidirMapping_->isUsing(kDefaultIngressStream));\n}\n\nTEST_F(HQStreamBaseTest, TestSingleStreamEgressOnly) {\n  EXPECT_EQ(kDefaultEgressStream, ssEgressMapping_->getEgressStreamId());\n  EXPECT_EQ(kDefaultEgressStream, ssEgressMapping_->getStreamId());\n\n  EXPECT_TRUE(ssEgressMapping_->isUsing(kDefaultEgressStream));\n\n  EXPECT_FALSE(ssEgressMapping_->isUsing(kDefaultBidirStream));\n  EXPECT_FALSE(ssEgressMapping_->isUsing(kDefaultIngressStream));\n}\n\nTEST_F(HQStreamDeathTest, TestSingleStreamEgressOnly) {\n  EXPECT_EXIT(ssEgressMapping_->getIngressStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Egress only stream\");\n}\n\nTEST_F(HQStreamBaseTest, TestCompositeBidirEmpty) {\n  EXPECT_FALSE(csBidirMappingEmpty_->isUsing(kDefaultEgressStream));\n  EXPECT_FALSE(csBidirMappingEmpty_->isUsing(kDefaultIngressStream));\n  EXPECT_FALSE(csBidirMappingEmpty_->isUsing(kDefaultBidirStream));\n}\n\nTEST_F(HQStreamDeathTest, TestCompositeBidirEmpty) {\n  EXPECT_EXIT(csBidirMappingEmpty_->getIngressStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ingress stream MUST be assigned before being accessed\");\n  EXPECT_EXIT(csBidirMappingEmpty_->getEgressStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Egress stream MUST be assigned before being accessed\");\n  EXPECT_EXIT(csBidirMappingEmpty_->getStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ambiguous call 'getStreamId' on a composite stream\");\n}\n\nTEST_F(HQStreamBaseTest, TestCompositeBidirEgress) {\n  EXPECT_EQ(kDefaultEgressStream,\n            csBidirMappingEgressSet_->getEgressStreamId());\n\n  EXPECT_TRUE(csBidirMappingEgressSet_->isUsing(kDefaultEgressStream));\n\n  EXPECT_FALSE(csBidirMappingEgressSet_->isUsing(kDefaultIngressStream));\n  EXPECT_FALSE(csBidirMappingEgressSet_->isUsing(kDefaultBidirStream));\n}\n\nTEST_F(HQStreamDeathTest, TestCompositeBidirEgress) {\n  EXPECT_EXIT(csBidirMappingEgressSet_->getIngressStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ingress stream MUST be assigned before being accessed\");\n  EXPECT_EXIT(csBidirMappingEgressSet_->getStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ambiguous call 'getStreamId' on a composite stream\");\n}\n\nTEST_F(HQStreamBaseTest, TestCompositeBidirIngress) {\n  EXPECT_EQ(kDefaultIngressStream,\n            csBidirMappingIngressSet_->getIngressStreamId());\n  EXPECT_FALSE(csBidirMappingIngressSet_->isUsing(kDefaultEgressStream));\n\n  EXPECT_TRUE(csBidirMappingIngressSet_->isUsing(kDefaultIngressStream));\n  EXPECT_FALSE(csBidirMappingIngressSet_->isUsing(kDefaultBidirStream));\n}\n\nTEST_F(HQStreamDeathTest, TestCompositeBidirIngress) {\n  EXPECT_EXIT(csBidirMappingIngressSet_->getEgressStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Egress stream MUST be assigned before being accessed\");\n  EXPECT_EXIT(csBidirMappingIngressSet_->getStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ambiguous call 'getStreamId' on a composite stream\");\n}\n\nTEST_F(HQStreamBaseTest, TestCompositeBidirBoth) {\n  EXPECT_EQ(kDefaultEgressStream, csBidirMappingBothSet_->getEgressStreamId());\n  EXPECT_EQ(kDefaultIngressStream,\n            csBidirMappingBothSet_->getIngressStreamId());\n\n  EXPECT_TRUE(csBidirMappingBothSet_->isUsing(kDefaultEgressStream));\n  EXPECT_TRUE(csBidirMappingBothSet_->isUsing(kDefaultIngressStream));\n\n  EXPECT_FALSE(csBidirMappingBothSet_->isUsing(kDefaultBidirStream));\n}\n\nTEST_F(HQStreamDeathTest, TestCompositeBidirEmptyEgress) {\n  EXPECT_EXIT(csBidirMappingBothSet_->getStreamId(),\n              KilledBySignal(SIGABRT),\n              \"Ambiguous call 'getStreamId' on a composite stream\");\n}\n\nTEST_F(HQStreamBaseTest, TestGetStreamDirection) {\n  EXPECT_EQ(ssEgressMapping_->getStreamDirection(),\n            HTTPException::Direction::EGRESS);\n\n  EXPECT_EQ(ssIngressMapping_->getStreamDirection(),\n            HTTPException::Direction::INGRESS);\n\n  EXPECT_EQ(ssBidirMapping_->getStreamDirection(),\n            HTTPException::Direction::INGRESS_AND_EGRESS);\n\n  EXPECT_EQ(csBidirMappingEmpty_->getStreamDirection(),\n            HTTPException::Direction::INGRESS_AND_EGRESS);\n\n  EXPECT_EQ(csBidirMappingEgressSet_->getStreamDirection(),\n            HTTPException::Direction::INGRESS_AND_EGRESS);\n\n  EXPECT_EQ(csBidirMappingIngressSet_->getStreamDirection(),\n            HTTPException::Direction::INGRESS_AND_EGRESS);\n\n  EXPECT_EQ(csBidirMappingBothSet_->getStreamDirection(),\n            HTTPException::Direction::INGRESS_AND_EGRESS);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQStreamDispatcherTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <deque>\n#include <folly/io/Cursor.h>\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n#include <proxygen/lib/http/session/test/HQSessionTestCommon.h>\n\nusing namespace proxygen;\nusing namespace quic;\nusing namespace testing;\n\nclass MockDispatcher\n    : public HQUniStreamDispatcher::Callback\n    , public HQBidiStreamDispatcher::Callback {\n public:\n  using ReadCallbackAssignF =\n      std::function<void(quic::StreamId, hq::UnidirectionalStreamType, size_t)>;\n  using PrefaceParseF =\n      std::function<folly::Optional<hq::UnidirectionalStreamType>(uint64_t)>;\n  using PrefaceBidiParseF =\n      std::function<folly::Optional<hq::BidirectionalStreamType>(uint64_t)>;\n  using StreamRejectF = std::function<void(quic::StreamId)>;\n  using NewPushStreamF =\n      std::function<void(quic::StreamId, hq::PushId, size_t)>;\n  using NewWTStreamF =\n      std::function<void(quic::StreamId, quic::StreamId, size_t)>;\n  using NewRequestF = std::function<void(quic::StreamId)>;\n\n  explicit MockDispatcher(folly::EventBase* evb) : evb_(evb) {\n  }\n\n  void expectOnNewPushStream(const NewPushStreamF& impl) {\n    auto& exp = EXPECT_CALL(\n        *this, dispatchPushStream(::testing::_, ::testing::_, ::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  void expectOnNewWTUni(const NewWTStreamF& impl) {\n    auto& exp = EXPECT_CALL(\n        *this, dispatchUniWTStream(::testing::_, ::testing::_, ::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  void expectOnNewWTBidi(const NewWTStreamF& impl) {\n    auto& exp = EXPECT_CALL(\n        *this, dispatchBidiWTStream(::testing::_, ::testing::_, ::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  void expectOnNewRequest(const NewRequestF& impl) {\n    auto& exp = EXPECT_CALL(*this, dispatchRequestStream(::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  void expectAssignReadCallback(const ReadCallbackAssignF& impl) {\n    auto& exp = EXPECT_CALL(\n        *this, dispatchControlStream(::testing::_, ::testing::_, ::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  void expectParsePreface(const PrefaceParseF& impl) {\n    auto& exp = EXPECT_CALL(*this, parseUniStreamPreface(::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl)).RetiresOnSaturation();\n    }\n  }\n\n  void expectParseBidiPreface(const PrefaceBidiParseF& impl) {\n    auto& exp = EXPECT_CALL(*this, parseBidiStreamPreface(::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl)).RetiresOnSaturation();\n    }\n  }\n\n  void expectRejectStream(const StreamRejectF& impl) {\n    auto& exp = EXPECT_CALL(*this, rejectStream(::testing::_));\n    if (impl) {\n      exp.WillOnce(::testing::Invoke(impl));\n    }\n  }\n\n  folly::EventBase* getEventBase() const override {\n    return evb_;\n  }\n\n  std::chrono::milliseconds getDispatchTimeout() const override {\n    return std::chrono::seconds(1);\n  }\n\n  MOCK_METHOD(void, dispatchPushStream, (quic::StreamId, hq::PushId, size_t));\n  MOCK_METHOD(void,\n              dispatchUniWTStream,\n              (quic::StreamId, quic::StreamId, size_t));\n  MOCK_METHOD(void,\n              dispatchControlStream,\n              (quic::StreamId, hq::UnidirectionalStreamType, size_t));\n  MOCK_METHOD(folly::Optional<hq::UnidirectionalStreamType>,\n              parseUniStreamPreface,\n              (uint64_t));\n  MOCK_METHOD(void, rejectStream, (quic::StreamId));\n\n  // Bidi methods\n  MOCK_METHOD(folly::Optional<hq::BidirectionalStreamType>,\n              parseBidiStreamPreface,\n              (uint64_t));\n\n  MOCK_METHOD(void, dispatchRequestStream, (quic::StreamId));\n\n  MOCK_METHOD(void,\n              dispatchBidiWTStream,\n              (quic::StreamId, quic::StreamId, size_t));\n  folly::EventBase* evb_{nullptr};\n};\n\ntemplate <typename T>\nclass HQStreamDispatcherTest : public Test {\n public:\n  using PeekIterator = quic::CircularDeque<StreamBuffer>::const_iterator;\n  using PeekData = MockDispatcher::PeekData;\n  using ReadError = MockDispatcher::ReadError;\n\n  void SetUp() override {\n    incomingData_.clear();\n  }\n\n  void TearDown() override {\n  }\n\n  // Different methods to feed the dispatcher\n\n  // Encodes and sends a single encoded integer the dispatcher\n  void sendData(quic::StreamId id, uint64_t val, uint8_t atLeast) {\n    folly::IOBufQueue data{folly::IOBufQueue::cacheChainLength()};\n    folly::io::QueueAppender appender(&data, atLeast);\n    encodeQuicIntegerWithAtLeast(val, atLeast, appender);\n    sendData(id, data.move());\n  }\n\n  // Encodes preface + push id\n  void sendData(quic::StreamId id,\n                uint64_t preface,\n                uint64_t pushId,\n                uint8_t atLeast,\n                uint8_t split) {\n    folly::IOBufQueue data{folly::IOBufQueue::cacheChainLength()};\n    folly::io::QueueAppender appender(&data, atLeast);\n    encodeQuicIntegerWithAtLeast(preface, atLeast, appender);\n    encodeQuicIntegerWithAtLeast(pushId, atLeast, appender);\n\n    for (size_t toClone = split;; toClone += split) {\n      incomingData_.clear();\n      folly::io::Cursor c(data.front());\n      auto buf = folly::IOBuf::create(data.chainLength());\n      c.cloneAtMost(buf, toClone);\n      sendData(id, std::move(buf));\n      if (toClone >= data.chainLength()) {\n        break;\n      }\n    }\n  }\n\n  void sendData(quic::StreamId id, std::unique_ptr<folly::IOBuf> data) {\n    incomingData_.emplace_back(std::move(data), 0, false);\n    dispatcher_.onDataAvailable(\n        id,\n        folly::Range<PeekIterator>(incomingData_.cbegin(),\n                                   incomingData_.size()));\n  }\n\n protected:\n  folly::EventBase evb_;\n  quic::CircularDeque<StreamBuffer> incomingData_;\n  MockDispatcher dispatcherCallback_{&evb_};\n  T dispatcher_{dispatcherCallback_, proxygen::TransportDirection::UPSTREAM};\n};\n\nusing UnidirectionalReadDispatcherTest =\n    HQStreamDispatcherTest<HQUniStreamDispatcher>;\nusing BidirectionalReadDispatcherTest =\n    HQStreamDispatcherTest<HQBidiStreamDispatcher>;\n\nTEST_F(UnidirectionalReadDispatcherTest, TestDispatchControlPreface) {\n\n  quic::StreamId expectedId = 5;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  dispatcherCallback_.expectParsePreface([&](uint64_t /* type */) {\n    return hq::UnidirectionalStreamType::CONTROL;\n  });\n\n  // Expect the assign call\n  dispatcherCallback_.expectAssignReadCallback(\n      [&](quic::StreamId id,\n          hq::UnidirectionalStreamType /* type */,\n          size_t consumed) {\n        ASSERT_EQ(id, expectedId);\n        ASSERT_EQ(consumed, atLeastBytes);\n      });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> assignReadCallback\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::CONTROL),\n           atLeastBytes);\n}\n\nTEST_F(UnidirectionalReadDispatcherTest,\n       TestDispatchPushPrefaceNewPushStreamApi) {\n\n  quic::StreamId expectedId = 5;\n  hq::PushId expectedPushId = 151234567;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  for (auto i = 0; i < 5; i++) {\n    dispatcherCallback_.expectParsePreface([&](uint64_t /* type */) {\n      return hq::UnidirectionalStreamType::PUSH;\n    });\n  }\n\n  // Expect the assign call\n  dispatcherCallback_.expectOnNewPushStream(\n      [&](quic::StreamId id, hq::PushId pushId, size_t consumed) {\n        ASSERT_EQ(id, expectedId);\n        ASSERT_EQ(pushId, expectedPushId);\n        ASSERT_EQ(consumed, atLeastBytes + atLeastBytes);\n      });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> onNewPushId\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::PUSH),\n           expectedPushId,\n           atLeastBytes,\n           /*split=*/1);\n}\n\nTEST_F(UnidirectionalReadDispatcherTest, TestDispatchWTUni) {\n\n  quic::StreamId expectedId = 5;\n  quic::StreamId expectedSessionId = 151234567;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  for (auto i = 0; i < 5; i++) {\n    dispatcherCallback_.expectParsePreface([&](uint64_t /* type */) {\n      return hq::UnidirectionalStreamType::WEBTRANSPORT;\n    });\n  }\n\n  // Expect the assign call\n  dispatcherCallback_.expectOnNewWTUni(\n      [&](quic::StreamId id, quic::StreamId sessionId, size_t consumed) {\n        ASSERT_EQ(id, expectedId);\n        ASSERT_EQ(sessionId, expectedSessionId);\n        ASSERT_EQ(consumed, atLeastBytes + atLeastBytes);\n      });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> onNewWTUni\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::WEBTRANSPORT),\n           expectedSessionId,\n           atLeastBytes,\n           /*split=*/1);\n}\n\nTEST_F(UnidirectionalReadDispatcherTest, TestRejectUnrecognizedPreface) {\n\n  quic::StreamId expectedId = 5;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  dispatcherCallback_.expectParsePreface(\n      [&](uint64_t /* type */) { return folly::none; });\n\n  // Expect the reject call\n  dispatcherCallback_.expectRejectStream(\n      [&](quic::StreamId id) { ASSERT_EQ(id, expectedId); });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> assignReadCallback\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::CONTROL),\n           atLeastBytes);\n}\n\nTEST_F(BidirectionalReadDispatcherTest, TestDispatchRequest) {\n\n  quic::StreamId expectedId = 5;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  dispatcherCallback_.expectParseBidiPreface([&](uint64_t /* type */) {\n    return hq::BidirectionalStreamType::REQUEST;\n  });\n  // Expect the assign call\n  dispatcherCallback_.expectOnNewRequest(\n      [&](quic::StreamId id) { ASSERT_EQ(id, expectedId); });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> onNewRequest\n  sendData(\n      expectedId, static_cast<uint64_t>(hq::FrameType::HEADERS), atLeastBytes);\n}\n\nTEST_F(BidirectionalReadDispatcherTest, TestDispatchWTBidi) {\n\n  quic::StreamId expectedId = 5;\n  quic::StreamId expectedSessionId = 151234567;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  for (auto i = 0; i < 5; i++) {\n    dispatcherCallback_.expectParseBidiPreface([&](uint64_t /* type */) {\n      return hq::BidirectionalStreamType::WEBTRANSPORT;\n    });\n  }\n  // Expect the assign call\n  dispatcherCallback_.expectOnNewWTBidi(\n      [&](quic::StreamId id, quic::StreamId sessionId, size_t consumed) {\n        ASSERT_EQ(id, expectedId);\n        ASSERT_EQ(sessionId, expectedSessionId);\n        ASSERT_EQ(consumed, atLeastBytes + atLeastBytes);\n      });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> onNewWTBidi\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::WEBTRANSPORT),\n           expectedSessionId,\n           atLeastBytes,\n           /*split=*/1);\n}\n\nTEST_F(BidirectionalReadDispatcherTest, TestRejectUnrecognizedPreface) {\n\n  quic::StreamId expectedId = 5;\n  uint8_t atLeastBytes = 4;\n  // Mock the preface parsing\n  dispatcherCallback_.expectParseBidiPreface(\n      [&](uint64_t /* type */) { return folly::none; });\n\n  // Expect the reject call\n  dispatcherCallback_.expectRejectStream(\n      [&](quic::StreamId id) { ASSERT_EQ(id, expectedId); });\n\n  // Prior to sending data, give the dispatcher ownership\n  // on the stream id (like \"onNewUnidirectionalStream\" does)\n  dispatcher_.takeTemporaryOwnership(expectedId);\n\n  // Attempt to write the preface\n  // Expected invocation chain:\n  //   sendData -> dispatcher->onData -> (...) -> assignReadCallback\n  sendData(expectedId,\n           static_cast<uint64_t>(hq::UnidirectionalStreamType::WEBTRANSPORT),\n           atLeastBytes);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQUpstreamSessionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/test/HQUpstreamSessionTest.h>\n\n#include <proxygen/lib/http/codec/CodecUtil.h>\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n\n#include <folly/futures/Future.h>\n#include <folly/portability/GTest.h>\n#include <limits>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n#include <proxygen/lib/http/session/test/HQSessionTestCommon.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nusing namespace proxygen;\nusing namespace proxygen::hq;\nusing namespace quic;\nusing namespace testing;\nusing namespace std::chrono;\n\nnamespace {\nconstexpr quic::StreamId kQPACKEncoderIngressStreamId = 7;\nconstexpr quic::StreamId kQPACKDecoderEgressStreamId = 10;\n} // namespace\n\nstd::pair<HTTPCodec::StreamID, std::unique_ptr<HTTPCodec>>\nHQUpstreamSessionTest::makeCodec(HTTPCodec::StreamID id) {\n  return {id,\n          std::make_unique<hq::HQStreamCodec>(\n              id,\n              TransportDirection::DOWNSTREAM,\n              qpackCodec_,\n              encoderWriteBuf_,\n              decoderWriteBuf_,\n              [] { return std::numeric_limits<uint64_t>::max(); },\n              ingressSettings_)};\n}\n\nvoid HQUpstreamSessionTest::sendResponse(quic::StreamId id,\n                                         const HTTPMessage& resp,\n                                         std::unique_ptr<folly::IOBuf> body,\n                                         bool eom) {\n  auto c = makeCodec(id);\n  auto res =\n      streams_.emplace(std::piecewise_construct,\n                       std::forward_as_tuple(id),\n                       std::forward_as_tuple(c.first, std::move(c.second)));\n  auto& stream = res.first->second;\n  stream.readEOF = eom;\n  stream.codec->generateHeader(\n      stream.buf, stream.codecId, resp, body == nullptr ? eom : false);\n  if (body && body->computeChainDataLength() > 0) {\n    stream.codec->generateBody(\n        stream.buf, stream.codecId, std::move(body), folly::none, eom);\n  }\n}\n\nvoid HQUpstreamSessionTest::sendPartialBody(quic::StreamId id,\n                                            std::unique_ptr<folly::IOBuf> body,\n                                            bool eom) {\n  auto it = streams_.find(id);\n  CHECK(it != streams_.end());\n  auto& stream = it->second;\n\n  stream.readEOF = eom;\n  if (body) {\n    stream.codec->generateBody(\n        stream.buf, stream.codecId, std::move(body), folly::none, eom);\n  }\n}\n\nquic::StreamId HQUpstreamSessionTest::nextUnidirectionalStreamId() {\n  auto id = nextUnidirectionalStreamId_;\n  nextUnidirectionalStreamId_ += 4;\n  return id;\n}\n\nvoid HQUpstreamSessionTest::SetUp() {\n  HQSessionTest::SetUp();\n  dynamic_cast<HQUpstreamSession*>(hqSession_)->setConnectCallback(&connectCb_);\n\n  EXPECT_CALL(connectCb_, connectSuccess());\n\n  hqSession_->onTransportReady();\n\n  createControlStreams();\n\n  flushAndLoop();\n  EXPECT_EQ(httpCallbacks_.settings, 1);\n}\n\nvoid HQUpstreamSessionTest::TearDown() {\n  // With control streams we may need an extra loop for proper shutdown\n  if (!socketDriver_->isClosed()) {\n    // Send the first GOAWAY with MAX_STREAM_ID immediately\n    sendGoaway(HTTPCodec::MaxStreamID);\n    // Schedule the second GOAWAY with the last seen stream ID, after some\n    // delay\n    sendGoaway(socketDriver_->getMaxStreamId(), milliseconds(50));\n  }\n  eventBase_.loopOnce();\n}\n\nvoid HQUpstreamSessionTest::sendGoaway(quic::StreamId lastStreamId,\n                                       milliseconds delay) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  egressControlCodec_->generateGoaway(\n      writeBuf, lastStreamId, ErrorCode::NO_ERROR);\n  socketDriver_->addReadEvent(connControlStreamId_, writeBuf.move(), delay);\n}\n\ntemplate <class HandlerType>\nstd::unique_ptr<StrictMock<HandlerType>>\nHQUpstreamSessionTest::openTransactionBase(bool expectStartPaused) {\n  // Returns a mock handler with txn_ field set in it\n  auto handler = std::make_unique<StrictMock<HandlerType>>();\n  handler->expectTransaction();\n  if (expectStartPaused) {\n    handler->expectEgressPaused();\n  }\n  HTTPTransaction* txn = hqSession_->newTransaction(handler.get());\n  EXPECT_EQ(txn, handler->txn_);\n  return handler;\n}\n\nstd::unique_ptr<StrictMock<MockHTTPHandler>>\nHQUpstreamSessionTest::openTransaction() {\n  return openTransactionBase<MockHTTPHandler>();\n}\n\nvoid HQUpstreamSessionTest::flushAndLoop(bool eof,\n                                         milliseconds eofDelay,\n                                         milliseconds initialDelay,\n                                         std::function<void()> extraEventsFn) {\n  flush(eof, eofDelay, initialDelay, extraEventsFn);\n  CHECK(eventBase_.loop());\n}\n\nvoid HQUpstreamSessionTest::flushAndLoopN(uint64_t n,\n                                          bool eof,\n                                          milliseconds eofDelay,\n                                          milliseconds initialDelay,\n                                          std::function<void()> extraEventsFn) {\n  flush(eof, eofDelay, initialDelay, extraEventsFn);\n  for (uint64_t i = 0; i < n; i++) {\n    eventBase_.loopOnce();\n  }\n}\n\nbool HQUpstreamSessionTest::flush(bool eof,\n                                  milliseconds eofDelay,\n                                  milliseconds initialDelay,\n                                  std::function<void()> extraEventsFn) {\n  bool done = true;\n  if (!encoderWriteBuf_.empty()) {\n    socketDriver_->addReadEvent(\n        kQPACKEncoderIngressStreamId, encoderWriteBuf_.move(), milliseconds(0));\n  }\n  for (auto& stream : streams_) {\n    if (socketDriver_->isStreamIdle(stream.first)) {\n      continue;\n    }\n    if (stream.second.buf.chainLength() > 0) {\n      socketDriver_->addReadEvent(\n          stream.first, stream.second.buf.move(), initialDelay);\n      done = false;\n    }\n    // EOM -> stream EOF\n    if (stream.second.readEOF) {\n      socketDriver_->addReadEOF(stream.first, eofDelay);\n      done = false;\n    }\n  }\n  if (!socketDriver_->inDatagrams_.empty()) {\n    socketDriver_->addDatagramsAvailableReadEvent(initialDelay);\n  }\n  if (extraEventsFn) {\n    extraEventsFn();\n  }\n  if (eof || eofDelay.count() > 0) {\n    /*  wonkiness.  Should somehow close the connection?\n     * socketDriver_->addReadEOF(1, eofDelay);\n     */\n  }\n  return done;\n}\n\nstd::unique_ptr<MockSessionObserver>\nHQUpstreamSessionTest::addMockSessionObserver(\n    MockSessionObserver::EventSet eventSet) {\n  auto observer = std::make_unique<NiceMock<MockSessionObserver>>(eventSet);\n  EXPECT_CALL(*observer, attached(_));\n  hqSession_->addObserver(observer.get());\n  return observer;\n}\nStrictMock<MockController>& HQUpstreamSessionTest::getMockController() {\n  return controllerContainer_.mockController;\n}\n\n// Use this test class for hq only tests\nusing HQUpstreamSessionTest = HQUpstreamSessionTest;\n// Use this test class for hq only tests with qpack encoder streams on/off\nusing HQUpstreamSessionTestQPACK = HQUpstreamSessionTest;\n// Use this test class for hq only tests with Datagram support\nusing HQUpstreamSessionTestDatagram = HQUpstreamSessionTest;\n\nTEST_P(HQUpstreamSessionTest, SimpleGet) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendPadding(123); // ignored by peer\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, GetWithTrailers) {\n  auto handler = openTransaction();\n  auto req = getGetRequest();\n  handler->txn_->sendHeaders(req);\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"trailer1\");\n  handler->txn_->sendTrailers(trailers);\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectTrailers();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  auto id = handler->txn_->getID();\n  sendResponse(id, *std::get<0>(resp), std::move(std::get<1>(resp)), false);\n  auto it = streams_.find(id);\n  CHECK(it != streams_.end());\n  auto& stream = it->second;\n  trailers.remove(\"x-trailer-1\");\n  trailers.add(\"x-trailer-2\", \"trailer2\");\n  stream.codec->generateTrailers(stream.buf, stream.codecId, trailers);\n  stream.codec->generateEOM(stream.buf, stream.codecId);\n  stream.readEOF = true;\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, AbortOnBodyWithBlockedQPACKTrailers) {\n  auto handler = openTransaction();\n  auto req = getGetRequest();\n  handler->txn_->sendHeaders(req);\n  HTTPHeaders trailers;\n  trailers.add(\"x-trailer-1\", \"trailer1\");\n  handler->txn_->sendTrailers(trailers);\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody([&handler] { handler->txn_->sendAbort(); });\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  auto id = handler->txn_->getID();\n  sendResponse(id, *std::get<0>(resp), std::move(std::get<1>(resp)), false);\n  auto control1 = encoderWriteBuf_.move();\n  flushAndLoopN(1);\n  auto it = streams_.find(id);\n  CHECK(it != streams_.end());\n  auto& stream = it->second;\n  trailers.remove(\"x-trailer-1\");\n  trailers.add(\"x-trailer-2\", \"trailer2\");\n  stream.codec->generateTrailers(stream.buf, stream.codecId, trailers);\n  stream.codec->generateEOM(stream.buf, stream.codecId);\n  stream.readEOF = true;\n  auto control2 = encoderWriteBuf_.move();\n  encoderWriteBuf_.append(std::move(control1));\n  flushAndLoopN(1);\n  encoderWriteBuf_.append(std::move(control2));\n\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, PriorityUpdateIntoTransport) {\n  auto handler = openTransaction();\n  auto req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, i\");\n  socketDriver_->expectSetPriority(handler->txn_->getID(),\n                                   quic::HTTPPriorityQueue::Priority(3, true));\n  handler->txn_->sendHeadersWithEOM(req);\n\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  std::get<0>(resp)->getHeaders().add(HTTP_HEADER_PRIORITY, \"u=5\");\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  socketDriver_->expectSetPriority(handler->txn_->getID(),\n                                   quic::HTTPPriorityQueue::Priority(5, false));\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, DisableEgressPrioritization) {\n  hqSession_->setEnableEgressPrioritization(false);\n  auto handler = openTransaction();\n  auto req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, i\");\n  handler->txn_->sendHeadersWithEOM(req);\n\n  handler->expectHeaders();\n  handler->expectBody(\n      [&]() { handler->txn_->updateAndSendPriority(HTTPPriority(5, true)); });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  std::get<0>(resp)->getHeaders().add(HTTP_HEADER_PRIORITY, \"u=5\");\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n\n  EXPECT_CALL(*socketDriver_->getSocket(), setStreamPriority(_, _)).Times(0);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, TestSupportsMoreTransactions) {\n  auto infoCb = std::make_unique<\n      testing::NiceMock<proxygen::MockHTTPSessionInfoCallback>>();\n  hqSession_->setInfoCallback(infoCb.get());\n\n  auto resp = makeResponse(200, 100);\n  // set the max number of bidirectional streams = 1.\n  socketDriver_->setMaxBidiStreams(1);\n\n  // we should be able to open only one transaction\n  EXPECT_TRUE(hqSession_->supportsMoreTransactions());\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n\n  // unable to create more transactions, should return nullptr\n  EXPECT_FALSE(hqSession_->supportsMoreTransactions());\n  auto* txn = hqSession_->newTransaction(handler.get());\n  EXPECT_FALSE(txn);\n\n  // receiving stream credits from peer should invoke\n  // onSettingsOutgoingStreamsNotFull cb\n  EXPECT_CALL(*infoCb, onSettingsOutgoingStreamsNotFull).Times(1);\n  socketDriver_->setMaxBidiStreams(2);\n  EXPECT_TRUE(hqSession_->supportsMoreTransactions());\n\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, SendPriorityUpdate) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->expectHeaders();\n  handler->expectBody([&]() {\n    socketDriver_->expectSetPriority(\n        handler->txn_->getID(), quic::HTTPPriorityQueue::Priority(5, true));\n    handler->txn_->updateAndSendPriority(HTTPPriority(5, true));\n  });\n  handler->txn_->sendEOM();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, SkipPriorityUpdateAfterSeenEOM) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM([&]() {\n    EXPECT_CALL(*socketDriver_->getSocket(),\n                setStreamPriority(handler->txn_->getID(), testing::_))\n        .Times(0);\n    handler->txn_->updateAndSendPriority(HTTPPriority(5, true));\n  });\n  handler->txn_->sendEOM();\n\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, NoNewTransactionIfSockIsNotGood) {\n  socketDriver_->sockGood_ = false;\n  EXPECT_EQ(hqSession_->newTransaction(nullptr), nullptr);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, DropConnectionWithEarlyDataFailedError) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n\n  EXPECT_CALL(*handler, _onError(_))\n      .WillOnce(Invoke([](const HTTPException& error) {\n        EXPECT_EQ(error.getProxygenError(), kErrorEarlyDataFailed);\n        EXPECT_TRUE(std::string(error.what()).find(\"quic loses race\") !=\n                    std::string::npos);\n      }));\n  handler->expectDetachTransaction();\n  socketDriver_->deliverConnectionError(\n      {HTTP3::ErrorCode::GIVEUP_ZERO_RTT, \"quic loses race\"});\n}\n\nTEST_P(HQUpstreamSessionTest, TestGetHistoricalMaxOutgoingStreams) {\n  EXPECT_EQ(hqSession_->getHistoricalMaxOutgoingStreams(), 0);\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  auto handler1 = openTransaction();\n  handler1->txn_->sendHeaders(getGetRequest());\n  handler1->txn_->sendEOM();\n  handler1->expectHeaders();\n  handler1->expectBody();\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n  auto resp1 = makeResponse(200, 100);\n  sendResponse(handler1->txn_->getID(),\n               *std::get<0>(resp1),\n               std::move(std::get<1>(resp1)),\n               true);\n  flushAndLoop();\n  EXPECT_EQ(hqSession_->getHistoricalMaxOutgoingStreams(), 2);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, ResponseTermedByFin) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.setHTTPVersion(1, 0);\n  // HTTP/1.0 response with no content-length, termed by tranport FIN\n  sendResponse(handler->txn_->getID(), resp, makeBuf(100), true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, WaitForReplaySafeCallback) {\n  auto handler = openTransaction();\n  StrictMock<folly::test::MockReplaySafetyCallback> cb1;\n  StrictMock<folly::test::MockReplaySafetyCallback> cb2;\n  StrictMock<folly::test::MockReplaySafetyCallback> cb3;\n\n  auto sock = socketDriver_->getSocket();\n  EXPECT_CALL(*sock, replaySafe()).WillRepeatedly(Return(false));\n  handler->txn_->addWaitingForReplaySafety(&cb1);\n  handler->txn_->addWaitingForReplaySafety(&cb2);\n  handler->txn_->addWaitingForReplaySafety(&cb3);\n  handler->txn_->removeWaitingForReplaySafety(&cb2);\n\n  ON_CALL(*sock, replaySafe()).WillByDefault(Return(true));\n  EXPECT_CALL(cb1, onReplaySafe_());\n  EXPECT_CALL(cb3, onReplaySafe_());\n  hqSession_->onReplaySafe();\n\n  handler->expectDetachTransaction();\n  handler->txn_->sendAbort();\n  hqSession_->closeWhenIdle();\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQUpstreamSessionTest, AlreadyReplaySafe) {\n  auto handler = openTransaction();\n\n  StrictMock<folly::test::MockReplaySafetyCallback> cb;\n\n  auto sock = socketDriver_->getSocket();\n  EXPECT_CALL(*sock, replaySafe()).WillRepeatedly(Return(true));\n  EXPECT_CALL(cb, onReplaySafe_());\n  handler->txn_->addWaitingForReplaySafety(&cb);\n\n  handler->expectDetachTransaction();\n  handler->txn_->sendAbort();\n  hqSession_->closeWhenIdle();\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQUpstreamSessionTest, Test100Continue) {\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  auto req = getPostRequest(10);\n  req.getHeaders().add(HTTP_HEADER_EXPECT, \"100-continue\");\n  handler->txn_->sendHeaders(req);\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  sendResponse(handler->txn_->getID(), *makeResponse(100), nullptr, false);\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, TestSetIngressTimeoutAfterSendEom) {\n  hqSession_->setIngressTimeoutAfterEom(true);\n\n  // Send EOM separately.\n  auto handler1 = openTransaction();\n  handler1->expectHeaders();\n  handler1->expectBody();\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n\n  auto transaction1 = handler1->txn_;\n  EXPECT_TRUE(transaction1->hasIdleTimeout());\n  transaction1->setIdleTimeout(std::chrono::milliseconds(100));\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendHeaders(getPostRequest(10));\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendBody(makeBuf(100) /* body */);\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendEOM();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction1->isScheduled());\n\n  auto response1 = makeResponse(200, 100);\n  sendResponse(transaction1->getID(),\n               *std::get<0>(response1),\n               std::move(std::get<1>(response1)),\n               true);\n  flushAndLoop();\n\n  // Send EOM with header.\n  auto handler2 = openTransaction();\n  handler2->expectHeaders();\n  handler2->expectBody();\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n\n  auto transaction2 = handler2->txn_;\n  EXPECT_FALSE(transaction2->isScheduled());\n  transaction2->sendHeadersWithOptionalEOM(getPostRequest(), true /* eom */);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction2->isScheduled());\n\n  auto response2 = makeResponse(200, 100);\n  sendResponse(transaction2->getID(),\n               *std::get<0>(response2),\n               std::move(std::get<1>(response2)),\n               true);\n  flushAndLoop();\n\n  // Send EOM with body.\n  auto handler3 = openTransaction();\n  handler3->expectHeaders();\n  handler3->expectBody();\n  handler3->expectEOM();\n  handler3->expectDetachTransaction();\n\n  auto transaction3 = handler3->txn_;\n  EXPECT_FALSE(transaction3->isScheduled());\n  transaction3->sendHeaders(getPostRequest());\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction3->isScheduled());\n  transaction3->sendBody(makeBuf(100) /* body */);\n  transaction3->sendEOM();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction3->isScheduled());\n\n  auto response3 = makeResponse(200, 100);\n  sendResponse(transaction3->getID(),\n               *std::get<0>(response3),\n               std::move(std::get<1>(response3)),\n               true);\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, GetAddresses) {\n  EXPECT_EQ(socketDriver_->localAddress_, hqSession_->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, hqSession_->getPeerAddress());\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTest, GetAddressesFromBase) {\n  auto* sessionBase = dynamic_cast<HTTPSessionBase*>(hqSession_);\n  EXPECT_EQ(socketDriver_->localAddress_, sessionBase->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, sessionBase->getPeerAddress());\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTest, GetAddressesAfterDropConnection) {\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n  EXPECT_EQ(socketDriver_->localAddress_, hqSession_->getLocalAddress());\n  EXPECT_EQ(socketDriver_->peerAddress_, hqSession_->getPeerAddress());\n}\n\nTEST_P(HQUpstreamSessionTest, DropConnectionTwice) {\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->closeWhenIdle();\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTest, DropConnectionTwiceWithPendingStreams) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  socketDriver_->addReadEvent(15, writeBuf.move());\n  flushAndLoopN(1);\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n  eventBase_.loopOnce();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, DropConnectionAfterCloseWhenIdle) {\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->closeWhenIdle();\n  flushAndLoopN(1);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTest, DropConnectionWithStreamAfterCloseWhenIdle) {\n  HQSession::DestructorGuard dg(hqSession_);\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  hqSession_->closeWhenIdle();\n  flushAndLoopN(1);\n  handler->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getHttp3ErrorCode(), HTTP3::ErrorCode::HTTP_NO_ERROR);\n  });\n  handler->expectDetachTransaction();\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTest, NotifyConnectCallbackBeforeDestruct) {\n  MockConnectCallback connectCb;\n  dynamic_cast<HQUpstreamSession*>(hqSession_)->setConnectCallback(&connectCb);\n  EXPECT_CALL(connectCb, connectError(_)).Times(1);\n  socketDriver_->deliverConnectionError(\n      {quic::LocalErrorCode::CONNECT_FAILED, \"Peer closed\"});\n}\n\nTEST_P(HQUpstreamSessionTest, DropFromConnectError) {\n  MockConnectCallback connectCb;\n  auto* upstreamSess = dynamic_cast<HQUpstreamSession*>(hqSession_);\n  upstreamSess->setConnectCallback(&connectCb);\n  EXPECT_CALL(connectCb, connectError(_)).WillOnce(InvokeWithoutArgs([&] {\n    hqSession_->dropConnection();\n  }));\n  socketDriver_->addOnConnectionEndEvent(0);\n  eventBase_.loop();\n}\n\nTEST_P(HQUpstreamSessionTest, FirstPeerPacketProcessed) {\n  MockConnectCallback connectCb;\n  auto* upstreamSess = dynamic_cast<HQUpstreamSession*>(hqSession_);\n  upstreamSess->setConnectCallback(&connectCb);\n  EXPECT_CALL(connectCb, onFirstPeerPacketProcessed());\n  upstreamSess->onFirstPeerPacketProcessed();\n\n  upstreamSess->closeWhenIdle();\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQUpstreamSessionTest, NotifyReplaySafeAfterTransportReady) {\n  MockConnectCallback connectCb;\n  auto* upstreamSess = dynamic_cast<HQUpstreamSession*>(hqSession_);\n  upstreamSess->setConnectCallback(&connectCb);\n\n  // onTransportReady gets called in SetUp() already\n\n  EXPECT_CALL(connectCb, onReplaySafe());\n  upstreamSess->onReplaySafe();\n\n  upstreamSess->closeWhenIdle();\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQUpstreamSessionTest, TestConnectionToken) {\n  HQSession::DestructorGuard dg(hqSession_);\n  auto handler = openTransaction();\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  // The transaction should not have a connection token\n  // by default.\n  EXPECT_EQ(handler->txn_->getConnectionToken(), folly::none);\n\n  // Passing connection token to a session should\n  // make it visible to the transaction.\n  HTTPTransaction::ConnectionToken connToken{\"TOKEN1234\"};\n  hqSession_->setConnectionToken(connToken);\n\n  EXPECT_NE(handler->txn_->getConnectionToken(), folly::none);\n  EXPECT_EQ(*handler->txn_->getConnectionToken(), connToken);\n\n  // Clean up the session and the transaction.\n  hqSession_->onConnectionError(\n      quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                      \"Connect Failure with Open streams\"));\n  eventBase_.loop();\n  EXPECT_EQ(hqSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::SHUTDOWN);\n}\n\nTEST_P(HQUpstreamSessionTest, OnConnectionErrorWithOpenStreams) {\n  HQSession::DestructorGuard dg(hqSession_);\n  auto handler = openTransaction();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  hqSession_->onConnectionError(\n      quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                      \"Connect Failure with Open streams\"));\n  eventBase_.loop();\n  EXPECT_EQ(hqSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::SHUTDOWN);\n}\n\nTEST_P(HQUpstreamSessionTest, OnConnectionErrorWithOpenStreamsPause) {\n  HQSession::DestructorGuard dg(hqSession_);\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n  handler1->txn_->sendHeaders(getGetRequest());\n  handler1->txn_->sendEOM();\n  handler2->txn_->sendHeaders(getGetRequest());\n  handler2->txn_->sendEOM();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler1->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  resp = makeResponse(200, 100);\n  sendResponse(handler2->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flush();\n  eventBase_.runInLoop([&] {\n    hqSession_->onConnectionError(\n        quic::QuicError(quic::LocalErrorCode::CONNECT_FAILED,\n                        \"Connect Failure with Open streams\"));\n  });\n  handler1->expectError(\n      [&](const HTTPException&) { handler2->txn_->pauseIngress(); });\n  handler1->expectDetachTransaction();\n  handler2->expectError();\n  handler2->expectDetachTransaction();\n  eventBase_.loop();\n  EXPECT_EQ(hqSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::SHUTDOWN);\n}\n\nTEST_P(HQUpstreamSessionTest, GoawayStreamsUnacknowledged) {\n  std::vector<std::unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n  auto numStreams = 4;\n  quic::StreamId goawayId = (numStreams * 4) / 2 + 4;\n  for (auto n = 1; n <= numStreams; n++) {\n    handlers.emplace_back(openTransaction());\n    auto handler = handlers.back().get();\n    handler->txn_->sendHeaders(getGetRequest());\n    handler->txn_->sendEOM();\n    EXPECT_CALL(*handler, _onGoaway(testing::_)).Times(2);\n    if (handler->txn_->getID() >= goawayId) {\n      handler->expectError([hdlr = handler](const HTTPException& err) {\n        EXPECT_TRUE(err.hasProxygenError());\n        EXPECT_EQ(err.getProxygenError(), kErrorStreamUnacknowledged);\n        ASSERT_EQ(\n            folly::to<std::string>(\"StreamUnacknowledged on transaction id: \",\n                                   hdlr->txn_->getID()),\n            std::string(err.what()));\n      });\n    } else {\n      handler->expectHeaders();\n      handler->expectBody();\n      handler->expectEOM();\n    }\n\n    if (n < numStreams) {\n      handler->expectDetachTransaction();\n    } else {\n      handler->expectDetachTransaction([&] {\n        // Make sure the session can't create any more transactions.\n        MockHTTPHandler handler2;\n        EXPECT_EQ(hqSession_->newTransaction(&handler2), nullptr);\n        // Send the responses for the acknowledged streams\n        for (auto& hdlr : handlers) {\n          auto id = hdlr->txn_->getID();\n          if (id < goawayId) {\n            auto resp = makeResponse(200, 100);\n            sendResponse(\n                id, *std::get<0>(resp), std::move(std::get<1>(resp)), true);\n          }\n        }\n        flush();\n      });\n    }\n  }\n\n  sendGoaway(HTTPCodec::MaxStreamID, milliseconds(50));\n  sendGoaway(goawayId, milliseconds(100));\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTest, GoawayIncreased) {\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  egressControlCodec_->generateGoaway(writeBuf, 12, ErrorCode::NO_ERROR);\n  socketDriver_->addReadEvent(connControlStreamId_, writeBuf.move());\n  flushAndLoopN(1);\n  proxygen::hq::HQControlCodec egressControlCodec2(\n      nextUnidirectionalStreamId_,\n      proxygen::TransportDirection::DOWNSTREAM,\n      proxygen::hq::StreamDirection::EGRESS,\n      egressSettings_);\n  egressControlCodec2.generateGoaway(writeBuf, 16, ErrorCode::NO_ERROR);\n  socketDriver_->addReadEvent(connControlStreamId_, writeBuf.move());\n  flushAndLoop();\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_ID_ERROR);\n}\n\nTEST_P(HQUpstreamSessionTest, DelayedQPACK) {\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto cont = makeResponse(100);\n  auto resp = makeResponse(200, 100);\n  cont->getHeaders().add(\"X-FB-Debug\", \"jvrbfihvuvvclgvfkbkikjlcbruleekj\");\n  std::get<0>(resp)->getHeaders().add(\"X-FB-Debug\",\n                                      \"egedljtrbullljdjjvtjkekebffefclj\");\n  sendResponse(handler->txn_->getID(), *cont, nullptr, false);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  auto control = encoderWriteBuf_.move();\n  flushAndLoopN(1);\n  encoderWriteBuf_.append(std::move(control));\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, DelayedQPACKTimeout) {\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectError();\n  auto resp = makeResponse(200, 100);\n  std::get<0>(resp)->getHeaders().add(\"X-FB-Debug\",\n                                      \"egedljtrbullljdjjvtjkekebffefclj\");\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  auto control = encoderWriteBuf_.move();\n  handler->expectDetachTransaction([this, &control]() mutable {\n    // have the header block arrive after destruction\n    encoderWriteBuf_.append(std::move(control));\n    eventBase_.runInLoop([this] { flush(); });\n    eventBase_.runAfterDelay([this] { hqSession_->closeWhenIdle(); }, 100);\n  });\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTest, QPACKDecoderStreamFlushed) {\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  handler->txn_->sendHeadersWithOptionalEOM(getGetRequest(), true);\n  flushAndLoopN(1);\n  handler->expectDetachTransaction();\n  handler->txn_->sendAbort();\n  flushAndLoop();\n  auto& decoderStream = socketDriver_->streams_[kQPACKDecoderEgressStreamId];\n  // type byte plus cancel\n  EXPECT_EQ(decoderStream.writeBuf.chainLength(), 2);\n\n  handler = openTransaction();\n  handler->txn_->sendHeadersWithOptionalEOM(getGetRequest(), true);\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  auto resp = makeResponse(200, 100);\n  std::get<0>(resp)->getHeaders().add(\"Response\", \"Dynamic\");\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  auto qpackData = encoderWriteBuf_.move();\n  flushAndLoopN(1);\n  encoderWriteBuf_.append(std::move(qpackData));\n  handler->expectDetachTransaction();\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n  // type byte plus cancel plus ack\n  EXPECT_EQ(decoderStream.writeBuf.chainLength(), 3);\n}\n\nTEST_P(HQUpstreamSessionTest, DelayedQPACKAfterReset) {\n  // Stand on your head and spit wooden nickels\n  // Ensure the session does not deliver input data to a transaction detached\n  // earlier the same loop\n  InSequence enforceOrder;\n  // Send two requests\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n  handler1->txn_->sendHeadersWithOptionalEOM(getGetRequest(), true);\n  handler2->txn_->sendHeadersWithOptionalEOM(getGetRequest(), true);\n  // Send a response to txn1 that will block on QPACK data\n  auto resp1 = makeResponse(302, 0);\n  std::get<0>(resp1)->getHeaders().add(\"Response1\", \"Dynamic\");\n  sendResponse(handler1->txn_->getID(),\n               *std::get<0>(resp1),\n               std::move(std::get<1>(resp1)),\n               true);\n  // Save first QPACK data\n  auto qpackData1 = encoderWriteBuf_.move();\n  // Send response to txn2 that will block on *different* QPACK data\n  auto resp2 = makeResponse(302, 0);\n  std::get<0>(resp2)->getHeaders().add(\"Respnse2\", \"Dynamic\");\n  sendResponse(handler2->txn_->getID(),\n               *std::get<0>(resp2),\n               std::move(std::get<1>(resp2)),\n               false);\n  // Save second QPACK data\n  auto qpackData2 = encoderWriteBuf_.move();\n\n  // Abort *both* txns when txn1 gets headers.  This will leave txn2 detached\n  // with pending input data in this loop.\n  handler1->expectHeaders([&] {\n    handler1->txn_->sendAbort();\n    handler2->txn_->sendAbort();\n  });\n\n  auto streamIt1 = streams_.find(handler1->txn_->getID());\n  CHECK(streamIt1 != streams_.end());\n  auto streamIt2 = streams_.find(handler2->txn_->getID());\n  CHECK(streamIt2 != streams_.end());\n  // add all the events in the same callback, with the stream data coming\n  // before the QPACK data\n  std::vector<MockQuicSocketDriver::ReadEvent> events;\n  events.emplace_back(handler2->txn_->getID(),\n                      streamIt2->second.buf.move(),\n                      streamIt2->second.readEOF,\n                      folly::none,\n                      false);\n  events.emplace_back(handler1->txn_->getID(),\n                      streamIt1->second.buf.move(),\n                      streamIt1->second.readEOF,\n                      folly::none,\n                      false);\n  events.emplace_back(kQPACKEncoderIngressStreamId,\n                      std::move(qpackData1),\n                      false,\n                      folly::none,\n                      false);\n  socketDriver_->addReadEvents(std::move(events));\n  handler2->expectDetachTransaction();\n  handler1->expectDetachTransaction();\n  eventBase_.loopOnce();\n  // Add the QPACK data that would unblock txn2.  It's long gone and this\n  // should be a no-op.\n  socketDriver_->addReadEvent(kQPACKEncoderIngressStreamId,\n                              std::move(qpackData2));\n  eventBase_.loopOnce();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTestQPACK, QPACKQueuedOnClose) {\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  std::get<0>(resp)->getHeaders().add(\"X-FB-Debug\",\n                                      \"egedljtrbullljdjjvtjkekebffefclj\");\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  auto control = encoderWriteBuf_.move();\n  // Entire response is delivered from the transport to the session\n  flushAndLoopN(1);\n  // Connection end but the stream is still pending\n  socketDriver_->addOnConnectionEndEvent(0);\n  eventBase_.loop();\n}\n\nTEST_P(HQUpstreamSessionTest, TestDropConnectionSynchronously) {\n  std::unique_ptr<testing::NiceMock<proxygen::MockHTTPSessionInfoCallback>>\n      infoCb = std::make_unique<\n          testing::NiceMock<proxygen::MockHTTPSessionInfoCallback>>();\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->expectError();\n  handler->expectDetachTransaction();\n  hqSession_->setInfoCallback(infoCb.get());\n  // the session is destroyed synchronously, so the destroy callback gets\n  // invoked\n  EXPECT_CALL(*infoCb.get(), onDestroy(_)).Times(1);\n  hqSession_->dropConnection();\n  infoCb.reset();\n  eventBase_.loopOnce();\n}\n\nTEST_P(HQUpstreamSessionTest, TestOnStopSendingHTTPRequestRejected) {\n  auto handler = openTransaction();\n  auto streamId = handler->txn_->getID();\n  handler->txn_->sendHeaders(getGetRequest());\n  eventBase_.loopOnce();\n  EXPECT_CALL(*socketDriver_->getSocket(),\n              resetStream(streamId, HTTP3::ErrorCode::HTTP_REQUEST_CANCELLED))\n      .Times(2) // once from on stopSending and once from sendAbort\n      .WillRepeatedly(Invoke([&](quic::StreamId id, quic::ApplicationErrorCode)\n                                 -> quic::Expected<void, quic::LocalErrorCode> {\n        // setWriteError will cancaleDeliveryCallbacks which will invoke\n        // onCanceled to decrementPendingByteEvents on the txn.\n        socketDriver_->setWriteError(id);\n        return {};\n      }));\n  EXPECT_CALL(*handler, _onError(_))\n      .Times(1)\n      .WillOnce(Invoke([](HTTPException ex) {\n        EXPECT_EQ(kErrorStreamUnacknowledged, ex.getProxygenError());\n      }));\n  handler->expectDetachTransaction();\n  hqSession_->onStopSending(streamId, HTTP3::ErrorCode::HTTP_REQUEST_REJECTED);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, TestGreaseFramePerSession) {\n  // a grease frame is created when creating the first transaction\n  auto handler1 = openTransaction();\n  auto streamId1 = handler1->txn_->getID();\n  handler1->txn_->sendHeaders(getGetRequest());\n  handler1->txn_->sendEOM();\n  handler1->expectHeaders();\n  handler1->expectBody();\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n  auto resp1 = makeResponse(200, 100);\n  sendResponse(handler1->txn_->getID(),\n               *std::get<0>(resp1),\n               std::move(std::get<1>(resp1)),\n               true);\n  flushAndLoop();\n  FakeHTTPCodecCallback callback1;\n  std::unique_ptr<HQStreamCodec> downstreamCodec =\n      std::make_unique<hq::HQStreamCodec>(\n          streamId1,\n          TransportDirection::DOWNSTREAM,\n          qpackCodec_,\n          encoderWriteBuf_,\n          decoderWriteBuf_,\n          [] { return std::numeric_limits<uint64_t>::max(); },\n          ingressSettings_);\n  downstreamCodec->setCallback(&callback1);\n  downstreamCodec->onIngress(\n      *socketDriver_->streams_[streamId1].writeBuf.front());\n  EXPECT_EQ(callback1.unknownFrames, 1);\n  EXPECT_EQ(callback1.greaseFrames, 1);\n\n  // no grease frame is created when creating the second transaction\n  auto handler2 = openTransaction();\n  auto streamId2 = handler2->txn_->getID();\n  handler2->txn_->sendHeaders(getGetRequest());\n  handler2->txn_->sendEOM();\n  handler2->expectHeaders();\n  handler2->expectBody();\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n  auto resp2 = makeResponse(200, 100);\n  sendResponse(handler2->txn_->getID(),\n               *std::get<0>(resp2),\n               std::move(std::get<1>(resp2)),\n               true);\n  flushAndLoop();\n  FakeHTTPCodecCallback callback2;\n  downstreamCodec->setCallback(&callback2);\n  downstreamCodec->onIngress(\n      *socketDriver_->streams_[streamId2].writeBuf.front());\n  EXPECT_EQ(callback2.unknownFrames, 0);\n  EXPECT_EQ(callback2.greaseFrames, 0);\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTest, TestIngressLimitedSessionWithNewRequests) {\n  auto infoCb = std::make_unique<\n      testing::NiceMock<proxygen::MockHTTPSessionInfoCallback>>();\n  hqSession_->setInfoCallback(infoCb.get());\n\n  // set limit to 5,000 for this test\n  hqSession_->setReadBufferLimit(5000);\n\n  // create first stream and generate large response exceeding ingress limit\n  EXPECT_CALL(*infoCb, onIngressLimitExceeded(_)).Times(1);\n  auto handler1 = openTransaction();\n  auto streamId1 = handler1->txn_->getID();\n  handler1->txn_->sendHeaders(getGetRequest());\n  handler1->txn_->sendEOM();\n  // pause ingress so we don't invoke notifyIngressBodyProcessed and decrement\n  // the session's pendingBodySize_\n  handler1->txn_->pauseIngress();\n  handler1->expectHeaders();\n  handler1->expectBody();\n\n  auto resp1 = makeResponse(200, 6000);\n  sendResponse(\n      streamId1, *std::get<0>(resp1), std::move(std::get<1>(resp1)), true);\n  flushAndLoop();\n\n  EXPECT_CALL(*socketDriver_->getSocket(), pauseRead(_));\n  auto handler2 = openTransaction();\n  handler2->expectDetachTransaction();\n  handler2->txn_->sendAbort();\n\n  // resume first txn and expect callbacks\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n  handler1->txn_->resumeIngress();\n\n  flushAndLoop();\n  hqSession_->setInfoCallback(nullptr);\n  hqSession_->closeWhenIdle();\n}\n\n//   - in HQ we already have sent SETTINGS in SetUp, so tests that multiple\n//     setting frames are not allowed\nTEST_P(HQUpstreamSessionTest, ExtraSettings) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  // Need to use a new codec. Since generating settings twice is\n  // forbidden\n  HQControlCodec auxControlCodec_{nextUnidirectionalStreamId_,\n                                  TransportDirection::DOWNSTREAM,\n                                  StreamDirection::EGRESS,\n                                  egressSettings_,\n                                  UnidirectionalStreamType::CONTROL};\n  folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n  auxControlCodec_.generateSettings(writeBuf);\n  socketDriver_->addReadEvent(\n      connControlStreamId_, writeBuf.move(), milliseconds(0));\n\n  flushAndLoop();\n\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_FRAME_UNEXPECTED);\n}\n\nTEST_P(HQUpstreamSessionTest, Observer_Attach_Detach_Destroyed) {\n\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    hqSession_->removeObserver(observer.get());\n  }\n\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, destroyed(_, _));\n    hqSession_->dropConnection();\n  }\n}\n\nTEST_P(HQUpstreamSessionTest, Observer_RequestStarted) {\n  // Add an observer NOT subscribed to the RequestStarted event\n  auto observerUnsubscribed =\n      addMockSessionObserver(MockSessionObserver::EventSetBuilder().build());\n  hqSession_->addObserver(observerUnsubscribed.get());\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::RequestStarted)\n          .build());\n  hqSession_->addObserver(observerSubscribed.get());\n\n  HTTPTransactionObserverAccessor* actualTxnObserverAccessor;\n  // expect to see a request started with header 'x-meta-test-header' having\n  // value 'abc123'\n  EXPECT_CALL(*observerSubscribed, requestStarted(_, _))\n      .WillOnce(\n          Invoke([&](HTTPSessionObserverAccessor*,\n                     const MockSessionObserver::RequestStartedEvent& event) {\n            EXPECT_EQ(event.request.getHeaders().getSingleOrEmpty(\n                          \"x-meta-test-header\"),\n                      \"abc123\");\n            actualTxnObserverAccessor = event.txnObserverAccessor;\n          }));\n\n  auto handler = openTransaction();\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(\"x-meta-test-header\", \"abc123\");\n  handler->txn_->sendHeaders(req);\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  flushAndLoop();\n  EXPECT_EQ(actualTxnObserverAccessor, handler->txn_->getObserverAccessor());\n  hqSession_->closeWhenIdle();\n}\n\n// Test Cases for which Settings are not sent in the test SetUp\nusing HQUpstreamSessionTestNoSettings = HQUpstreamSessionTest;\n\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTestNoSettings,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.shouldSendSettings_ = false;\n                           return tp;\n                         }()),\n                         paramsToTestName);\nTEST_P(HQUpstreamSessionTestNoSettings, SimpleGet) {\n  EXPECT_CALL(connectCb_, connectError(_)).Times(1);\n  socketDriver_->deliverConnectionError(\n      {quic::LocalErrorCode::CONNECT_FAILED, \"Peer closed\"});\n}\n\nTEST_P(HQUpstreamSessionTestNoSettings, GoawayBeforeSettings) {\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  sendGoaway(HTTPCodec::MaxStreamID);\n  flushAndLoop();\n\n  EXPECT_EQ(*socketDriver_->streams_[kConnectionStreamId].error,\n            HTTP3::ErrorCode::HTTP_MISSING_SETTINGS);\n}\n\n/**\n * Push tests\n */\n\nclass HQUpstreamSessionTestPush : public HQUpstreamSessionTest {\n public:\n  void SetUp() override {\n    HQUpstreamSessionTest::SetUp();\n    SetUpAssocHandler();\n    nextPushId_ = kInitialPushId;\n    lastPushPromiseHeadersSize_.compressed = 0;\n    lastPushPromiseHeadersSize_.uncompressed = 0;\n  }\n\n  void SetUpAssocHandler() {\n    // Create the primary request\n    assocHandler_ = openTransaction();\n    assocHandler_->txn_->sendHeaders(getGetRequest());\n    assocHandler_->expectDetachTransaction();\n  }\n\n  void TearDown() override {\n    HQUpstreamSessionTest::TearDown();\n  }\n\n  void SetUpServerPushLifecycleCallbacks() {\n    if (!SLCcallback_) {\n      SLCcallback_ = std::make_unique<MockServerPushLifecycleCallback>();\n      hqSession_->setServerPushLifecycleCallback(SLCcallback_.get());\n    }\n  }\n\n  hq::PushId nextPushId() {\n    auto id = nextPushId_;\n    nextPushId_ += kPushIdIncrement;\n    return id;\n  }\n\n  // NOTE: Using odd numbers for push ids, to allow detecting\n  // subtle bugs where streamID and pushID are quietly misplaced\n  bool isPushIdValid(hq::PushId pushId) {\n    return (pushId % 2) == 1;\n  }\n\n  using WriteFunctor =\n      std::function<folly::Optional<size_t>(folly::IOBufQueue&)>;\n  folly::Optional<size_t> writeUpTo(quic::StreamId id,\n                                    size_t maxlen,\n                                    WriteFunctor functor) {\n    // Lookup the stream\n    auto findRes = streams_.find(id);\n    if (findRes == streams_.end()) {\n      return folly::none;\n    }\n\n    folly::IOBufQueue tmpbuf{folly::IOBufQueue::cacheChainLength()};\n    auto funcres = functor(tmpbuf);\n    if (!funcres) {\n      return folly::none;\n    }\n\n    auto eventbuf = tmpbuf.splitAtMost(maxlen);\n    auto wlen = eventbuf->length();\n    CHECK_LE(wlen, maxlen) << \"The written len must not exceed the max len\";\n    socketDriver_->addReadEvent(id, std::move(eventbuf), milliseconds(0));\n    return wlen;\n  }\n\n  // Use the common facilities to write the quic integer\n  folly::Optional<size_t> writePushStreamPreface(quic::StreamId id,\n                                                 size_t maxlen) {\n    WriteFunctor f = [](folly::IOBufQueue& outbuf) {\n      return generateStreamPreface(outbuf, hq::UnidirectionalStreamType::PUSH);\n    };\n\n    auto res = writeUpTo(id, maxlen, f);\n    return res;\n  }\n\n  folly::Optional<size_t> writeUnframedPushId(quic::StreamId id,\n                                              size_t maxlen,\n                                              hq::PushId pushId) {\n    WriteFunctor f = [=](folly::IOBufQueue& outbuf) -> folly::Optional<size_t> {\n      folly::io::QueueAppender appender(&outbuf, 8);\n      uint8_t size = 1 << (folly::Random::rand32() % 4);\n      auto wlen = encodeQuicIntegerWithAtLeast(pushId, size, appender);\n      CHECK_GE(wlen, size);\n      return wlen;\n    };\n\n    auto res = writeUpTo(id, maxlen, f);\n    return res;\n  }\n\n  void expectPushPromiseBegin(\n      std::function<void(HTTPCodec::StreamID, hq::PushId)> callback =\n          std::function<void(HTTPCodec::StreamID, hq::PushId)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectPushPromiseBegin(callback);\n  }\n\n  void expectPushPromise(\n      std::function<void(HTTPCodec::StreamID, hq::PushId, HTTPMessage*)>\n          callback = std::function<\n              void(HTTPCodec::StreamID, hq::PushId, HTTPMessage*)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectPushPromise(callback);\n  }\n\n  void expectNascentPushStreamBegin(\n      std::function<void(HTTPCodec::StreamID, bool)> callback =\n          std::function<void(HTTPCodec::StreamID, bool)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectNascentPushStreamBegin(callback);\n  }\n\n  void expectNascentPushStream(\n      std::function<void(HTTPCodec::StreamID, hq::PushId, bool)> callback =\n          std::function<void(HTTPCodec::StreamID, hq::PushId, bool)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectNascentPushStream(callback);\n  }\n\n  void expectNascentEof(\n      std::function<void(HTTPCodec::StreamID, folly::Optional<hq::PushId>)>\n          callback = std::function<void(HTTPCodec::StreamID,\n                                        folly::Optional<hq::PushId>)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectNascentEof(callback);\n  }\n\n  void expectOrphanedNascentStream(\n      std::function<void(HTTPCodec::StreamID, folly::Optional<hq::PushId>)>\n          callback = std::function<void(HTTPCodec::StreamID,\n                                        folly::Optional<hq::PushId>)>()) {\n\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectOrphanedNascentStream(callback);\n  }\n\n  void expectHalfOpenPushedTxn(\n      std::function<\n          void(const HTTPTransaction*, hq::PushId, HTTPCodec::StreamID, bool)>\n          callback = std::function<void(const HTTPTransaction*,\n                                        hq::PushId,\n                                        HTTPCodec::StreamID,\n                                        bool)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectHalfOpenPushedTxn(callback);\n  }\n\n  void expectPushedTxn(std::function<void(const HTTPTransaction*,\n                                          HTTPCodec::StreamID,\n                                          hq::PushId,\n                                          HTTPCodec::StreamID,\n                                          bool)> callback =\n                           std::function<void(const HTTPTransaction*,\n                                              HTTPCodec::StreamID,\n                                              hq::PushId,\n                                              HTTPCodec::StreamID,\n                                              bool)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectPushedTxn(callback);\n  }\n\n  void expectPushedTxnTimeout(\n      std::function<void(const HTTPTransaction*)> callback =\n          std::function<void(const HTTPTransaction*)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectPushedTxnTimeout(callback);\n  }\n\n  void expectOrphanedHalfOpenPushedTxn(\n      std::function<void(const HTTPTransaction*)> callback =\n          std::function<void(const HTTPTransaction*)>()) {\n    SetUpServerPushLifecycleCallbacks();\n    SLCcallback_->expectOrphanedHalfOpenPushedTxn(callback);\n  }\n\n  void sendPushPromise(quic::StreamId streamId,\n                       hq::PushId pushId = kUnknownPushId,\n                       const std::string& url = \"/\",\n                       proxygen::HTTPHeaderSize* outHeaderSize = nullptr,\n                       bool eom = false) {\n    auto promise = getGetRequest(url);\n    promise.setURL(url);\n\n    return sendPushPromise(streamId, promise, pushId, outHeaderSize, eom);\n  }\n\n  void sendPushPromise(quic::StreamId streamId,\n                       const HTTPMessage& promiseHeadersBlock,\n                       hq::PushId pushId = kUnknownPushId,\n                       proxygen::HTTPHeaderSize* outHeaderSize = nullptr,\n                       bool eom = false) {\n\n    // In case the user is not interested in knowing the size\n    // of headers, but just in the fact that the headers were\n    // written, use a temporary size for checks\n    if (outHeaderSize == nullptr) {\n      outHeaderSize = &lastPushPromiseHeadersSize_;\n    }\n\n    if (pushId == kUnknownPushId) {\n      pushId = nextPushId();\n    }\n\n    auto c = makeCodec(streamId);\n    auto res =\n        streams_.emplace(std::piecewise_construct,\n                         std::forward_as_tuple(streamId),\n                         std::forward_as_tuple(c.first, std::move(c.second)));\n\n    auto& pushPromiseRequest = res.first->second;\n    pushPromiseRequest.id = streamId;\n\n    // Push promises should not have EOF set.\n    pushPromiseRequest.readEOF = eom;\n\n    // Write the push promise to the request buffer.\n    // The push promise includes the headers\n    pushPromiseRequest.codec->generatePushPromise(pushPromiseRequest.buf,\n                                                  streamId,\n                                                  promiseHeadersBlock,\n                                                  pushId,\n                                                  eom,\n                                                  outHeaderSize);\n  }\n\n  // Shared implementation for different push stream\n  // methods\n  ServerStream& createPushStreamImpl(quic::StreamId streamId,\n                                     folly::Optional<hq::PushId> pushId,\n                                     std::size_t len = kUnlimited,\n                                     bool eom = true) {\n\n    auto c = makeCodec(streamId);\n    // Setting a push id allows us to send push preface\n    auto res = streams_.emplace(\n        std::piecewise_construct,\n        std::forward_as_tuple(streamId),\n        std::forward_as_tuple(c.first, std::move(c.second), pushId));\n\n    auto& stream = res.first->second;\n    stream.id = stream.codec->createStream();\n    stream.readEOF = eom;\n\n    // Generate the push stream preface, and if there's enough headroom\n    // the unframed push id that follows it\n    auto prefaceRes = writePushStreamPreface(stream.id, len);\n    if (pushId.has_value()) {\n      if (prefaceRes) {\n        len -= *prefaceRes;\n        writeUnframedPushId(stream.id, len, *pushId);\n      }\n    }\n\n    return stream;\n  }\n\n  // Create a push stream with a header block and body\n  void createPushStream(quic::StreamId streamId,\n                        hq::PushId pushId,\n                        const HTTPMessage& resp,\n                        std::unique_ptr<folly::IOBuf> body = nullptr,\n                        bool eom = true) {\n\n    auto& stream = createPushStreamImpl(streamId, pushId, kUnlimited, eom);\n\n    // Write the response\n    stream.codec->generateHeader(\n        stream.buf, stream.codecId, resp, body == nullptr ? eom : false);\n    if (body) {\n      stream.codec->generateBody(\n          stream.buf, stream.codecId, std::move(body), folly::none, eom);\n    }\n  }\n\n  // Convenience method for creating a push stream without the\n  // need to allocate transport stream id\n  void createPushStream(hq::PushId pushId,\n                        const HTTPMessage& resp,\n                        std::unique_ptr<folly::IOBuf> body = nullptr,\n                        bool eom = true) {\n    return createPushStream(\n        nextUnidirectionalStreamId(), pushId, resp, std::move(body), eom);\n  }\n\n  // Create nascent stream (no body)\n  void createNascentPushStream(quic::StreamId streamId,\n                               folly::Optional<hq::PushId> pushId,\n                               std::size_t len = kUnlimited,\n                               bool eom = true) {\n    createPushStreamImpl(streamId, pushId, len, eom);\n  }\n\n  bool lastPushPromiseHeadersSizeValid() {\n    return ((lastPushPromiseHeadersSize_.uncompressed > 0) &&\n            (lastPushPromiseHeadersSize_.compressed > 0));\n  }\n\n  void createNascentPushStream(hq::PushId pushId,\n                               std::size_t prefaceBytes = kUnlimited,\n                               bool eom = true) {\n    return createNascentPushStream(\n        nextUnidirectionalStreamId(), pushId, prefaceBytes, eom);\n  }\n\n  std::unique_ptr<MockHTTPHandler> expectPushResponse() {\n    auto pushHandler = std::make_unique<MockHTTPHandler>();\n    pushHandler->expectTransaction();\n    assocHandler_->expectPushedTransaction(pushHandler.get());\n    // Promise/Response - with no lambda it lacks RetiresOnSaturation\n    pushHandler->expectHeaders([](std::shared_ptr<HTTPMessage>) {});\n    pushHandler->expectHeaders([](std::shared_ptr<HTTPMessage>) {});\n    pushHandler->expectBody();\n    pushHandler->expectEOM();\n    pushHandler->expectDetachTransaction();\n    return pushHandler;\n  }\n\n  proxygen::HTTPHeaderSize lastPushPromiseHeadersSize_;\n  hq::PushId nextPushId_;\n  std::unique_ptr<StrictMock<MockHTTPHandler>> assocHandler_;\n\n  std::unique_ptr<MockServerPushLifecycleCallback> SLCcallback_;\n};\n\nTEST_P(HQUpstreamSessionTestPush, DelayedQPACKPush) {\n  assocHandler_->txn_->sendAbort();\n  assocHandler_ = openTransaction();\n  assocHandler_->txn_->sendHeaders(getGetRequest());\n  assocHandler_->txn_->sendEOM();\n  assocHandler_->expectHeaders();\n  assocHandler_->expectBody();\n  auto pushHandler = expectPushResponse();\n  assocHandler_->expectEOM();\n  assocHandler_->expectDetachTransaction();\n\n  auto resp = makeResponse(200, 100);\n  sendResponse(assocHandler_->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               false);\n  flushAndLoopN(1);\n  auto pushPromiseRequest = getGetRequest();\n  pushPromiseRequest.getHeaders().set(\"Dynamic1\", \"a\");\n  hq::PushId pushId = nextPushId();\n  sendPushPromise(assocHandler_->txn_->getID(), pushPromiseRequest, pushId);\n  sendPartialBody(assocHandler_->txn_->getID(), nullptr, true);\n\n  auto control = encoderWriteBuf_.move();\n  flushAndLoopN(1);\n\n  encoderWriteBuf_.append(std::move(control));\n  flushAndLoopN(1);\n\n  HTTPMessage pushResp;\n  pushResp.setStatusCode(200);\n  pushResp.getHeaders().set(\"Dynamic2\", \"b\");\n  createPushStream(pushId, pushResp, makeBuf(100), true);\n\n  control = encoderWriteBuf_.move();\n  flushAndLoopN(1);\n\n  encoderWriteBuf_.append(std::move(control));\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestPushPromiseCallbacksInvoked) {\n  // the push promise is not followed by a push stream, and the eof is not\n  // set.\n  // The transaction is supposed to stay open and to time out eventually.\n  assocHandler_->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorTimeout);\n  });\n\n  hq::PushId pushId = nextPushId();\n\n  auto pushPromiseRequest = getGetRequest();\n\n  expectPushPromiseBegin(\n      [&](HTTPCodec::StreamID owningStreamId, hq::PushId promisedPushId) {\n        EXPECT_EQ(promisedPushId, pushId);\n        EXPECT_EQ(owningStreamId, assocHandler_->txn_->getID());\n      });\n\n  expectPushPromise([&](HTTPCodec::StreamID owningStreamId,\n                        hq::PushId promisedPushId,\n                        HTTPMessage* msg) {\n    EXPECT_EQ(promisedPushId, pushId);\n    EXPECT_EQ(owningStreamId, assocHandler_->txn_->getID());\n\n    EXPECT_THAT(msg, NotNull());\n\n    auto expectedHeaders = pushPromiseRequest.getHeaders();\n    auto actualHeaders = msg->getHeaders();\n\n    expectedHeaders.forEach(\n        [&](const std::string& header, const std::string& /* val */) {\n          EXPECT_TRUE(actualHeaders.exists(header));\n          EXPECT_EQ(expectedHeaders.getNumberOfValues(header),\n                    actualHeaders.getNumberOfValues(header));\n        });\n  });\n\n  HTTPCodec::StreamID nascentStreamId;\n\n  expectNascentPushStreamBegin([&](HTTPCodec::StreamID streamId, bool isEOF) {\n    nascentStreamId = streamId;\n    EXPECT_FALSE(isEOF);\n  });\n\n  expectNascentPushStream([&](HTTPCodec::StreamID pushStreamId,\n                              hq::PushId pushStreamPushId,\n                              bool /* isEOF */) {\n    EXPECT_EQ(pushStreamPushId, pushId);\n    EXPECT_EQ(pushStreamId, nascentStreamId);\n  });\n\n  sendPushPromise(assocHandler_->txn_->getID(), pushPromiseRequest, pushId);\n  EXPECT_TRUE(lastPushPromiseHeadersSizeValid());\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  assocHandler_->txn_->sendEOM();\n\n  auto pushHandler = expectPushResponse();\n\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestIngressPushStream) {\n\n  hq::PushId pushId = nextPushId();\n\n  auto pushPromiseRequest = getGetRequest();\n\n  HTTPCodec::StreamID nascentStreamId;\n\n  expectNascentPushStreamBegin([&](HTTPCodec::StreamID streamId, bool isEOF) {\n    nascentStreamId = streamId;\n    EXPECT_FALSE(isEOF);\n  });\n\n  expectNascentPushStream([&](HTTPCodec::StreamID streamId,\n                              hq::PushId pushStreamPushId,\n                              bool isEOF) {\n    EXPECT_EQ(streamId, nascentStreamId);\n    EXPECT_EQ(pushId, pushStreamPushId);\n    EXPECT_EQ(isEOF, false);\n  });\n\n  // Since push promise is not sent, full ingress push stream\n  // not going to be created\n  /*\n    expectOrphanedNascentStream([&](HTTPCodec::StreamID streamId,\n                                    folly::Optional<hq::PushId> maybePushId) {\n      ASSERT_EQ(streamId, nascentStreamId);\n      EXPECT_EQ(maybePushId.has_value(), true);\n      EXPECT_EQ(maybePushId.value(), pushId);\n    });\n  */\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  // Currently, the new transaction is not created corectly,\n  // and an error is expected. to be extended in the following\n  // diffs which add creation of pushed transaction\n  assocHandler_->expectError();\n\n  assocHandler_->txn_->sendEOM();\n  hqSession_->closeWhenIdle();\n  flushAndLoop(); // One read for the letter, one read for quic integer. Is\n                  // enough?\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestPushPromiseFollowedByPushStream) {\n  // the transaction is expected to timeout, since the PushPromise does not have\n  // EOF set, and it is not followed by a PushStream.\n  assocHandler_->expectError();\n\n  hq::PushId pushId = nextPushId();\n\n  auto pushPromiseRequest = getGetRequest();\n\n  expectPushPromiseBegin(\n      [&](HTTPCodec::StreamID owningStreamId, hq::PushId promisedPushId) {\n        EXPECT_EQ(promisedPushId, pushId);\n        EXPECT_EQ(owningStreamId, assocHandler_->txn_->getID());\n      });\n\n  expectPushPromise([&](HTTPCodec::StreamID owningStreamId,\n                        hq::PushId promisedPushId,\n                        HTTPMessage* msg) {\n    EXPECT_EQ(promisedPushId, pushId);\n    EXPECT_EQ(owningStreamId, assocHandler_->txn_->getID());\n\n    EXPECT_THAT(msg, NotNull());\n\n    auto expectedHeaders = pushPromiseRequest.getHeaders();\n    auto actualHeaders = msg->getHeaders();\n\n    expectedHeaders.forEach(\n        [&](const std::string& header, const std::string& /* val */) {\n          EXPECT_TRUE(actualHeaders.exists(header));\n          EXPECT_EQ(expectedHeaders.getNumberOfValues(header),\n                    actualHeaders.getNumberOfValues(header));\n        });\n  });\n\n  HTTPCodec::StreamID nascentStreamId;\n\n  expectNascentPushStreamBegin([&](HTTPCodec::StreamID streamId, bool isEOF) {\n    nascentStreamId = streamId;\n    folly::Optional<HTTPCodec::StreamID> expectedReadId(nascentStreamId);\n    EXPECT_CALL(infoCb_, onRead(testing::_, testing::_, expectedReadId))\n        .Times(testing::AtLeast(1));\n    EXPECT_FALSE(isEOF);\n  });\n\n  // since push stream arrives after the promise,\n  // full ingress push stream has to be created\n  expectNascentPushStream([&](HTTPCodec::StreamID pushStreamId,\n                              hq::PushId pushStreamPushId,\n                              bool /* isEOF */) {\n    EXPECT_EQ(pushStreamPushId, pushId);\n    EXPECT_EQ(pushStreamId, nascentStreamId);\n  });\n\n  proxygen::HTTPHeaderSize pushPromiseSize;\n\n  sendPushPromise(assocHandler_->txn_->getID(),\n                  pushPromiseRequest,\n                  pushId,\n                  &pushPromiseSize);\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  assocHandler_->txn_->sendEOM();\n\n  auto pushHandler = expectPushResponse();\n\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestAbortedPushedTransactionAfterPromise) {\n  assocHandler_->txn_->sendAbort();\n  assocHandler_ = openTransaction();\n  assocHandler_->txn_->sendHeaders(getGetRequest());\n  assocHandler_->txn_->sendEOM();\n  assocHandler_->expectHeaders();\n  assocHandler_->expectBody();\n  assocHandler_->expectEOM();\n  assocHandler_->expectDetachTransaction();\n\n  auto resp = makeResponse(200, 100);\n  sendResponse(assocHandler_->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               false);\n  flushAndLoopN(1);\n\n  auto pushHandler = std::make_unique<MockHTTPHandler>();\n  pushHandler->expectTransaction();\n  assocHandler_->expectPushedTransaction(pushHandler.get());\n  // Abort the pushed transaction upon reception of the push promise.\n  pushHandler->expectHeaders(\n      [&](std::shared_ptr<HTTPMessage>) { pushHandler->txn_->sendAbort(); });\n\n  auto pushPromiseRequest = getGetRequest();\n  hq::PushId pushId = nextPushId();\n  sendPushPromise(assocHandler_->txn_->getID(), pushPromiseRequest, pushId);\n  // Send body to close the main request stream\n  sendPartialBody(assocHandler_->txn_->getID(), nullptr, true);\n  pushHandler->expectDetachTransaction();\n\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestAbortedPushedTransactionAfterResponse) {\n  assocHandler_->txn_->sendAbort();\n  assocHandler_ = openTransaction();\n  assocHandler_->txn_->sendHeaders(getGetRequest());\n  assocHandler_->txn_->sendEOM();\n  assocHandler_->expectHeaders();\n  assocHandler_->expectBody();\n  assocHandler_->expectEOM();\n  assocHandler_->expectDetachTransaction();\n\n  auto resp = makeResponse(200, 100);\n  sendResponse(assocHandler_->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               false);\n  flushAndLoopN(1);\n\n  auto pushHandler = std::make_unique<MockHTTPHandler>();\n  pushHandler->expectTransaction();\n  assocHandler_->expectPushedTransaction(pushHandler.get());\n  // Expect normal promise.\n  pushHandler->expectHeaders([](std::shared_ptr<HTTPMessage>) {});\n\n  auto pushPromiseRequest = getGetRequest();\n  pushPromiseRequest.getHeaders().set(\"Dynamic1\", \"a\");\n  hq::PushId pushId = nextPushId();\n  sendPushPromise(assocHandler_->txn_->getID(), pushPromiseRequest, pushId);\n  sendPartialBody(assocHandler_->txn_->getID(), nullptr, true);\n  flushAndLoopN(1);\n\n  // Abort the pushed transaction on response.\n  pushHandler->expectHeaders(\n      [&](std::shared_ptr<HTTPMessage>) { pushHandler->txn_->sendAbort(); });\n  pushHandler->expectDetachTransaction();\n  HTTPMessage pushResp;\n  pushResp.setStatusCode(200);\n  pushResp.getHeaders().set(\"Dynamic2\", \"b\");\n  createPushStream(pushId, pushResp, makeBuf(100), true);\n\n  flushAndLoop();\n  hqSession_->closeWhenIdle();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestOnPushedTransaction) {\n  // the transaction is expected to timeout, since the PushPromise does not have\n  // EOF set, and it is not followed by a PushStream.\n  assocHandler_->expectError();\n  // assocHandler_->expectHeaders();\n\n  hq::PushId pushId = nextPushId();\n\n  auto pushPromiseRequest = getGetRequest();\n\n  proxygen::HTTPHeaderSize pushPromiseSize;\n\n  sendPushPromise(assocHandler_->txn_->getID(),\n                  pushPromiseRequest,\n                  pushId,\n                  &pushPromiseSize);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  // Once both push promise and push stream have been received, a push\n  // transaction should be created\n  assocHandler_->txn_->sendEOM();\n\n  auto pushHandler = expectPushResponse();\n\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestOnPushedTransactionOutOfOrder) {\n  // the transaction is expected to timeout, since the PushPromise does not have\n  // EOF set, and it is not followed by a PushStream.\n  assocHandler_->expectError();\n  // assocHandler_->expectHeaders();\n\n  hq::PushId pushId = nextPushId();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  auto pushPromiseRequest = getGetRequest();\n  proxygen::HTTPHeaderSize pushPromiseSize;\n  sendPushPromise(assocHandler_->txn_->getID(),\n                  pushPromiseRequest,\n                  pushId,\n                  &pushPromiseSize);\n\n  // Once both push promise and push stream have been received, a push\n  // transaction should be created\n  auto pushHandler = expectPushResponse();\n\n  assocHandler_->txn_->sendEOM();\n\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestCloseDroppedConnection) {\n  HQSession::DestructorGuard dg(hqSession_);\n  // Two \"onError\" calls are expected:\n  // the first when MockQuicSocketDriver closes the socket\n  // the second when the error is propagated to the stream\n  EXPECT_CALL(*assocHandler_, _onError(testing::_)).Times(2);\n\n  // Create a nascent push stream with a preface only\n  createNascentPushStream(1111 /* streamId */, folly::none /* pushId */);\n\n  // Run the event loop to let the dispatcher register the nascent stream\n  flushAndLoop();\n\n  // Drop the connection\n  hqSession_->dropConnection();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestPush, TestOrphanedPushStream) {\n  // the transaction is expected to timeout, since the PushPromise does not have\n  // EOF set, and it is not followed by a PushStream.\n  assocHandler_->expectError();\n\n  hq::PushId pushId = nextPushId();\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  createPushStream(pushId, resp, makeBuf(100), true);\n\n  assocHandler_->txn_->sendEOM();\n\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTest, TestNoDatagram) {\n  EXPECT_FALSE(httpCallbacks_.datagramEnabled);\n  auto handler = openTransaction();\n  EXPECT_EQ(handler->txn_->getDatagramSizeLimit(), 0);\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestDatagram, TestDatagramSettings) {\n  EXPECT_TRUE(httpCallbacks_.datagramEnabled);\n  auto handler = openTransaction();\n  EXPECT_GT(handler->txn_->getDatagramSizeLimit(), 0);\n  auto resp = makeResponse(200, 100);\n  sendResponse(handler->txn_->getID(),\n               *std::get<0>(resp),\n               std::move(std::get<1>(resp)),\n               true);\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestDatagram, TestReceiveDatagram) {\n  EXPECT_TRUE(httpCallbacks_.datagramEnabled);\n  auto handler = openTransaction();\n  auto id = handler->txn_->getID();\n  EXPECT_GT(handler->txn_->getDatagramSizeLimit(), 0);\n  MockHTTPTransactionTransportCallback transportCallback_;\n  handler->txn_->setTransportCallback(&transportCallback_);\n  EXPECT_CALL(transportCallback_, datagramBytesReceived(::testing::_)).Times(1);\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  auto resp = makeResponse(200, 0);\n  sendResponse(id, *std::get<0>(resp), std::move(std::get<1>(resp)), false);\n  handler->expectHeaders();\n  flushAndLoopN(1);\n  auto h3Datagram = getH3Datagram(id, folly::IOBuf::wrapBuffer(\"testtest\", 8));\n  socketDriver_->addDatagram(std::move(h3Datagram));\n  handler->expectDatagram();\n  flushAndLoopN(1);\n  auto it = streams_.find(id);\n  CHECK(it != streams_.end());\n  auto& stream = it->second;\n  stream.readEOF = true;\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestDatagram, TestReceiveEarlyDatagramsSingleStream) {\n  EXPECT_TRUE(httpCallbacks_.datagramEnabled);\n  auto handler = openTransaction();\n  auto id = handler->txn_->getID();\n  EXPECT_GT(handler->txn_->getDatagramSizeLimit(), 0);\n  handler->txn_->sendHeaders(getGetRequest());\n  handler->txn_->sendEOM();\n  for (auto i = 0; i < kDefaultMaxBufferedDatagrams * 2; ++i) {\n    auto h3Datagram =\n        getH3Datagram(id, folly::IOBuf::wrapBuffer(\"testtest\", 8));\n    socketDriver_->addDatagram(std::move(h3Datagram));\n  }\n  flushAndLoopN(1);\n  auto resp = makeResponse(200, 0);\n  sendResponse(id, *std::get<0>(resp), std::move(std::get<1>(resp)), false);\n  handler->expectHeaders();\n  EXPECT_CALL(*handler, _onDatagram(testing::_))\n      .Times(kDefaultMaxBufferedDatagrams);\n  flushAndLoopN(1);\n  auto it = streams_.find(id);\n  CHECK(it != streams_.end());\n  auto& stream = it->second;\n  stream.readEOF = true;\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nTEST_P(HQUpstreamSessionTestDatagram, TestReceiveEarlyDatagramsMultiStream) {\n  auto deliveredDatagrams = 0;\n  EXPECT_TRUE(httpCallbacks_.datagramEnabled);\n  std::vector<std::unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n\n  for (auto i = 0; i < kMaxStreamsWithBufferedDatagrams * 2; ++i) {\n    handlers.emplace_back(openTransaction());\n    auto handler = handlers.back().get();\n    auto id = handler->txn_->getID();\n    EXPECT_GT(handler->txn_->getDatagramSizeLimit(), 0);\n    handler->txn_->sendHeaders(getGetRequest());\n    handler->txn_->sendEOM();\n    auto h3Datagram =\n        getH3Datagram(id, folly::IOBuf::wrapBuffer(\"testtest\", 8));\n    socketDriver_->addDatagram(std::move(h3Datagram));\n    flushAndLoopN(1);\n  }\n\n  for (const auto& handler : handlers) {\n    auto id = handler->txn_->getID();\n    auto resp = makeResponse(200, 0);\n    sendResponse(id, *std::get<0>(resp), std::move(std::get<1>(resp)), false);\n    handler->expectHeaders();\n    EXPECT_CALL(*handler, _onDatagram(testing::_))\n        .WillRepeatedly(InvokeWithoutArgs([&]() { deliveredDatagrams++; }));\n    flushAndLoopN(1);\n    auto it = streams_.find(id);\n    CHECK(it != streams_.end());\n    auto& stream = it->second;\n    stream.readEOF = true;\n    handler->expectEOM();\n    handler->expectDetachTransaction();\n    flushAndLoopN(1);\n  }\n  EXPECT_EQ(deliveredDatagrams, kMaxStreamsWithBufferedDatagrams);\n  hqSession_->closeWhenIdle();\n  flushAndLoop();\n}\n\nclass HQUpstreamSessionTestWebTransport : public HQUpstreamSessionTest {\n public:\n  void SetUp() override {\n    HQUpstreamSessionTest::SetUp();\n    // Set up WT session\n    handler_ = openWTTransaction();\n    sessionId_ = handler_->txn_->getID();\n    handler_->txn_->sendHeaders(getWTConnectRequest());\n    handler_->expectHeaders();\n    sendResponse(sessionId_, getWTResponse(), nullptr, false);\n    flushAndLoopN(3);\n    wt_ = handler_->txn_->getWebTransport();\n    EXPECT_NE(wt_, nullptr);\n\n    // Set WebTransport stream limits to allow stream creation in tests\n    folly::IOBufQueue capsuleQueue;\n    WTMaxStreamsCapsule uniCapsule{.maximumStreams = 1};\n    writeWTMaxStreams(capsuleQueue, uniCapsule, false);\n    sendPartialBody(sessionId_, capsuleQueue.move(), false);\n    WTMaxStreamsCapsule bidiCapsule{.maximumStreams = 1};\n    writeWTMaxStreams(capsuleQueue, bidiCapsule, true);\n    sendPartialBody(sessionId_, capsuleQueue.move(), false);\n    flushAndLoopN(2);\n\n    // Connect the handler to the WebTransport filter so callbacks work\n    // The filter is installed when the 200 response is processed, so we can\n    // now set the handler on it\n    auto* wtFilter = hqSession_->getWebTransportFilter();\n    if (wtFilter) {\n      wtFilter->setHandler(dynamic_cast<WebTransportHandler*>(handler_.get()));\n    }\n  }\n\n  std::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>\n  openWTTransaction() {\n    auto handler =\n        std::make_unique<testing::StrictMock<proxygen::MockHTTPHandler>>();\n\n    // WebTransport CONNECT requests cause _setTransaction to be called twice:\n    // 1. When the original handler is set during normal transaction setup\n    // 2. When WebTransportFilter::install() replaces the handler after 200\n    // response\n    EXPECT_CALL(*handler, _setTransaction(testing::_))\n        .Times(2)\n        .WillOnce(testing::SaveArg<0>(&handler->txn_))\n        .WillOnce(testing::DoDefault());\n\n    HTTPTransaction* txn = hqSession_->newTransaction(handler.get());\n    EXPECT_EQ(txn, handler->txn_);\n    return handler;\n  }\n\n  void closeWTSession() {\n    // should this close INGRESS?\n    wt_->closeSession();\n    handler_->expectEOM();\n    handler_->expectDetachTransaction();\n    socketDriver_->addReadEOF(sessionId_, std::chrono::milliseconds(0));\n    hqSession_->closeWhenIdle();\n    flushAndLoop();\n  }\n\n  static HTTPMessage getWTConnectRequest() {\n    HTTPMessage req;\n    req.setHTTPVersion(1, 1);\n    req.setUpgradeProtocol(\"webtransport\");\n    req.setMethod(HTTPMethod::CONNECT);\n    req.setURL(\"/webtransport\");\n    req.getHeaders().set(HTTP_HEADER_HOST, \"www.facebook.com\");\n    return req;\n  }\n\n  static HTTPMessage getWTResponse() {\n    HTTPMessage resp;\n    resp.setHTTPVersion(1, 1);\n    resp.setStatusCode(200);\n    return resp;\n  }\n\n protected:\n  std::unique_ptr<StrictMock<MockHTTPHandler>> handler_;\n  uint64_t sessionId_;\n  WebTransport* wt_{nullptr};\n};\n\nclass MockDeliveryCallback : public WebTransport::ByteEventCallback {\n public:\n  MOCK_METHOD(void, onByteEvent, (quic::StreamId, uint64_t), (noexcept));\n\n  MOCK_METHOD(void,\n              onByteEventCanceled,\n              (quic::StreamId, uint64_t),\n              (noexcept));\n};\n\nTEST_P(HQUpstreamSessionTestWebTransport, FilterInstallation) {\n  auto* txnHandler = handler_->txn_->getHandler();\n  EXPECT_NE(txnHandler, nullptr);\n  auto* filter = dynamic_cast<WebTransportFilter*>(txnHandler);\n  EXPECT_NE(filter, nullptr);\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, BidirectionalStream) {\n  InSequence enforceOrder;\n\n  // Send WT_MAX_STREAMS_BIDI capsule to establish stream credit on the\n  // WebTransport level.\n  folly::IOBufQueue capsuleQueue;\n  WTMaxStreamsCapsule maxStreamsCapsule{.maximumStreams = 10};\n  auto writeResult = writeWTMaxStreams(capsuleQueue, maxStreamsCapsule, true);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n  sendPartialBody(sessionId_, std::move(capsuleData), false);\n  flushAndLoopN(2);\n\n  // Create a bidi WT stream\n  auto stream = wt_->createBidiStream().value();\n  auto id = stream.readHandle->getID();\n  // small write\n  auto mockCallback1 = std::make_unique<StrictMock<MockDeliveryCallback>>();\n  EXPECT_CALL(*mockCallback1, onByteEvent(id, 10)).Times(1);\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->onMaxData(1000);\n  stream.writeHandle->writeStreamData(makeBuf(10), false, mockCallback1.get());\n  eventBase_.loopOnce();\n\n  // shrink the fcw to force it to block\n  socketDriver_->setStreamFlowControlWindow(id, 100);\n  bool writeComplete = false;\n  auto mockCallback2 = std::make_unique<StrictMock<MockDeliveryCallback>>();\n  EXPECT_CALL(*mockCallback2, onByteEvent(id, 65536 + 10)).Times(1);\n  stream.writeHandle->writeStreamData(\n      makeBuf(65536), false, mockCallback2.get());\n\n  // capture the write handle and cancellation token to avoid UAF\n  auto writeHandle = stream.writeHandle;\n  auto cancelToken = writeHandle->getCancelToken();\n\n  stream.writeHandle->awaitWritable()\n      .value()\n      .via(&eventBase_)\n      .then([&, writeHandle, cancelToken](const auto&) {\n        VLOG(4) << \"big write complete\";\n        if (!cancelToken.isCancellationRequested()) {\n          // after it completes, write FIN\n          writeHandle->writeStreamData(nullptr, true, nullptr);\n#if 0\n        writeHandle\n            .value()\n            .via(&eventBase_)\n            .then([&](auto) {\n              VLOG(4) << \"fin write complete\";\n              writeComplete = true;\n            });\n        // ug, can't determine fin write complete;\n#endif\n          writeComplete = true;\n        } else {\n          VLOG(4) << \"Stream was cancelled, skipping FIN write\";\n          writeComplete = true;\n        }\n      });\n  eventBase_.loopOnce();\n  // grow the fcw which will complete the big write\n  socketDriver_->setStreamFlowControlWindow(id, 100000);\n  socketDriver_->setConnectionFlowControlWindow(100000);\n  wtImpl->onMaxData(100000);\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(writeComplete);\n\n  // Wait for a read\n  stream.readHandle->awaitNextRead(&eventBase_, [&](auto, auto, auto) {\n    VLOG(4) << \"read 1, adding 70k\";\n    // Now add a big buf, which will pause ingress\n    socketDriver_->addReadEvent(\n        id, makeBuf(70000), std::chrono::milliseconds(0));\n  });\n  // add a small read to trigger the above handler\n  socketDriver_->addReadEvent(id, makeBuf(10), std::chrono::milliseconds(0));\n  VLOG(4) << \"flushLoop 1\";\n  flushAndLoopN(4);\n  EXPECT_TRUE(socketDriver_->isStreamPaused(id));\n\n  // Read again\n  stream.readHandle->awaitNextRead(\n      &eventBase_, [&](auto, auto, auto streamData) {\n        VLOG(4) << \"read 2, adding EOF\";\n        EXPECT_EQ(streamData->data->computeChainDataLength(), 65535);\n        // Add EOF and wait for it\n        socketDriver_->addReadEOF(id, std::chrono::milliseconds(0));\n      });\n  VLOG(4) << \"flushLoop 2\";\n  flushAndLoopN(2);\n  stream.readHandle->awaitNextRead(\n      &eventBase_, [&](auto, auto, auto streamData) {\n        LOG(INFO) << \"read 3\";\n        EXPECT_EQ(streamData->data->computeChainDataLength(), 4465);\n        EXPECT_TRUE(streamData->fin);\n      });\n  VLOG(4) << \"flushLoop 3\";\n  flushAndLoopN(1);\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, RejectBidirectionalStream) {\n  WebTransport::BidiStreamHandle stream;\n  EXPECT_CALL(*handler_, onWebTransportBidiStream(_, _))\n      .WillOnce(SaveArg<1>(&stream));\n  folly::IOBufQueue buf(folly::IOBufQueue::cacheChainLength());\n  hq::writeWTStreamPreface(buf, hq::WebTransportStreamType::BIDI, sessionId_);\n  socketDriver_->addReadEvent(1, buf.move(), std::chrono::milliseconds(0));\n  eventBase_.loopOnce();\n\n  auto id = stream.writeHandle->getID();\n  EXPECT_EQ(id, 1);\n\n  // reset write handle\n  stream.writeHandle->resetStream(19);\n  EXPECT_EQ(\n      WebTransport::toApplicationErrorCode(*socketDriver_->streams_[id].error)\n          .value(),\n      19);\n\n  // stop sending read handle\n  stream.readHandle->stopSending(77);\n  EXPECT_EQ(\n      WebTransport::toApplicationErrorCode(*socketDriver_->streams_[id].error)\n          .value(),\n      77);\n\n  // add read error (peer reset)\n  socketDriver_->addReadError(id, 78, std::chrono::milliseconds(0));\n  eventBase_.loopOnce();\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, PairOfUnisReset) {\n  socketDriver_->setMaxUniStreams(10);\n\n  // Send WT_MAX_STREAMS_UNI capsule to establish stream credit on the\n  // WebTransport level.\n  folly::IOBufQueue capsuleQueue;\n  WTMaxStreamsCapsule maxStreamsCapsule{.maximumStreams = 10};\n  auto writeResult = writeWTMaxStreams(capsuleQueue, maxStreamsCapsule, false);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n  sendPartialBody(sessionId_, std::move(capsuleData), false);\n  flushAndLoopN(2);\n\n  auto writeHandle = wt_->createUniStream().value();\n  auto writeId = writeHandle->getID();\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(*handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n  folly::IOBufQueue buf(folly::IOBufQueue::cacheChainLength());\n  hq::writeWTStreamPreface(buf, hq::WebTransportStreamType::UNI, sessionId_);\n  socketDriver_->addReadEvent(15, buf.move(), std::chrono::milliseconds(0));\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n\n  auto readId = readHandle->getID();\n  EXPECT_EQ(readId, 15);\n\n  // Peer reset\n  folly::CancellationCallback writeCancel(\n      writeHandle->getCancelToken(), [&] { writeHandle->resetStream(77); });\n  socketDriver_->addStopSending(writeId, WebTransport::toHTTPErrorCode(19));\n  socketDriver_->addReadError(\n      readId, WebTransport::toHTTPErrorCode(77), std::chrono::milliseconds(0));\n  flushAndLoopN(2);\n  // cancel handler ran, reset stream with err=77\n  EXPECT_EQ(WebTransport::toApplicationErrorCode(\n                *socketDriver_->streams_[writeId].error)\n                .value(),\n            77);\n  // readHandle holds the reset error\n  readHandle->readStreamData()\n      .via(&eventBase_)\n      .thenValue([](auto) {})\n      .thenError(folly::tag_t<const WebTransport::Exception&>{},\n                 [](auto const& ex) { EXPECT_EQ(ex.error, 77); });\n\n  eventBase_.loopOnce();\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, Datagrams) {\n  InSequence enforceOrder;\n  EXPECT_TRUE(wt_->sendDatagram(makeBuf(10)));\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), 1);\n  EXPECT_EQ(socketDriver_->outDatagrams_[0].chainLength(), 11);\n  socketDriver_->addDatagram(getH3Datagram(0, makeBuf(10), folly::none));\n  socketDriver_->addDatagramsAvailableReadEvent();\n  EXPECT_CALL(*handler_, _onDatagram(testing::_)).WillOnce(Invoke([](auto dg) {\n    EXPECT_EQ(dg->computeChainDataLength(), 10);\n  }));\n  eventBase_.loopOnce();\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, CloseSessionCapsule) {\n  InSequence enforceOrder;\n\n  folly::IOBufQueue capsuleQueue;\n  CloseWebTransportSessionCapsule closeCapsule{.applicationErrorCode = 42,\n                                               .applicationErrorMessage =\n                                                   \"server initiated close\"};\n  auto writeResult = writeCloseWebTransportSession(capsuleQueue, closeCapsule);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n\n  EXPECT_CALL(*handler_,\n              onWebTransportSessionClose(folly::Optional<uint32_t>(42)))\n      .Times(1);\n  handler_->expectEOM();\n  handler_->expectDetachTransaction();\n\n  sendPartialBody(sessionId_, std::move(capsuleData), true);\n  flushAndLoop();\n\n  // Unlike HTTP/2 which has httpSession_->destroy(), HTTP/3 uses\n  // dropConnection() to immediately destroy the session and trigger\n  // detachSession() callback.\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, DrainSessionCapsule) {\n  InSequence enforceOrder;\n\n  folly::IOBufQueue capsuleQueue;\n  auto writeResult = writeDrainWebTransportSession(capsuleQueue);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n\n  EXPECT_CALL(*handler_, onSessionDrain()).Times(1);\n  handler_->expectEOM();\n  handler_->expectError();\n  handler_->expectDetachTransaction();\n\n  sendPartialBody(sessionId_, std::move(capsuleData), true);\n  flushAndLoop();\n\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, CloseSessionCapsuleWithStreams) {\n  InSequence enforceOrder;\n\n  // Send WT_MAX_STREAMS_BIDI capsule to establish stream credit on the\n  // WebTransport level.\n  folly::IOBufQueue capsuleQueue;\n  WTMaxStreamsCapsule maxStreamsCapsule{.maximumStreams = 10};\n  auto writeResult = writeWTMaxStreams(capsuleQueue, maxStreamsCapsule, true);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n  sendPartialBody(sessionId_, std::move(capsuleData), false);\n  flushAndLoopN(2);\n\n  // Send data using a bidi stream, then try to close the WebTransport session.\n  auto bidiStream = wt_->createBidiStream().value();\n  auto bidiStreamId = bidiStream.readHandle->getID();\n  VLOG(4) << \"Created bidi stream with ID: \" << bidiStreamId;\n\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->onMaxData(10000);\n  socketDriver_->setStreamFlowControlWindow(bidiStreamId, 10000);\n  socketDriver_->setConnectionFlowControlWindow(10000);\n\n  auto writeRes =\n      bidiStream.writeHandle->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(writeRes.hasValue());\n  eventBase_.loopOnce();\n  VLOG(4) << \"Successfully wrote 100 bytes to bidi stream\";\n\n  bool dataReceived = false;\n  bidiStream.readHandle->awaitNextRead(\n      &eventBase_, [&](auto, auto, auto streamData) {\n        VLOG(4) << \"Received \" << streamData->data->computeChainDataLength()\n                << \" bytes on bidi stream\";\n        EXPECT_EQ(streamData->data->computeChainDataLength(), 50);\n        dataReceived = true;\n      });\n\n  socketDriver_->addReadEvent(\n      bidiStreamId, makeBuf(50), std::chrono::milliseconds(0));\n\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(dataReceived);\n\n  // Send close capsule to terminate the WebTransport session and all streams\n  // associated with it.\n  folly::IOBufQueue closeCapsuleQueue;\n  CloseWebTransportSessionCapsule closeCapsule{.applicationErrorCode = 42,\n                                               .applicationErrorMessage =\n                                                   \"server initiated close\"};\n  auto closeWriteResult =\n      writeCloseWebTransportSession(closeCapsuleQueue, closeCapsule);\n  EXPECT_TRUE(closeWriteResult.has_value());\n\n  auto closeCapsuleData = closeCapsuleQueue.move();\n  EXPECT_GT(closeCapsuleData->computeChainDataLength(), 0);\n\n  EXPECT_CALL(*handler_,\n              onWebTransportSessionClose(folly::Optional<uint32_t>(42)))\n      .Times(1);\n  handler_->expectEOM();\n  handler_->expectDetachTransaction();\n\n  sendPartialBody(sessionId_, std::move(closeCapsuleData), true);\n  flushAndLoop();\n  EXPECT_TRUE(socketDriver_->streams_[bidiStreamId].error.has_value());\n\n  HQSession::DestructorGuard dg(hqSession_);\n  hqSession_->dropConnection();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, MaxStreamsBidiCapsule) {\n  InSequence enforceOrder;\n\n  // Testing stream limits with a WT_MAX_STREAMS_BIDI capsule.\n  // SetUp() set the initial limit to 1. Create one stream.\n  auto streamResult = wt_->createBidiStream();\n  EXPECT_TRUE(streamResult.hasValue());\n\n  // Send capsule to increase limit to 2\n  folly::IOBufQueue bidiCapsuleQueue;\n  WTMaxStreamsCapsule capsule{.maximumStreams = 2};\n  writeWTMaxStreams(bidiCapsuleQueue, capsule, true);\n  sendPartialBody(sessionId_, bidiCapsuleQueue.move(), false);\n  flushAndLoopN(2);\n\n  // Now we should be able to create another stream\n  auto bidiStreamResult = wt_->createBidiStream();\n  EXPECT_TRUE(bidiStreamResult.hasValue());\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, MaxStreamsUniCapsule) {\n  InSequence enforceOrder;\n\n  // Testing stream limits with a WT_MAX_STREAMS_UNI capsule.\n\n  // 3 control streams (control, QPACK encoder, QPACK decoder) are already\n  // created in SetUp(). To allow 2 additional WT uni streams, we need to set\n  // the limit to 5 total streams.\n  socketDriver_->setMaxUniStreams(5);\n\n  auto streamResult = wt_->createUniStream();\n  EXPECT_TRUE(streamResult.hasValue());\n\n  // Now send a capsule to increase the limit to 2\n  folly::IOBufQueue uniCapsuleQueue;\n  WTMaxStreamsCapsule capsule{.maximumStreams = 2};\n  writeWTMaxStreams(uniCapsuleQueue, capsule, false);\n  sendPartialBody(sessionId_, uniCapsuleQueue.move(), false);\n  flushAndLoopN(2);\n\n  // Verify we can still create streams with the new higher limit\n  auto uniStreamResult = wt_->createUniStream();\n  EXPECT_TRUE(uniStreamResult.hasValue());\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, SessionFlowControl) {\n  InSequence enforceOrder;\n\n  // Send WT_MAX_STREAMS_BIDI capsule to establish stream credit on the\n  // WebTransport level.\n  folly::IOBufQueue bidiCapsuleQueue;\n  WTMaxStreamsCapsule bidiCapsule{.maximumStreams = 10};\n  auto bidiWriteResult = writeWTMaxStreams(bidiCapsuleQueue, bidiCapsule, true);\n  EXPECT_TRUE(bidiWriteResult.has_value());\n  auto bidiCapsuleData = bidiCapsuleQueue.move();\n  EXPECT_GT(bidiCapsuleData->computeChainDataLength(), 0);\n  sendPartialBody(sessionId_, std::move(bidiCapsuleData), false);\n  flushAndLoopN(2);\n\n  // Send WT_MAX_DATA capsule with increased window\n  folly::IOBufQueue maxDataCapsuleQueue;\n  WTMaxDataCapsule maxDataCapsule{.maximumData = 100000};\n  auto maxDataWriteResult = writeWTMaxData(maxDataCapsuleQueue, maxDataCapsule);\n  EXPECT_TRUE(maxDataWriteResult.has_value());\n  auto maxDataCapsuleData = maxDataCapsuleQueue.move();\n  EXPECT_GT(maxDataCapsuleData->computeChainDataLength(), 0);\n\n  sendPartialBody(sessionId_, std::move(maxDataCapsuleData), false);\n  flushAndLoopN(2);\n\n  // Create a bidi stream and verify write succeeds with updated window\n  auto stream = wt_->createBidiStream().value();\n  auto id = stream.readHandle->getID();\n\n  // Set up stream flow control windows\n  socketDriver_->setStreamFlowControlWindow(id, 10000);\n  socketDriver_->setConnectionFlowControlWindow(10000);\n\n  // Send data that fits within the window\n  auto writeRes =\n      stream.writeHandle->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(writeRes.hasValue());\n  EXPECT_EQ(writeRes.value(), WebTransport::FCState::UNBLOCKED);\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, SessionFlowControlExceedLimit) {\n  InSequence enforceOrder;\n\n  // Send WT_MAX_STREAMS_BIDI capsule to establish stream credit on the\n  // WebTransport level.\n  folly::IOBufQueue bidiCapsuleQueue;\n  WTMaxStreamsCapsule bidiCapsule{.maximumStreams = 10};\n  auto bidiWriteResult = writeWTMaxStreams(bidiCapsuleQueue, bidiCapsule, true);\n  EXPECT_TRUE(bidiWriteResult.has_value());\n  auto bidiCapsuleData = bidiCapsuleQueue.move();\n  EXPECT_GT(bidiCapsuleData->computeChainDataLength(), 0);\n  sendPartialBody(sessionId_, std::move(bidiCapsuleData), false);\n  flushAndLoopN(2);\n\n  // Create a bidi stream\n  auto stream = wt_->createBidiStream().value();\n  auto id = stream.readHandle->getID();\n\n  // Set up stream flow control windows (larger than WT session window)\n  socketDriver_->setStreamFlowControlWindow(id, 100000);\n  socketDriver_->setConnectionFlowControlWindow(100000);\n\n  // The initial WT session window is 65536 (from WT_INITIAL_MAX_DATA setting).\n  // Send data that consumes most of the window (65500 bytes)\n  auto writeRes1 =\n      stream.writeHandle->writeStreamData(makeBuf(65500), false, nullptr);\n  EXPECT_TRUE(writeRes1.hasValue());\n  EXPECT_EQ(writeRes1.value(), WebTransport::FCState::UNBLOCKED);\n\n  // Try to send more data that would exceed flow control (65500 + 100 > 65536)\n  auto writeRes2 =\n      stream.writeHandle->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(writeRes2.hasValue());\n  EXPECT_EQ(writeRes2.value(), WebTransport::FCState::BLOCKED);\n\n  // Send WT_MAX_DATA capsule to increase the window to 100000\n  folly::IOBufQueue maxDataCapsuleQueue;\n  WTMaxDataCapsule maxDataCapsule{.maximumData = 100000};\n  auto maxDataWriteResult = writeWTMaxData(maxDataCapsuleQueue, maxDataCapsule);\n  EXPECT_TRUE(maxDataWriteResult.has_value());\n  auto maxDataCapsuleData = maxDataCapsuleQueue.move();\n  EXPECT_GT(maxDataCapsuleData->computeChainDataLength(), 0);\n\n  sendPartialBody(sessionId_, std::move(maxDataCapsuleData), false);\n  flushAndLoopN(2);\n\n  // Now the previously blocked write and an additional write should succeed\n  auto writeRes3 =\n      stream.writeHandle->writeStreamData(makeBuf(200), false, nullptr);\n  EXPECT_TRUE(writeRes3.hasValue());\n  EXPECT_EQ(writeRes3.value(), WebTransport::FCState::UNBLOCKED);\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, ReceiveWTStreamsBlockedCapsule) {\n  InSequence enforceOrder;\n\n  auto* wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set maxStreamID=2 and targetConcurrentStreams=8 for bidi streams\n  // This means shouldGrantStreamCredit should return true\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/2,\n      /*targetConcurrentStreams=*/8);\n\n  // Verify we should grant credit before receiving WT_STREAMS_BLOCKED\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(true));\n\n  // Create and send a WT_STREAMS_BLOCKED_BIDI capsule from server to client\n  folly::IOBufQueue capsuleQueue;\n  WTStreamsBlockedCapsule capsule{.maximumStreams = 2};\n  auto writeResult = writeWTStreamsBlocked(capsuleQueue, capsule, true);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n\n  sendPartialBody(sessionId_, std::move(capsuleData), false);\n\n  // Process the capsule - this should trigger onStreamsBlocked\n  // which should grant credit and send a WT_MAX_STREAMS capsule back\n  // Expected new maxStreamID: 2 + (8 / 2) = 6\n  flushAndLoopN(2);\n\n  // Verify that a WT_MAX_STREAMS capsule was sent in the response\n  // by checking that the write buffer contains data\n  EXPECT_GT(socketDriver_->streams_[sessionId_].writeBuf.chainLength(), 0);\n\n  // After granting credit, shouldGrantStreamCredit should return false\n  // because maxStreamID is now 6: 6 - 0 = 6, and 8 / 2 = 4, so 6 >= 4\n  EXPECT_FALSE(wtImpl->shouldGrantStreamCredit(true));\n\n  closeWTSession();\n}\n\nTEST_P(HQUpstreamSessionTestWebTransport, ReceiveWTDataBlockedCapsule) {\n  InSequence enforceOrder;\n\n  auto* wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set flow control limits where recv window is set to initial value\n  // bytesRead_ is 0, recvFlowController maxOffset is kDefaultWTReceiveWindow\n  // shouldGrantFlowControl will return true since bufferedBytes = 0\n  wtImpl->setFlowControlLimits(\n      /*sendWindow=*/std::numeric_limits<size_t>::max(),\n      /*recvWindow=*/kDefaultWTReceiveWindow);\n\n  // Verify we should grant flow control before receiving WT_DATA_BLOCKED\n  EXPECT_TRUE(wtImpl->shouldGrantFlowControl());\n\n  uint64_t currentMaxData = kDefaultWTReceiveWindow;\n  // Create and send a WT_DATA_BLOCKED capsule from server to client\n  folly::IOBufQueue capsuleQueue;\n  WTDataBlockedCapsule capsule{.maximumData = currentMaxData};\n  auto writeResult = writeWTDataBlocked(capsuleQueue, capsule);\n  EXPECT_TRUE(writeResult.has_value());\n  auto capsuleData = capsuleQueue.move();\n  EXPECT_GT(capsuleData->computeChainDataLength(), 0);\n\n  sendPartialBody(sessionId_, std::move(capsuleData), false);\n\n  // Process the capsule. This should trigger onDataBlocked\n  // which should grant credit and send a WT_MAX_DATA capsule back\n  flushAndLoopN(2);\n\n  // Verify that a WT_MAX_DATA capsule was sent in the response\n  // by checking that the write buffer contains data\n  EXPECT_GT(socketDriver_->streams_[sessionId_].writeBuf.chainLength(), 0);\n\n  // After granting credit, shouldGrantFlowControl should still return true\n  // because we haven't consumed any data yet (bytesRead_ is still 0)\n  EXPECT_TRUE(wtImpl->shouldGrantFlowControl());\n\n  closeWTSession();\n}\n\n/**\n * Instantiate the Parametrized test cases\n */\n\n// Make sure all the tests keep working with all the supported protocol versions\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTest,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\n// Instantiate tests for H3 Push functionality (requires HQ)\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTestPush,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.unidirectionalStreamsCredit = 4;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\n// Instantiate tests with QPACK on/off\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTestQPACK,\n                         Values(\n                             [] {\n                               TestParams tp;\n                               tp.alpn_ = \"h3\";\n                               return tp;\n                             }(),\n                             [] {\n                               TestParams tp;\n                               tp.alpn_ = \"h3\";\n                               tp.createQPACKStreams_ = false;\n                               return tp;\n                             }()),\n                         paramsToTestName);\n\n// Instantiate h3 datagram tests\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTestDatagram,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.datagrams_ = true;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n\n// Instantiate h3 webtransport tests\nINSTANTIATE_TEST_SUITE_P(HQUpstreamSessionTest,\n                         HQUpstreamSessionTestWebTransport,\n                         Values([] {\n                           TestParams tp;\n                           tp.alpn_ = \"h3\";\n                           tp.webTransport_ = true;\n                           return tp;\n                         }()),\n                         paramsToTestName);\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HQUpstreamSessionTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/futures/Future.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GTest.h>\n#include <limits>\n#include <proxygen/lib/http/HTTPHeaderSize.h>\n#include <proxygen/lib/http/codec/HQControlCodec.h>\n#include <proxygen/lib/http/codec/HQStreamCodec.h>\n#include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/test/HQSessionMocks.h>\n#include <proxygen/lib/http/session/test/HQSessionTestCommon.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\nclass HQUpstreamSessionTest : public HQSessionTest {\n public:\n  HQUpstreamSessionTest(\n      folly::Optional<TestParams> overrideParams = folly::none)\n      : HQSessionTest(proxygen::TransportDirection::UPSTREAM, overrideParams) {\n  }\n\n  void SetUp() override;\n  void TearDown() override;\n\n protected:\n  std::pair<proxygen::HTTPCodec::StreamID, std::unique_ptr<proxygen::HTTPCodec>>\n  makeCodec(proxygen::HTTPCodec::StreamID id);\n\n  void sendResponse(quic::StreamId id,\n                    const proxygen::HTTPMessage& resp,\n                    std::unique_ptr<folly::IOBuf> body = nullptr,\n                    bool eom = true);\n\n  void sendPartialBody(quic::StreamId id,\n                       std::unique_ptr<folly::IOBuf> body,\n                       bool eom = true);\n\n  quic::StreamId nextUnidirectionalStreamId();\n\n  void sendGoaway(\n      quic::StreamId lastStreamId,\n      std::chrono::milliseconds delay = std::chrono::milliseconds(0));\n\n  template <class HandlerType>\n  std::unique_ptr<testing::StrictMock<HandlerType>> openTransactionBase(\n      bool expectStartPaused = false);\n\n  std::unique_ptr<testing::StrictMock<proxygen::MockHTTPHandler>>\n  openTransaction();\n\n  void flushAndLoop(\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  void flushAndLoopN(\n      uint64_t n,\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  bool flush(\n      bool eof = false,\n      std::chrono::milliseconds eofDelay = std::chrono::milliseconds(0),\n      std::chrono::milliseconds initialDelay = std::chrono::milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>());\n\n  testing::StrictMock<proxygen::MockController>& getMockController();\n\n  std::unique_ptr<proxygen::MockSessionObserver> addMockSessionObserver(\n      proxygen::MockSessionObserver::EventSet eventSet);\n\n  // Representation of stream data\n  // If create with a push id, can be used\n  // as a push stream (requires writing the stream preface\n  // followed by unframed push id)\n  struct ServerStream {\n    ServerStream(proxygen::HTTPCodec::StreamID cId,\n                 std::unique_ptr<proxygen::HTTPCodec> c,\n                 folly::Optional<proxygen::hq::PushId> pId = folly::none)\n        : codecId(cId), codec(std::move(c)), pushId(pId) {\n    }\n\n    // Transport stream id\n    proxygen::HTTPCodec::StreamID id;\n\n    folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n    bool readEOF{false};\n    proxygen::HTTPCodec::StreamID codecId;\n\n    std::unique_ptr<proxygen::HTTPCodec> codec;\n\n    folly::Optional<proxygen::hq::PushId> pushId;\n  };\n\n  proxygen::MockConnectCallback connectCb_;\n  std::unordered_map<quic::StreamId, ServerStream> streams_;\n  folly::IOBufQueue encoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::IOBufQueue decoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};\n};\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTP2PriorityQueueBench.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Benchmark.h>\n#include <proxygen/lib/http/session/HTTP2PriorityQueue.h>\n\nusing namespace proxygen;\n\nnamespace {\nstatic char* fakeTxn = (char*)0xface0000;\n\nconst proxygen::HTTPCodec::StreamID kRootNodeId =\n    std::numeric_limits<uint64_t>::max();\n\nproxygen::HTTPTransaction* makeFakeTxn(proxygen::HTTPCodec::StreamID id) {\n  return (proxygen::HTTPTransaction*)(fakeTxn + id);\n}\n} // namespace\n\nBENCHMARK(Encode, iters) {\n  HTTP2PriorityQueue q_(WheelTimerInstance(), kRootNodeId);\n  uint64_t depth = 0;\n  for (size_t i = 0; i < iters; ++i) {\n    HTTPCodec::StreamID id = i * 2 + 1;\n    q_.addTransaction(\n        id, http2::DefaultPriority, makeFakeTxn(id), false, &depth);\n  }\n}\n\nint main(int argc, char** argv) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTP2PriorityQueueTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <array>\n#include <list>\n#include <map>\n#include <thread>\n\n#include <folly/Random.h>\n#include <folly/io/async/test/MockTimeoutManager.h>\n#include <folly/io/async/test/UndelayedDestruction.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/HTTP2PriorityQueue.h>\n\nusing namespace std::placeholders;\nusing namespace testing;\nusing folly::HHWheelTimer;\nusing folly::Random;\nusing folly::test::MockTimeoutManager;\n\nnamespace {\nstatic char* fakeTxn = (char*)0xface0000;\n\nconst proxygen::HTTPCodec::StreamID kRootNodeId =\n    std::numeric_limits<uint64_t>::max();\n\nproxygen::HTTPTransaction* makeFakeTxn(proxygen::HTTPCodec::StreamID id) {\n  return (proxygen::HTTPTransaction*)(fakeTxn + id);\n}\n\nproxygen::HTTPCodec::StreamID getTxnID(proxygen::HTTPTransaction* txn) {\n  return (proxygen::HTTPCodec::StreamID)((char*)txn - fakeTxn);\n}\n\n// folly::Random::rand32 is broken because it takes RNG by value\nuint32_t rand32(uint32_t max, folly::Random::DefaultGenerator& rng) {\n  if (max == 0) {\n    return 0;\n  }\n\n  return std::uniform_int_distribution<uint32_t>(0, max - 1)(rng);\n}\n\n} // namespace\n\nnamespace proxygen {\n\nusing IDList = std::list<std::pair<HTTPCodec::StreamID, uint8_t>>;\n\nclass QueueTest : public testing::Test {\n public:\n  explicit QueueTest(HHWheelTimer* timer = nullptr)\n      : q_(WheelTimerInstance(timer), kRootNodeId) {\n  }\n\n protected:\n  void addTransaction(HTTPCodec::StreamID id,\n                      http2::PriorityUpdate pri,\n                      bool pnode = false,\n                      uint64_t* depth = nullptr) {\n    HTTP2PriorityQueue::Handle h = q_.addTransaction(\n        id, pri, pnode ? nullptr : makeFakeTxn(id), false, depth);\n    // Blow away the old handle.  Hopefully the caller knows what they are doing\n    handles_.erase(id);\n    handles_.insert(std::make_pair(id, h));\n    if (!pnode) {\n      signalEgress(id, 1);\n    }\n  }\n\n  void removeTransaction(HTTPCodec::StreamID id) {\n    q_.removeTransaction(handles_[id]);\n  }\n\n  void updatePriority(HTTPCodec::StreamID id,\n                      http2::PriorityUpdate pri,\n                      uint64_t* depth = nullptr) {\n    handles_[id] = q_.updatePriority(handles_[id], pri, depth);\n  }\n\n  void signalEgress(HTTPCodec::StreamID id, bool mark) {\n    if (mark) {\n      q_.signalPendingEgress(handles_[id]);\n    } else {\n      q_.clearPendingEgress(handles_[id]);\n    }\n  }\n\n  void buildSimpleTree() {\n    addTransaction(\n        0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n    addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 3});\n    addTransaction(5, {.streamDependency = 0, .exclusive = false, .weight = 3});\n    addTransaction(7, {.streamDependency = 0, .exclusive = false, .weight = 7});\n    addTransaction(9, {.streamDependency = 5, .exclusive = false, .weight = 7});\n  }\n\n  bool visitNode(HTTP2PriorityQueue&,\n                 HTTPCodec::StreamID id,\n                 HTTPTransaction*,\n                 double r) {\n    nodes_.emplace_back(id, r * 100);\n    return false;\n  }\n\n  void dump() {\n    nodes_.clear();\n    q_.iterate(\n        std::bind(&QueueTest::visitNode, this, std::ref(q_), _1, _2, _3),\n        [] { return false; },\n        true);\n  }\n\n  void dumpBFS(const std::function<bool()>& stopFn) {\n    nodes_.clear();\n    q_.iterateBFS(\n        std::bind(&QueueTest::visitNode, this, _1, _2, _3, _4), stopFn, true);\n  }\n\n  void nextEgress() {\n    HTTP2PriorityQueue::NextEgressResult nextEgressResults;\n    q_.nextEgress(nextEgressResults);\n    nodes_.clear();\n    for (auto p : nextEgressResults) {\n      nodes_.emplace_back(getTxnID(p.first), p.second * 100);\n    }\n  }\n\n  HTTP2PriorityQueue q_;\n  std::map<HTTPCodec::StreamID, HTTP2PriorityQueue::Handle> handles_;\n  IDList nodes_;\n};\n\nTEST_F(QueueTest, Basic) {\n  buildSimpleTree();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {5, 25}, {9, 100}, {7, 50}}));\n\n  // Add another node, make sure we get the correct depth.\n  uint64_t depth;\n  addTransaction(11,\n                 {.streamDependency = 7, .exclusive = false, .weight = 15},\n                 false,\n                 &depth);\n  EXPECT_EQ(depth, 3);\n}\n\nTEST_F(QueueTest, RemoveLeaf) {\n  buildSimpleTree();\n\n  removeTransaction(3);\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {5, 33}, {9, 100}, {7, 66}}));\n}\n\nTEST_F(QueueTest, RemoveParent) {\n  buildSimpleTree();\n\n  removeTransaction(5);\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {7, 50}, {9, 25}}));\n}\n\nTEST_F(QueueTest, RemoveParentWeights) {\n  // weight_ / totalChildWeight_ < 1\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 0});\n  addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 255});\n  addTransaction(5, {.streamDependency = 0, .exclusive = false, .weight = 255});\n\n  removeTransaction(0);\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));\n}\n\nTEST_F(QueueTest, NodeDepth) {\n  uint64_t depth{33}; // initialize to some wrong value\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15},\n      false,\n      &depth);\n  EXPECT_EQ(depth, 1);\n\n  addTransaction(3,\n                 {.streamDependency = 0, .exclusive = false, .weight = 3},\n                 false,\n                 &depth);\n  EXPECT_EQ(depth, 2);\n\n  addTransaction(5,\n                 {.streamDependency = 3, .exclusive = true, .weight = 7},\n                 false,\n                 &depth);\n  EXPECT_EQ(depth, 3);\n\n  addTransaction(9,\n                 {.streamDependency = 0, .exclusive = false, .weight = 3},\n                 true,\n                 &depth);\n  EXPECT_EQ(depth, 2);\n  EXPECT_EQ(q_.numPendingEgress(), 3);\n  EXPECT_EQ(q_.numVirtualNodes(), 1);\n\n  depth = 55; // some unlikely depth\n  addTransaction(9,\n                 {.streamDependency = 0, .exclusive = false, .weight = 31},\n                 false,\n                 &depth);\n  EXPECT_EQ(depth, 2);\n  EXPECT_EQ(q_.numPendingEgress(), 4);\n  EXPECT_EQ(q_.numVirtualNodes(), 0);\n\n  addTransaction(11,\n                 {.streamDependency = 0, .exclusive = true, .weight = 7},\n                 false,\n                 &depth);\n  EXPECT_EQ(depth, 2);\n  EXPECT_EQ(q_.numPendingEgress(), 5);\n  EXPECT_EQ(q_.numVirtualNodes(), 0);\n\n  addTransaction(\n      13,\n      {.streamDependency = kRootNodeId, .exclusive = true, .weight = 23},\n      true,\n      &depth);\n  EXPECT_EQ(depth, 1);\n  EXPECT_EQ(q_.numPendingEgress(), 5);\n  EXPECT_EQ(q_.numVirtualNodes(), 1);\n\n  depth = 77; // some unlikely depth\n  addTransaction(\n      13,\n      {.streamDependency = kRootNodeId, .exclusive = true, .weight = 33},\n      false,\n      &depth);\n  EXPECT_EQ(depth, 1);\n  EXPECT_EQ(q_.numPendingEgress(), 6);\n  EXPECT_EQ(q_.numVirtualNodes(), 0);\n}\n\nTEST_F(QueueTest, UpdateWeight) {\n  buildSimpleTree();\n\n  uint64_t depth = 0;\n  updatePriority(\n      5, {.streamDependency = 0, .exclusive = false, .weight = 7}, &depth);\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 20}, {5, 40}, {9, 100}, {7, 40}}));\n  EXPECT_EQ(depth, 2);\n}\n\n// Previously the code would allow duplicate entries in the priority tree under\n// certain circumstances.\nTEST_F(QueueTest, DuplicateID) {\n  q_.addOrUpdatePriorityNode(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = true, .weight = 15});\n  q_.addOrUpdatePriorityNode(\n      3, {.streamDependency = 0, .exclusive = false, .weight = 15});\n  addTransaction(5, {.streamDependency = 3, .exclusive = false, .weight = 15});\n  addTransaction(3, {.streamDependency = 5, .exclusive = false, .weight = 15});\n  removeTransaction(5);\n  auto stopFn = [] { return false; };\n\n  dumpBFS(stopFn);\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}}));\n}\n\nTEST_F(QueueTest, UpdateWeightNotEnqueued) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 7});\n  addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 7});\n\n  signalEgress(0, false);\n  signalEgress(3, false);\n  uint64_t depth = 0;\n  updatePriority(\n      0, {.streamDependency = 3, .exclusive = false, .weight = 7}, &depth);\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{3, 100}, {0, 100}}));\n  EXPECT_EQ(depth, 2);\n}\n\nTEST_F(QueueTest, UpdateWeightExcl) {\n  buildSimpleTree();\n\n  updatePriority(5, {.streamDependency = 0, .exclusive = true, .weight = 7});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {5, 100}, {9, 40}, {3, 20}, {7, 40}}));\n  signalEgress(0, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{5, 100}}));\n}\n\nTEST_F(QueueTest, UpdateWeightExclDequeued) {\n  buildSimpleTree();\n\n  signalEgress(5, false);\n  updatePriority(5, {.streamDependency = 0, .exclusive = true, .weight = 7});\n  signalEgress(0, false);\n  nextEgress();\n\n  EXPECT_EQ(nodes_, IDList({{9, 40}, {7, 40}, {3, 20}}));\n}\n\nTEST_F(QueueTest, UpdateWeightUnknownParent) {\n  buildSimpleTree();\n\n  uint64_t depth = 0;\n  updatePriority(\n      5, {.streamDependency = 97, .exclusive = false, .weight = 15}, &depth);\n  dump();\n\n  EXPECT_EQ(nodes_,\n            IDList({{0, 50}, {3, 33}, {7, 66}, {97, 50}, {5, 100}, {9, 100}}));\n  EXPECT_EQ(depth, 2);\n\n  depth = 0;\n  updatePriority(\n      9, {.streamDependency = 99, .exclusive = false, .weight = 15}, &depth);\n  dump();\n\n  EXPECT_EQ(\n      nodes_,\n      IDList(\n          {{0, 33}, {3, 33}, {7, 66}, {97, 33}, {5, 100}, {99, 33}, {9, 100}}));\n  EXPECT_EQ(depth, 2);\n}\n\nTEST_F(QueueTest, UpdateParentSibling) {\n  buildSimpleTree();\n\n  updatePriority(5, {.streamDependency = 3, .exclusive = false, .weight = 3});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 33}, {5, 100}, {9, 100}, {7, 66}}));\n  signalEgress(0, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 66}, {3, 33}}));\n\n  // Clear 5's egress (so it is only in the tree because 9 has egress) and move\n  // it back.  Hit's a slightly different code path in reparent\n  signalEgress(5, false);\n  updatePriority(5, {.streamDependency = 0, .exclusive = false, .weight = 3});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {7, 50}, {5, 25}, {9, 100}}));\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));\n}\n\nTEST_F(QueueTest, UpdateParentSiblingExcl) {\n  buildSimpleTree();\n\n  updatePriority(7, {.streamDependency = 5, .exclusive = true, .weight = 3});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 50}, {5, 50}, {7, 100}, {9, 100}}));\n  signalEgress(0, false);\n  signalEgress(3, false);\n  signalEgress(5, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 100}}));\n}\n\nTEST_F(QueueTest, UpdateParentAncestor) {\n  buildSimpleTree();\n\n  updatePriority(\n      9, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 25}, {5, 25}, {7, 50}, {9, 50}}));\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{0, 50}, {9, 50}}));\n}\n\nTEST_F(QueueTest, UpdateParentAncestorExcl) {\n  buildSimpleTree();\n\n  updatePriority(\n      9, {.streamDependency = kRootNodeId, .exclusive = true, .weight = 15});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{9, 100}, {0, 100}, {3, 25}, {5, 25}, {7, 50}}));\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{9, 100}}));\n}\n\nTEST_F(QueueTest, UpdateParentDescendant) {\n  buildSimpleTree();\n\n  updatePriority(0, {.streamDependency = 5, .exclusive = false, .weight = 7});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{5, 100}, {9, 50}, {0, 50}, {3, 33}, {7, 66}}));\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{5, 100}}));\n  signalEgress(5, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{9, 50}, {0, 50}}));\n}\n\nTEST_F(QueueTest, UpdateParentDescendantExcl) {\n  buildSimpleTree();\n\n  updatePriority(0, {.streamDependency = 5, .exclusive = true, .weight = 7});\n  dump();\n\n  EXPECT_EQ(nodes_, IDList({{5, 100}, {0, 100}, {3, 20}, {7, 40}, {9, 40}}));\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{5, 100}}));\n  signalEgress(5, false);\n  signalEgress(0, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 40}, {9, 40}, {3, 20}}));\n}\n\nTEST_F(QueueTest, ExclusiveAdd) {\n  buildSimpleTree();\n\n  addTransaction(11, {.streamDependency = 0, .exclusive = true, .weight = 100});\n\n  dump();\n  EXPECT_EQ(nodes_,\n            IDList({{0, 100}, {11, 100}, {3, 25}, {5, 25}, {9, 100}, {7, 50}}));\n}\n\nTEST_F(QueueTest, AddUnknown) {\n  buildSimpleTree();\n\n  addTransaction(11,\n                 {.streamDependency = 75, .exclusive = false, .weight = 15});\n\n  dump();\n  EXPECT_EQ(\n      nodes_,\n      IDList(\n          {{0, 50}, {3, 25}, {5, 25}, {9, 100}, {7, 50}, {75, 50}, {11, 100}}));\n\n  // Now let's add the missing parent node and check if it was\n  // relocated properly\n  addTransaction(75, {.streamDependency = 0, .exclusive = false, .weight = 7});\n\n  dump();\n  EXPECT_EQ(nodes_,\n            IDList({{0, 100},\n                    {3, 16},\n                    {5, 16},\n                    {9, 100},\n                    {7, 33},\n                    {75, 33},\n                    {11, 100}}));\n}\n\nTEST_F(QueueTest, AddMax) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 255});\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{0, 100}}));\n}\n\nTEST_F(QueueTest, Misc) {\n  buildSimpleTree();\n\n  EXPECT_FALSE(q_.empty());\n  EXPECT_EQ(q_.numPendingEgress(), 5);\n  signalEgress(0, false);\n  EXPECT_EQ(q_.numPendingEgress(), 4);\n  EXPECT_FALSE(q_.empty());\n  removeTransaction(9);\n  removeTransaction(0);\n  dump();\n  EXPECT_EQ(nodes_, IDList({{3, 25}, {5, 25}, {7, 50}}));\n}\n\nTEST_F(QueueTest, IterateBFS) {\n  buildSimpleTree();\n\n  auto stopFn = [this] { return nodes_.size() > 2; };\n\n  dumpBFS(stopFn);\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {5, 25}, {7, 50}}));\n}\n\nTEST_F(QueueTest, NextEgress) {\n  buildSimpleTree();\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{0, 100}}));\n\n  addTransaction(11, {.streamDependency = 7, .exclusive = false, .weight = 15});\n  signalEgress(0, false);\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {5, 25}}));\n\n  signalEgress(5, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));\n  signalEgress(5, true);\n\n  signalEgress(3, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 66}, {5, 33}}));\n\n  signalEgress(5, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 66}, {9, 33}}));\n\n  signalEgress(7, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{11, 66}, {9, 33}}));\n\n  signalEgress(9, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{11, 100}}));\n\n  signalEgress(3, true);\n  signalEgress(7, true);\n  signalEgress(9, true);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));\n}\n\nTEST_F(QueueTest, NextEgressExclusiveAdd) {\n  buildSimpleTree();\n\n  // clear all egress\n  signalEgress(0, false);\n  signalEgress(3, false);\n  signalEgress(5, false);\n  signalEgress(7, false);\n  signalEgress(9, false);\n\n  // Add a transaction with exclusive dependency, clear its egress\n  addTransaction(11, {.streamDependency = 0, .exclusive = true, .weight = 100});\n  signalEgress(11, false);\n\n  // signal egress for a child that got moved via exclusive dep\n  signalEgress(3, true);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{3, 100}}));\n  EXPECT_EQ(q_.numPendingEgress(), 1);\n}\n\nTEST_F(QueueTest, NextEgressExclusiveAddWithEgress) {\n  buildSimpleTree();\n\n  // clear all egress, except 3\n  signalEgress(0, false);\n  signalEgress(5, false);\n  signalEgress(7, false);\n  signalEgress(9, false);\n\n  // Add a transaction with exclusive dependency, clear its egress\n  addTransaction(11, {.streamDependency = 0, .exclusive = true, .weight = 100});\n  signalEgress(11, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{3, 100}}));\n  EXPECT_EQ(q_.numPendingEgress(), 1);\n}\n\nTEST_F(QueueTest, UpdatePriorityReparentSubtree) {\n  buildSimpleTree();\n\n  // clear all egress, except 9\n  signalEgress(0, false);\n  signalEgress(3, false);\n  signalEgress(5, false);\n  signalEgress(7, false);\n\n  // Update priority of non-enqueued but in egress tree node\n  updatePriority(\n      5, {.streamDependency = 0, .exclusive = false, .weight = 14}, nullptr);\n\n  // update 9's weight and reparent\n  updatePriority(\n      9, {.streamDependency = 3, .exclusive = false, .weight = 14}, nullptr);\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{9, 100}}));\n}\n\nTEST_F(QueueTest, NextEgressRemoveParent) {\n  buildSimpleTree();\n\n  // Clear egress for all except txn=9\n  signalEgress(0, false);\n  signalEgress(3, false);\n  signalEgress(5, false);\n  signalEgress(7, false);\n\n  // Remove parent of 9 (5)\n  removeTransaction(5);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{9, 100}}));\n\n  // signal egress for 9's new siblings to verify weights\n  signalEgress(3, true);\n  signalEgress(7, true);\n\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {9, 25}, {3, 25}}));\n}\n\nTEST_F(QueueTest, AddExclusiveDescendantEnqueued) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 100});\n  addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 100});\n  addTransaction(5, {.streamDependency = 3, .exclusive = false, .weight = 100});\n  signalEgress(0, false);\n  signalEgress(3, false);\n  // add a new exclusive child of 1.  1's child 3 is not enqueued but is in the\n  // the egress tree.\n  addTransaction(7, {.streamDependency = 0, .exclusive = true, .weight = 100});\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 100}}));\n}\n\nTEST_F(QueueTest, NextEgressRemoveParentEnqueued) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 100});\n  addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 100});\n  addTransaction(5, {.streamDependency = 3, .exclusive = false, .weight = 100});\n  signalEgress(3, false);\n  // When 3's children (5) are added to 1, both are already in the egress tree\n  // and the signal does not need to propagate\n  removeTransaction(3);\n  signalEgress(0, false);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{5, 100}}));\n}\n\nTEST_F(QueueTest, NextEgressRemoveParentEnqueuedIndirect) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 100});\n  addTransaction(3, {.streamDependency = 0, .exclusive = false, .weight = 100});\n  addTransaction(5, {.streamDependency = 3, .exclusive = false, .weight = 100});\n  addTransaction(7, {.streamDependency = 0, .exclusive = false, .weight = 100});\n  signalEgress(3, false);\n  signalEgress(0, false);\n  // When 3's children (5) are added to 1, both are already in the egress tree\n  // and the signal does not need to propagate\n  removeTransaction(3);\n  nextEgress();\n  EXPECT_EQ(nodes_, IDList({{7, 50}, {5, 50}}));\n}\n\nTEST_F(QueueTest, ChromeTest) {\n  // Tries to simulate Chrome's current behavior by performing pseudo-random\n  // add-exclusive, signal, clear and remove with 3 insertion points\n  // (hi,mid,low).  Note the test uses rand32() with a particular seed so the\n  // output is predictable.\n  std::array<HTTPCodec::StreamID, 3> pris = {0, 3, 5};\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = true, .weight = 99});\n  signalEgress(0, false);\n  addTransaction(3, {.streamDependency = 0, .exclusive = true, .weight = 99});\n  signalEgress(3, false);\n  addTransaction(5, {.streamDependency = 3, .exclusive = true, .weight = 99});\n  signalEgress(5, false);\n\n  std::vector<HTTPCodec::StreamID> txns;\n  std::vector<HTTPCodec::StreamID> active;\n  std::vector<HTTPCodec::StreamID> inactive;\n  HTTPCodec::StreamID txn = 0;\n  uint64_t idx = 0;\n  HTTPCodec::StreamID nextId = 7;\n  auto gen = Random::create();\n  gen.seed(12345); // luggage combo\n  for (auto i = 4; i < 1000; i++) {\n    uint8_t action = rand32(4, gen);\n    if (action == 0) {\n      // add exclusive on pseudo-random priority anchor\n      uint8_t pri = rand32(3, gen);\n      HTTPCodec::StreamID dep = pris[pri];\n      txn = nextId;\n      nextId += 2;\n      VLOG(2) << \"Adding txn=\" << txn << \" with dep=\" << dep;\n      addTransaction(\n          txn,\n          {.streamDependency = (uint32_t)dep, .exclusive = true, .weight = 99});\n      txns.push_back(txn);\n      active.push_back(txn);\n    } else if (action == 1 && !inactive.empty()) {\n      // signal an inactive txn\n      idx = rand32(inactive.size(), gen);\n      txn = inactive[idx];\n      VLOG(2) << \"Activating txn=\" << txn;\n      signalEgress(txn, true);\n      inactive.erase(inactive.begin() + idx);\n      active.push_back(txn);\n    } else if (action == 2 && !active.empty()) {\n      // clear an active transaction\n      idx = rand32(active.size(), gen);\n      txn = active[idx];\n      VLOG(2) << \"Deactivating txn=\" << txn;\n      signalEgress(txn, false);\n      active.erase(active.begin() + idx);\n      inactive.push_back(txn);\n    } else if (action == 3 && !txns.empty()) {\n      // remove a transaction\n      idx = rand32(txns.size(), gen);\n      txn = txns[idx];\n      VLOG(2) << \"Removing txn=\" << txn;\n      removeTransaction(txn);\n      txns.erase(txns.begin() + idx);\n      auto it = std::find(active.begin(), active.end(), txn);\n      if (it != active.end()) {\n        active.erase(it);\n      }\n      it = std::find(inactive.begin(), inactive.end(), txn);\n      if (it != inactive.end()) {\n        inactive.erase(it);\n      }\n    }\n    VLOG(2) << \"Active nodes=\" << q_.numPendingEgress();\n    if (!q_.empty()) {\n      nextEgress();\n      EXPECT_GT(nodes_.size(), 0);\n    }\n  }\n}\n\nTEST_F(QueueTest, AddOrUpdate) {\n  q_.addOrUpdatePriorityNode(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  q_.addOrUpdatePriorityNode(\n      3, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 50}}));\n  q_.addOrUpdatePriorityNode(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 3});\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));\n}\n\nclass DanglingQueueTestBase {\n public:\n  DanglingQueueTestBase() {\n    // Just under two ticks\n    HTTP2PriorityQueue::setNodeLifetime(\n        std::chrono::milliseconds(2 * HHWheelTimer::DEFAULT_TICK_INTERVAL - 1));\n    EXPECT_CALL(timeoutManager_, scheduleTimeout(_, _))\n        .WillRepeatedly(Invoke(\n            [this](folly::AsyncTimeout* timeout, std::chrono::milliseconds) {\n              timeouts_.push_back(timeout);\n              return true;\n            }));\n  }\n\n protected:\n  void expireNodes() {\n    std::this_thread::sleep_for(\n        std::chrono::milliseconds(2 * HHWheelTimer::DEFAULT_TICK_INTERVAL));\n    // Node lifetime it just under two ticks, so firing twice expires all nodes\n    tick();\n    tick();\n  }\n\n  void tick() {\n    std::list<folly::AsyncTimeout*> timeouts;\n    std::swap(timeouts_, timeouts);\n    for (auto timeout : timeouts) {\n      timeout->timeoutExpired();\n    }\n  }\n\n  std::list<folly::AsyncTimeout*> timeouts_;\n  testing::NiceMock<MockTimeoutManager> timeoutManager_;\n  folly::UndelayedDestruction<HHWheelTimer> timer_{&timeoutManager_};\n};\n\n// Order declaration of the base classes for this fixture matters here: we want\n// to pass the timer initialized as part of DanglingQueueTest into to QueueTest,\n// so it must be initialized first.\nclass DanglingQueueTest\n    : public DanglingQueueTestBase\n    , public QueueTest {\n public:\n  DanglingQueueTest() : DanglingQueueTestBase(), QueueTest(&timer_) {\n  }\n};\n\nTEST_F(DanglingQueueTest, Basic) {\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  removeTransaction(0);\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({}));\n}\n\nTEST_F(DanglingQueueTest, Chain) {\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15},\n      true);\n  addTransaction(\n      3, {.streamDependency = 0, .exclusive = false, .weight = 15}, true);\n  addTransaction(\n      5, {.streamDependency = 3, .exclusive = false, .weight = 15}, true);\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}, {5, 100}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({}));\n}\n\nTEST_F(DanglingQueueTest, Drop) {\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15},\n      true);\n  addTransaction(\n      3, {.streamDependency = 0, .exclusive = false, .weight = 15}, true);\n  addTransaction(\n      5, {.streamDependency = 0, .exclusive = false, .weight = 15}, true);\n  dump();\n  q_.dropPriorityNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({}));\n}\n\nTEST_F(DanglingQueueTest, ExpireParentOfMismatchedTwins) {\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = true, .weight = 219},\n      false);\n  addTransaction(\n      3, {.streamDependency = 0, .exclusive = false, .weight = 146}, false);\n  addTransaction(\n      5, {.streamDependency = 0, .exclusive = false, .weight = 146}, false);\n  signalEgress(3, false);\n  signalEgress(5, true);\n  removeTransaction(0);\n  dump();\n  tick();\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));\n}\n\nTEST_F(DanglingQueueTest, AddExpireAdd) {\n  // Add a virtual node\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = true, .weight = 219},\n      true);\n  // expire it\n  expireNodes();\n  dump();\n  EXPECT_TRUE(q_.empty());\n  // Add a real node with the same id\n  addTransaction(\n      0,\n      {.streamDependency = kRootNodeId, .exclusive = true, .weight = 219},\n      false);\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}}));\n}\n\nclass DummyTimeout : public HHWheelTimer::Callback {\n  void timeoutExpired() noexcept override {\n  }\n};\n\nTEST_F(DanglingQueueTest, Refresh) {\n  // Having a long running timeout prevents HHWheelTimer::Callback::setScheduled\n  // from checking the real time\n  DummyTimeout t;\n  timer_.scheduleTimeout(&t, std::chrono::seconds(300));\n  addTransaction(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  addTransaction(\n      3, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 15});\n  // 0 is now virtual\n  removeTransaction(0);\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 50}}));\n  tick();\n  // before 0 times out, change it's priority, should still be there\n  updatePriority(\n      0, {.streamDependency = kRootNodeId, .exclusive = false, .weight = 3});\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));\n\n  tick();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{3, 100}}));\n}\n\nTEST_F(DanglingQueueTest, Max) {\n  buildSimpleTree();\n  q_.setMaxVirtualNodes(3);\n  for (auto i = 1; i <= 9; i += 2) {\n    removeTransaction(i == 1 ? 0 : i);\n  }\n  dump();\n  EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 50}, {5, 50}}));\n  // 0 expires first and it re-weights 3 and 5, which extends their lifetime\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));\n  expireNodes();\n  dump();\n  EXPECT_EQ(nodes_, IDList());\n}\n\nTEST_F(QueueTest, Rebuild) {\n  buildSimpleTree();\n  q_.rebuildTree();\n  dump();\n  EXPECT_EQ(nodes_, IDList({{3, 20}, {9, 20}, {5, 20}, {7, 20}, {0, 20}}));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPDefaultSessionCodecFactoryTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPDefaultSessionCodecFactory.h>\n\n#include <memory>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/services/AcceptorConfiguration.h>\n\nusing namespace proxygen;\n\nTEST(HTTPDefaultSessionCodecFactoryTest, GetCodecH2) {\n  auto conf = std::make_shared<AcceptorConfiguration>();\n  // If set directly on the acceptor, we should always return the H2C version.\n  conf->plaintextProtocol = \"h2c\";\n  HTTPDefaultSessionCodecFactory factory(std::move(conf));\n  auto codec = factory.getCodec(\n      \"http/1.1\", TransportDirection::DOWNSTREAM, false /* isTLS */);\n  auto* httpCodec = dynamic_cast<HTTP2Codec*>(codec.get());\n  EXPECT_NE(httpCodec, nullptr);\n  EXPECT_EQ(httpCodec->getProtocol(), CodecProtocol::HTTP_2);\n\n  // On a somewhat contrived example, if TLS we should return the version\n  // negotiated through ALPN.\n  codec = factory.getCodec(\n      \"http/1.1\", TransportDirection::UPSTREAM, true /* isTLS */);\n  auto* http1Codec = dynamic_cast<HTTP1xCodec*>(codec.get());\n  EXPECT_NE(http1Codec, nullptr);\n  EXPECT_EQ(http1Codec->getProtocol(), CodecProtocol::HTTP_1_1);\n}\n\nTEST(HTTPDefaultSessionCodecFactoryTest, GetCodec) {\n  auto conf = std::make_shared<AcceptorConfiguration>();\n  HTTPDefaultSessionCodecFactory factory(std::move(conf));\n\n  // Empty protocol should default to http/1.1\n  auto codec =\n      factory.getCodec(\"\", TransportDirection::DOWNSTREAM, false /* isTLS */);\n  auto* http1Codec = dynamic_cast<HTTP1xCodec*>(codec.get());\n  EXPECT_NE(http1Codec, nullptr);\n  EXPECT_EQ(http1Codec->getProtocol(), CodecProtocol::HTTP_1_1);\n\n  codec =\n      factory.getCodec(\"h2\", TransportDirection::DOWNSTREAM, false /* isTLS */);\n  auto* httpCodec = dynamic_cast<HTTP2Codec*>(codec.get());\n  EXPECT_NE(httpCodec, nullptr);\n  EXPECT_EQ(httpCodec->getProtocol(), CodecProtocol::HTTP_2);\n\n  // Not supported protocols should return nullptr.\n  codec = factory.getCodec(\n      \"not/supported\", TransportDirection::DOWNSTREAM, false /* isTLS */);\n  EXPECT_EQ(codec, nullptr);\n}\n\nstruct TestParams {\n  bool strict;\n  std::string plaintextProto;\n};\n\nclass HTTPDefaultSessionCodecFactoryValidationTest\n    : public ::testing::TestWithParam<TestParams> {};\n\nTEST_P(HTTPDefaultSessionCodecFactoryValidationTest, StrictValidation) {\n  auto conf = std::make_shared<AcceptorConfiguration>();\n  conf->plaintextProtocol = GetParam().plaintextProto;\n  auto isPlaintextProtocolEmpty = conf->plaintextProtocol.empty();\n  HTTPDefaultSessionCodecFactory factory(std::move(conf));\n  bool strict = GetParam().strict;\n  factory.setConfigFn([strict] {\n    HTTPCodecFactory::CodecConfig config;\n    config.strictValidation = strict;\n    return config;\n  });\n\n  auto codec = factory.getCodec(\n      http2::kProtocolString, TransportDirection::DOWNSTREAM, false);\n  HTTP2Codec upstream(TransportDirection::UPSTREAM);\n  HTTPMessage req;\n  folly::IOBufQueue output{folly::IOBufQueue::cacheChainLength()};\n  req.setURL(\"/foo\\xff\");\n  upstream.generateConnectionPreface(output);\n  upstream.generateSettings(output);\n  upstream.generateHeader(output, upstream.createStream(), req, true, nullptr);\n  FakeHTTPCodecCallback callbacks;\n  codec->setCallback(&callbacks);\n  codec->onIngress(*output.front());\n  EXPECT_EQ(callbacks.messageBegin, 1);\n  EXPECT_EQ(callbacks.headersComplete, strict ? 0 : 1);\n  EXPECT_EQ(callbacks.streamErrors, strict ? 1 : 0);\n  output.reset();\n\n  if (isPlaintextProtocolEmpty) {\n    callbacks.reset();\n    codec = factory.getCodec(\"http/1.1\", TransportDirection::DOWNSTREAM, true);\n    codec->setCallback(&callbacks);\n    codec->onIngress(\n        *folly::IOBuf::copyBuffer(\"GET /foo\\xff HTTP/1.1\\r\\n\\r\\n\"));\n    EXPECT_EQ(callbacks.messageBegin, 1);\n    EXPECT_EQ(callbacks.headersComplete, strict ? 0 : 1);\n    EXPECT_EQ(callbacks.streamErrors, strict ? 1 : 0);\n  }\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDefaultSessionCodecFactoryTest,\n    HTTPDefaultSessionCodecFactoryValidationTest,\n    ::testing::Values(TestParams({true, std::string()}),\n                      TestParams({true, std::string(\"h2c\")}),\n                      TestParams({false, std::string(\"\")}),\n                      TestParams({false, std::string(\"h2c\")})));\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPDownstreamSessionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <string>\n#include <vector>\n\n#include <folly/Conv.h>\n#include <folly/Range.h>\n#include <folly/futures/Promise.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/io/async/TimeoutManager.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/ControlMessageRateLimitFilter.h>\n#include <proxygen/lib/http/codec/DirectErrorsRateLimitFilter.h>\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n#include <proxygen/lib/http/session/HTTPDirectResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <proxygen/lib/http/session/HTTPErrorPage.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPSessionTest.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockByteEventTracker.h>\n#include <proxygen/lib/http/session/test/MockHTTPSessionStats.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <proxygen/lib/test/TestAsyncTransport.h>\n#include <wangle/acceptor/ConnectionManager.h>\n\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\nusing namespace std::chrono;\nusing folly::Promise;\n\ntemplate <typename C>\nclass HTTPDownstreamTest : public testing::Test {\n public:\n  explicit HTTPDownstreamTest(std::vector<int64_t> flowControl = {-1, -1, -1},\n                              bool startImmediately = true)\n      : eventBase_(),\n        transport_(new TestAsyncTransport(&eventBase_)),\n        transactionTimeouts_(makeTimeoutSet(&eventBase_)),\n        flowControl_(flowControl) {\n    EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n        .WillRepeatedly(Return(std::chrono::milliseconds(0)));\n    EXPECT_CALL(mockController_, getHeaderIndexingStrategy())\n        .WillRepeatedly(Return(HeaderIndexingStrategy::getDefaultInstance()));\n\n    {\n      InSequence s;\n      EXPECT_CALL(mockController_, attachSession(_));\n      EXPECT_CALL(mockController_, onTransportReady(_));\n    }\n\n    HTTPSession::setDefaultReadBufferLimit(65536);\n    HTTPTransaction::setEgressBufferLimit(65536);\n    auto codec = makeServerCodec<typename C::Codec>(C::version);\n    rawCodec_ = codec.get();\n    if (dynamic_cast<HTTP1xCodec*>(rawCodec_) != nullptr) {\n      dynamic_cast<HTTP1xCodec*>(rawCodec_)->setStrictValidation(true);\n    } else if (dynamic_cast<HTTP2Codec*>(rawCodec_) != nullptr) {\n      dynamic_cast<HTTP2Codec*>(rawCodec_)->setStrictValidation(true);\n    }\n\n    // If the codec is H2, getHeaderIndexingStrategy will be called when setting\n    // up the codec\n    if (rawCodec_->getProtocol() == CodecProtocol::HTTP_2) {\n      EXPECT_CALL(mockController_, getHeaderIndexingStrategy())\n          .WillOnce(Return(&testH2IndexingStrat_));\n    }\n\n    httpSession_ = new HTTPDownstreamSession(\n        transactionTimeouts_.get(),\n        std::move(folly::AsyncTransport::UniquePtr(transport_)),\n        localAddr,\n        peerAddr,\n        &mockController_,\n        std::move(codec),\n        mockTransportInfo /* no stats for now */,\n        &infoCb_);\n    for (auto& param : flowControl) {\n      if (param < 0) {\n        param = rawCodec_->getDefaultWindowSize();\n      }\n    }\n\n    // Ensure the H2 header indexing strategy was setup correctly if applicable\n    if (rawCodec_->getProtocol() == CodecProtocol::HTTP_2) {\n      auto* recastedCodec = dynamic_cast<HTTP2Codec*>(rawCodec_);\n      EXPECT_EQ(recastedCodec->getHeaderIndexingStrategy(),\n                &testH2IndexingStrat_);\n    }\n\n    httpSession_->setFlowControl(\n        flowControl[0], flowControl[1], flowControl[2]);\n    httpSession_->setEgressSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 200},\n                                     {SettingsId::HEADER_TABLE_SIZE, 5555},\n                                     {SettingsId::ENABLE_PUSH, 1}});\n    if (startImmediately) {\n      httpSession_->startNow();\n    }\n    clientCodec_ = makeClientCodec<typename C::Codec>(C::version);\n    clientCodec_->generateConnectionPreface(requests_);\n    clientCodec_->generateSettings(requests_);\n    clientCodec_->setCallback(&callbacks_);\n  }\n\n  HTTPCodec::StreamID sendRequest(const std::string& url = \"/\",\n                                  int8_t priority = 0,\n                                  bool eom = true) {\n    auto req = getGetRequest();\n    req.setURL(url);\n    req.setPriority(priority);\n    return sendRequest(req, eom);\n  }\n\n  HTTPCodec::StreamID sendRequest(const HTTPMessage& req, bool eom = true) {\n    auto streamID = clientCodec_->createStream();\n    clientCodec_->generateHeader(requests_, streamID, req, eom);\n    return streamID;\n  }\n\n  HTTPCodec::StreamID sendHeader() {\n    return sendRequest(\"/\", 0, false);\n  }\n\n  Promise<folly::Unit> sendRequestLater(HTTPMessage req, bool eof = false) {\n    Promise<folly::Unit> reqp;\n    reqp.getSemiFuture().via(&eventBase_).thenValue([=, this](auto&&) {\n      sendRequest(req);\n      transport_->addReadEvent(requests_, milliseconds(0));\n      if (eof) {\n        transport_->addReadEOF(milliseconds(0));\n      }\n    });\n    return reqp;\n  }\n\n  void SetUp() override {\n    folly::EventBaseManager::get()->clearEventBase();\n    HTTPSession::setDefaultWriteBufferLimit(65536);\n    HTTP2PriorityQueue::setNodeLifetime(std::chrono::milliseconds(2));\n    EXPECT_CALL(infoCb_, onTransactionAttached(_)).WillRepeatedly([this]() {\n      onTransactionSymmetricCounter++;\n    });\n    EXPECT_CALL(infoCb_, onTransactionDetached(_)).WillRepeatedly([this]() {\n      onTransactionSymmetricCounter--;\n    });\n  }\n\n  void TearDown() override {\n    EXPECT_EQ(onTransactionSymmetricCounter, 0);\n  }\n\n  void cleanup() {\n    EXPECT_CALL(mockController_, detachSession(_));\n    httpSession_->dropConnection();\n  }\n\n  std::unique_ptr<testing::StrictMock<MockHTTPHandler>>\n  addSimpleStrictHandler() {\n    std::unique_ptr<testing::StrictMock<MockHTTPHandler>> handler =\n        std::make_unique<testing::StrictMock<MockHTTPHandler>>();\n\n    // The ownership model here is suspect, but assume the callers won't destroy\n    // handler before it's requested\n    auto rawHandler = handler.get();\n    EXPECT_CALL(mockController_, getRequestHandler(testing::_, testing::_))\n        .WillOnce(testing::Return(rawHandler))\n        .RetiresOnSaturation();\n\n    EXPECT_CALL(*handler, _setTransaction(testing::_))\n        .WillOnce(testing::SaveArg<0>(&handler->txn_));\n\n    return handler;\n  }\n\n  std::unique_ptr<testing::NiceMock<MockHTTPHandler>> addSimpleNiceHandler() {\n    std::unique_ptr<testing::NiceMock<MockHTTPHandler>> handler =\n        std::make_unique<testing::NiceMock<MockHTTPHandler>>();\n\n    // See comment above\n    auto rawHandler = handler.get();\n    EXPECT_CALL(mockController_, getRequestHandler(testing::_, testing::_))\n        .WillOnce(testing::Return(rawHandler))\n        .RetiresOnSaturation();\n\n    EXPECT_CALL(*handler, _setTransaction(testing::_))\n        .WillOnce(testing::SaveArg<0>(&handler->txn_))\n        .RetiresOnSaturation();\n\n    return handler;\n  }\n\n  void onEOMTerminateHandlerExpectShutdown(MockHTTPHandler& handler) {\n    handler.expectEOM([&] { handler.terminate(); });\n    handler.expectDetachTransaction();\n    expectDetachSession();\n  }\n\n  void expectDetachSession() {\n    EXPECT_CALL(mockController_, detachSession(testing::_));\n  }\n\n  void addSingleByteReads(const char* data, milliseconds delay = {}) {\n    for (const char* p = data; *p != '\\0'; ++p) {\n      transport_->addReadEvent(p, 1, delay);\n    }\n  }\n\n  void flushRequestsAndLoop(\n      bool eof = false,\n      milliseconds eofDelay = milliseconds(0),\n      milliseconds initialDelay = milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>()) {\n    flushRequests(eof, eofDelay, initialDelay, extraEventsFn);\n    eventBase_.loop();\n  }\n\n  void flushRequestsAndLoopN(\n      uint64_t n,\n      bool eof = false,\n      milliseconds eofDelay = milliseconds(0),\n      milliseconds initialDelay = milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>()) {\n    flushRequests(eof, eofDelay, initialDelay, extraEventsFn);\n    for (uint64_t i = 0; i < n; i++) {\n      eventBase_.loopOnce();\n    }\n  }\n\n  void flushRequests(\n      bool eof = false,\n      milliseconds eofDelay = milliseconds(0),\n      milliseconds initialDelay = milliseconds(0),\n      std::function<void()> extraEventsFn = std::function<void()>()) {\n    transport_->addReadEvent(requests_, initialDelay);\n    requests_.move();\n    if (extraEventsFn) {\n      extraEventsFn();\n    }\n    if (eof) {\n      transport_->addReadEOF(eofDelay);\n    }\n    transport_->startReadEvents();\n  }\n\n  void testSimpleUpgrade(const std::string& upgradeHeader,\n                         CodecProtocol expectedProtocol,\n                         const std::string& expectedUpgradeHeader);\n\n  void gracefulShutdown() {\n    folly::DelayedDestruction::DestructorGuard g(httpSession_);\n    clientCodec_->generateGoaway(this->requests_, 0, ErrorCode::NO_ERROR);\n    expectDetachSession();\n    flushRequestsAndLoop(true);\n  }\n\n  void testPriorities(uint32_t numPriorities);\n\n  void testChunks(bool trailers);\n\n  void expectResponse(uint32_t code, bool expectBody, bool stopParsing = true) {\n    expectResponse(\n        code, ErrorCode::NO_ERROR, false, false, expectBody, stopParsing);\n  }\n\n  void expectResponse(uint32_t code = 200,\n                      ErrorCode errorCode = ErrorCode::NO_ERROR,\n                      bool expect100 = false,\n                      bool expectGoaway = false,\n                      bool expectBody = true,\n                      bool stopParsing = false) {\n    expectResponses(\n        1, code, errorCode, expect100, expectGoaway, expectBody, stopParsing);\n  }\n  void expectResponses(uint32_t n,\n                       uint32_t code = 200,\n                       ErrorCode errorCode = ErrorCode::NO_ERROR,\n                       bool expect100 = false,\n                       bool expectGoaway = false,\n                       bool expectBody = true,\n                       bool stopParsing = false) {\n    clientCodec_->setCallback(&callbacks_);\n    if (isParallelCodecProtocol(clientCodec_->getProtocol())) {\n      EXPECT_CALL(callbacks_, onSettings(_))\n          .WillOnce(Invoke([this](const SettingsList& settings) {\n            if (flowControl_[0] > 0) {\n              bool foundInitialWindow = false;\n              for (const auto& setting : settings) {\n                if (setting.id == SettingsId::INITIAL_WINDOW_SIZE) {\n                  EXPECT_EQ(flowControl_[0], setting.value);\n                  foundInitialWindow = true;\n                }\n              }\n              EXPECT_TRUE(foundInitialWindow);\n            }\n          }));\n    }\n    if (flowControl_[2] > 0) {\n      int64_t sessionDelta =\n          flowControl_[2] - clientCodec_->getDefaultWindowSize();\n      if (clientCodec_->supportsSessionFlowControl() && sessionDelta) {\n        EXPECT_CALL(callbacks_, onWindowUpdate(0, sessionDelta));\n      }\n    }\n    if (flowControl_[1] > 0) {\n      size_t initWindow = flowControl_[0] > 0\n                              ? flowControl_[0]\n                              : clientCodec_->getDefaultWindowSize();\n      int64_t streamDelta = flowControl_[1] - initWindow;\n      if (clientCodec_->supportsStreamFlowControl() && streamDelta) {\n        EXPECT_CALL(callbacks_, onWindowUpdate(1, streamDelta));\n      }\n    }\n\n    if (expectGoaway) {\n      EXPECT_CALL(callbacks_,\n                  onGoaway(HTTPCodec::StreamID(1), ErrorCode::NO_ERROR, _));\n    }\n\n    for (uint32_t i = 0; i < n; i++) {\n      uint8_t times = (expect100) ? 2 : 1;\n      EXPECT_CALL(callbacks_, onMessageBegin(_, _))\n          .Times(times)\n          .RetiresOnSaturation();\n      EXPECT_CALL(callbacks_, onHeadersComplete(_, _))\n          .WillOnce(Invoke(\n              [code](HTTPCodec::StreamID, std::shared_ptr<HTTPMessage> msg) {\n                EXPECT_EQ(msg->getStatusCode(), code);\n              }));\n      if (expect100) {\n        EXPECT_CALL(callbacks_, onHeadersComplete(_, _))\n            .WillOnce(Invoke(\n                [](HTTPCodec::StreamID, std::shared_ptr<HTTPMessage> msg) {\n                  EXPECT_EQ(msg->getStatusCode(), 100);\n                }))\n            .RetiresOnSaturation();\n      }\n      if (errorCode != ErrorCode::NO_ERROR) {\n        EXPECT_CALL(callbacks_, onAbort(_, _))\n            .WillOnce(Invoke([errorCode](HTTPCodec::StreamID, ErrorCode error) {\n              EXPECT_EQ(error, errorCode);\n            }));\n      }\n      if (expectBody) {\n        EXPECT_CALL(callbacks_, onBody(_, _, _)).RetiresOnSaturation();\n      }\n      EXPECT_CALL(callbacks_, onMessageComplete(_, _))\n          .WillOnce(InvokeWithoutArgs([i, n, stopParsing, this] {\n            if (stopParsing && i == n - 1) {\n              clientCodec_->setParserPaused(true);\n              breakParseOutput_ = true;\n            }\n          }))\n          .RetiresOnSaturation();\n    }\n    parseOutput(*clientCodec_);\n  }\n\n  void parseOutput(HTTPCodec& clientCodec) {\n    auto writeEvents = transport_->getWriteEvents();\n    clientCodec.setParserPaused(false);\n    breakParseOutput_ = false;\n    while (!breakParseOutput_ &&\n           (!writeEvents->empty() || !parseOutputStream_.empty())) {\n      if (!writeEvents->empty()) {\n        auto event = writeEvents->front();\n        auto vec = event->getIoVec();\n        for (size_t i = 0; i < event->getCount(); i++) {\n          parseOutputStream_.append(\n              folly::IOBuf::copyBuffer(vec[i].iov_base, vec[i].iov_len));\n        }\n        writeEvents->pop_front();\n      }\n      uint32_t consumed = clientCodec.onIngress(*parseOutputStream_.front());\n      ASSERT_TRUE(consumed > 0 || !writeEvents->empty());\n      parseOutputStream_.split(consumed);\n    }\n    if (!breakParseOutput_) {\n      EXPECT_EQ(parseOutputStream_.chainLength(), 0);\n    }\n    breakParseOutput_ = false;\n  }\n\n  void resumeWritesInLoop() {\n    eventBase_.runInLoop([this] { transport_->resumeWrites(); });\n  }\n\n  void resumeWritesAfterDelay(milliseconds delay) {\n    eventBase_.runAfterDelay([this] { transport_->resumeWrites(); },\n                             delay.count());\n  }\n\n  MockByteEventTracker* setMockByteEventTracker() {\n    auto byteEventTracker = new NiceMock<MockByteEventTracker>(nullptr);\n    httpSession_->setByteEventTracker(\n        std::unique_ptr<ByteEventTracker>(byteEventTracker));\n    EXPECT_CALL(*byteEventTracker, preSend(_, _, _, _))\n        .WillRepeatedly(Return(0));\n    EXPECT_CALL(*byteEventTracker, drainByteEvents()).WillRepeatedly(Return(0));\n    EXPECT_CALL(*byteEventTracker, processByteEvents(_, _))\n        .WillRepeatedly(Invoke([](std::shared_ptr<ByteEventTracker> self,\n                                  uint64_t bytesWritten) {\n          return self->ByteEventTracker::processByteEvents(self, bytesWritten);\n        }));\n\n    return byteEventTracker;\n  }\n\n  std::unique_ptr<MockSessionObserver> addMockSessionObserver(\n      MockSessionObserver::EventSet eventSet) {\n    auto observer = std::make_unique<NiceMock<MockSessionObserver>>(eventSet);\n    EXPECT_CALL(*observer, attached(_));\n    httpSession_->addObserver(observer.get());\n    return observer;\n  }\n\n  std::shared_ptr<MockSessionObserver> addMockSessionObserverShared(\n      MockSessionObserver::EventSet eventSet) {\n    auto observer = std::make_shared<NiceMock<MockSessionObserver>>(eventSet);\n    EXPECT_CALL(*observer, attached(_));\n    httpSession_->addObserver(observer);\n    return observer;\n  }\n\n  // Utility to loop the evb but avoid blocking if there are no queued events.\n  void evbLoopNonBlockN(size_t count) {\n    for (; count > 0; count--) {\n      eventBase_.loopOnce(EVLOOP_NONBLOCK);\n    }\n  }\n\n protected:\n  folly::EventBase eventBase_;\n  TestAsyncTransport* transport_; // invalid once httpSession_ is destroyed\n  folly::HHWheelTimer::UniquePtr transactionTimeouts_;\n  std::vector<int64_t> flowControl_;\n  StrictMock<MockController> mockController_;\n  HTTPDownstreamSession* httpSession_;\n  folly::IOBufQueue requests_{folly::IOBufQueue::cacheChainLength()};\n  unique_ptr<HTTPCodec> clientCodec_;\n  NiceMock<MockHTTPCodecCallback> callbacks_;\n  folly::IOBufQueue parseOutputStream_{folly::IOBufQueue::cacheChainLength()};\n  bool breakParseOutput_{false};\n  typename C::Codec* rawCodec_{nullptr};\n  HeaderIndexingStrategy testH2IndexingStrat_;\n  testing::NiceMock<proxygen::MockHTTPSessionInfoCallback> infoCb_;\n  uint64_t onTransactionSymmetricCounter{0};\n};\n\n// Uses TestAsyncTransport\nusing HTTPDownstreamSessionTest = HTTPDownstreamTest<HTTP1xCodecPair>;\nnamespace {\nclass HTTP2DownstreamSessionTest : public HTTPDownstreamTest<HTTP2CodecPair> {\n public:\n  HTTP2DownstreamSessionTest() : HTTPDownstreamTest<HTTP2CodecPair>() {\n  }\n\n  void SetUp() override {\n    HTTPDownstreamTest<HTTP2CodecPair>::SetUp();\n  }\n\n  void TearDown() override {\n  }\n};\n} // namespace\n\nnamespace {\nclass HTTP2DownstreamSessionEarlyShutdownTest\n    : public HTTPDownstreamTest<HTTP2CodecPair> {\n public:\n  HTTP2DownstreamSessionEarlyShutdownTest()\n      : HTTPDownstreamTest<HTTP2CodecPair>({-1, -1, -1}, false) {\n  }\n\n  void SetUp() override {\n    HTTPDownstreamTest<HTTP2CodecPair>::SetUp();\n  }\n\n  void TearDown() override {\n  }\n};\n} // namespace\n\nTEST_F(HTTP2DownstreamSessionEarlyShutdownTest, EarlyShutdown) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n\n  // Try shutting down the session and then starting it. This should be properly\n  // handled by the HTTPSession such that no HTTP/2 frames are sent in the\n  // wrong order.\n  StrictMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onFrameHeader(_, _, _, _, _)).Times(2);\n  EXPECT_CALL(callbacks, onSettings(_)).Times(1);\n  EXPECT_CALL(callbacks, onGoaway(_, _, _)).Times(1);\n  expectDetachSession();\n  httpSession_->notifyPendingShutdown();\n  httpSession_->startNow();\n  eventBase_.loop();\n  parseOutput(*clientCodec_);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionEarlyShutdownTest, EarlyShutdownDoubleGoaway) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n  httpSession_->enableDoubleGoawayDrain();\n\n  StrictMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onFrameHeader(_, _, _, _, _)).Times(3);\n  EXPECT_CALL(callbacks, onSettings(_)).Times(1);\n  EXPECT_CALL(callbacks, onGoaway(_, _, _)).Times(2);\n  expectDetachSession();\n  httpSession_->notifyPendingShutdown();\n  httpSession_->startNow();\n  eventBase_.loop();\n  parseOutput(*clientCodec_);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ShutdownDoubleGoaway) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n  httpSession_->enableDoubleGoawayDrain();\n\n  StrictMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onFrameHeader(_, _, _, _, _)).Times(3);\n  EXPECT_CALL(callbacks, onSettings(_)).Times(1);\n  EXPECT_CALL(callbacks, onGoaway(_, _, _)).Times(2);\n  expectDetachSession();\n  httpSession_->notifyPendingShutdown();\n  eventBase_.loop();\n  parseOutput(*clientCodec_);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTPDownstreamSessionTest, ImmediateEof) {\n  // Send EOF without any request data\n  EXPECT_CALL(mockController_, getRequestHandler(_, _)).Times(0);\n  expectDetachSession();\n\n  flushRequestsAndLoop(true, milliseconds(0));\n}\n\nTEST_F(HTTPDownstreamSessionTest, IdleTimeoutWithOpenStreamGraceful) {\n  // Send a request and simulate idle timeout.  Then send response.  Session\n  // will include Connection: close and terminate.\n  auto id = sendRequest(getGetRequest());\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n\n  // Simulate a read timeout, then send response in next loop\n  eventBase_.runAfterDelay(\n      [&] {\n        httpSession_->timeoutExpired();\n        eventBase_.runInLoop(\n            [&handler] { handler->sendReplyWithBody(200, 100); });\n      },\n      transactionTimeouts_->getDefaultTimeout().count());\n  handler->expectDetachTransaction();\n  expectDetachSession();\n\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop(true, milliseconds(0));\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  EXPECT_CALL(callbacks, onHeadersComplete(id, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> msg) {\n        EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"close\");\n      }));\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTPDownstreamSessionTest, IdleTimeoutNoStreams) {\n  httpSession_->timeoutExpired();\n  expectDetachSession();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, IdleTimeoutWithOpenStreamUngraceful) {\n  // Send a request, send response header, then simulate idle timeout.\n  // and send the remaining response.  Session will not include\n  // Connection: close and terminate.\n  sendRequest(getGetRequest());\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&handler] { handler->sendHeaders(200, 100); });\n  handler->expectEOM();\n\n  // Simulate a read timeout, then send response in next loop\n  eventBase_.runAfterDelay(\n      [&] {\n        httpSession_->timeoutExpired();\n        eventBase_.runInLoop([&handler] {\n          handler->sendBody(100);\n          handler->txn_->sendEOM();\n        });\n      },\n      transactionTimeouts_->getDefaultTimeout().count());\n  handler->expectDetachTransaction();\n  expectDetachSession();\n\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop(true, milliseconds(0));\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> msg) {\n        EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION),\n                  \"keep-alive\");\n      }));\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTPDownstreamSessionTest, Http10NoHeaders) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/\", msg->getURL());\n    EXPECT_EQ(\"/\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(0, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  auto req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  sendRequest(req);\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, Http10NoHeadersEof) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"http://example.com/foo?bar\", msg->getURL());\n    EXPECT_EQ(\"/foo\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"bar\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(0, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  const char* req = \"GET http://example.com/foo?bar HTTP/1.0\\r\\n\\r\\n\";\n  requests_.append(req, strlen(req));\n  flushRequestsAndLoop(true, milliseconds(0));\n}\n\nTEST_F(HTTPDownstreamSessionTest, SingleBytes) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"connection\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  addSingleByteReads(\n      \"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"\\r\\n\");\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, SingleBytesWithBody) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(3, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"content-length\"));\n    EXPECT_TRUE(hdrs.exists(\"myheader\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"1\"))\n      .WillOnce(ExpectString(\"2\"))\n      .WillOnce(ExpectString(\"3\"))\n      .WillOnce(ExpectString(\"4\"))\n      .WillOnce(ExpectString(\"5\"));\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  addSingleByteReads(\n      \"POST /somepath.php?param=foo HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"MyHeader: FooBar\\r\\n\"\n      \"Content-Length: 5\\r\\n\"\n      \"\\r\\n\"\n      \"12345\");\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, SplitBody) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"12345\"))\n      .WillOnce(ExpectString(\"abcde\"));\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addReadEvent(\n      \"POST / HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"Content-Length: 10\\r\\n\"\n      \"\\r\\n\"\n      \"12345\",\n      milliseconds(0));\n  transport_->addReadEvent(\"abcde\", milliseconds(5));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, MovableBuffer) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"connection\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addMovableReadEvent(\n      folly::IOBuf::copyBuffer(\"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n                               \"Host: example.com\\r\\n\"\n                               \"Connection: close\\r\\n\"\n                               \"\\r\\n\"));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, MovableBufferChained) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"connection\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  auto buf = folly::IOBuf::copyBuffer(\n      \"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\");\n  buf->prependChain(\n      folly::IOBuf::copyBuffer(\"Connection: close\\r\\n\"\n                               \"\\r\\n\"));\n  transport_->addMovableReadEvent(std::move(buf));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, MovableBufferMultiple) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"connection\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addMovableReadEvent(\n      folly::IOBuf::copyBuffer(\"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n                               \"Host: example.com\\r\\n\"));\n  transport_->addMovableReadEvent(\n      folly::IOBuf::copyBuffer(\"Connection: close\\r\\n\"\n                               \"\\r\\n\"));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, MovableBufferChainedEmptyBuffer) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(2, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"connection\"));\n\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"/somepath.php?param=foo\", msg->getURL());\n    EXPECT_EQ(\"/somepath.php\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"param=foo\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  auto buf = folly::IOBuf::copyBuffer(\n      \"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\");\n  buf->prependChain(folly::IOBuf::create(0));\n  buf->prependChain(\n      folly::IOBuf::copyBuffer(\"Connection: close\\r\\n\"\n                               \"\\r\\n\"));\n  transport_->addMovableReadEvent(std::move(buf));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, PostChunked) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    const HTTPHeaders& hdrs = msg->getHeaders();\n    EXPECT_EQ(3, hdrs.size());\n    EXPECT_TRUE(hdrs.exists(\"host\"));\n    EXPECT_TRUE(hdrs.exists(\"content-type\"));\n    EXPECT_TRUE(hdrs.exists(\"transfer-encoding\"));\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(\"http://example.com/cgi-bin/foo.aspx?abc&def\", msg->getURL());\n    EXPECT_EQ(\"/cgi-bin/foo.aspx\", msg->getPathAsStringPiece());\n    EXPECT_EQ(\"abc&def\", msg->getQueryStringAsStringPiece());\n    EXPECT_EQ(1, msg->getHTTPVersion().first);\n    EXPECT_EQ(1, msg->getHTTPVersion().second);\n  });\n  EXPECT_CALL(*handler, _onChunkHeader(3));\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).WillOnce(ExpectString(\"bar\"));\n  EXPECT_CALL(*handler, _onChunkComplete());\n  EXPECT_CALL(*handler, _onChunkHeader(0x22));\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"0123456789abcdef\\nfedcba9876543210\\n\"));\n  EXPECT_CALL(*handler, _onChunkComplete());\n  EXPECT_CALL(*handler, _onChunkHeader(3));\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).WillOnce(ExpectString(\"foo\"));\n  EXPECT_CALL(*handler, _onChunkComplete());\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addReadEvent(\n      \"POST http://example.com/cgi-bin/foo.aspx?abc&def \"\n      \"HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"Content-Type: text/pla\",\n      milliseconds(0));\n  transport_->addReadEvent(\n      \"in; charset=utf-8\\r\\n\"\n      \"Transfer-encoding: chunked\\r\\n\"\n      \"\\r\",\n      milliseconds(2));\n  transport_->addReadEvent(\n      \"\\n\"\n      \"3\\r\\n\"\n      \"bar\\r\\n\"\n      \"22\\r\\n\"\n      \"0123456789abcdef\\n\"\n      \"fedcba9876543210\\n\"\n      \"\\r\\n\"\n      \"3\\r\",\n      milliseconds(3));\n  transport_->addReadEvent(\n      \"\\n\"\n      \"foo\\r\\n\"\n      \"0\\r\\n\\r\\n\",\n      milliseconds(1));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, MultiMessage) {\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  EXPECT_CALL(*handler1, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"foo\"))\n      .WillOnce(ExpectString(\"bar9876\"));\n  handler1->expectEOM([&] { handler1->sendReply(); });\n  handler1->expectDetachTransaction();\n\n  auto handler2 = addSimpleNiceHandler();\n  handler2->expectHeaders();\n  EXPECT_CALL(*handler2, _onChunkHeader(0xa));\n  EXPECT_CALL(*handler2, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"some \"))\n      .WillOnce(ExpectString(\"data\\n\"));\n  EXPECT_CALL(*handler2, _onChunkComplete());\n  onEOMTerminateHandlerExpectShutdown(*handler2);\n\n  transport_->addReadEvent(\n      \"POST / HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"Content-Length: 10\\r\\n\"\n      \"\\r\\n\"\n      \"foo\",\n      milliseconds(0));\n  transport_->addReadEvent(\n      \"bar9876\"\n      \"POST /foo HTTP/1.1\\r\\n\"\n      \"Host: exa\",\n      milliseconds(20));\n  transport_->addReadEvent(\n      \"mple.com\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"Trans\",\n      milliseconds(0));\n  transport_->addReadEvent(\n      \"fer-encoding: chunked\\r\\n\"\n      \"\\r\\n\",\n      milliseconds(20));\n  transport_->addReadEvent(\"a\\r\\nsome \", milliseconds(0));\n  transport_->addReadEvent(\"data\\n\\r\\n0\\r\\n\\r\\n\", milliseconds(20));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, ClientPipelined) {\n  InSequence enforceOrder;\n\n  std::vector<std::unique_ptr<testing::NiceMock<MockHTTPHandler>>> handlers;\n  for (auto i = 0; i < 4; i++) {\n    auto handler = addSimpleNiceHandler();\n    handler->expectHeaders([i](std::shared_ptr<HTTPMessage> req) {\n      EXPECT_EQ(req->getHeaders().getSingleOrEmpty(\"Id\"),\n                folly::to<std::string>(i + 1));\n    });\n    handler->expectEOM([h = handler.get()] { h->sendReplyWithBody(200, 100); });\n    handler->expectDetachTransaction();\n    handlers.push_back(std::move(handler));\n  }\n\n  transport_->addReadEvent(\n      \"GET /echo HTTP/1.1\\r\\n\"\n      \"Host: jojo\\r\\n\"\n      \"Id: 1\\r\\n\"\n      \"\\r\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\"\n      \"Host: jojo\\r\\n\"\n      \"Id: 2\\r\\n\"\n      \"\\r\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\"\n      \"Host: jojo\\r\\n\"\n      \"Id: 3\\r\\n\"\n      \"\\r\\n\"\n      \"GET /echo HTTP/1.1\\r\\n\"\n      \"Host: jojo\\r\\n\"\n      \"Id: 4\\r\\n\"\n      \"\\r\\n\",\n      milliseconds(0));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  expectDetachSession();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, BadContentLength) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders();\n  handler->expectBody([](uint64_t, std::shared_ptr<folly::IOBuf> body) {\n    EXPECT_EQ(body->computeChainDataLength(), 6);\n  });\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectDetachTransaction();\n\n  // Test sending more bytes than advertised in Content-Length.  The proxy will\n  // ignore these since Connection: close was also specified.\n  //\n  // One could argue it would be better to 400 this kind of request.\n  auto req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  req.setWantsKeepalive(false);\n  req.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"6\");\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(10), HTTPCodec::NoPadding, true);\n  expectDetachSession();\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, FrameBasedPadding) {\n  // Send a request with padding. Padding should not affect anything.\n  auto handler = addSimpleStrictHandler();\n\n  const auto streamID = clientCodec_->createStream();\n  clientCodec_->generatePadding(requests_, streamID, 100);\n  clientCodec_->generateHeader(\n      requests_, streamID, getPostRequest(1'000), false);\n  clientCodec_->generatePadding(requests_, streamID, 200);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(1'000), HTTPCodec::NoPadding, false);\n  clientCodec_->generatePadding(requests_, streamID, 400);\n  clientCodec_->generateEOM(requests_, streamID);\n  clientCodec_->generatePadding(requests_, streamID, 800);\n\n  handler->expectHeaders();\n  handler->expectBody();\n  handler->expectEOM([&handler] {\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendHeaders(getResponse(200));\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendChunkHeader(10);\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendBody(makeBuf(10));\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendChunkTerminator();\n    handler->txn_->sendPadding(100);\n    handler->txn_->sendEOM();\n    handler->txn_->sendPadding(100);\n  });\n  handler->expectGoaway();\n  flushRequestsAndLoopN(1);\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  gracefulShutdown();\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  InSequence enforceOrder;\n  EXPECT_CALL(callbacks, onHeadersComplete(_, _));\n  // No chunk header received, since it is not allowed in HTTP/2\n  // EXPECT_CALL(callbacks, onChunkHeader(_, 10));\n  EXPECT_CALL(callbacks, onBody(_, _, 0));\n  // No chunk terminator received, since it is not allowed in HTTP/2\n  // EXPECT_CALL(callbacks, onChunkComplete(_));\n  EXPECT_CALL(callbacks, onMessageComplete(_, _));\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTPDownstreamSessionTest, Connect) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleStrictHandler();\n  // Send HTTP 200 OK to accept the CONNECT request\n  handler->expectHeaders([&handler] { handler->sendHeaders(200, 100); });\n\n  EXPECT_CALL(*handler, _onUpgrade(_));\n\n  // Data should be received using onBody\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"12345\"))\n      .WillOnce(ExpectString(\"abcde\"));\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addReadEvent(\n      \"CONNECT test HTTP/1.1\\r\\n\"\n      \"\\r\\n\"\n      \"12345\",\n      milliseconds(0));\n  transport_->addReadEvent(\"abcde\", milliseconds(5));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, ConnectRejected) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleStrictHandler();\n  // Send HTTP 400 to reject the CONNECT request\n  handler->expectHeaders([&handler] { handler->sendReplyCode(400); });\n\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  transport_->addReadEvent(\n      \"CONNECT test HTTP/1.1\\r\\n\"\n      \"\\r\\n\"\n      \"12345\",\n      milliseconds(0));\n  transport_->addReadEvent(\"abcde\", milliseconds(5));\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpUpgrade) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleStrictHandler();\n  // Send HTTP 101 Switching Protocls to accept the upgrade request\n  handler->expectHeaders([&handler] { handler->sendHeaders(101, 100); });\n\n  // Send the response in the new protocol after upgrade\n  EXPECT_CALL(*handler, _onUpgrade(_))\n      .WillOnce(Invoke([&handler](UpgradeProtocol /*protocol*/) {\n        handler->sendReplyCode(100);\n      }));\n\n  onEOMTerminateHandlerExpectShutdown(*handler);\n\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(HTTP_HEADER_UPGRADE, \"TEST/1.0\");\n  req.getHeaders().add(HTTP_HEADER_CONNECTION, \"upgrade\");\n  sendRequest(req);\n  flushRequestsAndLoop(true, milliseconds(0));\n}\n\nTEST(HTTPDownstreamTest, ParseErrorNoTxn) {\n  // 1) Get a parse error on SYN_STREAM for streamID == 1\n  // 2) Expect that the codec should be asked to generate an abort on\n  //    streamID==1\n  folly::EventBase evb;\n\n  // Setup the controller and its expecations.\n  NiceMock<MockController> mockController;\n\n  // Setup the codec, its callbacks, and its expectations.\n  auto codec = makeDownstreamParallelCodec();\n  HTTPCodec::Callback* codecCallback = nullptr;\n  EXPECT_CALL(*codec, setCallback(_))\n      .WillRepeatedly(SaveArg<0>(&codecCallback));\n  // Expect egress abort for streamID == 1\n  EXPECT_CALL(*codec, generateRstStream(_, 1, _));\n\n  // Setup transport\n  bool transportGood = true;\n  auto transport = newMockTransport(&evb);\n  EXPECT_CALL(*transport, good()).WillRepeatedly(ReturnPointee(&transportGood));\n  EXPECT_CALL(*transport, closeNow())\n      .WillRepeatedly(Assign(&transportGood, false));\n  EXPECT_CALL(*transport, writeChain(_, _, _))\n      .WillRepeatedly(\n          Invoke([&](folly::AsyncTransport::WriteCallback* callback,\n                     const shared_ptr<folly::IOBuf>&,\n                     folly::WriteFlags) { callback->writeSuccess(); }));\n\n  // Create the downstream session, thus initializing codecCallback\n  auto transactionTimeouts = makeInternalTimeoutSet(&evb);\n  auto session =\n      new HTTPDownstreamSession(transactionTimeouts.get(),\n                                folly::AsyncTransport::UniquePtr(transport),\n                                localAddr,\n                                peerAddr,\n                                &mockController,\n                                std::move(codec),\n                                mockTransportInfo,\n                                nullptr);\n  session->startNow();\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, \"foo\");\n  ex.setProxygenError(kErrorParseHeader);\n  ex.setCodecStatusCode(ErrorCode::REFUSED_STREAM);\n  codecCallback->onError(HTTPCodec::StreamID(1), ex, true);\n\n  // cleanup\n  session->dropConnection();\n  evb.loop();\n}\n\nTEST(HTTPDownstreamTest, ByteEventsDrained) {\n  // Test that byte events are drained before socket is closed\n  folly::EventBase evb;\n\n  NiceMock<MockController> mockController;\n  auto codec = makeDownstreamParallelCodec();\n  auto byteEventTracker = new MockByteEventTracker(nullptr);\n  auto transport = newMockTransport(&evb);\n  auto transactionTimeouts = makeInternalTimeoutSet(&evb);\n\n  // Create the downstream session\n  auto session =\n      new HTTPDownstreamSession(transactionTimeouts.get(),\n                                folly::AsyncTransport::UniquePtr(transport),\n                                localAddr,\n                                peerAddr,\n                                &mockController,\n                                std::move(codec),\n                                mockTransportInfo,\n                                nullptr);\n  session->setByteEventTracker(\n      std::unique_ptr<ByteEventTracker>(byteEventTracker));\n\n  InSequence enforceOrder;\n\n  session->startNow();\n\n  // Byte events should be drained first\n  EXPECT_CALL(*byteEventTracker, drainByteEvents()).Times(1);\n  EXPECT_CALL(*transport, closeNow()).Times(AtLeast(1));\n\n  // Close the socket\n  session->dropConnection();\n  evb.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpWithAckTiming) {\n  // This is to test cases where holding a byte event to a finished HTTP/1.1\n  // transaction does not masquerade as HTTP pipelining.\n  auto byteEventTracker = setMockByteEventTracker();\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() {\n    handler1->sendChunkedReplyWithBody(200, 100, 100, false);\n  });\n  // Hold a pending byte event\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _))\n      .WillOnce(Invoke(\n          [](HTTPTransaction* txn, uint64_t /*byteNo*/, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n  sendRequest();\n  flushRequestsAndLoop();\n  expectResponse();\n\n  // Send the secode request after receiving the first response (eg: clearly\n  // not pipelined)\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2]() {\n    handler2->sendChunkedReplyWithBody(200, 100, 100, false);\n  });\n  // This txn processed and destroyed before txn1\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _));\n  handler2->expectDetachTransaction();\n\n  sendRequest();\n  flushRequestsAndLoop();\n  expectResponse();\n\n  // Now clear the pending byte event (simulate ack) and the first txn\n  // goes away too\n  handler1->expectDetachTransaction();\n  handler1->txn_->decrementPendingByteEvents();\n  gracefulShutdown();\n}\n\nTEST_F(HTTPDownstreamSessionTest, TestOnContentMismatch) {\n  // Test the behavior when the reported content-length on the header\n  // is different from the actual length of the body.\n  // The expectation is simply to log the behavior, such as:\n  // \".. HTTPTransaction.cpp ] Content-Length/body mismatch: expected: .. \"\n\n  NiceMock<MockHTTPSessionStats> stats;\n  httpSession_->setSessionStats(&stats);\n  EXPECT_CALL(stats, _recordEgressContentLengthMismatches()).Times(2);\n\n  folly::EventBase base;\n  InSequence enforceOrder;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() {\n    // over-estimate the content-length on the header\n    handler1->sendHeaders(200, 105);\n    handler1->sendBody(100);\n    handler1->txn_->sendEOM();\n  });\n  sendRequest();\n  flushRequestsAndLoop();\n\n  auto handler2 = addSimpleNiceHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2]() {\n    // under-estimate the content-length on the header\n    handler2->sendHeaders(200, 95);\n    handler2->sendBody(100);\n    handler2->txn_->sendEOM();\n  });\n  sendRequest();\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpWithAckTimingPipeline) {\n  // Test a real pipelining case as well.  First request is done waiting for\n  // ack, then receive two pipelined requests.\n  auto byteEventTracker = setMockByteEventTracker();\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() {\n    handler1->sendChunkedReplyWithBody(200, 100, 100, false);\n  });\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _))\n      .WillOnce(Invoke(\n          [](HTTPTransaction* txn, uint64_t /*byteNo*/, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n  sendRequest();\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2]() {\n    handler2->sendChunkedReplyWithBody(200, 100, 100, false);\n  });\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _));\n  handler2->expectDetachTransaction();\n\n  sendRequest();\n  sendRequest();\n  auto handler3 = addSimpleStrictHandler();\n  handler3->expectHeaders();\n  handler3->expectEOM([&handler3]() {\n    handler3->sendChunkedReplyWithBody(200, 100, 100, false);\n  });\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _));\n  handler3->expectDetachTransaction();\n  flushRequestsAndLoop();\n  expectResponses(3);\n  handler1->expectDetachTransaction();\n  handler1->txn_->decrementPendingByteEvents();\n  gracefulShutdown();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpWithAckTimingPipelineError) {\n  auto* errorHandler = new HTTPDirectResponseHandler(400, \"Bad Request\");\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillOnce(Return(errorHandler));\n\n  // Test a real pipelining case as well.  First request is done waiting for\n  // ack, then receive a pipelined request and an error.\n  auto byteEventTracker = setMockByteEventTracker();\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() { handler1->sendReplyWithBody(200, 100); });\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _))\n      .WillOnce(Invoke(\n          [](HTTPTransaction* txn, uint64_t /*byteNo*/, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM();\n\n  // send first request\n  sendRequest();\n  // send a second request too, response for request 1 will arrive\n  sendRequest();\n  flushRequestsAndLoopN(2);\n  expectResponse(200);\n\n  // send a garbage character which will trigger a 400\n  transport_->addReadEvent(\"?\", milliseconds(0));\n  flushRequestsAndLoop();\n\n  // When the byte event is cleared, txn1 will go away\n  handler1->expectDetachTransaction();\n  handler1->txn_->decrementPendingByteEvents();\n  flushRequestsAndLoop();\n\n  // Now send a reply to 2.  Reads will be resumed and we'll get a 400 for the\n  // garbage character\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _));\n  handler2->expectDetachTransaction();\n  handler2->sendReplyWithBody(200, 100);\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop();\n  expectResponse(200, true);\n  expectResponse(400, false, false);\n  expectDetachSession();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpWithAckTimingConnError) {\n  // Send a request, response waits on a byte event\n  // Then send an error.  The session should close when the byte event completes\n  auto* errorHandler = new HTTPDirectResponseHandler(400, \"Bad Request\");\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillOnce(Return(errorHandler));\n\n  auto byteEventTracker = setMockByteEventTracker();\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() { handler1->sendReplyWithBody(200, 100); });\n  EXPECT_CALL(*byteEventTracker, addLastByteEvent(_, _, _))\n      .WillOnce(Invoke(\n          [](HTTPTransaction* txn, uint64_t /*byteNo*/, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n\n  // send first request\n  sendRequest();\n  flushRequestsAndLoopN(2);\n  expectResponse(200);\n\n  // send a garbage character which will trigger a 400\n  transport_->addReadEvent(\"?\", milliseconds(0));\n  flushRequestsAndLoop();\n\n  expectResponse(400, false, false);\n  // When the byte event is cleared, txn1 will go away\n  handler1->expectDetachTransaction();\n  // Add a 1 MB read - it shouldn't be buffered in the session\n  transport_->addMovableReadEvent(makeBuf(1000000), milliseconds(0));\n  eventBase_.loop();\n  expectDetachSession();\n  handler1->txn_->decrementPendingByteEvents();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTPDownstreamSessionTest, ServerStatusHeaderOnError) {\n  // On ingress error, controller will call getParseErrorHandler, instantiating\n  // a DirectResponseHandler. The handler then calls generate() on a\n  // HTTPStaticErrorPage, which will set a Server-Status header to the\n  // corresponding ProxygenError.\n  HTTPStaticErrorPage page{folly::IOBuf::fromString(\"\")};\n  HTTPTransaction::Handler* errorHandler =\n      new HTTPDirectResponseHandler(400, \"Bad Request\", &page);\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillOnce(Return(errorHandler));\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> msg) {\n        EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"Server-Status\"), \"27\");\n      }));\n\n  auto req = getGetRequest(\"/\");                 // construct basic get req\n  req.getHeaders().set(\"Host\", \" \\xfa\\r\\n\\r\\n\"); // set invalid header value in\n                                                 // host field\n  sendRequest(req);                              // send req with eom\n\n  folly::DelayedDestruction::DestructorGuard dg(httpSession_);\n  flushRequests();\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  parseOutput(*clientCodec_); // client parses resp with server-status set to\n                              // kErrorHeaderContentValidation (27)\n  expectDetachSession();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ServerStatusHeaderOnError) {\n  HTTPStaticErrorPage page{folly::IOBuf::fromString(\"\")};\n  HTTPTransaction::Handler* errorHandler =\n      new HTTPDirectResponseHandler(400, \"Bad Request\", &page);\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillOnce(Return(errorHandler));\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  EXPECT_CALL(callbacks, onHeadersComplete(1, _))\n      .WillOnce(Invoke([](HTTPCodec::StreamID,\n                          std::shared_ptr<HTTPMessage> msg) {\n        EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"Server-Status\"), \"27\");\n      }));\n\n  auto req = getGetRequest(\"/\");\n  req.getHeaders().set(\"Host\", \" \\xfa\\r\\n\\r\\n\");\n  sendRequest(req);\n\n  flushRequests();\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  parseOutput(*clientCodec_);\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestPing) {\n  // send a request with a PING, should get the PING first\n  auto handler = addSimpleStrictHandler();\n  sendRequest();\n  auto pingData = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::steady_clock::now().time_since_epoch())\n                      .count();\n  clientCodec_->generatePingRequest(requests_, pingData);\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectGoaway();\n  flushRequestsAndLoopN(1);\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  gracefulShutdown();\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  InSequence enforceOrder;\n  EXPECT_CALL(callbacks, onPingReply(pingData));\n  EXPECT_CALL(callbacks, onHeadersComplete(_, _));\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestPingWithPreSendSplit) {\n  auto byteEventTracker = new NiceMock<MockByteEventTracker>(nullptr);\n  EXPECT_CALL(*byteEventTracker, drainByteEvents()).WillRepeatedly(Return(0));\n  EXPECT_CALL(*byteEventTracker, processByteEvents(_, _))\n      .WillRepeatedly(Invoke([](std::shared_ptr<ByteEventTracker> self,\n                                uint64_t bytesWritten) {\n        return self->ByteEventTracker::processByteEvents(self, bytesWritten);\n      }));\n\n  // send a request with a PING, should get the PING first\n  auto pingData = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::steady_clock::now().time_since_epoch())\n                      .count();\n  auto handler = addSimpleStrictHandler();\n  sendRequest();\n  handler->expectHeaders();\n  handler->expectEOM([this, &handler, byteEventTracker, pingData] {\n    // set the new tracker now, so we don't get invoked when sending SETTINGS\n    httpSession_->setByteEventTracker(\n        std::unique_ptr<ByteEventTracker>(byteEventTracker));\n    // Pause writes so only the first write goes through and the remainder\n    // gets buffered in writeBuf_\n    transport_->pauseWrites();\n    handler->sendReplyWithBody(200, 100);\n    eventBase_.runInLoop([this, pingData] {\n      folly::IOBufQueue pingBuf{folly::IOBufQueue::cacheChainLength()};\n      clientCodec_->generatePingRequest(pingBuf, pingData);\n      transport_->addReadEvent(pingBuf, milliseconds(0));\n      transport_->resumeWrites();\n    });\n  });\n  // Split the write buffer on a non-frame boundary the first time\n  EXPECT_CALL(*byteEventTracker, preSend(_, _, _, _))\n      .WillOnce(Return(1))\n      .WillRepeatedly(Return(0));\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(2);\n  HTTPSession::DestructorGuard g(httpSession_);\n  gracefulShutdown();\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  InSequence enforceOrder;\n  EXPECT_CALL(callbacks, onHeadersComplete(_, _));\n  EXPECT_CALL(callbacks, onPingReply(pingData));\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, SetByteEventTracker) {\n  // Send two requests with writes paused, which will queue several byte events,\n  // including last byte events which are holding a reference to the\n  // transaction.\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1]() { handler1->sendReplyWithBody(200, 100); });\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2, this]() {\n    handler2->sendReplyWithBody(200, 100);\n    transport_->pauseWrites();\n  });\n\n  sendRequest();\n  sendRequest();\n  // Resume writes from the loop callback\n  eventBase_.runInLoop([this] { transport_->resumeWrites(); });\n\n  // Graceful shutdown will notify of GOAWAY\n  EXPECT_CALL(*handler1, _onGoaway(ErrorCode::NO_ERROR));\n  EXPECT_CALL(*handler2, _onGoaway(ErrorCode::NO_ERROR));\n  // The original byteEventTracker will process the last byte event of the\n  // first transaction, and detach by deleting the event.  Swap out the tracker.\n  handler1->expectDetachTransaction([this] {\n    auto tracker = std::make_unique<ByteEventTracker>(httpSession_);\n    httpSession_->setByteEventTracker(std::move(tracker));\n  });\n  // handler2 should also be detached immediately because the new\n  // ByteEventTracker continues procesing where the old one left off.\n  handler2->expectDetachTransaction();\n  gracefulShutdown();\n}\n\n/**\n * Few tests cases here:\n *     1. Invoking ::sendAbort(NO_ERROR) on a transaction after eom has been\n * flushed will flush a RST_STREAM/NO_ERROR frame (no need to defer anything in\n * this specific case)\n *\n *     2. Invoking ::sendAbort(NO_ERROR) on a transaction after eom has been\n * queued, but not flushed, will defer a RST_STREAM/NO_ERROR frame until after\n * eom has been written to the underlying transport\n *\n *     3. Invoking ::sendAbort(NO_ERROR) on a transaction that has not queued an\n * eom will be translated to ErrorCode::CANCEL\n */\nTEST_F(HTTP2DownstreamSessionTest, SendNoErrorAfterEomFlush) {\n  // test case #1 where eom is flushed immediately\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/0);\n    handler->txn_->sendEOM();\n    handler->txn_->sendAbort(ErrorCode::NO_ERROR);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  sendRequest(getPostRequest(), /*eom=*/false);\n  handler->expectDetachTransaction();\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  EXPECT_CALL(callbacks_, onAbort(_, ErrorCode::NO_ERROR));\n  expectResponse(/*code=*/200,\n                 /*errorCode=*/ErrorCode::NO_ERROR,\n                 /*expect100=*/false,\n                 /*expectGoaway=*/false,\n                 /*expectBody=*/false);\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, SendDeferredNoError) {\n  // test case #2 where fin flag is set on data frame\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/200);\n    txn->pauseIngress();\n    handler->sendBody(100);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  sendRequest(getPostRequest(), /*eom=*/false);\n\n  // should not detach transaction yet since there is a pending eom and abort\n  // will not be invoked until the resp eom is flushed\n  EXPECT_CALL(*handler, _detachTransaction).Times(0);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  // resuming ingress and sending the rest of body & eom should detach the\n  // transaction\n  handler->expectDetachTransaction();\n  txn->sendBody(makeBuf(100));\n  txn->sendEOM();\n  txn->sendAbort(ErrorCode::NO_ERROR);\n  txn->resumeEgress(); // unnecessary since pending eom wil resume egress\n                       // anyways\n  evbLoopNonBlockN(2);\n\n  // since we send two body chunks, we add one additional expectation\n  EXPECT_CALL(callbacks_, onBody(_, _, _)).Times(1);\n  EXPECT_CALL(callbacks_, onAbort(_, ErrorCode::NO_ERROR));\n  expectResponse(/*code=*/200, /*errorCode=*/ErrorCode::NO_ERROR);\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, InvalidNoErrorAbort) {\n  // test case #3 where NO_ERROR is transformed into CANCEL if there is no\n  // pending eom\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  handler->expectHeaders([&]() {\n    handler->sendHeaders(/*code=*/200, /*content_length=*/200);\n    handler->sendBody(100);\n    txn->sendAbort(ErrorCode::NO_ERROR);\n  });\n\n  // withold client's eom to verify that the transaction is detached, as abort\n  // will mark ingress and egress complete and destroy the transaction\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  sendRequest(getPostRequest(), /*eom=*/false);\n\n  // this should detach transaction since it is immediately aborted\n  EXPECT_CALL(*handler, _detachTransaction);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  EXPECT_CALL(callbacks_, onHeadersComplete(_, _));\n  EXPECT_CALL(callbacks_, onAbort(_, ErrorCode::INTERNAL_ERROR));\n\n  parseOutput(*clientCodec_);\n  gracefulShutdown();\n}\n\nTEST_F(HTTPDownstreamSessionTest, TestTrackedByteEventTracker) {\n  auto byteEventTracker = setMockByteEventTracker();\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  size_t bytesToSend = 200;\n  size_t expectedTrackedByteOffset = bytesToSend + 99;\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, &bytesToSend]() {\n    handler1->sendHeaders(200, 200);\n    handler1->sendBodyWithLastByteFlushedTracking(bytesToSend);\n    handler1->txn_->sendEOM();\n  });\n\n  EXPECT_CALL(*byteEventTracker,\n              addTrackedByteEvent(_, expectedTrackedByteOffset, _))\n      .WillOnce(Invoke(\n          [](HTTPTransaction* txn, uint64_t /*byteNo*/, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n  sendRequest();\n  flushRequestsAndLoop();\n  handler1->expectDetachTransaction();\n  handler1->txn_->decrementPendingByteEvents();\n  gracefulShutdown();\n}\n\nstruct TestOnTxnByteEventWrittenToBufParams {\n  uint64_t byteOffset{0};\n  ByteEvent::EventType eventType;\n  bool timestampTx{false};\n  bool timestampAck{false};\n};\n\nclass OnTxnByteEventWrittenToBufTest\n    : public HTTPDownstreamSessionTest\n    , public ::testing::WithParamInterface<\n          TestOnTxnByteEventWrittenToBufParams> {\n public:\n  static std::vector<TestOnTxnByteEventWrittenToBufParams> getTestingValues() {\n    std::vector<TestOnTxnByteEventWrittenToBufParams> vals;\n    for (const auto& byteOffset : {0, 1, 2}) {\n      for (const auto& eventType : {ByteEvent::EventType::FIRST_BYTE,\n                                    ByteEvent::EventType::LAST_BYTE,\n                                    ByteEvent::EventType::TRACKED_BYTE}) {\n        for (const auto& timestampTx : {true, false}) {\n          for (const auto& timestampAck : {true, false}) {\n            TestOnTxnByteEventWrittenToBufParams params;\n            params.byteOffset = byteOffset;\n            params.eventType = eventType;\n            params.timestampTx = timestampTx;\n            params.timestampAck = timestampAck;\n            vals.push_back(params);\n          }\n        }\n      }\n    }\n    return vals;\n  }\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    HTTPDownstreamSessionTest,\n    OnTxnByteEventWrittenToBufTest,\n    ::testing::ValuesIn(OnTxnByteEventWrittenToBufTest::getTestingValues()));\n\nTEST_P(OnTxnByteEventWrittenToBufTest, TestOnTxnByteEventWrittenToBuf) {\n  HTTP2PriorityQueue txnEgressQueue;\n  NiceMock<MockHTTPTransactionTransport> transport;\n  HTTPTransaction txn{TransportDirection::DOWNSTREAM,\n                      HTTPCodec::StreamID(1),\n                      1,\n                      transport,\n                      txnEgressQueue};\n\n  const auto byteEventTracker = setMockByteEventTracker();\n  const auto params = GetParam();\n  InSequence enforceOrder;\n\n  transport_->setEorTracking(true);\n  transport_->setAppBytesWritten(params.byteOffset);\n  transport_->setRawBytesWritten(params.byteOffset);\n  if (params.timestampTx) {\n    EXPECT_CALL(*byteEventTracker,\n                addTxByteEvent(params.byteOffset, params.eventType, &txn, _))\n        .WillOnce(Invoke([&](uint64_t /*offset*/,\n                             ByteEvent::EventType /*eventType*/,\n                             HTTPTransaction* /*txn*/,\n                             ByteEvent::Callback) {\n          // do nothing\n        }));\n  }\n  if (params.timestampAck) {\n    EXPECT_CALL(*byteEventTracker,\n                addAckByteEvent(params.byteOffset, params.eventType, &txn, _))\n        .WillOnce(Invoke([&](uint64_t /*offset*/,\n                             ByteEvent::EventType /*eventType*/,\n                             HTTPTransaction* /*txn*/,\n                             ByteEvent::Callback) {\n          // do nothing\n        }));\n  }\n\n  const auto byteEvent = make_shared<TransactionByteEvent>(\n      params.byteOffset, params.eventType, &txn);\n  byteEvent->timestampTx_ = params.timestampTx;\n  byteEvent->timestampAck_ = params.timestampAck;\n  byteEventTracker->onTxnByteEventWrittenToBuf(*byteEvent);\n\n  cleanup();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Trailers) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler]() {\n    handler->sendReplyWithBody(\n        200, 100, true /* keepalive */, true /* sendEOM */, true /*trailers*/);\n  });\n  handler->expectDetachTransaction();\n\n  HTTPSession::DestructorGuard g(httpSession_);\n  sendRequest();\n  flushRequestsAndLoop(true, milliseconds(0));\n\n  EXPECT_CALL(callbacks_, onMessageBegin(1, _)).Times(1);\n  EXPECT_CALL(callbacks_, onHeadersComplete(1, _)).Times(1);\n  EXPECT_CALL(callbacks_, onBody(1, _, _));\n  EXPECT_CALL(callbacks_, onTrailersComplete(1, _));\n  EXPECT_CALL(callbacks_, onMessageComplete(1, _));\n\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n}\n\nTEST_F(HTTPDownstreamSessionTest, Trailers) {\n  testChunks(true);\n}\n\nTEST_F(HTTPDownstreamSessionTest, ExplicitChunks) {\n  testChunks(false);\n}\n\ntemplate <class C>\nvoid HTTPDownstreamTest<C>::testChunks(bool trailers) {\n  InSequence enforceOrder;\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler, trailers]() {\n    handler->sendChunkedReplyWithBody(200, 100, 17, trailers);\n  });\n  handler->expectDetachTransaction();\n\n  HTTPSession::DestructorGuard g(httpSession_);\n  sendRequest();\n  flushRequestsAndLoop(true, milliseconds(0));\n\n  EXPECT_CALL(callbacks_, onMessageBegin(1, _)).Times(1);\n  EXPECT_CALL(callbacks_, onHeadersComplete(1, _)).Times(1);\n  for (int i = 0; i < 6; i++) {\n    EXPECT_CALL(callbacks_, onChunkHeader(1, _));\n    EXPECT_CALL(callbacks_, onBody(1, _, _));\n    EXPECT_CALL(callbacks_, onChunkComplete(1));\n  }\n  if (trailers) {\n    EXPECT_CALL(callbacks_, onTrailersComplete(1, _));\n  }\n  EXPECT_CALL(callbacks_, onMessageComplete(1, _));\n\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpDrain) {\n  InSequence enforceOrder;\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders([this, &handler1] {\n    handler1->sendHeaders(200, 100);\n    httpSession_->notifyPendingShutdown();\n  });\n  handler1->expectEOM([&handler1] {\n    handler1->sendBody(100);\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectDetachTransaction();\n\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders([&handler2] { handler2->sendHeaders(200, 100); });\n  handler2->expectEOM([&handler2] {\n    handler2->sendBody(100);\n    handler2->txn_->sendEOM();\n  });\n  handler2->expectDetachTransaction();\n\n  expectDetachSession();\n\n  sendRequest();\n  sendRequest();\n  flushRequestsAndLoop();\n}\n\n// 1) receive full request\n// 2) notify pending shutdown\n// 3) wait for session read timeout -> should be ignored\n// 4) response completed\nTEST_F(HTTPDownstreamSessionTest, HttpDrainLongRunning) {\n  EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n      .WillRepeatedly(Return(std::chrono::milliseconds(100)));\n  InSequence enforceSequence;\n\n  auto connMan =\n      wangle::ConnectionManager::makeUnique(&eventBase_, milliseconds(125));\n  connMan->addConnection(httpSession_);\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([this, &handler] {\n    httpSession_->notifyPendingShutdown();\n    eventBase_.tryRunAfterDelay(\n        [&handler] { handler->sendReplyWithBody(200, 100); }, 200);\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  expectDetachSession();\n\n  sendRequest();\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, EarlyAbort) {\n  StrictMock<MockHTTPHandler> handler;\n\n  InSequence enforceOrder;\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n\n  EXPECT_CALL(handler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) {\n        handler.txn_ = txn;\n        handler.txn_->sendAbort();\n      }));\n  handler.expectDetachTransaction();\n  expectDetachSession();\n\n  addSingleByteReads(\n      \"GET /somepath.php?param=foo HTTP/1.1\\r\\n\"\n      \"Host: example.com\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"\\r\\n\");\n  transport_->addReadEOF(milliseconds(0));\n  transport_->startReadEvents();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpWritesDrainingTimeout) {\n  sendRequest();\n  sendHeader();\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, this] {\n    transport_->pauseWrites();\n    handler1->sendHeaders(200, 1000);\n  });\n  handler1->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                     handler1->txn_->getID()),\n              std::string(ex.what()));\n    handler1->txn_->sendAbort();\n  });\n  handler1->expectDetachTransaction();\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpRateLimitNormal) {\n  // The rate-limiting code grabs the event base from the EventBaseManager,\n  // so we need to set it.\n  folly::EventBaseManager::get()->setEventBase(&eventBase_, false);\n\n  // Create a request\n  sendRequest();\n\n  InSequence handlerSequence;\n\n  // Set a low rate-limit on the transaction\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders([&] {\n    uint32_t rateLimit_kbps = 640;\n    handler1->txn_->setEgressRateLimit(rateLimit_kbps * 1024);\n  });\n  // Send a somewhat big response that we know will get rate-limited\n  handler1->expectEOM([&handler1] {\n    // At 640kbps, this should take slightly over 800ms\n    uint32_t rspLengthBytes = 100000;\n    handler1->sendHeaders(200, rspLengthBytes);\n    handler1->sendBody(rspLengthBytes);\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectDetachTransaction();\n\n  // Keep the session around even after the event base loop completes so we can\n  // read the counters on a valid object.\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop();\n\n  proxygen::TimePoint timeFirstWrite =\n      transport_->getWriteEvents()->front()->getTime();\n  proxygen::TimePoint timeLastWrite =\n      transport_->getWriteEvents()->back()->getTime();\n  int64_t writeDuration =\n      (int64_t)millisecondsBetween(timeLastWrite, timeFirstWrite).count();\n  EXPECT_GE(writeDuration, 800);\n\n  cleanup();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, RateLimitNormal) {\n  // The rate-limiting code grabs the event base from the EventBaseManager,\n  // so we need to set it.\n  folly::EventBaseManager::get()->setEventBase(&eventBase_, false);\n\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                                100000);\n  clientCodec_->generateSettings(requests_);\n  clientCodec_->generateWindowUpdate(requests_, 0, 10000);\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders([&] {\n    uint32_t rateLimit_kbps = 640;\n    handler1->txn_->setEgressRateLimit(rateLimit_kbps * 1024);\n  });\n\n  handler1->expectEOM([&handler1] {\n    // At 640kbps, this should take slightly over 800ms\n    uint32_t rspLengthBytes = 100000;\n    handler1->sendHeaders(200, rspLengthBytes);\n    handler1->sendBody(rspLengthBytes);\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectDetachTransaction();\n\n  // Keep the session around even after the event base loop completes so we can\n  // read the counters on a valid object.\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop(true, milliseconds(50));\n\n  proxygen::TimePoint timeFirstWrite =\n      transport_->getWriteEvents()->front()->getTime();\n  proxygen::TimePoint timeLastWrite =\n      transport_->getWriteEvents()->back()->getTime();\n  int64_t writeDuration =\n      (int64_t)millisecondsBetween(timeLastWrite, timeFirstWrite).count();\n  EXPECT_GE(writeDuration, 800);\n  expectDetachSession();\n}\n\n/**\n * This test will reset the connection while the server is waiting around\n * to send more bytes (so as to keep under the rate limit).\n */\nTEST_F(HTTP2DownstreamSessionTest, RateLimitRst) {\n  // The rate-limiting code grabs the event base from the EventBaseManager,\n  // so we need to set it.\n  folly::EventBaseManager::get()->setEventBase(&eventBase_, false);\n\n  folly::IOBufQueue rst{folly::IOBufQueue::cacheChainLength()};\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                                100000);\n  clientCodec_->generateSettings(requests_);\n  auto streamID = sendRequest();\n  clientCodec_->generateRstStream(rst, streamID, ErrorCode::CANCEL);\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders([&] {\n    uint32_t rateLimit_kbps = 640;\n    handler1->txn_->setEgressRateLimit(rateLimit_kbps * 1024);\n  });\n  handler1->expectEOM([&handler1] {\n    uint32_t rspLengthBytes = 100000;\n    handler1->sendHeaders(200, rspLengthBytes);\n    handler1->sendBody(rspLengthBytes);\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectError();\n  handler1->expectDetachTransaction();\n  expectDetachSession();\n\n  flushRequestsAndLoop(true, milliseconds(50), milliseconds(0), [&] {\n    transport_->addReadEvent(rst, milliseconds(10));\n  });\n}\n\n// Send a 1.0 request, egress the EOM with the last body chunk on a paused\n// socket, and let it timeout.  dropConnection()\n// to removeTransaction with writesDraining_=true\nTEST_F(HTTPDownstreamSessionTest, WriteTimeout) {\n  HTTPMessage req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  sendRequest(req);\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, this] {\n    handler1->sendHeaders(200, 100);\n    eventBase_.tryRunAfterDelay(\n        [&handler1, this] {\n          transport_->pauseWrites();\n          handler1->sendBody(100);\n          handler1->txn_->sendEOM();\n        },\n        50);\n  });\n  handler1->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                     handler1->txn_->getID()),\n              std::string(ex.what()));\n  });\n  handler1->expectDetachTransaction();\n\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\n// Send an abort from the write timeout path while pipelining\nTEST_F(HTTPDownstreamSessionTest, WriteTimeoutPipeline) {\n  const char* buf =\n      \"GET / HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n\"\n      \"GET / HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n\";\n  requests_.append(buf, strlen(buf));\n\n  auto handler2 = addSimpleNiceHandler();\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, this] {\n    handler1->sendHeaders(200, 100);\n    eventBase_.runInLoop([&handler1, this] {\n      transport_->pauseWrites();\n      handler1->sendBody(100);\n      handler1->txn_->sendEOM();\n    });\n  });\n  handler2->expectHeaders();\n  handler2->expectEOM();\n  handler2->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                     handler2->txn_->getID()),\n              std::string(ex.what()));\n    handler2->txn_->sendAbort();\n  });\n  handler2->expectDetachTransaction();\n  handler1->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                     handler1->txn_->getID()),\n              std::string(ex.what()));\n    handler1->txn_->sendAbort();\n  });\n  handler1->expectDetachTransaction();\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTPDownstreamSessionTest, BodyPacketization) {\n  HTTPMessage req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  req.setWantsKeepalive(false);\n  sendRequest(req);\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1] { handler1->sendReplyWithBody(200, 32768); });\n  handler1->expectDetachTransaction();\n\n  expectDetachSession();\n\n  // Keep the session around even after the event base loop completes so we can\n  // read the counters on a valid object.\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop();\n\n  EXPECT_EQ(transport_->getWriteEvents()->size(), 1);\n}\n\nTEST_F(HTTPDownstreamSessionTest, HttpMalformedPkt1) {\n  // Create a HTTP connection and keep sending just '\\n' to the HTTP1xCodec.\n  std::string data(90000, '\\n');\n  requests_.append(data.data(), data.length());\n\n  expectDetachSession();\n\n  flushRequestsAndLoop(true, milliseconds(0));\n}\n\nTEST_F(HTTPDownstreamSessionTest, BigExplcitChunkWrite) {\n  // even when the handler does a massive write, the transport only gets small\n  // writes\n  sendRequest();\n\n  auto handler = addSimpleNiceHandler();\n  handler->expectHeaders([&handler] {\n    handler->sendHeaders(200, 100, false);\n    size_t len = 16 * 1024 * 1024;\n    handler->txn_->sendChunkHeader(len);\n    auto chunk = makeBuf(len);\n    handler->txn_->sendBody(std::move(chunk));\n    handler->txn_->sendChunkTerminator();\n    handler->txn_->sendEOM();\n  });\n  handler->expectDetachTransaction();\n\n  expectDetachSession();\n\n  // Keep the session around even after the event base loop completes so we can\n  // read the counters on a valid object.\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop();\n\n  EXPECT_GT(transport_->getWriteEvents()->size(), 250);\n}\n\n// ==== upgrade tests ====\n\n// Test upgrade to a protocol unknown to HTTPSession\nTEST_F(HTTPDownstreamSessionTest, HttpUpgradeNonNative) {\n  auto handler = addSimpleStrictHandler();\n\n  handler->expectHeaders([&handler, this] {\n    // TODO: there is serious weirdness in this case.  The HTTP parser must\n    // be paused from this point until the response headers are sent.  If we\n    // delay the 101 headers by 1 loop, the case breaks without the\n    // pause/resume\n    handler->txn_->pauseIngress();\n    eventBase_.runInLoop([&handler] {\n      handler->sendHeaders(101, 0, true, {{\"Upgrade\", \"blarf\"}});\n      handler->txn_->resumeIngress();\n    });\n  });\n  EXPECT_CALL(*handler, _onUpgrade(UpgradeProtocol::TCP));\n\n  sendRequest(getUpgradeRequest(\"blarf\"), /*eom=*/false);\n  // Enough to receive the request, wait a loop, send the response\n  flushRequestsAndLoopN(3);\n\n  // Now send random blarf data\n  handler->expectBody([&handler] {\n    handler->txn_->sendBody(makeBuf(100));\n    handler->txn_->sendEOM();\n  });\n\n  folly::IOBufQueue bq{folly::IOBufQueue::cacheChainLength()};\n  bq.append(makeBuf(100));\n  transport_->addReadEvent(bq, milliseconds(0));\n\n  // The server sent EOM, which means transport writes should be closed now\n  flushRequestsAndLoopN(2);\n  EXPECT_FALSE(transport_->good());\n\n  // Add EOF from the client to complete the teardown\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  expectDetachSession();\n  transport_->addReadEOF(milliseconds(0));\n  eventBase_.loop();\n}\n\n// Test upgrade to a protocol unknown to HTTPSession, but don't switch\n// protocols\nTEST_F(HTTPDownstreamSessionTest, HttpUpgradeNonNativeIgnore) {\n  auto handler = addSimpleStrictHandler();\n\n  handler->expectHeaders([&handler] { handler->sendReplyWithBody(200, 100); });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  sendRequest(getUpgradeRequest(\"blarf\"));\n\n  expectDetachSession();\n  flushRequestsAndLoop(true);\n}\n\n// Test upgrade to a protocol unknown to HTTPSession\nTEST_F(HTTPDownstreamSessionTest, HttpUpgradeNonNativePipeline) {\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders([&handler1](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE), \"blarf\");\n    handler1->sendReplyWithBody(200, 100);\n  });\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders(\n      [&handler2] { handler2->sendReplyWithBody(200, 100); });\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n\n  sendRequest(getUpgradeRequest(\"blarf\"));\n  transport_->addReadEvent(\n      \"GET / HTTP/1.1\\r\\n\"\n      \"\\r\\n\");\n  expectDetachSession();\n  flushRequestsAndLoop(true);\n}\n\ntemplate <class C>\nvoid HTTPDownstreamTest<C>::testPriorities(uint32_t numPriorities) {\n  uint32_t iterations = 10;\n  uint32_t maxPriority = numPriorities - 1;\n  std::vector<std::unique_ptr<testing::NiceMock<MockHTTPHandler>>> handlers;\n  for (int pri = numPriorities - 1; pri >= 0; pri--) {\n    for (uint32_t i = 0; i < iterations; i++) {\n      sendRequest(\"/\", pri * (8 / numPriorities));\n      InSequence handlerSequence;\n      auto handler = addSimpleNiceHandler();\n      auto rawHandler = handler.get();\n      handlers.push_back(std::move(handler));\n      rawHandler->expectHeaders();\n      rawHandler->expectEOM(\n          [rawHandler] { rawHandler->sendReplyWithBody(200, 1000); });\n      rawHandler->expectDetachTransaction([] {});\n    }\n  }\n\n  auto buf = requests_.move();\n  buf->coalesce();\n  requests_.append(std::move(buf));\n\n  flushRequestsAndLoop();\n\n  std::list<HTTPCodec::StreamID> streams;\n  EXPECT_CALL(callbacks_, onMessageBegin(_, _))\n      .Times(iterations * numPriorities);\n  EXPECT_CALL(callbacks_, onHeadersComplete(_, _))\n      .Times(iterations * numPriorities);\n  // body is variable and hence ignored\n  EXPECT_CALL(callbacks_, onMessageComplete(_, _))\n      .Times(iterations * numPriorities)\n      .WillRepeatedly(Invoke([&](HTTPCodec::StreamID stream, bool /*upgrade*/) {\n        streams.push_back(stream);\n      }));\n\n  parseOutput(*clientCodec_);\n\n  // transactions finish in priority order (higher streamIDs first)\n  EXPECT_EQ(streams.size(), iterations * numPriorities);\n  auto txn = streams.begin();\n  for (int band = maxPriority; band >= 0; band--) {\n    auto upperID = iterations * 2 * (band + 1);\n    auto lowerID = iterations * 2 * band;\n    for (uint32_t i = 0; i < iterations; i++) {\n      EXPECT_LE(lowerID, (uint32_t)*txn);\n      EXPECT_GE(upperID, (uint32_t)*txn);\n      ++txn;\n    }\n  }\n}\n\n// Verifies that the read timeout is not running when no ingress is expected/\n// required to proceed\nTEST_F(HTTP2DownstreamSessionTest, H2Timeout) {\n  sendRequest();\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&] {\n    handler1->sendHeaders(200, 1000);\n    handler1->sendBody(1000);\n    eventBase_.runAfterDelay([&handler1] { handler1->txn_->sendEOM(); }, 600);\n  });\n  handler1->expectDetachTransaction();\n\n  flushRequestsAndLoop(false, milliseconds(0), milliseconds(10));\n\n  cleanup();\n}\n\n// Verifies that the read timer is running while a transaction is blocked\n// on a window update\nTEST_F(HTTP2DownstreamSessionTest, H2TimeoutWin) {\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                                500);\n  clientCodec_->generateSettings(requests_);\n  auto streamID = sendRequest();\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&] { handler->sendReplyWithBody(200, 1000); });\n  handler->expectEgressPaused();\n  handler->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\n                  \"ingress timeout, streamID=\",\n                  streamID,\n                  \", timeout=\",\n                  transactionTimeouts_->getDefaultTimeout().count(),\n                  \"ms\"),\n              std::string(ex.what()));\n    handler->terminate();\n  });\n  handler->expectDetachTransaction();\n\n  flushRequestsAndLoop();\n\n  cleanup();\n}\n\nTYPED_TEST_SUITE_P(HTTPDownstreamTest);\n\nTYPED_TEST_P(HTTPDownstreamTest, TestMaxTxnOverriding) {\n  this->httpSession_->setEgressSettings(\n      {{SettingsId::MAX_CONCURRENT_STREAMS, 1}});\n\n  auto handler = this->addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n\n  this->sendRequest();\n  // This one is over the limit\n  auto streamId = this->sendRequest();\n\n  this->flushRequestsAndLoop();\n\n  EXPECT_CALL(this->callbacks_, onSettings(_));\n  EXPECT_CALL(this->callbacks_, onAbort(streamId, ErrorCode::REFUSED_STREAM));\n\n  this->parseOutput(*this->clientCodec_);\n  handler->sendReplyWithBody(200, 100);\n  handler->expectDetachTransaction();\n\n  this->flushRequestsAndLoop();\n  this->cleanup();\n}\n\nTYPED_TEST_P(HTTPDownstreamTest, TestWritesDraining) {\n  auto badCodec =\n      makeServerCodec<typename TypeParam::Codec>(TypeParam::version);\n  this->sendRequest();\n  badCodec->generatePushPromise(\n      this->requests_, 2 /* bad */, getGetRequest(), 1);\n\n  this->expectDetachSession();\n\n  InSequence handlerSequence;\n  auto handler1 = this->addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM();\n  handler1->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorMalformedInput);\n\n    ASSERT_TRUE(folly::StringPiece(ex.what()).startsWith(\n        \"Shutdown transport: MalformedInput\"))\n        << ex.what();\n  });\n  handler1->expectDetachTransaction();\n\n  this->flushRequestsAndLoop();\n}\n\nTYPED_TEST_P(HTTPDownstreamTest, TestBodySizeLimit) {\n  this->clientCodec_->generateWindowUpdate(this->requests_, 0, 65536);\n  this->sendRequest();\n  this->sendRequest();\n\n  InSequence handlerSequence;\n  auto handler1 = this->addSimpleNiceHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM();\n  auto handler2 = this->addSimpleNiceHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&] {\n    handler1->sendReplyWithBody(200, 33000);\n    handler2->sendReplyWithBody(200, 33000);\n  });\n  handler1->expectDetachTransaction();\n  handler2->expectDetachTransaction();\n\n  this->flushRequestsAndLoop();\n\n  std::list<HTTPCodec::StreamID> streams;\n  EXPECT_CALL(this->callbacks_, onMessageBegin(1, _));\n  EXPECT_CALL(this->callbacks_, onHeadersComplete(1, _));\n  EXPECT_CALL(this->callbacks_, onMessageBegin(3, _));\n  EXPECT_CALL(this->callbacks_, onHeadersComplete(3, _));\n  for (uint32_t i = 0; i < 8; i++) {\n    EXPECT_CALL(this->callbacks_, onBody(1, _, _));\n    EXPECT_CALL(this->callbacks_, onBody(3, _, _));\n  }\n  EXPECT_CALL(this->callbacks_, onBody(1, _, _));\n  EXPECT_CALL(this->callbacks_, onMessageComplete(1, _));\n  EXPECT_CALL(this->callbacks_, onBody(3, _, _));\n  EXPECT_CALL(this->callbacks_, onMessageComplete(3, _));\n\n  this->parseOutput(*this->clientCodec_);\n\n  this->cleanup();\n}\n\n#define IF_HTTP2(X)                                                 \\\n  if (this->clientCodec_->getProtocol() == CodecProtocol::HTTP_2) { \\\n    X;                                                              \\\n  }\n\n// Test exceeding the MAX_CONCURRENT_STREAMS setting.  The txn should get\n// REFUSED_STREAM, and other streams can complete normally\nTYPED_TEST_P(HTTPDownstreamTest, TestMaxTxns) {\n  auto settings = this->rawCodec_->getEgressSettings();\n  auto maxTxns = settings->getSetting(SettingsId::MAX_CONCURRENT_STREAMS, 100);\n  std::list<unique_ptr<StrictMock<MockHTTPHandler>>> handlers;\n  {\n    for (auto i = 0U; i < maxTxns; i++) {\n      this->sendRequest();\n      auto handler = this->addSimpleStrictHandler();\n      handler->expectHeaders();\n      handler->expectEOM();\n      handlers.push_back(std::move(handler));\n    }\n    auto streamID = this->sendRequest();\n    this->clientCodec_->generateGoaway(this->requests_, 0, ErrorCode::NO_ERROR);\n\n    for (auto& handler : handlers) {\n      EXPECT_CALL(*handler, _onGoaway(ErrorCode::NO_ERROR));\n    }\n\n    this->flushRequestsAndLoop();\n\n    EXPECT_CALL(this->callbacks_, onSettings(_));\n    EXPECT_CALL(this->callbacks_, onAbort(streamID, ErrorCode::REFUSED_STREAM));\n\n    this->parseOutput(*this->clientCodec_);\n  }\n  // handlers can finish out of order?\n  for (auto& handler : handlers) {\n    handler->sendReplyWithBody(200, 100);\n    handler->expectDetachTransaction();\n  }\n  this->expectDetachSession();\n  this->eventBase_.loop();\n  // how did the session detach before?\n  this->httpSession_->timeoutExpired();\n}\n\n// Set max streams=1\n// send two h2 requests a few ms apart.\n// Block writes\n// generate a complete response for txn=1 before parsing txn=3\n// HTTPSession should allow the txn=3 to be served rather than refusing it\nTEST_F(HTTP2DownstreamSessionTest, H2MaxConcurrentStreams) {\n  HTTPMessage req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  req.setWantsKeepalive(false);\n  sendRequest(req);\n  auto req2p = sendRequestLater(req, true);\n\n  httpSession_->setEgressSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 1}});\n\n  InSequence handlerSequence;\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1, req, this, &req2p] {\n    transport_->pauseWrites();\n    handler1->sendReplyWithBody(200, 100);\n    req2p.setValue();\n  });\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([this] { resumeWritesInLoop(); });\n  handler1->expectDetachTransaction(\n      [&handler2] { handler2->sendReplyWithBody(200, 100); });\n  handler2->expectDetachTransaction();\n\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, SlowDrainWithoutFIN) {\n  auto connMan =\n      wangle::ConnectionManager::makeUnique(&eventBase_, milliseconds(500));\n  connMan->addConnection(httpSession_);\n  httpSession_->enableDoubleGoawayDrain();\n  EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n      .WillRepeatedly(Return(std::chrono::milliseconds(100)));\n  HTTPMessage req = getGetRequest();\n  req.setHTTPVersion(1, 1);\n  req.setWantsKeepalive(true);\n  auto id = sendRequest(req);\n  // open session flow control window so it's not in the way\n  clientCodec_->generateWindowUpdate(requests_, 0, 10000);\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&] {\n    // Send first goaway, schedule timeout for second goaway\n    httpSession_->notifyPendingShutdown();\n  });\n  eventBase_.runAfterDelay(\n      [&] {\n        // second GOAWAY has been sent\n        // send a response that exceeds the flow control window\n        handler->expectEgressPaused();\n        handler->sendReplyWithBody(200, 70000);\n        eventBase_.runAfterDelay(\n            [&] {\n              // The timeout should have fired but the transaction is still\n              // there, timeout gets reset open stream flow control window to\n              // allow response to finish\n              handler->expectDetachTransaction();\n              clientCodec_->generateWindowUpdate(requests_, id, 10000);\n              transport_->addReadEvent(requests_, milliseconds(0));\n              EXPECT_CALL(mockController_, detachSession(testing::_));\n            },\n            150);\n      },\n      600);\n\n  // should timeout FIN\n  flushRequestsAndLoop();\n}\n\nREGISTER_TYPED_TEST_SUITE_P(HTTPDownstreamTest,\n                            TestWritesDraining,\n                            TestBodySizeLimit,\n                            TestMaxTxns,\n                            TestMaxTxnOverriding);\n\nusing ParallelCodecs = ::testing::Types<HTTP2CodecPair>;\nINSTANTIATE_TYPED_TEST_SUITE_P(ParallelCodecs,\n                               HTTPDownstreamTest,\n                               ParallelCodecs);\n\nclass HTTP2DownstreamSessionFCTest : public HTTPDownstreamTest<HTTP2CodecPair> {\n public:\n  HTTP2DownstreamSessionFCTest()\n      : HTTPDownstreamTest<HTTP2CodecPair>(\n            {-1, -1, 2 * http2::kInitialWindow}) {\n  }\n};\n\nTEST_F(HTTP2DownstreamSessionFCTest, TestSessionFlowControl) {\n  eventBase_.loopOnce();\n\n  InSequence sequence;\n  EXPECT_CALL(callbacks_, onSettings(_));\n  EXPECT_CALL(callbacks_, onWindowUpdate(0, http2::kInitialWindow));\n  parseOutput(*clientCodec_);\n\n  cleanup();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestEOFOnBlockedStream) {\n  sendRequest();\n\n  auto handler1 = addSimpleStrictHandler();\n\n  InSequence handlerSequence;\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1] { handler1->sendReplyWithBody(200, 80000); });\n  handler1->expectEgressPaused();\n\n  handler1->expectError([&](const HTTPException& ex) {\n    // Not optimal to have a different error code here than the session\n    // flow control case, but HTTPException direction is immutable and\n    // building another one seems not future proof.\n    EXPECT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS_AND_EGRESS);\n  });\n  handler1->expectDetachTransaction();\n\n  expectDetachSession();\n\n  flushRequestsAndLoop(true, milliseconds(10));\n}\n\nTEST_F(HTTP2DownstreamSessionFCTest, TestEOFOnBlockedSession) {\n  HTTPTransaction::setEgressBufferLimit(25000);\n  transport_->pauseWrites();\n  sendRequest();\n  sendRequest();\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1] {\n    handler1->sendHeaders(200, 40000);\n    handler1->sendBody(32769);\n  });\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&handler2, this] {\n    handler2->sendHeaders(200, 40000);\n    handler2->sendBody(32768);\n    eventBase_.runInLoop([this] { transport_->addReadEOF(milliseconds(0)); });\n  });\n  handler1->expectEgressPaused();\n  handler2->expectEgressPaused();\n  handler1->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS_AND_EGRESS);\n  });\n  handler1->expectDetachTransaction();\n  handler2->expectError([&](const HTTPException& ex) {\n    EXPECT_EQ(ex.getDirection(), HTTPException::Direction::INGRESS_AND_EGRESS);\n  });\n  handler2->expectDetachTransaction();\n\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, NewTxnEgressPaused) {\n  // Send 1 request with prio=0\n  // Have egress pause while sending the first response\n  // Send a second request with prio=1\n  // Finish the body and eom both responses\n  // Unpause egress\n  // The first txn should complete first\n\n  auto id1 = sendRequest(\"/\", 0);\n  auto req2 = getGetRequest();\n  req2.setPriority(1);\n  auto req2p = sendRequestLater(req2, true);\n\n  HTTPTransaction::setEgressBufferLimit(500);\n  transport_->pauseWrites();\n\n  unique_ptr<StrictMock<MockHTTPHandler>> handler1;\n  unique_ptr<StrictMock<MockHTTPHandler>> handler2;\n\n  {\n    InSequence handlerSequence;\n    handler1 = addSimpleStrictHandler();\n    handler1->expectHeaders();\n    handler1->expectEOM([&handler1, &req2p] {\n      handler1->sendHeaders(200, 1000);\n      handler1->sendBody(750); // over the limit\n      req2p.setValue();\n    });\n    handler1->expectEgressPaused([] { LOG(INFO) << \"paused 1\"; });\n\n    handler2 = addSimpleStrictHandler();\n    handler2->expectHeaders();\n    handler2->expectEOM([&] {\n      // Technically shouldn't send while handler is egress paused, but meh.\n      handler1->sendBody(250);\n      handler1->txn_->sendEOM();\n      handler2->sendReplyWithBody(200, 1000);\n      resumeWritesInLoop();\n    });\n    handler2->expectEgressPaused();\n    handler1->expectDetachTransaction();\n    handler2->expectDetachTransaction();\n  }\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop();\n\n  std::list<HTTPCodec::StreamID> streams;\n  EXPECT_CALL(callbacks_, onMessageBegin(_, _)).Times(2);\n  EXPECT_CALL(callbacks_, onHeadersComplete(_, _)).Times(2);\n  // body is variable and hence ignored;\n  EXPECT_CALL(callbacks_, onMessageComplete(_, _))\n      .WillRepeatedly(Invoke([&](HTTPCodec::StreamID stream, bool /*upgrade*/) {\n        streams.push_back(stream);\n      }));\n  parseOutput(*clientCodec_);\n\n  EXPECT_EQ(*streams.begin(), id1);\n  cleanup();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ZeroDeltaWindowUpdate) {\n  // generateHeader() will create a session and a transaction\n  auto streamID = sendHeader();\n  // First generate a frame with delta=1 so as to pass the checks, and then\n  // hack the frame so that delta=0 without modifying other checks\n  clientCodec_->generateWindowUpdate(requests_, streamID, 1);\n  requests_.trimEnd(http2::kFrameWindowUpdateSize);\n  folly::io::QueueAppender appender(&requests_, http2::kFrameWindowUpdateSize);\n  appender.writeBE<uint32_t>(static_cast<uint32_t>(0));\n\n  auto handler = addSimpleStrictHandler();\n\n  InSequence handlerSequence;\n  handler->expectHeaders();\n  handler->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getCodecStatusCode(), ErrorCode::PROTOCOL_ERROR);\n    ASSERT_EQ(\"streamID=1 with window update delta=0\", std::string(ex.what()));\n  });\n  handler->expectDetachTransaction();\n  expectDetachSession();\n\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, PaddingFlowControl) {\n  // generateHeader() will create a session and a transaction\n  auto streamID = sendHeader();\n  // This sends a total of 33kb including padding, so we should get a session\n  // and stream window update\n  for (auto i = 0; i < 129; i++) {\n    clientCodec_->generateBody(requests_, streamID, makeBuf(1), 255, false);\n  }\n\n  auto handler = addSimpleStrictHandler();\n\n  InSequence handlerSequence;\n  handler->expectHeaders([&] {\n    handler->txn_->pauseIngress();\n    eventBase_.runAfterDelay([&] { handler->txn_->resumeIngress(); }, 100);\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(1);\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  HTTPSession::DestructorGuard g(httpSession_);\n  flushRequestsAndLoop(false, milliseconds(0), milliseconds(0), [&] {\n    clientCodec_->generateRstStream(requests_, streamID, ErrorCode::CANCEL);\n    clientCodec_->generateGoaway(requests_, 0, ErrorCode::NO_ERROR);\n    transport_->addReadEvent(requests_, milliseconds(110));\n  });\n\n  std::list<HTTPCodec::StreamID> streams;\n  EXPECT_CALL(callbacks_, onWindowUpdate(0, _));\n  EXPECT_CALL(callbacks_, onWindowUpdate(1, _));\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, GracefulDrainOnTimeout) {\n  InSequence handlerSequence;\n  std::chrono::milliseconds gracefulTimeout(200);\n  httpSession_->enableDoubleGoawayDrain();\n  EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n      .WillOnce(InvokeWithoutArgs([&] {\n        // Once session asks for graceful shutdown timeout, expect the client\n        // to receive the first GOAWAY\n        eventBase_.runInLoop([&] {\n          EXPECT_CALL(callbacks_,\n                      onGoaway(std::numeric_limits<int32_t>::max(),\n                               ErrorCode::NO_ERROR,\n                               _));\n          parseOutput(*clientCodec_);\n        });\n        return gracefulTimeout;\n      }));\n\n  // Simulate ConnectionManager idle timeout\n  eventBase_.runAfterDelay([&] { httpSession_->timeoutExpired(); },\n                           transactionTimeouts_->getDefaultTimeout().count());\n  HTTPSession::DestructorGuard g(httpSession_);\n  auto start = getCurrentTime();\n  eventBase_.loop();\n  auto finish = getCurrentTime();\n  auto minDuration =\n      gracefulTimeout + transactionTimeouts_->getDefaultTimeout();\n  EXPECT_GE((finish - start).count(), minDuration.count());\n  EXPECT_CALL(callbacks_, onGoaway(0, ErrorCode::NO_ERROR, _));\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n  httpSession_->timeoutExpired();\n}\n\n/*\n * The sequence of streams are generated in the following order:\n * - [client --> server] request 1st stream (getGetRequest())\n * - [server --> client] respond 1st stream (res with length 100)\n * - [server --> client] request 2nd stream (req)\n * - [server --> client] respond 2nd stream (res with length 200 + EOM)\n * - [client --> server] RST_STREAM on the 1st stream\n */\nTEST_F(HTTP2DownstreamSessionTest, ServerPush) {\n  // Create a dummy request and a dummy response messages\n  HTTPMessage req, res;\n  req.getHeaders().set(\"HOST\", \"www.foo.com\");\n  req.setURL(\"https://www.foo.com/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  // enable server push\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::ENABLE_PUSH, 1);\n  clientCodec_->generateSettings(requests_);\n  // generateHeader() will create a session and a transaction\n  auto assocStreamId = clientCodec_->createStream();\n  clientCodec_->generateHeader(\n      requests_, assocStreamId, getGetRequest(), false, nullptr);\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n\n  InSequence handlerSequence;\n  handler->expectHeaders([&] {\n    // Generate response for the associated stream\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(100));\n    handler->txn_->pauseIngress();\n\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    // Generate a push request (PUSH_PROMISE)\n    auto outgoingStreams = httpSession_->getNumOutgoingStreams();\n    pushTxn->sendHeaders(req);\n    EXPECT_EQ(httpSession_->getNumOutgoingStreams(), outgoingStreams);\n    // Generate a push response\n    pushTxn->sendHeaders(res);\n    EXPECT_EQ(httpSession_->getNumOutgoingStreams(), outgoingStreams + 1);\n    pushTxn->sendBody(makeBuf(200));\n    pushTxn->sendEOM();\n\n    eventBase_.runAfterDelay([&] { handler->txn_->resumeIngress(); }, 100);\n  });\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  transport_->addReadEvent(requests_, milliseconds(0));\n  clientCodec_->generateRstStream(requests_, assocStreamId, ErrorCode::CANCEL);\n  clientCodec_->generateGoaway(requests_, 2, ErrorCode::NO_ERROR);\n  transport_->addReadEvent(requests_, milliseconds(200));\n  transport_->startReadEvents();\n  HTTPSession::DestructorGuard g(httpSession_);\n  eventBase_.loop();\n\n  EXPECT_CALL(callbacks_, onMessageBegin(1, _));\n  EXPECT_CALL(callbacks_, onHeadersComplete(1, _));\n  EXPECT_CALL(callbacks_, onPushMessageBegin(2, 1, _));\n  EXPECT_CALL(callbacks_, onHeadersComplete(2, _));\n  EXPECT_CALL(callbacks_, onMessageBegin(2, _));\n  EXPECT_CALL(callbacks_, onHeadersComplete(2, _));\n  EXPECT_CALL(callbacks_, onMessageComplete(2, _));\n\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ServerPushAbortPaused) {\n  HTTPTransaction::setEgressBufferLimit(500);\n  // Create a dummy request and a dummy response messages\n  HTTPMessage req, res;\n  req.getHeaders().set(\"HOST\", \"www.foo.com\");\n  req.setURL(\"https://www.foo.com/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  transport_->pauseWrites();\n\n  // enable server push\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::ENABLE_PUSH, 1);\n  clientCodec_->generateSettings(requests_);\n  // generateHeader() will create a session and a transaction\n  auto assocStreamId = HTTPCodec::StreamID(1);\n  clientCodec_->generateHeader(\n      requests_, assocStreamId, getGetRequest(), false, nullptr);\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler;\n\n  InSequence handlerSequence;\n  HTTPTransaction* pushTxn;\n  handler->expectHeaders([&] {\n    // Generate response for the associated stream\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(1000));\n    handler->txn_->pauseIngress();\n\n    pushTxn = handler->txn_->newPushedTransaction(&pushHandler);\n    ASSERT_NE(pushTxn, nullptr);\n    // Generate a push request (PUSH_PROMISE)\n    pushTxn->sendHeaders(req);\n    // Send headers and some body\n    pushTxn->sendHeaders(res);\n    pushTxn->sendBody(makeBuf(1000));\n  });\n\n  handler->expectEgressPaused();\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n  EXPECT_CALL(pushHandler, _onEgressPaused());\n\n  // Expect error on the associated stream.\n  // The push transaction can still push data.\n  handler->expectError([&](const HTTPException&) {\n    pushTxn->sendBody(makeBuf(1000));\n    pushTxn->sendEOM();\n    resumeWritesInLoop();\n    httpSession_->closeWhenIdle();\n  });\n\n  // Associated stream is destroyed.\n  handler->expectDetachTransaction();\n\n  // Push stream finishes on transaction timeout.\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  transport_->addReadEvent(requests_, milliseconds(0));\n  // Cancels associated stream\n  clientCodec_->generateRstStream(requests_, assocStreamId, ErrorCode::CANCEL);\n  transport_->addReadEvent(requests_, milliseconds(10));\n  transport_->startReadEvents();\n  HTTPSession::DestructorGuard g(httpSession_);\n\n  expectDetachSession();\n  eventBase_.loop();\n\n  parseOutput(*clientCodec_);\n\n  EXPECT_EQ(httpSession_->getNumOutgoingStreams(), 0);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ServerPushAfterWriteTimeout) {\n  HTTPTransaction::setEgressBufferLimit(500);\n  // Create a dummy request and a dummy response messages\n  HTTPMessage req, res;\n  req.getHeaders().set(\"HOST\", \"www.foo.com\");\n  req.setURL(\"https://www.foo.com/\");\n  res.setStatusCode(200);\n  res.setStatusMessage(\"Ohai\");\n\n  transport_->pauseWrites();\n\n  // enable server push\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::ENABLE_PUSH, 1);\n  clientCodec_->generateSettings(requests_);\n  // generateHeader() will create a session and a transaction\n  auto assocStreamId = HTTPCodec::StreamID(3);\n  clientCodec_->generateHeader(\n      requests_, assocStreamId, getGetRequest(), true, nullptr);\n\n  auto handler = addSimpleStrictHandler();\n  StrictMock<MockHTTPPushHandler> pushHandler1;\n\n  // InSequence handlerSequence;\n  handler->expectHeaders([&] {\n    // Generate response for the associated stream\n    handler->txn_->sendHeaders(res);\n    handler->txn_->sendBody(makeBuf(1000));\n\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler1);\n    ASSERT_NE(pushTxn, nullptr);\n    // Generate a push request (PUSH_PROMISE)\n    pushTxn->sendHeaders(req);\n    // Send headers and some body\n    pushTxn->sendHeaders(res);\n    pushTxn->sendBody(makeBuf(1000));\n  });\n  handler->expectEgressPaused();\n  EXPECT_CALL(pushHandler1, _setTransaction(_))\n      .WillOnce(Invoke([&](HTTPTransaction* txn) { pushHandler1.txn_ = txn; }));\n  handler->expectEOM();\n  EXPECT_CALL(pushHandler1, _onEgressPaused());\n\n  EXPECT_CALL(pushHandler1, _onError(_)).WillOnce(InvokeWithoutArgs([&] {\n    StrictMock<MockHTTPPushHandler> pushHandler2;\n    auto* pushTxn = handler->txn_->newPushedTransaction(&pushHandler2);\n    EXPECT_EQ(pushTxn, nullptr);\n  }));\n  handler->expectError();\n  handler->expectDetachTransaction();\n  EXPECT_CALL(pushHandler1, _detachTransaction());\n\n  transport_->addReadEvent(requests_, milliseconds(0));\n  transport_->startReadEvents();\n  HTTPSession::DestructorGuard g(httpSession_);\n  eventBase_.loop();\n\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n  EXPECT_EQ(httpSession_->getNumOutgoingStreams(), 0);\n}\n\n#if 0\nTEST_F(HTTP2DownstreamSessionTest, TestPriorityFCBlocked) {\n  // Send two requests that are not stream f/c blocked but are conn f/c blocked\n  // Send a third request with higher priority, ensure it is not blocked by\n  // any low pri bytes.\n\n  // virtual priority node\n  auto priGroupID = clientCodec_->createStream();\n  auto req = getGetRequest();\n  req.setHTTP2Priority(HTTPMessage::HTTP2Priority{priGroupID, false, 255});\n  auto id1 = sendRequest(req);\n  auto id2 = sendRequest(req);\n\n  auto handler1 = addSimpleStrictHandler();\n  handler1->expectHeaders();\n  handler1->expectEOM([&] {\n    handler1->sendHeaders(200, 36 * 1024);\n    handler1->txn_->sendBody(makeBuf(32 * 1024));\n  });\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&] {\n    handler2->sendHeaders(200, 36 * 1024);\n    handler2->txn_->sendBody(makeBuf(32 * 1024));\n  });\n  flushRequestsAndLoopN(2);\n\n  // Send high pri request.  It shouldn't pause, and send a reply\n  req.setHTTP2Priority(HTTPMessage::HTTP2Priority{0, true, 3});\n  auto id3 = sendRequest(req);\n  clientCodec_->generateWindowUpdate(requests_, 0, 64 * 1024);\n  auto handler3 = addSimpleStrictHandler();\n  handler3->expectHeaders();\n  handler3->expectEOM([&] {\n    handler3->expectDetachTransaction();\n    handler3->sendReplyWithBody(200, 32 * 1024);\n    handler1->expectDetachTransaction();\n    handler1->txn_->sendBody(makeBuf(4 * 1024));\n    handler1->txn_->sendEOM();\n    handler2->expectDetachTransaction();\n    handler2->txn_->sendBody(makeBuf(4 * 1024));\n    handler2->txn_->sendEOM();\n  });\n\n  // twice- once to send and once to receive\n  flushRequestsAndLoopN(2);\n  {\n    InSequence enforceOrder;\n    EXPECT_CALL(callbacks_, onSettings(_));\n    EXPECT_CALL(callbacks_, onMessageBegin(id1, _));\n    EXPECT_CALL(callbacks_, onHeadersComplete(id1, _));\n    EXPECT_CALL(callbacks_, onMessageBegin(id2, _));\n    EXPECT_CALL(callbacks_, onHeadersComplete(id2, _));\n    EXPECT_CALL(callbacks_, onMessageBegin(id3, _));\n    EXPECT_CALL(callbacks_, onHeadersComplete(id3, _));\n    EXPECT_CALL(callbacks_, onMessageComplete(id3, _));\n    EXPECT_CALL(callbacks_, onMessageComplete(id1, _));\n    EXPECT_CALL(callbacks_, onMessageComplete(id2, _));\n    parseOutput(*clientCodec_);\n  }\n\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  this->eventBase_.loop();\n  httpSession_->timeoutExpired();\n}\n#endif\n\nTEST_F(HTTP2DownstreamSessionTest, TestHeadersRateLimitExceeded) {\n  httpSession_->enableDoubleGoawayDrain();\n  httpSession_->setRateLimitParams(\n      RateLimiter::Type::HEADERS, 100, std::chrono::seconds(0));\n\n  std::vector<std::unique_ptr<testing::StrictMock<MockHTTPHandler>>> handlers;\n  for (int i = 0; i < 100; i++) {\n    auto handler = addSimpleStrictHandler();\n    auto rawHandler = handler.get();\n    handlers.push_back(std::move(handler));\n    rawHandler->expectHeaders();\n    rawHandler->expectEOM(\n        [rawHandler] { rawHandler->sendReplyWithBody(200, 100); });\n    sendRequest();\n  }\n  // Straw that breaks the camel's back\n  auto id = sendRequest();\n  for (int i = 0; i < 100; i++) {\n    handlers[i]->expectGoaway();\n    handlers[i]->expectDetachTransaction();\n  }\n  expectDetachSession();\n  flushRequestsAndLoopN(2);\n  EXPECT_CALL(callbacks_, onGoaway(id, ErrorCode::NO_ERROR, _));\n  parseOutput(*clientCodec_);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestControlMsgRateLimitExceeded) {\n  clientCodec_->createStream();\n\n  httpSession_->setRateLimitParams(\n      RateLimiter::Type::MISC_CONTROL_MSGS, 100, std::chrono::seconds(0));\n\n  // Send 97 PING, 1 SETTINGS, and 3 PING frames. This should exceed the\n  // limit of 10, causing us to drop the connection.\n  for (uint32_t i = 0;\n       i < ControlMessageRateLimiter::kMaxEventsPerIntervalLowerBound - 3;\n       i++) {\n    clientCodec_->generatePingRequest(requests_);\n  }\n\n  clientCodec_->generateSettings(requests_);\n\n  for (int i = 0; i < 3; i++) {\n    clientCodec_->generatePingRequest(requests_);\n  }\n\n  expectDetachSession();\n\n  flushRequestsAndLoopN(1);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestControlMsgResetRateLimitTouched) {\n  // clear pending settings frame\n  flushRequestsAndLoop();\n\n  auto streamid = clientCodec_->createStream();\n\n  httpSession_->setRateLimitParams(\n      ControlMessageRateLimiter::Type::MISC_CONTROL_MSGS, 10, milliseconds(0));\n\n  // Send 97 PRIORITY, 1 SETTINGS, and 2 PING frames. This doesn't exceed the\n  // limit of 10.\n  for (uint32_t i = 0;\n       i < ControlMessageRateLimiter::kMaxEventsPerIntervalLowerBound - 3;\n       i++) {\n    clientCodec_->generatePriority(requests_, streamid, HTTPPriority{3, false});\n  }\n\n  clientCodec_->generateSettings(requests_);\n\n  for (int i = 0; i < 2; i++) {\n    clientCodec_->generatePingRequest(requests_);\n  }\n  //\n\n  // We should reset the number of control frames seen, enabling us to send\n  // more without hitting the rate limit\n  flushRequestsAndLoop();\n\n  // Send 10 control frames. This is just within the rate limits that we have\n  // set.\n  for (int i = 0; i < 5; i++) {\n    clientCodec_->generatePriority(requests_, streamid, HTTPPriority{3, false});\n  }\n\n  clientCodec_->generateSettings(requests_);\n\n  for (int i = 0; i < 4; i++) {\n    clientCodec_->generatePingRequest(requests_);\n  }\n\n  flushRequestsAndLoopN(2);\n\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  this->eventBase_.loop();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, DirectErrorHandlingLimitTouched) {\n  httpSession_->setRateLimitParams(RateLimiter::Type::DIRECT_ERROR_HANDLING,\n                                   10,\n                                   std::chrono::milliseconds(0));\n\n  // Send 50 messages, each of which cause direct error handling. Since\n  // this doesn't exceed the limit, this should not cause the connection\n  // to be dropped.\n  for (uint32_t i = 0;\n       i < DirectErrorsRateLimiter::kMaxEventsPerIntervalLowerBound;\n       i++) {\n    auto req = getGetRequest();\n    // Invalid method, causes the error to be handled directly\n    req.setMethod(\"11111111\");\n    sendRequest(req, false);\n  }\n\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillRepeatedly(Return(nullptr));\n\n  flushRequestsAndLoop();\n\n  for (int i = 0; i < 10; i++) {\n    auto req = getGetRequest();\n    // Invalid method, causes the error to be handled directly\n    req.setMethod(\"11111111\");\n    sendRequest(req, false);\n  }\n\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillRepeatedly(Return(nullptr));\n\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, DirectErrorHandlingLimitExceeded) {\n  httpSession_->setRateLimitParams(RateLimiter::Type::DIRECT_ERROR_HANDLING,\n                                   10,\n                                   std::chrono::milliseconds(0));\n\n  // Send eleven messages, each of which causes direct error handling. Since\n  // this exceeds the limit, the connection should be dropped.\n  for (uint32_t i = 0;\n       i < DirectErrorsRateLimiter::kMaxEventsPerIntervalLowerBound + 1;\n       i++) {\n    auto req = getGetRequest();\n    // Invalid method, causes the error to be handled directly\n    req.setMethod(\"11111111\");\n    sendRequest(req, false);\n  }\n\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillRepeatedly(Return(nullptr));\n\n  expectDetachSession();\n  flushRequestsAndLoopN(2);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestPriorityWeightsTinyWindow) {\n  httpSession_->setWriteBufferLimit(2 * 65536);\n  InSequence enforceOrder;\n  auto id1 = sendRequest();\n  auto id2 = sendRequest();\n\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders();\n  handler1->expectEOM([&] { handler1->sendReplyWithBody(200, 32 * 1024); });\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&] { handler2->sendReplyWithBody(200, 32 * 1024); });\n\n  handler1->expectDetachTransaction();\n\n  // twice- once to send and once to receive\n  flushRequestsAndLoopN(2);\n  EXPECT_CALL(callbacks_, onSettings(_));\n  EXPECT_CALL(callbacks_, onMessageBegin(id1, _));\n  EXPECT_CALL(callbacks_, onHeadersComplete(id1, _));\n  EXPECT_CALL(callbacks_, onMessageBegin(id2, _));\n  EXPECT_CALL(callbacks_, onHeadersComplete(id2, _));\n  for (auto i = 0; i < 7; i++) {\n    EXPECT_CALL(callbacks_, onBody(id1, _, _))\n        .WillOnce(ExpectBodyLen(4 * 1024));\n    EXPECT_CALL(callbacks_, onBody(id2, _, _))\n        .WillOnce(ExpectBodyLen(4 * 1024));\n  }\n  EXPECT_CALL(callbacks_, onBody(id1, _, _))\n      .WillOnce(ExpectBodyLen(4 * 1024 - 1));\n  EXPECT_CALL(callbacks_, onBody(id2, _, _))\n      .WillOnce(ExpectBodyLen(4 * 1024 - 1));\n  EXPECT_CALL(callbacks_, onBody(id1, _, _)).WillOnce(ExpectBodyLen(1));\n  EXPECT_CALL(callbacks_, onMessageComplete(id1, _));\n  parseOutput(*clientCodec_);\n\n  // open the window\n  clientCodec_->generateWindowUpdate(requests_, 0, 100);\n  handler2->expectDetachTransaction();\n  flushRequestsAndLoopN(2);\n\n  EXPECT_CALL(callbacks_, onBody(id2, _, _)).WillOnce(ExpectBodyLen(1));\n  EXPECT_CALL(callbacks_, onMessageComplete(id2, _));\n  parseOutput(*clientCodec_);\n\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  this->eventBase_.loop();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestShortContentLength) {\n  auto req = getPostRequest(10);\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(20), HTTPCodec::NoPadding, true);\n  auto handler1 = addSimpleStrictHandler();\n\n  InSequence enforceOrder;\n  handler1->expectHeaders();\n  handler1->expectError([&handler1](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorParseBody);\n    handler1->txn_->sendAbort();\n  });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\n/**\n * If handler chooses to untie itself with transaction during onError,\n * detachTransaction shouldn't be expected\n */\nTEST_F(HTTP2DownstreamSessionTest, TestBadContentLengthUntieHandler) {\n  auto req = getPostRequest(10);\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(20), HTTPCodec::NoPadding, true);\n  auto handler1 = addSimpleStrictHandler();\n\n  InSequence enforceOrder;\n  handler1->expectHeaders();\n  handler1->expectError([&](const HTTPException&) {\n    if (handler1->txn_) {\n      handler1->txn_->setHandler(nullptr);\n    }\n    handler1->txn_ = nullptr;\n  });\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestLongContentLength) {\n  auto req = getPostRequest(30);\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(20), HTTPCodec::NoPadding, true);\n  auto handler1 = addSimpleStrictHandler();\n\n  InSequence enforceOrder;\n  handler1->expectHeaders();\n  handler1->expectBody();\n  handler1->expectError([&handler1](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorParseBody);\n    handler1->txn_->sendAbort();\n  });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestMalformedContentLength) {\n  auto req = getPostRequest();\n  req.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"malformed\");\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(20), HTTPCodec::NoPadding, true);\n  auto handler1 = addSimpleStrictHandler();\n\n  InSequence enforceOrder;\n  handler1->expectHeaders();\n  handler1->expectBody();\n  handler1->expectEOM([&handler1] { handler1->sendReplyWithBody(200, 100); });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestHeadContentLength) {\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  req.setMethod(HTTPMethod::HEAD);\n  sendRequest(req);\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1] {\n    handler1->sendHeaders(200, 100);\n    // no body for head\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Test304ContentLength) {\n  // This test covers egress, where we log, but don't fail or crash,\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  req.setMethod(HTTPMethod::GET);\n  sendRequest(req);\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders();\n  handler1->expectEOM([&handler1] {\n    handler1->sendHeaders(304, 100);\n    handler1->txn_->sendEOM();\n  });\n  handler1->expectDetachTransaction();\n  flushRequestsAndLoop();\n\n  gracefulShutdown();\n}\n\n// chunked with wrong content-length\nTEST_F(HTTPDownstreamSessionTest, HttpShortContentLength) {\n  InSequence enforceOrder;\n  auto req = getPostRequest(10);\n  req.setIsChunked(true);\n  req.getHeaders().add(HTTP_HEADER_TRANSFER_ENCODING, \"chunked\");\n  auto streamID = sendRequest(req, false);\n  clientCodec_->generateChunkHeader(requests_, streamID, 20);\n  clientCodec_->generateBody(\n      requests_, streamID, makeBuf(20), HTTPCodec::NoPadding, false);\n  clientCodec_->generateChunkTerminator(requests_, streamID);\n  clientCodec_->generateEOM(requests_, streamID);\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders();\n  EXPECT_CALL(*handler1, _onChunkHeader(20));\n\n  handler1->expectError([&handler1](const HTTPException& ex) {\n    EXPECT_EQ(ex.getProxygenError(), kErrorParseBody);\n    handler1->txn_->sendAbort();\n  });\n  handler1->expectDetachTransaction();\n  expectDetachSession();\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestSessionStallByFlowControl) {\n  NiceMock<MockHTTPSessionStats> stats;\n  // By default the send and receive windows are 64K each.\n  // If we use only a single transaction, that transaction\n  // will be paused on reaching 64K. Therefore, to pause the session,\n  // it is used 2 transactions each sending 32K.\n\n  // Make write buffer limit exceding 64K, for example 128K\n  httpSession_->setWriteBufferLimit(128 * 1024);\n  httpSession_->setSessionStats(&stats);\n\n  InSequence enforceOrder;\n  sendRequest();\n  sendRequest();\n\n  auto handler1 = addSimpleStrictHandler();\n\n  handler1->expectHeaders();\n  handler1->expectEOM([&] { handler1->sendReplyWithBody(200, 32 * 1024); });\n\n  auto handler2 = addSimpleStrictHandler();\n  handler2->expectHeaders();\n  handler2->expectEOM([&] { handler2->sendReplyWithBody(200, 32 * 1024); });\n\n  EXPECT_CALL(stats, _recordSessionStalled()).Times(1);\n\n  handler1->expectDetachTransaction();\n\n  // twice- once to send and once to receive\n  flushRequestsAndLoopN(2);\n\n  // open the window\n  clientCodec_->generateWindowUpdate(requests_, 0, 100);\n  handler2->expectDetachTransaction();\n  flushRequestsAndLoopN(2);\n\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  flushRequestsAndLoop();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestTransactionStallByFlowControl) {\n  NiceMock<MockHTTPSessionStats> stats;\n\n  httpSession_->setSessionStats(&stats);\n\n  // Set the client side stream level flow control wind to 500 bytes,\n  // and try to send 1000 bytes through it.\n  // Then the flow control kicks in and stalls the transaction.\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                                500);\n  clientCodec_->generateSettings(requests_);\n\n  auto streamID = sendRequest();\n\n  EXPECT_CALL(stats, _recordTransactionOpened());\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&] { handler->sendReplyWithBody(200, 1000); });\n\n  EXPECT_CALL(stats, _recordTransactionStalled());\n  handler->expectEgressPaused();\n\n  handler->expectError([&](const HTTPException& ex) {\n    ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\n                  \"ingress timeout, streamID=\",\n                  streamID,\n                  \", timeout=\",\n                  transactionTimeouts_->getDefaultTimeout().count(),\n                  \"ms\"),\n              std::string(ex.what()));\n    handler->terminate();\n  });\n\n  handler->expectDetachTransaction();\n\n  EXPECT_CALL(stats, _recordTransactionClosed());\n\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestTransactionNotStallByFlowControl) {\n  NiceMock<MockHTTPSessionStats> stats;\n\n  httpSession_->setSessionStats(&stats);\n\n  clientCodec_->getEgressSettings()->setSetting(SettingsId::INITIAL_WINDOW_SIZE,\n                                                500);\n  clientCodec_->generateSettings(requests_);\n\n  sendRequest();\n\n  EXPECT_CALL(stats, _recordTransactionOpened());\n\n  InSequence handlerSequence;\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&] { handler->sendReplyWithBody(200, 500); });\n\n  // The egtress paused is notified due to existing logics,\n  // but egress transaction should not be counted as stalled by flow control,\n  // because there is nore more bytes to send\n  handler->expectEgressPaused();\n\n  handler->expectDetachTransaction();\n\n  EXPECT_CALL(stats, _recordTransactionClosed());\n\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestSetEgressSettings) {\n  SettingsList settings = {{SettingsId::HEADER_TABLE_SIZE, 5555},\n                           {SettingsId::MAX_FRAME_SIZE, 16384},\n                           {SettingsId::ENABLE_PUSH, 1}};\n\n  const HTTPSettings* codecSettings = rawCodec_->getEgressSettings();\n  for (const auto& setting : settings) {\n    const HTTPSetting* currSetting = codecSettings->getSetting(setting.id);\n    if (currSetting) {\n      EXPECT_EQ(setting.value, currSetting->value);\n    }\n  }\n\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestDuplicateRequestStream) {\n  // Send the following:\n  // HEADERS id=1\n  // HEADERS id=2\n  // HEADERS id=1 (trailers)\n  // HEADERS id=2 -> contains pseudo-headers after EOM so ignored\n  auto handler2 = addSimpleStrictHandler();\n  auto handler1 = addSimpleStrictHandler();\n  auto streamID1 = sendRequest(\"/withtrailers\", 0, false);\n  auto streamID2 = sendRequest();\n  HTTPHeaders trailers;\n  trailers.add(\"Foo\", \"Bar\");\n  // generate trailers includes FIN=true\n  clientCodec_->generateTrailers(requests_, streamID1, trailers);\n\n  clientCodec_->generateHeader(requests_, streamID2, getGetRequest(), false);\n  handler1->expectHeaders();\n  handler2->expectHeaders();\n  handler2->expectEOM();\n  handler1->expectTrailers();\n  handler1->expectEOM([&] {\n    handler1->sendReplyWithBody(200, 100);\n    // 2 got an error after EOM, which gets ignored - need a response to\n    // cleanly terminate it\n    handler2->sendReplyWithBody(200, 100);\n  });\n  handler2->expectError();\n  handler1->expectDetachTransaction();\n  handler2->expectDetachTransaction();\n  flushRequestsAndLoop();\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, TestPingPreserveData) {\n  auto pingData = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::steady_clock::now().time_since_epoch())\n                      .count();\n  clientCodec_->generatePingRequest(requests_, pingData);\n  EXPECT_CALL(callbacks_, onPingReply(pingData));\n  flushRequestsAndLoop();\n  parseOutput(*clientCodec_);\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, DropConnectionWithPendingShutdownCallback) {\n  // A request to set up EOM writing in the loop later.\n  auto handler = addSimpleStrictHandler();\n  sendRequest();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  flushRequestsAndLoopN(1);\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n\n  // To avoid onWriteComplete which will shutdownTransport\n  transport_->pauseWrites();\n\n  // To schedule the ShutdownTransportCallback\n  flushRequestsAndLoopN(1, true);\n\n  expectDetachSession();\n  httpSession_->dropConnection(\"Async drop\");\n}\n\nTEST_F(HTTP2DownstreamSessionTest, DropAlreadyShuttingDownConnection) {\n  // A request to set up EOM writing in the loop later.\n  auto handler = addSimpleStrictHandler();\n  sendRequest();\n  handler->expectHeaders();\n  handler->expectEOM([&handler] { handler->sendReplyWithBody(200, 100); });\n  flushRequestsAndLoopN(1);\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n\n  // This would shutdownTransport\n  flushRequestsAndLoopN(1, true);\n\n  expectDetachSession();\n  httpSession_->dropConnection(\"Async drop\");\n}\n\nTEST_F(HTTP2DownstreamSessionTest, PingProbes) {\n  // Send an immediate ping probe, and send a reply\n  httpSession_->enablePingProbes(std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 /*extendIntervalOnIngress=*/true,\n                                 /*immediate=*/true);\n  MockHTTPSessionStats stats;\n  httpSession_->setSessionStats(&stats);\n  eventBase_.loopOnce();\n  uint64_t pingVal = 0;\n  EXPECT_CALL(callbacks_, onPingRequest(_)).WillOnce(SaveArg<0>(&pingVal));\n  EXPECT_CALL(stats, _recordSessionPeriodicPingProbeTimeout()).Times(0);\n  parseOutput(*clientCodec_);\n  clientCodec_->generatePingReply(requests_, pingVal);\n  flushRequestsAndLoopN(1);\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  flushRequestsAndLoopN(1);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, PingProbeTimeout) {\n  // Send an immediate ping probe, but don't reply.  Connection is dropped.\n  httpSession_->enablePingProbes(std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 /*extendIntervalOnIngress=*/true,\n                                 /*immediate=*/true);\n  MockHTTPSessionStats stats;\n  httpSession_->setSessionStats(&stats);\n  eventBase_.loopOnce();\n  uint64_t pingVal = 0;\n  EXPECT_CALL(callbacks_, onPingRequest(_)).WillOnce(SaveArg<0>(&pingVal));\n  EXPECT_CALL(stats, _recordSessionPeriodicPingProbeTimeout()).Times(1);\n  parseOutput(*clientCodec_);\n  expectDetachSession();\n  flushRequestsAndLoop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, PingProbeTimeoutRefresh) {\n  httpSession_->enablePingProbes(std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 /*extendIntervalOnIngress=*/true,\n                                 /*immediate=*/false);\n  MockHTTPSessionStats stats;\n  httpSession_->setSessionStats(&stats);\n  EXPECT_CALL(stats, _recordSessionPeriodicPingProbeTimeout()).Times(1);\n  // Don't send an immediate probe.  Send a request after 250ms, which starts\n  // the probe interval timer. The ping probe interval fires at 1250 and times\n  // out at 2250.\n  proxygen::TimePoint start = getCurrentTime();\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM();\n  eventBase_.runAfterDelay(\n      [this] {\n        sendRequest();\n        flushRequests();\n      },\n      250);\n  handler->expectError();\n  handler->expectDetachTransaction();\n  expectDetachSession();\n  eventBase_.loop();\n  auto duration = millisecondsBetween(getCurrentTime(), start);\n  EXPECT_GE(duration.count(), 2250);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, PingProbeInvalid) {\n  // Send an immediate ping probe, send a reply with a different value.\n  // It doesn't drop the connection.\n  httpSession_->enablePingProbes(std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 /*extendIntervalOnIngress=*/true,\n                                 /*immediate=*/true);\n  eventBase_.loopOnce();\n  uint64_t pingVal = 0;\n  EXPECT_CALL(callbacks_, onPingRequest(_)).WillOnce(SaveArg<0>(&pingVal));\n  parseOutput(*clientCodec_);\n  // Send a reply for a ping the prober didn't send (we could send one with\n  // sendPing, but meh)\n  clientCodec_->generatePingReply(requests_, pingVal + 1);\n  flushRequestsAndLoopN(1);\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  eventBase_.loop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, CancelPingProbesOnRequest) {\n  // Send an immediate ping probe, don't reply, but send a request/response.\n  // When the session goes idle, the ping probe timeout should be cancelled.\n  httpSession_->enablePingProbes(std::chrono::seconds(1),\n                                 std::chrono::seconds(1),\n                                 /*extendIntervalOnIngress=*/true,\n                                 /*immediate=*/true);\n  eventBase_.loopOnce();\n  auto handler = addSimpleStrictHandler();\n  sendRequest();\n  handler->expectHeaders();\n  handler->expectEOM([&handler, this] {\n    handler->sendReplyWithBody(200, 100);\n    eventBase_.runAfterDelay([this] { httpSession_->timeoutExpired(); }, 1250);\n  });\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  expectDetachSession();\n  flushRequestsAndLoop();\n  EXPECT_CALL(callbacks_, onPingRequest(_));\n  parseOutput(*clientCodec_);\n  // Session idle times out\n  EXPECT_EQ(httpSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::TIMEOUT);\n  httpSession_->timeoutExpired(); // FIN timeout\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_Attach_Detach_Destroy) {\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    httpSession_->removeObserver(observer.get());\n  }\n\n  // Test destroyed callback when session is destroyed\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    auto handler = addSimpleStrictHandler();\n    handler->expectHeaders();\n    handler->expectEOM([&handler]() {\n      handler->sendReplyWithBody(200 /* status code */,\n                                 100 /* content size */,\n                                 true /* keepalive */,\n                                 true /* sendEOM */,\n                                 false /*trailers*/);\n    });\n    handler->expectDetachTransaction();\n    HTTPSession::DestructorGuard g(httpSession_);\n    HTTPMessage req = getGetRequest();\n    sendRequest(req);\n    flushRequestsAndLoop(true, milliseconds(0));\n\n    EXPECT_CALL(*observer, destroyed(_, _));\n    expectDetachSession();\n    httpSession_->closeWhenIdle();\n  }\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_Attach_Detach_Destroy_Shared) {\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserverShared(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    httpSession_->removeObserver(observer.get());\n  }\n\n  // Test destroyed callback when session is destroyed\n  {\n    auto observer = addMockSessionObserverShared(eventSet);\n    auto handler = addSimpleStrictHandler();\n    handler->expectHeaders();\n    handler->expectEOM([&handler]() {\n      handler->sendReplyWithBody(200 /* status code */,\n                                 100 /* content size */,\n                                 true /* keepalive */,\n                                 true /* sendEOM */,\n                                 false /*trailers*/);\n    });\n    handler->expectDetachTransaction();\n    HTTPSession::DestructorGuard g(httpSession_);\n    HTTPMessage req = getGetRequest();\n    sendRequest(req);\n    flushRequestsAndLoop(true, milliseconds(0));\n\n    EXPECT_CALL(*observer, destroyed(_, _));\n    expectDetachSession();\n    httpSession_->closeWhenIdle();\n  }\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_RequestStarted) {\n\n  // Add an observer NOT subscribed to the RequestStarted event\n  auto observerUnsubscribed =\n      addMockSessionObserver(MockSessionObserver::EventSetBuilder().build());\n  httpSession_->addObserver(observerUnsubscribed.get());\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::RequestStarted)\n          .build());\n  httpSession_->addObserver(observerSubscribed.get());\n\n  EXPECT_CALL(*observerUnsubscribed, requestStarted(_, _)).Times(0);\n\n  HTTPTransactionObserverAccessor* actualTxnObserverAccessor;\n  // Subscribed observer expects to receive RequestStarted callback, with a\n  // request header x-meta-test-header and value \"abc123\"\n  EXPECT_CALL(*observerSubscribed, requestStarted(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPSessionObserverAccessor*,\n              const HTTPSessionObserverInterface::RequestStartedEvent& event) {\n            EXPECT_EQ(event.request.getHeaders().getSingleOrEmpty(\n                          \"x-meta-test-header\"),\n                      \"abc123\");\n            actualTxnObserverAccessor = event.txnObserverAccessor;\n          }));\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler]() {\n    handler->sendReplyWithBody(200 /* status code */,\n                               100 /* content size */,\n                               true /* keepalive */,\n                               true /* sendEOM */,\n                               false /*trailers*/);\n  });\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(\"x-meta-test-header\", \"abc123\");\n  sendRequest(req);\n  flushRequestsAndLoop(true, milliseconds(0));\n  EXPECT_EQ(actualTxnObserverAccessor, handler->txn_->getObserverAccessor());\n  expectDetachSession();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_SendPingByObserver) {\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::PreWrite)\n          .build());\n  httpSession_->addObserver(observerSubscribed.get());\n\n  // Subscribed observer expects to receive PreWrite callback\n  // Check if transactions are about to write a threshold amount of bytes\n  auto pingData = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::steady_clock::now().time_since_epoch())\n                      .count();\n  EXPECT_CALL(*observerSubscribed, preWrite(_, _))\n      .WillOnce(Invoke(\n          [&pingData](\n              HTTPSessionObserverAccessor* sessionObserverAccessor_,\n              const proxygen::HTTPSessionObserverInterface::PreWriteEvent&\n                  event) {\n            EXPECT_EQ(event.pendingEgressBytes, 100);\n            EXPECT_THAT(sessionObserverAccessor_, NotNull());\n            sessionObserverAccessor_->sendPing(pingData);\n          }));\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler]() {\n    handler->sendReplyWithBody(200 /* status code */,\n                               100 /* content size */,\n                               true /* keepalive */,\n                               true /* sendEOM */,\n                               false /*trailers*/);\n  });\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  HTTPMessage req = getGetRequest();\n  sendRequest(req);\n\n  flushRequestsAndLoop(true, milliseconds(0));\n  expectDetachSession();\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n\n  InSequence enforceOrder;\n  EXPECT_CALL(callbacks, onPingRequest(pingData));\n\n  parseOutput(*clientCodec_);\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_PingReply) {\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::PingReply)\n          .build());\n  httpSession_->addObserver(observerSubscribed.get());\n  uint64_t pingId = 0;\n  EXPECT_CALL(callbacks_, onPingRequest(_)).WillOnce(SaveArg<0>(&pingId));\n  httpSession_->sendPing();\n  eventBase_.loopOnce();\n  EXPECT_CALL(*observerSubscribed, pingReply(_, _))\n      .WillOnce(Invoke(\n          [&pingId](\n              HTTPSessionObserverAccessor* sessionObserverAccessor_,\n              const proxygen::HTTPSessionObserverInterface::PingReplyEvent&\n                  event) {\n            EXPECT_EQ(event.id, pingId);\n            EXPECT_THAT(sessionObserverAccessor_, NotNull());\n          }));\n  parseOutput(*clientCodec_);\n  clientCodec_->generatePingReply(requests_, pingId);\n  flushRequestsAndLoopN(1);\n  httpSession_->closeWhenIdle();\n  expectDetachSession();\n  flushRequestsAndLoopN(1);\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, Observer_PreWrite) {\n\n  // Add an observer NOT subscribed to the PreWrite event\n  auto observerUnsubscribed =\n      addMockSessionObserver(MockSessionObserver::EventSetBuilder().build());\n  httpSession_->addObserver(observerUnsubscribed.get());\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::PreWrite)\n          .build());\n  httpSession_->addObserver(observerSubscribed.get());\n\n  EXPECT_CALL(*observerUnsubscribed, preWrite(_, _)).Times(0);\n\n  // Subscribed observer expects to receive PreWrite callback\n  // Check if transactions are about to write a threshold amount of bytes\n  EXPECT_CALL(*observerSubscribed, preWrite(_, _))\n      .WillOnce(\n          Invoke([](HTTPSessionObserverAccessor*,\n                    const proxygen::HTTPSessionObserverInterface::PreWriteEvent&\n                        event) { EXPECT_EQ(event.pendingEgressBytes, 100); }));\n\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders();\n  handler->expectEOM([&handler]() {\n    handler->sendReplyWithBody(200 /* status code */,\n                               100 /* content size */,\n                               true /* keepalive */,\n                               true /* sendEOM */,\n                               false /*trailers*/);\n  });\n  handler->expectDetachTransaction();\n  HTTPSession::DestructorGuard g(httpSession_);\n  HTTPMessage req = getGetRequest();\n  sendRequest(req);\n\n  flushRequestsAndLoop(true, milliseconds(0));\n\n  expectDetachSession();\n}\n\nTEST_F(HTTPDownstreamSessionTest, InvariantViolation) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  // attempting to egress body first => invariantViolation\n  handler->expectHeaders([&]() { handler->sendBody(100); });\n\n  EXPECT_CALL(*handler, _onInvariantViolation(_)).WillOnce([&]() {\n    // invariantViolation callback can send headers if allowed\n    CHECK(txn->canSendHeaders());\n    txn->sendHeaders(getResponse(500, 0));\n  });\n\n  sendRequest(getGetRequest(), /*eom=*/true);\n\n  // this should detach transaction since invariantViolation triggers abort\n  EXPECT_CALL(*handler, _detachTransaction);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(1);\n\n  EXPECT_CALL(callbacks_, onHeadersComplete(_, _)).WillOnce([](auto, auto msg) {\n    EXPECT_EQ(msg->getStatusCode(), 500);\n  });\n\n  parseOutput(*clientCodec_);\n  gracefulShutdown();\n}\n\nTEST_F(HTTPDownstreamSessionTest, WebsocketUpgradeSessionUnresuable) {\n  /**\n   * after rejecting a client's upgrade, the HTTPDownstreamSession should no\n   * longer be parsing bytes off the wire\n   */\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n\n  // rejects client ws upgrade w/ 400\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&](auto msg) {\n    EXPECT_TRUE(msg->isIngressWebsocketUpgrade());\n    handler->sendReplyCode(401);\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  // send client ws upgrade\n  auto wsReq = getGetRequest();\n  wsReq.setEgressWebsocketUpgrade();\n  sendRequest(wsReq, /*eom=*/false);\n  flushRequests(/*eof=*/false);\n  evbLoopNonBlockN(2);\n\n  // send another request, nothing should be parsed (need to use a new codec to\n  // avoid CHECK failures)\n  EXPECT_CALL(mockController_, getRequestHandler(_, _)).Times(0);\n  HTTP1xCodec codec{TransportDirection::UPSTREAM};\n  auto req = getGetRequest();\n  codec.generateHeader(requests_, codec.createStream(), req, /*eom=*/true);\n  flushRequests(/*eof=*/false);\n  evbLoopNonBlockN(2);\n\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, InvariantViolation) {\n  auto handler = addSimpleStrictHandler();\n  auto& txn = handler->txn_;\n  // attempting to egress body first => invariantViolation\n  handler->expectHeaders([&]() { handler->sendBody(100); });\n\n  EXPECT_CALL(*handler, _onInvariantViolation(_)).WillOnce([&]() {\n    // invariantViolation callback can send headers if allowed\n    CHECK(txn->canSendHeaders());\n    txn->sendHeaders(getResponse(500, 0));\n    txn->sendEOM();\n  });\n\n  sendRequest(getGetRequest(), /*eom=*/true);\n\n  // this should detach transaction since invariantViolation triggers abort\n  EXPECT_CALL(*handler, _detachTransaction);\n  flushRequestsAndLoopN(1);\n  evbLoopNonBlockN(2);\n\n  EXPECT_CALL(callbacks_, onHeadersComplete(_, _)).WillOnce([](auto, auto msg) {\n    EXPECT_EQ(msg->getStatusCode(), 500);\n  });\n  EXPECT_CALL(callbacks_, onAbort(_, ErrorCode::NO_ERROR));\n\n  parseOutput(*clientCodec_);\n  gracefulShutdown();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, EnableServerEarlyResponse) {\n  httpSession_->enableServerEarlyResponse();\n\n  // send only headers of large post request\n  HTTPMessage req = getPostRequest(/*contentLength=*/1'000'000);\n  sendRequest(req, /*eom=*/false);\n\n  // send full response upon post headers\n  auto handler = addSimpleStrictHandler();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(msg->getMethod(), HTTPMethod::POST);\n    handler->txn_->sendHeaders(getResponse(200));\n    handler->txn_->sendEOM();\n  });\n\n  // early resp should abort & detach txn\n  handler->expectDetachTransaction();\n  expectDetachSession();\n  flushRequestsAndLoop();\n\n  // expect RST_STREAM/NO_ERROR\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  clientCodec_->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onHeadersComplete(_, _));\n  EXPECT_CALL(callbacks, onMessageComplete(_, _));\n  EXPECT_CALL(callbacks, onAbort(_, ErrorCode::NO_ERROR));\n\n  eventBase_.loopOnce();\n  httpSession_->closeWhenIdle();\n  eventBase_.loop();\n  parseOutput(*clientCodec_);\n  httpSession_->timeoutExpired();\n}\nTEST_F(HTTP2DownstreamSessionTest, IdleSessionDoubleGoawayReadTimeout) {\n  // Enable double GOAWAY drain\n  auto connMan =\n      wangle::ConnectionManager::makeUnique(&eventBase_, milliseconds(125));\n  connMan->addConnection(httpSession_);\n  httpSession_->enableDoubleGoawayDrain();\n\n  // Set up the controller to return a graceful shutdown timeout of 100ms\n  EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n      .WillRepeatedly(Return(std::chrono::milliseconds(100)));\n\n  // Simulate a read timeout on an idle session\n  httpSession_->timeoutExpired();\n\n  // Expect the first GOAWAY to be sent immediately\n  {\n    EXPECT_CALL(\n        callbacks_,\n        onGoaway(std::numeric_limits<int32_t>::max(), ErrorCode::NO_ERROR, _));\n    eventBase_.loopOnce();\n    parseOutput(*clientCodec_);\n  }\n  // Expect the second GOAWAY to be sent 100ms later\n  eventBase_.runAfterDelay(\n      [&] {\n        EXPECT_CALL(callbacks_, onGoaway(0, ErrorCode::NO_ERROR, _));\n        parseOutput(*clientCodec_);\n      },\n      200);\n\n  // Expect the session to close 100ms later\n  expectDetachSession();\n\n  // Run the event loop\n  eventBase_.loop();\n}\n\nTEST_F(HTTP2DownstreamSessionTest, ZeroContentLengthTest) {\n  // send post req with two CLs, one invalid (i.e. \"-1\") & another valid (i.e.\n  // \"0\"); mistmatched CLs should trigger an error\n  auto req = getGetRequest(\"/\");\n  req.setMethod(HTTPMethod::POST);\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"-1\");\n  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"0\");\n\n  // serialize and send\n  EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n      .WillOnce(Return(nullptr));\n  auto id = sendRequest(req, /*eom=*/false);\n  clientCodec_->generateBody(\n      requests_, id, makeBuf(10), HTTPCodec::NoPadding, false);\n  clientCodec_->generateEOM(requests_, id);\n  flushRequestsAndLoopN(2);\n\n  gracefulShutdown();\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPSessionAcceptorTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPSessionAcceptor.h>\n\n#include <cstdlib>\n\n#include <folly/io/async/AsyncSSLSocket.h>\n#include <folly/io/async/test/MockAsyncServerSocket.h>\n#include <folly/io/async/test/MockAsyncSocket.h>\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/utils/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing folly::AsyncSocket;\nusing folly::AsyncSSLSocket;\nusing folly::SocketAddress;\nusing folly::test::MockAsyncSocket;\n\nnamespace {\n\nconst std::string kTestDir = getContainingDirectory(XLOG_FILENAME).str();\n\n}\n\nclass HTTPTargetSessionAcceptor : public HTTPSessionAcceptor {\n public:\n  explicit HTTPTargetSessionAcceptor(\n      std::shared_ptr<const AcceptorConfiguration> accConfig)\n      : HTTPSessionAcceptor(std::move(accConfig)) {\n  }\n\n  HTTPTransaction::Handler* newHandler(HTTPTransaction& /*txn*/,\n                                       HTTPMessage* /*msg*/) noexcept override {\n    return new MockHTTPHandler();\n  }\n\n  void onCreate(const HTTPSessionBase& session) override {\n    EXPECT_EQ(expectedProto_,\n              getCodecProtocolString(session.getCodecProtocol()));\n    sessionsCreated_++;\n  }\n\n  void connectionReady(AsyncSocket::UniquePtr sock,\n                       const SocketAddress& clientAddr,\n                       const std::string& nextProtocolName,\n                       SecureTransportType secureTransportType,\n                       wangle::TransportInfo& tinfo) {\n    HTTPSessionAcceptor::connectionReady(std::move(sock),\n                                         clientAddr,\n                                         nextProtocolName,\n                                         secureTransportType,\n                                         tinfo);\n  }\n\n  void onSessionCreationError(ProxygenError /*error*/) override {\n    sessionCreationErrors_++;\n  }\n\n  uint32_t sessionsCreated_{0};\n  uint32_t sessionCreationErrors_{0};\n  std::string expectedProto_;\n};\n\nclass HTTPSessionAcceptorTestBase\n    : public ::testing::TestWithParam<const char*> {\n public:\n  virtual void setupSSL() {\n    setenv(\"test_cert_pem_path\", (kTestDir + \"test_cert1.pem\").c_str(), 0);\n    setenv(\"test_cert_key_path\", (kTestDir + \"test_cert1.key\").c_str(), 0);\n    sslCtxConfig_.setCertificate(std::getenv(\"test_cert_pem_path\"),\n                                 std::getenv(\"test_cert_key_path\"),\n                                 \"\");\n\n    sslCtxConfig_.isDefault = true;\n    sslCtxConfig_.clientVerification =\n        folly::SSLContext::VerifyClientCertificate::DO_NOT_REQUEST;\n    config_->sslContextConfigs.emplace_back(sslCtxConfig_);\n  }\n\n  void SetUp() override {\n    config_ = std::make_shared<AcceptorConfiguration>();\n    SocketAddress address(\"127.0.0.1\", 0);\n    config_->bindAddress = address;\n    setupSSL();\n    newAcceptor();\n  }\n\n  void newAcceptor() {\n    acceptor_ = std::make_unique<HTTPTargetSessionAcceptor>(config_);\n    EXPECT_CALL(mockServerSocket_, addAcceptCallback(_, _, _));\n    acceptor_->init(&mockServerSocket_, &eventBase_);\n  }\n\n protected:\n  std::shared_ptr<AcceptorConfiguration> config_;\n  wangle::SSLContextConfig sslCtxConfig_;\n  std::unique_ptr<HTTPTargetSessionAcceptor> acceptor_;\n  folly::EventBase eventBase_;\n  folly::test::MockAsyncServerSocket mockServerSocket_;\n};\n\nclass HTTPSessionAcceptorTestNPN : public HTTPSessionAcceptorTestBase {};\nclass HTTPSessionAcceptorTestNPNPlaintext\n    : public HTTPSessionAcceptorTestBase {};\nclass HTTPSessionAcceptorTestNPNJunk : public HTTPSessionAcceptorTestBase {};\n\n// Verify HTTPSessionAcceptor creates the correct codec based on NPN\nTEST_P(HTTPSessionAcceptorTestNPN, Npn) {\n  std::string proto(GetParam());\n  if (proto == \"\") {\n    acceptor_->expectedProto_ = \"http/1.1\";\n  } else if (proto.find(\"h2\") != std::string::npos) {\n    acceptor_->expectedProto_ = \"http/2\";\n  } else {\n    acceptor_->expectedProto_ = proto;\n  }\n\n  auto ctx = std::make_shared<folly::SSLContext>();\n  AsyncSSLSocket::UniquePtr sock(new AsyncSSLSocket(ctx, &eventBase_));\n  SocketAddress clientAddress;\n  wangle::TransportInfo tinfo;\n  acceptor_->connectionReady(\n      std::move(sock), clientAddress, proto, SecureTransportType::TLS, tinfo);\n  EXPECT_EQ(acceptor_->sessionsCreated_, 1);\n  EXPECT_EQ(acceptor_->sessionCreationErrors_, 0);\n}\n\nstd::array<char const*, 3> protos1{\"h2\", \"http/1.1\", \"\"};\nINSTANTIATE_TEST_SUITE_P(NPNPositive,\n                         HTTPSessionAcceptorTestNPN,\n                         ::testing::ValuesIn(protos1));\n\n// Verify HTTPSessionAcceptor creates the correct plaintext codec\nTEST_P(HTTPSessionAcceptorTestNPNPlaintext, PlaintextProtocols) {\n  std::string proto(GetParam());\n  config_->plaintextProtocol = proto;\n  newAcceptor();\n  if (proto == \"h2c\") {\n    acceptor_->expectedProto_ = \"http/2\";\n  } else {\n    acceptor_->expectedProto_ = proto;\n  }\n  AsyncSocket::UniquePtr sock(new AsyncSocket(&eventBase_));\n  SocketAddress clientAddress;\n  wangle::TransportInfo tinfo;\n  acceptor_->connectionReady(\n      std::move(sock), clientAddress, \"\", SecureTransportType::NONE, tinfo);\n  EXPECT_EQ(acceptor_->sessionsCreated_, 1);\n  EXPECT_EQ(acceptor_->sessionCreationErrors_, 0);\n}\n\nstd::array<char const*, 1> protos2{\"h2c\"};\nINSTANTIATE_TEST_SUITE_P(NPNPlaintext,\n                         HTTPSessionAcceptorTestNPNPlaintext,\n                         ::testing::ValuesIn(protos2));\n\n// Verify HTTPSessionAcceptor closes the socket on invalid NPN\nTEST_F(HTTPSessionAcceptorTestNPNJunk, Npn) {\n  std::string proto(\"/http/1.1\");\n  MockAsyncSocket::UniquePtr sock(new MockAsyncSocket(&eventBase_));\n  SocketAddress clientAddress;\n  wangle::TransportInfo tinfo;\n  EXPECT_CALL(*sock.get(), closeNow());\n  acceptor_->connectionReady(\n      std::move(sock), clientAddress, proto, SecureTransportType::NONE, tinfo);\n  EXPECT_EQ(acceptor_->sessionsCreated_, 0);\n  EXPECT_EQ(acceptor_->sessionCreationErrors_, 1);\n}\n\nTEST_F(HTTPSessionAcceptorTestNPN, AcceptorConfigCapture) {\n  newAcceptor();\n  config_.reset();\n  acceptor_->expectedProto_ = \"http/1.1\";\n  AsyncSocket::UniquePtr sock(new AsyncSocket(&eventBase_));\n  SocketAddress clientAddress;\n  wangle::TransportInfo tinfo;\n  acceptor_->connectionReady(\n      std::move(sock), clientAddress, \"\", SecureTransportType::NONE, tinfo);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPSessionActivityTrackerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <proxygen/lib/http/session/HTTPSessionActivityTracker.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockByteEventTracker.h>\n\nusing namespace testing;\nusing namespace proxygen;\n\nclass MockHTTPSessionActivityTracker : public HTTPSessionActivityTracker {\n public:\n  MockHTTPSessionActivityTracker(size_t ingressTH, size_t egressTH)\n      : HTTPSessionActivityTracker(nullptr, ingressTH, egressTH) {\n  }\n  MOCK_METHOD(void, reportActivity, ());\n};\n\nclass HTTPSessionActivityTrackerTest : public Test {\n public:\n  void SetUp() override {\n    timeouts_ = folly::HHWheelTimer::newTimer(\n        &evb_,\n        std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n        folly::TimeoutManager::InternalEnum::INTERNAL,\n        std::chrono::milliseconds(60000));\n    transaction_ = std::make_unique<proxygen::MockHTTPTransaction>(\n        proxygen::TransportDirection::DOWNSTREAM,\n        1,\n        1,\n        downstreamEgressQueue_,\n        timeouts_.get(),\n        folly::none);\n    httpSessionActivityTracker_ =\n        std::make_unique<MockHTTPSessionActivityTracker>(1000, 1000);\n  }\n\n protected:\n  std::unique_ptr<MockHTTPSessionActivityTracker> httpSessionActivityTracker_;\n  proxygen::HTTP2PriorityQueue downstreamEgressQueue_;\n  folly::EventBase evb_;\n  folly::HHWheelTimer::UniquePtr timeouts_;\n  std::unique_ptr<proxygen::MockHTTPTransaction> transaction_;\n};\n\nTEST_F(HTTPSessionActivityTrackerTest, onIngress) {\n  EXPECT_CALL(*httpSessionActivityTracker_, reportActivity()).Times(3);\n  EXPECT_FALSE(httpSessionActivityTracker_->onIngressBody(500));\n  EXPECT_FALSE(httpSessionActivityTracker_->onIngressBody(300));\n  EXPECT_TRUE(httpSessionActivityTracker_->onIngressBody(700));\n  EXPECT_TRUE(httpSessionActivityTracker_->onIngressBody(2700));\n  EXPECT_TRUE(httpSessionActivityTracker_->onIngressBody(900));\n}\n\nTEST_F(HTTPSessionActivityTrackerTest, addTrackedEgressByteEvent) {\n\n  {\n    MockByteEventTracker byteEventTracker(nullptr);\n    EXPECT_CALL(byteEventTracker, addTrackedByteEvent(_, _, _)).Times(0);\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        500, 400, &byteEventTracker, transaction_.get());\n  }\n  {\n    MockByteEventTracker byteEventTracker(nullptr);\n    httpSessionActivityTracker_ =\n        std::make_unique<MockHTTPSessionActivityTracker>(1000, 1000);\n    EXPECT_CALL(byteEventTracker,\n                addTrackedByteEvent(transaction_.get(), 1000, _));\n    EXPECT_CALL(byteEventTracker,\n                addTrackedByteEvent(transaction_.get(), 2000, _));\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        0, 500, &byteEventTracker, transaction_.get());\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        500, 1600, &byteEventTracker, transaction_.get());\n  }\n  {\n    MockByteEventTracker byteEventTracker(nullptr);\n    httpSessionActivityTracker_ =\n        std::make_unique<MockHTTPSessionActivityTracker>(1000, 1000);\n    EXPECT_CALL(byteEventTracker,\n                addTrackedByteEvent(transaction_.get(), 1000, _));\n    EXPECT_CALL(byteEventTracker,\n                addTrackedByteEvent(transaction_.get(), 2000, _));\n    EXPECT_CALL(byteEventTracker,\n                addTrackedByteEvent(transaction_.get(), 3000, _));\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        0, 1100, &byteEventTracker, transaction_.get());\n    httpSessionActivityTracker_->addTrackedEgressByteEvent(\n        1100, 2600, &byteEventTracker, transaction_.get());\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPSessionBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n// Benchmark for per-transaction operations in HTTPSession.\n//\n// Measures the cost of checkIfEgressRateLimitedByUpstream() across N\n// concurrent transactions in a real HTTPDownstreamSession, useful for\n// detecting performance regressions in the egress rate-limiting path.\n\n#include <folly/Benchmark.h>\n#include <folly/init/Init.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n\n#include <proxygen/lib/http/session/test/TestUtils.h>\n\nusing namespace proxygen;\nusing namespace testing;\nusing namespace folly;\n\nnamespace {\n\n// Minimal transport callback matching production behavior.\nclass BenchTransportCallback : public HTTPTransaction::TransportCallback {\n public:\n  void firstHeaderByteFlushed() noexcept override {\n  }\n  void firstByteFlushed() noexcept override {\n  }\n  void trackedByteFlushed() noexcept override {\n  }\n  void lastByteFlushed() noexcept override {\n  }\n  void lastByteAcked(std::chrono::milliseconds) noexcept override {\n  }\n  void trackedByteEventTX(const ByteEvent&) noexcept override {\n  }\n  void trackedByteEventAck(const ByteEvent&) noexcept override {\n  }\n  void egressBufferEmpty() noexcept override {\n    egressBufferEmptyCalls_++;\n  }\n  void headerBytesGenerated(HTTPHeaderSize&) noexcept override {\n  }\n  void headerBytesReceived(const HTTPHeaderSize&) noexcept override {\n  }\n  void bodyBytesGenerated(size_t) noexcept override {\n  }\n  void bodyBytesReceived(size_t) noexcept override {\n  }\n  void transportAppRateLimited() noexcept override {\n  }\n  void datagramBytesGenerated(size_t) noexcept override {\n  }\n  void datagramBytesReceived(size_t) noexcept override {\n  }\n\n  uint64_t egressBufferEmptyCalls_{0};\n};\n\n// Manages the lifecycle of a downstream HTTP/2 session with N concurrent\n// transactions. Uses NiceMock<MockHTTPCodec> + NiceMock<MockAsyncTransport>\n// to avoid real I/O. Transactions are configured in the state that triggers\n// egressBufferEmpty(): transportCallback_ set, EOM not queued, egress body\n// buffer empty.\nclass SessionBenchmarkHelper {\n public:\n  explicit SessionBenchmarkHelper(uint32_t numTransactions)\n      : numTransactions_(numTransactions) {\n    setup();\n  }\n\n  ~SessionBenchmarkHelper() {\n    teardown();\n  }\n\n  // Call checkIfEgressRateLimitedByUpstream() on every transaction.\n  void invokeCheckOnAllTransactions() {\n    for (auto& handler : handlers_) {\n      if (handler->txn_) {\n        handler->txn_->checkIfEgressRateLimitedByUpstream();\n      }\n    }\n  }\n\n private:\n  void setup() {\n    codec_ = new NiceMock<MockHTTPCodec>();\n    transport_ = new NiceMock<folly::test::MockAsyncTransport>();\n    transactionTimeouts_ = makeTimeoutSet(&eventBase_);\n\n    ON_CALL(*transport_, good()).WillByDefault(Return(true));\n    ON_CALL(*transport_, getEventBase()).WillByDefault(Return(&eventBase_));\n    ON_CALL(*transport_, setReadCB(_)).WillByDefault(SaveArg<0>(&transportCb_));\n    ON_CALL(*transport_, writeChain(_, _, _))\n        .WillByDefault([](folly::AsyncTransport::WriteCallback* cb,\n                          std::shared_ptr<folly::IOBuf>,\n                          folly::WriteFlags) { cb->writeSuccess(); });\n\n    ON_CALL(mockController_, getGracefulShutdownTimeout())\n        .WillByDefault(Return(std::chrono::milliseconds(0)));\n    ON_CALL(mockController_, getHeaderIndexingStrategy())\n        .WillByDefault(Return(HeaderIndexingStrategy::getDefaultInstance()));\n    EXPECT_CALL(mockController_, attachSession(_)).Times(1);\n    EXPECT_CALL(mockController_, onTransportReady(_)).Times(1);\n\n    ON_CALL(*codec_, setCallback(_)).WillByDefault(SaveArg<0>(&codecCallback_));\n    ON_CALL(*codec_, supportsParallelRequests()).WillByDefault(Return(true));\n    ON_CALL(*codec_, supportsPushTransactions()).WillByDefault(Return(true));\n    ON_CALL(*codec_, getTransportDirection())\n        .WillByDefault(Return(TransportDirection::DOWNSTREAM));\n    ON_CALL(*codec_, supportsStreamFlowControl()).WillByDefault(Return(true));\n    ON_CALL(*codec_, getProtocol())\n        .WillByDefault(Return(CodecProtocol::HTTP_2));\n    ON_CALL(*codec_, supportsSessionFlowControl()).WillByDefault(Return(true));\n    ON_CALL(*codec_, getIngressSettings())\n        .WillByDefault(Return(&ingressSettings_));\n    ON_CALL(*codec_, isReusable()).WillByDefault(Return(true));\n    ON_CALL(*codec_, isWaitingToDrain()).WillByDefault(Return(false));\n    ON_CALL(*codec_, getDefaultWindowSize())\n        .WillByDefault(Return(http2::kInitialWindow));\n    ON_CALL(*codec_, mapPriorityToDependency(_)).WillByDefault(Return(0));\n    ON_CALL(*codec_, setParserPaused(_)).WillByDefault(Return());\n\n    fakeMockCodec(*codec_);\n\n    HTTPSession::setDefaultReadBufferLimit(65536);\n    HTTPTransaction::setEgressBufferLimit(65536);\n    httpSession_ =\n        new HTTPDownstreamSession(transactionTimeouts_.get(),\n                                  folly::AsyncTransport::UniquePtr(transport_),\n                                  localAddr,\n                                  peerAddr,\n                                  &mockController_,\n                                  std::unique_ptr<HTTPCodec>(codec_),\n                                  mockTransportInfo,\n                                  nullptr);\n    httpSession_->setEgressSettings(\n        {{SettingsId::MAX_CONCURRENT_STREAMS, numTransactions_ + 100}});\n    httpSession_->startNow();\n    eventBase_.loop();\n\n    handlers_.reserve(numTransactions_);\n    transportCallbacks_.reserve(numTransactions_);\n    for (uint32_t i = 0; i < numTransactions_; i++) {\n      handlers_.push_back(std::make_unique<NiceMock<MockHTTPHandler>>());\n      transportCallbacks_.push_back(std::make_unique<BenchTransportCallback>());\n    }\n\n    for (uint32_t i = 0; i < numTransactions_; i++) {\n      HTTPCodec::StreamID streamID = i * 2 + 1;\n      auto req = makeGetRequest();\n\n      EXPECT_CALL(mockController_, getRequestHandler(_, _))\n          .WillOnce(Return(handlers_[i].get()))\n          .RetiresOnSaturation();\n      EXPECT_CALL(*handlers_[i], _setTransaction(_))\n          .WillOnce([this, i](HTTPTransaction* txn) {\n            handlers_[i]->txn_ = txn;\n            txn->setTransportCallback(transportCallbacks_[i].get());\n          });\n      EXPECT_CALL(*handlers_[i], _onHeadersComplete(_));\n\n      codecCallback_->onMessageBegin(streamID, req.get());\n      codecCallback_->onHeadersComplete(streamID, std::move(req));\n    }\n\n    eventBase_.loop();\n  }\n\n  void teardown() {\n    for (auto& handler : handlers_) {\n      if (handler->txn_) {\n        EXPECT_CALL(*handler, _onError(_)).WillOnce(Return());\n        EXPECT_CALL(*handler, _detachTransaction()).WillOnce(Return());\n      }\n    }\n    EXPECT_CALL(mockController_, detachSession(_)).Times(AtMost(1));\n    httpSession_->dropConnection();\n  }\n\n  uint32_t numTransactions_;\n  EventBase eventBase_;\n  NiceMock<MockHTTPCodec>* codec_{nullptr};\n  NiceMock<folly::test::MockAsyncTransport>* transport_{nullptr};\n  folly::AsyncTransport::ReadCallback* transportCb_{nullptr};\n  folly::HHWheelTimer::UniquePtr transactionTimeouts_;\n  NiceMock<MockController> mockController_;\n  HTTPCodec::Callback* codecCallback_{nullptr};\n  HTTPDownstreamSession* httpSession_{nullptr};\n  HTTPSettings ingressSettings_{\n      {SettingsId::INITIAL_WINDOW_SIZE, http2::kInitialWindow}};\n\n  std::vector<std::unique_ptr<NiceMock<MockHTTPHandler>>> handlers_;\n  std::vector<std::unique_ptr<BenchTransportCallback>> transportCallbacks_;\n};\n\n// Measures the per-iteration cost of calling\n// checkIfEgressRateLimitedByUpstream() on all N transactions in a real\n// HTTPDownstreamSession, exercising the full code path including condition\n// checks and the egressBufferEmpty() callback.\nvoid benchCheckEgressRateLimited(uint32_t iters, uint32_t numTransactions) {\n  SessionBenchmarkHelper* helper = nullptr;\n\n  BENCHMARK_SUSPEND {\n    helper = new SessionBenchmarkHelper(numTransactions);\n  }\n\n  for (uint32_t iter = 0; iter < iters; iter++) {\n    helper->invokeCheckOnAllTransactions();\n  }\n\n  folly::doNotOptimizeAway(helper);\n\n  BENCHMARK_SUSPEND {\n    delete helper;\n  }\n}\n\n} // namespace\n\nBENCHMARK_NAMED_PARAM(benchCheckEgressRateLimited, 10_txns, 10)\nBENCHMARK_NAMED_PARAM(benchCheckEgressRateLimited, 100_txns, 100)\nBENCHMARK_NAMED_PARAM(benchCheckEgressRateLimited, 500_txns, 500)\nBENCHMARK_NAMED_PARAM(benchCheckEgressRateLimited, 1000_txns, 1000)\n\nint main(int argc, char** argv) {\n  folly::Init init(&argc, &argv, true);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPSessionMocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <array>\n#include <vector>\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <proxygen/lib/http/session/HTTPSessionController.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/test/MockHTTPSessionStats.h>\n\nnamespace proxygen {\n\nclass HTTPHandlerBase {\n public:\n  HTTPHandlerBase() = default;\n  HTTPHandlerBase(HTTPTransaction* txn, HTTPMessage* msg)\n      : txn_(txn), msg_(msg) {\n  }\n\n  void terminate() {\n    txn_->sendAbort();\n  }\n\n  void sendRequest() {\n    sendRequest(getGetRequest());\n  }\n\n  void sendRequest(HTTPMessage req) {\n    // this copies but it's test code meh\n    txn_->sendHeaders(req);\n    txn_->sendEOM();\n  }\n\n  using HeaderMap = std::map<std::string, std::string>;\n  void sendHeaders(uint32_t code,\n                   uint32_t content_length,\n                   bool keepalive = true,\n                   HeaderMap headers = HeaderMap()) {\n    HTTPMessage reply;\n    reply.setStatusCode(code);\n    reply.setHTTPVersion(1, 1);\n    reply.setWantsKeepalive(keepalive);\n    reply.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH,\n                           folly::to<std::string>(content_length));\n    for (auto& nv : headers) {\n      reply.getHeaders().add(nv.first, nv.second);\n    }\n    txn_->sendHeaders(reply);\n  }\n\n  void sendReply() {\n    sendReplyCode(200);\n  }\n\n  void sendReplyCode(uint32_t code) {\n    sendHeaders(code, 0, true);\n    txn_->sendEOM();\n  }\n\n  void sendBody(uint32_t content_length) {\n    constexpr uint32_t kMaxChunkSize = 4'096;\n    std::array<char, kMaxChunkSize> buf;\n    buf.fill('a');\n    while (content_length > 0) {\n      const uint32_t toSend = std::min(kMaxChunkSize, content_length);\n      txn_->sendBody(folly::IOBuf::copyBuffer(buf.data(), toSend));\n      content_length -= toSend;\n    }\n  }\n\n  void sendBodyWithLastByteFlushedTracking(uint32_t content_length) {\n    txn_->setLastByteFlushedTrackingEnabled(true);\n    sendBody(content_length);\n  }\n\n  void sendReplyWithBody(uint32_t code,\n                         uint32_t content_length,\n                         bool keepalive = true,\n                         bool sendEOM = true,\n                         bool hasTrailers = false,\n                         uint16_t padding = 0) {\n    sendHeaders(code, content_length, keepalive);\n    sendBody(content_length);\n    if (hasTrailers) {\n      HTTPHeaders trailers;\n      trailers.add(\"X-Trailer1\", \"Foo\");\n      txn_->sendTrailers(trailers);\n    }\n    if (padding) {\n      txn_->sendPadding(padding);\n    }\n    if (sendEOM) {\n      txn_->sendEOM();\n    }\n  }\n\n  void sendEOM() {\n    txn_->sendEOM();\n  }\n\n  void sendChunkedReplyWithBody(uint32_t code,\n                                uint32_t content_length,\n                                uint32_t chunkSize,\n                                bool hasTrailers,\n                                bool sendEOM = true) {\n    HTTPMessage reply;\n    reply.setStatusCode(code);\n    reply.setHTTPVersion(1, 1);\n    reply.setIsChunked(true);\n    txn_->sendHeaders(reply);\n    const std::vector<char> buf(chunkSize, 'a');\n    while (content_length > 0) {\n      const uint32_t toSend = std::min(content_length, chunkSize);\n      txn_->sendChunkHeader(toSend);\n      txn_->sendBody(folly::IOBuf::copyBuffer(buf.data(), toSend));\n      txn_->sendChunkTerminator();\n      content_length -= toSend;\n    }\n    if (hasTrailers) {\n      HTTPHeaders trailers;\n      trailers.add(\"X-Trailer1\", \"Foo\");\n      txn_->sendTrailers(trailers);\n    }\n    if (sendEOM) {\n      txn_->sendEOM();\n    }\n  }\n\n  HTTPTransaction* txn_{nullptr};\n  HTTPTransaction* pushedTxn_{nullptr};\n\n  std::shared_ptr<HTTPMessage> msg_;\n};\n\nclass MockHTTPHandler\n    : public HTTPHandlerBase\n    , public HTTPTransaction::Handler\n    , public WebTransportHandler {\n public:\n  MockHTTPHandler() {\n    setupInvariantViolation();\n  }\n  MockHTTPHandler(HTTPTransaction& txn,\n                  HTTPMessage* msg,\n                  const folly::SocketAddress&)\n      : HTTPHandlerBase(&txn, msg) {\n    setupInvariantViolation();\n  }\n\n  void setupInvariantViolation() {\n    ON_CALL(*this, _onInvariantViolation(testing::_))\n        .WillByDefault(testing::Invoke(\n            [](const HTTPException& ex) { LOG(FATAL) << ex.what(); }));\n  }\n\n  void setTransaction(HTTPTransaction* txn) noexcept override {\n    _setTransaction(txn);\n  }\n  MOCK_METHOD(void, _setTransaction, (HTTPTransaction*));\n\n  void detachTransaction() noexcept override {\n    _detachTransaction();\n  }\n  MOCK_METHOD(void, _detachTransaction, ());\n\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override {\n    _onHeadersComplete(std::shared_ptr<HTTPMessage>(msg.release()));\n  }\n\n  MOCK_METHOD(void, _onHeadersComplete, (std::shared_ptr<HTTPMessage>));\n\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    _onBody(std::shared_ptr<folly::IOBuf>(chain.release()));\n  }\n  MOCK_METHOD(void, _onBody, (std::shared_ptr<folly::IOBuf>));\n\n  void onBodyWithOffset(uint64_t bodyOffset,\n                        std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    _onBodyWithOffset(bodyOffset,\n                      std::shared_ptr<folly::IOBuf>(chain.release()));\n  }\n  MOCK_METHOD(void,\n              _onBodyWithOffset,\n              (uint64_t, std::shared_ptr<folly::IOBuf>));\n\n  void onDatagram(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    _onDatagram(std::shared_ptr<folly::IOBuf>(chain.release()));\n  }\n  MOCK_METHOD(void, _onDatagram, (std::shared_ptr<folly::IOBuf>));\n\n  MOCK_METHOD(void,\n              onWebTransportBidiStream,\n              (HTTPCodec::StreamID, WebTransport::BidiStreamHandle),\n              (noexcept));\n  MOCK_METHOD(void,\n              onWebTransportUniStream,\n              (HTTPCodec::StreamID, WebTransport::StreamReadHandle*),\n              (noexcept));\n  MOCK_METHOD(void,\n              onWebTransportSessionClose,\n              (folly::Optional<uint32_t>),\n              (noexcept));\n\n  void onNewUniStream(\n      WebTransport::StreamReadHandle* readHandle) noexcept override {\n    onWebTransportUniStream(0, readHandle);\n  }\n\n  void onNewBidiStream(\n      WebTransport::BidiStreamHandle bidiHandle) noexcept override {\n    onWebTransportBidiStream(0, bidiHandle);\n  }\n\n  void onSessionEnd(folly::Optional<uint32_t> error) noexcept override {\n    onWebTransportSessionClose(error);\n  }\n\n  MOCK_METHOD(void, onSessionDrain, (), (noexcept, override));\n\n  void onChunkHeader(size_t length) noexcept override {\n    _onChunkHeader(length);\n  }\n  MOCK_METHOD(void, _onChunkHeader, (size_t));\n\n  void onChunkComplete() noexcept override {\n    _onChunkComplete();\n  }\n  MOCK_METHOD(void, _onChunkComplete, ());\n\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override {\n    _onTrailers(std::shared_ptr<HTTPHeaders>(trailers.release()));\n  }\n\n  MOCK_METHOD(void, _onTrailers, (std::shared_ptr<HTTPHeaders>));\n\n  void onEOM() noexcept override {\n    _onEOM();\n  }\n  MOCK_METHOD(void, _onEOM, ());\n\n  void onUpgrade(UpgradeProtocol protocol) noexcept override {\n    _onUpgrade(protocol);\n  }\n  MOCK_METHOD(void, _onUpgrade, (UpgradeProtocol));\n\n  void onError(const HTTPException& error) noexcept override {\n    _onError(error);\n  }\n  MOCK_METHOD(void, _onError, (const HTTPException&));\n\n  void onInvariantViolation(const HTTPException& error) noexcept override {\n    _onInvariantViolation(error);\n  }\n  MOCK_METHOD(void, _onInvariantViolation, (const HTTPException&));\n\n  void onGoaway(ErrorCode errCode) noexcept override {\n    _onGoaway(errCode);\n  }\n  MOCK_METHOD(void, _onGoaway, (ErrorCode));\n\n  void onEgressPaused() noexcept override {\n    _onEgressPaused();\n  }\n  MOCK_METHOD(void, _onEgressPaused, ());\n\n  void onEgressResumed() noexcept override {\n    _onEgressResumed();\n  }\n  MOCK_METHOD(void, _onEgressResumed, ());\n\n  void onPushedTransaction(HTTPTransaction* txn) noexcept override {\n    _onPushedTransaction(txn);\n  }\n  MOCK_METHOD(void, _onPushedTransaction, (HTTPTransaction*));\n\n  void expectTransaction(std::function<void(HTTPTransaction* txn)> callback) {\n    EXPECT_CALL(*this, _setTransaction(testing::_))\n        .WillOnce(testing::Invoke(callback))\n        .RetiresOnSaturation();\n  }\n\n  void expectTransaction(HTTPTransaction** pTxn = nullptr) {\n    EXPECT_CALL(*this, _setTransaction(testing::_))\n        .WillOnce(testing::SaveArg<0>(pTxn ? pTxn : &txn_));\n  }\n\n  void expectPushedTransaction(HTTPTransactionHandler* handler = nullptr) {\n    EXPECT_CALL(*this, _onPushedTransaction(testing::_))\n        .WillOnce(testing::Invoke([handler](HTTPTransaction* txn) {\n          if (handler) {\n            txn->setHandler(handler);\n          }\n        }));\n  }\n\n  void expectHeaders(std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onHeadersComplete(testing::_))\n          .WillOnce(testing::InvokeWithoutArgs(callback))\n          .RetiresOnSaturation();\n    } else {\n      EXPECT_CALL(*this, _onHeadersComplete(testing::_));\n    }\n  }\n\n  void expectHeaders(std::function<void(std::shared_ptr<HTTPMessage>)> cb) {\n    EXPECT_CALL(*this, _onHeadersComplete(testing::_))\n        .WillOnce(testing::Invoke(cb))\n        .RetiresOnSaturation();\n  }\n\n  void expectTrailers(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onTrailers(testing::_))\n          .WillOnce(testing::InvokeWithoutArgs(callback))\n          .RetiresOnSaturation();\n    } else {\n      EXPECT_CALL(*this, _onTrailers(testing::_));\n    }\n  }\n\n  void expectTrailers(\n      std::function<void(std::shared_ptr<HTTPHeaders> trailers)> cb) {\n    EXPECT_CALL(*this, _onTrailers(testing::_))\n        .WillOnce(testing::Invoke(cb))\n        .RetiresOnSaturation();\n  }\n\n  void expectChunkHeader(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onChunkHeader(testing::_))\n          .WillOnce(testing::InvokeWithoutArgs(callback));\n    } else {\n      EXPECT_CALL(*this, _onChunkHeader(testing::_));\n    }\n  }\n\n  void expectBody(std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onBodyWithOffset(testing::_, testing::_))\n          .WillOnce(testing::InvokeWithoutArgs(callback));\n    } else {\n      EXPECT_CALL(*this, _onBodyWithOffset(testing::_, testing::_));\n    }\n  }\n\n  void expectBodyRepeatedly(\n      const std::function<void()>& callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onBodyWithOffset(testing::_, testing::_))\n          .WillRepeatedly(testing::InvokeWithoutArgs(callback));\n    } else {\n      EXPECT_CALL(*this, _onBodyWithOffset(testing::_, testing::_));\n    }\n  }\n\n  void expectBody(\n      std::function<void(uint64_t, std::shared_ptr<folly::IOBuf>)> callback) {\n    EXPECT_CALL(*this, _onBodyWithOffset(testing::_, testing::_))\n        .WillOnce(testing::Invoke(callback));\n  }\n\n  void expectDatagram(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onDatagram(testing::_))\n          .WillOnce(testing::InvokeWithoutArgs(callback));\n    } else {\n      EXPECT_CALL(*this, _onDatagram(testing::_));\n    }\n  }\n\n  void expectDatagram(\n      std::function<void(std::shared_ptr<folly::IOBuf>)> callback) {\n    EXPECT_CALL(*this, _onDatagram(testing::_))\n        .WillOnce(testing::Invoke(callback));\n  }\n\n  void expectChunkComplete(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onChunkComplete())\n          .WillOnce(testing::InvokeWithoutArgs(callback));\n    } else {\n      EXPECT_CALL(*this, _onChunkComplete());\n    }\n  }\n\n  void expectEOM(std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onEOM()).WillOnce(testing::Invoke(callback));\n    } else {\n      EXPECT_CALL(*this, _onEOM());\n    }\n  }\n\n  void expectEgressPaused(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onEgressPaused()).WillOnce(testing::Invoke(callback));\n    } else {\n      EXPECT_CALL(*this, _onEgressPaused());\n    }\n  }\n\n  void expectEgressResumed(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onEgressResumed())\n          .WillOnce(testing::Invoke(callback));\n    } else {\n      EXPECT_CALL(*this, _onEgressResumed());\n    }\n  }\n\n  void expectError(std::function<void(const HTTPException& ex)> callback =\n                       std::function<void(const HTTPException& ex)>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onError(testing::_))\n          .WillOnce(testing::Invoke(callback))\n          .RetiresOnSaturation();\n    } else {\n      EXPECT_CALL(*this, _onError(testing::_)).RetiresOnSaturation();\n    }\n  }\n\n  void expectGoaway(std::function<void(ErrorCode)> callback =\n                        std::function<void(ErrorCode)>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _onGoaway(testing::_))\n          .WillOnce(testing::Invoke(callback));\n    } else {\n      EXPECT_CALL(*this, _onGoaway(testing::_));\n    }\n  }\n\n  void expectDetachTransaction(\n      std::function<void()> callback = std::function<void()>()) {\n    if (callback) {\n      EXPECT_CALL(*this, _detachTransaction())\n          .WillOnce(testing::Invoke(callback))\n          .RetiresOnSaturation();\n    } else {\n      EXPECT_CALL(*this, _detachTransaction()).RetiresOnSaturation();\n    }\n  }\n};\n\nclass MockHTTPPushHandler\n    : public HTTPHandlerBase\n    , public HTTPTransaction::PushHandler {\n public:\n  MockHTTPPushHandler() = default;\n  MockHTTPPushHandler(HTTPTransaction& txn,\n                      HTTPMessage* msg,\n                      const folly::SocketAddress&)\n      : HTTPHandlerBase(&txn, msg) {\n  }\n\n  void setTransaction(HTTPTransaction* txn) noexcept override {\n    _setTransaction(txn);\n  }\n  MOCK_METHOD(void, _setTransaction, (HTTPTransaction*));\n\n  void detachTransaction() noexcept override {\n    _detachTransaction();\n  }\n  MOCK_METHOD(void, _detachTransaction, ());\n\n  void onError(const HTTPException& error) noexcept override {\n    _onError(error);\n  }\n  MOCK_METHOD(void, _onError, (const HTTPException&));\n\n  void onGoaway(ErrorCode errCode) noexcept override {\n    _onGoaway(errCode);\n  }\n  MOCK_METHOD(void, _onGoaway, (ErrorCode));\n\n  void onEgressPaused() noexcept override {\n    _onEgressPaused();\n  }\n  MOCK_METHOD(void, _onEgressPaused, ());\n\n  void onEgressResumed() noexcept override {\n    _onEgressResumed();\n  }\n  MOCK_METHOD(void, _onEgressResumed, ());\n\n  void sendPushHeaders(const std::string& path,\n                       const std::string& host,\n                       uint32_t content_length) {\n    HTTPMessage push;\n    push.setURL(path);\n    push.getHeaders().set(HTTP_HEADER_HOST, host);\n    push.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH,\n                          folly::to<std::string>(content_length));\n    txn_->sendHeaders(push);\n  }\n};\n\nclass MockController : public HTTPSessionController {\n public:\n  MOCK_METHOD(HTTPTransactionHandler*,\n              getRequestHandler,\n              (HTTPTransaction&, HTTPMessage* msg));\n\n  MOCK_METHOD(HTTPTransactionHandler*,\n              getParseErrorHandler,\n              (HTTPTransaction*,\n               const HTTPException&,\n               const folly::SocketAddress&));\n\n  MOCK_METHOD(HTTPTransactionHandler*,\n              getTransactionTimeoutHandler,\n              (HTTPTransaction * txn, const folly::SocketAddress&));\n\n  MOCK_METHOD(void, attachSession, (HTTPSessionBase*));\n  MOCK_METHOD(void, detachSession, (const HTTPSessionBase*));\n  MOCK_METHOD(void, onSessionCodecChange, (HTTPSessionBase*));\n  MOCK_METHOD(void, onTransportReady, (HTTPSessionBase*));\n\n  MOCK_METHOD(std::chrono::milliseconds,\n              getGracefulShutdownTimeout,\n              (),\n              (const));\n\n  MOCK_METHOD(const HeaderIndexingStrategy*,\n              getHeaderIndexingStrategy,\n              (),\n              (const));\n};\n\nclass MockUpstreamController : public HTTPUpstreamSessionController {\n public:\n  MOCK_METHOD(void, attachSession, (HTTPSessionBase*));\n  MOCK_METHOD(void, detachSession, (const HTTPSessionBase*));\n  MOCK_METHOD(void, onSessionCodecChange, (HTTPSessionBase*));\n\n  MOCK_METHOD(const HeaderIndexingStrategy*,\n              getHeaderIndexingStrategy,\n              (),\n              (const));\n};\n\nACTION_P(ExpectString, expected) {\n  EXPECT_EQ(arg1->toString(), expected);\n}\n\nACTION_P(ExpectBodyLen, expectedLen) {\n  EXPECT_EQ(arg1->computeChainDataLength(), expectedLen);\n}\n\nclass MockHTTPSessionInfoCallback : public HTTPSession::InfoCallback {\n public:\n  MOCK_METHOD(void, onCreate, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onTransportReady, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onConnectionError, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onIngressError, (const HTTPSessionBase&, ProxygenError));\n  MOCK_METHOD(void, onIngressEOF, ());\n  MOCK_METHOD(void, onRead, (const HTTPSessionBase&, size_t));\n  MOCK_METHOD(void,\n              onRead,\n              (const HTTPSessionBase&,\n               size_t,\n               folly::Optional<HTTPCodec::StreamID>));\n  MOCK_METHOD(void, onWrite, (const HTTPSessionBase&, size_t));\n  MOCK_METHOD(void, onRequestBegin, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onRequestEnd, (const HTTPSessionBase&, uint32_t));\n  MOCK_METHOD(void, onActivateConnection, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onDeactivateConnection, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onDestroy, (const HTTPSessionBase&));\n  MOCK_METHOD(void,\n              onIngressMessage,\n              (const HTTPSessionBase&, const HTTPMessage&));\n  MOCK_METHOD(void, onIngressLimitExceeded, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onIngressPaused, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onTransactionAttached, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onTransactionDetached, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onPingReplySent, (int64_t));\n  MOCK_METHOD(void, onPingReplyReceived, ());\n  MOCK_METHOD(void, onSettingsOutgoingStreamsFull, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onSettingsOutgoingStreamsNotFull, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onFlowControlWindowClosed, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onEgressBuffered, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onEgressBufferCleared, (const HTTPSessionBase&));\n  MOCK_METHOD(void, onSettings, (const HTTPSessionBase&, const SettingsList&));\n  MOCK_METHOD(void, onSettingsAck, (const HTTPSessionBase&));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPSessionTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Memory.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/TransportDirection.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_enum<Version>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeClientCodec(Version version) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::UPSTREAM,\n                                   version);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::HTTP1xCodec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeClientCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::UPSTREAM);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::HTTP2Codec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeClientCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::UPSTREAM);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::MockHTTPCodec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeClientCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>();\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_enum<Version>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeServerCodec(Version version) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::DOWNSTREAM,\n                                   (Version)version);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::HTTP1xCodec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeServerCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::DOWNSTREAM);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::HTTP2Codec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeServerCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>(proxygen::TransportDirection::DOWNSTREAM);\n}\n\ntemplate <class MyCodec, class Version>\ntypename std::enable_if<std::is_same<MyCodec, proxygen::MockHTTPCodec>::value,\n                        std::unique_ptr<MyCodec>>::type\nmakeServerCodec(Version /*version*/) {\n  return std::make_unique<MyCodec>();\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPTransactionMocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/transport/test/MockAsyncTransportCertificate.h>\n\nnamespace proxygen {\n\n#if defined(__clang__) && __clang_major__ >= 3 && __clang_minor__ >= 6\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Winconsistent-missing-override\"\n#endif\n\nclass MockHTTPTransactionTransport : public HTTPTransaction::Transport {\n public:\n  MockHTTPTransactionTransport() {\n    EXPECT_CALL(*this, getCodecNonConst())\n        .WillRepeatedly(testing::ReturnRef(mockCodec_));\n  }\n\n  MOCK_METHOD((void), pauseIngress, (HTTPTransaction*), (noexcept));\n  MOCK_METHOD((void), resumeIngress, (HTTPTransaction*), (noexcept));\n  MOCK_METHOD((void), transactionTimeout, (HTTPTransaction*), (noexcept));\n  MOCK_METHOD((void),\n              sendHeaders,\n              (HTTPTransaction*, const HTTPMessage&, HTTPHeaderSize*, bool),\n              (noexcept));\n  MOCK_METHOD((size_t),\n              sendBody,\n              (HTTPTransaction*, std::shared_ptr<folly::IOBuf>, bool, bool),\n              (noexcept));\n\n  size_t sendBody(HTTPTransaction* txn,\n                  std::unique_ptr<folly::IOBuf> iob,\n                  bool eom,\n                  bool trackLastByteFlushed) noexcept override {\n    return sendBody(txn,\n                    std::shared_ptr<folly::IOBuf>(iob.release()),\n                    eom,\n                    trackLastByteFlushed);\n  }\n\n  MOCK_METHOD((HTTPSessionBase*), getHTTPSessionBase, (), ());\n  MOCK_METHOD((size_t),\n              sendChunkHeader,\n              (HTTPTransaction*, size_t),\n              (noexcept));\n  MOCK_METHOD((size_t), sendChunkTerminator, (HTTPTransaction*), (noexcept));\n  MOCK_METHOD((size_t), sendPadding, (HTTPTransaction*, uint16_t), (noexcept));\n  MOCK_METHOD((size_t),\n              sendEOM,\n              (HTTPTransaction*, const HTTPHeaders*),\n              (noexcept));\n  MOCK_METHOD((size_t), sendAbort, (HTTPTransaction*, ErrorCode), (noexcept));\n  MOCK_METHOD((size_t),\n              sendPriority,\n              (HTTPTransaction*, const http2::PriorityUpdate&),\n              (noexcept));\n  MOCK_METHOD((size_t),\n              changePriority,\n              (HTTPTransaction*, HTTPPriority),\n              (noexcept));\n  MOCK_METHOD((void), notifyPendingEgress, (), (noexcept));\n  MOCK_METHOD((void), detach, (HTTPTransaction*), (noexcept));\n  MOCK_METHOD((size_t),\n              sendWindowUpdate,\n              (HTTPTransaction*, uint32_t),\n              (noexcept));\n  MOCK_METHOD((void), notifyIngressBodyProcessed, (uint32_t), (noexcept));\n  MOCK_METHOD((void), notifyEgressBodyBuffered, (int64_t), (noexcept));\n  MOCK_METHOD((const folly::SocketAddress&),\n              getLocalAddressNonConst,\n              (),\n              (noexcept));\n  MOCK_METHOD((HTTPTransaction*),\n              newPushedTransaction,\n              (HTTPCodec::StreamID assocStreamId,\n               HTTPTransaction::PushHandler* handler,\n               ProxygenError* error),\n              (noexcept));\n  MOCK_METHOD((HTTPTransaction*),\n              newExTransaction,\n              (HTTPTransaction::Handler * handler,\n               HTTPCodec::StreamID controlStream,\n               bool unidirectional),\n              (noexcept));\n\n  const folly::SocketAddress& getLocalAddress() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getLocalAddressNonConst();\n  }\n  MOCK_METHOD((const folly::SocketAddress&),\n              getPeerAddressNonConst,\n              (),\n              (noexcept));\n\n  const folly::SocketAddress& getPeerAddress() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getPeerAddressNonConst();\n  }\n  MOCK_METHOD(void, describe, (std::ostream&), (const));\n  MOCK_METHOD((std::chrono::seconds), getLatestIdleTime, (), (const));\n\n  MOCK_METHOD((const wangle::TransportInfo&),\n              getSetupTransportInfoNonConst,\n              (),\n              (noexcept));\n\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getSetupTransportInfoNonConst();\n  }\n\n  MOCK_METHOD(bool, getCurrentTransportInfo, (wangle::TransportInfo*));\n  MOCK_METHOD(void, getFlowControlInfo, (HTTPTransaction::FlowControlInfo*));\n\n  MOCK_METHOD(folly::Optional<HTTPPriority>, getHTTPPriority, ());\n\n  MOCK_METHOD((HTTPTransaction::Transport::Type),\n              getSessionTypeNonConst,\n              (),\n              (noexcept));\n\n  HTTPTransaction::Transport::Type getSessionType() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getSessionTypeNonConst();\n  }\n\n  MOCK_METHOD((const HTTPCodec&), getCodecNonConst, (), (noexcept));\n\n  const HTTPCodec& getCodec() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)->getCodecNonConst();\n  }\n  MOCK_METHOD(void, drain, ());\n  MOCK_METHOD(bool, isDraining, (), (const));\n  MOCK_METHOD(std::string, getSecurityProtocol, (), (const));\n\n  MOCK_METHOD(const folly::AsyncTransport*, getTransport, (), (const));\n  MOCK_METHOD(folly::AsyncTransport*, getTransport, ());\n\n  MOCK_METHOD((void),\n              addWaitingForReplaySafety,\n              (folly::AsyncTransport::ReplaySafetyCallback*),\n              (noexcept));\n  MOCK_METHOD((void),\n              removeWaitingForReplaySafety,\n              (folly::AsyncTransport::ReplaySafetyCallback*),\n              (noexcept));\n  MOCK_METHOD(bool, needToBlockForReplaySafety, (), (const));\n\n  MOCK_METHOD((const folly::AsyncTransport*),\n              getUnderlyingTransportNonConst,\n              (),\n              (noexcept));\n\n  const folly::AsyncTransport* getUnderlyingTransport()\n      const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getUnderlyingTransportNonConst();\n  }\n  MOCK_METHOD(bool, isReplaySafe, (), (const));\n  MOCK_METHOD(void, setHTTP2PrioritiesEnabled, (bool));\n  MOCK_METHOD(bool, getHTTP2PrioritiesEnabled, (), (const));\n\n  MOCK_METHOD(folly::Optional<const HTTPMessage::HTTP2Priority>,\n              getHTTPPriority,\n              (uint8_t level));\n\n  MOCK_METHOD(\n      (folly::Expected<folly::Unit, ErrorCode>),\n      peek,\n      (const folly::Function<\n          void(HTTPCodec::StreamID, uint64_t, const folly::IOBuf&) const>&));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, ErrorCode>), consume, (size_t));\n\n  MOCK_METHOD((folly::Optional<HTTPTransaction::ConnectionToken>),\n              getConnectionTokenNonConst,\n              (),\n              (noexcept));\n  folly::Optional<HTTPTransaction::ConnectionToken> getConnectionToken()\n      const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getConnectionTokenNonConst();\n  }\n\n  void setConnectionToken(HTTPTransaction::ConnectionToken token) {\n    EXPECT_CALL(*this, getConnectionTokenNonConst())\n        .WillRepeatedly(testing::Return(token));\n  }\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendDatagram,\n              (std::shared_ptr<folly::IOBuf>),\n              (noexcept));\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) override {\n    return sendDatagram(std::shared_ptr<folly::IOBuf>(datagram.release()));\n  }\n\n  MOCK_METHOD((uint16_t), getDatagramSizeLimitNonConst, (), (noexcept));\n  uint16_t getDatagramSizeLimit() const noexcept override {\n    return const_cast<MockHTTPTransactionTransport*>(this)\n        ->getDatagramSizeLimitNonConst();\n  }\n\n  MOCK_METHOD((bool), supportsWebTransport, (), (const));\n  MOCK_METHOD((folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>),\n              newWebTransportBidiStream,\n              ());\n\n  MOCK_METHOD((folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>),\n              newWebTransportUniStream,\n              ());\n\n  MOCK_METHOD(bool, canCreateUniStream, (), (override));\n  MOCK_METHOD(bool, canCreateBidiStream, (), (override));\n\n  MOCK_METHOD((folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>),\n              sendWebTransportStreamData,\n              (HTTPCodec::StreamID,\n               std::unique_ptr<folly::IOBuf>,\n               bool,\n               WebTransport::ByteEventCallback*));\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              resetWebTransportEgress,\n              (HTTPCodec::StreamID, uint32_t));\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              notifyPendingWriteOnStream,\n              (HTTPCodec::StreamID, quic::StreamWriteCallback*));\n\n  MOCK_METHOD((folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                               WebTransport::ErrorCode>),\n              readWebTransportData,\n              (HTTPCodec::StreamID, size_t));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              initiateReadOnBidiStream,\n              (HTTPCodec::StreamID, quic::StreamReadCallback*));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              pauseWebTransportIngress,\n              (HTTPCodec::StreamID));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              resumeWebTransportIngress,\n              (HTTPCodec::StreamID));\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              stopReadingWebTransportIngress,\n              (HTTPCodec::StreamID, folly::Optional<uint32_t>));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendWTMaxData,\n              (uint64_t));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendWTMaxStreams,\n              (uint64_t, bool));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendWTStreamsBlocked,\n              (uint64_t, bool));\n\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendWTDataBlocked,\n              (uint64_t));\n\n  MOCK_METHOD(bool, usesEncodedApplicationErrorCodes, ());\n\n  MOCK_METHOD(bool, isPeerInitiatedStream, (HTTPCodec::StreamID), (override));\n\n  MOCK_METHOD(void, trackEgressBodyOffset, (uint64_t, ByteEvent::EventFlags));\n\n  MockHTTPCodec mockCodec_;\n};\n\nclass MockHTTPTransaction : public HTTPTransaction {\n public:\n  MockHTTPTransaction(\n      TransportDirection direction,\n      HTTPCodec::StreamID id,\n      uint32_t seqNo,\n      // Must be const for gmock\n      const HTTP2PriorityQueue& egressQueue,\n      folly::HHWheelTimer* timer = nullptr,\n      const folly::Optional<std::chrono::milliseconds>& transactionTimeout =\n          folly::Optional<std::chrono::milliseconds>(),\n      HTTPSessionStats* stats = nullptr,\n      bool useFlowControl = false,\n      uint32_t receiveInitialWindowSize = 0,\n      uint32_t sendInitialWindowSize = 0,\n      http2::PriorityUpdate priority = http2::DefaultPriority,\n      folly::Optional<HTTPCodec::StreamID> assocStreamId = HTTPCodec::NoStream)\n      : HTTPTransaction(direction,\n                        id,\n                        seqNo,\n                        mockTransport_,\n                        const_cast<HTTP2PriorityQueue&>(egressQueue),\n                        timer,\n                        transactionTimeout,\n                        stats,\n                        useFlowControl,\n                        receiveInitialWindowSize,\n                        sendInitialWindowSize,\n                        priority,\n                        assocStreamId),\n        defaultAddress_(\"127.0.0.1\", 80) {\n    EXPECT_CALL(mockTransport_, getLocalAddressNonConst())\n        .WillRepeatedly(testing::ReturnRef(defaultAddress_));\n    EXPECT_CALL(mockTransport_, getPeerAddressNonConst())\n        .WillRepeatedly(testing::ReturnRef(defaultAddress_));\n    EXPECT_CALL(mockTransport_, getCodecNonConst())\n        .WillRepeatedly(testing::ReturnRef(mockTransport_.mockCodec_));\n    EXPECT_CALL(mockTransport_, getSetupTransportInfoNonConst())\n        .WillRepeatedly(testing::ReturnRef(setupTransportInfo_));\n    EXPECT_CALL(mockTransport_, getUnderlyingTransportNonConst())\n        .WillRepeatedly(testing::Return(&mockAsyncTransport_));\n    EXPECT_CALL(mockAsyncTransport_, getPeerCertificate())\n        .WillRepeatedly(testing::Return(&mockPeerCertificate_));\n    // Some tests unfortunately require a half-mocked HTTPTransaction.\n    ON_CALL(*this, setHandler(testing::_))\n        .WillByDefault(testing::Invoke([this](HTTPTransactionHandler* handler) {\n          this->setHandlerUnmocked(handler);\n        }));\n\n    // By default we expect canSendHeaders in test to return true\n    // Tests that specifically require canSendHeaders to return false need\n    // to set the behavior locally.  This is due to the fact that the mocked\n    // methods below imply internal state is not correctly tracked/managed\n    // in the context of tests\n    ON_CALL(*this, canSendHeaders()).WillByDefault(testing::Return(true));\n  }\n\n  MockHTTPTransaction(\n      TransportDirection direction,\n      HTTPCodec::StreamID id,\n      uint32_t seqNo,\n      // Must be const for gmock\n      const HTTP2PriorityQueue& egressQueue,\n      const WheelTimerInstance& timeout,\n      HTTPSessionStats* stats = nullptr,\n      bool useFlowControl = false,\n      uint32_t receiveInitialWindowSize = 0,\n      uint32_t sendInitialWindowSize = 0,\n      http2::PriorityUpdate priority = http2::DefaultPriority,\n      folly::Optional<HTTPCodec::StreamID> assocStreamId = HTTPCodec::NoStream)\n      : MockHTTPTransaction(direction,\n                            id,\n                            seqNo,\n                            egressQueue,\n                            timeout.getWheelTimer(),\n                            timeout.getDefaultTimeout(),\n                            stats,\n                            useFlowControl,\n                            receiveInitialWindowSize,\n                            sendInitialWindowSize,\n                            priority,\n                            assocStreamId) {\n  }\n\n  MOCK_METHOD(bool, extraResponseExpected, (), (const));\n\n  MOCK_METHOD(void, setHandler, (HTTPTransactionHandler*));\n\n  void setHandlerUnmocked(HTTPTransactionHandler* handler) {\n    HTTPTransaction::setHandler(handler);\n  }\n\n  MOCK_METHOD(bool, canSendHeaders, (), (const));\n  MOCK_METHOD(void, sendHeaders, (const HTTPMessage& headers));\n  MOCK_METHOD(void, sendHeadersWithEOM, (const HTTPMessage& headers));\n  MOCK_METHOD(void,\n              sendHeadersWithOptionalEOM,\n              (const HTTPMessage& headers, bool eom));\n  MOCK_METHOD(void, sendBody, (std::shared_ptr<folly::IOBuf>));\n\n  void sendBody(std::unique_ptr<folly::IOBuf> iob) noexcept override {\n    sendBody(std::shared_ptr<folly::IOBuf>(iob.release()));\n  }\n\n  void sendAbort() {\n    return HTTPTransaction::sendAbort();\n  }\n\n  MOCK_METHOD(void, sendPadding, (uint16_t));\n  MOCK_METHOD(void, sendChunkHeader, (size_t));\n  MOCK_METHOD(void, sendChunkTerminator, ());\n  MOCK_METHOD(void, sendTrailers, (const HTTPHeaders& trailers));\n  MOCK_METHOD(void, sendEOM, ());\n  MOCK_METHOD(void, sendAbort, (ErrorCode errorCode));\n  MOCK_METHOD(void, drop, ());\n  MOCK_METHOD(void, pauseIngress, ());\n  MOCK_METHOD(void, resumeIngress, ());\n  MOCK_METHOD(bool, handlerEgressPaused, (), (const));\n  MOCK_METHOD(HTTPTransaction*,\n              newPushedTransaction,\n              (HTTPPushTransactionHandler*, ProxygenError*));\n  MOCK_METHOD(void, setReceiveWindow, (uint32_t));\n  MOCK_METHOD(const Window&, getReceiveWindow, (), (const));\n  MOCK_METHOD(void,\n              setTransportCallback,\n              (HTTPTransaction::TransportCallback*));\n\n  MOCK_METHOD(void,\n              addWaitingForReplaySafety,\n              (folly::AsyncTransport::ReplaySafetyCallback*));\n  MOCK_METHOD(void,\n              removeWaitingForReplaySafety,\n              (folly::AsyncTransport::ReplaySafetyCallback*));\n  MOCK_METHOD(void, updateAndSendPriority, (HTTPPriority));\n  void enablePush() {\n    EXPECT_CALL(mockTransport_.mockCodec_, supportsPushTransactions())\n        .WillRepeatedly(testing::Return(true));\n  }\n\n  void setupCodec(CodecProtocol protocol) {\n    EXPECT_CALL(mockTransport_.mockCodec_, getProtocol())\n        .WillRepeatedly(testing::Return(protocol));\n  }\n  testing::NiceMock<MockHTTPTransactionTransport> mockTransport_;\n  testing::NiceMock<folly::test::MockAsyncTransport> mockAsyncTransport_;\n  testing::NiceMock<MockAsyncTransportCertificate> mockPeerCertificate_;\n  const folly::SocketAddress defaultAddress_;\n  wangle::TransportInfo setupTransportInfo_;\n};\n\nclass MockHTTPTransactionTransportCallback\n    : public HTTPTransaction::TransportCallback {\n public:\n  MockHTTPTransactionTransportCallback() = default;\n  MOCK_METHOD((void), firstHeaderByteFlushed, (), (noexcept));\n  MOCK_METHOD((void), firstByteFlushed, (), (noexcept));\n  MOCK_METHOD((void), trackedByteFlushed, (), (noexcept));\n  MOCK_METHOD((void), lastByteFlushed, (), (noexcept));\n  MOCK_METHOD((void), lastByteAcked, (std::chrono::milliseconds), (noexcept));\n  MOCK_METHOD((void), trackedByteEventTX, (const ByteEvent&), (noexcept));\n  MOCK_METHOD((void), trackedByteEventAck, (const ByteEvent&), (noexcept));\n  MOCK_METHOD((void), egressBufferEmpty, (), (noexcept));\n  MOCK_METHOD((void), headerBytesGenerated, (HTTPHeaderSize&), (noexcept));\n  MOCK_METHOD((void), headerBytesReceived, (const HTTPHeaderSize&), (noexcept));\n  MOCK_METHOD((void), bodyBytesGenerated, (size_t), (noexcept));\n  MOCK_METHOD((void), bodyBytesReceived, (size_t), (noexcept));\n  MOCK_METHOD((void), transportAppRateLimited, (), (noexcept));\n  MOCK_METHOD((void), datagramBytesGenerated, (size_t), (noexcept));\n  MOCK_METHOD((void), datagramBytesReceived, (size_t), (noexcept));\n};\n\n#if defined(__clang__) && __clang_major__ >= 3 && __clang_minor__ >= 6\n#pragma clang diagnostic pop\n#endif\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPTransactionSMTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/HTTPTransactionEgressSM.h>\n#include <proxygen/lib/http/session/HTTPTransactionIngressSM.h>\n\nusing namespace proxygen;\n\nclass EgressStateMachineFixture : public ::testing::Test {\n public:\n  EgressStateMachineFixture()\n      : instance_(HTTPTransactionEgressSM::getNewInstance()) {\n  }\n\n  void follow(HTTPTransactionEgressSM::Event e) {\n    EXPECT_TRUE(HTTPTransactionEgressSM::transit(instance_, e));\n  }\n\n  void fail(HTTPTransactionEgressSM::Event e) {\n    EXPECT_FALSE(HTTPTransactionEgressSM::transit(instance_, e));\n  }\n\n private:\n  HTTPTransactionEgressSM::State instance_;\n};\n\nclass IngressStateMachineFixture : public ::testing::Test {\n public:\n  IngressStateMachineFixture()\n      : instance_(HTTPTransactionIngressSM::getNewInstance()) {\n  }\n\n  void follow(HTTPTransactionIngressSM::Event e) {\n    EXPECT_TRUE(HTTPTransactionIngressSM::transit(instance_, e));\n  }\n\n  void fail(HTTPTransactionIngressSM::Event e) {\n    EXPECT_FALSE(HTTPTransactionIngressSM::transit(instance_, e));\n  }\n\n private:\n  HTTPTransactionIngressSM::State instance_;\n};\n\n// Egress tests\n\nTEST_F(EgressStateMachineFixture, BadEgressTransitions1) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  fail(HTTPTransactionEgressSM::Event::sendEOM);\n}\n\nTEST_F(EgressStateMachineFixture, BadEgressTransitions2) {\n  fail(HTTPTransactionEgressSM::Event::sendBody);\n}\n\nTEST_F(EgressStateMachineFixture, BadEgressTransitions3) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  fail(HTTPTransactionEgressSM::Event::sendEOM);\n}\n\nTEST_F(EgressStateMachineFixture, BadEgressTransitions4) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendChunkTerminator);\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  fail(HTTPTransactionEgressSM::Event::sendChunkTerminator);\n}\n\nTEST_F(EgressStateMachineFixture, EgressChunkedTransitions) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendChunkTerminator);\n\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendChunkTerminator);\n\n  follow(HTTPTransactionEgressSM::Event::sendChunkHeader);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendChunkTerminator);\n\n  follow(HTTPTransactionEgressSM::Event::sendTrailers);\n\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n}\n\nTEST_F(EgressStateMachineFixture, NormalEgressTransitions) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n}\n\nTEST_F(EgressStateMachineFixture, NormalEgressTransitionsWithTrailers) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  follow(HTTPTransactionEgressSM::Event::sendTrailers);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n}\n\nTEST_F(EgressStateMachineFixture, WeirdEgressTransitions) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendTrailers);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n}\n\nTEST_F(EgressStateMachineFixture, NormalDatagramTransitions) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n}\n\nTEST_F(EgressStateMachineFixture, NormalDatagramTransitionsWithTrailers) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendTrailers);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n}\n\nTEST_F(EgressStateMachineFixture, BadDatagramTransitionBeforeHeaders) {\n  fail(HTTPTransactionEgressSM::Event::sendDatagram);\n}\n\nTEST_F(EgressStateMachineFixture, BadDatagramTransitionAfterBody) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendBody);\n  fail(HTTPTransactionEgressSM::Event::sendDatagram);\n}\n\nTEST_F(EgressStateMachineFixture, BadDatagramTransitionAfterTrailers) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendTrailers);\n  fail(HTTPTransactionEgressSM::Event::sendDatagram);\n}\n\nTEST_F(EgressStateMachineFixture, BadDatagramTransitionAfterEOM) {\n  follow(HTTPTransactionEgressSM::Event::sendHeaders);\n  follow(HTTPTransactionEgressSM::Event::sendDatagram);\n  follow(HTTPTransactionEgressSM::Event::sendEOM);\n  follow(HTTPTransactionEgressSM::Event::eomFlushed);\n  fail(HTTPTransactionEgressSM::Event::sendDatagram);\n}\n\n// Ingress tests\n\nTEST_F(IngressStateMachineFixture, BadIngressTransitions1) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  fail(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, BadIngressTransitions2) {\n  fail(HTTPTransactionIngressSM::Event::onBody);\n}\n\nTEST_F(IngressStateMachineFixture, BadIngressTransitions3) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n  fail(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, BadIngressTransitions4) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onChunkComplete);\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  fail(HTTPTransactionIngressSM::Event::onChunkComplete);\n}\n\nTEST_F(IngressStateMachineFixture, IngressChunkedTransitions) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onChunkComplete);\n\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onChunkComplete);\n\n  follow(HTTPTransactionIngressSM::Event::onChunkHeader);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onChunkComplete);\n\n  follow(HTTPTransactionIngressSM::Event::onTrailers);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, NormalIngressTransitions) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, WeirdIngressTransitions) {\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onTrailers);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, NormalDatagramTransitions) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onBody);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, NormalDatagramTransitionsWithTrailers) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onTrailers);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionBodyBeforeFinal) {\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  fail(HTTPTransactionIngressSM::Event::onBody);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionTrailersBeforeFinal) {\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  fail(HTTPTransactionIngressSM::Event::onTrailers);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionEOMBeforeFinal) {\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  fail(HTTPTransactionIngressSM::Event::onEOM);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionBeforeHeaders) {\n  fail(HTTPTransactionIngressSM::Event::onDatagram);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionBeforeFinalHeaders) {\n  follow(HTTPTransactionIngressSM::Event::onNonFinalHeaders);\n  fail(HTTPTransactionIngressSM::Event::onDatagram);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionAfterTrailers) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onTrailers);\n  fail(HTTPTransactionIngressSM::Event::onDatagram);\n}\n\nTEST_F(IngressStateMachineFixture, BadDatagramTransitionAfterEOM) {\n  follow(HTTPTransactionIngressSM::Event::onFinalHeaders);\n  follow(HTTPTransactionIngressSM::Event::onDatagram);\n  follow(HTTPTransactionIngressSM::Event::onEOM);\n  fail(HTTPTransactionIngressSM::Event::onDatagram);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPTransactionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <chrono>\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <memory>\n#include <proxygen/lib/http/session/ByteEvents.h>\n#include <proxygen/lib/http/session/HTTP2PriorityQueue.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/session/test/MockHTTPTransactionObserver.h>\n\nusing namespace proxygen;\nusing namespace testing;\nusing namespace std::chrono_literals;\n\nclass HTTPTransactionObservabilityTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    txn_ = std::make_unique<HTTPTransaction>(TransportDirection::DOWNSTREAM,\n                                             HTTPCodec::StreamID(1),\n                                             0,\n                                             transport_,\n                                             txnEgressQueue_,\n                                             &evb_.timer());\n  }\n\n  void TearDown() override {\n    if (txn_) {\n      EXPECT_CALL(transport_, detach(txn_.get())).WillOnce([this] {\n        txn_.reset();\n      });\n      EXPECT_CALL(transport_, sendAbort(txn_.get(), _));\n      txn_->sendAbort();\n    }\n  }\n\n  std::unique_ptr<MockHTTPTransactionObserver> addMockTxnObserver(\n      MockHTTPTransactionObserver::EventSet eventSet) {\n    auto observer =\n        std::make_unique<NiceMock<MockHTTPTransactionObserver>>(eventSet);\n    EXPECT_CALL(*observer, attached(Eq(txn_->getObserverAccessor()))).Times(1);\n    txn_->addObserver(observer.get());\n    return std::move(observer);\n  }\n\n  std::shared_ptr<MockHTTPTransactionObserver> addMockTxnObserverShared(\n      MockHTTPTransactionObserver::EventSet eventSet) {\n    auto observer =\n        std::make_shared<NiceMock<MockHTTPTransactionObserver>>(eventSet);\n    EXPECT_CALL(*observer, attached(Eq(txn_->getObserverAccessor()))).Times(1);\n    txn_->addObserver(observer);\n    return observer;\n  }\n\n  folly::EventBase evb_;\n  HTTP2PriorityQueue txnEgressQueue_;\n  StrictMock<MockHTTPTransactionTransport> transport_;\n  std::unique_ptr<HTTPTransaction> txn_;\n};\n\nTEST_F(HTTPTransactionObservabilityTest, ObserverAttachedDetached) {\n  MockHTTPTransactionObserver::EventSet eventSet;\n  auto observer = addMockTxnObserver(eventSet);\n  EXPECT_CALL(*observer, detached(Eq(txn_->getObserverAccessor()))).Times(1);\n  txn_->removeObserver(observer.get());\n}\n\nTEST_F(HTTPTransactionObservabilityTest, ObserverAttachedDestroyed) {\n  MockHTTPTransactionObserver::EventSet eventSet;\n  auto observer = addMockTxnObserver(eventSet);\n  EXPECT_CALL(*observer, destroyed(txn_->getObserverAccessor(), _));\n  txn_ = nullptr;\n}\n\nTEST_F(HTTPTransactionObservabilityTest, ObserverAttachedDetachedSharedPtr) {\n  MockHTTPTransactionObserver::EventSet eventSet;\n  auto observer = addMockTxnObserverShared(eventSet);\n  EXPECT_CALL(*observer, detached(Eq(txn_->getObserverAccessor()))).Times(1);\n  txn_->removeObserver(observer.get());\n}\n\nTEST_F(HTTPTransactionObservabilityTest, ObserverAttachedDestroyedSharedPtr) {\n  MockHTTPTransactionObserver::EventSet eventSet;\n  auto observer = addMockTxnObserverShared(eventSet);\n  EXPECT_CALL(*observer, destroyed(txn_->getObserverAccessor(), _));\n  txn_ = nullptr;\n}\n\nTEST_F(HTTPTransactionObservabilityTest,\n       OnBytesEvent_ObserverNotSubscribedToTxnBytesEvent) {\n  auto observer =\n      addMockTxnObserver(MockHTTPTransactionObserver::EventSetBuilder()\n                             .build() /* no events set */);\n  EXPECT_CALL(*observer, onBytesEvent(_, _)).Times(0);\n  txn_->onEgressHeaderFirstByte();\n}\n\nTEST_F(HTTPTransactionObservabilityTest,\n       OnBytesEvent_ObserverSubscribedToTxnBytesEvent) {\n  auto observer = addMockTxnObserver(\n      MockHTTPTransactionObserver::EventSetBuilder()\n          .enable(MockHTTPTransactionObserver::Events::TxnBytes)\n          .build());\n  InSequence s;\n  EXPECT_CALL(*observer, onBytesEvent(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPTransactionObserverAccessor* observerAccessor,\n              const proxygen::HTTPTransactionObserverInterface::TxnBytesEvent&\n                  event) {\n            EXPECT_THAT(observerAccessor, Eq(txn_->getObserverAccessor()));\n            EXPECT_THAT(event.type,\n                        Eq(proxygen::HTTPTransactionObserverInterface::\n                               TxnBytesEvent::Type::FIRST_HEADER_BYTE_WRITE));\n          }));\n  EXPECT_CALL(*observer, onBytesEvent(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPTransactionObserverAccessor* observerAccessor,\n              const proxygen::HTTPTransactionObserverInterface::TxnBytesEvent&\n                  event) {\n            EXPECT_THAT(observerAccessor, Eq(txn_->getObserverAccessor()));\n            EXPECT_THAT(event.type,\n                        Eq(proxygen::HTTPTransactionObserverInterface::\n                               TxnBytesEvent::Type::FIRST_BODY_BYTE_WRITE));\n          }));\n  EXPECT_CALL(*observer, onBytesEvent(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPTransactionObserverAccessor* observerAccessor,\n              const proxygen::HTTPTransactionObserverInterface::TxnBytesEvent&\n                  event) {\n            EXPECT_THAT(observerAccessor, Eq(txn_->getObserverAccessor()));\n            EXPECT_THAT(event.type,\n                        Eq(proxygen::HTTPTransactionObserverInterface::\n                               TxnBytesEvent::Type::LAST_BODY_BYTE_WRITE));\n          }));\n  EXPECT_CALL(*observer, onBytesEvent(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPTransactionObserverAccessor* observerAccessor,\n              const proxygen::HTTPTransactionObserverInterface::TxnBytesEvent&\n                  event) {\n            EXPECT_THAT(observerAccessor, Eq(txn_->getObserverAccessor()));\n            EXPECT_THAT(event.type,\n                        Eq(proxygen::HTTPTransactionObserverInterface::\n                               TxnBytesEvent::Type::FIRST_BODY_BYTE_ACK));\n          }));\n  EXPECT_CALL(*observer, onBytesEvent(_, _))\n      .WillOnce(Invoke(\n          [&](HTTPTransactionObserverAccessor* observerAccessor,\n              const proxygen::HTTPTransactionObserverInterface::TxnBytesEvent&\n                  event) {\n            EXPECT_THAT(observerAccessor, Eq(txn_->getObserverAccessor()));\n            EXPECT_THAT(event.type,\n                        Eq(proxygen::HTTPTransactionObserverInterface::\n                               TxnBytesEvent::Type::LAST_BODY_BYTE_ACK));\n          }));\n  txn_->onEgressHeaderFirstByte();\n  txn_->onEgressBodyFirstByte();\n  txn_->onEgressBodyLastByte();\n  txn_->onEgressTrackedByteEventAck(\n      {0 /* byteOffset */, ByteEvent::FIRST_BYTE});\n  txn_->onEgressLastByteAck(42ms /* latency */);\n\n  // Currently, observers are not notified of egress bytes acked for non-first\n  // byte\n  EXPECT_CALL(*observer, onBytesEvent(_, _)).Times(0);\n  txn_->onEgressTrackedByteEventAck(\n      {2 /* byteOffset */, ByteEvent::TRACKED_BYTE});\n  txn_->onEgressTrackedByteEventAck({3 /* byteOffset */, ByteEvent::LAST_BYTE});\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPTransactionWebTransportTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n\nusing namespace testing;\nusing WTFCState = proxygen::WebTransport::FCState;\nnamespace {\nconstexpr uint32_t WT_APP_ERROR_1 = 19;\nconstexpr uint32_t WT_APP_ERROR_2 = 77;\nfolly::Optional<uint32_t> makeOpt(uint32_t n) {\n  return n;\n}\n} // namespace\n\nnamespace proxygen::test {\nclass HTTPTransactionWebTransportTest : public testing::Test {\n public:\n  void SetUp() override {\n    setup(/*withHandler=*/true);\n  }\n\n  void setup(bool withHandler) {\n    makeTxn();\n    EXPECT_CALL(transport_, describe(_)).WillRepeatedly(Return());\n    EXPECT_CALL(transport_, supportsWebTransport())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(transport_, usesEncodedApplicationErrorCodes())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(transport_, canCreateUniStream()).WillRepeatedly(Return(true));\n    EXPECT_CALL(transport_, canCreateBidiStream()).WillRepeatedly(Return(true));\n    // Set up isPeerInitiatedStream for mock test stream ids\n    // In these tests:\n    //   id 0: self-initiated bidi stream\n    //   id 1: peer-initiated bidi stream\n    //   id 2: used for both peer-initiated AND self-initiated uni streams\n    //\n    // We return true for ids 1 and 2. This works because:\n    // - For id 1: correctly identifies peer-initiated bidi streams\n    // - For id 2: correctly identifies peer-initiated uni streams\n    // - For id 2: incorrectly returns true for self-initiated uni streams,\n    //   but self-initiated egress-only streams should never trigger the credit\n    //   granting logic (which only runs when ingress/read handles close)\n    EXPECT_CALL(transport_, isPeerInitiatedStream(_))\n        .WillRepeatedly(\n            [](HTTPCodec::StreamID id) { return id == 1 || id == 2; });\n    if (withHandler) {\n      handler_.expectTransaction();\n      txn_->setHandler(&handler_);\n      handler_.expectDetachTransaction();\n    }\n    EXPECT_CALL(transport_, sendHeaders(txn_.get(), _, _, false));\n    EXPECT_CALL(transport_, notifyPendingEgress()).Times(AtLeast(0));\n    EXPECT_CALL(transport_, detach(txn_.get())).WillOnce([this] {\n      txn_.reset();\n    });\n    HTTPMessage req;\n    req.setHTTPVersion(1, 1);\n    req.setUpgradeProtocol(\"webtransport\");\n    req.setMethod(HTTPMethod::CONNECT);\n    req.setURL(\"/webtransport\");\n    req.getHeaders().set(HTTP_HEADER_HOST, \"www.facebook.com\");\n    txn_->sendHeaders(req);\n    auto resp = std::make_unique<HTTPMessage>();\n    resp->setHTTPVersion(1, 1);\n    resp->setStatusCode(200);\n    if (withHandler) {\n      handler_.expectHeaders();\n    }\n    txn_->onIngressHeadersComplete(std::move(resp));\n\n    wt_ = txn_->getWebTransport();\n    EXPECT_NE(wt_, nullptr);\n    auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n    ASSERT_NE(wtImpl, nullptr);\n    wtImpl->setFlowControlLimits(0, kDefaultWTReceiveWindow);\n  }\n\n  void TearDown() override {\n    if (txn_) {\n      EXPECT_CALL(transport_, sendAbort(txn_.get(), _));\n      txn_->sendAbort();\n    }\n  }\n\n protected:\n  folly::EventBase eventBase_;\n  testing::StrictMock<MockHTTPTransactionTransport> transport_;\n  testing::StrictMock<MockHTTPHandler> handler_;\n  HTTP2PriorityQueue txnEgressQueue_;\n  std::unique_ptr<HTTPTransaction> txn_;\n\n  static void readCallback(\n      folly::Try<WebTransport::StreamData> streamData,\n      bool expectException,\n      size_t expectedLength,\n      bool expectFin,\n      folly::Optional<uint32_t> expectedErrorCode = folly::none) {\n    VLOG(4) << __func__ << \" expectException=\" << uint64_t(expectException)\n            << \" expectedLength=\" << expectedLength\n            << \" expectFin=\" << expectFin;\n    EXPECT_EQ(streamData.hasException(), expectException);\n    if (expectException || streamData.hasException()) {\n      if (expectedErrorCode) {\n        auto wtEx = streamData.tryGetExceptionObject<WebTransport::Exception>();\n        EXPECT_NE(wtEx, nullptr);\n        if (wtEx) {\n          EXPECT_EQ(wtEx->error, *expectedErrorCode);\n        }\n      }\n      return;\n    }\n    if (streamData->data) {\n      EXPECT_EQ(streamData->data->computeChainDataLength(), expectedLength);\n    } else {\n      EXPECT_EQ(expectedLength, 0);\n    }\n    EXPECT_EQ(streamData->fin, expectFin);\n  }\n\n  HTTPTransaction& makeTxn() {\n    txn_ = std::make_unique<HTTPTransaction>(TransportDirection::DOWNSTREAM,\n                                             HTTPCodec::StreamID(1),\n                                             0,\n                                             transport_,\n                                             txnEgressQueue_,\n                                             &evb_.timer());\n    return *txn_;\n  }\n  WebTransport* wt_{nullptr};\n  folly::EventBase evb_;\n};\n\nclass MockDeliveryCallback : public WebTransport::ByteEventCallback {\n public:\n  MOCK_METHOD(void, onByteEvent, (quic::StreamId, uint64_t), (noexcept));\n\n  MOCK_METHOD(void,\n              onByteEventCanceled,\n              (quic::StreamId, uint64_t),\n              (noexcept));\n};\n\nTEST_F(HTTPTransactionWebTransportTest, CreateStreams) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Initialize self flow control to 0, then set to 4\n  wtImpl->setSelfBidiStreamFlowControl(0);\n  wtImpl->setSelfUniStreamFlowControl(0);\n  wtImpl->onMaxStreams(4, true);\n  wtImpl->onMaxStreams(4, false);\n\n  // Set peer flow control (for granting credit to peer)\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportBidiStream()).WillOnce(Return(0));\n  EXPECT_CALL(transport_, initiateReadOnBidiStream(_, _))\n      .WillOnce(Return(folly::unit));\n  auto res = wt_->createBidiStream();\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_CALL(transport_, resetWebTransportEgress(0, WT_APP_ERROR_1))\n      .WillOnce(Return(folly::unit));\n  EXPECT_EQ(res->writeHandle->getID(), 0);\n  res->writeHandle->resetStream(WT_APP_ERROR_1);\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n  res->readHandle->stopSending(WT_APP_ERROR_2);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto res2 = wt_->createUniStream();\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, true, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n\n  wtImpl->onMaxData(1000);\n  res2.value()->writeStreamData(nullptr, true, nullptr);\n\n  // Try creating streams but fail at transport\n  EXPECT_CALL(transport_, newWebTransportBidiStream())\n      .WillOnce(Return(folly::makeUnexpected(\n          WebTransport::ErrorCode::STREAM_CREATION_ERROR)));\n  EXPECT_CALL(transport_, sendWTStreamsBlocked(4, true));\n  EXPECT_EQ(wt_->createBidiStream().error(),\n            WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  EXPECT_CALL(transport_, newWebTransportUniStream())\n      .WillOnce(Return(folly::makeUnexpected(\n          WebTransport::ErrorCode::STREAM_CREATION_ERROR)));\n  EXPECT_CALL(transport_, sendWTStreamsBlocked(4, false));\n  EXPECT_EQ(wt_->createUniStream().error(),\n            WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n\n  EXPECT_CALL(transport_, sendEOM(txn_.get(), nullptr));\n  wt_->closeSession();\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReadStream) {\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  auto implHandle = txn_->onWebTransportUniStream(0);\n  EXPECT_NE(readHandle, nullptr);\n\n  // read with no data buffered\n  auto fut = readHandle->readStreamData()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   readCallback(std::move(streamData), false, 10, false);\n                 });\n  EXPECT_FALSE(fut.isReady());\n\n  EXPECT_CALL(transport_, sendWTMaxData(kDefaultWTReceiveWindow + 10))\n      .WillOnce(Return(folly::unit));\n  implHandle->dataAvailable(makeBuf(10), false);\n  EXPECT_FALSE(fut.isReady());\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n\n  // buffer data with no read\n  implHandle->dataAvailable(makeBuf(32768), false);\n\n  // full buffer, blocked\n  EXPECT_CALL(transport_, pauseWebTransportIngress(0));\n  EXPECT_CALL(transport_, readWebTransportData(_, _)).WillOnce(Invoke([] {\n    return std::make_pair(makeBuf(32768), false);\n  }));\n  EXPECT_CALL(transport_, sendWTMaxData(kDefaultWTReceiveWindow + 10 + 65536))\n      .WillOnce(Return(folly::unit));\n  implHandle->readAvailable(0);\n  EXPECT_CALL(transport_, resumeWebTransportIngress(0));\n  fut = readHandle->readStreamData()\n            .via(&eventBase_)\n            .thenTry([](auto streamData) {\n              readCallback(std::move(streamData), false, 65536, false);\n            });\n  EXPECT_FALSE(fut.isReady());\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n\n  // fin\n  fut = readHandle->readStreamData()\n            .via(&eventBase_)\n            .thenTry([](auto streamData) {\n              readCallback(std::move(streamData), false, 0, true);\n            });\n  EXPECT_FALSE(fut.isReady());\n\n  // it gets stopReadingWebTransportIngress when the EOF is read out\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n\n  implHandle->dataAvailable(nullptr, true);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReadStreamBufferedError) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  auto implHandle = txn_->onWebTransportUniStream(2);\n  EXPECT_NE(readHandle, nullptr);\n\n  implHandle->readError(implHandle->getID(),\n                        quic::QuicError(quic::ApplicationErrorCode(\n                            WebTransport::toHTTPErrorCode(WT_APP_ERROR_2))));\n\n  // read with buffered error - simulate coming directly from QUIC with\n  // encoded error code\n  auto fut =\n      readHandle->readStreamData()\n          .via(&eventBase_)\n          .thenTry([](auto streamData) {\n            readCallback(std::move(streamData), true, 0, false, WT_APP_ERROR_2);\n          });\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReadStreamError) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  auto implHandle = txn_->onWebTransportUniStream(2);\n  EXPECT_NE(readHandle, nullptr);\n\n  // read with nothing queued\n  auto fut =\n      readHandle->readStreamData()\n          .via(&eventBase_)\n          .thenTry([](auto streamData) {\n            readCallback(std::move(streamData), true, 0, false, WT_APP_ERROR_2);\n          });\n  EXPECT_FALSE(fut.isReady());\n\n  // Don't encode the error, it will be passed directly\n  implHandle->readError(\n      implHandle->getID(),\n      quic::QuicError(quic::ApplicationErrorCode(WT_APP_ERROR_2)));\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReadStreamCancel) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  txn_->onWebTransportUniStream(2);\n  EXPECT_NE(readHandle, nullptr);\n\n  // Get the read future\n  auto fut = readHandle->readStreamData();\n\n  // Cancel the future, the transport will get a STOP_SENDING\n  EXPECT_CALL(\n      transport_,\n      stopReadingWebTransportIngress(2, makeOpt(WebTransport::kInternalError)))\n      .WillOnce(Return(folly::unit));\n  fut.cancel();\n  EXPECT_TRUE(fut.isReady());\n  EXPECT_NE(fut.result().tryGetExceptionObject<folly::FutureCancellation>(),\n            nullptr);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, WriteFails) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto res = wt_->createUniStream();\n  EXPECT_TRUE(res.hasValue());\n\n  wtImpl->onMaxData(1000);\n\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(\n          Return(folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR)));\n  EXPECT_EQ(res.value()->writeStreamData(makeBuf(10), false, nullptr).error(),\n            WebTransport::ErrorCode::SEND_ERROR);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, WriteStreamPauseStopSending) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // Grant flow control space before any writes\n  wtImpl->onMaxData(1000);\n\n  // Block write, then resume\n  bool ready = false;\n  quic::StreamWriteCallback* wcb{nullptr};\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::BLOCKED));\n  auto res = writeHandle.value()->writeStreamData(makeBuf(10), false, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_CALL(transport_, notifyPendingWriteOnStream(2, testing::_))\n      .WillOnce(DoAll(SaveArg<1>(&wcb), Return(folly::unit)));\n  writeHandle.value()\n      ->awaitWritable()\n      .value()\n      .via(&eventBase_)\n      .thenTry([&ready](auto writeReady) {\n        EXPECT_FALSE(writeReady.hasException());\n        ready = true;\n      });\n  EXPECT_FALSE(ready);\n  wcb->onStreamWriteReady(2, 65536);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(ready);\n\n  // Block write/stop sending\n  ready = false;\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::BLOCKED));\n  auto res2 = writeHandle.value()->writeStreamData(makeBuf(10), false, nullptr);\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_CALL(transport_, notifyPendingWriteOnStream(2, testing::_))\n      .WillOnce(DoAll(SaveArg<1>(&wcb), Return(folly::unit)));\n  writeHandle.value()\n      ->awaitWritable()\n      .value()\n      .via(&eventBase_)\n      .thenTry([&ready, &writeHandle, this](auto writeReady) {\n        EXPECT_TRUE(\n            writeReady.withException([](const WebTransport::Exception& ex) {\n              EXPECT_EQ(ex.error, WT_APP_ERROR_2);\n            }));\n        EXPECT_CALL(transport_, resetWebTransportEgress(2, WT_APP_ERROR_1));\n        writeHandle.value()->resetStream(WT_APP_ERROR_1);\n        ready = true;\n      });\n  EXPECT_FALSE(ready);\n  txn_->onWebTransportStopSending(2, WT_APP_ERROR_2);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(ready);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, AwaitWritableCancel) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // Block write\n  quic::StreamWriteCallback* wcb{nullptr};\n  EXPECT_CALL(transport_, notifyPendingWriteOnStream(2, testing::_))\n      .WillOnce(DoAll(SaveArg<1>(&wcb), Return(folly::unit)));\n  // awaitWritable\n  auto fut = writeHandle.value()->awaitWritable().value();\n\n  // Cancel future\n  fut.cancel();\n  EXPECT_TRUE(fut.isReady());\n  EXPECT_TRUE(fut.hasException());\n  EXPECT_NE(fut.result().tryGetExceptionObject<folly::FutureCancellation>(),\n            nullptr);\n\n  // awaitWritable again\n  bool ready = false;\n  EXPECT_CALL(transport_, notifyPendingWriteOnStream(2, testing::_))\n      .WillOnce(DoAll(SaveArg<1>(&wcb), Return(folly::unit)));\n  writeHandle.value()\n      ->awaitWritable()\n      .value()\n      .via(&eventBase_)\n      .thenTry([&ready, &writeHandle, this](auto writeReady) {\n        EXPECT_TRUE(writeReady.hasValue());\n        EXPECT_CALL(transport_, resetWebTransportEgress(2, WT_APP_ERROR_1));\n        writeHandle.value()->resetStream(WT_APP_ERROR_1);\n        ready = true;\n      });\n  EXPECT_FALSE(ready);\n\n  // Resume - only happens once because the reset, maybe?\n  wtImpl->onMaxData(1000);\n  wcb->onStreamWriteReady(2, 65536);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(ready);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, BidiStreamEdgeCases) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  WebTransport::BidiStreamHandle streamHandle{};\n  EXPECT_CALL(handler_, onWebTransportBidiStream(_, _))\n      .WillOnce(SaveArg<1>(&streamHandle));\n\n  auto bidiHandle = txn_->onWebTransportBidiStream(0);\n  EXPECT_NE(streamHandle.readHandle, nullptr);\n  EXPECT_NE(streamHandle.writeHandle, nullptr);\n\n  // deliver EOF before read\n  bidiHandle.readHandle->dataAvailable(nullptr, true);\n\n  // it gets stopReadingWebTransportIngress when the EOF is read out\n  EXPECT_CALL(transport_,\n              stopReadingWebTransportIngress(0, folly::Optional<uint32_t>()));\n\n  EXPECT_CALL(transport_, sendWTMaxData(kDefaultWTReceiveWindow)).Times(0);\n  auto fut = streamHandle.readHandle->readStreamData()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   readCallback(std::move(streamData), false, 0, true);\n                 });\n  EXPECT_FALSE(fut.isReady());\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n\n  // Cancellation handling\n  folly::CancellationCallback writeCancel(\n      streamHandle.writeHandle->getCancelToken(), [&streamHandle] {\n        // Write cancelled:\n        // We can retrieve the stop sending code from the handle\n        EXPECT_EQ(streamHandle.writeHandle->exception()->error, WT_APP_ERROR_2);\n        // attempt to write, will error, but don't reset the stream\n        auto res = streamHandle.writeHandle->writeStreamData(\n            makeBuf(10), true, nullptr);\n        EXPECT_TRUE(res.hasError());\n        EXPECT_EQ(res.error(), WebTransport::ErrorCode::STOP_SENDING);\n      });\n  // Deliver SS\n  txn_->onWebTransportStopSending(0, WT_APP_ERROR_2);\n  EXPECT_CALL(transport_,\n              resetWebTransportEgress(0, WebTransport::kInternalError));\n  // Note the egress stream was not reset, will be reset when the txn detaches\n}\n\nTEST_F(HTTPTransactionWebTransportTest, StreamDetachWithOpenStreams) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportBidiStream()).WillOnce(Return(0));\n  EXPECT_CALL(transport_, initiateReadOnBidiStream(_, _))\n      .WillOnce(Return(folly::unit));\n  auto res = wt_->createBidiStream();\n  EXPECT_FALSE(res.hasError());\n  bool readCancelled = false;\n  bool writeCancelled = false;\n  folly::CancellationCallback readCancel(\n      res->readHandle->getCancelToken(), [&readCancelled, &res, this] {\n        res->readHandle->readStreamData()\n            .via(&eventBase_)\n            .thenValue([](auto) {})\n            .thenError(folly::tag_t<const WebTransport::Exception&>{},\n                       [](auto const& ex) {\n                         VLOG(4) << \"readCancelled\";\n                         EXPECT_EQ(ex.error, WebTransport::kInternalError);\n                       });\n        readCancelled = true;\n      });\n  folly::CancellationCallback writeCancel(res->writeHandle->getCancelToken(),\n                                          [&] {\n                                            VLOG(4) << \"writeCancelled\";\n                                            writeCancelled = true;\n                                          });\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, \"aborted\");\n  handler_.expectError();\n  EXPECT_CALL(transport_, resetWebTransportEgress(0, _));\n  EXPECT_CALL(\n      transport_,\n      stopReadingWebTransportIngress(0, makeOpt(WebTransport::kInternalError)));\n  txn_->onError(ex);\n  EXPECT_TRUE(readCancelled);\n  EXPECT_TRUE(writeCancelled);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, NoHandler) {\n  TearDown();\n  setup(/*withHandler=*/false);\n  EXPECT_CALL(\n      transport_,\n      stopReadingWebTransportIngress(0, makeOpt(WebTransport::kInternalError)))\n      .RetiresOnSaturation();\n  EXPECT_CALL(transport_,\n              resetWebTransportEgress(0, WebTransport::kInternalError));\n  txn_->onWebTransportBidiStream(0);\n  EXPECT_CALL(\n      transport_,\n      stopReadingWebTransportIngress(1, makeOpt(WebTransport::kInternalError)));\n  txn_->onWebTransportUniStream(1);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, StreamIDAPIs) {\n  EXPECT_CALL(transport_, newWebTransportBidiStream()).WillOnce(Return(0));\n  quic::StreamReadCallback* quicReadCallback{nullptr};\n  EXPECT_CALL(transport_, initiateReadOnBidiStream(_, _))\n      .WillOnce(DoAll(SaveArg<1>(&quicReadCallback), Return(folly::unit)));\n  auto res = wt_->createBidiStream();\n  auto id = res->readHandle->getID();\n\n  // read by id\n  auto fut = wt_->readStreamData(id)\n                 .value()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   readCallback(std::move(streamData), false, 10, 0);\n                 });\n  EXPECT_FALSE(fut.isReady());\n  EXPECT_CALL(transport_, readWebTransportData(_, _)).WillOnce(Invoke([] {\n    return std::make_pair(makeBuf(10), false);\n  }));\n  EXPECT_CALL(transport_, sendWTMaxData(kDefaultWTReceiveWindow + 10))\n      .WillOnce(Return(folly::unit));\n  quicReadCallback->readAvailable(id);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n\n  // stopSending by ID\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n  wt_->stopSending(id, WT_APP_ERROR_1);\n\n  // write by ID\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->onMaxData(1000);\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(id, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  auto res2 = wt_->writeStreamData(id, makeBuf(10), false, nullptr);\n  EXPECT_TRUE(res2.hasValue());\n\n  // resetStream by ID\n  EXPECT_CALL(transport_, resetWebTransportEgress(id, WT_APP_ERROR_2));\n  wt_->resetStream(id, WT_APP_ERROR_2);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, InvalidStreamIDAPIs) {\n  uint64_t id = 7;\n\n  EXPECT_EQ(wt_->stopSending(id, WT_APP_ERROR_1).error(),\n            WebTransport::ErrorCode::INVALID_STREAM_ID);\n  EXPECT_EQ(wt_->resetStream(id, WT_APP_ERROR_2).error(),\n            WebTransport::ErrorCode::INVALID_STREAM_ID);\n  EXPECT_EQ(wt_->readStreamData(id).error(),\n            WebTransport::ErrorCode::INVALID_STREAM_ID);\n  EXPECT_EQ(wt_->writeStreamData(id, makeBuf(10), false, nullptr).error(),\n            WebTransport::ErrorCode::INVALID_STREAM_ID);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, SendDatagram) {\n  EXPECT_CALL(transport_, sendDatagram(_)).WillOnce(Return(folly::unit));\n  EXPECT_TRUE(wt_->sendDatagram(makeBuf(100)));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, RefreshTimeout) {\n  txn_->setIdleTimeout(std::chrono::milliseconds(100));\n  evb_.runAfterDelay(\n      [this] {\n        WebTransport::StreamReadHandle* readHandle{nullptr};\n        EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n            .WillOnce(SaveArg<1>(&readHandle));\n\n        txn_->onWebTransportUniStream(0);\n        EXPECT_NE(readHandle, nullptr);\n      },\n      50);\n  evb_.runAfterDelay(\n      [this] {\n        EXPECT_CALL(transport_,\n                    stopReadingWebTransportIngress(\n                        0, makeOpt(WebTransport::kSessionGone)))\n            .WillOnce(Return(folly::unit));\n        handler_.expectEOM();\n        txn_->onIngressEOM();\n        EXPECT_CALL(transport_, sendEOM(txn_.get(), nullptr));\n        wt_->closeSession();\n      },\n      150);\n  evb_.loop();\n}\n\nTEST_F(HTTPTransactionWebTransportTest, StopSendingThenAbort) {\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  txn_->onWebTransportUniStream(0);\n  EXPECT_NE(readHandle, nullptr);\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n  readHandle->stopSending(WT_APP_ERROR_2);\n  // there's no way to abort this stream anymore.  stopSending removes the\n  // read callback.\n  // Questioning my life choices -- there's no way now to get the reset error\n  // code.\n  // 1. Should you be able to resetStream() while write is outstanding? (yes)\n  // 2. Should you be able to stopSending() when read is outstanding?\n  eventBase_.loopOnce();\n}\n\nTEST_F(HTTPTransactionWebTransportTest, WriteBufferingBasic) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // no flow control space initially, writes should be buffered\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res = writeHandle.value()->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_EQ(*res, WTFCState::BLOCKED);\n\n  // grant flow control and verify buffered data is flushed\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(1000);\n\n  // write more data with available flow control, should send immediately\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  res = writeHandle.value()->writeStreamData(makeBuf(50), false, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_EQ(*res, WTFCState::UNBLOCKED);\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, WriteBufferingEOF) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // write data with EOF but no flow control, should buffer\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res = writeHandle.value()->writeStreamData(makeBuf(100), true, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_EQ(*res, WTFCState::BLOCKED);\n\n  // grant flow control, should flush buffered data with EOF\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, true, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(1000);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, WriteBufferingPartialSend) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // write large data without flow control, should buffer\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res =\n      writeHandle.value()->writeStreamData(makeBuf(1000), false, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_EQ(*res, WTFCState::BLOCKED);\n\n  // grant partial flow control, should flush partial data\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  EXPECT_CALL(transport_, sendWTDataBlocked(500));\n  wtImpl->onMaxData(500);\n\n  // grant more flow control, should flush remaining buffered data\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(1000);\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, StreamWriteReadyCallback) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // write data without flow control, should buffer\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res = writeHandle.value()->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(res.hasValue());\n  EXPECT_EQ(*res, WTFCState::BLOCKED);\n\n  bool writeReady = false;\n  quic::StreamWriteCallback* wcb{nullptr};\n  EXPECT_CALL(transport_, notifyPendingWriteOnStream(2, testing::_))\n      .WillOnce(DoAll(SaveArg<1>(&wcb), Return(folly::unit)));\n  writeHandle.value()\n      ->awaitWritable()\n      .value()\n      .via(&eventBase_)\n      .thenTry([&writeReady](const auto& result) {\n        EXPECT_FALSE(result.hasException());\n        writeReady = true;\n      });\n  EXPECT_FALSE(writeReady);\n\n  // trigger onStreamWriteReady, should flush buffered writes and resolve\n  // promise\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(1000);\n  wcb->onStreamWriteReady(2, 65536);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(writeReady);\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, FlushBufferedWritesMultiple) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  auto dcb1 = std::make_unique<StrictMock<MockDeliveryCallback>>();\n  auto dcb2 = std::make_unique<StrictMock<MockDeliveryCallback>>();\n  auto dcb3 = std::make_unique<StrictMock<MockDeliveryCallback>>();\n\n  // write multiple chunks without flow control, should buffer into three\n  // entries\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res1 =\n      writeHandle.value()->writeStreamData(makeBuf(100), false, dcb1.get());\n  EXPECT_TRUE(res1.hasValue());\n  EXPECT_EQ(*res1, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res2 =\n      writeHandle.value()->writeStreamData(makeBuf(200), false, dcb2.get());\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_EQ(*res2, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res3 =\n      writeHandle.value()->writeStreamData(makeBuf(150), false, dcb3.get());\n  EXPECT_TRUE(res3.hasValue());\n  EXPECT_EQ(*res3, WTFCState::BLOCKED);\n\n  // grant flow control, should flush first buffered entry\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, dcb1.get()))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  EXPECT_CALL(transport_, sendWTDataBlocked(100));\n  wtImpl->onMaxData(100);\n\n  // grant more flow control, should flush second buffered entry\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, dcb2.get()))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  EXPECT_CALL(transport_, sendWTDataBlocked(300));\n  wtImpl->onMaxData(300);\n\n  // grant more flow control, should flush third buffered entry\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, dcb3.get()))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(500);\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, CoalescedWrites) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  // write multiple chunks without flow control, should buffer all into one\n  // coalesced entry because of no delivery callbacks\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res1 =\n      writeHandle.value()->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(res1.hasValue());\n  EXPECT_EQ(*res1, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res2 =\n      writeHandle.value()->writeStreamData(makeBuf(200), false, nullptr);\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_EQ(*res2, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res3 =\n      writeHandle.value()->writeStreamData(makeBuf(150), false, nullptr);\n  EXPECT_TRUE(res3.hasValue());\n  EXPECT_EQ(*res3, WTFCState::BLOCKED);\n\n  // grant flow control, should flush the entire buffered entry\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(1000);\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, CoalescedWritesPartialFlowControl) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/4);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  EXPECT_FALSE(writeHandle.hasError());\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res1 =\n      writeHandle.value()->writeStreamData(makeBuf(100), false, nullptr);\n  EXPECT_TRUE(res1.hasValue());\n  EXPECT_EQ(*res1, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res2 =\n      writeHandle.value()->writeStreamData(makeBuf(200), false, nullptr);\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_EQ(*res2, WTFCState::BLOCKED);\n\n  EXPECT_CALL(transport_, sendWTDataBlocked(0));\n  auto res3 =\n      writeHandle.value()->writeStreamData(makeBuf(150), false, nullptr);\n  EXPECT_TRUE(res3.hasValue());\n  EXPECT_EQ(*res3, WTFCState::BLOCKED);\n\n  // due to coalescing with nullptr callbacks, we expect only 2 transport calls\n  // (not 3) when flow control is granted in non-aligned chunks\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  EXPECT_CALL(transport_, sendWTDataBlocked(250));\n  wtImpl->onMaxData(250);\n\n  EXPECT_CALL(transport_,\n              sendWebTransportStreamData(2, testing::_, false, nullptr))\n      .WillOnce(Return(WTFCState::UNBLOCKED));\n  wtImpl->onMaxData(450);\n\n  EXPECT_CALL(transport_, resetWebTransportEgress(_, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, RecvFlowControlCloseSession) {\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  auto implHandle = txn_->onWebTransportUniStream(0);\n  EXPECT_NE(readHandle, nullptr);\n\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  EXPECT_FALSE(wtImpl->isSessionTerminated());\n  EXPECT_FALSE(wtImpl->getSessionCloseError().has_value());\n\n  EXPECT_CALL(transport_, readWebTransportData(0, 65535))\n      .WillOnce(Invoke([](auto, auto) {\n        return std::make_pair(makeBuf(2 * kDefaultWTReceiveWindow), false);\n      }));\n  EXPECT_CALL(\n      transport_,\n      stopReadingWebTransportIngress(0, makeOpt(WebTransport::kSessionGone)))\n      .WillOnce(Return(folly::unit));\n\n  // This tests both paths:\n  // 1. dataAvailable returns SESSION_CLOSED when flow control is exceeded\n  // 2. readAvailable calls terminateSession when dataAvailable returns\n  // SESSION_CLOSED\n  implHandle->readAvailable(0);\n\n  EXPECT_TRUE(wtImpl->isSessionTerminated());\n  EXPECT_TRUE(wtImpl->getSessionCloseError().has_value());\n  EXPECT_EQ(wtImpl->getSessionCloseError().value(),\n            WebTransport::kInternalError);\n\n  EXPECT_EQ(wt_->createUniStream().error(),\n            WebTransport::ErrorCode::SESSION_TERMINATED);\n  EXPECT_EQ(wt_->createBidiStream().error(),\n            WebTransport::ErrorCode::SESSION_TERMINATED);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ClientUniStreamRst) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  EXPECT_CALL(transport_, newWebTransportUniStream()).WillOnce(Return(2));\n  auto writeHandle = wt_->createUniStream();\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/1,\n      /*targetConcurrentStreams=*/4);\n  EXPECT_FALSE(writeHandle.hasError());\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(false));\n\n  // For unidirectional egress streams (created by the client), we should NOT\n  // send MaxStreams when they close, as that's controlled by the peer.\n  // Only bidirectional streams and ingress unidirectional streams should\n  // trigger MaxStreams updates.\n  EXPECT_CALL(transport_, resetWebTransportEgress(2, _));\n  writeHandle.value()->resetStream(WebTransport::kInternalError);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, UniStreamCredit) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/1,\n      /*targetConcurrentStreams=*/4);\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(false));\n\n  // Create a peer-initiated ingress unidirectional stream\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  // This simulates the peer opening a unidirectional stream to us (ingress)\n  txn_->onWebTransportUniStream(2);\n  EXPECT_NE(readHandle, nullptr);\n\n  // When we close this ingress stream, we should send MaxStreams\n  // The new maxStreamID should be: 1 + (4 / 2) = 3\n  EXPECT_CALL(transport_, sendWTMaxStreams(3, false))\n      .WillOnce(Return(folly::unit));\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(2, _))\n      .WillRepeatedly(Return(folly::unit));\n\n  auto implHandle =\n      dynamic_cast<WebTransportImpl::StreamReadHandle*>(readHandle);\n  ASSERT_NE(implHandle, nullptr);\n\n  // Simulate the application reading and abandoning the stream due to an error\n  // This triggers deliverReadError -> closeIngressStream ->\n  // maybeGrantStreamCredit -> sendWTMaxStreams\n  implHandle->deliverReadError(\n      WebTransport::Exception(WebTransport::kInternalError, \"test error\"));\n  auto fut = readHandle->readStreamData()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   EXPECT_TRUE(streamData.hasException());\n                 });\n\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n}\n\nTEST_F(HTTPTransactionWebTransportTest, BidiStreamCredit) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/5,\n      /*targetConcurrentStreams=*/12);\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(true));\n\n  // Create a peer-initiated bidirectional stream\n  WebTransport::BidiStreamHandle bidiHandle{};\n  EXPECT_CALL(handler_, onWebTransportBidiStream(_, _))\n      .WillOnce(SaveArg<1>(&bidiHandle));\n\n  // This simulates the peer opening a bidirectional stream to us (ingress)\n  txn_->onWebTransportBidiStream(1);\n  EXPECT_NE(bidiHandle.readHandle, nullptr);\n  EXPECT_NE(bidiHandle.writeHandle, nullptr);\n\n  // For bidi streams, we need to close BOTH sides.\n  // Close the write side. This should NOT trigger MaxStreams yet\n  // (we need both sides closed for peer-initiated bidi streams)\n  EXPECT_CALL(transport_, resetWebTransportEgress(1, _));\n  bidiHandle.writeHandle->resetStream(WebTransport::kInternalError);\n\n  // Close the read side. This SHOULD trigger MaxStreams\n  // because both sides of the peer-initiated bidi stream are now closed\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(1, _))\n      .WillRepeatedly(Return(folly::unit));\n  // New maxStreamID should be: 5 + (12 / 2) = 11\n  EXPECT_CALL(transport_, sendWTMaxStreams(11, true))\n      .WillOnce(Return(folly::unit));\n\n  auto implHandle =\n      dynamic_cast<WebTransportImpl::StreamReadHandle*>(bidiHandle.readHandle);\n  ASSERT_NE(implHandle, nullptr);\n\n  // Simulate the application reading and abandoning the stream due to an error\n  // This triggers deliverReadError -> closeIngressStream ->\n  // maybeGrantStreamCredit -> sendWTMaxStreams (because the egress side was\n  // already closed above)\n  implHandle->deliverReadError(\n      WebTransport::Exception(WebTransport::kInternalError, \"test error\"));\n  auto fut = bidiHandle.readHandle->readStreamData()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   EXPECT_TRUE(streamData.hasException());\n                 });\n\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n}\n\nTEST_F(HTTPTransactionWebTransportTest, SelfMaxStreams) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Initialize self flow control to 0 for this test\n  wtImpl->setSelfBidiStreamFlowControl(0);\n\n  WTMaxStreamsCapsule capsule{1};\n  wtImpl->onMaxStreams(capsule.maximumStreams, true);\n\n  // We should be able to create 1 stream successfully\n  EXPECT_CALL(transport_, newWebTransportBidiStream()).WillOnce(Return(0));\n  EXPECT_CALL(transport_, initiateReadOnBidiStream(0, _));\n  auto result1 = wt_->createBidiStream();\n  EXPECT_FALSE(result1.hasError());\n\n  // Trying to create a second stream should fail (we've reached the limit)\n  EXPECT_CALL(transport_, newWebTransportBidiStream())\n      .WillOnce(Return(folly::makeUnexpected(\n          WebTransport::ErrorCode::STREAM_CREATION_ERROR)));\n  EXPECT_CALL(transport_, sendWTStreamsBlocked(1, true));\n  auto result2 = wt_->createBidiStream();\n  EXPECT_TRUE(result2.hasError());\n  EXPECT_EQ(result2.error(), WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n  EXPECT_CALL(transport_, resetWebTransportEgress(0, _));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, PeerMaxStreams) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set peer flow control:\n  // maxStreamID = 2, targetConcurrentStreams = 4\n  // 2 - 0 = 2, and 4 / 2 = 2, so 2 >= 2 is true (should NOT grant credit yet)\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/2,\n      /*targetConcurrentStreams=*/4);\n  EXPECT_FALSE(wtImpl->shouldGrantStreamCredit(true));\n\n  WebTransport::BidiStreamHandle bidiHandle{};\n  EXPECT_CALL(handler_, onWebTransportBidiStream(_, _))\n      .WillOnce(SaveArg<1>(&bidiHandle));\n  auto implHandle = txn_->onWebTransportBidiStream(1);\n  EXPECT_NE(bidiHandle.readHandle, nullptr);\n  EXPECT_NE(bidiHandle.writeHandle, nullptr);\n\n  // Close both sides of the bidi stream to simulate peer closing it.\n  // First reset the write side, this closes the egress stream\n  EXPECT_CALL(transport_,\n              resetWebTransportEgress(1, WebTransport::kInternalError));\n  bidiHandle.writeHandle->resetStream(WebTransport::kInternalError);\n\n  // Now close the read side. Since egress is already closed, this should\n  // trigger sendWTMaxStreams\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(1, _))\n      .WillRepeatedly(Return(folly::unit));\n  EXPECT_CALL(transport_, sendWTMaxStreams(4, true));\n\n  // Directly deliver an error to close the ingress stream\n  implHandle.readHandle->deliverReadError(\n      WebTransport::Exception(WebTransport::kInternalError, \"test\"));\n\n  auto readFuture = bidiHandle.readHandle->readStreamData()\n                        .via(&eventBase_)\n                        .thenTry([](auto streamData) {\n                          EXPECT_TRUE(streamData.hasException());\n                        });\n  eventBase_.loopOnce();\n  EXPECT_TRUE(readFuture.isReady());\n\n  // After closing one stream: 2 - 1 = 1, and 4 / 2 = 2, so 1 < 2 (credit was\n  // granted)\n  EXPECT_FALSE(wtImpl->shouldGrantStreamCredit(true));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, MaxStreamsInvalid) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set initial bidi stream flow control state where we need more stream credit\n  // maxStreamID = 6, targetConcurrentStreams = 14\n  // 6 - 0 = 6, and 14 / 2 = 7, so 6 < 7 is true (should grant credit)\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/6,\n      /*targetConcurrentStreams=*/14);\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(true));\n\n  WTMaxStreamsCapsule capsule{5};\n  wtImpl->onMaxStreams(capsule.maximumStreams, true);\n\n  // After onMaxStreams, maxStreamID will not be updated because maxStreamId >\n  // maximumStreams, so shouldGrantStreamCredit() should still return true\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(true));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, NewBidiStreamFailsAtMaxStreamID) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set peer flow control\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/10);\n\n  // Initialize self flow control to 0, then set to 4\n  wtImpl->setSelfBidiStreamFlowControl(0);\n  wtImpl->onMaxStreams(4, true);\n\n  EXPECT_CALL(transport_, sendWTStreamsBlocked(4, true));\n  EXPECT_CALL(transport_, newWebTransportBidiStream())\n      .WillOnce(Return(folly::makeUnexpected(\n          WebTransport::ErrorCode::STREAM_CREATION_ERROR)));\n\n  auto result = wt_->createBidiStream();\n  EXPECT_TRUE(result.hasError());\n  EXPECT_EQ(result.error(), WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, NewUniStreamFailsAtMaxStreamID) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Set peer flow control\n  wtImpl->setUniStreamFlowControl(\n      /*maxStreamId=*/4,\n      /*targetConcurrentStreams=*/10);\n\n  // Initialize self flow control to 0, then set to 4\n  wtImpl->setSelfUniStreamFlowControl(0);\n  wtImpl->onMaxStreams(4, false);\n\n  EXPECT_CALL(transport_, sendWTStreamsBlocked(4, false));\n  EXPECT_CALL(transport_, newWebTransportUniStream())\n      .WillOnce(Return(folly::makeUnexpected(\n          WebTransport::ErrorCode::STREAM_CREATION_ERROR)));\n\n  auto result = wt_->createUniStream();\n  EXPECT_TRUE(result.hasError());\n  EXPECT_EQ(result.error(), WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReceiveWTStreamsBlockedCapsule) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  wtImpl->setBidiStreamFlowControl(\n      /*maxStreamId=*/2,\n      /*targetConcurrentStreams=*/8);\n  EXPECT_TRUE(wtImpl->shouldGrantStreamCredit(true));\n\n  // When we receive a WT_STREAMS_BLOCKED capsule, we should call\n  // sendWTMaxStreams. The new maxStreamID should be: 2 + (8 / 2) = 6\n  EXPECT_CALL(transport_, sendWTMaxStreams(6, true))\n      .WillOnce(Return(folly::unit));\n\n  wtImpl->onStreamsBlocked(2, true);\n\n  // After granting credit, shouldGrantStreamCredit should return false\n  // because maxStreamID is now 6: 6 - 0 = 6, and 8 / 2 = 4, so 6 >= 4\n  EXPECT_FALSE(wtImpl->shouldGrantStreamCredit(true));\n}\n\nTEST_F(HTTPTransactionWebTransportTest, ReceiveWTDataBlockedCapsule) {\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(wt_);\n  ASSERT_NE(wtImpl, nullptr);\n\n  // Simulate having kDefaultWTReceiveWindow bytes\n  // available, and the peer has consumed most of it. To trigger\n  // shouldGrantFlowControl(), we need bufferedBytes < kDefaultWTReceiveWindow /\n  // 2. This means the application needs to read enough data.\n  WebTransport::StreamReadHandle* readHandle{nullptr};\n  EXPECT_CALL(handler_, onWebTransportUniStream(_, _))\n      .WillOnce(SaveArg<1>(&readHandle));\n\n  txn_->onWebTransportUniStream(0);\n  EXPECT_NE(readHandle, nullptr);\n\n  // Get the implementation handle to use dataAvailable\n  auto implHandle =\n      dynamic_cast<WebTransportImpl::StreamReadHandle*>(readHandle);\n  ASSERT_NE(implHandle, nullptr);\n\n  // Deliver data close to our window limit to put peer in blocked state.\n  // We'll deliver (kDefaultWTReceiveWindow - 100) bytes.\n  EXPECT_CALL(\n      transport_,\n      sendWTMaxData(kDefaultWTReceiveWindow + kDefaultWTReceiveWindow - 100))\n      .WillOnce(Return(folly::unit));\n  auto fcState =\n      implHandle->dataAvailable(makeBuf(kDefaultWTReceiveWindow - 100), false);\n  EXPECT_EQ(fcState, WebTransport::FCState::BLOCKED);\n\n  // Read the data to consume it, making room in our flow control window.\n  EXPECT_CALL(transport_, resumeWebTransportIngress(0));\n  auto fut = readHandle->readStreamData()\n                 .via(&eventBase_)\n                 .thenTry([](auto streamData) {\n                   readCallback(std::move(streamData),\n                                false,\n                                kDefaultWTReceiveWindow - 100,\n                                false);\n                 });\n  eventBase_.loopOnce();\n  EXPECT_TRUE(fut.isReady());\n\n  // Now buffered bytes should be low enough (< kDefaultWTReceiveWindow / 2)\n  // for shouldGrantFlowControl() to return true.\n  EXPECT_TRUE(wtImpl->shouldGrantFlowControl());\n\n  // When we receive a WT_DATA_BLOCKED capsule with maxData matching our current\n  // limit (which is now kDefaultWTReceiveWindow + kDefaultWTReceiveWindow -\n  // 100), we should send WT_MAX_DATA with increased limit newMaxData = (2 *\n  // kDefaultWTReceiveWindow - 100) + kDefaultWTReceiveWindow\n  const uint64_t currentMaxData = 2 * kDefaultWTReceiveWindow - 100;\n  EXPECT_CALL(transport_,\n              sendWTMaxData(currentMaxData + kDefaultWTReceiveWindow))\n      .WillOnce(Return(folly::unit));\n\n  wtImpl->onDataBlocked(currentMaxData);\n\n  EXPECT_CALL(transport_, stopReadingWebTransportIngress(0, _))\n      .WillRepeatedly(Return(folly::unit));\n}\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/session/test/HTTPUpstreamSessionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/TimeoutManager.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HTTPCodecFactory.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPSessionTest.h>\n#include <proxygen/lib/http/session/test/MockByteEventTracker.h>\n#include <proxygen/lib/http/session/test/MockSessionObserver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <proxygen/lib/test/TestAsyncTransport.h>\n#include <string>\n#include <vector>\n#include <wangle/acceptor/ConnectionManager.h>\n\nusing folly::test::MockAsyncTransport;\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing std::string;\nusing std::unique_ptr;\n\nclass TestPriorityAdapter : public HTTPUpstreamSession::PriorityAdapter {\n public:\n  folly::Optional<const HTTPMessage::HTTP2Priority> getHTTPPriority(\n      uint8_t level) override {\n    if (priorityMap_.empty()) {\n      return folly::none;\n    }\n    auto it = priorityMap_.find(level);\n    if (it == priorityMap_.end()) {\n      return minPriority_;\n    }\n    return it->second;\n  }\n\n  ~TestPriorityAdapter() override = default;\n\n  std::map<uint8_t, HTTPMessage::HTTP2Priority> priorityMap_;\n  HTTPMessage::HTTP2Priority minPriority_{std::make_tuple(0, false, 0)};\n  HTTPCodec::StreamID parentId_{0};\n  HTTPCodec::StreamID hiPriId_{0};\n  HTTPCodec::StreamID loPriId_{0};\n  HTTPMessage::HTTP2Priority hiPri_{std::make_tuple(0, false, 0)};\n  HTTPMessage::HTTP2Priority loPri_{std::make_tuple(0, false, 0)};\n};\n\ntemplate <class C>\nclass HTTPUpstreamTest\n    : public testing::Test\n    , public HTTPSessionBase::InfoCallback {\n public:\n  explicit HTTPUpstreamTest(std::vector<int64_t> flowControl = {-1, -1, -1})\n      : eventBase_(),\n        transport_(new NiceMock<MockAsyncTransport>()),\n        transactionTimeouts_(folly::HHWheelTimer::newTimer(\n            &eventBase_,\n            std::chrono::milliseconds(\n                folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n            folly::TimeoutManager::InternalEnum::INTERNAL,\n            std::chrono::milliseconds(500))),\n        flowControl_(flowControl) {\n  }\n\n  void resumeWrites() {\n    std::vector<folly::AsyncTransport::WriteCallback*> cbs;\n    std::swap(cbs, cbs_);\n    pauseWrites_ = false;\n    for (auto cb : cbs) {\n      handleWrite(cb);\n    }\n  }\n\n  virtual void onWriteChain(folly::AsyncTransport::WriteCallback* callback,\n                            std::shared_ptr<folly::IOBuf> iob,\n                            folly::WriteFlags) {\n    if (pauseWrites_) {\n      cbs_.push_back(callback);\n      return; // let write requests timeout\n    }\n    auto mybuf = iob->clone();\n    mybuf->unshare();\n    writes_.append(std::move(mybuf));\n    handleWrite(callback);\n  }\n\n  void handleWrite(folly::AsyncTransport::WriteCallback* callback) {\n    if (failWrites_) {\n      folly::AsyncSocketException ex(folly::AsyncSocketException::UNKNOWN, \"\");\n      callback->writeErr(0, ex);\n    } else {\n      if (writeInLoop_) {\n        eventBase_.runInLoop([&] { callback->writeSuccess(); });\n      } else {\n        callback->writeSuccess();\n      }\n    }\n  }\n\n  void SetUp() override {\n    commonSetUp(makeClientCodec<typename C::Codec>(C::version));\n  }\n\n  void TearDown() override {\n    folly::AsyncSocketException ex(folly::AsyncSocketException::UNKNOWN, \"\");\n    for (auto& cb : cbs_) {\n      cb->writeErr(0, ex);\n    }\n    EXPECT_EQ(onTransactionSymmetricCounter, 0);\n  }\n\n  void commonSetUp(unique_ptr<HTTPCodec> codec) {\n    HTTPSession::setDefaultReadBufferLimit(65536);\n    HTTPSession::setDefaultWriteBufferLimit(65536);\n    HTTPTransaction::setEgressBufferLimit(65536);\n    EXPECT_CALL(*transport_, writeChain(_, _, _))\n        .WillRepeatedly(Invoke(this, &HTTPUpstreamTest<C>::onWriteChain));\n    EXPECT_CALL(*transport_, setReadCB(_))\n        .WillRepeatedly(SaveArg<0>(&readCallback_));\n    EXPECT_CALL(*transport_, getReadCB()).WillRepeatedly(Return(readCallback_));\n    EXPECT_CALL(*transport_, getEventBase())\n        .WillRepeatedly(ReturnPointee(&eventBasePtr_));\n    EXPECT_CALL(*transport_, good())\n        .WillRepeatedly(ReturnPointee(&transportGood_));\n    EXPECT_CALL(*transport_, closeNow())\n        .WillRepeatedly(Assign(&transportGood_, false));\n    EXPECT_CALL(*transport_, shutdownWriteNow())\n        .WillRepeatedly(Assign(&transportGood_, false));\n    EXPECT_CALL(*transport_, isReplaySafe()).WillOnce(Return(false));\n    EXPECT_CALL(*transport_, setReplaySafetyCallback(_))\n        .WillRepeatedly(SaveArg<0>(&replaySafetyCallback_));\n    EXPECT_CALL(*transport_, attachEventBase(_))\n        .WillRepeatedly(SaveArg<0>(&eventBasePtr_));\n\n    for (auto& param : flowControl_) {\n      if (param < 0) {\n        param = codec->getDefaultWindowSize();\n      }\n    }\n\n    auto rawCodec = codec.get();\n    if (dynamic_cast<HTTP1xCodec*>(rawCodec) != nullptr) {\n      dynamic_cast<HTTP1xCodec*>(rawCodec)->setStrictValidation(true);\n    } else if (dynamic_cast<HTTP2Codec*>(rawCodec) != nullptr) {\n      dynamic_cast<HTTP2Codec*>(rawCodec)->setStrictValidation(true);\n    }\n\n    httpSession_ = new HTTPUpstreamSession(\n        transactionTimeouts_.get(),\n        std::move(folly::AsyncTransport::UniquePtr(transport_)),\n        localAddr_,\n        peerAddr_,\n        std::move(codec),\n        mockTransportInfo_,\n        this);\n    httpSession_->setFlowControl(\n        flowControl_[0], flowControl_[1], flowControl_[2]);\n    httpSession_->setMaxConcurrentOutgoingStreams(10);\n    httpSession_->startNow();\n    eventBase_.loop();\n    ASSERT_EQ(this->sessionDestroyed_, false);\n  }\n\n  unique_ptr<typename C::Codec> makeServerCodec() {\n    return ::makeServerCodec<typename C::Codec>(C::version);\n  }\n\n  void enableExHeader(typename C::Codec* serverCodec) {\n    if (!serverCodec || serverCodec->getProtocol() != CodecProtocol::HTTP_2) {\n      return;\n    }\n\n    auto clientCodec = makeClientCodec<HTTP2Codec>(2);\n    folly::IOBufQueue c2s{folly::IOBufQueue::cacheChainLength()};\n    clientCodec->generateConnectionPreface(c2s);\n    clientCodec->generateSettings(c2s);\n    auto setup = c2s.move();\n    serverCodec->onIngress(*setup);\n  }\n\n  void parseOutput(HTTPCodec& serverCodec) {\n    uint32_t consumed = std::numeric_limits<uint32_t>::max();\n    while (!writes_.empty() && consumed > 0) {\n      consumed = serverCodec.onIngress(*writes_.front());\n      writes_.split(consumed);\n    }\n    EXPECT_TRUE(writes_.empty());\n  }\n\n  void readAndLoop(const std::string& input) {\n    readAndLoop((const uint8_t*)input.data(), input.length());\n  }\n\n  void readAndLoop(folly::IOBuf* buf) {\n    buf->coalesce();\n    readAndLoop(buf->data(), buf->length());\n  }\n\n  void readAndLoop(const uint8_t* input, size_t length) {\n    CHECK_NOTNULL(readCallback_);\n    void* buf;\n    size_t bufSize;\n    while (length > 0) {\n      readCallback_->getReadBuffer(&buf, &bufSize);\n      // This is somewhat specific to our implementation, but currently we\n      // always return at least some space from getReadBuffer\n      CHECK_GT(bufSize, 0);\n      bufSize = std::min(bufSize, length);\n      memcpy(buf, input, bufSize);\n      readCallback_->readDataAvailable(bufSize);\n      eventBasePtr_->loop();\n      length -= bufSize;\n      input += bufSize;\n    }\n  }\n\n  void testBasicRequest();\n  void testBasicRequestHttp10(bool keepalive);\n\n  // HTTPSession::InfoCallback interface\n  void onCreate(const HTTPSessionBase&) override {\n    sessionCreated_ = true;\n  }\n  void onDestroy(const HTTPSessionBase&) override {\n    sessionDestroyed_ = true;\n  }\n  void onSettingsOutgoingStreamsFull(const HTTPSessionBase&) override {\n    transactionsFull_ = true;\n  }\n  void onSettingsOutgoingStreamsNotFull(const HTTPSessionBase&) override {\n    transactionsFull_ = false;\n  }\n\n  std::unique_ptr<StrictMock<MockHTTPHandler>> openTransaction(\n      bool expectStartPaused = false) {\n    // Returns a mock handler with txn_ field set in it\n    auto handler = std::make_unique<StrictMock<MockHTTPHandler>>();\n    handler->expectTransaction();\n    if (expectStartPaused) {\n      handler->expectEgressPaused();\n    }\n    auto txn = httpSession_->newTransaction(handler.get());\n    EXPECT_EQ(txn, handler->txn_);\n    return handler;\n  }\n\n  std::unique_ptr<NiceMock<MockHTTPHandler>> openNiceTransaction(\n      bool expectStartPaused = false) {\n    // Returns a mock handler with txn_ field set in it\n    auto handler = std::make_unique<NiceMock<MockHTTPHandler>>();\n    handler->expectTransaction();\n    if (expectStartPaused) {\n      handler->expectEgressPaused();\n    }\n    auto txn = httpSession_->newTransaction(handler.get());\n    EXPECT_EQ(txn, handler->txn_);\n    return handler;\n  }\n\n  void testSimpleUpgrade(const std::string& upgradeReqHeader,\n                         const std::string& upgradeRespHeader,\n                         CodecProtocol respCodecVersion);\n\n  MockByteEventTracker* setMockByteEventTracker() {\n    auto byteEventTracker = new MockByteEventTracker(nullptr);\n    httpSession_->setByteEventTracker(\n        std::unique_ptr<ByteEventTracker>(byteEventTracker));\n    EXPECT_CALL(*byteEventTracker, preSend(_, _, _, _))\n        .WillRepeatedly(Return(0));\n    EXPECT_CALL(*byteEventTracker, drainByteEvents()).WillRepeatedly(Return(0));\n    EXPECT_CALL(*byteEventTracker, processByteEvents(_, _))\n        .WillRepeatedly(Invoke([](std::shared_ptr<ByteEventTracker> self,\n                                  uint64_t bytesWritten) {\n          return self->ByteEventTracker::processByteEvents(self, bytesWritten);\n        }));\n\n    return byteEventTracker;\n  }\n\n  std::unique_ptr<MockSessionObserver> addMockSessionObserver(\n      MockSessionObserver::EventSet eventSet) {\n    auto observer = std::make_unique<NiceMock<MockSessionObserver>>(eventSet);\n    // MockSessionObserver::EventSetBuilder().enableAllEvents().build()\n    EXPECT_CALL(*observer, attached(_));\n    httpSession_->addObserver(observer.get());\n    return observer;\n  }\n\n  void onTransactionAttached(const HTTPSessionBase&) override {\n    onTransactionSymmetricCounter++;\n  }\n\n  void onTransactionDetached(const HTTPSessionBase&) override {\n    onTransactionSymmetricCounter--;\n  }\n\n protected:\n  bool sessionCreated_{false};\n  bool sessionDestroyed_{false};\n\n  bool transactionsFull_{false};\n  bool transportGood_{true};\n\n  folly::EventBase eventBase_;\n  folly::EventBase* eventBasePtr_{&eventBase_};\n  MockAsyncTransport* transport_; // invalid once httpSession_ is destroyed\n  folly::AsyncTransport::ReadCallback* readCallback_{nullptr};\n  folly::AsyncTransport::ReplaySafetyCallback* replaySafetyCallback_{nullptr};\n  folly::HHWheelTimer::UniquePtr transactionTimeouts_;\n  std::vector<int64_t> flowControl_;\n  wangle::TransportInfo mockTransportInfo_;\n  folly::SocketAddress localAddr_{\"127.0.0.1\", 80};\n  folly::SocketAddress peerAddr_{\"127.0.0.1\", 12345};\n  HTTPUpstreamSession* httpSession_{nullptr};\n  folly::IOBufQueue writes_{folly::IOBufQueue::cacheChainLength()};\n  std::vector<folly::AsyncTransport::WriteCallback*> cbs_;\n  bool failWrites_{false};\n  bool pauseWrites_{false};\n  bool writeInLoop_{false};\n  uint64_t onTransactionSymmetricCounter{0};\n};\nTYPED_TEST_SUITE_P(HTTPUpstreamTest);\n\ntemplate <class C>\nclass TimeoutableHTTPUpstreamTest : public HTTPUpstreamTest<C> {\n public:\n  TimeoutableHTTPUpstreamTest() : HTTPUpstreamTest<C>() {\n    // make it non-internal for this test class\n    HTTPUpstreamTest<C>::transactionTimeouts_ = folly::HHWheelTimer::newTimer(\n        &this->HTTPUpstreamTest<C>::eventBase_,\n        std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n        folly::AsyncTimeout::InternalEnum::NORMAL,\n        std::chrono::milliseconds(500));\n  }\n};\n\nusing HTTPUpstreamSessionTest = HTTPUpstreamTest<HTTP1xCodecPair>;\nusing HTTP2UpstreamSessionTest = HTTPUpstreamTest<HTTP2CodecPair>;\n\nTEST_F(HTTP2UpstreamSessionTest, IngressGoawayAbortUncreatedStreams) {\n  // Tests whether the session aborts the streams which are not created\n  // at the remote end.\n\n  // Create buf for GOAWAY with last good stream as 0 (no streams created)\n  HTTP2Codec egressCodec(TransportDirection::DOWNSTREAM);\n  folly::IOBufQueue respBuf{folly::IOBufQueue::cacheChainLength()};\n  egressCodec.generateSettings(respBuf);\n  egressCodec.generateGoaway(respBuf, 0, ErrorCode::NO_ERROR);\n  std::unique_ptr<folly::IOBuf> goawayFrame = respBuf.move();\n  goawayFrame->coalesce();\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectGoaway();\n  handler->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorStreamUnacknowledged);\n    ASSERT_EQ(folly::to<std::string>(\"StreamUnacknowledged on transaction id: \",\n                                     handler->txn_->getID()),\n              std::string(err.what()));\n  });\n  handler->expectDetachTransaction([this] {\n    // Make sure the session can't create any more transactions.\n    MockHTTPHandler handler2;\n    EXPECT_EQ(httpSession_->newTransaction(&handler2), nullptr);\n  });\n\n  // Send the GET request\n  handler->sendRequest();\n\n  // Receive GOAWAY frame while waiting for SYN_REPLY\n  readAndLoop(goawayFrame->data(), goawayFrame->length());\n\n  // Session will delete itself after the abort\n}\n\nTEST_F(HTTP2UpstreamSessionTest, IngressGoawaySessionError) {\n  // Tests whether the session aborts non-refused stream\n\n  // Create buf for GOAWAY with last good stream as 1\n  HTTP2Codec egressCodec(TransportDirection::DOWNSTREAM);\n  folly::IOBufQueue respBuf{folly::IOBufQueue::cacheChainLength()};\n  egressCodec.generateSettings(respBuf);\n  egressCodec.generateGoaway(respBuf, 1, ErrorCode::PROTOCOL_ERROR);\n  std::unique_ptr<folly::IOBuf> goawayFrame = respBuf.move();\n  goawayFrame->coalesce();\n\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n  handler2->expectGoaway();\n  handler1->expectGoaway();\n  // handler2 was refused\n  handler2->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorStreamUnacknowledged);\n    ASSERT_EQ(folly::to<std::string>(\"StreamUnacknowledged on transaction id: \",\n                                     handler2->txn_->getID()),\n              std::string(err.what()));\n  });\n  // handler1 wasn't refused - receives an error\n  handler1->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorConnectionReset);\n    ASSERT_EQ(folly::to<std::string>(\n                  \"ConnectionReset on transaction id: \",\n                  handler1->txn_->getID(),\n                  \". GOAWAY error with codec error: PROTOCOL_ERROR \"\n                  \"with debug info: \"),\n              std::string(err.what()));\n  });\n  handler1->expectDetachTransaction([this] {\n    // Make sure the session can't create any more transactions.\n    MockHTTPHandler handler3;\n    EXPECT_EQ(httpSession_->newTransaction(&handler3), nullptr);\n  });\n  handler2->expectDetachTransaction();\n\n  // Send the GET request\n  handler1->sendRequest();\n  handler2->sendRequest();\n\n  // Receive GOAWAY frame while waiting for SYN_REPLY\n  readAndLoop(goawayFrame->data(), goawayFrame->length());\n\n  // Session will delete itself after the abort\n}\n\nMATCHER_P(HasAbsoluteValue, value, \"\") {\n  return arg == std::abs(value);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestUnderLimitOnWriteError) {\n  InSequence enforceOrder;\n\n  NiceMock<MockHTTPSessionStats> stats, nextStats;\n  httpSession_->setSessionStats(&stats);\n\n  constexpr size_t bodyLen = 70000;\n\n  // create request and send headers\n  auto handler = openTransaction();\n  auto req = getPostRequest();\n  handler->txn_->sendHeaders(req);\n\n  // pause writes to prevent onWriteSuccess callbacks\n  pauseWrites_ = true;\n  EXPECT_CALL(stats, _recordPendingBufferedWriteBytes(bodyLen));\n  handler->expectEgressPaused();\n  // invoked via writechain when writing headers > 0\n  EXPECT_CALL(stats, _recordPendingBufferedWriteBytes(Gt(0)));\n\n  // send body\n  handler->txn_->sendBody(makeBuf(bodyLen));\n  eventBase_.loopOnce();\n\n  // setting another stats should \"transfer\" PendingBufferedWriteBytes\n  int64_t bufferedWrites{0};\n  EXPECT_CALL(stats, _recordPendingBufferedWriteBytes)\n      .WillOnce(SaveArg<0>(&bufferedWrites));\n  EXPECT_CALL(nextStats,\n              _recordPendingBufferedWriteBytes(\n                  HasAbsoluteValue(std::ref(bufferedWrites))));\n  httpSession_->setSessionStats(&nextStats);\n\n  // but no expectEgressResumed\n  handler->expectError();\n  handler->expectDetachTransaction();\n  EXPECT_CALL(nextStats,\n              _recordPendingBufferedWriteBytes(Eq(std::ref(bufferedWrites))));\n  failWrites_ = true;\n  resumeWrites();\n\n  eventBase_.loop();\n  EXPECT_EQ(this->sessionDestroyed_, true);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestOverlimitResume) {\n  HTTPTransaction::setEgressBufferLimit(500);\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n\n  auto req = getPostRequest();\n  handler1->txn_->sendHeaders(req);\n  handler2->txn_->sendHeaders(req);\n  // pause writes\n  pauseWrites_ = true;\n  eventBase_.loopOnce();\n\n  handler1->expectEgressPaused();\n  handler2->expectEgressPaused();\n\n  // send body\n  handler1->txn_->sendBody(makeBuf(1000));\n  handler2->txn_->sendBody(makeBuf(1000));\n\n  // when this handler is resumed, re-pause the pipe\n  handler1->expectEgressResumed([&] {\n    handler1->txn_->sendBody(makeBuf(1000));\n    pauseWrites_ = true;\n  });\n  // handler2 will get a shot\n  handler2->expectEgressResumed(\n      [&] { handler2->txn_->sendBody(makeBuf(1000)); });\n\n  // But the handlers pause again\n  handler1->expectEgressPaused();\n  handler2->expectEgressPaused();\n  resumeWrites();\n\n  // They both get resumed\n  handler1->expectEgressResumed([&] { handler1->txn_->sendEOM(); });\n  handler2->expectEgressResumed([&] { handler2->txn_->sendEOM(); });\n\n  resumeWrites();\n\n  this->eventBase_.loop();\n\n  // less than graceful shutdown\n  handler1->expectError();\n  handler1->expectDetachTransaction();\n  handler2->expectError();\n  handler2->expectDetachTransaction();\n\n  httpSession_->dropConnection();\n  EXPECT_EQ(this->sessionDestroyed_, true);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestSettingsAck) {\n  auto serverCodec = makeServerCodec();\n  folly::IOBufQueue buf{folly::IOBufQueue::cacheChainLength()};\n  serverCodec->generateSettings(buf);\n  auto settingsFrame = buf.move();\n  settingsFrame->coalesce();\n\n  InSequence enforceOrder;\n\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  serverCodec->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onSettings(_));\n  EXPECT_CALL(callbacks, onSettingsAck());\n\n  readAndLoop(settingsFrame.get());\n  parseOutput(*serverCodec);\n  httpSession_->dropConnection();\n  EXPECT_EQ(sessionDestroyed_, true);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestSettingsInfoCallbacks) {\n  auto serverCodec = makeServerCodec();\n\n  folly::IOBufQueue settingsBuf{folly::IOBufQueue::cacheChainLength()};\n  serverCodec->generateSettings(settingsBuf);\n  auto settingsFrame = settingsBuf.move();\n\n  folly::IOBufQueue settingsAckBuf{folly::IOBufQueue::cacheChainLength()};\n  serverCodec->generateSettingsAck(settingsAckBuf);\n  auto settingsAckFrame = settingsAckBuf.move();\n\n  MockHTTPSessionInfoCallback infoCb;\n  httpSession_->setInfoCallback(&infoCb);\n\n  EXPECT_CALL(infoCb, onRead(_, _, _)).Times(2);\n  EXPECT_CALL(infoCb, onWrite(_, _)).Times(1);\n  EXPECT_CALL(infoCb, onDestroy(_)).Times(1);\n\n  EXPECT_CALL(infoCb, onSettings(_, _)).Times(1);\n  EXPECT_CALL(infoCb, onSettingsAck(_)).Times(1);\n\n  InSequence enforceOrder;\n\n  readAndLoop(settingsFrame.get());\n  readAndLoop(settingsAckFrame.get());\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestSetControllerInitHeaderIndexingStrat) {\n  StrictMock<MockUpstreamController> mockController;\n  HeaderIndexingStrategy testH2IndexingStrat;\n  EXPECT_CALL(mockController, getHeaderIndexingStrategy())\n      .WillOnce(Return(&testH2IndexingStrat));\n\n  httpSession_->setController(&mockController);\n\n  auto handler = openTransaction();\n  handler->expectDetachTransaction();\n\n  const auto* h2Codec =\n      static_cast<const HTTP2Codec*>(&handler->txn_->getTransport().getCodec());\n  EXPECT_EQ(h2Codec->getHeaderIndexingStrategy(), &testH2IndexingStrat);\n\n  handler->txn_->sendAbort();\n  eventBase_.loop();\n\n  EXPECT_CALL(mockController, detachSession(_));\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, FullResponsePriorToEgressCompleteNoError) {\n  // Server codec manually generates full response w/ eom followed by\n  // RST_STREAM=NO_ERROR prior to client egress complete\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue serverEgress(folly::IOBufQueue::cacheChainLength());\n  egressCodec->generateSettings(serverEgress);\n\n  // open upstream transaction and send half of body\n  auto handler = openTransaction();\n  EXPECT_CALL(*handler, _detachTransaction()).WillOnce([&]() {\n    // reset txn_ if detachTransaction is invoked\n    handler->txn_ = nullptr;\n  });\n  auto req = getPostRequest(/*contentLength=*/2000);\n  handler->txn_->sendHeaders(req);\n  eventBase_.loopOnce();\n  handler->txn_->sendBody(makeBuf(1000));\n  eventBase_.loopOnce();\n\n  // send server response header\n  auto txnId = handler->txn_->getID();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  egressCodec->generateHeader(serverEgress, txnId, resp);\n\n  // once we rx server headers, pause ingress to verify that NO_ERROR is\n  // properly deferred\n  handler->expectHeaders([this, &handler](auto&&) {\n    handler->txn_->pauseIngress();\n    eventBase_.terminateLoopSoon();\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(0);\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  EXPECT_CALL(*handler, _onError(_)).Times(0);\n\n  // generate body, EOM & RST_STREAM w/ NO_ERROR\n  egressCodec->generateBody(\n      serverEgress, txnId, makeBuf(400), HTTPCodec::NoPadding, /*eom=*/true);\n  egressCodec->generateRstStream(serverEgress, txnId, ErrorCode::NO_ERROR);\n\n  // send to transaction\n  auto input = serverEgress.move();\n  input->coalesce();\n  readAndLoop(input->data(), input->length());\n  eventBase_.loop();\n\n  // txn should still exist\n  CHECK_NOTNULL(handler->txn_);\n\n  // resume ingress and loop again\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(1);\n  EXPECT_CALL(*handler, _onEOM()).Times(1);\n  handler->expectError([this](auto&& ex) {\n    CHECK(ex.hasCodecStatusCode());\n    EXPECT_EQ(ex.getCodecStatusCode(), ErrorCode::NO_ERROR);\n    eventBase_.terminateLoopSoon();\n  });\n  handler->txn_->resumeIngress();\n  eventBase_.loop();\n\n  // txn should have detached here\n  CHECK(handler->txn_ == nullptr);\n\n  httpSession_->dropConnection();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, FullResponsePriorToEgressCompleteCancelError) {\n  /**\n   * Similar to the above test, but rather than resuming ingress and expecting a\n   * RST_STREAM=NO_ERROR, we validate that rx'ing an error other than NO_ERROR\n   * (e.g. CANCEL) will bypass ingress paused and deliver the error maintaining\n   * the old behaviour\n   */\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue serverEgress(folly::IOBufQueue::cacheChainLength());\n  egressCodec->generateSettings(serverEgress);\n\n  // open upstream transaction and send half of body\n  auto handler = openTransaction();\n  EXPECT_CALL(*handler, _detachTransaction()).WillOnce([&]() {\n    // reset txn_ if detachTransaction is invoked\n    handler->txn_ = nullptr;\n  });\n  auto req = getPostRequest(/*contentLength=*/2000);\n  handler->txn_->sendHeaders(req);\n  eventBase_.loopOnce();\n  handler->txn_->sendBody(makeBuf(1000));\n  eventBase_.loopOnce();\n\n  // send server response header\n  auto txnId = handler->txn_->getID();\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  egressCodec->generateHeader(serverEgress, txnId, resp);\n\n  // once we rx server headers, pause ingress to verify that NO_ERROR is\n  // properly deferred\n  handler->expectHeaders([this, &handler](auto&&) {\n    handler->txn_->pauseIngress();\n    eventBase_.terminateLoopSoon();\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(0);\n  EXPECT_CALL(*handler, _onEOM()).Times(0);\n  EXPECT_CALL(*handler, _onError(_)).Times(0);\n\n  // generate body, EOM & RST_STREAM w/ NO_ERROR\n  egressCodec->generateBody(\n      serverEgress, txnId, makeBuf(400), HTTPCodec::NoPadding, /*eom=*/true);\n  egressCodec->generateRstStream(serverEgress, txnId, ErrorCode::NO_ERROR);\n\n  // send to transaction\n  auto input = serverEgress.move();\n  input->coalesce();\n  readAndLoop(input->data(), input->length());\n  eventBase_.loop();\n\n  // txn should still exist\n  CHECK_NOTNULL(handler->txn_);\n\n  // keep ingress paused; ErrorCode::CANCEL should invoke ::onError\n  handler->expectError([this](auto&& ex) {\n    CHECK(ex.hasCodecStatusCode());\n    EXPECT_EQ(ex.getCodecStatusCode(), ErrorCode::CANCEL);\n    eventBase_.terminateLoopSoon();\n  });\n\n  egressCodec->generateRstStream(serverEgress, txnId, ErrorCode::CANCEL);\n  input = serverEgress.move();\n  input->coalesce();\n  readAndLoop(input->data(), input->length());\n\n  // txn should have detached here\n  CHECK(handler->txn_ == nullptr);\n\n  httpSession_->dropConnection();\n}\n\nclass HTTP2UpstreamSessionWithVirtualNodesTest\n    : public HTTPUpstreamTest<MockHTTPCodecPair> {\n public:\n  void SetUp() override {\n    auto codec = std::make_unique<NiceMock<MockHTTPCodec>>();\n    codecPtr_ = codec.get();\n    EXPECT_CALL(*codec, supportsParallelRequests())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec, getTransportDirection())\n        .WillRepeatedly(Return(TransportDirection::UPSTREAM));\n    EXPECT_CALL(*codec, getProtocol())\n        .WillRepeatedly(Return(CodecProtocol::HTTP_2));\n    EXPECT_CALL(*codec, setCallback(_)).WillRepeatedly(SaveArg<0>(&codecCb_));\n    EXPECT_CALL(*codec, createStream()).WillRepeatedly(Invoke([&] {\n      auto ret = nextOutgoingTxn_;\n      nextOutgoingTxn_ += 2;\n      return ret;\n    }));\n    commonSetUp(std::move(codec));\n  }\n\n  void commonSetUp(unique_ptr<HTTPCodec> codec) {\n    HTTPSession::setDefaultReadBufferLimit(65536);\n    HTTPSession::setDefaultWriteBufferLimit(65536);\n    EXPECT_CALL(*transport_, writeChain(_, _, _))\n        .WillRepeatedly(\n            Invoke(this, &HTTPUpstreamTest<MockHTTPCodecPair>::onWriteChain));\n    EXPECT_CALL(*transport_, setReadCB(_))\n        .WillRepeatedly(SaveArg<0>(&readCallback_));\n    EXPECT_CALL(*transport_, getReadCB()).WillRepeatedly(Return(readCallback_));\n    EXPECT_CALL(*transport_, getEventBase())\n        .WillRepeatedly(Return(&eventBase_));\n    EXPECT_CALL(*transport_, good())\n        .WillRepeatedly(ReturnPointee(&transportGood_));\n    EXPECT_CALL(*transport_, closeNow())\n        .WillRepeatedly(Assign(&transportGood_, false));\n    folly::AsyncTransport::UniquePtr transportPtr(transport_);\n    httpSession_ = new HTTPUpstreamSession(transactionTimeouts_.get(),\n                                           std::move(transportPtr),\n                                           localAddr_,\n                                           peerAddr_,\n                                           std::move(codec),\n                                           mockTransportInfo_,\n                                           this);\n    eventBase_.loop();\n    ASSERT_EQ(this->sessionDestroyed_, false);\n  }\n\n  void TearDown() override {\n    EXPECT_TRUE(sessionDestroyed_);\n  }\n\n protected:\n  MockHTTPCodec* codecPtr_{nullptr};\n  HTTPCodec::Callback* codecCb_{nullptr};\n  uint32_t nextOutgoingTxn_{1};\n  std::vector<HTTPCodec::StreamID> dependencies;\n  uint8_t level_{3};\n};\n\nTYPED_TEST_P(HTTPUpstreamTest, ImmediateEof) {\n  // Receive an EOF without any request data\n  this->readCallback_->readEOF();\n  this->eventBase_.loop();\n  EXPECT_EQ(this->sessionDestroyed_, true);\n}\n\ntemplate <class CodecPair>\nvoid HTTPUpstreamTest<CodecPair>::testBasicRequest() {\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_TRUE(msg->is2xxResponse());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  CHECK(httpSession_->supportsMoreTransactions());\n  CHECK_EQ(httpSession_->getNumOutgoingStreams(), 0);\n}\n\nTEST_F(HTTPUpstreamSessionTest, BasicRequest) {\n  testBasicRequest();\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, TwoRequests) {\n  testBasicRequest();\n  testBasicRequest();\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, 10Requests) {\n  for (uint16_t i = 0; i < 10; i++) {\n    testBasicRequest();\n  }\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, ResponseWithoutRequest) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n  EXPECT_EQ(httpSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::TRANSACTION_ABORT);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, ResponseWithoutRequest) {\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n\n  egressCodec->generateSettings(output);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(\"header1\", \"value1\");\n  egressCodec->generateHeader(output, 1, resp);\n  auto input = output.move();\n  input->coalesce();\n  readAndLoop(input->data(), input->length());\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  egressCodec->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onGoaway(_, ErrorCode::PROTOCOL_ERROR, _));\n  parseOutput(*egressCodec);\n}\n\nTEST_F(HTTPUpstreamSessionTest, TestFirstHeaderByteEventTracker) {\n  auto byteEventTracker = setMockByteEventTracker();\n\n  EXPECT_CALL(*byteEventTracker, addFirstHeaderByteEvent(_, _, _))\n      .WillOnce(Invoke(\n          [](uint64_t /*byteNo*/, HTTPTransaction* txn, ByteEvent::Callback) {\n            txn->incrementPendingByteEvents();\n          }));\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  CHECK(httpSession_->supportsMoreTransactions());\n  CHECK_EQ(httpSession_->getNumOutgoingStreams(), 0);\n  handler->txn_->decrementPendingByteEvents();\n  httpSession_->destroy();\n}\n\ntemplate <class CodecPair>\nvoid HTTPUpstreamTest<CodecPair>::testBasicRequestHttp10(bool keepalive) {\n  HTTPMessage req = getGetRequest();\n  req.setHTTPVersion(1, 0);\n  if (keepalive) {\n    req.getHeaders().set(HTTP_HEADER_CONNECTION, \"Keep-Alive\");\n  }\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(200, msg->getStatusCode());\n    EXPECT_EQ(keepalive ? \"keep-alive\" : \"close\",\n              msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_CONNECTION));\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _));\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  if (keepalive) {\n    readAndLoop(\n        \"HTTP/1.0 200 OK\\r\\n\"\n        \"Connection: keep-alive\\r\\n\"\n        \"Content-length: 7\\r\\n\\r\\n\"\n        \"content\");\n  } else {\n    readAndLoop(\n        \"HTTP/1.0 200 OK\\r\\n\"\n        \"Connection: close\\r\\n\"\n        \"Content-length: 7\\r\\n\\r\\n\"\n        \"content\");\n  }\n}\n\nTEST_F(HTTPUpstreamSessionTest, Http10Keepalive) {\n  testBasicRequestHttp10(true);\n  testBasicRequestHttp10(false);\n}\n\nTEST_F(HTTPUpstreamSessionTest, BasicTrailers) {\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  EXPECT_CALL(*handler, _onTrailers(_));\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\"\n      \"X-Trailer1: foo\\r\\n\"\n      \"\\r\\n\");\n\n  CHECK(httpSession_->supportsMoreTransactions());\n  CHECK_EQ(httpSession_->getNumOutgoingStreams(), 0);\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, BasicTrailers) {\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n\n  egressCodec->generateSettings(output);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(\"header1\", \"value1\");\n  egressCodec->generateHeader(output, 1, resp);\n  auto buf = makeBuf(100);\n  egressCodec->generateBody(\n      output, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);\n  HTTPHeaders trailers;\n  trailers.set(\"trailer2\", \"value2\");\n  egressCodec->generateTrailers(output, 1, trailers);\n\n  std::unique_ptr<folly::IOBuf> input = output.move();\n  input->coalesce();\n\n  auto handler = openTransaction();\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(200, msg->getStatusCode());\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"header1\"), \"value1\");\n  });\n  handler->expectBody();\n  handler->expectTrailers([&](std::shared_ptr<HTTPHeaders> trailers) {\n    EXPECT_EQ(trailers->getSingleOrEmpty(\"trailer2\"), \"value2\");\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(input->data(), input->length());\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, HeadersThenBodyThenHeaders) {\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n\n  egressCodec->generateSettings(output);\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(\"header1\", \"value1\");\n  egressCodec->generateHeader(output, 1, resp);\n  auto buf = makeBuf(100);\n  egressCodec->generateBody(\n      output, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);\n  // generate same headers again on the same stream\n  egressCodec->generateHeader(output, 1, resp);\n\n  std::unique_ptr<folly::IOBuf> input = output.move();\n  input->coalesce();\n\n  auto handler = openTransaction();\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(200, msg->getStatusCode());\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"header1\"), \"value1\");\n  });\n  handler->expectBody();\n  handler->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorIngressStateTransition);\n    ASSERT_EQ(\n        \"Invalid ingress state transition, state=RegularBodyReceived, \"\n        \"event=onFinalHeaders, streamID=1\",\n        std::string(err.what()));\n  });\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(input->data(), input->length());\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, TwoRequestsWithPause) {\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n\n  handler->expectEOM([&]() { handler->txn_->pauseIngress(); });\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  // Even though the previous transaction paused ingress just before it\n  // finished up, reads resume automatically when the number of\n  // transactions goes to zero. This way, the second request can read\n  // without having to call resumeIngress()\n  testBasicRequest();\n  httpSession_->destroy();\n}\n\nusing HTTPUpstreamTimeoutTest = TimeoutableHTTPUpstreamTest<HTTP1xCodecPair>;\nTEST_F(HTTPUpstreamTimeoutTest, WriteTimeoutAfterResponse) {\n  // Test where the upstream session times out while writing the request\n  // to the server, but after the server has already sent back a full\n  // response.\n  pauseWrites_ = true;\n  HTTPMessage req = getPostRequest();\n\n  InSequence enforceOrder;\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    ASSERT_EQ(err.getDirection(), HTTPException::Direction::INGRESS_AND_EGRESS);\n    EXPECT_EQ(err.getProxygenError(), kErrorWriteTimeout);\n    ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                     handler->txn_->getID()),\n              std::string(err.what()));\n  });\n  handler->expectDetachTransaction();\n\n  handler->txn_->sendHeaders(req);\n  // Don't send the body, but get a response immediately\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n}\n\nTEST_F(HTTPUpstreamTimeoutTest, SetIngressTimeoutAfterSendEom) {\n  InSequence enforceOrder;\n  httpSession_->setIngressTimeoutAfterEom(true);\n\n  auto handler1 = openTransaction();\n  handler1->expectHeaders();\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n\n  // Send EOM separately.\n  auto transaction1 = handler1->txn_;\n  EXPECT_TRUE(transaction1->hasIdleTimeout());\n  transaction1->setIdleTimeout(std::chrono::milliseconds(100));\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendHeaders(getPostRequest());\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendBody(makeBuf(100) /* body */);\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction1->isScheduled());\n\n  transaction1->sendEOM();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction1->isScheduled());\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  // Send EOM with header.\n  auto handler2 = openTransaction();\n  handler2->expectHeaders();\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n\n  auto transaction2 = handler2->txn_;\n  EXPECT_FALSE(transaction2->isScheduled());\n  transaction2->sendHeadersWithOptionalEOM(getPostRequest(), true /* eom */);\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction2->isScheduled());\n\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  // Send EOM with body.\n  auto handler3 = openTransaction();\n  handler3->expectHeaders();\n  handler3->expectEOM();\n  handler3->expectDetachTransaction();\n\n  auto transaction3 = handler3->txn_;\n  EXPECT_FALSE(transaction3->isScheduled());\n  transaction3->sendHeaders(getPostRequest());\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transaction3->isScheduled());\n  transaction3->sendBody(makeBuf(100) /* body */);\n  transaction3->sendEOM();\n  eventBase_.loopOnce();\n  EXPECT_TRUE(transaction3->isScheduled());\n\n  readAndLoop(\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, SetTransactionTimeout) {\n  // Test that setting a new timeout on the transaction will cancel\n  // the old one.\n  auto handler = openTransaction();\n  handler->expectDetachTransaction();\n\n  EXPECT_TRUE(handler->txn_->hasIdleTimeout());\n  handler->txn_->setIdleTimeout(std::chrono::milliseconds(747));\n  EXPECT_TRUE(handler->txn_->hasIdleTimeout());\n  EXPECT_TRUE(handler->txn_->isScheduled());\n  EXPECT_EQ(transactionTimeouts_->count(), 1);\n  handler->txn_->sendAbort();\n  eventBase_.loop();\n}\n\nTEST_F(HTTPUpstreamSessionTest, ReadTimeout) {\n  NiceMock<MockUpstreamController> controller;\n  httpSession_->setController(&controller);\n  auto cm = wangle::ConnectionManager::makeUnique(\n      &eventBase_, std::chrono::milliseconds(50));\n  cm->addConnection(httpSession_, true);\n  eventBase_.loop();\n}\n\nTEST_F(HTTPUpstreamSessionTest, 100ContinueKeepalive) {\n  // Test a request with 100 continue on a keepalive connection. Then make\n  // another request.\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().set(HTTP_HEADER_EXPECT, \"100-continue\");\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_FALSE(msg->is2xxResponse());\n    EXPECT_EQ(100, msg->getStatusCode());\n  });\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest(req);\n  readAndLoop(\n      \"HTTP/1.1 100 Continue\\r\\n\\r\\n\"\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Transfer-Encoding: chunked\\r\\n\\r\\n\"\n      \"0\\r\\n\\r\\n\");\n\n  // Now make sure everything still works\n  testBasicRequest();\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, 417Keepalive) {\n  // Test a request with 100 continue on a keepalive connection. Then make\n  // another request after the expectation fails.\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().set(HTTP_HEADER_EXPECT, \"100-continue\");\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(417, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest(req);\n  readAndLoop(\n      \"HTTP/1.1 417 Expectation Failed\\r\\n\"\n      \"Content-Length: 0\\r\\n\\r\\n\");\n\n  // Now make sure everything still works\n  testBasicRequest();\n  EXPECT_FALSE(sessionDestroyed_);\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, 101Upgrade) {\n  // Test an upgrade request with sending 101 response. Then send\n  // some data and check the onBody callback contents\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().set(HTTP_HEADER_UPGRADE, \"http/2.0\");\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsChunked());\n    EXPECT_EQ(101, msg->getStatusCode());\n  });\n  EXPECT_CALL(*handler, _onUpgrade(_));\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(\"Test Body\\r\\n\"));\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->txn_->sendHeaders(req);\n  eventBase_.loop();\n  readAndLoop(\n      \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n      \"Upgrade: http/2.0\\r\\n\\r\\n\"\n      \"Test Body\\r\\n\");\n\n  handler->sendBody(100);\n  handler->sendEOM();\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n  EXPECT_FALSE(transportGood_);\n\n  readCallback_->readEOF();\n  eventBase_.loop();\n}\n\n// ===== Upgrade Tests ====\n\ntemplate <class CodecPair>\nvoid HTTPUpstreamTest<CodecPair>::testSimpleUpgrade(\n    const std::string& upgradeReqHeader,\n    const std::string& upgradeRespHeader,\n    CodecProtocol respCodecVersion) {\n  InSequence dummy;\n  auto handler = openTransaction();\n  NiceMock<MockUpstreamController> controller;\n\n  httpSession_->setController(&controller);\n  EXPECT_CALL(controller, onSessionCodecChange(httpSession_));\n\n  EXPECT_EQ(httpSession_->getMaxConcurrentOutgoingStreams(), 1);\n\n  HeaderIndexingStrategy testH2IndexingStrat;\n  if (respCodecVersion == CodecProtocol::HTTP_2) {\n    EXPECT_CALL(controller, getHeaderIndexingStrategy())\n        .WillOnce(Return(&testH2IndexingStrat));\n  }\n\n  handler->expectHeaders([](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  auto txn = handler->txn_;\n  HTTPMessage req = getUpgradeRequest(upgradeReqHeader);\n  txn->sendHeaders(req);\n  txn->sendEOM();\n  eventBase_.loopOnce(); // force HTTP/1.1 writes\n  writes_.move();        // clear them out\n  readAndLoop(\n      folly::to<string>(\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                        \"Upgrade: \",\n                        upgradeRespHeader,\n                        \"\\r\\n\"\n                        \"\\r\\n\"));\n\n  if (respCodecVersion == CodecProtocol::HTTP_2) {\n    const auto* codec =\n        dynamic_cast<const HTTP2Codec*>(&txn->getTransport().getCodec());\n    ASSERT_NE(codec, nullptr);\n    EXPECT_EQ(codec->getHeaderIndexingStrategy(), &testH2IndexingStrat);\n  }\n\n  readAndLoop(getResponseBuf(respCodecVersion, txn->getID(), 200, 100).get());\n\n  EXPECT_EQ(httpSession_->getMaxConcurrentOutgoingStreams(), 10);\n  httpSession_->destroy();\n}\n\nTEST_F(HTTPUpstreamSessionTest, HttpUpgrade101Unexpected) {\n  InSequence dummy;\n  auto handler = openTransaction();\n\n  EXPECT_CALL(*handler, _onError(_));\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  eventBase_.loop();\n  readAndLoop(\n      folly::to<string>(\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                        \"Upgrade: h2c\\r\\n\"\n                        \"\\r\\n\"));\n  EXPECT_EQ(readCallback_, nullptr);\n  EXPECT_TRUE(sessionDestroyed_);\n}\n\nTEST_F(HTTPUpstreamSessionTest, HttpUpgrade101MissingUpgrade) {\n  InSequence dummy;\n  auto handler = openTransaction();\n\n  EXPECT_CALL(*handler, _onError(_));\n  handler->expectDetachTransaction();\n\n  handler->sendRequest(getUpgradeRequest(\"test-proto\"));\n  readAndLoop(\n      folly::to<string>(\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                        \"\\r\\n\"));\n  EXPECT_EQ(readCallback_, nullptr);\n  EXPECT_TRUE(sessionDestroyed_);\n}\n\nTEST_F(HTTPUpstreamSessionTest, HttpUpgrade101BogusHeader) {\n  InSequence dummy;\n  auto handler = openTransaction();\n\n  EXPECT_CALL(*handler, _onError(_));\n  handler->expectDetachTransaction();\n\n  handler->sendRequest(getUpgradeRequest(\"test-proto\"));\n  eventBase_.loop();\n  readAndLoop(\n      folly::to<string>(\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                        \"Upgrade: blarf\\r\\n\"\n                        \"\\r\\n\"));\n  EXPECT_EQ(readCallback_, nullptr);\n  EXPECT_TRUE(sessionDestroyed_);\n}\n\nTEST_F(HTTPUpstreamSessionTest, FailedUpgradeDrainsSession) {\n  // rejected upgrade (e.g. 500 resp from upstream) should drain the connection;\n  // future ::newTransactions should fail\n  folly::DelayedDestruction::DestructorGuard g(httpSession_);\n  HTTPMessage req = getGetRequest();\n  req.setEgressWebsocketUpgrade();\n\n  auto handler = openTransaction();\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(msg->getStatusCode(), 500);\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->txn_->sendHeadersWithEOM(req);\n  eventBase_.loop();\n\n  readAndLoop(\n      \"HTTP/1.1 500 Internal Server Error\\r\\n\"\n      \"Content-length: 0\\r\\n\\r\\n\");\n\n  // new txn fails\n  NiceMock<MockHTTPHandler> handler2;\n  EXPECT_FALSE(httpSession_->newTransaction(&handler2));\n}\n\nclass HTTPUpstreamRecvStreamTest : public HTTPUpstreamSessionTest {\n public:\n  HTTPUpstreamRecvStreamTest() : HTTPUpstreamTest({100000, 105000, 110000}) {\n  }\n};\n\nclass NoFlushUpstreamSessionTest : public HTTPUpstreamTest<HTTP2CodecPair> {\n public:\n  void onWriteChain(folly::AsyncTransport::WriteCallback* callback,\n                    std::shared_ptr<folly::IOBuf>,\n                    folly::WriteFlags) override {\n    if (!timesCalled_++) {\n      callback->writeSuccess();\n    } else {\n      cbs_.push_back(callback);\n    }\n    // do nothing -- let unacked egress build up\n  }\n\n  ~NoFlushUpstreamSessionTest() override {\n    folly::AsyncSocketException ex(folly::AsyncSocketException::UNKNOWN, \"\");\n    for (auto& cb : cbs_) {\n      cb->writeErr(0, ex);\n    }\n  }\n\n private:\n  uint32_t timesCalled_{0};\n  std::vector<folly::AsyncTransport::WriteCallback*> cbs_;\n};\n\nTEST_F(NoFlushUpstreamSessionTest, DeleteTxnOnUnpause) {\n  // Test where the handler gets onEgressPaused() and triggers another\n  // HTTPSession call to iterate over all transactions (to ensure nested\n  // iteration works).\n\n  HTTPMessage req = getGetRequest();\n\n  InSequence enforceOrder;\n\n  auto handler1 = openNiceTransaction();\n  auto handler2 = openNiceTransaction();\n  auto handler3 = openNiceTransaction();\n  handler2->expectEgressPaused([this] {\n    // This time it is invoked by the session on all transactions\n    httpSession_->dropConnection();\n  });\n  handler2->txn_->sendHeaders(req);\n  // This happens when the body write fills the txn egress queue\n  // Send a body big enough to pause egress\n  handler2->txn_->onIngressWindowUpdate(100);\n  handler2->txn_->sendBody(makeBuf(httpSession_->getWriteBufferLimit() + 1));\n  eventBase_.loop();\n}\n\nclass MockHTTPUpstreamTest : public HTTPUpstreamTest<MockHTTPCodecPair> {\n public:\n  void SetUp() override {\n    auto codec = std::make_unique<NiceMock<MockHTTPCodec>>();\n    codecPtr_ = codec.get();\n    EXPECT_CALL(*codec, supportsParallelRequests())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec, getTransportDirection())\n        .WillRepeatedly(Return(TransportDirection::UPSTREAM));\n    EXPECT_CALL(*codec, setCallback(_)).WillRepeatedly(SaveArg<0>(&codecCb_));\n    EXPECT_CALL(*codec, isReusable()).WillRepeatedly(ReturnPointee(&reusable_));\n    EXPECT_CALL(*codec, isWaitingToDrain())\n        .WillRepeatedly(ReturnPointee(&reusable_));\n    EXPECT_CALL(*codec, getDefaultWindowSize()).WillRepeatedly(Return(65536));\n    EXPECT_CALL(*codec, getProtocol())\n        .WillRepeatedly(Return(CodecProtocol::HTTP_2));\n    EXPECT_CALL(*codec, generateGoaway(_, _, _, _))\n        .WillRepeatedly(Invoke([&](folly::IOBufQueue& writeBuf,\n                                   HTTPCodec::StreamID lastStream,\n                                   ErrorCode,\n                                   std::shared_ptr<folly::IOBuf>) {\n          goawayCount_++;\n          size_t written = 0;\n          if (reusable_) {\n            writeBuf.append(\"GOAWAY\", 6);\n            written = 6;\n            reusable_ = false;\n          }\n          return written;\n        }));\n    EXPECT_CALL(*codec, createStream()).WillRepeatedly(Invoke([&] {\n      auto ret = nextOutgoingTxn_;\n      nextOutgoingTxn_ += 2;\n      return ret;\n    }));\n\n    commonSetUp(std::move(codec));\n  }\n\n  void TearDown() override {\n    EXPECT_TRUE(sessionDestroyed_);\n  }\n\n  std::unique_ptr<StrictMock<MockHTTPHandler>> openTransaction() {\n    // Returns a mock handler with txn_ field set in it\n    auto handler = std::make_unique<StrictMock<MockHTTPHandler>>();\n    handler->expectTransaction();\n    auto txn = httpSession_->newTransaction(handler.get());\n    EXPECT_EQ(txn, handler->txn_);\n    return handler;\n  }\n\n  MockHTTPCodec* codecPtr_{nullptr};\n  HTTPCodec::Callback* codecCb_{nullptr};\n  bool reusable_{true};\n  uint32_t nextOutgoingTxn_{1};\n  uint32_t goawayCount_{0};\n};\n\nTEST_F(HTTP2UpstreamSessionTest, ServerPush) {\n  httpSession_->setEgressSettings({{SettingsId::ENABLE_PUSH, 1}});\n\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n\n  HTTPMessage push;\n  push.getHeaders().set(\"HOST\", \"www.foo.com\");\n  push.setURL(\"https://www.foo.com/\");\n  egressCodec->generateSettings(output);\n  // PUSH_PROMISE\n  egressCodec->generatePushPromise(output, 2, push, 1);\n\n  // Pushed resource\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(\"ohai\", \"push\");\n  egressCodec->generateHeader(output, 2, resp);\n  auto buf = makeBuf(100);\n  egressCodec->generateBody(\n      output, 2, std::move(buf), HTTPCodec::NoPadding, true /* eom */);\n\n  // Original resource\n  resp.getHeaders().set(\"ohai\", \"orig\");\n  egressCodec->generateHeader(output, 1, resp);\n  buf = makeBuf(100);\n  egressCodec->generateBody(\n      output, 1, std::move(buf), HTTPCodec::NoPadding, true /* eom */);\n\n  std::unique_ptr<folly::IOBuf> input = output.move();\n  input->coalesce();\n\n  MockHTTPHandler pushHandler;\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  EXPECT_CALL(*handler, _onPushedTransaction(_))\n      .WillOnce(Invoke([&pushHandler](HTTPTransaction* pushTxn) {\n        pushTxn->setHandler(&pushHandler);\n      }));\n  EXPECT_CALL(pushHandler, _setTransaction(_));\n  pushHandler.expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(httpSession_->getNumIncomingStreams(), 1);\n    EXPECT_TRUE(msg->getIsChunked());\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(msg->getPathAsStringPiece(), \"/\");\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST),\n              \"www.foo.com\");\n  });\n  pushHandler.expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(msg->getStatusCode(), 200);\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"ohai\"), \"push\");\n  });\n  pushHandler.expectBody();\n  pushHandler.expectEOM();\n  pushHandler.expectDetachTransaction();\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"ohai\"), \"orig\");\n  });\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  handler->sendRequest();\n  readAndLoop(input->data(), input->length());\n\n  EXPECT_EQ(httpSession_->getNumIncomingStreams(), 0);\n  httpSession_->destroy();\n}\n\nclass MockHTTP2UpstreamTest : public MockHTTPUpstreamTest {\n public:\n  void SetUp() override {\n    MockHTTPUpstreamTest::SetUp();\n\n    // This class assumes we are doing a test for HTTP/2+ where\n    // this function is *not* a no-op. Indicate this via a positive number\n    // of bytes being generated for writing RST_STREAM.\n\n    ON_CALL(*codecPtr_, generateRstStream(_, _, _)).WillByDefault(Return(1));\n  }\n};\n\nTEST_F(MockHTTP2UpstreamTest, ParseErrorNoTxn) {\n  // 1) Create streamID == 1\n  // 2) Send request\n  // 3) Detach handler\n  // 4) Get an ingress parse error on reply\n  // Expect that the codec should be asked to generate an abort on streamID==1\n\n  // Setup the codec expectations.\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _))\n      .WillOnce(Invoke(\n          [](folly::IOBufQueue& writeBuf,\n             HTTPCodec::StreamID,\n             const HTTPMessage&,\n             bool,\n             HTTPHeaderSize*,\n             folly::Optional<HTTPHeaders>) { writeBuf.append(\"1\", 1); }));\n  EXPECT_CALL(*codecPtr_, generateEOM(_, _)).WillOnce(Return(20));\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, 1, _));\n\n  // 1)\n  auto handler = openTransaction();\n\n  // 2)\n  handler->sendRequest(getPostRequest());\n\n  // 3) Note this sendAbort() doesn't destroy the txn since byte events are\n  // enqueued\n  handler->txn_->sendAbort();\n\n  // 4)\n  HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS, \"foo\");\n  ex.setProxygenError(kErrorParseHeader);\n  ex.setCodecStatusCode(ErrorCode::REFUSED_STREAM);\n  codecCb_->onError(1, ex, true);\n\n  // cleanup\n  handler->expectDetachTransaction();\n  httpSession_->dropConnection();\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTPUpstreamTest, 0MaxOutgoingTxns) {\n  // Test where an upstream session gets a SETTINGS frame with 0 max\n  // outgoing transactions. In our implementation, we jsut send a GOAWAY\n  // and close the connection.\n\n  codecCb_->onSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 0}});\n  EXPECT_TRUE(transactionsFull_);\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockHTTPUpstreamTest, OutgoingTxnSettings) {\n  // Create 2 transactions, then receive a settings frame from\n  // the server indicating 1 parallel transaction at a time is allowed.\n  // Then get another SETTINGS frame indicating 100 max parallel\n  // transactions. Expect that HTTPSession invokes both info callbacks.\n\n  NiceMock<MockHTTPHandler> handler1;\n  NiceMock<MockHTTPHandler> handler2;\n  httpSession_->newTransaction(&handler1);\n  httpSession_->newTransaction(&handler2);\n\n  codecCb_->onSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 1}});\n  EXPECT_TRUE(transactionsFull_);\n  codecCb_->onSettings({{SettingsId::MAX_CONCURRENT_STREAMS, 100}});\n  EXPECT_FALSE(transactionsFull_);\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockHTTPUpstreamTest, IngressGoawayDrain) {\n  // Tests whether the session drains existing transactions and\n  // deletes itself after receiving a GOAWAY.\n\n  InSequence enforceOrder;\n\n  auto handler = openTransaction();\n  EXPECT_CALL(*handler, _onGoaway(ErrorCode::NO_ERROR));\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  // Send the GET request\n  handler->sendRequest();\n\n  // Receive GOAWAY frame with last good stream as 1\n  codecCb_->onGoaway(1, ErrorCode::NO_ERROR);\n\n  // New transactions cannot be created afrer goaway\n  EXPECT_FALSE(httpSession_->isReusable());\n  EXPECT_EQ(httpSession_->newTransaction(handler.get()), nullptr);\n\n  // Receive 200 OK\n  auto resp = makeResponse(200);\n  codecCb_->onMessageBegin(1, resp.get());\n  codecCb_->onHeadersComplete(1, std::move(resp));\n  codecCb_->onMessageComplete(1, false);\n  eventBase_.loop();\n\n  // Session will delete itself after getting the response\n}\n\nTEST_F(MockHTTPUpstreamTest, Goaway) {\n  // Make sure existing txns complete successfully even if we drain the\n  // upstream session\n  const unsigned numTxns = 10;\n  MockHTTPHandler handler[numTxns];\n\n  InSequence enforceOrder;\n\n  for (unsigned i = 0; i < numTxns; ++i) {\n    handler[i].expectTransaction();\n    handler[i].expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n      EXPECT_FALSE(msg->getIsUpgraded());\n      EXPECT_EQ(200, msg->getStatusCode());\n    });\n    httpSession_->newTransaction(&handler[i]);\n\n    // Send the GET request\n    handler[i].sendRequest();\n\n    // Receive 200 OK\n    auto resp = makeResponse(200);\n    codecCb_->onMessageBegin(handler[i].txn_->getID(), resp.get());\n    codecCb_->onHeadersComplete(handler[i].txn_->getID(), std::move(resp));\n  }\n\n  codecCb_->onGoaway(numTxns * 2 + 1, ErrorCode::NO_ERROR);\n  for (unsigned i = 0; i < numTxns; ++i) {\n    handler[i].expectEOM();\n    handler[i].expectDetachTransaction();\n    codecCb_->onMessageComplete(i * 2 + 1, false);\n  }\n  eventBase_.loop();\n\n  // Session will delete itself after drain completes\n}\n\nTEST_F(MockHTTPUpstreamTest, GoawayPreHeaders) {\n  // Make sure existing txns complete successfully even if we drain the\n  // upstream session\n  MockHTTPHandler handler;\n\n  InSequence enforceOrder;\n\n  handler.expectTransaction();\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _))\n      .WillOnce(Invoke([&](folly::IOBufQueue& writeBuf,\n                           HTTPCodec::StreamID /*stream*/,\n                           const HTTPMessage& /*msg*/,\n                           bool /*eom*/,\n                           HTTPHeaderSize* /*size*/,\n                           folly::Optional<HTTPHeaders>) {\n        writeBuf.append(\"HEADERS\", 7);\n      }));\n  handler.expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  httpSession_->newTransaction(&handler);\n  httpSession_->drain();\n\n  // Send the GET request\n  handler.sendRequest();\n\n  // Receive 200 OK\n  auto resp = makeResponse(200);\n  codecCb_->onMessageBegin(handler.txn_->getID(), resp.get());\n  codecCb_->onHeadersComplete(handler.txn_->getID(), std::move(resp));\n\n  codecCb_->onGoaway(1, ErrorCode::NO_ERROR);\n  handler.expectEOM();\n  handler.expectDetachTransaction();\n  codecCb_->onMessageComplete(1, false);\n  eventBase_.loop();\n\n  auto buf = writes_.move();\n  ASSERT_TRUE(buf != nullptr);\n  EXPECT_EQ(buf->moveToFbString().data(), string(\"HEADERSGOAWAY\"));\n  // Session will delete itself after drain completes\n}\n\nTEST_F(MockHTTPUpstreamTest, NoWindowUpdateOnDrain) {\n  EXPECT_CALL(*codecPtr_, supportsStreamFlowControl())\n      .WillRepeatedly(Return(true));\n\n  auto handler = openTransaction();\n\n  handler->sendRequest();\n  httpSession_->drain();\n  auto streamID = handler->txn_->getID();\n\n  EXPECT_CALL(*handler, _onGoaway(ErrorCode::NO_ERROR));\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _)).Times(3);\n  handler->expectEOM();\n\n  handler->expectDetachTransaction();\n\n  uint32_t outstanding = 0;\n  uint32_t sendWindow = 65536;\n  uint32_t toSend = sendWindow * 1.55;\n\n  NiceMock<MockHTTPSessionStats> stats;\n\n  httpSession_->setSessionStats(&stats);\n\n  // Below is expected to be called by httpSession_ destructor\n  EXPECT_CALL(stats, _recordPendingBufferedReadBytes(0));\n\n  // We'll get exactly one window update because we are draining\n  EXPECT_CALL(*codecPtr_, generateWindowUpdate(_, _, _))\n      .WillOnce(Invoke([&](folly::IOBufQueue& writeBuf,\n                           HTTPCodec::StreamID /*stream*/,\n                           uint32_t delta) {\n        EXPECT_EQ(delta, sendWindow);\n        outstanding -= delta;\n        uint32_t len = std::min(toSend, sendWindow - outstanding);\n        EXPECT_CALL(stats, _recordPendingBufferedReadBytes(len));\n        EXPECT_CALL(stats, _recordPendingBufferedReadBytes(-1 * (int32_t)len));\n        EXPECT_LT(len, sendWindow);\n        toSend -= len;\n        EXPECT_EQ(toSend, 0);\n        eventBase_.tryRunAfterDelay(\n            [this, streamID, len] {\n              failWrites_ = true;\n              auto respBody = makeBuf(len);\n              codecCb_->onBody(streamID, std::move(respBody), 0);\n              codecCb_->onMessageComplete(streamID, false);\n            },\n            50);\n\n        const std::string dummy(\"window\");\n        writeBuf.append(dummy);\n        return 6;\n      }));\n\n  codecCb_->onGoaway(streamID, ErrorCode::NO_ERROR);\n  auto resp = makeResponse(200);\n  codecCb_->onMessageBegin(streamID, resp.get());\n  codecCb_->onHeadersComplete(streamID, std::move(resp));\n\n  // While there is room and the window and body to send\n  while (sendWindow - outstanding > 0 && toSend > 0) {\n    // Send up to a 36k chunk\n    uint32_t len = std::min(toSend, uint32_t(36000));\n    // limited by the available window\n    len = std::min(len, sendWindow - outstanding);\n    EXPECT_CALL(stats, _recordPendingBufferedReadBytes(len));\n    EXPECT_CALL(stats, _recordPendingBufferedReadBytes(-1 * (int32_t)len));\n    auto respBody = makeBuf(len);\n    toSend -= len;\n    outstanding += len;\n    codecCb_->onBody(streamID, std::move(respBody), 0);\n  }\n\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTPUpstreamTest, GetWithBody) {\n  // Should be allowed to send a GET request with body.\n  NiceMock<MockHTTPHandler> handler;\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, \"10\");\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _));\n  EXPECT_CALL(*codecPtr_, generateBody(_, _, _, _, true));\n\n  auto txn = httpSession_->newTransaction(&handler);\n  txn->sendHeaders(req);\n  txn->sendBody(makeBuf(10));\n  txn->sendEOM();\n\n  eventBase_.loop();\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockHTTPUpstreamTest, HeaderWithEom) {\n  NiceMock<MockHTTPHandler> handler;\n  HTTPMessage req = getGetRequest();\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, true, _, _));\n\n  auto txn = httpSession_->newTransaction(&handler);\n  txn->sendHeadersWithEOM(req);\n  eventBase_.loop();\n  EXPECT_TRUE(txn->isEgressComplete());\n  httpSession_->dropConnection();\n}\n\ntemplate <int stage>\nclass TestAbortPost : public MockHTTPUpstreamTest {\n public:\n  void doAbortTest() {\n    // Send an abort at various points while receiving the response to a GET\n    // The test is broken into \"stages\"\n    // Stage 0) headers received\n    // Stage 1) chunk header received\n    // Stage 2) body received\n    // Stage 3) chunk complete received\n    // Stage 4) trailers received\n    // Stage 5) eom received\n    // This test makes sure expected callbacks are received if an abort is\n    // sent before any of these stages.\n    InSequence enforceOrder;\n    StrictMock<MockHTTPHandler> handler;\n    HTTPMessage req = getPostRequest(10);\n\n    std::unique_ptr<HTTPMessage> resp;\n    std::unique_ptr<folly::IOBuf> respBody;\n    std::tie(resp, respBody) = makeResponse(200, 50);\n\n    handler.expectTransaction();\n    EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _));\n\n    if (stage > 0) {\n      handler.expectHeaders();\n    }\n    if (stage > 1) {\n      EXPECT_CALL(handler, _onChunkHeader(_));\n    }\n    if (stage > 2) {\n      EXPECT_CALL(handler, _onBodyWithOffset(_, _));\n    }\n    if (stage > 3) {\n      EXPECT_CALL(handler, _onChunkComplete());\n    }\n    if (stage > 4) {\n      EXPECT_CALL(handler, _onTrailers(_));\n    }\n    if (stage > 5) {\n      handler.expectEOM();\n    }\n\n    auto txn = httpSession_->newTransaction(&handler);\n    auto streamID = txn->getID();\n    txn->sendHeaders(req);\n    txn->sendBody(makeBuf(5)); // only send half the body\n\n    auto doAbort = [&] {\n      EXPECT_CALL(*codecPtr_, generateRstStream(_, txn->getID(), _));\n      handler.expectDetachTransaction();\n      const auto id = txn->getID();\n      txn->sendAbort();\n      EXPECT_CALL(*codecPtr_,\n                  generateRstStream(_, id, ErrorCode::STREAM_CLOSED))\n          .Times(AtLeast(0));\n    };\n\n    if (stage == 0) {\n      doAbort();\n    }\n    codecCb_->onHeadersComplete(streamID, std::move(resp));\n    if (stage == 1) {\n      doAbort();\n    }\n    codecCb_->onChunkHeader(streamID, respBody->computeChainDataLength());\n    if (stage == 2) {\n      doAbort();\n    }\n    codecCb_->onBody(streamID, std::move(respBody), 0);\n    if (stage == 3) {\n      doAbort();\n    }\n    codecCb_->onChunkComplete(streamID);\n    if (stage == 4) {\n      doAbort();\n    }\n    codecCb_->onTrailersComplete(streamID, std::make_unique<HTTPHeaders>());\n    if (stage == 5) {\n      doAbort();\n    }\n    codecCb_->onMessageComplete(streamID, false);\n\n    eventBase_.loop();\n  }\n};\n\nusing TestAbortPost1 = TestAbortPost<1>;\nusing TestAbortPost2 = TestAbortPost<2>;\nusing TestAbortPost3 = TestAbortPost<3>;\nusing TestAbortPost4 = TestAbortPost<4>;\nusing TestAbortPost5 = TestAbortPost<5>;\n\nTEST_F(TestAbortPost1, Test) {\n  doAbortTest();\n}\nTEST_F(TestAbortPost2, Test) {\n  doAbortTest();\n}\nTEST_F(TestAbortPost3, Test) {\n  doAbortTest();\n}\nTEST_F(TestAbortPost4, Test) {\n  doAbortTest();\n}\nTEST_F(TestAbortPost5, Test) {\n  doAbortTest();\n}\n\nTEST_F(MockHTTPUpstreamTest, AbortUpgrade) {\n  // This is basically the same test as above, just for the upgrade path\n  InSequence enforceOrder;\n  StrictMock<MockHTTPHandler> handler;\n  HTTPMessage req = getPostRequest(10);\n\n  std::unique_ptr<HTTPMessage> resp = makeResponse(200);\n\n  handler.expectTransaction();\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _));\n\n  auto txn = httpSession_->newTransaction(&handler);\n  const auto streamID = txn->getID();\n  txn->sendHeaders(req);\n  txn->sendBody(makeBuf(5)); // only send half the body\n\n  handler.expectHeaders();\n  codecCb_->onHeadersComplete(streamID, std::move(resp));\n\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, streamID, _));\n  handler.expectDetachTransaction();\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, streamID, ErrorCode::STREAM_CLOSED));\n  txn->sendAbort();\n  codecCb_->onMessageComplete(streamID, true); // upgrade\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, streamID, ErrorCode::STREAM_CLOSED));\n  codecCb_->onMessageComplete(streamID, false); // eom\n\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTPUpstreamTest, DrainBeforeSendHeaders) {\n  // Test that drain on session before sendHeaders() is called on open txn\n\n  InSequence enforceOrder;\n  MockHTTPHandler pushHandler;\n\n  auto handler = openTransaction();\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _));\n\n  handler->expectHeaders();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n\n  httpSession_->drain();\n  handler->sendRequest();\n  codecCb_->onHeadersComplete(handler->txn_->getID(), makeResponse(200));\n  codecCb_->onMessageComplete(handler->txn_->getID(), false); // eom\n\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTP2UpstreamTest, ReceiveDoubleGoaway) {\n  // Test that we handle receiving two goaways correctly\n\n  auto req = getGetRequest();\n\n  // Open 2 txns but doesn't send headers yet\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n\n  // Get first goaway acking many un-started txns\n  handler1->expectGoaway();\n  handler2->expectGoaway();\n  codecCb_->onGoaway(101, ErrorCode::NO_ERROR);\n  reusable_ = false;\n\n  // This txn should be alive since it was ack'd by the above goaway\n  handler1->txn_->sendHeaders(req);\n\n  // Second goaway acks the only the current outstanding transaction\n  handler1->expectGoaway();\n  handler2->expectGoaway();\n  handler2->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorStreamUnacknowledged);\n    ASSERT_EQ(folly::to<std::string>(\"StreamUnacknowledged on transaction id: \",\n                                     handler2->txn_->getID()),\n              std::string(err.what()));\n  });\n  handler2->expectDetachTransaction();\n  codecCb_->onGoaway(handler1->txn_->getID(), ErrorCode::NO_ERROR);\n\n  // Clean up\n  httpSession_->drain();\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, handler1->txn_->getID(), _));\n  handler1->expectDetachTransaction();\n  handler1->txn_->sendAbort();\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTP2UpstreamTest, ReceiveDoubleGoaway2) {\n  // Test that we handle receiving two goaways correctly\n  httpSession_->enableDoubleGoawayDrain();\n  auto req = getGetRequest();\n\n  // Open 2 txns but doesn't send headers yet\n  auto handler1 = openTransaction();\n  auto handler2 = openTransaction();\n\n  // Get first goaway acking many un-started txns\n  handler1->expectGoaway();\n  handler2->expectGoaway();\n  codecCb_->onGoaway(http2::kMaxStreamID, ErrorCode::NO_ERROR);\n  reusable_ = false;\n  // this doesn't trigger a goaway\n  EXPECT_EQ(goawayCount_, 0);\n\n  // Sending all unstarted headers should not send the final GOAWAY.\n  handler1->txn_->sendHeadersWithEOM(req);\n  handler2->txn_->sendHeadersWithEOM(req);\n  EXPECT_EQ(goawayCount_, 0);\n\n  handler1->expectHeaders();\n  handler1->expectEOM();\n  handler1->expectDetachTransaction();\n  handler2->expectHeaders();\n  handler2->expectEOM();\n  handler2->expectDetachTransaction();\n  codecCb_->onHeadersComplete(1, makeResponse(200));\n  codecCb_->onMessageComplete(1, false);\n  codecCb_->onHeadersComplete(3, makeResponse(200));\n  codecCb_->onMessageComplete(3, false);\n\n  eventBase_.loop();\n  EXPECT_GE(goawayCount_, 1);\n}\n\nTEST_F(MockHTTP2UpstreamTest, ServerPushInvalidAssoc) {\n  // Test that protocol error is generated on server push\n  // with invalid assoc stream id\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  auto handler = openTransaction();\n\n  int streamID = handler->txn_->getID();\n  int pushID = streamID + 1;\n  int badAssocID = streamID + 2;\n\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::PROTOCOL_ERROR));\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::STREAM_CLOSED))\n      .Times(2);\n\n  auto resp = makeResponse(200);\n  codecCb_->onPushMessageBegin(pushID, badAssocID, resp.get());\n  codecCb_->onHeadersComplete(pushID, std::move(resp));\n  codecCb_->onMessageComplete(pushID, false);\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n\n  resp = makeResponse(200);\n  codecCb_->onMessageBegin(streamID, resp.get());\n  codecCb_->onHeadersComplete(streamID, std::move(resp));\n  codecCb_->onMessageComplete(streamID, false);\n\n  // Cleanup\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, streamID, _));\n  EXPECT_CALL(*handler, _detachTransaction());\n  handler->terminate();\n\n  EXPECT_TRUE(!httpSession_->hasActiveTransactions());\n  httpSession_->destroy();\n}\n\nTEST_F(MockHTTP2UpstreamTest, ServerPushAfterFin) {\n  // Test that protocol error is generated on server push\n  // after FIN is received on regular response on the stream\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  auto handler = openTransaction();\n\n  int streamID = handler->txn_->getID();\n  int pushID = streamID + 1;\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n\n  auto resp = makeResponse(200);\n  codecCb_->onMessageBegin(streamID, resp.get());\n  codecCb_->onHeadersComplete(streamID, std::move(resp));\n  codecCb_->onMessageComplete(streamID, false);\n\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::PROTOCOL_ERROR))\n      .WillOnce(InvokeWithoutArgs([this] {\n        // Verify that the assoc txn is still present\n        EXPECT_TRUE(httpSession_->hasActiveTransactions());\n        return 1;\n      }));\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::STREAM_CLOSED))\n      .Times(2);\n\n  resp = makeResponse(200);\n  codecCb_->onPushMessageBegin(pushID, streamID, resp.get());\n  codecCb_->onHeadersComplete(pushID, std::move(resp));\n  codecCb_->onMessageComplete(pushID, false);\n\n  // Cleanup\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, streamID, _));\n  EXPECT_CALL(*handler, _detachTransaction());\n  handler->terminate();\n\n  EXPECT_TRUE(!httpSession_->hasActiveTransactions());\n  httpSession_->destroy();\n}\n\nTEST_F(MockHTTP2UpstreamTest, ServerPushHandlerInstallFail) {\n  // Test that REFUSED_STREAM error is generated when the session\n  // fails to install the server push handler\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  auto handler = openTransaction();\n\n  int streamID = handler->txn_->getID();\n  int pushID = streamID + 1;\n\n  EXPECT_CALL(*handler, _onPushedTransaction(_))\n      .WillOnce(Invoke([](HTTPTransaction* txn) {\n        // Intentionally unset the handler on the upstream push txn\n        txn->setHandler(nullptr);\n      }));\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::REFUSED_STREAM));\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::STREAM_CLOSED))\n      .Times(2);\n\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  resp->setStatusMessage(\"OK\");\n  codecCb_->onPushMessageBegin(pushID, streamID, resp.get());\n  codecCb_->onHeadersComplete(pushID, std::move(resp));\n  codecCb_->onMessageComplete(pushID, false);\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_FALSE(msg->getIsUpgraded());\n    EXPECT_EQ(200, msg->getStatusCode());\n  });\n  handler->expectEOM();\n\n  resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  resp->setStatusMessage(\"OK\");\n  codecCb_->onMessageBegin(streamID, resp.get());\n  codecCb_->onHeadersComplete(streamID, std::move(resp));\n  codecCb_->onMessageComplete(streamID, false);\n\n  // Cleanup\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, streamID, _));\n  EXPECT_CALL(*handler, _detachTransaction());\n  handler->terminate();\n\n  EXPECT_TRUE(!httpSession_->hasActiveTransactions());\n  httpSession_->destroy();\n}\n\nTEST_F(MockHTTP2UpstreamTest, ServerPushUnhandledAssoc) {\n  // Test that REFUSED_STREAM error is generated when the assoc txn\n  // is unhandled\n  InSequence enforceOrder;\n  auto req = getGetRequest();\n  auto handler = openTransaction();\n\n  int streamID = handler->txn_->getID();\n  int pushID = streamID + 1;\n\n  // Forcefully unset the handler on the assoc txn\n  handler->txn_->setHandler(nullptr);\n\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::REFUSED_STREAM));\n  EXPECT_CALL(*codecPtr_,\n              generateRstStream(_, pushID, ErrorCode::STREAM_CLOSED))\n      .Times(2);\n\n  auto resp = std::make_unique<HTTPMessage>();\n  resp->setStatusCode(200);\n  resp->setStatusMessage(\"OK\");\n  codecCb_->onPushMessageBegin(pushID, streamID, resp.get());\n  codecCb_->onHeadersComplete(pushID, std::move(resp));\n  codecCb_->onMessageComplete(pushID, false);\n\n  // Cleanup\n  EXPECT_CALL(*codecPtr_, generateRstStream(_, streamID, _));\n  handler->terminate();\n\n  EXPECT_TRUE(!httpSession_->hasActiveTransactions());\n  httpSession_->destroy();\n}\n\nTEST_F(MockHTTPUpstreamTest, HeadersThenBodyThenHeaders) {\n  HTTPMessage req = getGetRequest();\n  auto handler = openTransaction();\n  handler->txn_->sendHeaders(req);\n\n  handler->expectHeaders();\n  EXPECT_CALL(*handler, _onBodyWithOffset(_, _));\n  // After getting the second headers, transaction will detach the handler\n  handler->expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorIngressStateTransition);\n    ASSERT_EQ(\n        \"Invalid ingress state transition, state=RegularBodyReceived, \"\n        \"event=onFinalHeaders, streamID=1\",\n        std::string(err.what()));\n  });\n  handler->expectDetachTransaction();\n  auto resp = makeResponse(200);\n  codecCb_->onMessageBegin(1, resp.get());\n  codecCb_->onHeadersComplete(1, std::move(resp));\n  codecCb_->onBody(1, makeBuf(20), 0);\n  // Now receive headers again, on the same stream (illegal!)\n  codecCb_->onHeadersComplete(1, makeResponse(200));\n  eventBase_.loop();\n}\n\nTEST_F(MockHTTP2UpstreamTest, DelayUpstreamWindowUpdate) {\n  EXPECT_CALL(*codecPtr_, supportsStreamFlowControl())\n      .WillRepeatedly(Return(true));\n\n  auto handler = openTransaction();\n  handler->txn_->setReceiveWindow(1000000); // One miiiillion\n\n  InSequence enforceOrder;\n  EXPECT_CALL(*codecPtr_, generateHeader(_, _, _, _, _, _));\n  EXPECT_CALL(*codecPtr_, generateWindowUpdate(_, _, _));\n\n  HTTPMessage req = getGetRequest();\n  handler->txn_->sendHeaders(req);\n  handler->expectDetachTransaction();\n  handler->txn_->sendAbort();\n  httpSession_->destroy();\n}\n\nTEST_F(MockHTTPUpstreamTest, ForceShutdownInSetTransaction) {\n  StrictMock<MockHTTPHandler> handler;\n  handler.expectTransaction([&](HTTPTransaction* txn) {\n    handler.txn_ = txn;\n    httpSession_->dropConnection();\n  });\n  handler.expectError([&](const HTTPException& err) {\n    EXPECT_TRUE(err.hasProxygenError());\n    EXPECT_EQ(err.getProxygenError(), kErrorDropped);\n    ASSERT_EQ(folly::to<std::string>(\"Dropped on transaction id: \",\n                                     handler.txn_->getID()),\n              std::string(err.what()));\n  });\n  handler.expectDetachTransaction();\n  (void)httpSession_->newTransaction(&handler);\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestReplaySafetyCallback) {\n  auto sock = dynamic_cast<HTTPTransaction::Transport*>(httpSession_);\n\n  StrictMock<folly::test::MockReplaySafetyCallback> cb1;\n  StrictMock<folly::test::MockReplaySafetyCallback> cb2;\n  StrictMock<folly::test::MockReplaySafetyCallback> cb3;\n\n  EXPECT_CALL(*transport_, isReplaySafe()).WillRepeatedly(Return(false));\n  sock->addWaitingForReplaySafety(&cb1);\n  sock->addWaitingForReplaySafety(&cb2);\n  sock->addWaitingForReplaySafety(&cb3);\n  sock->removeWaitingForReplaySafety(&cb2);\n\n  ON_CALL(*transport_, isReplaySafe()).WillByDefault(Return(true));\n  EXPECT_CALL(cb1, onReplaySafe_());\n  EXPECT_CALL(cb3, onReplaySafe_());\n  replaySafetyCallback_->onReplaySafe();\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestAlreadyReplaySafe) {\n  auto sock = dynamic_cast<HTTPTransaction::Transport*>(httpSession_);\n\n  StrictMock<folly::test::MockReplaySafetyCallback> cb;\n\n  EXPECT_CALL(*transport_, isReplaySafe()).WillRepeatedly(Return(true));\n  EXPECT_CALL(cb, onReplaySafe_());\n  sock->addWaitingForReplaySafety(&cb);\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestChainedBufIngress) {\n  auto buf = folly::IOBuf::copyBuffer(\"hi\");\n  buf->prependChain(folly::IOBuf::copyBuffer(\"hello\"));\n\n  MockHTTPSessionInfoCallback infoCb;\n  this->httpSession_->setInfoCallback(&infoCb);\n\n  EXPECT_CALL(infoCb, onRead(_, 7, _));\n  readCallback_->readBufferAvailable(std::move(buf));\n\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, AttachDetach) {\n  folly::EventBase base;\n  auto timer = folly::HHWheelTimer::newTimer(\n      &base,\n      std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      folly::TimeoutManager::InternalEnum::INTERNAL,\n      std::chrono::milliseconds(500));\n  WheelTimerInstance timerInstance(timer.get());\n  uint64_t filterCount = 0;\n  auto fn = [&filterCount](HTTPCodecFilter* /*filter*/) { filterCount++; };\n\n  InSequence enforceOrder;\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n  egressCodec->generateConnectionPreface(output);\n  egressCodec->generateSettings(output);\n\n  for (auto i = 0; i < 2; i++) {\n    auto handler = openTransaction();\n    handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n      EXPECT_EQ(200, msg->getStatusCode());\n    });\n    handler->expectBody();\n    handler->expectEOM();\n    handler->expectDetachTransaction();\n\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    egressCodec->generateHeader(output, handler->txn_->getID(), resp);\n    egressCodec->generateBody(output,\n                              handler->txn_->getID(),\n                              makeBuf(20),\n                              HTTPCodec::NoPadding,\n                              true /* eom */);\n\n    handler->sendRequest();\n    auto buf = output.move();\n    buf->coalesce();\n    readAndLoop(buf.get());\n\n    httpSession_->detachThreadLocals();\n    httpSession_->attachThreadLocals(\n        &base, nullptr, timerInstance, nullptr, fn, nullptr, nullptr);\n    EXPECT_EQ(filterCount, 2);\n    filterCount = 0;\n    base.loopOnce();\n  }\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, DetachFlowControlTimeout) {\n  folly::EventBase base;\n  auto timer = folly::HHWheelTimer::newTimer(\n      &base,\n      std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      folly::TimeoutManager::InternalEnum::INTERNAL,\n      std::chrono::milliseconds(500));\n  WheelTimerInstance timerInstance(timer.get());\n  uint64_t filterCount = 0;\n  auto fn = [&filterCount](HTTPCodecFilter* /*filter*/) { filterCount++; };\n\n  InSequence enforceOrder;\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n  egressCodec->generateConnectionPreface(output);\n  egressCodec->generateSettings(output);\n\n  for (auto i = 0; i < 2; i++) {\n    auto handler = openTransaction();\n    if (i == 1) {\n      handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n        EXPECT_EQ(200, msg->getStatusCode());\n      });\n      handler->expectBody();\n      handler->expectEOM();\n\n      HTTPMessage resp;\n      resp.setStatusCode(200);\n      egressCodec->generateHeader(output, handler->txn_->getID(), resp);\n      egressCodec->generateBody(output,\n                                handler->txn_->getID(),\n                                makeBuf(20),\n                                HTTPCodec::NoPadding,\n                                true /* eom */);\n    } else {\n      handler->expectEgressPaused();\n      egressCodec->generateWindowUpdate(output, 0, 65536 * 2);\n    }\n    handler->expectDetachTransaction();\n\n    handler->txn_->sendHeaders(getPostRequest(65536 - 2 * i));\n    handler->txn_->sendBody(makeBuf(65536 - 2 * i));\n    handler->txn_->sendEOM();\n    if (i == 0) {\n      eventBase_.loopOnce();\n      handler->txn_->sendAbort();\n      // Even though there are no transactions, the fc timeout is still\n      // registered.\n      EXPECT_FALSE(httpSession_->isDetachable(false));\n    }\n\n    auto buf = output.move();\n    buf->coalesce();\n    readAndLoop(buf.get());\n\n    EXPECT_TRUE(httpSession_->isDetachable(false));\n    httpSession_->detachThreadLocals();\n    httpSession_->attachThreadLocals(\n        &base, nullptr, timerInstance, nullptr, fn, nullptr, nullptr);\n    EXPECT_EQ(filterCount, 2);\n    filterCount = 0;\n    base.loopOnce();\n  }\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestPingPreserveData) {\n  auto serverCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n  serverCodec->generateConnectionPreface(output);\n  serverCodec->generateSettings(output);\n\n  auto pingData = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::steady_clock::now().time_since_epoch())\n                      .count();\n  serverCodec->generatePingRequest(output, pingData);\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  serverCodec->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onPingReply(pingData));\n  auto buf = output.move();\n  buf->coalesce();\n  readAndLoop(buf.get());\n  parseOutput(*serverCodec);\n  httpSession_->destroy();\n}\n\nclass H2LargeFlowControl : public HTTP2UpstreamSessionTest {\n  void SetUp() override {\n    constexpr uint32_t kCapacity = 1024 * 1024;\n    flowControl_ = {kCapacity, kCapacity, kCapacity};\n    HTTP2UpstreamSessionTest::SetUp();\n  }\n};\n\n/*\n * Verifies that WINDOW_UPDATE is sent when at least kMinThreshold bytes are\n * read.\n */\nTEST_F(H2LargeFlowControl, WindowUpdateThresholdTest) {\n  auto handler = openTransaction();\n  handler->txn_->pauseIngress(); // buffer incoming bytes\n  handler->sendRequest();        // getRequest, sends request and eom\n  eventBase_.loopOnce();\n  eventBase_.loopOnce();\n\n  auto serverCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n  serverCodec->generateConnectionPreface(output);\n  serverCodec->generateSettings(output);\n\n  // enqueue 1MB of data into txn\n  auto resp = makeResponse(200);\n  serverCodec->generateHeader(output, handler->txn_->getID(), *resp, false);\n  serverCodec->generateBody(output,\n                            handler->txn_->getID(),\n                            makeBuf(1024 * 1024),\n                            folly::none /* padding */,\n                            false /* eom */);\n\n  auto input = output.move();\n  input->coalesce();\n  readAndLoop(input->data(),\n              input->length()); // buffers server's headers and body to txn\n\n  uint32_t bytesReadSoFar = 0;\n  constexpr uint32_t kMinThreshold = 128 * 1024;\n\n  handler->expectHeaders();\n  // setting up expectation, pause ingress after reading 128KB in callback\n  handler->expectBodyRepeatedly([&] {\n    bytesReadSoFar += input->computeChainDataLength();\n    if (bytesReadSoFar >= kMinThreshold) {\n      handler->txn_->pauseIngress();\n    }\n  });\n\n  handler->txn_->resumeIngress(); // only sent to the handler after this point\n  handler->expectBodyRepeatedly([&] {});\n  handler->expectDetachTransaction();\n\n  // expect window update here\n  NiceMock<MockHTTPCodecCallback> callbacks;\n  serverCodec->setCallback(&callbacks);\n  EXPECT_CALL(callbacks, onWindowUpdate(0, _)).Times(AnyNumber());\n  EXPECT_CALL(callbacks, onWindowUpdate(handler->txn_->getID(), kMinThreshold))\n      .Times(AtLeast(1));\n\n  handler->txn_->resumeIngress();\n  eventBase_.loop();\n  handler->txn_->sendAbort();\n  parseOutput(*serverCodec); // client's buffer -> serverCodec\n  httpSession_->destroy();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, TestConnectionToken) {\n  auto handler = openTransaction();\n  handler->expectError();\n  handler->expectDetachTransaction();\n\n  // The transaction should not have a connection token\n  // by default.\n  EXPECT_EQ(handler->txn_->getConnectionToken(), folly::none);\n\n  // Passing connection token to a session should\n  // make it visible to the transaction.\n  HTTPTransaction::ConnectionToken connToken{\"TOKEN1234\"};\n  httpSession_->setConnectionToken(connToken);\n\n  EXPECT_NE(handler->txn_->getConnectionToken(), folly::none);\n  EXPECT_EQ(*handler->txn_->getConnectionToken(), connToken);\n\n  eventBase_.loop();\n  httpSession_->dropConnection();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, HTTPPriority) {\n  auto handler = openTransaction();\n  handler->expectError();\n  handler->expectDetachTransaction();\n  EXPECT_EQ(handler->txn_->getHTTPPriority(), folly::none);\n  eventBase_.loop();\n  httpSession_->dropConnection();\n}\n\nTEST_F(HTTP2UpstreamSessionTest, Observer_Attach_Detach_Destroy) {\n  MockSessionObserver::EventSet eventSet;\n\n  // Test attached/detached callbacks when adding/removing observers\n  {\n    auto observer = addMockSessionObserver(eventSet);\n    EXPECT_CALL(*observer, detached(_));\n    httpSession_->removeObserver(observer.get());\n  }\n\n  // Test destroyed callback when session is destroyed\n  {\n\n    auto observer = addMockSessionObserver(eventSet);\n    httpSession_->addObserver(observer.get());\n\n    auto egressCodec = makeServerCodec();\n    folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n    egressCodec->generateSettings(output);\n    HTTPMessage resp;\n    resp.setStatusCode(200);\n    egressCodec->generateHeader(output, 1, resp);\n    auto buf = makeBuf(100);\n    egressCodec->generateBody(\n        output, 1, std::move(buf), HTTPCodec::NoPadding, true /* eom */);\n    std::unique_ptr<folly::IOBuf> input = output.move();\n    input->coalesce();\n\n    auto handler = openTransaction();\n\n    handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n      EXPECT_EQ(200, msg->getStatusCode());\n    });\n    handler->expectBody();\n    handler->expectEOM();\n    handler->expectDetachTransaction();\n    HTTPMessage req = getGetRequest();\n    handler->sendRequest(req);\n    readAndLoop(input->data(), input->length());\n    EXPECT_CALL(*observer, destroyed(_, _));\n    httpSession_->destroy();\n  }\n}\n\nTEST_F(HTTP2UpstreamSessionTest, Observer_RequestStarted) {\n\n  // Add an observer NOT subscribed to the RequestStarted event\n  auto observerUnsubscribed =\n      addMockSessionObserver(MockSessionObserver::EventSetBuilder().build());\n  httpSession_->addObserver(observerUnsubscribed.get());\n\n  // Add an observer subscribed to this event\n  auto observerSubscribed = addMockSessionObserver(\n      MockSessionObserver::EventSetBuilder()\n          .enable(HTTPSessionObserverInterface::Events::RequestStarted)\n          .build());\n  httpSession_->addObserver(observerSubscribed.get());\n\n  EXPECT_CALL(*observerUnsubscribed, requestStarted(_, _)).Times(0);\n\n  HTTPTransactionObserverAccessor* actualTxnObserverAccessor;\n  // expect to see a request started with header 'x-meta-test-header' having\n  // value 'abc123'\n  EXPECT_CALL(*observerSubscribed, requestStarted(_, _))\n      .WillOnce(\n          Invoke([&](HTTPSessionObserverAccessor*,\n                     const MockSessionObserver::RequestStartedEvent& event) {\n            EXPECT_EQ(event.request.getHeaders().getSingleOrEmpty(\n                          \"x-meta-test-header\"),\n                      \"abc123\");\n            actualTxnObserverAccessor = event.txnObserverAccessor;\n          }));\n  auto egressCodec = makeServerCodec();\n  folly::IOBufQueue output(folly::IOBufQueue::cacheChainLength());\n\n  egressCodec->generateSettings(output);\n\n  // While we are testing for the presence of the expected request headers (in\n  // this test), We also add response headers to check we do not trigger\n  // \"ingress\" call back on the client\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  resp.getHeaders().set(\"header1\", \"value1\");\n  egressCodec->generateHeader(output, 1, resp);\n  auto buf = makeBuf(100);\n  egressCodec->generateBody(\n      output, 1, std::move(buf), HTTPCodec::NoPadding, true /* eom */);\n  std::unique_ptr<folly::IOBuf> input = output.move();\n  input->coalesce();\n\n  auto handler = openTransaction();\n\n  handler->expectHeaders([&](std::shared_ptr<HTTPMessage> msg) {\n    EXPECT_EQ(200, msg->getStatusCode());\n    EXPECT_EQ(msg->getHeaders().getSingleOrEmpty(\"header1\"), \"value1\");\n  });\n  handler->expectBody();\n  handler->expectEOM();\n  handler->expectDetachTransaction();\n  HTTPMessage req = getGetRequest();\n  req.getHeaders().add(\"x-meta-test-header\", \"abc123\");\n  handler->sendRequest(req);\n  readAndLoop(input->data(), input->length());\n  EXPECT_EQ(actualTxnObserverAccessor, handler->txn_->getObserverAccessor());\n  httpSession_->destroy();\n}\n\n// Register and instantiate all our type-paramterized tests\nREGISTER_TYPED_TEST_SUITE_P(HTTPUpstreamTest, ImmediateEof);\n\nusing AllTypes = ::testing::Types<HTTP1xCodecPair, HTTP2CodecPair>;\nINSTANTIATE_TYPED_TEST_SUITE_P(AllTypesPrefix, HTTPUpstreamTest, AllTypes);\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockByteEventTracker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/session/ByteEventTracker.h>\n\nnamespace proxygen {\n\nclass MockByteEventTracker : public ByteEventTracker {\n public:\n  explicit MockByteEventTracker(Callback* callback)\n      : ByteEventTracker(callback) {\n  }\n\n  MOCK_METHOD(void,\n              addPingByteEvent,\n              (size_t, TimePoint, uint64_t, ByteEvent::Callback));\n  MOCK_METHOD(void,\n              addFirstBodyByteEvent,\n              (uint64_t, HTTPTransaction*, ByteEvent::Callback));\n  MOCK_METHOD(void,\n              addFirstHeaderByteEvent,\n              (uint64_t, HTTPTransaction*, ByteEvent::Callback));\n  MOCK_METHOD(size_t, drainByteEvents, ());\n  MOCK_METHOD(bool,\n              processByteEvents,\n              (std::shared_ptr<ByteEventTracker>, uint64_t));\n  MOCK_METHOD(uint64_t, preSend, (bool*, bool*, bool*, uint64_t));\n\n  MOCK_METHOD((void),\n              addTrackedByteEvent,\n              (HTTPTransaction*, uint64_t, ByteEvent::Callback),\n              (noexcept));\n  MOCK_METHOD((void),\n              addLastByteEvent,\n              (HTTPTransaction*, uint64_t, ByteEvent::Callback),\n              (noexcept));\n  MOCK_METHOD(\n      (void),\n      addTxByteEvent,\n      (uint64_t, ByteEvent::EventType, HTTPTransaction*, ByteEvent::Callback),\n      (noexcept));\n  MOCK_METHOD(\n      (void),\n      addAckByteEvent,\n      (uint64_t, ByteEvent::EventType, HTTPTransaction*, ByteEvent::Callback),\n      (noexcept));\n\n  // passthru to callback implementation functions\n  void onTxnByteEventWrittenToBuf(const ByteEvent& event) {\n    callback_->onTxnByteEventWrittenToBuf(event);\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockCodecDownstreamTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <fizz/record/Extensions.h>\n#include <fizz/record/Types.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/TimeoutManager.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/session/HTTPDirectResponseHandler.h>\n#include <proxygen/lib/http/session/HTTPDownstreamSession.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n#include <proxygen/lib/http/session/test/HTTPSessionMocks.h>\n#include <proxygen/lib/http/session/test/HTTPSessionTest.h>\n#include <proxygen/lib/http/session/test/MockSecondaryAuthManager.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <proxygen/lib/test/TestAsyncTransport.h>\n#include <sstream>\n#include <string>\n#include <vector>\n\nusing folly::test::MockAsyncTransport;\n\nusing namespace wangle;\nusing namespace fizz;\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nconst HTTPSettings kDefaultIngressSettings{\n    {SettingsId::INITIAL_WINDOW_SIZE, http2::kInitialWindow}};\nconst HTTPSettings kIngressCertAuthSettings{\n    {SettingsId::SETTINGS_HTTP_CERT_AUTH, 128}};\nHTTPSettings kEgressCertAuthSettings{\n    {SettingsId::SETTINGS_HTTP_CERT_AUTH, 128}};\n\nclass MockCodecDownstreamTest : public testing::Test {\n public:\n  MockCodecDownstreamTest()\n      : eventBase_(),\n        codec_(new StrictMock<MockHTTPCodec>()),\n        transport_(new NiceMock<MockAsyncTransport>()),\n        transactionTimeouts_(makeTimeoutSet(&eventBase_)) {\n\n    HTTP2PriorityQueue::setNodeLifetime(std::chrono::milliseconds(2));\n    EXPECT_CALL(*transport_, writeChain(_, _, _))\n        .WillRepeatedly(Invoke(this, &MockCodecDownstreamTest::onWriteChain));\n    EXPECT_CALL(*transport_, good())\n        .WillRepeatedly(ReturnPointee(&transportGood_));\n    EXPECT_CALL(*transport_, closeNow())\n        .WillRepeatedly(Assign(&transportGood_, false));\n    EXPECT_CALL(*transport_, getEventBase())\n        .WillRepeatedly(Return(&eventBase_));\n    EXPECT_CALL(*transport_, setReadCB(_))\n        .WillRepeatedly(SaveArg<0>(&transportCb_));\n    EXPECT_CALL(mockController_, getGracefulShutdownTimeout())\n        .WillRepeatedly(Return(std::chrono::milliseconds(0)));\n    EXPECT_CALL(mockController_, getHeaderIndexingStrategy())\n        .WillRepeatedly(Return(HeaderIndexingStrategy::getDefaultInstance()));\n    EXPECT_CALL(mockController_, attachSession(_));\n    EXPECT_CALL(mockController_, onTransportReady(_));\n    EXPECT_CALL(*codec_, setCallback(_))\n        .WillRepeatedly(SaveArg<0>(&codecCallback_));\n    EXPECT_CALL(*codec_, supportsParallelRequests())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec_, supportsPushTransactions())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec_, getTransportDirection())\n        .WillRepeatedly(Return(TransportDirection::DOWNSTREAM));\n    EXPECT_CALL(*codec_, getEgressSettings());\n    EXPECT_CALL(*codec_, supportsStreamFlowControl())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec_, getProtocol())\n        .WillRepeatedly(Return(CodecProtocol::HTTP_2));\n    EXPECT_CALL(*codec_, getUserAgent()).WillRepeatedly(ReturnRef(userAgent_));\n    EXPECT_CALL(*codec_, setParserPaused(_)).WillRepeatedly(Return());\n    EXPECT_CALL(*codec_, supportsSessionFlowControl())\n        .WillRepeatedly(Return(true));\n    EXPECT_CALL(*codec_, getIngressSettings())\n        .WillRepeatedly(Return(&kDefaultIngressSettings));\n    EXPECT_CALL(*codec_, isReusable())\n        .WillRepeatedly(ReturnPointee(&reusable_));\n    EXPECT_CALL(*codec_, isWaitingToDrain())\n        .WillRepeatedly(ReturnPointee(&drainPending_));\n    EXPECT_CALL(*codec_, generateSettings(_));\n    EXPECT_CALL(*codec_, getDefaultWindowSize())\n        .WillRepeatedly(Return(http2::kInitialWindow));\n    EXPECT_CALL(*codec_, createStream()).WillRepeatedly(InvokeWithoutArgs([&] {\n      return pushStreamID_ += 2;\n    }));\n    EXPECT_CALL(*codec_, enableDoubleGoawayDrain()).WillRepeatedly(Invoke([&] {\n      doubleGoaway_ = true;\n    }));\n    EXPECT_CALL(*codec_, generateGoaway(_, _, _, _))\n        .WillRepeatedly(Invoke([this](IOBufQueue& writeBuf,\n                                      HTTPCodec::StreamID /*lastStream*/,\n                                      ErrorCode,\n                                      std::shared_ptr<folly::IOBuf>) {\n          LOG(INFO) << \"MOCK GENERATE GOAWAY\";\n          if (reusable_) {\n            reusable_ = false;\n            drainPending_ = doubleGoaway_;\n          } else if (!drainPending_) {\n            return 0;\n          } else {\n            drainPending_ = false;\n          }\n          if (liveGoaways_) {\n            writeBuf.append(string(\"x\"));\n          }\n          return 1;\n        }));\n    EXPECT_CALL(*codec_, generateRstStream(_, _, _)).WillRepeatedly(Return(1));\n    EXPECT_CALL(*codec_, mapPriorityToDependency(_)).WillRepeatedly(Return(0));\n\n    HTTPSession::setDefaultReadBufferLimit(65536);\n    httpSession_ =\n        new HTTPDownstreamSession(transactionTimeouts_.get(),\n                                  AsyncTransport::UniquePtr(transport_),\n                                  localAddr,\n                                  peerAddr,\n                                  &mockController_,\n                                  std::unique_ptr<HTTPCodec>(codec_),\n                                  mockTransportInfo,\n                                  nullptr);\n    httpSession_->startNow();\n    eventBase_.loop();\n  }\n\n  void onWriteChain(folly::AsyncTransport::WriteCallback* callback,\n                    std::shared_ptr<IOBuf> /*iob*/,\n                    WriteFlags) {\n    writeCount_++;\n    if (invokeWriteSuccess_) {\n      callback->writeSuccess();\n    } else {\n      cbs_.push_back(callback);\n    }\n  }\n\n  ~MockCodecDownstreamTest() override {\n    AsyncSocketException ex(AsyncSocketException::UNKNOWN, \"\");\n    for (auto& cb : cbs_) {\n      cb->writeErr(0, ex);\n    }\n  }\n\n  void SetUp() override {\n    HTTPSession::setDefaultWriteBufferLimit(65536);\n  }\n\n  // Pass a function to execute inside HTTPCodec::onIngress(). This\n  // function also takes care of passing an empty ingress buffer to the codec.\n  template <class T>\n  void onIngressImpl(T f) {\n    EXPECT_CALL(*codec_, onIngress(_)).WillOnce(Invoke([&f](const IOBuf& buf) {\n      CHECK_GT(buf.computeChainDataLength(), 0);\n      // The test should be independent of the dummy buffer,\n      // so don't pass it in.\n      f();\n      return buf.computeChainDataLength();\n    }));\n\n    void* buf;\n    size_t bufSize;\n    transportCb_->getReadBuffer(&buf, &bufSize);\n    transportCb_->readDataAvailable(bufSize);\n  }\n\n  void testGoaway(bool doubleGoaway, bool dropConnection);\n\n  void testConnFlowControlBlocked(bool timeout);\n\n protected:\n  EventBase eventBase_;\n  // invalid once httpSession_ is destroyed\n  StrictMock<MockHTTPCodec>* codec_;\n  std::string userAgent_{\"MockCodec\"};\n  HTTPCodec::Callback* codecCallback_{nullptr};\n  NiceMock<MockAsyncTransport>* transport_;\n  folly::AsyncTransport::ReadCallback* transportCb_;\n  folly::HHWheelTimer::UniquePtr transactionTimeouts_;\n  StrictMock<MockController> mockController_;\n  HTTPDownstreamSession* httpSession_;\n  HTTPCodec::StreamID pushStreamID_{0};\n  bool reusable_{true};\n  bool transportGood_{true};\n  bool drainPending_{false};\n  bool doubleGoaway_{false};\n  bool liveGoaways_{false};\n  bool invokeWriteSuccess_{false};\n  uint32_t writeCount_{0};\n  std::vector<folly::AsyncTransport::WriteCallback*> cbs_;\n};\n\nTEST_F(MockCodecDownstreamTest, OnAbortThenTimeouts) {\n  // Test what happens when txn1 (out of many transactions) gets an abort\n  // followed by a transaction timeout followed by a write timeout\n  MockHTTPHandler handler1;\n  MockHTTPHandler handler2;\n  auto req1 = makeGetRequest();\n  auto req2 = makeGetRequest();\n\n  fakeMockCodec(*codec_);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1))\n      .WillOnce(Return(&handler2));\n\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n  EXPECT_CALL(handler1, _onHeadersComplete(_))\n      .WillOnce(Invoke([&handler1](std::shared_ptr<HTTPMessage>) {\n        handler1.sendHeaders(200, 100);\n        handler1.sendBody(100);\n      }));\n  EXPECT_CALL(handler1, _onError(_));\n  EXPECT_CALL(handler1, _detachTransaction());\n  EXPECT_CALL(handler2, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler2](HTTPTransaction* txn) { handler2.txn_ = txn; }));\n  EXPECT_CALL(handler2, _onHeadersComplete(_))\n      .WillOnce(Invoke([&handler2](std::shared_ptr<HTTPMessage>) {\n        handler2.sendHeaders(200, 100);\n        handler2.sendBody(100);\n      }));\n  EXPECT_CALL(handler2, _onBodyWithOffset(_, _));\n  EXPECT_CALL(handler2, _onError(_))\n      .WillOnce(Invoke([&](const HTTPException& ex) {\n        ASSERT_EQ(ex.getProxygenError(), kErrorWriteTimeout);\n        ASSERT_EQ(folly::to<std::string>(\"WriteTimeout on transaction id: \",\n                                         handler2.txn_->getID()),\n                  std::string(ex.what()));\n      }));\n  EXPECT_CALL(handler2, _detachTransaction());\n\n  EXPECT_CALL(mockController_, detachSession(_));\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(3), req2.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(3), std::move(req2));\n  // do the write, enqeue byte event\n  eventBase_.loopOnce();\n\n  // recv an abort, detach the handler from txn1 (txn1 stays around due to the\n  // enqueued byte event)\n  codecCallback_->onAbort(HTTPCodec::StreamID(1), ErrorCode::PROTOCOL_ERROR);\n  // recv a transaction timeout on txn1 (used to erroneously create a direct\n  // response handler)\n  handler1.txn_->timeoutExpired();\n\n  // have a write timeout expire (used to cause the direct response handler to\n  // write out data, messing up the state machine)\n  eventBase_.runAfterDelay(\n      [this] {\n        // refresh ingress timeout\n        codecCallback_->onBody(HTTPCodec::StreamID(3), makeBuf(10), 0);\n      },\n      transactionTimeouts_->getDefaultTimeout().count() / 2);\n  // hold evb open long enough to fire write timeout (since transactionTimeouts_\n  // is internal\n  eventBase_.runAfterDelay(\n      [] {}, transactionTimeouts_->getDefaultTimeout().count() + 100);\n  eventBase_.loop();\n}\n\nTEST_F(MockCodecDownstreamTest, ServerPush) {\n  MockHTTPHandler handler;\n  MockHTTPPushHandler pushHandler;\n  auto req = makeGetRequest();\n  HTTPTransaction* pushTxn = nullptr;\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(_)).WillOnce(SaveArg<0>(&handler.txn_));\n\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage>) {\n        pushTxn = handler.txn_->newPushedTransaction(&pushHandler);\n        pushHandler.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler.sendBody(100);\n        pushTxn->sendEOM();\n        eventBase_.loopOnce(); // flush the push txn's body\n      }));\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n\n  EXPECT_CALL(*codec_, generatePushPromise(_, 2, _, _, _, _));\n  EXPECT_CALL(*codec_,\n              generateBody(_, 2, PtrBufHasLen(uint64_t(100)), _, true));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  EXPECT_CALL(handler, _onEOM()).WillOnce(Invoke([&] {\n    handler.sendReplyWithBody(200, 100);\n    eventBase_.loopOnce(); // flush the response to the normal request\n  }));\n\n  EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _));\n  EXPECT_CALL(*codec_,\n              generateBody(_, 1, PtrBufHasLen(uint64_t(100)), _, true));\n  EXPECT_CALL(handler, _detachTransaction());\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req));\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ServerPushAfterGoaway) {\n  // Tests if goaway\n  //   - drains acknowledged server push transactions\n  //   - aborts server pushed transactions not created at the client\n  //   - prevents new transactions from being created.\n  MockHTTPHandler handler;\n  MockHTTPPushHandler pushHandler1;\n  MockHTTPPushHandler pushHandler2;\n  HTTPTransaction* pushTxn = nullptr;\n\n  fakeMockCodec(*codec_);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n\n  EXPECT_CALL(handler, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler](HTTPTransaction* txn) { handler.txn_ = txn; }));\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage>) {\n        // Initiate server push transactions.\n        pushTxn = handler.txn_->newPushedTransaction(&pushHandler1);\n        CHECK_EQ(pushTxn->getID(), HTTPCodec::StreamID(2));\n        pushHandler1.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler1.sendBody(100);\n        pushTxn->sendEOM();\n        // Initiate the second push transaction which will be aborted\n        pushTxn = handler.txn_->newPushedTransaction(&pushHandler2);\n        CHECK_EQ(pushTxn->getID(), HTTPCodec::StreamID(4));\n        pushHandler2.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler2.sendBody(100);\n        pushTxn->sendEOM();\n      }));\n  // Push transaction 1 - drained\n  EXPECT_CALL(pushHandler1, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler1](HTTPTransaction* txn) { pushHandler1.txn_ = txn; }));\n  EXPECT_CALL(pushHandler1, _detachTransaction());\n  // Push transaction 2 - aborted by onError after goaway\n  EXPECT_CALL(pushHandler2, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler2](HTTPTransaction* txn) { pushHandler2.txn_ = txn; }));\n  EXPECT_CALL(pushHandler2, _onError(_))\n      .WillOnce(Invoke([&](const HTTPException& err) {\n        EXPECT_TRUE(err.hasProxygenError());\n        EXPECT_EQ(err.getProxygenError(), kErrorStreamUnacknowledged);\n        ASSERT_EQ(\n            folly::to<std::string>(\"StreamUnacknowledged on transaction id: \",\n                                   pushHandler2.txn_->getID()),\n            std::string(err.what()));\n      }));\n  EXPECT_CALL(pushHandler2, _detachTransaction());\n\n  EXPECT_CALL(handler, _onEOM());\n  EXPECT_CALL(handler, _detachTransaction());\n\n  // Receive client request\n  auto req = makeGetRequest();\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req));\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  // Receive goaway acknowledging only the first pushed transactions with id 2.\n  codecCallback_->onGoaway(2, ErrorCode::NO_ERROR);\n\n  // New server pushed transaction cannot be created after goaway\n  MockHTTPPushHandler pushHandler3;\n  EXPECT_EQ(handler.txn_->newPushedTransaction(&pushHandler3), nullptr);\n\n  // Send response to the initial client request and this destroys the session\n  handler.sendReplyWithBody(200, 100);\n\n  eventBase_.loop();\n\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ServerPushAbort) {\n  // Test that assoc txn and other push txns are not affected when client aborts\n  // a push txn\n  MockHTTPHandler handler;\n  MockHTTPPushHandler pushHandler1;\n  MockHTTPPushHandler pushHandler2;\n  HTTPTransaction* pushTxn1 = nullptr;\n  HTTPTransaction* pushTxn2 = nullptr;\n\n  fakeMockCodec(*codec_);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n\n  EXPECT_CALL(handler, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler](HTTPTransaction* txn) { handler.txn_ = txn; }));\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage>) {\n        // Initiate server push transactions\n        pushTxn1 = handler.txn_->newPushedTransaction(&pushHandler1);\n        CHECK_EQ(pushTxn1->getID(), HTTPCodec::StreamID(2));\n        pushHandler1.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler1.sendBody(100);\n\n        pushTxn2 = handler.txn_->newPushedTransaction(&pushHandler2);\n        CHECK_EQ(pushTxn2->getID(), HTTPCodec::StreamID(4));\n        pushHandler2.sendPushHeaders(\"/bar\", \"www.bar.com\", 200);\n        pushHandler2.sendBody(200);\n        pushTxn2->sendEOM();\n      }));\n\n  // pushTxn1 should be aborted\n  EXPECT_CALL(pushHandler1, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler1](HTTPTransaction* txn) { pushHandler1.txn_ = txn; }));\n  EXPECT_CALL(pushHandler1, _onError(_))\n      .WillOnce(Invoke([&](const HTTPException& err) {\n        EXPECT_TRUE(err.hasProxygenError());\n        EXPECT_EQ(err.getProxygenError(), kErrorStreamAbort);\n        ASSERT_EQ(\"Stream aborted, streamID=2, code=CANCEL\",\n                  std::string(err.what()));\n      }));\n  EXPECT_CALL(pushHandler1, _detachTransaction());\n\n  EXPECT_CALL(pushHandler2, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler2](HTTPTransaction* txn) { pushHandler2.txn_ = txn; }));\n  EXPECT_CALL(pushHandler2, _detachTransaction());\n\n  EXPECT_CALL(handler, _onEOM());\n  EXPECT_CALL(handler, _detachTransaction());\n\n  // Receive client request\n  auto req = makeGetRequest();\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req));\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  // Send client abort on one push txn\n  codecCallback_->onAbort(HTTPCodec::StreamID(2), ErrorCode::CANCEL);\n\n  handler.sendReplyWithBody(200, 100);\n\n  eventBase_.loop();\n\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ServerPushAbortAssoc) {\n  // Test that associated push transactions remain alive when client aborts\n  // the assoc stream\n  MockHTTPHandler handler;\n  MockHTTPPushHandler pushHandler1;\n  MockHTTPPushHandler pushHandler2;\n\n  fakeMockCodec(*codec_);\n  invokeWriteSuccess_ = true;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n\n  EXPECT_CALL(handler, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler](HTTPTransaction* txn) { handler.txn_ = txn; }));\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage>) {\n        // Initiate server push transactions\n        auto pushTxn = handler.txn_->newPushedTransaction(&pushHandler1);\n        CHECK_EQ(pushTxn->getID(), HTTPCodec::StreamID(2));\n        pushHandler1.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler1.sendBody(100);\n\n        pushTxn = handler.txn_->newPushedTransaction(&pushHandler2);\n        CHECK_EQ(pushTxn->getID(), HTTPCodec::StreamID(4));\n        pushHandler2.sendPushHeaders(\"/foo\", \"www.foo.com\", 100);\n        pushHandler2.sendBody(100);\n      }));\n\n  // Both push txns and the assoc txn should remain alive, and in this case\n  // time out.\n  EXPECT_CALL(pushHandler1, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler1](HTTPTransaction* txn) { pushHandler1.txn_ = txn; }));\n  EXPECT_CALL(pushHandler2, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler2](HTTPTransaction* txn) { pushHandler2.txn_ = txn; }));\n  EXPECT_CALL(pushHandler1, _detachTransaction());\n  EXPECT_CALL(pushHandler2, _detachTransaction());\n\n  EXPECT_CALL(handler, _onError(_))\n      .WillOnce(Invoke([&](const HTTPException& err) {\n        EXPECT_TRUE(err.hasProxygenError());\n        EXPECT_EQ(err.getProxygenError(), kErrorStreamAbort);\n        ASSERT_EQ(\"Stream aborted, streamID=1, code=CANCEL\",\n                  std::string(err.what()));\n      }));\n  EXPECT_CALL(handler, _detachTransaction());\n\n  // Receive client request\n  auto req = makeGetRequest();\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req));\n  eventBase_.loopOnce();\n\n  // Send client abort on assoc stream\n  codecCallback_->onAbort(HTTPCodec::StreamID(1), ErrorCode::CANCEL);\n\n  // Push txns can still send body and EOM\n  pushHandler1.sendBody(200);\n  pushHandler2.sendBody(200);\n  pushHandler1.sendEOM();\n  pushHandler2.sendEOM();\n\n  eventBase_.loopOnce();\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ServerPushClientMessage) {\n  // Test that error is generated when client sends data on a pushed stream\n  MockHTTPHandler handler;\n  MockHTTPPushHandler pushHandler;\n  auto req = makeGetRequest();\n  HTTPTransaction* pushTxn = nullptr;\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(_)).WillOnce(SaveArg<0>(&handler.txn_));\n\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage> msg) {\n        pushTxn = handler.txn_->newPushedTransaction(&pushHandler);\n        // auto pri = handler.txn_->getPriority();\n      }));\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(Invoke(\n          [&pushHandler](HTTPTransaction* txn) { pushHandler.txn_ = txn; }));\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req));\n\n  EXPECT_CALL(*codec_, generateRstStream(_, 2, ErrorCode::STREAM_CLOSED))\n      .WillRepeatedly(Return(1));\n  EXPECT_CALL(pushHandler, _onError(_))\n      .WillOnce(Invoke([&](const HTTPException& ex) {\n        EXPECT_TRUE(ex.hasCodecStatusCode());\n        EXPECT_EQ(ex.getCodecStatusCode(), ErrorCode::STREAM_CLOSED);\n        ASSERT_EQ(\"Downstream attempts to send ingress, abort.\",\n                  std::string(ex.what()));\n      }));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n\n  // While the assoc stream is open and pushHandler has been initialized, send\n  // an upstream message on the push stream causing a RST_STREAM.\n  req = makeGetRequest();\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(2), req.get());\n\n  EXPECT_CALL(handler, _onEOM()).WillOnce(InvokeWithoutArgs([&] {\n    handler.sendReplyWithBody(200, 100);\n    eventBase_.loop(); // flush the response to the assoc request\n  }));\n  EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _));\n  EXPECT_CALL(*codec_,\n              generateBody(_, 1, PtrBufHasLen(uint64_t(100)), _, true));\n  EXPECT_CALL(handler, _detachTransaction());\n\n  // Complete the assoc request/response\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  eventBase_.loop();\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ReadTimeout) {\n  // Test read timeout path\n  codec_->enableDoubleGoawayDrain();\n  MockHTTPHandler handler1;\n  auto req1 = makeGetRequest();\n\n  EXPECT_CALL(*codec_, onIngressEOF()).WillRepeatedly(Return());\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n  EXPECT_CALL(handler1, _onHeadersComplete(_));\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  httpSession_->timeoutExpired();\n  EXPECT_EQ(httpSession_->getConnectionCloseReason(),\n            ConnectionCloseReason::TIMEOUT);\n\n  EXPECT_CALL(handler1, _onEOM()).WillOnce(Invoke([&handler1]() {\n    handler1.txn_->pauseIngress();\n  }));\n\n  // send the EOM, then another timeout.  Still no-op since it's waiting\n  // upstream\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n  httpSession_->timeoutExpired();\n\n  EXPECT_CALL(*transport_, writeChain(_, _, _))\n      .WillRepeatedly(\n          Invoke([](folly::AsyncTransport::WriteCallback* callback,\n                    std::shared_ptr<folly::IOBuf>,\n                    folly::WriteFlags) { callback->writeSuccess(); }));\n\n  EXPECT_CALL(handler1, _detachTransaction());\n\n  // Send the response, timeout.  Now it's idle and should close.\n  handler1.txn_->resumeIngress();\n  EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _));\n  handler1.sendReplyWithBody(200, 100);\n  EXPECT_CALL(*codec_,\n              generateBody(_, 1, PtrBufHasLen(uint64_t(100)), _, true));\n  EXPECT_CALL(*codec_, isBusy()).WillRepeatedly(Return(false));\n  EXPECT_CALL(mockController_, detachSession(_));\n  eventBase_.loop();\n}\n\nTEST_F(MockCodecDownstreamTest, Ping) {\n  // Test ping mechanism and that we prioritize the ping reply\n  MockHTTPHandler handler1;\n  auto req1 = makeGetRequest();\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n  EXPECT_CALL(handler1, _onHeadersComplete(_));\n  EXPECT_CALL(handler1, _onEOM()).WillOnce(InvokeWithoutArgs([&handler1]() {\n    handler1.sendReplyWithBody(200, 100);\n  }));\n\n  // Header egresses immediately\n  EXPECT_CALL(*codec_, generateHeader(_, _, _, _, _, _));\n  // Ping jumps ahead of queued body in the loop callback\n  EXPECT_CALL(*codec_, generatePingReply(_, _));\n  EXPECT_CALL(*codec_, generateBody(_, _, _, _, true));\n  EXPECT_CALL(handler1, _detachTransaction());\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n  codecCallback_->onPingRequest(1);\n\n  eventBase_.loop();\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, FlowControlAbort) {\n  MockHTTPHandler handler1;\n  auto req1 = makePostRequest();\n\n  InSequence enforceOrder;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n  EXPECT_CALL(handler1, _onHeadersComplete(_))\n      .WillOnce(\n          InvokeWithoutArgs([&handler1]() { handler1.txn_->sendAbort(); }));\n\n  // Header egresses immediately\n  EXPECT_CALL(handler1, _detachTransaction());\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  EXPECT_CALL(*codec_, generateWindowUpdate(_, 0, http2::kInitialWindow));\n  codecCallback_->onBody(\n      HTTPCodec::StreamID(1), makeBuf(http2::kInitialWindow), 0);\n  EXPECT_CALL(*codec_, generateWindowUpdate(_, 0, http2::kInitialWindow));\n  codecCallback_->onBody(\n      HTTPCodec::StreamID(1), makeBuf(http2::kInitialWindow), 0);\n\n  eventBase_.loop();\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, Buffering) {\n  StrictMock<MockHTTPHandler> handler;\n  auto req1 = makePostRequest(20);\n  auto chunk = makeBuf(10);\n  auto chunkStr = chunk->clone()->moveToFbString();\n\n  fakeMockCodec(*codec_);\n\n  httpSession_->setDefaultReadBufferLimit(10);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n\n  EXPECT_CALL(handler, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler](HTTPTransaction* txn) { handler.txn_ = txn; }));\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(\n          InvokeWithoutArgs([&handler]() { handler.txn_->pauseIngress(); }));\n\n  EXPECT_CALL(*transport_, writeChain(_, _, _))\n      .WillRepeatedly(Invoke([&](folly::AsyncTransport::WriteCallback* callback,\n                                 const shared_ptr<IOBuf>&,\n                                 WriteFlags) { callback->writeSuccess(); }));\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  for (int i = 0; i < 2; i++) {\n    codecCallback_->onBody(HTTPCodec::StreamID(1), chunk->clone(), 0);\n  }\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  EXPECT_CALL(handler, _onBodyWithOffset(_, _))\n      .WillOnce(ExpectString(chunkStr + chunkStr));\n\n  EXPECT_CALL(handler, _onEOM());\n\n  EXPECT_CALL(handler, _detachTransaction());\n\n  eventBase_.tryRunAfterDelay(\n      [&handler, this] {\n        handler.txn_->resumeIngress();\n        handler.sendReplyWithBody(200, 100);\n        eventBase_.runInLoop([this] { httpSession_->dropConnection(); });\n      },\n      30);\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  eventBase_.loop();\n}\n\nTEST_F(MockCodecDownstreamTest, FlowControlWindow) {\n  // Test window updates\n  MockHTTPHandler handler1;\n  auto req1 = makeGetRequest();\n\n  fakeMockCodec(*codec_);\n\n  {\n    InSequence enforceOrder;\n    EXPECT_CALL(mockController_, getRequestHandler(_, _))\n        .WillOnce(Return(&handler1));\n\n    EXPECT_CALL(handler1, _setTransaction(_))\n        .WillOnce(\n            Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n    EXPECT_CALL(handler1, _onHeadersComplete(_))\n        .WillOnce(InvokeWithoutArgs([this]() {\n          codecCallback_->onSettings({{SettingsId::INITIAL_WINDOW_SIZE, 4000}});\n        }));\n    EXPECT_CALL(*codec_, generateSettingsAck(_));\n    EXPECT_CALL(handler1, _onEOM()).WillOnce(InvokeWithoutArgs([&handler1]() {\n      handler1.sendHeaders(200, 16000);\n      handler1.sendBody(12000);\n      // 12kb buffered -> pause upstream\n    }));\n    EXPECT_CALL(handler1, _onEgressPaused())\n        .WillOnce(InvokeWithoutArgs([this]() {\n          eventBase_.runInLoop([this] {\n            // triggers 4k send, 8kb buffered, handler still paused\n            codecCallback_->onWindowUpdate(1, 4000);\n          });\n          eventBase_.runAfterDelay(\n              [this] {\n                // triggers 6k send, 2kb buffered, handler still paused\n                codecCallback_->onWindowUpdate(1, 6000);\n              },\n              10);\n          eventBase_.runAfterDelay(\n              [this] {\n                // triggers 2kb send, 0 buffered, 2k window => resume\n                codecCallback_->onWindowUpdate(1, 4000);\n              },\n              20);\n        }));\n    EXPECT_CALL(handler1, _onEgressResumed())\n        .WillOnce(InvokeWithoutArgs([&handler1]() {\n          handler1.sendBody(4000);\n          // 2kb send, 2kb buffered => pause upstream\n        }));\n    EXPECT_CALL(handler1, _onEgressPaused())\n        .WillOnce(InvokeWithoutArgs([this]() {\n          eventBase_.runInLoop([this] {\n            // triggers 2kb send, resume\n            codecCallback_->onWindowUpdate(1, 4000);\n          });\n        }));\n    EXPECT_CALL(handler1, _onEgressResumed())\n        .WillOnce(\n            InvokeWithoutArgs([&handler1]() { handler1.txn_->sendEOM(); }));\n\n    EXPECT_CALL(handler1, _detachTransaction());\n\n    codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n    codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n    codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n    // Pad coverage numbers\n    std::ostringstream stream;\n    stream << *handler1.txn_ << *httpSession_ << httpSession_->getLocalAddress()\n           << httpSession_->getPeerAddress();\n    EXPECT_TRUE(httpSession_->isBusy());\n\n    EXPECT_CALL(*codec_, onIngressEOF());\n    EXPECT_CALL(mockController_, detachSession(_));\n  }\n\n  EXPECT_CALL(*transport_, writeChain(_, _, _))\n      .WillRepeatedly(\n          Invoke([](folly::AsyncTransport::WriteCallback* callback,\n                    std::shared_ptr<folly::IOBuf>,\n                    folly::WriteFlags) { callback->writeSuccess(); }));\n  eventBase_.loop();\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, DoubleResume) {\n  // Test ping mechanism and egress re-ordering\n  MockHTTPHandler handler1;\n  auto req1 = makePostRequest(5);\n  auto buf = makeBuf(5);\n  auto bufStr = buf->clone()->moveToFbString();\n\n  fakeMockCodec(*codec_);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(\n          Invoke([&handler1](HTTPTransaction* txn) { handler1.txn_ = txn; }));\n  EXPECT_CALL(handler1, _onHeadersComplete(_))\n      .WillOnce(InvokeWithoutArgs([&handler1, this] {\n        handler1.txn_->pauseIngress();\n        eventBase_.tryRunAfterDelay(\n            [&handler1] { handler1.txn_->resumeIngress(); }, 50);\n      }));\n  EXPECT_CALL(handler1, _onBodyWithOffset(_, _))\n      .WillOnce(Invoke(\n          [&handler1, &bufStr](uint64_t, std::shared_ptr<folly::IOBuf> chain) {\n            EXPECT_EQ(bufStr, chain->moveToFbString());\n            handler1.txn_->pauseIngress();\n            handler1.txn_->resumeIngress();\n          }));\n\n  EXPECT_CALL(handler1, _onEOM()).WillOnce(InvokeWithoutArgs([&handler1]() {\n    handler1.sendReplyWithBody(200, 100, false);\n  }));\n  EXPECT_CALL(handler1, _detachTransaction());\n\n  codecCallback_->onMessageBegin(HTTPCodec::StreamID(1), req1.get());\n  codecCallback_->onHeadersComplete(HTTPCodec::StreamID(1), std::move(req1));\n  codecCallback_->onBody(HTTPCodec::StreamID(1), std::move(buf), 0);\n  codecCallback_->onMessageComplete(HTTPCodec::StreamID(1), false);\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n\n  EXPECT_CALL(*transport_, writeChain(_, _, _))\n      .WillRepeatedly(\n          Invoke([](folly::AsyncTransport::WriteCallback* callback,\n                    std::shared_ptr<folly::IOBuf>,\n                    folly::WriteFlags) { callback->writeSuccess(); }));\n\n  eventBase_.loop();\n  httpSession_->dropConnection();\n}\n\nvoid MockCodecDownstreamTest::testConnFlowControlBlocked(bool timeout) {\n  // Let the connection level flow control window fill and then make sure\n  // control frames still can be processed\n  NiceMock<MockHTTPHandler> handler1;\n  NiceMock<MockHTTPHandler> handler2;\n  auto wantToWrite = http2::kInitialWindow + 50000;\n  auto wantToWriteStr = folly::to<string>(wantToWrite);\n  auto req1 = makeGetRequest();\n  auto req2 = makeGetRequest();\n  auto resp1 = makeResponse(200);\n  resp1->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, wantToWriteStr);\n  auto resp2 = makeResponse(200);\n  resp2->getHeaders().set(HTTP_HEADER_CONTENT_LENGTH, wantToWriteStr);\n  invokeWriteSuccess_ = true;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(SaveArg<0>(&handler1.txn_));\n  EXPECT_CALL(handler1, _onHeadersComplete(_));\n  EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _))\n      .WillOnce(\n          Invoke([](folly::IOBufQueue& writeBuf,\n                    HTTPCodec::StreamID,\n                    const HTTPMessage&,\n                    bool,\n                    HTTPHeaderSize*,\n                    folly::Optional<HTTPHeaders>) { writeBuf.append(\"\", 1); }));\n  unsigned bodyLen = 0;\n  EXPECT_CALL(*codec_, generateBody(_, 1, _, _, false))\n      .WillRepeatedly(Invoke([&](folly::IOBufQueue& /*writeBuf*/,\n                                 HTTPCodec::StreamID,\n                                 std::shared_ptr<folly::IOBuf> chain,\n                                 folly::Optional<uint8_t>,\n                                 bool /*eom*/) {\n        bodyLen += chain->computeChainDataLength();\n        return 0; // don't want byte events\n      }));\n\n  codecCallback_->onMessageBegin(1, req1.get());\n  codecCallback_->onHeadersComplete(1, std::move(req1));\n  codecCallback_->onWindowUpdate(1, wantToWrite); // ensure the per-stream\n                                                  // window doesn't block\n  handler1.txn_->sendHeaders(*resp1);\n  handler1.txn_->sendBody(makeBuf(wantToWrite)); // conn blocked, stream open\n  handler1.txn_->sendEOM();\n  eventBase_.loopOnce();                    // actually send (most of) the body\n  CHECK_EQ(bodyLen, http2::kInitialWindow); // should have written a full window\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler2));\n  EXPECT_CALL(handler2, _setTransaction(_))\n      .WillOnce(SaveArg<0>(&handler2.txn_));\n  EXPECT_CALL(handler2, _onHeadersComplete(_));\n  EXPECT_CALL(*codec_, generateHeader(_, 3, _, _, _, _))\n      .WillOnce(\n          Invoke([](folly::IOBufQueue& writeBuf,\n                    HTTPCodec::StreamID,\n                    const HTTPMessage&,\n                    bool,\n                    HTTPHeaderSize*,\n                    folly::Optional<HTTPHeaders>) { writeBuf.append(\"\", 1); }));\n\n  auto writeCount = writeCount_;\n\n  // Make sure we can send headers of response to a second request\n  codecCallback_->onMessageBegin(3, req2.get());\n  codecCallback_->onHeadersComplete(3, std::move(req2));\n  handler2.txn_->sendHeaders(*resp2);\n\n  eventBase_.loopOnce();\n\n  EXPECT_EQ(writeCount + 1, writeCount_);\n\n  if (timeout) {\n    // don't send a window update, the handlers will get timeouts\n    EXPECT_CALL(handler1, _onError(_))\n        .WillOnce(Invoke([](const HTTPException& ex) {\n          EXPECT_EQ(ex.getProxygenError(), kErrorTimeout);\n        }));\n    EXPECT_CALL(handler2, _onError(_))\n        .WillOnce(Invoke([](const HTTPException& ex) {\n          EXPECT_EQ(ex.getProxygenError(), kErrorTimeout);\n        }));\n    EXPECT_CALL(mockController_, detachSession(_));\n    // send a window update to refresh the stream level timeout\n    codecCallback_->onWindowUpdate(1, 1);\n    // silly, the timeout set is internal and there's no fd, so hold the\n    // eventBase open until the timeout can fire\n    eventBase_.runAfterDelay([] {}, 500);\n  } else {\n    // Give a connection level window update of 10 bytes -- this\n    // should allow 10 bytes of the txn1 response to be written\n    codecCallback_->onWindowUpdate(0, 10);\n    EXPECT_CALL(*codec_,\n                generateBody(_, 1, PtrBufHasLen(uint64_t(10)), _, false));\n    eventBase_.loopOnce();\n\n    // Just tear everything down now.\n    EXPECT_CALL(handler1, _detachTransaction());\n    codecCallback_->onAbort(handler1.txn_->getID(), ErrorCode::INTERNAL_ERROR);\n    eventBase_.loopOnce();\n\n    EXPECT_CALL(handler2, _detachTransaction());\n    EXPECT_CALL(mockController_, detachSession(_));\n    httpSession_->dropConnection();\n  }\n\n  eventBase_.loop();\n}\n\nTEST_F(MockCodecDownstreamTest, ConnFlowControlBlocked) {\n  testConnFlowControlBlocked(false);\n}\n\nTEST_F(MockCodecDownstreamTest, ConnFlowControlTimeout) {\n  testConnFlowControlBlocked(true);\n}\n\nTEST_F(MockCodecDownstreamTest, UnpausedLargePost) {\n  // Make sure that a large POST that streams into the handler generates\n  // connection level flow control so that the entire POST can be received.\n  InSequence enforceOrder;\n  NiceMock<MockHTTPHandler> handler1;\n  unsigned kNumChunks = 10;\n  auto wantToWrite = http2::kInitialWindow * kNumChunks;\n  auto wantToWriteStr = folly::to<string>(wantToWrite);\n  auto req1 = makePostRequest(wantToWrite);\n  auto req1Body = makeBuf(wantToWrite);\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(SaveArg<0>(&handler1.txn_));\n\n  EXPECT_CALL(handler1, _onHeadersComplete(_));\n  for (unsigned i = 0; i < kNumChunks; ++i) {\n    EXPECT_CALL(*codec_, generateWindowUpdate(_, 0, http2::kInitialWindow));\n    EXPECT_CALL(handler1,\n                _onBodyWithOffset(_, PtrBufHasLen(http2::kInitialWindow)));\n    EXPECT_CALL(*codec_, generateWindowUpdate(_, 1, http2::kInitialWindow));\n  }\n  EXPECT_CALL(handler1, _onEOM());\n\n  codecCallback_->onMessageBegin(1, req1.get());\n  codecCallback_->onHeadersComplete(1, std::move(req1));\n  // Give kNumChunks chunks, each of the maximum window size. We should generate\n  // window update for each chunk\n  for (unsigned i = 0; i < kNumChunks; ++i) {\n    codecCallback_->onBody(1, makeBuf(http2::kInitialWindow), 0);\n  }\n  codecCallback_->onMessageComplete(1, false);\n\n  // Just tear everything down now.\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, IngressPausedWindowUpdate) {\n  // Test sending a large response body while the handler has ingress paused. We\n  // should process the ingress window_updates and deliver the full body\n  InSequence enforceOrder;\n  NiceMock<MockHTTPHandler> handler1;\n  auto req = makeGetRequest();\n  size_t respSize = http2::kInitialWindow * 10;\n  unique_ptr<HTTPMessage> resp;\n  unique_ptr<folly::IOBuf> respBody;\n  tie(resp, respBody) = makeResponse(200, respSize);\n  size_t written = 0;\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler1));\n  EXPECT_CALL(handler1, _setTransaction(_))\n      .WillOnce(SaveArg<0>(&handler1.txn_));\n\n  EXPECT_CALL(handler1, _onHeadersComplete(_))\n      .WillOnce(InvokeWithoutArgs([&]() {\n        // Pause ingress. Make sure we process the window updates anyway\n        handler1.txn_->pauseIngress();\n      }));\n  EXPECT_CALL(*codec_, generateHeader(_, _, _, _, _, _));\n  EXPECT_CALL(*codec_, generateBody(_, _, _, _, _))\n      .WillRepeatedly(Invoke([&](folly::IOBufQueue&,\n                                 HTTPCodec::StreamID,\n                                 std::shared_ptr<folly::IOBuf> chain,\n                                 folly::Optional<uint8_t>,\n                                 bool /*eom*/) {\n        auto len = chain->computeChainDataLength();\n        written += len;\n        return len;\n      }));\n\n  codecCallback_->onWindowUpdate(0, respSize); // open conn-level window\n  codecCallback_->onMessageBegin(1, req.get());\n  codecCallback_->onHeadersComplete(1, std::move(req));\n  EXPECT_TRUE(handler1.txn_->isIngressPaused());\n\n  // Unblock txn-level flow control and try to egress the body\n  codecCallback_->onWindowUpdate(1, respSize);\n  handler1.txn_->sendHeaders(*resp);\n  handler1.txn_->sendBody(std::move(respBody));\n\n  eventBase_.loop();\n  EXPECT_EQ(written, respSize);\n\n  // Just tear everything down now.\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ShutdownThenSendPushHeaders) {\n  // Test that notifying session of shutdown before sendHeaders() called on a\n  // pushed txn lets that push txn finish.\n  EXPECT_CALL(*codec_, supportsPushTransactions()).WillRepeatedly(Return(true));\n\n  InSequence enforceOrder;\n  NiceMock<MockHTTPHandler> handler;\n  MockHTTPPushHandler pushHandler;\n  auto req = makeGetRequest();\n\n  EXPECT_CALL(mockController_, getRequestHandler(_, _))\n      .WillOnce(Return(&handler));\n  EXPECT_CALL(handler, _setTransaction(_)).WillOnce(SaveArg<0>(&handler.txn_));\n\n  EXPECT_CALL(handler, _onHeadersComplete(_))\n      .WillOnce(Invoke([&](std::shared_ptr<HTTPMessage>) {\n        auto pushTxn = handler.txn_->newPushedTransaction(&pushHandler);\n        // start shutdown process\n        httpSession_->notifyPendingShutdown();\n        // we should be able to process new requests\n        EXPECT_TRUE(codec_->isReusable());\n        pushHandler.sendPushHeaders(\"/foo\", \"www.foo.com\", 0);\n        // we should* still* be able to process new requests\n        EXPECT_TRUE(codec_->isReusable());\n        pushTxn->sendEOM();\n      }));\n  EXPECT_CALL(pushHandler, _setTransaction(_))\n      .WillOnce(SaveArg<0>(&pushHandler.txn_));\n  EXPECT_CALL(*codec_, generatePushPromise(_, 2, _, _, _, _));\n  EXPECT_CALL(*codec_, generateEOM(_, 2));\n  EXPECT_CALL(pushHandler, _detachTransaction());\n  EXPECT_CALL(handler, _onEOM()).WillOnce(Invoke([&] { handler.sendReply(); }));\n  EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _));\n  EXPECT_CALL(*codec_, generateEOM(_, 1));\n  EXPECT_CALL(handler, _detachTransaction());\n\n  codecCallback_->onMessageBegin(1, req.get());\n  codecCallback_->onHeadersComplete(1, std::move(req));\n  codecCallback_->onMessageComplete(1, false);\n\n  // finish shutdown\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n\n  eventBase_.loop();\n}\n\nTEST_F(MockCodecDownstreamTest, ReadIobufChainShutdown) {\n  // Given an ingress IOBuf chain of 2 parts, if we shutdown after reading the\n  // first part of the chain, we shouldn't read the second part.  One way to\n  // simulate a 2 part chain is to put more ingress in readBuf while we are\n  // inside HTTPCodec::onIngress()\n\n  InSequence enforceOrder;\n\n  auto f = [&]() {\n    void* buf;\n    size_t bufSize;\n    transportCb_->getReadBuffer(&buf, &bufSize);\n    transportCb_->readDataAvailable(bufSize);\n  };\n\n  EXPECT_CALL(*codec_, onIngress(_))\n      .WillOnce(Invoke([&](const IOBuf& buf) {\n        // This first time, don't process any data. This will cause the\n        // ingress chain to grow in size later.\n        EXPECT_FALSE(buf.isChained());\n        return 0;\n      }))\n      .WillOnce(Invoke([&](const IOBuf& buf) {\n        // Now there should be a second buffer in the chain.\n        EXPECT_TRUE(buf.isChained());\n        // Shutdown writes. This enough to destroy the session.\n        httpSession_->closeWhenIdle();\n        return buf.length();\n      }));\n  // We shouldn't get a third onIngress() callback. This will be enforced by the\n  // test framework since the codec is a strict mock.\n  EXPECT_CALL(*codec_, isBusy());\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n\n  f();\n  f(); // The first time wasn't processed, so this should make a len=2 chain.\n  eventBase_.loop();\n}\n\nvoid MockCodecDownstreamTest::testGoaway(bool doubleGoaway,\n                                         bool dropConnection) {\n  NiceMock<MockHTTPHandler> handler;\n  MockHTTPHandler pushHandler;\n\n  liveGoaways_ = true;\n  if (doubleGoaway) {\n    EXPECT_CALL(mockController_, getRequestHandler(_, _))\n        .WillOnce(Return(&handler));\n    EXPECT_CALL(handler, _setTransaction(_))\n        .WillOnce(SaveArg<0>(&handler.txn_));\n\n    EXPECT_CALL(handler, _onHeadersComplete(_));\n    EXPECT_CALL(handler, _onEOM()).WillOnce(Invoke([&] {\n      handler.sendReply();\n    }));\n    EXPECT_CALL(*codec_, generateHeader(_, 1, _, _, _, _));\n    EXPECT_CALL(*codec_, generateEOM(_, 1));\n    EXPECT_CALL(handler, _detachTransaction());\n\n    // Turn on double GOAWAY drain\n    codec_->enableDoubleGoawayDrain();\n  }\n\n  // Send a GOAWAY acking uninitiated transactions\n  EXPECT_FALSE(drainPending_);\n  httpSession_->notifyPendingShutdown();\n  EXPECT_EQ(drainPending_, doubleGoaway);\n  EXPECT_FALSE(reusable_);\n\n  if (doubleGoaway) {\n    // Should be able to process new requests\n    auto req1 = makeGetRequest();\n    codecCallback_->onMessageBegin(1, req1.get());\n    codecCallback_->onHeadersComplete(1, std::move(req1));\n    codecCallback_->onMessageComplete(1, false);\n  }\n\n  folly::AsyncTransport::WriteCallback* cb = nullptr;\n  {\n    InSequence enforceOrder;\n    EXPECT_CALL(*transport_, writeChain(_, _, _))\n        .WillOnce(Invoke([&](folly::AsyncTransport::WriteCallback* callback,\n                             const shared_ptr<IOBuf>,\n                             WriteFlags) {\n          // don't immediately flush the goaway\n          cb = callback;\n        }));\n    if (doubleGoaway && !dropConnection) {\n      EXPECT_CALL(*transport_, writeChain(_, _, _))\n          .WillOnce(Invoke(this, &MockCodecDownstreamTest::onWriteChain));\n    }\n  }\n  if (!dropConnection) {\n    // drop connection doesn't get onIngressEOF\n    EXPECT_CALL(*codec_, onIngressEOF());\n  }\n  eventBase_.loopOnce();\n\n  EXPECT_CALL(mockController_, detachSession(_));\n  if (dropConnection) {\n    EXPECT_CALL(*transport_, closeWithReset())\n        .Times(AtLeast(1))\n        .WillOnce(DoAll(Assign(&transportGood_, false), Invoke([cb] {\n                          AsyncSocketException ex(AsyncSocketException::UNKNOWN,\n                                                  \"\");\n                          cb->writeErr(0, ex);\n                        })));\n\n    httpSession_->dropConnection();\n  } else {\n    EXPECT_CALL(*codec_, isBusy());\n    httpSession_->closeWhenIdle();\n    cb->writeSuccess();\n    httpSession_->timeoutExpired();\n  }\n  EXPECT_FALSE(drainPending_);\n  EXPECT_FALSE(reusable_);\n}\n\nTEST_F(MockCodecDownstreamTest, SendDoubleGoawayTimeout) {\n  testGoaway(true, true);\n}\nTEST_F(MockCodecDownstreamTest, SendDoubleGoawayIdle) {\n  testGoaway(true, false);\n}\nTEST_F(MockCodecDownstreamTest, SendGoawayTimeout) {\n  testGoaway(false, true);\n}\nTEST_F(MockCodecDownstreamTest, SendGoawayIdle) {\n  testGoaway(false, false);\n}\n\nTEST_F(MockCodecDownstreamTest, DropConnection) {\n  NiceMock<MockHTTPHandler> handler;\n  MockHTTPHandler pushHandler;\n\n  liveGoaways_ = true;\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  EXPECT_CALL(*transport_, closeWithReset())\n      .Times(AtLeast(1))\n      .WillOnce(Assign(&transportGood_, false));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, DropConnectionNogoaway) {\n  NiceMock<MockHTTPHandler> handler;\n  MockHTTPHandler pushHandler;\n\n  liveGoaways_ = false;\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  EXPECT_CALL(*transport_, closeNow())\n      .Times(AtLeast(1))\n      .WillOnce(Assign(&transportGood_, false));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, ShutdownThenError) {\n  // Test that we ignore any errors after we shutdown the socket in HTTPSession.\n  onIngressImpl([&] {\n    // This executes as the implementation of HTTPCodec::onIngress()\n    InSequence dummy;\n\n    HTTPException err(HTTPException::Direction::INGRESS, \"foo\");\n    err.setHttpStatusCode(400);\n    HTTPMessage req = getGetRequest();\n    EXPECT_CALL(mockController_, getParseErrorHandler(_, _, _))\n        .WillOnce(Return(nullptr));\n\n    // Creates and adds a txn to the session\n    codecCallback_->onMessageBegin(1, &req);\n\n    EXPECT_CALL(*codec_, onIngressEOF());\n    httpSession_->closeWhenIdle();\n\n    codecCallback_->onError(1, err, false);\n  });\n  // flush the shutdown callback\n  EXPECT_CALL(mockController_, detachSession(_));\n  eventBase_.loopOnce();\n  httpSession_->timeoutExpired();\n}\n\nTEST_F(MockCodecDownstreamTest, PingDuringShutdown) {\n  onIngressImpl([&] {\n    InSequence dummy;\n\n    // Shutdown writes only. Since the session is empty, this normally\n    // causes the session to close, but it is held open since we are in\n    // the middle of parsing ingress.\n    EXPECT_CALL(*codec_, isBusy());\n    EXPECT_CALL(*codec_, onIngressEOF());\n    httpSession_->closeWhenIdle();\n\n    // We read a ping off the wire, which makes us enqueue a ping reply\n    EXPECT_CALL(*codec_, generatePingReply(_, _)).WillOnce(Return(10));\n    codecCallback_->onPingRequest(1);\n\n    // When this function returns, the controller gets detachSession()\n    EXPECT_CALL(mockController_, detachSession(_));\n  });\n}\n\nTEST_F(MockCodecDownstreamTest, SettingsAck) {\n  EXPECT_CALL(*codec_, generateSettingsAck(_));\n  codecCallback_->onSettings({{SettingsId::INITIAL_WINDOW_SIZE, 4000}});\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n\nTEST_F(MockCodecDownstreamTest, TestSendCertificateRequest) {\n  auto certRequestContext = folly::IOBuf::copyBuffer(\"0123456789abcdef\");\n  fizz::SignatureAlgorithms sigAlgs;\n  sigAlgs.supported_signature_algorithms.push_back(\n      SignatureScheme::ecdsa_secp256r1_sha256);\n  std::vector<fizz::Extension> extensions;\n  fizz::Extension ext;\n  fizz::Error err;\n  EXPECT_EQ(encodeExtension(ext, err, sigAlgs), fizz::Status::Success);\n  extensions.push_back(std::move(ext));\n\n  std::unique_ptr<StrictMock<MockSecondaryAuthManager>> secondAuthManager_(\n      new StrictMock<MockSecondaryAuthManager>());\n  httpSession_->setSecondAuthManager(std::move(secondAuthManager_));\n  auto authManager = dynamic_cast<MockSecondaryAuthManager*>(\n      httpSession_->getSecondAuthManager());\n  EXPECT_CALL(*codec_, getIngressSettings())\n      .WillOnce(Return(&kIngressCertAuthSettings));\n  EXPECT_CALL(*codec_, getEgressSettings())\n      .WillOnce(Return(&kEgressCertAuthSettings));\n  EXPECT_CALL(*authManager, createAuthRequest(_, _))\n      .WillOnce(InvokeWithoutArgs([]() {\n        return std::make_pair(120, IOBuf::copyBuffer(\"authenticatorrequest\"));\n      }));\n  EXPECT_CALL(*codec_, generateCertificateRequest(_, _, _))\n      .WillOnce(Return(20));\n  auto encodedSize = httpSession_->sendCertificateRequest(\n      std::move(certRequestContext), std::move(extensions));\n  EXPECT_EQ(encodedSize, 20);\n\n  EXPECT_CALL(*codec_, onIngressEOF());\n  EXPECT_CALL(mockController_, detachSession(_));\n  httpSession_->dropConnection();\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockHTTPSessionStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n\nnamespace proxygen {\n\nclass DummyHTTPSessionStats : public HTTPSessionStats {\n public:\n  void recordTransactionOpened() noexcept override {};\n  void recordTransactionClosed() noexcept override {};\n  void recordTransactionsServed(uint64_t) noexcept override {};\n  void recordSessionReused() noexcept override {};\n  // virtual void recordSessionIdleTime(std::chrono::seconds) noexcept {};\n  void recordTransactionStalled() noexcept override {};\n  void recordSessionStalled() noexcept override {\n  }\n  void recordEgressContentLengthMismatches() noexcept override {\n  }\n  void recordSessionPeriodicPingProbeTimeout() noexcept override {\n  }\n\n  void recordPresendIOSplit() noexcept override {\n  }\n  void recordPresendExceedLimit() noexcept override {\n  }\n  void recordTTLBAExceedLimit() noexcept override {\n  }\n  void recordTTLBANotFound() noexcept override {\n  }\n  void recordTTLBAReceived() noexcept override {\n  }\n  void recordTTLBATimeout() noexcept override {\n  }\n  void recordTTLBATracked() noexcept override {\n  }\n  void recordTTBTXExceedLimit() noexcept override {\n  }\n  void recordTTBTXReceived() noexcept override {\n  }\n  void recordTTBTXTimeout() noexcept override {\n  }\n  void recordTTBTXNotFound() noexcept override {\n  }\n  void recordTTBTXTracked() noexcept override {\n  }\n};\n\nclass MockHTTPSessionStats : public DummyHTTPSessionStats {\n public:\n  MockHTTPSessionStats() = default;\n  void recordTransactionOpened() noexcept override {\n    _recordTransactionOpened();\n  }\n  MOCK_METHOD(void, _recordTransactionOpened, ());\n  void recordTransactionClosed() noexcept override {\n    _recordTransactionClosed();\n  }\n  MOCK_METHOD(void, _recordTransactionClosed, ());\n  void recordTransactionsServed(uint64_t num) noexcept override {\n    _recordTransactionsServed(num);\n  }\n  MOCK_METHOD(void, _recordTransactionsServed, (uint64_t));\n  void recordSessionReused() noexcept override {\n    _recordSessionReused();\n  }\n  MOCK_METHOD(void, _recordSessionReused, ());\n  void recordSessionIdleTime(std::chrono::seconds param) noexcept override {\n    _recordSessionIdleTime(param);\n  }\n  MOCK_METHOD(void, _recordSessionIdleTime, (std::chrono::seconds));\n  void recordTransactionStalled() noexcept override {\n    _recordTransactionStalled();\n  }\n  MOCK_METHOD(void, _recordTransactionStalled, ());\n  void recordSessionStalled() noexcept override {\n    _recordSessionStalled();\n  }\n  MOCK_METHOD(void, _recordSessionStalled, ());\n  void recordEgressContentLengthMismatches() noexcept override {\n    _recordEgressContentLengthMismatches();\n  }\n  MOCK_METHOD(void, _recordEgressContentLengthMismatches, ());\n  void recordPendingBufferedReadBytes(int64_t num) noexcept override {\n    _recordPendingBufferedReadBytes(num);\n  }\n  MOCK_METHOD(void, _recordPendingBufferedReadBytes, (int64_t));\n  void recordPendingBufferedWriteBytes(int64_t num) noexcept override {\n    _recordPendingBufferedWriteBytes(num);\n  }\n  MOCK_METHOD(void, _recordPendingBufferedWriteBytes, (int64_t));\n  void recordSessionPeriodicPingProbeTimeout() noexcept override {\n    _recordSessionPeriodicPingProbeTimeout();\n  }\n  MOCK_METHOD(void, _recordSessionPeriodicPingProbeTimeout, ());\n};\n\nclass FakeSessionStats : public DummyHTTPSessionStats {\n public:\n  ~FakeSessionStats() override {\n    EXPECT_EQ(pendingBufferedReadBytes_, 0);\n    EXPECT_EQ(pendingBufferedWriteBytes_, 0);\n  }\n\n  void recordPendingBufferedReadBytes(int64_t bufferedBytes) noexcept override {\n    pendingBufferedReadBytes_ += bufferedBytes;\n  }\n  void recordPendingBufferedWriteBytes(\n      int64_t bufferedBytes) noexcept override {\n    pendingBufferedWriteBytes_ += bufferedBytes;\n  }\n\n protected:\n  int64_t pendingBufferedReadBytes_{0};\n  int64_t pendingBufferedWriteBytes_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockHTTPTransactionObserver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPTransactionObserverInterface.h>\n\nnamespace proxygen {\n\nclass MockHTTPTransactionObserver\n    : public HTTPTransactionObserverContainerBaseT::ManagedObserver {\n public:\n  using HTTPTransactionObserverContainerBaseT::ManagedObserver::ManagedObserver;\n\n  ~MockHTTPTransactionObserver() override = default;\n  MOCK_METHOD(void, attached, (HTTPTransactionObserverAccessor*), (noexcept));\n  MOCK_METHOD(void, detached, (HTTPTransactionObserverAccessor*), (noexcept));\n  MOCK_METHOD(void,\n              destroyed,\n              (HTTPTransactionObserverAccessor*,\n               typename HTTPTransactionObserverContainer::ObserverContainer::\n                   ManagedObserver::DestroyContext*),\n              (noexcept));\n  MOCK_METHOD(void,\n              onBytesEvent,\n              (HTTPTransactionObserverAccessor*, const TxnBytesEvent&),\n              (noexcept));\n};\n\nclass MockHTTPTransactionObserverAccessor\n    : public HTTPTransactionObserverAccessor {\n public:\n  ~MockHTTPTransactionObserverAccessor() override = default;\n\n  MOCK_METHOD(bool,\n              addObserver,\n              (HTTPTransactionObserverContainer::Observer * observer),\n              (override));\n  MOCK_METHOD(\n      bool,\n      addObserver,\n      (std::shared_ptr<HTTPTransactionObserverContainer::Observer> observer),\n      (override));\n\n  MOCK_METHOD(uint64_t, getTxnId, (), (const, override));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockQuicSocketDriver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Format.h>\n#include <gmock/gmock.h>\n#include <gtest/gtest.h>\n#include <proxygen/lib/transport/test/MockAsyncTransportCertificate.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <quic/common/events/FollyQuicEventBase.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nnamespace quic {\n\nusing PeekIterator = CircularDeque<StreamBuffer>::const_iterator;\n\n// The driver stores connection state in a Stream State structure\n// so use an id outside the on the wire id space\nconstexpr uint64_t kConnectionStreamId = std::numeric_limits<uint64_t>::max();\n\n// Checks the error condition, and if true either aborts the program or logs\n// and executes code that is intended to treat the error as a soft-error. The\n// behavior is controlled by the strictErrorCheck_ variable set via\n// setStrictErrorCheck().\n// NOTE: do not change this in a do { } while(0). softErrorHandler could be a\n// `continue` statement\n#define ERROR_IF(condition, message, softErrorHandler) \\\n  {                                                    \\\n    if (condition) {                                   \\\n      if (strictErrorCheck_) {                         \\\n        CHECK(!(condition)) << message;                \\\n      } else {                                         \\\n        LOG(ERROR) << message;                         \\\n        softErrorHandler;                              \\\n      }                                                \\\n    }                                                  \\\n  }\n\nclass MockQuicSocketDriver : public folly::EventBase::LoopCallback {\n public:\n  enum StateEnum { NEW, OPEN, PAUSED, CLOSED, ERROR };\n  enum TransportEnum { CLIENT, SERVER };\n\n  // support giving a callback to the caller (i.e. the actual test code)\n  // whenever the socket receives data, so that the test driver can parse\n  // that data and do stuff with it; i.e. detect control streams and feed the\n  // incoming data to a codec\n  class LocalAppCallback {\n   public:\n    virtual ~LocalAppCallback() = default;\n    virtual void unidirectionalReadCallback(\n        quic::StreamId id, std::unique_ptr<folly::IOBuf> buf) = 0;\n    virtual void readCallback(quic::StreamId id,\n                              std::unique_ptr<folly::IOBuf> buf) = 0;\n  };\n\n private:\n  struct StreamState {\n    bool fireByteEventAt(uint64_t offset) {\n      return (offset < nextWriteOffset ||\n              (offset == nextWriteOffset && writeEOF));\n    }\n\n    uint64_t nextWriteOffset{0};\n    // data to be read by application\n    folly::IOBufQueue readBuf{folly::IOBufQueue::cacheChainLength()};\n    uint64_t readBufOffset{0};\n    uint64_t readOffset{0};\n    bool readEOF{false};\n    bool writeEOF{false};\n    union WriteCallback {\n      quic::ConnectionWriteCallback* conn{nullptr};\n      quic::StreamWriteCallback* stream;\n    };\n    WriteCallback pendingWriteCb;\n    bool isPendingWriteCbStreamNotif{false};\n    // data written by application\n    folly::IOBufQueue unsentBuf{folly::IOBufQueue::cacheChainLength()};\n    bool pendingWriteEOF{false};\n    // data waiting to be flushed\n    folly::IOBufQueue pendingWriteBuf{folly::IOBufQueue::cacheChainLength()};\n    // BufMeta waiting to be flushed\n    uint64_t pendingBufMetaLength{0};\n    // data 'delivered' to peer. There is currently no BufMeta version of this.\n    folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};\n    StateEnum readState{NEW};\n    StateEnum writeState{OPEN};\n    folly::Optional<quic::ApplicationErrorCode> error;\n    QuicSocket::ReadCallback* readCB{nullptr};\n    QuicSocket::PeekCallback* peekCB{nullptr};\n    using ByteEventList = std::list<std::pair<uint64_t, ByteEventCallback*>>;\n    ByteEventList txCallbacks;\n    ByteEventList deliveryCallbacks;\n    uint64_t flowControlWindow{65536};\n    bool isControl{false};\n    uint64_t lastSkipOffset{0};\n    uint64_t fakePeekOffset{0};\n    uint64_t numWriteChainInvocations{0};\n  };\n\n public:\n  using StreamStateMap = std::map<StreamId, StreamState>;\n  using StreamStatePair = std::pair<const StreamId, StreamState>;\n\n  explicit MockQuicSocketDriver(\n      folly::EventBase* eventBase,\n      QuicSocket::ConnectionSetupCallback* connSetupCb,\n      QuicSocket::ConnectionCallback* connCb,\n      TransportEnum transportType,\n      std::string alpn = \"h3\")\n      : eventBase_(eventBase),\n        quicEventBase_(std::make_shared<quic::FollyQuicEventBase>(eventBase_)),\n        transportType_(transportType),\n        sock_(std::make_shared<MockQuicSocket>(eventBase, connSetupCb, connCb)),\n        alpn_(alpn) {\n\n    if (transportType_ == TransportEnum::SERVER) {\n      nextBidirectionalStreamId_ = 1;\n      nextUnidirectionalStreamId_ = 3;\n    } else {\n      nextBidirectionalStreamId_ = 0;\n      nextUnidirectionalStreamId_ = 2;\n    }\n\n    EXPECT_CALL(*sock_, setConnectionSetupCallback(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](QuicSocket::ConnectionSetupCallback* cb) {\n              sock_->setupCb_ = cb;\n            }));\n    EXPECT_CALL(*sock_, setConnectionCallback(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](QuicSocket::ConnectionCallback* cb) {\n              sock_->connCb_ = cb;\n            }));\n    EXPECT_CALL(*sock_, isClientStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [](quic::StreamId stream) { return (stream & 0b01) == 0; }));\n\n    EXPECT_CALL(*sock_, isServerStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [](quic::StreamId stream) { return stream & 0b01; }));\n\n    EXPECT_CALL(*sock_, isUnidirectionalStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [](quic::StreamId stream) { return stream & 0b10; }));\n\n    EXPECT_CALL(*sock_, isBidirectionalStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [](quic::StreamId stream) { return !(stream & 0b10); }));\n\n    EXPECT_CALL(*sock_, getState()).WillRepeatedly(testing::Return(nullptr));\n\n    EXPECT_CALL(*sock_, getTransportSettings())\n        .WillRepeatedly(testing::ReturnRef(transportSettings_));\n\n    EXPECT_CALL(*sock_, getConnectionBufferAvailable())\n        .WillRepeatedly(testing::Return(bufferAvailable_));\n\n    EXPECT_CALL(*sock_, getClientConnectionId())\n        .WillRepeatedly(testing::Return(\n            quic::ConnectionId::createAndMaybeCrash({0x11, 0x11, 0x11, 0x11})));\n\n    EXPECT_CALL(*sock_, getServerConnectionId())\n        .WillRepeatedly(testing::Return(\n            quic::ConnectionId::createAndMaybeCrash({0x11, 0x11, 0x11, 0x11})));\n\n    EXPECT_CALL(*sock_, getAppProtocol())\n        .WillRepeatedly(testing::Return(alpn_));\n\n    EXPECT_CALL(*sock_, good())\n        .WillRepeatedly(testing::ReturnPointee(&sockGood_));\n\n    EXPECT_CALL(*sock_, getEventBase())\n        .WillRepeatedly(testing::Return(quicEventBase_));\n\n    EXPECT_CALL(*sock_, setControlStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](quic::StreamId id) -> quic::Optional<quic::LocalErrorCode> {\n              auto stream = streams_.find(id);\n              if (id == kConnectionStreamId || stream == streams_.end()) {\n                return quic::LocalErrorCode::STREAM_NOT_EXISTS;\n              }\n              stream->second.isControl = true;\n              return std::nullopt;\n            }));\n\n    EXPECT_CALL(*sock_, getConnectionFlowControl())\n        .WillRepeatedly(testing::Invoke([this]() {\n          auto& connection = streams_[kConnectionStreamId];\n          flowControlAccess_.emplace(kConnectionStreamId);\n          return QuicSocket::FlowControlState(\n              {connection.flowControlWindow,\n               connection.nextWriteOffset + connection.flowControlWindow,\n               0,\n               0});\n        }));\n\n    EXPECT_CALL(*sock_, getStreamFlowControl(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id)\n                -> quic::Expected<quic::QuicSocket::FlowControlState,\n                                  quic::LocalErrorCode> {\n              if (isReceivingStream(id)) {\n                return quic::make_unexpected(LocalErrorCode::INVALID_OPERATION);\n              }\n              checkNotReadOnlyStream(id);\n              auto& stream = streams_[id];\n              if (stream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INTERNAL_ERROR);\n              }\n              flowControlAccess_.emplace(id);\n              return QuicSocket::FlowControlState(\n                  {stream.flowControlWindow,\n                   stream.nextWriteOffset + stream.flowControlWindow,\n                   0,\n                   0});\n            }));\n    EXPECT_CALL(*sock_, getMaxWritableOnStream(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](\n                StreamId id) -> quic::Expected<uint64_t, quic::LocalErrorCode> {\n              auto streamFCW = sock_->getStreamFlowControl(id);\n              if (!streamFCW) {\n                return quic::make_unexpected(streamFCW.error());\n              }\n              auto connFCW = sock_->getConnectionFlowControl();\n              if (!connFCW) {\n                return quic::make_unexpected(connFCW.error());\n              }\n\n              auto fcAvailable = std::min(streamFCW->sendWindowAvailable,\n                                          connFCW->sendWindowAvailable);\n              return std::min(fcAvailable, bufferAvailable_);\n            }));\n\n    EXPECT_CALL(*sock_, setConnectionFlowControlWindow(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](uint64_t windowSize)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              setConnectionFlowControlWindow(windowSize);\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, setStreamFlowControlWindow(testing::_, testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](StreamId id, uint64_t windowSize)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              checkNotReadOnlyStream(id);\n              setStreamFlowControlWindow(id, windowSize);\n              sock_->connCb_->onFlowControlUpdate(id);\n              return {};\n            }));\n\n    using ReadCBResult = quic::Expected<void, LocalErrorCode>;\n    EXPECT_CALL(*sock_, setReadCallback(testing::_, testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id,\n                   QuicSocket::ReadCallback* cb,\n                   quic::Optional<ApplicationErrorCode> error) -> ReadCBResult {\n              checkNotWriteOnlyStream(id);\n              auto& stream = streams_[id];\n              if (cb == nullptr && stream.readCB == nullptr) {\n                // matches real transport\n                return quic::make_unexpected(LocalErrorCode::INVALID_OPERATION);\n              }\n              stream.readCB = cb;\n              if (cb == nullptr && error.has_value()) {\n                stream.error = error.value();\n              }\n              if (cb && stream.readState == NEW) {\n                stream.readState = OPEN;\n              } else if (cb && stream.readState == OPEN) {\n                if (!stream.readBuf.empty()) {\n                  eventBase_->runInLoop(this, true);\n                }\n              } else if (stream.readState == ERROR) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INTERNAL_ERROR);\n              }\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, pauseRead(testing::_))\n        .WillRepeatedly(testing::Invoke([this](StreamId id) -> ReadCBResult {\n          checkNotWriteOnlyStream(id);\n          auto& stream = streams_[id];\n          if (stream.readState == OPEN) {\n            stream.readState = PAUSED;\n            return {};\n          } else {\n            return quic::make_unexpected(quic::LocalErrorCode::INTERNAL_ERROR);\n          }\n        }));\n\n    EXPECT_CALL(*sock_, resumeRead(testing::_))\n        .WillRepeatedly(testing::Invoke([this](StreamId id) -> ReadCBResult {\n          checkNotWriteOnlyStream(id);\n          auto& stream = streams_[id];\n          if (stream.readState == PAUSED) {\n            stream.readState = OPEN;\n            if (!stream.readBuf.empty() || stream.readEOF) {\n              // error is delivered immediately even if paused\n              ERROR_IF(!stream.readCB,\n                       \"can not deliver readAvailable: read callback \"\n                       \"is not set\",\n                       return quic::make_unexpected(\n                           quic::LocalErrorCode::INTERNAL_ERROR));\n              stream.readCB->readAvailable(id);\n            }\n            return {};\n          } else {\n            return quic::make_unexpected(quic::LocalErrorCode::INTERNAL_ERROR);\n          }\n        }));\n\n    using PeekCBResult = quic::Expected<void, LocalErrorCode>;\n    EXPECT_CALL(*sock_, setPeekCallback(testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id, QuicSocket::PeekCallback* cb) -> PeekCBResult {\n              checkNotWriteOnlyStream(id);\n              auto& stream = streams_[id];\n              stream.peekCB = cb;\n              if (cb && stream.readState == NEW) {\n                stream.readState = OPEN;\n              } else if (cb && stream.readState == OPEN) {\n                eventBase_->runInLoop(this, true);\n              } else if (stream.readState == ERROR) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INTERNAL_ERROR);\n              }\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, consume(testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id,\n                   size_t amount) -> quic::Expected<void, LocalErrorCode> {\n              auto& stream = streams_[id];\n              stream.readBuf.splitAtMost(amount);\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, readNaked(testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id, size_t maxLen) -> MockQuicSocket::ReadResult {\n              auto& stream = streams_[id];\n              std::pair<folly::IOBuf*, bool> result;\n              if (stream.readState == OPEN) {\n                if (maxLen == 0) {\n                  maxLen = std::numeric_limits<size_t>::max();\n                }\n                // Gather all buffers in the queue so that split won't\n                // run dry\n                stream.readBuf.gather(stream.readBuf.chainLength());\n\n                result.first = stream.readBuf.splitAtMost(maxLen).release();\n                result.second = stream.readBuf.empty() && stream.readEOF;\n                if (result.second) {\n                  stream.readState = CLOSED;\n                }\n              } else if (stream.readState == ERROR) {\n                // If reads return a LocalErrorCode, writes are also in error\n                stream.writeState = ERROR;\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INTERNAL_ERROR);\n              } else {\n                result.second = true;\n              }\n              stream.readOffset += result.first->length();\n              return result;\n            }));\n\n    EXPECT_CALL(*sock_, notifyPendingWriteOnStream(testing::_, testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](StreamId id, quic::StreamWriteCallback* wcb)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              checkNotReadOnlyStream(id);\n              auto result = notifyPendingWriteImpl(\n                  id,\n                  StreamState::WriteCallback({.stream = wcb}),\n                  /* isStreamNotif */ true);\n              if (result.hasValue()) {\n                return {};\n              } else {\n                return quic::make_unexpected(result.error());\n              }\n            }));\n\n    EXPECT_CALL(*sock_, notifyPendingWriteOnConnection(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](quic::ConnectionWriteCallback* wcb)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              auto result = notifyPendingWriteImpl(\n                  quic::kConnectionStreamId,\n                  StreamState::WriteCallback({.conn = wcb}));\n              if (result.hasValue()) {\n                return {};\n              } else {\n                return quic::make_unexpected(result.error());\n              }\n            }));\n\n    EXPECT_CALL(*sock_, getDatagramSizeLimit())\n        .WillRepeatedly(testing::Invoke(\n            [this]() -> uint16_t { return getDatagramSizeLimitImpl(); }));\n\n    EXPECT_CALL(*sock_, setDatagramCallback(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](QuicSocket::DatagramCallback* cb)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              auto& connStream = streams_[kConnectionStreamId];\n              if (connStream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::CONNECTION_CLOSED);\n              }\n              datagramCB_ = cb;\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, writeDatagram(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](MockQuicSocket::SharedBuf data)\n                                -> quic::MockQuicSocket::WriteResult {\n              auto& connStream = streams_[kConnectionStreamId];\n              if (connStream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::CONNECTION_CLOSED);\n              }\n              if (data->computeChainDataLength() >\n                  this->getDatagramSizeLimitImpl()) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INVALID_WRITE_DATA);\n              }\n              outDatagrams_.emplace_back(data->clone());\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, readDatagrams(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](size_t atMost) -> quic::Expected<std::vector<ReadDatagram>,\n                                                    quic::LocalErrorCode> {\n              auto& connStream = streams_[kConnectionStreamId];\n              if (connStream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::CONNECTION_CLOSED);\n              }\n              if (atMost == 0) {\n                atMost = inDatagrams_.size();\n              } else {\n                atMost = std::min(atMost, inDatagrams_.size());\n              }\n              std::vector<ReadDatagram> retDatagrams;\n              retDatagrams.reserve(atMost);\n              std::transform(inDatagrams_.begin(),\n                             inDatagrams_.begin() + atMost,\n                             std::back_inserter(retDatagrams),\n                             [](ReadDatagram& dg) { return std::move(dg); });\n              inDatagrams_.erase(inDatagrams_.begin(),\n                                 inDatagrams_.begin() + atMost);\n              return retDatagrams;\n            }));\n\n    EXPECT_CALL(*sock_, readDatagramBufs(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](size_t atMost) -> quic::Expected<std::vector<quic::BufPtr>,\n                                                    quic::LocalErrorCode> {\n              auto& connStream = streams_[kConnectionStreamId];\n              if (connStream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::CONNECTION_CLOSED);\n              }\n              if (atMost == 0) {\n                atMost = inDatagrams_.size();\n              } else {\n                atMost = std::min(atMost, inDatagrams_.size());\n              }\n              std::vector<BufPtr> retDatagrams;\n              retDatagrams.reserve(atMost);\n              std::transform(\n                  inDatagrams_.begin(),\n                  inDatagrams_.begin() + atMost,\n                  std::back_inserter(retDatagrams),\n                  [](ReadDatagram& dg) { return dg.bufQueue().move(); });\n              inDatagrams_.erase(inDatagrams_.begin(),\n                                 inDatagrams_.begin() + atMost);\n              return retDatagrams;\n            }));\n\n    EXPECT_CALL(*sock_, setPingCallback(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](QuicSocket::PingCallback* cb)\n                                -> quic::Expected<void, quic::LocalErrorCode> {\n              auto& connStream = streams_[kConnectionStreamId];\n              if (connStream.writeState == CLOSED) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::CONNECTION_CLOSED);\n              }\n              pingCB_ = cb;\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_,\n                writeChain(testing::_, testing::_, testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](StreamId id,\n                   MockQuicSocket::SharedBuf data,\n                   bool eof,\n                   ByteEventCallback* cb) -> quic::MockQuicSocket::WriteResult {\n              ERROR_IF(id == kConnectionStreamId,\n                       \"writeChain(kConnectionStreamId) not handled\",\n                       return quic::make_unexpected(\n                           quic::LocalErrorCode::INTERNAL_ERROR));\n              checkNotReadOnlyStream(id);\n              auto& stream = streams_[id];\n              auto& connState = streams_[kConnectionStreamId];\n              ERROR_IF(connState.writeState == CLOSED,\n                       \"writeChain on CLOSED connection\",\n                       return quic::make_unexpected(\n                           quic::LocalErrorCode::INTERNAL_ERROR));\n              // check stream.writeState == ERROR -> deliver error\n              if (stream.writeState == ERROR) {\n                // if writes return a LocalErrorCode, reads are also in error\n                stream.readState = ERROR;\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::INTERNAL_ERROR);\n              }\n              stream.numWriteChainInvocations++;\n              if (!data) {\n                data = folly::IOBuf::create(0);\n              } else {\n                stream.unsentBuf.append(data->clone());\n              }\n              // clip to FCW\n              uint64_t length = std::min(\n                  static_cast<uint64_t>(stream.unsentBuf.chainLength()),\n                  stream.flowControlWindow);\n              length = std::min(length, connState.flowControlWindow);\n              auto toSend = stream.unsentBuf.splitAtMost(length);\n              if (localAppCb_) {\n                if (sock_->isUnidirectionalStream(id)) {\n                  localAppCb_->unidirectionalReadCallback(id, toSend->clone());\n                } else {\n                  localAppCb_->readCallback(id, toSend->clone());\n                }\n              }\n              stream.pendingWriteBuf.append(std::move(toSend));\n              setStreamFlowControlWindow(id, stream.flowControlWindow - length);\n              setConnectionFlowControlWindow(connState.flowControlWindow -\n                                             length);\n              // handle non-zero -> 0 transition, call flowControlUpdate\n              stream.nextWriteOffset += length;\n              if (stream.unsentBuf.empty() && eof) {\n                stream.writeEOF = true;\n              } else if (eof) {\n                stream.pendingWriteEOF = eof;\n              }\n              if (cb) {\n                // The API doesn't allow for registering a last-byte tx cb here\n                auto finOffset =\n                    stream.nextWriteOffset + stream.unsentBuf.chainLength();\n                auto type = quic::ByteEvent::Type::ACK;\n                VLOG(4) << \"onByteEventRegistered id=\" << id\n                        << \" offset=\" << finOffset\n                        << \" type=\" << uint64_t(type);\n                cb->onByteEventRegistered(\n                    {.id = id, .offset = finOffset, .type = type});\n                stream.deliveryCallbacks.emplace_back(finOffset, cb);\n              }\n              eventBase_->runInLoop([this, deleted = deleted_] {\n                if (!*deleted) {\n                  flushWrites();\n                }\n              });\n              CHECK(stream.unsentBuf.empty() || stream.flowControlWindow == 0 ||\n                    connState.flowControlWindow == 0);\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, closeGracefully())\n        .WillRepeatedly(testing::Invoke([this]() {\n          closeConnection();\n          expectStreamsIdle();\n        }));\n\n    // For the purpose of testing close and closeNe=ow are identical\n    EXPECT_CALL(*sock_, closeNow(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](quic::Optional<QuicError> errorCode) {\n              closeImpl(errorCode);\n            }));\n    EXPECT_CALL(*sock_, close(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](quic::Optional<QuicError> errorCode) {\n              // close does not invoke onConnectionEnd/onConnectionError\n              sock_->connCb_ = nullptr;\n              sock_->setupCb_ = nullptr;\n              closeImpl(errorCode);\n            }));\n    EXPECT_CALL(*sock_, resetStream(testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](quic::StreamId id, quic::ApplicationErrorCode error)\n                -> quic::Expected<void, quic::LocalErrorCode> {\n              checkNotReadOnlyStream(id);\n              auto& stream = streams_[id];\n              stream.error = error;\n              stream.writeState = ERROR;\n              stream.unsentBuf.move();\n              stream.pendingWriteBuf.move();\n              stream.pendingWriteCb.stream = nullptr;\n              stream.pendingBufMetaLength = 0;\n              cancelDeliveryCallbacks(id, stream);\n              return {};\n            }));\n    EXPECT_CALL(*sock_, unsetAllReadCallbacks())\n        .WillRepeatedly(testing::Invoke([this]() {\n          for (auto& stream : streams_) {\n            if (!(sock_->isUnidirectionalStream(stream.first) &&\n                  isSendingStream(stream.first))) {\n              stream.second.readCB = nullptr;\n            }\n          }\n        }));\n    EXPECT_CALL(*sock_, stopSending(testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](quic::StreamId id, quic::ApplicationErrorCode error)\n                -> quic::Expected<void, quic::LocalErrorCode> {\n              checkNotWriteOnlyStream(id);\n              auto& stream = streams_[id];\n              stream.error = error;\n              // This doesn't set readState to error, because we can\n              // still receive after sending STOP_SENDING\n              return {};\n            }));\n    EXPECT_CALL(*sock_, createBidirectionalStream(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](bool /*replaySafe*/)\n                                -> quic::Expected<StreamId, LocalErrorCode> {\n              if (nextBidirectionalStreamId_ >= maxBidiStreamID_) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::STREAM_LIMIT_EXCEEDED);\n              }\n\n              auto streamId = nextBidirectionalStreamId_;\n              nextBidirectionalStreamId_ += 4;\n              streams_[streamId].readState = OPEN;\n              return streamId;\n            }));\n    EXPECT_CALL(*sock_, createUnidirectionalStream(testing::_))\n        .WillRepeatedly(\n            testing::Invoke([this](bool /*replaySafe*/)\n                                -> quic::Expected<StreamId, LocalErrorCode> {\n              if (nextUnidirectionalStreamId_ >= maxUniStreamID_) {\n                return quic::make_unexpected(\n                    quic::LocalErrorCode::STREAM_LIMIT_EXCEEDED);\n              }\n\n              auto streamId = nextUnidirectionalStreamId_;\n              nextUnidirectionalStreamId_ += 4;\n              streams_[streamId];\n              // caller of createUnidirectionalStream should not expect a\n              // readState is Open.\n              streams_[streamId].readState = CLOSED;\n              return streamId;\n            }));\n    EXPECT_CALL(*sock_, getNumOpenableBidirectionalStreams())\n        .WillRepeatedly(testing::Invoke([this] {\n          return maxBidiStreamID_ >= nextBidirectionalStreamId_\n                     ? (maxBidiStreamID_ - nextBidirectionalStreamId_) / 4\n                     : 0;\n        }));\n    EXPECT_CALL(*sock_, getNumOpenableUnidirectionalStreams())\n        .WillRepeatedly(testing::Invoke([this] {\n          return maxUniStreamID_ >= nextUnidirectionalStreamId_\n                     ? (maxUniStreamID_ - nextUnidirectionalStreamId_) / 4\n                     : 0;\n        }));\n    EXPECT_CALL(*sock_, getStreamWriteOffset(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](\n                quic::StreamId id) -> quic::Expected<size_t, LocalErrorCode> {\n              checkNotReadOnlyStream(id);\n              auto it = streams_.find(id);\n              if (it == streams_.end()) {\n                return quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS);\n              }\n              ERROR_IF(\n                  it->second.writeState == CLOSED,\n                  fmt::format(\"getStreamWriteOffset on CLOSED streamId={}\", id),\n                  return quic::make_unexpected(\n                      LocalErrorCode::STREAM_NOT_EXISTS));\n              // TODO: :/ sigh. BufMeta breaks this.\n              return it->second.nextWriteOffset -\n                     it->second.pendingWriteBuf.chainLength();\n            }));\n    EXPECT_CALL(*sock_, getStreamWriteBufferedBytes(testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](\n                quic::StreamId id) -> quic::Expected<size_t, LocalErrorCode> {\n              checkNotReadOnlyStream(id);\n              auto it = streams_.find(id);\n              if (it == streams_.end()) {\n                return quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS);\n              }\n\n              ERROR_IF(\n                  it->second.writeState == CLOSED,\n                  fmt::format(\n                      \"getStreamWriteBufferedBytes on CLOSED streamId={}\", id),\n                  return quic::make_unexpected(\n                      LocalErrorCode::STREAM_NOT_EXISTS));\n              return it->second.pendingWriteBuf.chainLength() +\n                     it->second.unsentBuf.chainLength() +\n                     it->second.pendingBufMetaLength;\n            }));\n    EXPECT_CALL(*sock_,\n                registerDeliveryCallback(testing::_, testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](quic::StreamId id, uint64_t offset, ByteEventCallback* cb)\n                -> quic::Expected<void, LocalErrorCode> {\n              auto result = sock_->registerByteEventCallback(\n                  quic::ByteEvent::Type::ACK, id, offset, cb);\n              if (result.has_value()) {\n                return {};\n              } else {\n                return quic::make_unexpected(result.error());\n              }\n            }));\n    EXPECT_CALL(*sock_, registerTxCallback(testing::_, testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](quic::StreamId id, uint64_t offset, ByteEventCallback* cb)\n                -> quic::Expected<void, LocalErrorCode> {\n              auto result = sock_->registerByteEventCallback(\n                  quic::ByteEvent::Type::TX, id, offset, cb);\n              if (result.has_value()) {\n                return {};\n              } else {\n                return quic::make_unexpected(result.error());\n              }\n            }));\n    EXPECT_CALL(*sock_,\n                registerByteEventCallback(\n                    testing::_, testing::_, testing::_, testing::_))\n        .WillRepeatedly(testing::Invoke(\n            [this](\n                ByteEvent::Type type,\n                quic::StreamId id,\n                uint64_t offset,\n                ByteEventCallback* cb) -> quic::Expected<void, LocalErrorCode> {\n              auto& connState = streams_[kConnectionStreamId];\n              ERROR_IF(connState.writeState == CLOSED,\n                       \"registerByteEventCallback on CLOSED connection\",\n                       return quic::make_unexpected(\n                           quic::LocalErrorCode::INTERNAL_ERROR));\n              checkNotReadOnlyStream(id);\n              auto it = streams_.find(id);\n              if (it == streams_.end()) {\n                return quic::make_unexpected(LocalErrorCode::STREAM_NOT_EXISTS);\n              }\n              ERROR_IF(\n                  it->second.writeState == CLOSED,\n                  fmt::format(\"registerByteEventCallback on CLOSED streamId={}\",\n                              id),\n                  return quic::make_unexpected(\n                      LocalErrorCode::STREAM_NOT_EXISTS));\n              VLOG(4) << \"onByteEventRegistered id=\" << id\n                      << \" offset=\" << offset << \" type=\" << uint64_t(type);\n              cb->onByteEventRegistered(\n                  {.id = id, .offset = offset, .type = type});\n              if (it->second.fireByteEventAt(offset)) {\n                // already available, fire the cb from the loop\n                eventBase_->runInLoop(\n                    [id, offset, type, cb] {\n                      VLOG(4) << \"onByteEvent id=\" << id << \" offset=\" << offset\n                              << \" type=\" << uint64_t(type);\n                      cb->onByteEvent(\n                          {.id = id, .offset = offset, .type = type});\n                    },\n                    /*thisIteration=*/true);\n                return {};\n              }\n\n              if (type == quic::ByteEvent::Type::ACK) {\n                it->second.deliveryCallbacks.emplace_back(offset, cb);\n              } else {\n                it->second.txCallbacks.emplace_back(offset, cb);\n              }\n              return {};\n            }));\n\n    EXPECT_CALL(*sock_, cancelDeliveryCallbacksForStream(testing::_))\n        .WillRepeatedly(testing::Invoke([this](quic::StreamId id) -> void {\n          cancelDeliveryCallbacks(id, streams_[id]);\n        }));\n    localAddress_.setFromIpPort(\"0.0.0.0\", folly::Random::rand32());\n    peerAddress_.setFromIpPort(\"127.0.0.0\", folly::Random::rand32());\n    EXPECT_CALL(*sock_, getLocalAddress())\n        .WillRepeatedly(testing::ReturnRef(localAddress_));\n    EXPECT_CALL(*sock_, getPeerAddress())\n        .WillRepeatedly(testing::ReturnRef(peerAddress_));\n    EXPECT_CALL(*sock_, getPeerCertificate())\n        .WillRepeatedly(testing::Return(mockCertificate));\n  }\n\n  void expectSetPriority(StreamId id,\n                         const HTTPPriorityQueue::Priority& expectedPri) {\n    EXPECT_CALL(*sock_, setStreamPriority(id, testing::_))\n        .WillOnce(testing::Invoke(\n            [expectedPri](StreamId, const PriorityQueue::Priority& pri)\n                -> quic::Expected<void, quic::LocalErrorCode> {\n              EXPECT_EQ(HTTPPriorityQueue::Priority(pri), expectedPri);\n              return {};\n            }));\n  }\n\n  uint16_t getDatagramSizeLimitImpl() {\n    return quic::kDefaultUDPSendPacketLen - quic::kMaxDatagramPacketOverhead;\n  }\n\n  quic::StreamId getMaxStreamId() {\n    return std::max_element(\n               streams_.begin(),\n               streams_.end(),\n               [](const std::pair<const StreamId, StreamState>& a,\n                  const std::pair<const StreamId, StreamState>& b) -> bool {\n                 return a.first < b.first && b.first != kConnectionStreamId;\n               })\n        ->first;\n  }\n\n  void setMaxBidiStreams(uint64_t maxBidiStreams) {\n    auto maxBidiStreamID = maxBidiStreams * 4 +\n                           ((transportType_ == TransportEnum::SERVER) ? 1 : 0);\n    if (maxBidiStreamID > maxBidiStreamID_) {\n      maxBidiStreamID_ = maxBidiStreamID;\n      if (sock_->connCb_) {\n        auto openableStreams =\n            (maxBidiStreamID_ - nextBidirectionalStreamId_) / 4;\n        sock_->connCb_->onBidirectionalStreamsAvailable(openableStreams);\n      }\n    } else {\n      maxBidiStreamID_ = maxBidiStreamID;\n    }\n  }\n\n  void setMaxUniStreams(uint64_t maxUniStreams) {\n    auto maxUniStreamID =\n        maxUniStreams * 4 + ((transportType_ == TransportEnum::SERVER) ? 3 : 2);\n    if (maxUniStreamID > maxUniStreamID_) {\n      maxUniStreamID_ = maxUniStreamID;\n      if (sock_->connCb_) {\n        auto openableStreams =\n            (maxUniStreamID_ - nextUnidirectionalStreamId_) / 4;\n        sock_->connCb_->onUnidirectionalStreamsAvailable(openableStreams);\n      }\n    } else {\n      maxUniStreamID_ = maxUniStreamID;\n    }\n  }\n\n  bool isClosed() {\n    return streams_[kConnectionStreamId].readState != OPEN &&\n           streams_[kConnectionStreamId].writeState != OPEN;\n  }\n\n  void setLocalAppCallback(LocalAppCallback* localAppCb) {\n    localAppCb_ = localAppCb;\n  }\n\n  void checkNotReadOnlyStream(quic::StreamId id) {\n    CHECK(!(sock_->isUnidirectionalStream(id) && isReceivingStream(id)))\n        << \"API not supported on read-only unidirectional stream. streamID=\"\n        << id;\n  }\n\n  void checkNotWriteOnlyStream(quic::StreamId id) {\n    CHECK(!(sock_->isUnidirectionalStream(id) && isSendingStream(id)))\n        << \"API not supported on write-only unidirectional stream. streamID=\"\n        << id;\n  }\n\n  bool isSendingStream(StreamId stream) {\n    return sock_->isUnidirectionalStream(stream) &&\n           ((transportType_ == TransportEnum::CLIENT &&\n             sock_->isClientStream(stream)) ||\n            (transportType_ == TransportEnum::SERVER &&\n             sock_->isServerStream(stream)));\n  }\n\n  bool isReceivingStream(StreamId stream) {\n    return sock_->isUnidirectionalStream(stream) &&\n           ((transportType_ == TransportEnum::CLIENT &&\n             sock_->isServerStream(stream)) ||\n            (transportType_ == TransportEnum::SERVER &&\n             sock_->isClientStream(stream)));\n  }\n\n  static bool isIdle(StateEnum state) {\n    return state == CLOSED || state == ERROR;\n  }\n\n  bool isStreamIdle(StreamId id) {\n    return isIdle(streams_[id].readState);\n  }\n\n  bool isStreamPaused(StreamId id) {\n    return streams_[id].readState == PAUSED;\n  }\n\n  void deliverErrorOnAllStreams(QuicError error) {\n    for (auto& it : streams_) {\n      auto& stream = it.second;\n      if (it.first == kConnectionStreamId) {\n        deliverWriteError(it.first, stream, error.code);\n        continue;\n      }\n      if (!isIdle(stream.readState)) {\n        if (stream.readCB || stream.peekCB) {\n          auto rcb = stream.readCB;\n          auto pcb = stream.peekCB;\n          stream.readCB = nullptr;\n          stream.peekCB = nullptr;\n          if (rcb) {\n            rcb->readError(it.first,\n                           QuicError(error.code, std::string(error.message)));\n          } else {\n            pcb->peekError(it.first,\n                           QuicError(error.code, std::string(error.message)));\n          }\n        }\n        stream.readState = ERROR;\n      }\n      if (!isIdle(stream.writeState)) {\n        deliverWriteError(it.first, stream, error.code);\n      }\n      cancelDeliveryCallbacks(it.first, stream);\n    }\n  }\n\n  void deliverConnectionError(QuicError error) {\n    deliverErrorOnAllStreams(error);\n    auto cb = sock_->connCb_;\n    sock_->connCb_ = nullptr;\n    if (cb) {\n      bool noError = false;\n      switch (error.code.type()) {\n        case QuicErrorCode::Type::LocalErrorCode: {\n          LocalErrorCode& err = *error.code.asLocalErrorCode();\n          noError = err == LocalErrorCode::NO_ERROR ||\n                    err == LocalErrorCode::IDLE_TIMEOUT;\n          break;\n        }\n        case QuicErrorCode::Type::TransportErrorCode: {\n          TransportErrorCode& err = *error.code.asTransportErrorCode();\n          noError = err == TransportErrorCode::NO_ERROR;\n          break;\n        }\n        case QuicErrorCode::Type::ApplicationErrorCode: {\n          noError = false;\n          break;\n        }\n      }\n      if (noError) {\n        cb->onConnectionEnd();\n      } else {\n        cb->onConnectionError(std::move(error));\n      }\n    }\n    auto& connState = streams_[kConnectionStreamId];\n    connState.readState = CLOSED;\n    connState.writeState = CLOSED;\n  }\n\n  void deliverWriteError(quic::StreamId id,\n                         StreamState& stream,\n                         QuicErrorCode errorCode) {\n    if (stream.pendingWriteCb.stream || stream.pendingWriteCb.conn) {\n      auto cb = stream.pendingWriteCb;\n      stream.pendingWriteCb.stream = nullptr;\n      if (stream.isPendingWriteCbStreamNotif) {\n        cb.stream->onStreamWriteError(id, QuicError(errorCode));\n      } else {\n        cb.conn->onConnectionWriteError(QuicError(errorCode));\n      }\n    }\n    stream.writeState = ERROR;\n  }\n\n  void cancelDeliveryCallbacks(quic::StreamId id, StreamState& stream) {\n    while (!stream.txCallbacks.empty()) {\n      stream.txCallbacks.front().second->onByteEventCanceled(\n          {id, stream.txCallbacks.front().first, ByteEvent::Type::TX});\n      stream.txCallbacks.pop_front();\n    }\n    while (!stream.deliveryCallbacks.empty()) {\n      stream.deliveryCallbacks.front().second->onByteEventCanceled(\n          {id, stream.deliveryCallbacks.front().first, ByteEvent::Type::ACK});\n      stream.deliveryCallbacks.pop_front();\n    }\n  }\n\n  uint64_t maxConnWritable() {\n    return streams_[kConnectionStreamId].flowControlWindow;\n  }\n\n  uint64_t maxStreamWritable(StreamId id) {\n    return std::min(streams_[id].flowControlWindow, maxConnWritable());\n  }\n\n  folly::Expected<folly::Unit, quic::LocalErrorCode> notifyPendingWriteImpl(\n      StreamId id, StreamState::WriteCallback wcb, bool streamNotif = false) {\n    auto& stream = streams_[id];\n    if (stream.writeState == PAUSED) {\n      stream.pendingWriteCb = wcb;\n      stream.isPendingWriteCbStreamNotif = streamNotif;\n      return folly::unit;\n    } else if (stream.writeState == OPEN) {\n      if (wcb.stream == nullptr) {\n        return folly::makeUnexpected(LocalErrorCode::INVALID_WRITE_CALLBACK);\n      }\n\n      stream.pendingWriteCb = wcb;\n      stream.isPendingWriteCbStreamNotif = streamNotif;\n      eventBase_->runInLoop(\n          [this, id, &stream, deleted = deleted_] {\n            if (*deleted) {\n              return;\n            }\n            // This callback was scheduled to be delivered when the stream\n            // writeState was OPEN, do not deliver the callback if the state\n            // changed in the meantime\n            if (stream.writeState != OPEN) {\n              return;\n            }\n            ERROR_IF(!stream.pendingWriteCb.stream,\n                     fmt::format(\"write callback not set when calling \"\n                                 \"onConnectionWriteReady for streamId={}\",\n                                 id),\n                     return);\n            auto maxStreamToWrite = maxStreamWritable(id);\n            auto maxConnToWrite = maxConnWritable();\n            if (!maxConnToWrite && !maxStreamToWrite) {\n              return;\n            }\n\n            auto writeCb = stream.pendingWriteCb;\n            stream.pendingWriteCb.stream = nullptr;\n            if (stream.isPendingWriteCbStreamNotif) {\n              writeCb.stream->onStreamWriteReady(id, maxStreamToWrite);\n            } else {\n              writeCb.conn->onConnectionWriteReady(maxConnToWrite);\n            }\n          },\n          true);\n    } else {\n      // closed, error\n      return folly::makeUnexpected(LocalErrorCode::CONNECTION_CLOSED);\n    }\n    return folly::unit;\n  }\n\n  void expectStreamsIdle(bool connection = false) {\n    for (auto& it : streams_) {\n      if ((!it.second.isControl && it.first != kConnectionStreamId) ||\n          connection) {\n        EXPECT_TRUE(isIdle(it.second.readState))\n            << \"stream=\" << it.first << \" readState=\" << it.second.readState;\n        EXPECT_TRUE(isIdle(it.second.writeState))\n            << \"stream=\" << it.first << \" writeState=\" << it.second.writeState;\n      }\n    }\n  }\n\n  void expectStreamWritesPaused(StreamId id) {\n    EXPECT_EQ(streams_[id].writeState, PAUSED);\n  }\n\n  void expectConnWritesPaused() {\n    EXPECT_EQ(streams_[quic::kConnectionStreamId].writeState, PAUSED);\n  }\n\n  ~MockQuicSocketDriver() override {\n    expectStreamsIdle(true);\n    *deleted_ = true;\n  }\n\n  void writePendingDataAndAck(StreamState& stream, StreamId id) {\n    stream.writeBuf.append(stream.pendingWriteBuf.move());\n    if (stream.pendingBufMetaLength > 0) {\n      stream.nextWriteOffset += stream.pendingBufMetaLength;\n      stream.pendingBufMetaLength = 0;\n    }\n    if (stream.writeEOF) {\n      stream.writeState = CLOSED;\n    }\n\n    // Deliver tx callbacks\n    fireCallbacks(id, stream, stream.txCallbacks, ByteEvent::Type::TX);\n\n    // delay delivery callbacks 50ms\n    eventBase_->runAfterDelay(\n        [this, &stream, id, deleted = deleted_] {\n          if (*deleted) {\n            return;\n          }\n          fireCallbacks(\n              id, stream, stream.deliveryCallbacks, ByteEvent::Type::ACK);\n        },\n        50);\n  }\n\n  void fireCallbacks(uint64_t id,\n                     StreamState& stream,\n                     StreamState::ByteEventList& callbacks,\n                     ByteEvent::Type type) {\n    while (!callbacks.empty()) {\n      auto cb = callbacks.front();\n      if (!stream.fireByteEventAt(cb.first)) {\n        break;\n      }\n      VLOG(4) << \"onByteEvent id=\" << id << \" offset=\" << cb.first\n              << \" type=\" << uint64_t(type);\n      cb.second->onByteEvent({.id = id, .offset = cb.first, .type = type});\n      callbacks.pop_front();\n    }\n  }\n\n  void closeConnection() {\n    flushWrites();\n    auto& connState = streams_[kConnectionStreamId];\n    connState.readState = CLOSED;\n    connState.writeState = CLOSED;\n  }\n\n  void closeImpl(quic::Optional<QuicError> errorCode) {\n    closeConnection();\n    if (errorCode) {\n      quic::ApplicationErrorCode* err =\n          errorCode->code.asApplicationErrorCode();\n      if (err) {\n        auto& connState = streams_[kConnectionStreamId];\n        connState.error = *err;\n      }\n    }\n    deliverConnectionError(errorCode.value_or(\n        QuicError(LocalErrorCode::NO_ERROR, \"Closing socket with no error\")));\n    sock_->connCb_ = nullptr;\n    sock_->setupCb_ = nullptr;\n    sockGood_ = false;\n  }\n  folly::Optional<quic::ApplicationErrorCode> getConnErrorCode() {\n    return streams_[kConnectionStreamId].error;\n  }\n\n  void flushWrites(StreamId id = kConnectionStreamId) {\n    auto& connState = streams_[kConnectionStreamId];\n    for (auto& it : streams_) {\n      if (it.first == kConnectionStreamId ||\n          (id != kConnectionStreamId && it.first != id)) {\n        continue;\n      }\n      auto& stream = it.second;\n      bool hasDataToWrite =\n          (!stream.pendingWriteBuf.empty() || stream.pendingBufMetaLength > 0 ||\n           stream.writeEOF);\n      if (connState.writeState == OPEN && stream.writeState == OPEN &&\n          hasDataToWrite) {\n        // handle 0->non-zero transition, call flowControlUpdate\n        setStreamFlowControlWindow(it.first,\n                                   stream.flowControlWindow +\n                                       stream.pendingWriteBuf.chainLength() +\n                                       stream.pendingBufMetaLength);\n        setConnectionFlowControlWindow(connState.flowControlWindow +\n                                       stream.pendingWriteBuf.chainLength() +\n                                       stream.pendingBufMetaLength);\n        writePendingDataAndAck(stream, it.first);\n      } else if (hasDataToWrite) {\n        // If we are paused only write the data that we have pending and don't\n        // trigger flow control updates to simulate reads from the other side\n        writePendingDataAndAck(stream, it.first);\n      }\n    }\n  }\n\n  void addDatagramsAvailableReadEvent(\n      std::chrono::milliseconds delayFromPrevious =\n          std::chrono::milliseconds(0)) {\n    addReadEventInternal(kConnectionStreamId,\n                         nullptr,\n                         false,\n                         folly::none,\n                         delayFromPrevious,\n                         false,\n                         true,\n                         false);\n  }\n\n  void addPingReceivedReadEvent(std::chrono::milliseconds delayFromPrevious =\n                                    std::chrono::milliseconds(0)) {\n    addReadEventInternal(kConnectionStreamId,\n                         nullptr,\n                         false,\n                         folly::none,\n                         delayFromPrevious,\n                         false,\n                         false,\n                         true);\n  }\n\n  void addPingAcknowledgedReadEvent(\n      std::chrono::milliseconds delayFromPrevious =\n          std::chrono::milliseconds(0)) {\n    addReadEventInternal(kConnectionStreamId,\n                         nullptr,\n                         false,\n                         folly::none,\n                         delayFromPrevious,\n                         false,\n                         false,\n                         false,\n                         true);\n  }\n\n  void addReadEvent(StreamId streamId,\n                    std::unique_ptr<folly::IOBuf> buf,\n                    std::chrono::milliseconds delayFromPrevious =\n                        std::chrono::milliseconds(0),\n                    uint64_t fakePeekOffset = 0) {\n    addReadEventInternal(streamId,\n                         std::move(buf),\n                         false,\n                         folly::none,\n                         delayFromPrevious,\n                         false /* stopSending */,\n                         false /* datagramsAvailable */,\n                         false /* pingReceived */,\n                         false /* pingAcknowledged */,\n                         fakePeekOffset);\n  }\n\n  void addReadEvent(StreamId streamId,\n                    std::unique_ptr<folly::IOBuf> buf,\n                    bool eof,\n                    std::chrono::milliseconds delayFromPrevious =\n                        std::chrono::milliseconds(0)) {\n    addReadEventInternal(\n        streamId, std::move(buf), eof, folly::none, delayFromPrevious);\n  }\n\n  void addReadEOF(StreamId streamId,\n                  std::chrono::milliseconds delayFromPrevious =\n                      std::chrono::milliseconds(0)) {\n    addReadEventInternal(\n        streamId, nullptr, true, folly::none, delayFromPrevious);\n  }\n\n  void addReadError(StreamId streamId,\n                    QuicErrorCode error,\n                    std::chrono::milliseconds delayFromPrevious =\n                        std::chrono::milliseconds(0)) {\n    addReadEventInternal(streamId, nullptr, false, error, delayFromPrevious);\n  }\n\n  void addStopSending(StreamId streamId,\n                      ApplicationErrorCode error,\n                      std::chrono::milliseconds delayFromPrevious =\n                          std::chrono::milliseconds(0)) {\n    QuicErrorCode qec = error;\n    addReadEventInternal(\n        streamId, nullptr, false, qec, delayFromPrevious, true);\n  }\n\n  void addDatagram(std::unique_ptr<folly::IOBuf> datagram,\n                   TimePoint recvTime = TimePoint(0ns)) {\n    inDatagrams_.emplace_back(recvTime, std::move(datagram));\n  }\n\n  void addDatagram(ReadDatagram datagram) {\n    inDatagrams_.push_back(std::move(datagram));\n  }\n\n  void setReadError(StreamId streamId) {\n    streams_[streamId].readState = ERROR;\n  }\n\n  void setWriteError(StreamId streamId) {\n    streams_[streamId].writeState = ERROR;\n    cancelDeliveryCallbacks(streamId, streams_[streamId]);\n  }\n\n  void addOnConnectionEndEvent(uint32_t millisecondsDelay) {\n    eventBase_->runAfterDelay(\n        [this, deleted = deleted_] {\n          if (!*deleted && sock_->connCb_) {\n            deliverErrorOnAllStreams(\n                {quic::LocalErrorCode::NO_ERROR, \"onConnectionEnd\"});\n            auto& connState = streams_[kConnectionStreamId];\n            connState.readState = CLOSED;\n            connState.writeState = CLOSED;\n            auto cb = sock_->connCb_;\n            // clear or cancel all the callbacks\n            sock_->connCb_ = nullptr;\n            sock_->setupCb_ = nullptr;\n            for (auto& it : streams_) {\n              auto& stream = it.second;\n              stream.readCB = nullptr;\n              stream.peekCB = nullptr;\n              stream.pendingWriteCb.stream = nullptr;\n            }\n            if (cb) {\n              cb->onConnectionEnd();\n            }\n          }\n        },\n        millisecondsDelay);\n  }\n\n  // Schedules a callback in this loop if the delay is zero,\n  // otherwise sets a timeout\n  void runInThisLoopOrAfterDelay(folly::Func cob, uint32_t millisecondsDelay) {\n    if (millisecondsDelay == 0) {\n      eventBase_->runInLoop(std::move(cob), true);\n    } else {\n      // runAfterDelay doesn't guarantee order if two events run after the\n      // same delay.  So queue the function and only use runAfterDelay to\n      // signal the event.\n      events_.emplace_back(std::move(cob));\n      eventBase_->runAfterDelay(\n          [this] {\n            ERROR_IF(events_.empty(), \"no events to schedule\", return);\n            auto event = std::move(events_.front());\n            events_.pop_front();\n            event();\n          },\n          millisecondsDelay);\n    }\n  }\n\n  struct ReadEvent {\n    ReadEvent(StreamId s,\n              std::unique_ptr<folly::IOBuf> b,\n              bool e,\n              folly::Optional<QuicErrorCode> er,\n              bool ss,\n              bool da = false,\n              bool pr = false,\n              bool pa = false,\n              uint64_t fpo = 0)\n        : streamId(s),\n          buf(std::move(b)),\n          eof(e),\n          error(er),\n          stopSending(ss),\n          datagramsAvailable(da),\n          pingReceived(pr),\n          pingAcknowledged(pa),\n          fakePeekOffset(fpo) {\n    }\n\n    StreamId streamId;\n    std::unique_ptr<folly::IOBuf> buf;\n    bool eof;\n    folly::Optional<QuicErrorCode> error;\n    bool stopSending;\n    bool datagramsAvailable;\n    bool pingReceived;\n    bool pingAcknowledged;\n    uint64_t fakePeekOffset;\n  };\n\n  void addReadEventInternal(StreamId streamId,\n                            std::unique_ptr<folly::IOBuf> buf,\n                            bool eof,\n                            folly::Optional<QuicErrorCode> error,\n                            std::chrono::milliseconds delayFromPrevious =\n                                std::chrono::milliseconds(0),\n                            bool stopSending = false,\n                            bool datagramsAvailable = false,\n                            bool pingReceived = false,\n                            bool pingAcknowledged = false,\n                            uint64_t fakePeekOffset = 0) {\n    std::vector<ReadEvent> events;\n    events.emplace_back(streamId,\n                        std::move(buf),\n                        eof,\n                        error,\n                        stopSending,\n                        datagramsAvailable,\n                        pingReceived,\n                        pingAcknowledged,\n                        fakePeekOffset);\n    addReadEvents(std::move(events), delayFromPrevious);\n  }\n\n  void addReadEvents(std::vector<ReadEvent> events,\n                     std::chrono::milliseconds delayFromPrevious =\n                         std::chrono::milliseconds(0)) {\n    ERROR_IF(streams_[kConnectionStreamId].readState == CLOSED,\n             \"adding read event on CLOSED connection\",\n             return);\n    cumulativeDelay_ += delayFromPrevious;\n    runInThisLoopOrAfterDelay(\n        [events = std::move(events), this, deleted = deleted_]() mutable {\n          // zero out cumulative delay\n          cumulativeDelay_ = std::chrono::milliseconds(0);\n          if (*deleted) {\n            return;\n          }\n          // This read event was scheduled to run in the evb, when it was\n          // scheduled the connection state was not CLOSED for reads.\n          // let's make sure this still holds\n          if (streams_[kConnectionStreamId].readState == CLOSED) {\n            return;\n          }\n          for (auto& event : events) {\n            auto& stream = streams_[event.streamId];\n            if (event.fakePeekOffset > 0) {\n              stream.fakePeekOffset = event.fakePeekOffset;\n            }\n            if (!event.error) {\n              ERROR_IF(stream.readState == CLOSED,\n                       fmt::format(\"scheduling event on CLOSED streamId={}\",\n                                   event.streamId),\n                       return);\n            } else {\n              ERROR_IF((event.buf && !event.buf->empty()) || event.eof,\n                       fmt::format(\"scheduling an error event with either a \"\n                                   \"buffer or eof on streamId={}\",\n                                   event.streamId),\n                       return);\n            }\n            if (event.streamId == kConnectionStreamId &&\n                event.datagramsAvailable && !event.error && datagramCB_) {\n              datagramCB_->onDatagramsAvailable();\n              continue;\n            }\n            if (event.streamId == kConnectionStreamId && event.pingReceived &&\n                !event.error && pingCB_) {\n              pingCB_->onPing();\n              continue;\n            }\n            if (event.streamId == kConnectionStreamId &&\n                event.pingAcknowledged && !event.error && pingCB_) {\n              pingCB_->pingAcknowledged();\n              continue;\n            }\n            auto bufLen = event.buf ? event.buf->computeChainDataLength() : 0;\n            stream.readBufOffset += bufLen;\n            stream.readBuf.append(std::move(event.buf));\n            stream.readEOF |= ((event.eof) ? 1 : 0);\n            if (stream.readState == NEW) {\n              stream.readState = OPEN;\n              if (sock_->connCb_) {\n                if (sock_->isUnidirectionalStream(event.streamId)) {\n                  if (isPeerStream(event.streamId)) {\n                    stream.writeState = CLOSED;\n                    sock_->connCb_->onNewUnidirectionalStream(event.streamId);\n                  } else {\n                    CHECK(event.error) << \"Non-error on self-uni stream\";\n                  }\n                } else {\n                  sock_->connCb_->onNewBidirectionalStream(event.streamId);\n                }\n              }\n            }\n            if (event.error && event.stopSending) {\n              if (sock_->connCb_) {\n                quic::ApplicationErrorCode* err =\n                    event.error->asApplicationErrorCode();\n                if (err) {\n                  sock_->connCb_->onStopSending(event.streamId, *err);\n                }\n              }\n              return;\n            }\n            if (stream.peekCB) {\n              if (event.error) {\n                if (!stream.readCB) {\n                  stream.peekCB->peekError(event.streamId,\n                                           QuicError(*event.error));\n                  stream.readState = ERROR;\n                }\n              } else if (stream.readState != PAUSED && stream.readBuf.front()) {\n                CircularDeque<StreamBuffer> fakeReadBuffer;\n                stream.readBuf.gather(stream.readBuf.chainLength());\n                auto copyBuf = stream.readBuf.front()->clone();\n                fakeReadBuffer.emplace_back(std::move(copyBuf),\n                                            stream.fakePeekOffset\n                                                ? stream.fakePeekOffset\n                                                : stream.readOffset,\n                                            stream.readEOF);\n                stream.peekCB->onDataAvailable(\n                    event.streamId,\n                    folly::Range<PeekIterator>(fakeReadBuffer.cbegin(),\n                                               fakeReadBuffer.size()));\n              }\n            }\n            if (stream.readCB) {\n              if (event.error) {\n                stream.readCB->readError(event.streamId,\n                                         QuicError(*event.error));\n                stream.readState = ERROR;\n              } else if (stream.readState != PAUSED) {\n                stream.readCB->readAvailable(event.streamId);\n                eventBase_->runInLoop(this);\n              } // else if PAUSED, no-op\n            }\n          }\n        },\n        cumulativeDelay_.count());\n  }\n\n  bool isPeerStream(quic::StreamId id) {\n    return (\n        (transportType_ == TransportEnum::SERVER &&\n         sock_->isClientStream(id)) ||\n        (transportType_ == TransportEnum::CLIENT && sock_->isServerStream(id)));\n  }\n\n  enum class PauseResumeResult { NONE = 0, PAUSED = 1, RESUMED = 2 };\n  PauseResumeResult pauseOrResumeWrites(StreamState& stream,\n                                        quic::StreamId streamId) {\n    if (stream.writeState == OPEN && stream.flowControlWindow == 0) {\n      pauseWrites(streamId);\n      return PauseResumeResult::PAUSED;\n    } else if (stream.writeState == PAUSED && stream.flowControlWindow > 0) {\n      resumeWrites(streamId);\n      return PauseResumeResult::RESUMED;\n    }\n    return PauseResumeResult::NONE;\n  }\n\n  void setConnectionFlowControlWindow(uint64_t windowSize) {\n    auto& connStream = streams_[kConnectionStreamId];\n    ERROR_IF(connStream.writeState == CLOSED,\n             \"setConnectionFlowControlWindow on CLOSED connection\",\n             return);\n    connStream.flowControlWindow = windowSize;\n    if (pauseOrResumeWrites(connStream, kConnectionStreamId) ==\n        PauseResumeResult::RESUMED) {\n      // Connection Flow control became unblocked, resume any streams that were\n      // blocked on connection flow control\n      for (auto& stream : streams_) {\n        if (stream.first == kConnectionStreamId) {\n          continue;\n        }\n        if (!stream.second.unsentBuf.empty() &&\n            stream.second.flowControlWindow > 0) {\n          resumeWrites(stream.first, /*connFCEvent=*/true);\n        }\n      }\n    }\n  }\n\n  void setStreamFlowControlWindow(StreamId streamId, uint64_t windowSize) {\n    auto& stream = streams_[streamId];\n\n    ERROR_IF(stream.writeState == CLOSED,\n             \"setStreamFlowControlWindow on CLOSED connection\",\n             return);\n    stream.flowControlWindow = windowSize;\n    pauseOrResumeWrites(stream, streamId);\n  }\n\n  void pauseWrites(StreamId streamId) {\n    auto& stream = streams_[streamId];\n    ERROR_IF(stream.writeState != OPEN,\n             fmt::format(\"pauseWrites on not OPEN streamId={}\", streamId),\n             return);\n    stream.writeState = PAUSED;\n  }\n\n  // This is to model the fact that the transport may close a stream without\n  // giving a readError callback\n  void forceStreamClose(StreamId streamId) {\n    auto& stream = streams_[streamId];\n    stream.readState = CLOSED;\n    stream.writeState = CLOSED;\n    cancelDeliveryCallbacks(streamId, stream);\n  }\n\n  void resumeWrites(StreamId streamId, bool connFCEvent = false) {\n    auto& stream = streams_[streamId];\n    ERROR_IF(stream.writeState != PAUSED && !connFCEvent,\n             fmt::format(\"resumeWrites on not PAUSED streamId={}\", streamId),\n             return);\n    stream.writeState = OPEN;\n    // first flush any buffered writes\n    flushWrites(streamId);\n    // now check onConnectionWriteReady/onStreamWriteReady call is warranted.\n    uint64_t maxWritableOnStream = maxStreamWritable(streamId);\n    uint64_t maxWritableOnConn = maxConnWritable();\n    bool shouldResume = stream.writeState == OPEN &&\n                        stream.pendingWriteCb.stream &&\n                        (maxWritableOnConn > 0 || maxWritableOnStream > 0);\n\n    if (shouldResume) {\n      uint64_t window = stream.isPendingWriteCbStreamNotif ? maxWritableOnStream\n                                                           : maxWritableOnConn;\n\n      eventBase_->runInLoop(\n          [wcb = stream.pendingWriteCb,\n           deleted = deleted_,\n           streamId,\n           window,\n           streamNotif = stream.isPendingWriteCbStreamNotif] {\n            if (!*deleted) {\n              if (streamNotif) {\n                wcb.stream->onStreamWriteReady(streamId, window);\n              } else {\n                wcb.conn->onConnectionWriteReady(window);\n              }\n            }\n          },\n          true);\n    }\n    if (streamId != quic::kConnectionStreamId) {\n      sock_->connCb_->onFlowControlUpdate(streamId);\n    }\n    stream.pendingWriteCb.stream = nullptr;\n    if (!stream.unsentBuf.empty()) {\n      // re-invoke write chain with the pending data\n      sock_->writeChain(\n          streamId, stream.unsentBuf.move(), stream.pendingWriteEOF, nullptr);\n    }\n  }\n\n  std::shared_ptr<MockQuicSocket> getSocket() {\n    return sock_;\n  }\n\n  void runLoopCallback() noexcept override {\n    bool reschedule = false;\n    for (auto& it : streams_) {\n      auto streamID = it.first;\n      auto& streamState = it.second;\n      auto hasCB = streamState.readCB || streamState.peekCB;\n      auto hasDataOrEOF = !streamState.readBuf.empty() || streamState.readEOF;\n      if (streamID != kConnectionStreamId && hasCB &&\n          streamState.readState == OPEN && hasDataOrEOF) {\n        if (streamState.peekCB) {\n          CircularDeque<StreamBuffer> fakeReadBuffer;\n          std::unique_ptr<folly::IOBuf> copyBuf;\n          std::size_t copyBufLen = 0;\n          if (streamState.readBuf.chainLength() > 0) {\n            copyBuf = streamState.readBuf.front()->clone();\n            copyBufLen = copyBuf->computeChainDataLength();\n          }\n          VLOG(6) << \"peek onDataAvailable id=\" << it.first\n                  << \" len=\" << copyBufLen\n                  << \" offset=\" << streamState.readOffset;\n          ERROR_IF(streamState.readBufOffset < copyBufLen,\n                   fmt::format(\"readOffset({}) is lower than current read \"\n                               \"buffer offset({}) for streamId={}\",\n                               streamState.readBufOffset,\n                               copyBufLen,\n                               it.first),\n                   continue);\n          fakeReadBuffer.emplace_back(std::move(copyBuf),\n                                      streamState.fakePeekOffset\n                                          ? streamState.fakePeekOffset\n                                          : streamState.readOffset,\n                                      streamState.readEOF);\n          streamState.peekCB->onDataAvailable(\n              it.first,\n              folly::Range<PeekIterator>(fakeReadBuffer.cbegin(),\n                                         fakeReadBuffer.size()));\n        }\n        if (streamState.readCB) {\n          streamState.readCB->readAvailable(it.first);\n          reschedule = true;\n        }\n      }\n    }\n    if (reschedule) {\n      eventBase_->runInLoop(this);\n    }\n  }\n\n  void setStrictErrorCheck(bool strict) {\n    strictErrorCheck_ = strict;\n  }\n\n  uint64_t getNumWriteChainInvocations(StreamId id) {\n    return streams_[id].numWriteChainInvocations;\n  }\n\n  bool strictErrorCheck_{true};\n  folly::EventBase* eventBase_;\n  std::shared_ptr<quic::FollyQuicEventBase> quicEventBase_;\n  TransportSettings transportSettings_;\n  uint64_t bufferAvailable_{std::numeric_limits<uint64_t>::max()};\n  // keeping this ordered for better debugging\n  StreamStateMap streams_;\n  std::list<folly::Func> events_;\n  TransportEnum transportType_;\n  std::shared_ptr<MockQuicSocket> sock_;\n  std::chrono::milliseconds cumulativeDelay_{std::chrono::milliseconds(0)};\n  bool sockGood_{true};\n  std::set<StreamId> flowControlAccess_;\n  uint64_t nextBidirectionalStreamId_;\n  uint64_t nextUnidirectionalStreamId_;\n  uint64_t maxBidiStreamID_{400};\n  uint64_t maxUniStreamID_{0};\n  std::shared_ptr<bool> deleted_{new bool(false)};\n  LocalAppCallback* localAppCb_{nullptr};\n  std::string alpn_;\n  QuicSocket::DatagramCallback* datagramCB_{nullptr};\n  std::vector<quic::BufQueue> outDatagrams_{};\n  std::vector<ReadDatagram> inDatagrams_{};\n  QuicSocket::PingCallback* pingCB_{nullptr};\n  folly::SocketAddress localAddress_;\n  folly::SocketAddress peerAddress_;\n  std::shared_ptr<proxygen::MockAsyncTransportCertificate> mockCertificate{\n      std::make_shared<proxygen::MockAsyncTransportCertificate>()};\n}; // namespace quic\n\n} // namespace quic\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockSecondaryAuthManager.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/session/SecondaryAuthManager.h>\n\nnamespace proxygen {\n\nclass MockSecondaryAuthManager : public SecondaryAuthManager {\n public:\n  MOCK_METHOD((std::pair<uint16_t, std::unique_ptr<folly::IOBuf>>),\n              createAuthRequest,\n              (std::shared_ptr<folly::IOBuf>, std::vector<fizz::Extension>&));\n  std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> createAuthRequest(\n      std::unique_ptr<folly::IOBuf> certRequestContext,\n      std::vector<fizz::Extension> extensions) override {\n    return createAuthRequest(\n        std::shared_ptr<folly::IOBuf>(certRequestContext.release()),\n        extensions);\n  }\n  MOCK_METHOD((std::pair<uint16_t, std::unique_ptr<folly::IOBuf>>),\n              getAuthenticator,\n              (const fizz::AsyncFizzBase&,\n               TransportDirection,\n               uint16_t,\n               std::shared_ptr<folly::IOBuf>));\n  std::pair<uint16_t, std::unique_ptr<folly::IOBuf>> getAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t requestId,\n      std::unique_ptr<folly::IOBuf> authRequest) override {\n    return getAuthenticator(\n        transport,\n        dir,\n        requestId,\n        std::shared_ptr<folly::IOBuf>(authRequest.release()));\n  }\n  MOCK_METHOD(bool,\n              validateAuthenticator,\n              (const fizz::AsyncFizzBase&,\n               TransportDirection,\n               uint16_t,\n               std::shared_ptr<folly::IOBuf>));\n  bool validateAuthenticator(\n      const fizz::AsyncFizzBase& transport,\n      TransportDirection dir,\n      uint16_t certId,\n      std::unique_ptr<folly::IOBuf> authenticator) override {\n    return validateAuthenticator(\n        transport,\n        dir,\n        certId,\n        std::shared_ptr<folly::IOBuf>(authenticator.release()));\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/MockSessionObserver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverContainer.h>\n#include <proxygen/lib/http/observer/HTTPSessionObserverInterface.h>\n\nnamespace proxygen {\n\nclass MockSessionObserver\n    : public HTTPSessionObserverContainerBaseT::ManagedObserver {\n public:\n  using HTTPSessionObserverContainerBaseT::ManagedObserver::ManagedObserver;\n  MOCK_METHOD(void, attached, (HTTPSessionObserverAccessor*), (noexcept));\n  MOCK_METHOD(void, detached, (HTTPSessionObserverAccessor*), (noexcept));\n  MOCK_METHOD(void,\n              destroyed,\n              (HTTPSessionObserverAccessor*,\n               typename HTTPSessionObserverContainer::ObserverContainer::\n                   ManagedObserver::DestroyContext*),\n              (noexcept));\n  MOCK_METHOD(void,\n              requestStarted,\n              (HTTPSessionObserverAccessor*, const RequestStartedEvent&),\n              (noexcept));\n  MOCK_METHOD(void,\n              preWrite,\n              (HTTPSessionObserverAccessor*, const PreWriteEvent&),\n              (noexcept));\n  MOCK_METHOD(void,\n              pingReply,\n              (HTTPSessionObserverAccessor*, const PingReplyEvent&),\n              (noexcept));\n  ~MockSessionObserver() override = default;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/SecondaryAuthManagerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/SecondaryAuthManager.h>\n\n#include <fizz/crypto/test/TestUtil.h>\n#include <fizz/protocol/test/Mocks.h>\n#include <fizz/record/Extensions.h>\n#include <fizz/record/Types.h>\n#include <folly/String.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\nusing namespace fizz;\nusing namespace fizz::test;\nusing namespace folly;\n\nStringPiece expected_auth_request = {\n    \"120000303132333435363738396162636465660008000d000400020403\"};\n\nStringPiece expected_cert = {\n    \"308201ee30820195a003020102020900c569eec901ce86d9300a06082a8648ce3d04030230\"\n    \"54310b3009060355040613025553310b300906035504080c024e59310b300906035504070c\"\n    \"024e59310d300b060355040a0c0446697a7a310d300b060355040b0c0446697a7a310d300b\"\n    \"06035504030c0446697a7a301e170d3137303430343138323930395a170d34313131323431\"\n    \"38323930395a3054310b3009060355040613025553310b300906035504080c024e59310b30\"\n    \"0906035504070c024e59310d300b060355040a0c0446697a7a310d300b060355040b0c0446\"\n    \"697a7a310d300b06035504030c0446697a7a3059301306072a8648ce3d020106082a8648ce\"\n    \"3d030107034200049d87bcaddb65d8dcf6df8b148a9679b5b710db19c95a9badfff13468cb\"\n    \"358b4e21d24a5c826112658ebb96d64e2985dfb41c1948334391a4aa81b67837e2dbf0a350\"\n    \"304e301d0603551d0e041604143c5b8ba954d9752faf3c8ad6d1a62449dccaa850301f0603\"\n    \"551d230418301680143c5b8ba954d9752faf3c8ad6d1a62449dccaa850300c0603551d1304\"\n    \"0530030101ff300a06082a8648ce3d04030203470030440220349b7d34d7132fb2756576e0\"\n    \"bfa36cbe1723337a7a6f5ef9c8d3bf1aa7efa4a5022025c50a91e0aa4272f1f52c3d5583a7\"\n    \"d7cee14b178835273a0bd814303e62d714\"};\n\nTEST(SecondaryAuthManagerTest, AuthenticatorRequest) {\n  auto certRequestContext = folly::IOBuf::copyBuffer(\"0123456789abcdef\");\n  fizz::SignatureAlgorithms sigAlgs;\n  sigAlgs.supported_signature_algorithms.push_back(\n      SignatureScheme::ecdsa_secp256r1_sha256);\n  std::vector<fizz::Extension> extensions;\n  fizz::Extension ext;\n  fizz::Error err;\n  EXPECT_EQ(encodeExtension(ext, err, sigAlgs), fizz::Status::Success);\n  extensions.push_back(std::move(ext));\n  SecondaryAuthManager authManager;\n  auto authRequestPair = authManager.createAuthRequest(\n      std::move(certRequestContext), std::move(extensions));\n  auto requestId = authRequestPair.first;\n  auto authRequest = std::move(authRequestPair.second);\n  EXPECT_EQ(requestId, 0);\n  EXPECT_EQ(expected_auth_request,\n            StringPiece(hexlify(authRequest->coalesce())));\n}\n\nTEST(SecondaryAuthManagerTest, Authenticator) {\n  // Instantiate a SecondaryAuthManager.\n  auto cert = fizz::test::getCert(kP256Certificate);\n  auto key = fizz::test::getPrivateKey(kP256Key);\n  std::vector<folly::ssl::X509UniquePtr> certs;\n  certs.push_back(std::move(cert));\n  std::unique_ptr<fizz::SelfCert> certPtr =\n      std::make_unique<openssl::OpenSSLSelfCertImpl<openssl::KeyType::P256>>(\n          std::move(key), std::move(certs));\n  EXPECT_NE(certPtr, nullptr);\n  SecondaryAuthManager authManager(std::move(certPtr));\n  // Genearte an authenticator request.\n  auto certRequestContext = folly::IOBuf::copyBuffer(\"0123456789abcdef\");\n  fizz::SignatureAlgorithms sigAlgs;\n  sigAlgs.supported_signature_algorithms.push_back(\n      SignatureScheme::ecdsa_secp256r1_sha256);\n  std::vector<fizz::Extension> extensions;\n  fizz::Extension ext;\n  fizz::Error err;\n  EXPECT_EQ(encodeExtension(ext, err, sigAlgs), fizz::Status::Success);\n  extensions.push_back(std::move(ext));\n  auto authRequestPair = authManager.createAuthRequest(\n      std::move(certRequestContext), std::move(extensions));\n  auto requestId = authRequestPair.first;\n  auto authRequest = std::move(authRequestPair.second);\n\n  // Generate an authenticator.\n  MockAsyncFizzBase fizzBase;\n  EXPECT_CALL(fizzBase, getCipher()).WillRepeatedly(InvokeWithoutArgs([]() {\n    folly::Optional<CipherSuite> cipher = CipherSuite::TLS_AES_128_GCM_SHA256;\n    return cipher;\n  }));\n  EXPECT_CALL(fizzBase, getSupportedSigSchemes())\n      .WillRepeatedly(InvokeWithoutArgs([]() {\n        std::vector<SignatureScheme> schemes = {\n            SignatureScheme::ecdsa_secp256r1_sha256};\n        return schemes;\n      }));\n  EXPECT_CALL(fizzBase, _getExportedKeyingMaterial(_, _, _))\n      .WillRepeatedly(InvokeWithoutArgs(\n          []() { return folly::IOBuf::copyBuffer(\"exportedmaterial\"); }));\n  auto authenticatorPair =\n      authManager.getAuthenticator(fizzBase,\n                                   TransportDirection::UPSTREAM,\n                                   requestId,\n                                   std::move(authRequest));\n  auto certId = authenticatorPair.first;\n  auto authenticator = std::move(authenticatorPair.second);\n\n  // Validate the authenticator.\n  auto isValid = authManager.validateAuthenticator(\n      fizzBase, TransportDirection::UPSTREAM, certId, std::move(authenticator));\n  auto cachedCertId = authManager.getCertId(requestId);\n  EXPECT_TRUE(cachedCertId.has_value());\n  EXPECT_EQ(*cachedCertId, certId);\n  auto peerCert = authManager.getPeerCert(certId);\n  EXPECT_TRUE(peerCert.has_value());\n  EXPECT_EQ((*peerCert).size(), 1);\n  EXPECT_EQ(expected_cert,\n            StringPiece(hexlify(((*peerCert)[0].cert_data)->coalesce())));\n  EXPECT_TRUE(isValid);\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/TestUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/test/TestUtils.h>\n\nusing folly::test::MockAsyncTransport;\n\nusing namespace folly;\n\nnamespace proxygen {\n\nconst wangle::TransportInfo mockTransportInfo = wangle::TransportInfo();\nconst SocketAddress localAddr{\"127.0.0.1\", 80};\nconst SocketAddress peerAddr{\"127.0.0.1\", 12345};\n\nfolly::HHWheelTimer::UniquePtr makeInternalTimeoutSet(EventBase* evb) {\n  folly::HHWheelTimer::UniquePtr t(folly::HHWheelTimer::newTimer(\n      evb,\n      std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      TimeoutManager::InternalEnum::INTERNAL,\n      std::chrono::milliseconds(500)));\n  return t;\n}\n\nfolly::HHWheelTimer::UniquePtr makeTimeoutSet(EventBase* evb) {\n  folly::HHWheelTimer::UniquePtr t(folly::HHWheelTimer::newTimer(\n      evb,\n      std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n      folly::AsyncTimeout::InternalEnum::NORMAL,\n      std::chrono::milliseconds(500)));\n  return t;\n}\n\ntesting::NiceMock<MockAsyncTransport>* newMockTransport(EventBase* evb) {\n  auto transport = new testing::NiceMock<MockAsyncTransport>();\n  EXPECT_CALL(*transport, getEventBase()).WillRepeatedly(testing::Return(evb));\n  return transport;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/TestUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/TimeoutManager.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/HTTP1xCodec.h>\n#include <proxygen/lib/http/codec/HTTP2Codec.h>\n#include <proxygen/lib/http/codec/test/MockHTTPCodec.h>\n#include <proxygen/lib/http/session/HTTPSession.h>\n\nnamespace proxygen {\n\nextern const wangle::TransportInfo mockTransportInfo;\nextern const folly::SocketAddress localAddr;\nextern const folly::SocketAddress peerAddr;\n\nfolly::HHWheelTimer::UniquePtr makeInternalTimeoutSet(folly::EventBase* evb);\n\nfolly::HHWheelTimer::UniquePtr makeTimeoutSet(folly::EventBase* evb);\n\ntesting::NiceMock<folly::test::MockAsyncTransport>* newMockTransport(\n    folly::EventBase* evb);\n\nstruct HTTP1xCodecPair {\n  using Codec = HTTP1xCodec;\n  static const int version = 1;\n};\n\nstruct HTTP2CodecPair {\n  using Codec = HTTP2Codec;\n  static const int version = 2;\n};\n\nstruct MockHTTPCodecPair {\n  using Codec = MockHTTPCodec;\n  static const int version = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/session/test/WebTransportFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/codec/webtransport/WebTransportFramer.h>\n#include <proxygen/lib/http/session/WebTransportFilter.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n\nusing namespace proxygen;\nusing namespace proxygen::test;\nusing namespace testing;\n\nnamespace {\nconst uint32_t WT_ERROR_CODE = 42;\nconst std::string WT_REASON = \"test close reason\";\n} // namespace\n\nclass MockWebTransportFilter : public WebTransportFilter {\n public:\n  MockWebTransportFilter(HTTPTransaction* txn, CodecVersion version)\n      : WebTransportFilter(txn, version) {\n  }\n\n  MOCK_METHOD(void, onPaddingCapsule, (PaddingCapsule), (override, noexcept));\n  MOCK_METHOD(void,\n              onWTResetStreamCapsule,\n              (WTResetStreamCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTStopSendingCapsule,\n              (WTStopSendingCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void, onWTStreamCapsule, (WTStreamCapsule), (override, noexcept));\n  MOCK_METHOD(void,\n              onWTMaxDataCapsule,\n              (WTMaxDataCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTMaxStreamDataCapsule,\n              (WTMaxStreamDataCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTMaxStreamsBidiCapsule,\n              (WTMaxStreamsCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTMaxStreamsUniCapsule,\n              (WTMaxStreamsCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTDataBlockedCapsule,\n              (WTDataBlockedCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTStreamDataBlockedCapsule,\n              (WTStreamDataBlockedCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTStreamsBlockedBidiCapsule,\n              (WTStreamsBlockedCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onWTStreamsBlockedUniCapsule,\n              (WTStreamsBlockedCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void, onDatagramCapsule, (DatagramCapsule), (override, noexcept));\n  MOCK_METHOD(void,\n              onCloseWTSessionCapsule,\n              (CloseWebTransportSessionCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onDrainWTSessionCapsule,\n              (DrainWebTransportSessionCapsule),\n              (override, noexcept));\n  MOCK_METHOD(void,\n              onConnectionError,\n              (CapsuleCodec::ErrorCode),\n              (override, noexcept));\n\n  MOCK_METHOD((folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>),\n              newWebTransportBidiStream,\n              (),\n              (override));\n  MOCK_METHOD((folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>),\n              newWebTransportUniStream,\n              (),\n              (override));\n  MOCK_METHOD((folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>),\n              sendWebTransportStreamData,\n              (HTTPCodec::StreamID,\n               std::unique_ptr<folly::IOBuf>,\n               bool,\n               WebTransport::ByteEventCallback*),\n              (override));\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              resetWebTransportEgress,\n              (HTTPCodec::StreamID, uint32_t),\n              (override));\n  MOCK_METHOD((folly::Expected<folly::Unit, WebTransport::ErrorCode>),\n              sendDatagram,\n              (std::unique_ptr<folly::IOBuf>),\n              (override));\n  MOCK_METHOD((const folly::SocketAddress&),\n              getLocalAddress,\n              (),\n              (override, const));\n  MOCK_METHOD((const folly::SocketAddress&),\n              getPeerAddress,\n              (),\n              (override, const));\n};\n\nclass WebTransportFilterTest : public Test {\n protected:\n  void SetUp() override {\n    handler_ = std::make_unique<StrictMock<MockWebTransportHandler>>();\n    egressQueue_ = std::make_unique<NiceMock<HTTP2PriorityQueue>>();\n\n    txn_ = std::make_unique<NiceMock<MockHTTPTransaction>>(\n        TransportDirection::DOWNSTREAM,\n        1, // stream id\n        0, // seq no\n        *egressQueue_,\n        nullptr,                // timer\n        folly::none,            // idle timeout\n        nullptr,                // stats\n        false,                  // use flow control\n        1024,                   // receive window\n        1024,                   // send window\n        http2::DefaultPriority, // priority\n        folly::none             // assoc stream id\n    );\n\n    filter_ =\n        std::make_unique<MockWebTransportFilter>(txn_.get(), CodecVersion::H3);\n    filter_->setHandler(handler_.get());\n  }\n\n  void TearDown() override {\n    filter_->clearTransaction();\n  }\n\n  std::unique_ptr<folly::IOBuf> createCloseSessionCapsule(\n      uint32_t errorCode = WT_ERROR_CODE,\n      const std::string& reason = WT_REASON) {\n    folly::IOBufQueue queue;\n    CloseWebTransportSessionCapsule capsule{.applicationErrorCode = errorCode,\n                                            .applicationErrorMessage = reason};\n    writeCloseWebTransportSession(queue, capsule);\n\n    return queue.move();\n  }\n\n  std::unique_ptr<StrictMock<MockWebTransportHandler>> handler_;\n  std::unique_ptr<NiceMock<HTTP2PriorityQueue>> egressQueue_;\n  std::unique_ptr<NiceMock<MockHTTPTransaction>> txn_;\n  std::unique_ptr<MockWebTransportFilter> filter_;\n};\n\nTEST_F(WebTransportFilterTest, OnCloseWebTransportSessionCapsule) {\n  // expect the capsule callback to be called and call through to original\n  // implementation to test for onSessionEnd()\n  EXPECT_CALL(*filter_, onCloseWTSessionCapsule(_))\n      .WillOnce(Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n        filter_->WebTransportFilter::onCloseWTSessionCapsule(capsule);\n      }));\n  EXPECT_CALL(*handler_, onSessionEnd(Optional<uint32_t>(WT_ERROR_CODE)));\n\n  auto capsuleData = createCloseSessionCapsule();\n  filter_->onBody(std::move(capsuleData));\n}\n\nTEST_F(WebTransportFilterTest, OnCloseWebTransportSessionCapsuleNoHandler) {\n  // clear the handler to test the case where handler is null\n  filter_->setHandler(nullptr);\n\n  auto capsuleData = createCloseSessionCapsule();\n  // this should not crash even without a handler\n  EXPECT_NO_THROW(filter_->onBody(std::move(capsuleData)));\n}\n\nTEST_F(WebTransportFilterTest, MultipleCapsulesInOneBody) {\n  folly::IOBufQueue queue;\n\n  // first capsule - close session\n  auto firstCapsule = createCloseSessionCapsule(WT_ERROR_CODE);\n  queue.append(std::move(firstCapsule));\n\n  // second capsule - another close session\n  auto secondCapsule = createCloseSessionCapsule(WT_ERROR_CODE + 1);\n  queue.append(std::move(secondCapsule));\n\n  EXPECT_CALL(*filter_, onCloseWTSessionCapsule(_))\n      .Times(2)\n      .WillRepeatedly(\n          Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n            filter_->WebTransportFilter::onCloseWTSessionCapsule(capsule);\n          }));\n\n  EXPECT_CALL(*handler_, onSessionEnd(Optional<uint32_t>(WT_ERROR_CODE)));\n  EXPECT_CALL(*handler_, onSessionEnd(Optional<uint32_t>(WT_ERROR_CODE + 1)));\n\n  filter_->onBody(queue.move());\n}\n\nTEST_F(WebTransportFilterTest, StreamCreationLimits) {\n  // Use H2 codec version so the filter manages stream IDs directly without\n  // delegating to an underlying transport provider\n  auto filter =\n      std::make_unique<WebTransportFilter>(txn_.get(), CodecVersion::H2);\n\n  // Initially should NOT be able to create streams since maxStreams is 0 for H2\n  EXPECT_FALSE(filter->canCreateUniStream());\n  EXPECT_FALSE(filter->canCreateBidiStream());\n\n  WTMaxStreamsCapsule uniCapsule{2};\n  WTMaxStreamsCapsule bidiCapsule{3};\n  filter->onWTMaxStreamsUniCapsule(uniCapsule);\n  filter->onWTMaxStreamsBidiCapsule(bidiCapsule);\n  EXPECT_TRUE(filter->canCreateUniStream());\n  EXPECT_TRUE(filter->canCreateBidiStream());\n\n  // Create the first uni stream (stream ID will be 3)\n  auto uniResult1 = filter->newWebTransportUniStream();\n  EXPECT_TRUE(uniResult1.hasValue());\n  EXPECT_EQ(uniResult1.value(), 3);\n  EXPECT_TRUE(filter->canCreateUniStream());\n\n  // Create the second uni stream (stream ID will be 7)\n  auto uniResult2 = filter->newWebTransportUniStream();\n  EXPECT_TRUE(uniResult2.hasValue());\n  EXPECT_EQ(uniResult2.value(), 7);\n\n  // Now we've created 2 uni streams, limit reached, should not be able to\n  // create more\n  EXPECT_FALSE(filter->canCreateUniStream());\n\n  // Create the first bidi stream (stream ID will be 1)\n  auto bidiResult1 = filter->newWebTransportBidiStream();\n  EXPECT_TRUE(bidiResult1.hasValue());\n  EXPECT_EQ(bidiResult1.value(), 1);\n  EXPECT_TRUE(filter->canCreateBidiStream());\n\n  // Create the second and third bidi streams\n  auto bidiResult2 = filter->newWebTransportBidiStream();\n  auto bidiResult3 = filter->newWebTransportBidiStream();\n  EXPECT_TRUE(bidiResult2.hasValue());\n  EXPECT_TRUE(bidiResult3.hasValue());\n\n  // Now we've created 3 bidi streams, limit reached, should not be able to\n  // create more\n  EXPECT_FALSE(filter->canCreateBidiStream());\n}\n\nTEST_F(WebTransportFilterTest,\n       OnCloseWebTransportSessionCapsuleWhenEgressComplete) {\n  // Test that sendEOM() is NOT called when transaction egress is already\n  // complete\n  auto egressQueueForPushed = std::make_unique<NiceMock<HTTP2PriorityQueue>>();\n\n  // Create an upstream transaction with assocStreamId set, which starts it in\n  // SendingDone state\n  auto txnWithEgressComplete = std::make_unique<NiceMock<MockHTTPTransaction>>(\n      TransportDirection::UPSTREAM, // Must be upstream for SendingDone\n      2,                            // stream id\n      0,                            // seq no\n      *egressQueueForPushed,\n      nullptr,                // timer\n      folly::none,            // idle timeout\n      nullptr,                // stats\n      false,                  // use flow control\n      1024,                   // receive window\n      1024,                   // send window\n      http2::DefaultPriority, // priority\n      1                       // assocStreamId - this triggers SendingDone\n  );\n\n  // Verify the transaction is in SendingDone state\n  EXPECT_TRUE(txnWithEgressComplete->isEgressComplete());\n\n  // Create a filter with this transaction\n  auto filterWithCompleteTxn = std::make_unique<MockWebTransportFilter>(\n      txnWithEgressComplete.get(), CodecVersion::H3);\n  filterWithCompleteTxn->setHandler(handler_.get());\n\n  // Verify that sendEOM() is NOT called when egress is already complete\n  EXPECT_CALL(*txnWithEgressComplete, sendEOM()).Times(0);\n\n  // Expect the capsule callback to be called and call through to the real\n  // implementation to test the fix\n  EXPECT_CALL(*filterWithCompleteTxn, onCloseWTSessionCapsule(_))\n      .WillOnce(Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n        filterWithCompleteTxn->WebTransportFilter::onCloseWTSessionCapsule(\n            capsule);\n      }));\n\n  EXPECT_CALL(*handler_, onSessionEnd(Optional<uint32_t>(WT_ERROR_CODE)));\n\n  auto capsuleData = createCloseSessionCapsule();\n  filterWithCompleteTxn->onBody(std::move(capsuleData));\n}\n\nTEST_F(WebTransportFilterTest,\n       OnCloseWebTransportSessionCapsuleWhenEgressNotComplete) {\n  // Verify the normal case where egress is not complete, sendEOM() should be\n  // called. The transaction is in Start state by default (not SendingDone).\n\n  // Verify that sendEOM() IS called when egress is not complete\n  EXPECT_CALL(*txn_, sendEOM()).Times(1);\n\n  // Expect the capsule callback to be called and call through to the real\n  // implementation to test the normal case\n  EXPECT_CALL(*filter_, onCloseWTSessionCapsule(_))\n      .WillOnce(Invoke([&](const CloseWebTransportSessionCapsule& capsule) {\n        filter_->WebTransportFilter::onCloseWTSessionCapsule(capsule);\n      }));\n\n  EXPECT_CALL(*handler_, onSessionEnd(Optional<uint32_t>(WT_ERROR_CODE)));\n\n  auto capsuleData = createCloseSessionCapsule();\n  filter_->onBody(std::move(capsuleData));\n}\n"
  },
  {
    "path": "proxygen/lib/http/session/test/test_cert1.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpgIBAAKCAQEAvS23j+9p6oBzjAQwT0WW9vjnZJiQe1D7AtN4lFfoPsMhCb3x\nWyLhCnC1sastBb8aSY8cmnjTes4Q8NIFF5dHGWIXt/Uk9r0gPOHSbGfqDhCBsnR8\n4x+dn/+QQ3RTY2rrs9fgoiXOcz/W0TKqTVhbEUkrLyOfmiZ1B2AcX3FndwK3AEJJ\n3Vyx6xhAHCCe0cHw4gStVp2t0w25cgk8+wuFTwIakd1B1MY4c7MBjGLZHPMJ9hA1\nK9WUBGfVlxc1ZqSjJh61iGpq2Ms7JYgF19vzonGJ/5MndwBYuv1KES5uPymZSab3\nDHybeCsGaQj6VEhIhiEdBzTJb/9jc3A8oUOEHQIDAQABAoIBAQCqcRQfRE8jDlQM\nZIG6CjK3lQ21Xpdd41oj86+Bx6nhUiDkDBP2Tnh+1Yl954GK2eCvQZc9vXqb/Cjb\nL/2SgaN3RR+lh+Kjw0XWVIcnUIBZ55lS1Qzn/MYhLzok7BttSRnPUMJ8lJ9qyqua\nk5FIWkDCVC/qKqhTVC7AVGF7xEq5t1Xa3irUtYXD8FJAc63j5qxTIu58ZHzsKzRY\nSThN0wodJD/kOcbL65ckT9hdsaSkb5SYDagD5+p1N3hp8sS1bgJ+UprLnfpp4upC\nGhdsRnEOhNXWQsq6g41ji3HWl+u5FeTxCFZJyxghB2rVTugslNeXLcWqIqOau9Pq\nV2yMljBBAoGBAN/IiqvZeVOK6q9Kb1/aW83quiCOExfEJWLdRCfMx0f3XEH4via0\nvXFISgJuCrvjlFOYbfujIyZ1ykpScJkMfanS6LcjwiHaIfGwDgERv2zhrRAE+Y7x\nkjzOWqjSvyIJR43eSbGfRjcyNxAWPhDwZnmHqIAJpIraaRG7tK/O+qmlAoGBANhp\n0/tosHvlEpAPtYlm36C75Yyjje+sG3hXIM3fdPIwMTj5/S4FYJhqM+ZbBnbx2VGZ\naXIFJXwPIbQWB3/833rsZnynVeiWwdNp0i5MdrN+BFsPsb2lSXKY0w+r6Oa8VOl1\nfJykGpeWwwxsVJhnQDGjN9gnjomRJC1krsPONLcZAoGBAIzZazaItc14VQgpYHpD\nNJ8hiy7sXWYLBcD5JWmrgQ5xyXaYeg5gmhAQAM0Qt97Ueco32ZmVb41IfB9VTht2\nei2GWwTSE+E8qzq0pcvUgNimHkhD6VMoBvQQqY0ywCxLDOdlLpsGdapOB8wvQ7dj\nqAv773lPGGpw18raivpQ2wIlAoGBAJI534wOTsFIJOTGWIlAw3WdwBrpICyDhAQq\nCvZQOHq0aW3wLEv+Qih6ChtqAdI40/g4ynDKXWuX5dQC1op6WJ58QTuU7Y084DZz\nWBsEe2gIi/Tjg9t6ZUhTTrlWFQZi1pTwV4SXtYgbzCsGv5pCmbNwb3lUMRFuyp0J\nXLTdDe1xAoGBALzZUiwKCHp3gpKjrFmJFd+p93WNLM+SVnc+hmtZFrotoLaefC0n\n+YvPJb44mqhLxAuzU45D1zT0x5XQae5kjMRvo4AMmwgiivDYoOSpyxwlxvUsODeF\nhtZ2m3xhthLv164FJ+hMDQO6brnKxix2q9kPZJxYR1+CReDad9BXsnxf\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "proxygen/lib/http/session/test/test_cert1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxUaHJp\nZnQgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MCAXDTIwMDUyODE2MDIyOFoYDzIxMjAw\nNTA0MTYwMjI4WjAnMSUwIwYDVQQDDBxUaHJpZnQgQ2VydGlmaWNhdGUgQXV0aG9y\naXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvS23j+9p6oBzjAQw\nT0WW9vjnZJiQe1D7AtN4lFfoPsMhCb3xWyLhCnC1sastBb8aSY8cmnjTes4Q8NIF\nF5dHGWIXt/Uk9r0gPOHSbGfqDhCBsnR84x+dn/+QQ3RTY2rrs9fgoiXOcz/W0TKq\nTVhbEUkrLyOfmiZ1B2AcX3FndwK3AEJJ3Vyx6xhAHCCe0cHw4gStVp2t0w25cgk8\n+wuFTwIakd1B1MY4c7MBjGLZHPMJ9hA1K9WUBGfVlxc1ZqSjJh61iGpq2Ms7JYgF\n19vzonGJ/5MndwBYuv1KES5uPymZSab3DHybeCsGaQj6VEhIhiEdBzTJb/9jc3A8\noUOEHQIDAQABo4GUMIGRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP8oUaI9\njhoc3ODIS9BSnw4M+SCuME8GA1UdIwRIMEaAFP8oUaI9jhoc3ODIS9BSnw4M+SCu\noSukKTAnMSUwIwYDVQQDDBxUaHJpZnQgQ2VydGlmaWNhdGUgQXV0aG9yaXR5ggEB\nMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAb9GFRPWp3MdAnm5x\nRmXmqhFkxW9FwN0jKz49t+jM/tjgOkJ3hk5cxYAUrIiAwYoWo+qxkVbuGQDaLAmE\n5jm9BqCGorP1ZLTyy6uSmgD3BlVn27Zn5Hn52ab0D+3FjdRZ/yzqQzl71VwQ+nz1\nLc6clwY2aZBM1pFk8NeMDdylXqvOqiRjjhHGrBfvJtgWuXf9TElR5kqCt1cIYqpq\ngWRdf54bUtxD/RLNF3SxYxJF9TL/COASI7AF9beKqznMCvGazpPxEs+63a96t1UE\nPP5qOdgEMojA2amj7bWG6nMK60LpenBEpsQWuQD05MRla9uqN+8nzKEuWvg4cslE\n9MU9rQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "proxygen/lib/http/sink/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_sink_client_sink\n  SRCS\n    HTTPTransactionSink.cpp\n  DEPS\n    proxygen_http_http_headers\n    proxygen_http_session_hq_session\n  EXPORTED_DEPS\n    proxygen_http_session_http_transaction\n    proxygen_http_sink_client_sink_interface\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_sink_flow_control_info)\n\nproxygen_add_library(proxygen_http_sink_client_sink_interface\n  EXPORTED_DEPS\n    proxygen_http_message\n    proxygen_http_sink_flow_control_info\n    Folly::folly_cpp_attributes\n)\n"
  },
  {
    "path": "proxygen/lib/http/sink/FlowControlInfo.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\nnamespace proxygen {\nstruct FlowControlInfo {\n  bool sessionWritesPaused_{false};\n  bool sessionReadsPaused_{false};\n  bool flowControlEnabled_{false};\n  int64_t sessionSendWindow_{-1};\n  int64_t sessionRecvWindow_{-1};\n  int64_t sessionSendOutstanding_{-1};\n  int64_t sessionRecvOutstanding_{-1};\n  int64_t streamSendWindow_{-1};\n  int64_t streamRecvWindow_{-1};\n  int64_t streamSendOutstanding_{-1};\n  int64_t streamRecvOutstanding_{-1};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/HTTPSink.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/CppAttributes.h>\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/sink/FlowControlInfo.h>\n\nnamespace folly {\nclass AsyncTransport;\n}\nnamespace quic {\nclass QuicSocket;\n}\n\nnamespace proxygen {\n\n/**\n * A HTTPSink wraps a http transaction (ClientTxnSink), or an AsyncRequest\n * (AsyncRequestSink).\n *\n * Its purpose is to receive http txn events from HTTPRevProxyHandler.\n * This abstraction allows HTTPRevProxyHandler to continue the normal flow\n * in a similar way as it had http txn.\n *\n * Life-cycle\n * ==========\n * A HTTPSink is owned by a unique_ptr in HTTPRevProxyHandler. It's created\n * when a http txn (/AyncRequest) is set, and destructed when the http txn\n * /AsyncRequest finishes.\n */\nclass HTTPSink {\n public:\n  virtual ~HTTPSink() = default;\n\n  [[nodiscard]] virtual folly::Optional<HTTPCodec::StreamID> getStreamID()\n      const = 0;\n  [[nodiscard]] virtual CodecProtocol getCodecProtocol() const = 0;\n  [[nodiscard]] virtual const folly::SocketAddress& getLocalAddress() const = 0;\n  [[nodiscard]] virtual const folly::SocketAddress& getPeerAddress() const = 0;\n  [[nodiscard]] virtual const folly::AsyncTransportCertificate*\n  getPeerCertificate() const = 0;\n  [[nodiscard]] virtual folly::Optional<HTTPPriority> getHTTPPriority()\n      const = 0;\n  [[nodiscard]] virtual int getTCPTransportFD() const = 0;\n  [[nodiscard]] virtual quic::QuicSocket* getQUICTransport() const = 0;\n  [[nodiscard]] virtual std::chrono::seconds getSessionIdleDuration() const = 0;\n  virtual void getCurrentFlowControlInfo(FlowControlInfo*) const = 0;\n  [[nodiscard]] virtual CompressionInfo getHeaderCompressionInfo() const = 0;\n\n  virtual void detachAndAbortIfIncomplete(std::unique_ptr<HTTPSink> self) = 0;\n  // Sending data\n  virtual void sendHeaders(const HTTPMessage& headers) = 0;\n  virtual void sendHeadersWithEOM(const HTTPMessage& headers) = 0;\n  virtual void sendHeadersWithOptionalEOM(const HTTPMessage& headers,\n                                          bool eom) = 0;\n  virtual void sendBody(std::unique_ptr<folly::IOBuf> body) = 0;\n  virtual void sendChunkHeader(size_t length) = 0;\n  virtual void sendChunkTerminator() = 0;\n  virtual void sendTrailers(const HTTPHeaders& trailers) = 0;\n  virtual void sendPadding(uint16_t bytes) = 0;\n  virtual void sendEOM() = 0;\n  virtual bool isEgressEOMSeen() = 0;\n  virtual void sendAbort() = 0;\n  virtual void updateAndSendPriority(HTTPPriority priority) = 0;\n  enum class ByteEventFlags : uint8_t { ACK = 0x01, TX = 0x02 };\n  virtual bool trackEgressBodyOffset(uint64_t bodyOffset,\n                                     ByteEventFlags flags) = 0;\n\n  // Check state\n  [[nodiscard]] virtual bool canSendHeaders() const = 0;\n  virtual const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept = 0;\n  virtual void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const = 0;\n\n  // Flow control\n  virtual void pauseIngress() = 0;\n  virtual void resumeIngress() = 0;\n  [[nodiscard]] virtual bool isIngressPaused() const = 0;\n  [[nodiscard]] virtual bool isEgressPaused() const = 0;\n  virtual void setEgressRateLimit(uint64_t bitsPerSecond) = 0;\n  // Client timeout\n  virtual void timeoutExpired() = 0;\n  virtual void setIdleTimeout(std::chrono::milliseconds timeout) = 0;\n  // Capabilities\n  [[nodiscard]] virtual bool supportsPush() const = 0;\n  // Logging\n  virtual void describe(std::ostream& os) = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/HTTPTransactionSink.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/session/HQSession.h>\n#include <proxygen/lib/http/sink/HTTPTransactionSink.h>\n\n#include <proxygen/lib/http/RFC2616.h>\n\nnamespace proxygen {\n\nint HTTPTransactionSink::getTCPTransportFD() const {\n  auto transport = httpTransaction_->getTransport().getUnderlyingTransport();\n  const folly::AsyncSocket* sock = nullptr;\n  if (transport) {\n    sock = transport->getUnderlyingTransport<folly::AsyncSocket>();\n  }\n  if (sock) {\n    return sock->getNetworkSocket().toFd();\n  }\n  return -1;\n}\n\nconst folly::AsyncTransportCertificate*\nHTTPTransactionSink::getPeerCertificate() const {\n  auto transport = httpTransaction_->getTransport().getUnderlyingTransport();\n  if (transport) {\n    return transport->getPeerCertificate();\n  } else {\n    auto quicSocket = getQUICTransport();\n    if (quicSocket) {\n      return quicSocket->getPeerCertificate().get();\n    }\n  }\n  return nullptr;\n}\n\nquic::QuicSocket* HTTPTransactionSink::getQUICTransport() const {\n  auto session = httpTransaction_->getTransport().getHTTPSessionBase();\n  if (auto hqSession = dynamic_cast<HQSession*>(session)) {\n    return hqSession->getQuicSocket();\n  }\n  return nullptr;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/HTTPTransactionSink.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n\n#include \"proxygen/lib/http/sink/HTTPSink.h\"\n\nnamespace proxygen {\n\n/**\n * A HTTPTransactionSink forwards events to the client txn.\n */\nclass HTTPTransactionSink : public HTTPSink {\n public:\n  explicit HTTPTransactionSink(HTTPTransaction* clientTxn)\n      : httpTransaction_{clientTxn} {\n    XCHECK(clientTxn)\n        << \"HTTPTransactionSink must be created with a valid clientTxn.\";\n    if (httpTransaction_->getSequenceNumber() > 0) {\n      // TODO: There can be a gap between session open and first req\n      // but the session API precondition is seqNo > 0\n      sessionIdleDuration_ =\n          httpTransaction_->getTransport().getLatestIdleTime();\n    }\n  }\n  ~HTTPTransactionSink() override = default;\n  [[nodiscard]] folly::Optional<HTTPCodec::StreamID> getStreamID()\n      const override {\n    return httpTransaction_->getID();\n  }\n\n  [[nodiscard]] CodecProtocol getCodecProtocol() const override {\n    return httpTransaction_->getTransport().getCodec().getProtocol();\n  }\n  [[nodiscard]] folly::Optional<HTTPPriority> getHTTPPriority() const override {\n    return httpTransaction_->getHTTPPriority();\n  }\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return httpTransaction_->getLocalAddress();\n  }\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return httpTransaction_->getPeerAddress();\n  }\n  [[nodiscard]] const folly::AsyncTransportCertificate* getPeerCertificate()\n      const override;\n  [[nodiscard]] int getTCPTransportFD() const override;\n  [[nodiscard]] quic::QuicSocket* getQUICTransport() const override;\n  [[nodiscard]] std::chrono::seconds getSessionIdleDuration() const override {\n    return sessionIdleDuration_;\n  }\n  void getCurrentFlowControlInfo(FlowControlInfo* info) const override {\n    return httpTransaction_->getCurrentFlowControlInfo(info);\n  }\n  [[nodiscard]] CompressionInfo getHeaderCompressionInfo() const override {\n    return httpTransaction_->getCompressionInfo();\n  }\n  void detachAndAbortIfIncomplete(std::unique_ptr<HTTPSink> self) override {\n    CHECK_EQ(self.get(), this);\n    httpTransaction_->setTransportCallback(nullptr);\n    httpTransaction_->setHandler(nullptr);\n    if (!(httpTransaction_->isEgressComplete() ||\n          httpTransaction_->isEgressEOMQueued()) ||\n        !httpTransaction_->isIngressComplete()) {\n      sendAbort();\n    }\n    self.reset();\n  }\n\n  // Sending data\n  void sendHeaders(const HTTPMessage& headers) override {\n    httpTransaction_->sendHeaders(headers);\n  }\n  void sendHeadersWithEOM(const HTTPMessage& headers) override {\n    httpTransaction_->sendHeadersWithEOM(headers);\n  }\n  void sendHeadersWithOptionalEOM(const HTTPMessage& headers,\n                                  bool eom) override {\n    httpTransaction_->sendHeadersWithOptionalEOM(headers, eom);\n  }\n  void sendBody(std::unique_ptr<folly::IOBuf> body) override {\n    httpTransaction_->sendBody(std::move(body));\n  }\n  void sendChunkHeader(size_t length) override {\n    httpTransaction_->sendChunkHeader(length);\n  }\n  void sendChunkTerminator() override {\n    httpTransaction_->sendChunkTerminator();\n  }\n  void sendTrailers(const HTTPHeaders& trailers) override {\n    httpTransaction_->sendTrailers(trailers);\n  }\n  void sendPadding(uint16_t bytes) override {\n    httpTransaction_->sendPadding(bytes);\n  }\n  void sendEOM() override {\n    httpTransaction_->sendEOM();\n  }\n  bool isEgressEOMSeen() override {\n    return httpTransaction_->isEgressEOMSeen();\n  }\n  void sendAbort() override {\n    httpTransaction_->sendAbort();\n  }\n  void updateAndSendPriority(HTTPPriority priority) override {\n    httpTransaction_->updateAndSendPriority(priority);\n  }\n  bool trackEgressBodyOffset(uint64_t bodyOffset,\n                             ByteEventFlags flags) override {\n    return httpTransaction_->trackEgressBodyOffset(\n        bodyOffset, ByteEvent::EventFlags(flags));\n  }\n  [[nodiscard]] bool canSendHeaders() const override {\n    return httpTransaction_->canSendHeaders();\n  }\n  [[nodiscard]] const wangle::TransportInfo& getSetupTransportInfo()\n      const noexcept override {\n    return httpTransaction_->getSetupTransportInfo();\n  }\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const override {\n    httpTransaction_->getCurrentTransportInfo(tinfo);\n  }\n  // Flow control\n  void pauseIngress() override {\n    httpTransaction_->pauseIngress();\n  }\n  void resumeIngress() override {\n    httpTransaction_->resumeIngress();\n  }\n  [[nodiscard]] bool isIngressPaused() const override {\n    return httpTransaction_->isIngressPaused();\n  }\n  [[nodiscard]] bool isEgressPaused() const override {\n    return httpTransaction_->isEgressPaused();\n  }\n  void setEgressRateLimit(uint64_t bitsPerSecond) override {\n    httpTransaction_->setEgressRateLimit(bitsPerSecond);\n  }\n  // Client timeout\n  void timeoutExpired() override {\n    httpTransaction_->timeoutExpired();\n  }\n  void setIdleTimeout(std::chrono::milliseconds timeout) override {\n    httpTransaction_->setIdleTimeout(timeout);\n  }\n  // Capabilities\n  [[nodiscard]] bool supportsPush() const override {\n    return true;\n  }\n  // Logging\n  void describe(std::ostream& os) override {\n    os << *httpTransaction_;\n  }\n\n private:\n  HTTPTransaction* httpTransaction_;\n  std::chrono::seconds sessionIdleDuration_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/HTTPTunnelSink.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/sink/HTTPTunnelSink.h>\n#include <utility>\n\nnamespace proxygen {\n\nconstexpr uint16_t kMinReadSize = 1460;\nconstexpr uint16_t kMaxReadSize = 4000;\nconstexpr uint8_t kMaxOutstandingWrites = 1;\n\nvoid HTTPTunnelSink::detachAndAbortIfIncomplete(\n    std::unique_ptr<HTTPSink> self) {\n  sock_->setReadCB(nullptr);\n  handler_ = nullptr;\n  // If we haven't seen either EOM, sock is still active so close it\n  if (!egressEOMSeen_ && !ingressEOMRead_) {\n    sock_->closeWithReset();\n  }\n  XCHECK(self.get() == this);\n  if (outstandingWrites_ > 0) {\n    destroyOnWriteComplete_ = true;\n    void(self.release());\n  }\n}\n\nvoid HTTPTunnelSink::sendBody(std::unique_ptr<folly::IOBuf> body) {\n  DestructorCheck::Safety safety(*this);\n  resetIdleTimeout();\n  ++outstandingWrites_;\n  sock_->writeChain(this, std::move(body));\n  if (safety.destroyed()) {\n    return;\n  }\n  if (outstandingWrites_ >= kMaxOutstandingWrites && !handlerEgressPaused_) {\n    handlerEgressPaused_ = true;\n    handler_->onEgressPaused();\n  }\n}\n\nvoid HTTPTunnelSink::sendEOM() {\n  sock_->shutdownWrite();\n  egressEOMSeen_ = true;\n  if (ingressEOMRead_) {\n    handler_->detachTransaction();\n  }\n}\n\nbool HTTPTunnelSink::isEgressEOMSeen() {\n  return egressEOMSeen_;\n}\n\nvoid HTTPTunnelSink::sendAbort() {\n  sock_->closeWithReset();\n  handler_->detachTransaction();\n}\n\nvoid HTTPTunnelSink::getCurrentTransportInfo(\n    wangle::TransportInfo* tinfo) const {\n  auto sock = sock_->getUnderlyingTransport<folly::AsyncSocket>();\n  if (sock) {\n    tinfo->initWithSocket(sock);\n#if defined(__linux__) || defined(__FreeBSD__)\n    tinfo->readTcpCongestionControl(sock);\n    tinfo->readMaxPacingRate(sock);\n#endif // defined(__linux__) || defined(__FreeBSD__)\n    tinfo->totalBytes = sock->getRawBytesWritten();\n  }\n}\n\nvoid HTTPTunnelSink::pauseIngress() {\n  sock_->setReadCB(nullptr);\n}\n\nvoid HTTPTunnelSink::resumeIngress() {\n  sock_->setReadCB(this);\n}\n\n[[nodiscard]] bool HTTPTunnelSink::isIngressPaused() const {\n  return sock_->getReadCallback() == nullptr;\n}\n\n[[nodiscard]] bool HTTPTunnelSink::isEgressPaused() const {\n  return outstandingWrites_ >= kMaxOutstandingWrites;\n}\n\nvoid HTTPTunnelSink::timeoutExpired() noexcept {\n  XLOG(DBG4) << \"Closing socket now\";\n  sock_->closeNow();\n  if (handler_) {\n    DestructorCheck::Safety safety(*this);\n    handler_->onError(HTTPException(\n        HTTPException::Direction::INGRESS_AND_EGRESS, \"Idle timeout expired\"));\n    if (!safety.destroyed() && handler_) {\n      handler_->detachTransaction();\n    }\n  }\n  idleTimeout_ = std::chrono::milliseconds(0);\n}\n\nvoid HTTPTunnelSink::setIdleTimeout(std::chrono::milliseconds timeout) {\n  if (timeout.count() != 0) {\n    idleTimeout_ = timeout;\n    resetIdleTimeout();\n  }\n}\n\n// ReadCallback methods\nvoid HTTPTunnelSink::getReadBuffer(void** buf, size_t* bufSize) {\n  std::pair<void*, uint32_t> readSpace =\n      readBuf_.preallocate(kMinReadSize, kMaxReadSize);\n  *buf = readSpace.first;\n  *bufSize = readSpace.second;\n}\n\nvoid HTTPTunnelSink::readDataAvailable(size_t readSize) noexcept {\n  resetIdleTimeout();\n  readBuf_.postallocate(readSize);\n  while (!readBuf_.empty()) {\n    // Skip any 0 length buffers. Since readBuf_ is not empty, we are\n    // guaranteed to find a non-empty buffer.\n    while (readBuf_.front()->length() == 0) {\n      readBuf_.pop_front();\n    }\n    handler_->onBody(readBuf_.pop_front());\n  }\n}\n\nvoid HTTPTunnelSink::readEOF() noexcept {\n  DestructorCheck::Safety safety(*this);\n  ingressEOMRead_ = true;\n  handler_->onEOM();\n  if (!safety.destroyed() && egressEOMSeen_ && handler_) {\n    handler_->detachTransaction();\n  }\n}\n\nvoid HTTPTunnelSink::readErr(const folly::AsyncSocketException& err) noexcept {\n  DestructorCheck::Safety safety(*this);\n  handler_->onError(\n      HTTPException(HTTPException::Direction::INGRESS_AND_EGRESS, err.what()));\n  if (!safety.destroyed() && handler_) {\n    handler_->detachTransaction();\n  }\n}\n\n// Returns true if this sink is destroyed\nbool HTTPTunnelSink::writeComplete() {\n  outstandingWrites_--;\n  if (outstandingWrites_ == 0 && destroyOnWriteComplete_) {\n    delete this;\n    return true;\n  }\n\n  return false;\n}\n\n// WriteCallback methods\nvoid HTTPTunnelSink::writeSuccess() noexcept {\n  bool destroyed = writeComplete();\n  if (!destroyed) {\n    // If we drop below the max outstanding writes, resume egress\n    if (outstandingWrites_ < kMaxOutstandingWrites && handlerEgressPaused_ &&\n        handler_) {\n      handlerEgressPaused_ = false;\n      handler_->onEgressResumed();\n    }\n    resetIdleTimeout();\n  }\n}\n\nvoid HTTPTunnelSink::writeErr(size_t,\n                              const folly::AsyncSocketException& err) noexcept {\n  bool destroyed = writeComplete();\n  if (!destroyed && handler_) {\n    DestructorCheck::Safety safety(*this);\n    handler_->onError(HTTPException(\n        HTTPException::Direction::INGRESS_AND_EGRESS, err.what()));\n    if (!safety.destroyed() && handler_) {\n      handler_->detachTransaction();\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/HTTPTunnelSink.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <folly/io/async/AsyncSocket.h>\n#include <folly/io/async/AsyncTransport.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <proxygen/lib/http/sink/HTTPSink.h>\n#include <proxygen/lib/utils/ConditionalGate.h>\n\nnamespace proxygen {\n\n/**\n * A HTTPTunnelSink writes data straight to an AsyncTransport.\n */\nclass HTTPTunnelSink\n    : public HTTPSink\n    , public folly::DestructorCheck\n    , public folly::AsyncTransport::ReadCallback\n    , private folly::AsyncTransport::WriteCallback\n    , private folly::HHWheelTimer::Callback {\n\n public:\n  explicit HTTPTunnelSink(folly::AsyncTransport::UniquePtr socket,\n                          HTTPTransactionHandler* handler)\n      : sock_(std::move(socket)), handler_(handler) {\n    CHECK(sock_);\n    sock_->getLocalAddress(&localAddress_);\n    sock_->getPeerAddress(&peerAddress_);\n  }\n\n  ~HTTPTunnelSink() override = default;\n\n  [[nodiscard]] folly::Optional<HTTPCodec::StreamID> getStreamID()\n      const override {\n    return folly::none;\n  }\n\n  [[nodiscard]] CodecProtocol getCodecProtocol() const override {\n    // This is meaningless :(\n    return CodecProtocol::HTTP_1_1;\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return localAddress_;\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return peerAddress_;\n  }\n\n  [[nodiscard]] const folly::AsyncTransportCertificate* getPeerCertificate()\n      const override {\n    return sock_->getPeerCertificate();\n  }\n\n  [[nodiscard]] folly::Optional<HTTPPriority> getHTTPPriority() const override {\n    return folly::none;\n  }\n\n  [[nodiscard]] int getTCPTransportFD() const override {\n    return CHECK_NOTNULL(sock_->getUnderlyingTransport<folly::AsyncSocket>())\n        ->getNetworkSocket()\n        .toFd();\n  }\n\n  [[nodiscard]] quic::QuicSocket* getQUICTransport() const override {\n    return nullptr;\n  }\n\n  [[nodiscard]] std::chrono::seconds getSessionIdleDuration() const override {\n    return std::chrono::seconds(0);\n  }\n\n  void getCurrentFlowControlInfo(FlowControlInfo*) const override {\n  }\n\n  [[nodiscard]] CompressionInfo getHeaderCompressionInfo() const override {\n    return {};\n  }\n\n  void detachAndAbortIfIncomplete(std::unique_ptr<HTTPSink> self) override;\n  // Sending data\n  void sendHeaders(const HTTPMessage&) override {\n  }\n\n  void sendHeadersWithEOM(const HTTPMessage&) override {\n    sendEOM();\n  }\n\n  void sendHeadersWithOptionalEOM(const HTTPMessage&, bool eom) override {\n    if (eom) {\n      sendEOM();\n    }\n  }\n\n  void sendBody(std::unique_ptr<folly::IOBuf> body) override;\n\n  void sendChunkHeader(size_t) override {\n  }\n\n  void sendChunkTerminator() override {\n  }\n\n  void sendTrailers(const HTTPHeaders&) override {\n  }\n\n  void sendPadding(uint16_t) override {\n  }\n\n  void sendEOM() override;\n\n  bool isEgressEOMSeen() override;\n\n  void sendAbort() override;\n\n  void updateAndSendPriority(HTTPPriority) override {\n  }\n\n  bool trackEgressBodyOffset(uint64_t, ByteEventFlags) override {\n    return false;\n  }\n\n  // Check state\n  [[nodiscard]] bool canSendHeaders() const override {\n    return sock_->good();\n  }\n\n  const wangle::TransportInfo& getSetupTransportInfo() const noexcept override {\n    return transportInfo_;\n  }\n\n  void getCurrentTransportInfo(wangle::TransportInfo* tinfo) const override;\n\n  // Flow control\n  void pauseIngress() override;\n\n  void resumeIngress() override;\n\n  [[nodiscard]] bool isIngressPaused() const override;\n\n  [[nodiscard]] bool isEgressPaused() const override;\n\n  void setEgressRateLimit(uint64_t) override {\n  }\n\n  void timeoutExpired() noexcept override;\n\n  void setIdleTimeout(std::chrono::milliseconds timeout) override;\n\n  // Capabilities\n  [[nodiscard]] bool supportsPush() const override {\n    return false;\n  }\n\n  // Logging\n  void describe(std::ostream& os) override {\n    os << \"HTTPTunnelSink on \" << localAddress_ << \" to \" << peerAddress_;\n  }\n\n  // Callback methods\n  void getReadBuffer(void** buf, size_t* bufSize) override;\n\n  void readDataAvailable(size_t readSize) noexcept override;\n\n  void readEOF() noexcept override;\n\n  void readErr(const folly::AsyncSocketException& err) noexcept override;\n\n  void writeSuccess() noexcept override;\n\n  void writeErr(size_t,\n                const folly::AsyncSocketException& err) noexcept override;\n\n private:\n  void resetIdleTimeout() {\n    cancelTimeout();\n    if (idleTimeout_.count()) {\n      sock_->getEventBase()->timer().scheduleTimeout(this, idleTimeout_);\n    }\n  }\n\n  // Returns true if this sink was destroyed\n  bool writeComplete();\n\n  folly::AsyncTransport::UniquePtr sock_;\n  folly::SocketAddress peerAddress_;\n  folly::SocketAddress localAddress_;\n  wangle::TransportInfo transportInfo_;\n\n  /** Chain of ingress IOBufs */\n  folly::IOBufQueue readBuf_{folly::IOBufQueue::cacheChainLength()};\n  HTTPTransactionHandler* handler_{nullptr};\n\n  std::chrono::milliseconds idleTimeout_{0};\n  bool destroyOnWriteComplete_{false};\n  uint8_t outstandingWrites_{0};\n\n  bool ingressEOMRead_{false};\n  bool egressEOMSeen_{false};\n  bool handlerEgressPaused_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/sink/test/HTTPConnectSinkTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/SocketAddress.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/io/async/test/MockAsyncTransport.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/facebook/lib/http/sink/HTTPConnectSink.h>\n\n#include <memory>\n\n#include \"proxygen/facebook/revproxy/test/MockHTTPTransactionHandler.h\"\n\nnamespace proxygen {\nusing namespace testing;\nusing namespace folly::test;\n\n// Subclass which tracks deletion\nclass TestHTTPConnectSink : public HTTPConnectSink {\n public:\n  using HTTPConnectSink::HTTPConnectSink;\n  ~TestHTTPConnectSink() override {\n    if (isDeleted != nullptr) {\n      *isDeleted = true;\n    }\n  }\n  std::shared_ptr<bool> isDeleted;\n};\n\nclass HTTPConnectSinkTest : public Test {\n public:\n  void SetUp() override {\n    evb_ = folly::EventBaseManager::get()->getEventBase();\n    mockSocket_ = new MockAsyncTransport();\n    mockHandler_ = new MockHTTPTransactionHandler();\n    sink_ = std::make_unique<TestHTTPConnectSink>(\n        folly::AsyncTransport::UniquePtr(mockSocket_), mockHandler_);\n    sinkDeleted_ = std::make_shared<bool>(false);\n    sink_->isDeleted = sinkDeleted_;\n  }\n\n  void TearDown() override {\n    sink_.reset();\n    delete mockHandler_;\n    // Don't delete mockSocket_ because it's owned by sink_\n  }\n\n protected:\n  folly::EventBase *evb_;\n  MockAsyncTransport *mockSocket_;\n  MockHTTPTransactionHandler *mockHandler_;\n\n  std::shared_ptr<bool> sinkDeleted_;\n\n  std::unique_ptr<TestHTTPConnectSink> sink_;\n};\n\nusing WriteCB = folly::AsyncTransport::WriteCallback;\n\nTEST_F(HTTPConnectSinkTest, test_egress_backpressure) {\n  // Test that egress backpressure is propagated to the handler\n\n  // Mock underlying write to just store the callbacks for later\n  std::vector<WriteCB *> writeCallbacks;\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(Invoke([&writeCallbacks](WriteCB *cb, auto &&, auto &&) {\n        writeCallbacks.push_back(cb);\n      }));\n\n  // Send bytes, which should trigger egress pause\n  EXPECT_CALL(*mockHandler_, onEgressPaused);\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"Is anybody there?\"));\n  EXPECT_TRUE(sink_->isEgressPaused());\n\n  // Write success to just one callback, which should resume egress\n  EXPECT_CALL(*mockHandler_, onEgressResumed).WillOnce(Return());\n  writeCallbacks[0]->writeSuccess();\n  EXPECT_FALSE(sink_->isEgressPaused());\n\n  // Send another body, which should trigger egress pausing again\n  EXPECT_CALL(*mockHandler_, onEgressPaused).WillOnce(Return());\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"Oh, it's just me?\"));\n\n  // Write success to both remaining callbacks, which should resume egress again\n  EXPECT_CALL(*mockHandler_, onEgressResumed).WillOnce(Return());\n  writeCallbacks[1]->writeSuccess();\n\n  // Re-mock the write callback to just call writeSuccess immediately\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(\n          Invoke([](WriteCB *cb, auto &&, auto &&) { cb->writeSuccess(); }));\n\n  // Egress shouldn't pause and resume, which we're checking w/ WillOnce\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"We made it!\"));\n}\n\nTEST_F(HTTPConnectSinkTest, test_ingress_control) {\n  folly::AsyncTransport::ReadCallback *read_cb = sink_.get();\n  EXPECT_CALL(*mockSocket_, setReadCB).WillRepeatedly(Invoke([&](auto cb) {\n    read_cb = cb;\n  }));\n  EXPECT_CALL(*mockSocket_, getReadCallback).WillRepeatedly(Invoke([&]() {\n    return read_cb;\n  }));\n  EXPECT_FALSE(sink_->isIngressPaused());\n  sink_->pauseIngress();\n  EXPECT_EQ(read_cb, nullptr);\n  EXPECT_TRUE(sink_->isIngressPaused());\n  sink_->resumeIngress();\n  EXPECT_EQ(read_cb, sink_.get());\n}\n\nTEST_F(HTTPConnectSinkTest, test_bytes_read) {\n  // Test that read bytes are propagated to the handler\n  std::string bytes = \"Shoelace is a cute dog!\";\n  size_t num_bytes = bytes.size();\n\n  // get an IOBuf to write to\n  void *buf = nullptr;\n  size_t siz = 0;\n  sink_->getReadBuffer(&buf, &siz);\n  memcpy(buf, bytes.c_str(), num_bytes);\n\n  EXPECT_CALL(*mockHandler_, onBody)\n      .WillOnce(Invoke([&bytes](auto &&written_bytes) {\n        EXPECT_EQ(written_bytes->moveToFbString().toStdString(), bytes);\n      }));\n  sink_->readDataAvailable(num_bytes);\n}\n\n// Run all callbacks on evb until the specified time has passed\nvoid sleep_evb(folly::EventBase *evb, std::chrono::milliseconds timeout) {\n  bool done = false;\n  evb->scheduleAt([&done]() { done = true; }, evb->now() + timeout);\n  while (!done) {\n    evb->loopOnce();\n  }\n};\n\nTEST_F(HTTPConnectSinkTest, test_idle_timeout) {\n  EXPECT_CALL(*mockSocket_, getEventBase).WillRepeatedly(Invoke([&]() {\n    return evb_;\n  }));\n  sink_->setIdleTimeout(std::chrono::milliseconds(10));\n\n  // Wait 8ms; the timeout shouldn't have expired\n  sleep_evb(evb_, std::chrono::milliseconds(8));\n\n  // Test that read bytes are propagated to the handler\n  std::string bytes = \"Shoelace is a cute dog!\";\n  size_t num_bytes = bytes.size();\n\n  // get an IOBuf to write to\n  void *buf = nullptr;\n  size_t siz = 0;\n  sink_->getReadBuffer(&buf, &siz);\n  memcpy(buf, bytes.c_str(), num_bytes);\n\n  EXPECT_CALL(*mockHandler_, onBody)\n      .WillOnce(Invoke([&bytes](auto &&written_bytes) {\n        EXPECT_EQ(written_bytes->moveToFbString().toStdString(), bytes);\n      }));\n  sink_->readDataAvailable(num_bytes);\n\n  // Wait another 8ms; the timeout was reset, so this still shouldn't expire\n  sleep_evb(evb_, std::chrono::milliseconds(8));\n\n  // Ok, now, expect callbacks which happen during timeout expiration\n  EXPECT_CALL(*mockHandler_, onError).WillOnce(Return());\n  EXPECT_CALL(*mockHandler_, detachTransaction).WillOnce(Return());\n  EXPECT_CALL(*mockSocket_, closeNow).WillOnce(Return());\n  sleep_evb(evb_, std::chrono::milliseconds(8));\n}\n\nTEST_F(HTTPConnectSinkTest, test_delayed_destruction) {\n  // Mock underlying write to just store the callbacks for later\n  std::vector<WriteCB *> writeCallbacks;\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(Invoke([&writeCallbacks](WriteCB *cb, auto &&, auto &&) {\n        writeCallbacks.push_back(cb);\n      }));\n\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"hello world\"));\n\n  EXPECT_CALL(*mockSocket_, closeWithReset).WillOnce(Return());\n  sink_->detachAndAbortIfIncomplete(std::move(sink_));\n  // There's an outstanding write, so the sink should not be deleted yet\n  EXPECT_EQ(*sinkDeleted_, false);\n\n  // The sink should be destroyed after the final write\n  writeCallbacks[0]->writeSuccess();\n  EXPECT_EQ(*sinkDeleted_, true);\n}\n\nTEST_F(HTTPConnectSinkTest, test_destruction_on_abort) {\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(\n          Invoke([](WriteCB *cb, auto &&, auto &&) { cb->writeSuccess(); }));\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"hello world\"));\n\n  EXPECT_CALL(*mockSocket_, closeWithReset).WillOnce(Return());\n  sink_->detachAndAbortIfIncomplete(std::move(sink_));\n\n  // The sink should be destroyed\n  EXPECT_EQ(*sinkDeleted_, true);\n}\n\nTEST_F(HTTPConnectSinkTest, test_ingress_eom) {\n  // expect handler onEOM\n  EXPECT_CALL(*mockHandler_, onEOM).WillOnce(Return());\n  sink_->readEOF();\n\n  // now that we've read an upstream EOM, if we send a downstream EOM,\n  // the handler will detach\n  EXPECT_CALL(*mockHandler_, detachTransaction).WillOnce(Return());\n  EXPECT_CALL(*mockSocket_, shutdownWrite).WillOnce(Return());\n  sink_->sendEOM();\n  EXPECT_TRUE(sink_->isEgressEOMSeen());\n}\n\nTEST_F(HTTPConnectSinkTest, test_send_abort) {\n  EXPECT_CALL(*mockHandler_, detachTransaction).WillOnce(Return());\n  EXPECT_CALL(*mockSocket_, closeWithReset).WillOnce(Return());\n  sink_->sendAbort();\n}\n\nTEST_F(HTTPConnectSinkTest, test_read_error) {\n  EXPECT_CALL(*mockHandler_, onError).WillOnce(Return());\n  EXPECT_CALL(*mockHandler_, detachTransaction).WillOnce(Return());\n  sink_->readErr(folly::AsyncSocketException(\n      folly::AsyncSocketException::AsyncSocketExceptionType::UNKNOWN, \"test\"));\n}\n\nTEST_F(HTTPConnectSinkTest, test_read_error_with_abort) {\n  EXPECT_CALL(*mockHandler_, onError).WillOnce(Invoke([&](auto &&) {\n    sink_->detachAndAbortIfIncomplete(std::move(sink_));\n  }));\n  EXPECT_CALL(*mockHandler_, detachTransaction).Times(0);\n  sink_->readErr(folly::AsyncSocketException(\n      folly::AsyncSocketException::AsyncSocketExceptionType::UNKNOWN, \"test\"));\n}\n\nTEST_F(HTTPConnectSinkTest, test_write_error) {\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(Invoke([](WriteCB *cb, auto &&, auto &&) {\n        cb->writeErr(\n            0,\n            folly::AsyncSocketException(\n                folly::AsyncSocketException::AsyncSocketExceptionType::UNKNOWN,\n                \"test\"));\n      }));\n  EXPECT_CALL(*mockHandler_, onError).WillOnce(Return());\n  EXPECT_CALL(*mockHandler_, detachTransaction).WillOnce(Return());\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"hello world\"));\n}\n\nTEST_F(HTTPConnectSinkTest, test_write_error_with_abort) {\n  EXPECT_CALL(*mockSocket_, writeChain)\n      .WillRepeatedly(Invoke([](WriteCB *cb, auto &&, auto &&) {\n        cb->writeErr(\n            0,\n            folly::AsyncSocketException(\n                folly::AsyncSocketException::AsyncSocketExceptionType::UNKNOWN,\n                \"test\"));\n      }));\n  EXPECT_CALL(*mockHandler_, onError).WillOnce(Invoke([&](auto &&) {\n    sink_->detachAndAbortIfIncomplete(std::move(sink_));\n  }));\n  EXPECT_CALL(*mockHandler_, detachTransaction).Times(0);\n  sink_->sendBody(folly::IOBuf::copyBuffer(\"hello world\"));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ConnectionStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/stats/ConnectionStats.h\"\n\nusing namespace facebook::fb303;\n\nnamespace proxygen {\n\nMinimalConnectionStats::MinimalConnectionStats(const std::string& prefix,\n                                               uint8_t verbosity) {\n  req_.emplace(prefix + \"_req\", SUM);\n  egressBytes_.emplace(prefix + \"_egress_bytes\", SUM);\n  upstreamLoadShed_.emplace(prefix + \"_req_was_loadshed_by_upstream\", SUM);\n\n  if (verbosity > 8) {\n    resp_.emplace(prefix + \"_resp\", SUM);\n    ingressBytes_.emplace(prefix + \"_ingress_bytes\", SUM);\n    egressBodyBytes_.emplace(prefix + \"_egress_body_bytes\", SUM);\n    ingressBodyBytes_.emplace(prefix + \"_ingress_body_bytes\", SUM);\n\n    totalDuration_.emplace(prefix + \"_conn_duration\",\n                           facebook::fb303::ExportTypeConsts::kNone,\n                           facebook::fb303::QuantileConsts::kP50_P95_P99);\n    currConns_.emplace(prefix + \"_conn\");\n    newConns_.emplace(prefix + \"_new_conn\", SUM);\n    currTcpConns_.emplace(prefix + \"_tcp_conn\");\n    newTcpConns_.emplace(prefix + \"_new_tcp_conn\", SUM);\n  }\n}\n\nvoid MinimalConnectionStats::recordConnectionOpen() {\n  BaseStats::incrementOptionalCounter(currConns_, 1);\n  BaseStats::addToOptionalStat(newConns_, 1);\n}\n\nvoid MinimalConnectionStats::recordTcpConnectionOpen() {\n  BaseStats::incrementOptionalCounter(currTcpConns_, 1);\n  BaseStats::addToOptionalStat(newTcpConns_, 1);\n}\n\nvoid MinimalConnectionStats::recordConnectionClose() {\n  BaseStats::incrementOptionalCounter(currConns_, -1);\n}\n\nvoid MinimalConnectionStats::recordRequest() {\n  BaseStats::addToOptionalStat(req_, 1);\n}\n\nvoid MinimalConnectionStats::recordResponse(\n    folly::Optional<uint16_t> responseCode, bool hasRetryAfterHeader) {\n  BaseStats::addToOptionalStat(resp_, 1);\n  if (responseCode.has_value() && upstreamLoadShed_ && hasRetryAfterHeader &&\n      responseCode.value() == 503) {\n    upstreamLoadShed_->add(1);\n  }\n}\n\nvoid MinimalConnectionStats::recordDuration(size_t duration) {\n  BaseStats::addValueToOptionalStat(totalDuration_, duration);\n}\n\nvoid MinimalConnectionStats::addEgressBytes(size_t bytes) {\n  BaseStats::addToOptionalStat(egressBytes_, bytes);\n}\n\nvoid MinimalConnectionStats::addIngressBytes(size_t bytes) {\n  BaseStats::addToOptionalStat(ingressBytes_, bytes);\n}\n\nvoid MinimalConnectionStats::addEgressBodyBytes(size_t bytes) {\n  BaseStats::addToOptionalStat(egressBodyBytes_, bytes);\n}\n\nvoid MinimalConnectionStats::addIngressBodyBytes(size_t bytes) {\n  BaseStats::addToOptionalStat(ingressBodyBytes_, bytes);\n}\n\nTLConnectionStats::TLConnectionStats(const std::string& prefix,\n                                     uint8_t verbosity)\n    : MinimalConnectionStats(prefix, verbosity) {\n  responseCodes_.emplace(prefix + \"_\", verbosity);\n}\n\nvoid TLConnectionStats::recordResponse(folly::Optional<uint16_t> responseCode,\n                                       bool hasRetryAfterHeader) {\n  MinimalConnectionStats::recordResponse(responseCode, hasRetryAfterHeader);\n  if (responseCodes_ && responseCode.has_value()) {\n    responseCodes_->addStatus(responseCode.value());\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ConnectionStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n\n#include <proxygen/lib/http/stats/TLResponseCodeStats.h>\n\n#include <proxygen/lib/stats/StatsWrapper.h>\n\nnamespace proxygen {\n\n/**\n * Connection stats abstract interface.\n */\nclass ConnectionStats {\n public:\n  virtual ~ConnectionStats() = default;\n\n  virtual void recordConnectionOpen() = 0;\n\n  virtual void recordTcpConnectionOpen() = 0;\n\n  virtual void recordConnectionClose() = 0;\n\n  virtual void recordRequest() = 0;\n\n  virtual void recordResponse(\n      folly::Optional<uint16_t> responseCode = folly::none,\n      bool hasRetryAfterHeader = false) = 0;\n\n  virtual void recordDuration(size_t duration) = 0;\n\n  virtual void addEgressBytes(size_t bytes) = 0;\n\n  virtual void addIngressBytes(size_t bytes) = 0;\n\n  virtual void addEgressBodyBytes(size_t bytes) = 0;\n\n  virtual void addIngressBodyBytes(size_t bytes) = 0;\n};\n\n/**\n * Wraps connection stat counters.  One instance should be created per\n * uniquely named counter and shared accross threads as necessary.\n * This class doesn't include response code statistics. If those are needed use\n * TLConnectionStats defined below.\n */\nclass MinimalConnectionStats : public ConnectionStats {\n public:\n  explicit MinimalConnectionStats(const std::string& prefix, uint8_t verbosity);\n\n  void recordConnectionOpen() override;\n\n  void recordTcpConnectionOpen() override;\n\n  void recordConnectionClose() override;\n\n  void recordRequest() override;\n\n  void recordResponse(folly::Optional<uint16_t> responseCode = folly::none,\n                      bool hasRetryAfterHeader = false) override;\n\n  void recordDuration(size_t duration) override;\n\n  void addEgressBytes(size_t bytes) override;\n\n  void addIngressBytes(size_t bytes) override;\n\n  void addEgressBodyBytes(size_t bytes) override;\n\n  void addIngressBodyBytes(size_t bytes) override;\n\n private:\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> req_;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> resp_;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> egressBytes_;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> ingressBytes_;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> egressBodyBytes_;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> ingressBodyBytes_;\n  std::optional<StatsWrapper::TLTimeseriesMinute> upstreamLoadShed_;\n  std::optional<BaseStats::LazyQuantileStatWrapper> totalDuration_;\n\n  std::optional<StatsWrapper::TLCounter> currConns_;\n  std::optional<StatsWrapper::TLTimeseries> newConns_;\n\n  std::optional<StatsWrapper::TLCounter> currTcpConns_;\n  std::optional<StatsWrapper::TLTimeseries> newTcpConns_;\n};\n\n/*\n * Adds response code stats to the basic connection stats.\n * If you are unsure what class to use, use this one. It should be used as the\n * default for most cases.\n */\nclass TLConnectionStats : public MinimalConnectionStats {\n public:\n  explicit TLConnectionStats(const std::string& prefix, uint8_t verbosity);\n\n  void recordResponse(folly::Optional<uint16_t> responseCode = folly::none,\n                      bool hasRetryAfterHeader = false) override;\n\n private:\n  std::optional<TLResponseCodeStats> responseCodes_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HTTPCodecStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/stats/HTTPCodecStats.h>\n\nusing facebook::fb303::RATE;\nusing facebook::fb303::SUM;\n\nnamespace {\nstatic std::array<const char*, 14> kErrorStrings{\n    \"Ok\",\n    \"Protocol_Error\",\n    \"Internal_Error\",\n    \"Flow_Control_Error\",\n    \"Settings_Timeout\",\n    \"Stream_Closed\",\n    \"Frame_Size_Error\",\n    \"Refused_Stream\",\n    \"Cancel\",\n    \"Compression_Error\",\n    \"Connect_Error\",\n    \"Enhance_Your_Calm\",\n    \"Inadequate_Security\",\n    \"Http_1_1_Required\",\n};\n\n} // namespace\n\nnamespace proxygen {\n\n// TLHTTPCodecStats\n\nTLHTTPCodecStats::TLHTTPCodecStats(const std::string& prefix)\n    : openConn_(prefix + \"_conn.sum\"),\n      ingressSynStream_(prefix + \"_ingress_syn_stream\", SUM, RATE),\n      ingressSynReply_(prefix + \"_ingress_syn_reply\", SUM, RATE),\n      ingressPushPromise_(prefix + \"_ingress_push_promise\", SUM, RATE),\n      ingressExStream_(prefix + \"_ingress_ex_stream\", SUM, RATE),\n      ingressData_(prefix + \"_ingress_data\", SUM, RATE),\n      ingressRst_(prefix + \"_ingress_rst\", SUM, RATE),\n      ingressSettings_(prefix + \"_ingress_settings\", SUM, RATE),\n      ingressPingRequest_(prefix + \"_ingress_ping_request\", SUM, RATE),\n      ingressPingReply_(prefix + \"_ingress_ping_reply\", SUM, RATE),\n      ingressGoaway_(prefix + \"_ingress_goaway\", SUM, RATE),\n      ingressGoawayDrain_(prefix + \"_ingress_goaway_drain\", SUM, RATE),\n      ingressWindowUpdate_(prefix + \"_ingress_window_update\", SUM, RATE),\n      ingressPriority_(prefix + \"_ingress_priority\", SUM, RATE),\n      egressSynStream_(prefix + \"_egress_syn_stream\", SUM, RATE),\n      egressSynReply_(prefix + \"_egress_syn_reply\", SUM, RATE),\n      egressPushPromise_(prefix + \"_egress_push_promise\", SUM, RATE),\n      egressExStream_(prefix + \"_egress_ex_stream\", SUM, RATE),\n      egressData_(prefix + \"_egress_data\", SUM, RATE),\n      egressRst_(prefix + \"_egress_rst\", SUM, RATE),\n      egressSettings_(prefix + \"_egress_settings\", SUM, RATE),\n      egressPingRequest_(prefix + \"_egress_ping_request\", SUM, RATE),\n      egressPingReply_(prefix + \"_egress_ping_reply\", SUM, RATE),\n      egressGoaway_(prefix + \"_egress_goaway\", SUM, RATE),\n      egressGoawayDrain_(prefix + \"_egress_goaway_drain\", SUM, RATE),\n      egressWindowUpdate_(prefix + \"_egress_window_update\", SUM, RATE),\n      egressPriority_(prefix + \"_egress_priority\", SUM, RATE) {\n  ingressRstStatus_.reserve(kErrorStrings.size());\n  egressRstStatus_.reserve(kErrorStrings.size());\n  ingressGoawayStatus_.reserve(kErrorStrings.size());\n  egressGoawayStatus_.reserve(kErrorStrings.size());\n  for (auto errString : kErrorStrings) {\n    ingressRstStatus_.emplace_back(prefix + \"_ingress_rst_\" + errString, SUM);\n    egressRstStatus_.emplace_back(prefix + \"_egress_rst_\" + errString, SUM);\n    ingressGoawayStatus_.emplace_back(prefix + \"_ingress_goaway_\" + errString,\n                                      SUM);\n    egressGoawayStatus_.emplace_back(prefix + \"_egress_goaway_\" + errString,\n                                     SUM);\n  }\n}\n\nvoid TLHTTPCodecStats::incrementParallelConn(int64_t amount) {\n  openConn_.incrementValue(amount);\n}\nvoid TLHTTPCodecStats::recordIngressSynStream() {\n  ingressSynStream_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressSynReply() {\n  ingressSynReply_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressPushPromise() {\n  ingressPushPromise_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressExStream() {\n  ingressExStream_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressData() {\n  ingressData_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressRst(ErrorCode statusCode) {\n  ingressRst_.add(1);\n  auto index = uint32_t(statusCode);\n  if (index >= kErrorStrings.size()) {\n    LOG(ERROR) << \"Unknown ingress reset status code=\" << index;\n    index = (uint32_t)ErrorCode::PROTOCOL_ERROR;\n  }\n  ingressRstStatus_[index].add(1);\n}\nvoid TLHTTPCodecStats::recordIngressSettings() {\n  ingressSettings_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressPingRequest() {\n  ingressPingRequest_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressPingReply() {\n  ingressPingReply_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressGoaway(ErrorCode statusCode) {\n  ingressGoaway_.add(1);\n  auto index = uint32_t(statusCode);\n  if (index >= kErrorStrings.size()) {\n    LOG(ERROR) << \"Unknown ingress goaway status code=\" << index;\n    index = (uint32_t)ErrorCode::PROTOCOL_ERROR;\n  }\n  ingressGoawayStatus_[index].add(1);\n}\nvoid TLHTTPCodecStats::recordIngressGoawayDrain() {\n  ingressGoawayDrain_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressWindowUpdate() {\n  ingressWindowUpdate_.add(1);\n}\nvoid TLHTTPCodecStats::recordIngressPriority() {\n  ingressPriority_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressSynStream() {\n  egressSynStream_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressSynReply() {\n  egressSynReply_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressPushPromise() {\n  egressPushPromise_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressExStream() {\n  egressExStream_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressData() {\n  egressData_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressRst(ErrorCode statusCode) {\n  egressRst_.add(1);\n  auto index = uint32_t(statusCode);\n  if (index >= kErrorStrings.size()) {\n    LOG(ERROR) << \"Unknown egress reset status code=\" << index;\n    index = (uint32_t)ErrorCode::PROTOCOL_ERROR;\n  }\n  egressRstStatus_[index].add(1);\n}\nvoid TLHTTPCodecStats::recordEgressSettings() {\n  egressSettings_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressPingRequest() {\n  egressPingRequest_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressPingReply() {\n  egressPingReply_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressGoaway(ErrorCode statusCode) {\n  egressGoaway_.add(1);\n  auto index = uint32_t(statusCode);\n  if (index >= kErrorStrings.size()) {\n    LOG(ERROR) << \"Unknown egress goaway status code=\" << index;\n    index = (uint32_t)ErrorCode::PROTOCOL_ERROR;\n  }\n  egressGoawayStatus_[index].add(1);\n}\nvoid TLHTTPCodecStats::recordEgressGoawayDrain() {\n  egressGoawayDrain_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressWindowUpdate() {\n  egressWindowUpdate_.add(1);\n}\nvoid TLHTTPCodecStats::recordEgressPriority() {\n  egressPriority_.add(1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HTTPCodecStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/ErrorCode.h>\n#include <proxygen/lib/stats/StatsWrapper.h>\n#include <string>\n\nnamespace proxygen {\n\n/**\n * A stats interface for tracking HTTP events, primarily those on parallel\n * protocols.\n */\nclass HTTPCodecStats {\n public:\n  virtual ~HTTPCodecStats() = default;\n\n  virtual void incrementParallelConn(int64_t amount) = 0;\n\n  virtual void recordIngressSynStream() = 0;\n  virtual void recordIngressSynReply() = 0;\n  virtual void recordIngressPushPromise() = 0;\n  virtual void recordIngressExStream() = 0;\n  virtual void recordIngressData() = 0;\n  virtual void recordIngressRst(ErrorCode statusCode) = 0;\n  virtual void recordIngressSettings() = 0;\n  virtual void recordIngressPingRequest() = 0;\n  virtual void recordIngressPingReply() = 0;\n  virtual void recordIngressGoaway(ErrorCode statusCode) = 0;\n  virtual void recordIngressGoawayDrain() = 0;\n  virtual void recordIngressWindowUpdate() = 0;\n  virtual void recordIngressPriority() = 0;\n\n  virtual void recordEgressSynStream() = 0;\n  virtual void recordEgressSynReply() = 0;\n  virtual void recordEgressPushPromise() = 0;\n  virtual void recordEgressExStream() = 0;\n  virtual void recordEgressData() = 0;\n  virtual void recordEgressRst(ErrorCode statusCode) = 0;\n  virtual void recordEgressSettings() = 0;\n  virtual void recordEgressPingRequest() = 0;\n  virtual void recordEgressPingReply() = 0;\n  virtual void recordEgressGoaway(ErrorCode statusCode) = 0;\n  virtual void recordEgressGoawayDrain() = 0;\n  virtual void recordEgressWindowUpdate() = 0;\n  virtual void recordEgressPriority() = 0;\n};\n\n/**\n * A TCSD implementation of HTTPCodecStats\n */\nclass TLHTTPCodecStats : public HTTPCodecStats {\n public:\n  explicit TLHTTPCodecStats(const std::string& prefix);\n  explicit TLHTTPCodecStats(const TLHTTPCodecStats&) = delete;\n  TLHTTPCodecStats& operator=(const TLHTTPCodecStats&) = delete;\n  ~TLHTTPCodecStats() override = default;\n\n  void incrementParallelConn(int64_t amount) override;\n\n  void recordIngressSynStream() override;\n  void recordIngressSynReply() override;\n  void recordIngressPushPromise() override;\n  void recordIngressExStream() override;\n  void recordIngressData() override;\n  void recordIngressRst(ErrorCode statusCode) override;\n  void recordIngressSettings() override;\n  void recordIngressPingRequest() override;\n  void recordIngressPingReply() override;\n  void recordIngressGoaway(ErrorCode statusCode) override;\n  void recordIngressGoawayDrain() override;\n  void recordIngressWindowUpdate() override;\n  void recordIngressPriority() override;\n\n  void recordEgressSynStream() override;\n  void recordEgressSynReply() override;\n  void recordEgressPushPromise() override;\n  void recordEgressExStream() override;\n  void recordEgressData() override;\n  void recordEgressRst(ErrorCode statusCode) override;\n  void recordEgressSettings() override;\n  void recordEgressPingRequest() override;\n  void recordEgressPingReply() override;\n  void recordEgressGoaway(ErrorCode statusCode) override;\n  void recordEgressGoawayDrain() override;\n  void recordEgressWindowUpdate() override;\n  void recordEgressPriority() override;\n\n private:\n  StatsWrapper::TLCounter openConn_;\n  StatsWrapper::TLTimeseries ingressSynStream_;\n  StatsWrapper::TLTimeseries ingressSynReply_;\n  StatsWrapper::TLTimeseries ingressPushPromise_;\n  StatsWrapper::TLTimeseries ingressExStream_;\n  StatsWrapper::TLTimeseries ingressData_;\n  StatsWrapper::TLTimeseries ingressRst_;\n  std::vector<StatsWrapper::TLTimeseries> ingressRstStatus_;\n  StatsWrapper::TLTimeseries ingressSettings_;\n  StatsWrapper::TLTimeseries ingressPingRequest_;\n  StatsWrapper::TLTimeseries ingressPingReply_;\n  StatsWrapper::TLTimeseries ingressGoaway_;\n  StatsWrapper::TLTimeseries ingressGoawayDrain_;\n  std::vector<StatsWrapper::TLTimeseries> ingressGoawayStatus_;\n  StatsWrapper::TLTimeseries ingressWindowUpdate_;\n  StatsWrapper::TLTimeseries ingressPriority_;\n\n  StatsWrapper::TLTimeseries egressSynStream_;\n  StatsWrapper::TLTimeseries egressSynReply_;\n  StatsWrapper::TLTimeseries egressPushPromise_;\n  StatsWrapper::TLTimeseries egressExStream_;\n  StatsWrapper::TLTimeseries egressData_;\n  StatsWrapper::TLTimeseries egressRst_;\n  std::vector<StatsWrapper::TLTimeseries> egressRstStatus_;\n  StatsWrapper::TLTimeseries egressSettings_;\n  StatsWrapper::TLTimeseries egressPingRequest_;\n  StatsWrapper::TLTimeseries egressPingReply_;\n  StatsWrapper::TLTimeseries egressGoaway_;\n  StatsWrapper::TLTimeseries egressGoawayDrain_;\n  std::vector<StatsWrapper::TLTimeseries> egressGoawayStatus_;\n  StatsWrapper::TLTimeseries egressWindowUpdate_;\n  StatsWrapper::TLTimeseries egressPriority_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HTTPCodecStatsFilter.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/stats/HTTPCodecStatsFilter.h>\n\n#include <proxygen/lib/http/codec/HTTP2Constants.h>\n#include <proxygen/lib/http/stats/HTTPCodecStats.h>\n\nnamespace proxygen {\n\nHTTPCodecStatsFilter::HTTPCodecStatsFilter(HTTPCodecStats* counters,\n                                           CodecProtocol protocol)\n    : counters_(counters), protocol_(protocol) {\n  counters_->incrementParallelConn(1);\n}\n\nHTTPCodecStatsFilter::~HTTPCodecStatsFilter() {\n  counters_->incrementParallelConn(-1);\n}\n\nvoid HTTPCodecStatsFilter::onPushMessageBegin(StreamID stream,\n                                              StreamID assocStream,\n                                              HTTPMessage* msg) {\n  counters_->recordIngressPushPromise();\n  callback_->onPushMessageBegin(stream, assocStream, msg);\n}\n\nvoid HTTPCodecStatsFilter::onHeadersComplete(StreamID stream,\n                                             std::unique_ptr<HTTPMessage> msg) {\n  if (call_->getTransportDirection() == TransportDirection::DOWNSTREAM ||\n      msg->isRequest()) {\n    // Request or PUSH_PROMISE, recordIngressPushPromise already called\n    counters_->recordIngressSynStream();\n  } else {\n    counters_->recordIngressSynReply();\n  }\n  callback_->onHeadersComplete(stream, std::move(msg));\n}\n\nvoid HTTPCodecStatsFilter::onBody(StreamID stream,\n                                  std::unique_ptr<folly::IOBuf> chain,\n                                  uint16_t padding) {\n  counters_->recordIngressData();\n  callback_->onBody(stream, std::move(chain), padding);\n}\n\nvoid HTTPCodecStatsFilter::onAbort(StreamID stream, ErrorCode statusCode) {\n  if (stream) {\n    counters_->recordIngressRst(statusCode);\n  } else {\n    counters_->recordIngressGoaway(statusCode);\n  }\n  callback_->onAbort(stream, statusCode);\n}\n\nvoid HTTPCodecStatsFilter::onGoaway(uint64_t lastGoodStreamID,\n                                    ErrorCode statusCode,\n                                    std::unique_ptr<folly::IOBuf> debugData) {\n  counters_->recordIngressGoaway(statusCode);\n  if (lastGoodStreamID == http2::kMaxStreamID) {\n    counters_->recordIngressGoawayDrain();\n  }\n  callback_->onGoaway(lastGoodStreamID, statusCode, std::move(debugData));\n}\n\nvoid HTTPCodecStatsFilter::onPingRequest(uint64_t data) {\n  counters_->recordIngressPingRequest();\n  callback_->onPingRequest(data);\n}\n\nvoid HTTPCodecStatsFilter::onPingReply(uint64_t data) {\n  counters_->recordIngressPingReply();\n  callback_->onPingReply(data);\n}\n\nvoid HTTPCodecStatsFilter::onWindowUpdate(StreamID stream, uint32_t amount) {\n  counters_->recordIngressWindowUpdate();\n  callback_->onWindowUpdate(stream, amount);\n}\n\nvoid HTTPCodecStatsFilter::onSettings(const SettingsList& settings) {\n  counters_->recordIngressSettings();\n  callback_->onSettings(settings);\n}\n\nvoid HTTPCodecStatsFilter::onSettingsAck() {\n  counters_->recordIngressSettings();\n  callback_->onSettingsAck();\n}\n\nvoid HTTPCodecStatsFilter::onPriority(StreamID stream,\n                                      const HTTPPriority& priority) {\n  counters_->recordIngressPriority();\n  callback_->onPriority(stream, priority);\n}\n\nvoid HTTPCodecStatsFilter::generateHeader(\n    folly::IOBufQueue& writeBuf,\n    StreamID stream,\n    const HTTPMessage& msg,\n    bool eom,\n    HTTPHeaderSize* size,\n    const folly::Optional<HTTPHeaders>& extraHeaders) {\n  if (call_->getTransportDirection() == TransportDirection::UPSTREAM) {\n    counters_->recordEgressSynStream();\n  } else {\n    counters_->recordEgressSynReply();\n  }\n  return call_->generateHeader(writeBuf, stream, msg, eom, size, extraHeaders);\n}\n\nvoid HTTPCodecStatsFilter::generatePushPromise(folly::IOBufQueue& writeBuf,\n                                               StreamID stream,\n                                               const HTTPMessage& msg,\n                                               StreamID assocStream,\n                                               bool eom,\n                                               HTTPHeaderSize* size) {\n  counters_->recordEgressPushPromise();\n  return call_->generatePushPromise(\n      writeBuf, stream, msg, assocStream, eom, size);\n}\n\nsize_t HTTPCodecStatsFilter::generateBody(folly::IOBufQueue& writeBuf,\n                                          StreamID stream,\n                                          std::unique_ptr<folly::IOBuf> chain,\n                                          folly::Optional<uint8_t> padding,\n                                          bool eom) {\n  counters_->recordEgressData();\n  return call_->generateBody(writeBuf, stream, std::move(chain), padding, eom);\n}\n\nsize_t HTTPCodecStatsFilter::generateRstStream(folly::IOBufQueue& writeBuf,\n                                               StreamID stream,\n                                               ErrorCode statusCode) {\n  counters_->recordEgressRst(statusCode);\n  return call_->generateRstStream(writeBuf, stream, statusCode);\n}\n\nsize_t HTTPCodecStatsFilter::generateGoaway(\n    folly::IOBufQueue& writeBuf,\n    StreamID lastStream,\n    ErrorCode statusCode,\n    std::unique_ptr<folly::IOBuf> debugData) {\n  auto written = call_->generateGoaway(\n      writeBuf, lastStream, statusCode, std::move(debugData));\n  if (written) {\n    counters_->recordEgressGoaway(statusCode);\n    if (lastStream == HTTPCodec::MaxStreamID) {\n      counters_->recordEgressGoawayDrain();\n    }\n  }\n  return written;\n}\n\nsize_t HTTPCodecStatsFilter::generatePingRequest(\n    folly::IOBufQueue& writeBuf, folly::Optional<uint64_t> data) {\n  counters_->recordEgressPingRequest();\n  return call_->generatePingRequest(writeBuf, data);\n}\n\nsize_t HTTPCodecStatsFilter::generatePingReply(folly::IOBufQueue& writeBuf,\n                                               uint64_t data) {\n  counters_->recordEgressPingReply();\n  return call_->generatePingReply(writeBuf, data);\n}\n\nsize_t HTTPCodecStatsFilter::generateSettings(folly::IOBufQueue& buf) {\n  counters_->recordEgressSettings();\n  return call_->generateSettings(buf);\n}\n\nsize_t HTTPCodecStatsFilter::generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                                                  StreamID stream,\n                                                  uint32_t delta) {\n  counters_->recordEgressWindowUpdate();\n  return call_->generateWindowUpdate(writeBuf, stream, delta);\n}\n\nsize_t HTTPCodecStatsFilter::generatePriority(folly::IOBufQueue& writeBuf,\n                                              StreamID streamId,\n                                              HTTPPriority pri) {\n  counters_->recordEgressPriority();\n  return call_->generatePriority(writeBuf, streamId, pri);\n}\n\nsize_t HTTPCodecStatsFilter::generatePushPriority(folly::IOBufQueue& writeBuf,\n                                                  StreamID pushId,\n                                                  HTTPPriority pri) {\n  counters_->recordEgressPriority();\n  return call_->generatePushPriority(writeBuf, pushId, pri);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HTTPCodecStatsFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/CodecProtocol.h>\n#include <proxygen/lib/http/codec/HTTPCodecFilter.h>\n\nnamespace proxygen {\n\nclass HTTPCodecStats;\n\nclass HTTPCodecStatsFilter : public PassThroughHTTPCodecFilter {\n public:\n  explicit HTTPCodecStatsFilter(HTTPCodecStats* counters,\n                                CodecProtocol protocol);\n  ~HTTPCodecStatsFilter() override;\n  HTTPCodecStatsFilter(const HTTPCodecStatsFilter&) = delete;\n  HTTPCodecStatsFilter& operator=(const HTTPCodecStatsFilter&) = delete;\n  HTTPCodecStatsFilter(HTTPCodecStatsFilter&&) = delete;\n  HTTPCodecStatsFilter& operator=(HTTPCodecStatsFilter&&) = delete;\n\n  void setCounters(HTTPCodecStats* counters) {\n    counters_ = counters;\n  }\n  // ingress\n\n  bool isHTTP2() const {\n    return isHTTP2CodecProtocol(protocol_);\n  }\n\n  bool isHQ() const {\n    return isHQCodecProtocol(protocol_);\n  }\n\n  void onPushMessageBegin(StreamID stream,\n                          StreamID assocStream,\n                          HTTPMessage* msg) override;\n\n  void onHeadersComplete(StreamID stream,\n                         std::unique_ptr<HTTPMessage> msg) override;\n\n  void onBody(StreamID stream,\n              std::unique_ptr<folly::IOBuf> chain,\n              uint16_t padding) override;\n\n  void onAbort(StreamID stream, ErrorCode statusCode) override;\n\n  void onGoaway(uint64_t lastGoodStreamID,\n                ErrorCode statusCode,\n                std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  void onPingRequest(uint64_t data) override;\n\n  void onPingReply(uint64_t data) override;\n\n  void onWindowUpdate(StreamID stream, uint32_t amount) override;\n\n  void onSettings(const SettingsList& settings) override;\n\n  void onSettingsAck() override;\n\n  void onPriority(StreamID stream, const HTTPPriority& pri) override;\n\n  // egress\n\n  void generateHeader(\n      folly::IOBufQueue& writeBuf,\n      StreamID stream,\n      const HTTPMessage& msg,\n      bool eom,\n      HTTPHeaderSize* size,\n      const folly::Optional<HTTPHeaders>& extraHeaders) override;\n\n  void generatePushPromise(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           const HTTPMessage& msg,\n                           StreamID assocStream,\n                           bool eom,\n                           HTTPHeaderSize* size) override;\n\n  size_t generateBody(folly::IOBufQueue& writeBuf,\n                      StreamID stream,\n                      std::unique_ptr<folly::IOBuf> chain,\n                      folly::Optional<uint8_t> padding,\n                      bool eom) override;\n\n  size_t generateRstStream(folly::IOBufQueue& writeBuf,\n                           StreamID stream,\n                           ErrorCode statusCode) override;\n\n  size_t generateGoaway(\n      folly::IOBufQueue& writeBuf,\n      StreamID lastStream,\n      ErrorCode statusCode,\n      std::unique_ptr<folly::IOBuf> debugData = nullptr) override;\n\n  size_t generatePingRequest(\n      folly::IOBufQueue& writeBuf,\n      folly::Optional<uint64_t> data = folly::none) override;\n\n  size_t generatePingReply(folly::IOBufQueue& writeBuf, uint64_t data) override;\n\n  size_t generateSettings(folly::IOBufQueue& writeBuf) override;\n\n  size_t generateWindowUpdate(folly::IOBufQueue& writeBuf,\n                              StreamID stream,\n                              uint32_t delta) override;\n\n  size_t generatePriority(folly::IOBufQueue& writeBuf,\n                          StreamID streamId,\n                          HTTPPriority pri) override;\n  size_t generatePushPriority(folly::IOBufQueue& writeBuf,\n                              StreamID pushId,\n                              HTTPPriority pri) override;\n\n private:\n  HTTPCodecStats* counters_;\n  CodecProtocol protocol_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HeaderCodecStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/stats/HeaderCodecStats.h>\n\nusing facebook::fb303::AVG;\nusing facebook::fb303::SUM;\nusing std::string;\n\nnamespace {\n\nstd::array<const char*, 3> kCompressionTypes = {\"gzip\", \"hpack\", \"qpack\"};\n\n}\n\nnamespace proxygen {\n\nTLHeaderCodecStats::TLHeaderCodecStats(const string& prefix) {\n  encodeCompr_.reserve(kCompressionTypes.size());\n  encodeUncompr_.reserve(kCompressionTypes.size());\n  decodeCompr_.reserve(kCompressionTypes.size());\n  decodeUncompr_.reserve(kCompressionTypes.size());\n  encodes_.reserve(kCompressionTypes.size());\n  decodes_.reserve(kCompressionTypes.size());\n  decodeErrors_.reserve(kCompressionTypes.size());\n  decodeTooLarge_.reserve(kCompressionTypes.size());\n  for (auto comprType : kCompressionTypes) {\n    encodeCompr_.emplace_back(std::make_unique<StatsWrapper::TLHistogram>(\n        prefix + \"_\" + comprType + \"_encode_compr\",\n        100,\n        0,\n        10000,\n        SUM,\n        AVG,\n        10,\n        50,\n        90));\n    encodeUncompr_.emplace_back(std::make_unique<StatsWrapper::TLHistogram>(\n        prefix + \"_\" + comprType + \"_encode_uncompr\",\n        100,\n        0,\n        10000,\n        SUM,\n        AVG,\n        10,\n        50,\n        90));\n    decodeCompr_.emplace_back(std::make_unique<StatsWrapper::TLHistogram>(\n        prefix + \"_\" + comprType + \"_decode_compr\",\n        100,\n        0,\n        10000,\n        SUM,\n        AVG,\n        10,\n        50,\n        90));\n    decodeUncompr_.emplace_back(std::make_unique<StatsWrapper::TLHistogram>(\n        prefix + \"_\" + comprType + \"_decode_uncompr\",\n        100,\n        0,\n        10000,\n        SUM,\n        AVG,\n        10,\n        50,\n        90));\n    encodes_.emplace_back(prefix + \"_\" + comprType + \"_encodes\", SUM);\n    decodes_.emplace_back(prefix + \"_\" + comprType + \"_decodes\", SUM);\n    decodeErrors_.emplace_back(prefix + \"_\" + comprType + \"_decode_errors\",\n                               SUM);\n    decodeTooLarge_.emplace_back(prefix + \"_\" + comprType + \"_decode_too_large\",\n                                 SUM);\n  }\n}\n\nvoid TLHeaderCodecStats::recordEncode(HeaderCodec::Type type,\n                                      HTTPHeaderSize& size) {\n  auto i = (uint32_t)type;\n  CHECK(i < encodes_.size());\n  encodes_[i].add(1);\n  CHECK(i < encodeCompr_.size());\n  encodeCompr_[i]->add(size.compressed);\n  CHECK(i < encodeUncompr_.size());\n  encodeUncompr_[i]->add(size.uncompressed);\n}\n\nvoid TLHeaderCodecStats::recordDecode(HeaderCodec::Type type,\n                                      HTTPHeaderSize& size) {\n  auto i = (uint32_t)type;\n  CHECK(i < decodes_.size());\n  decodes_[i].add(1);\n  CHECK(i < decodeCompr_.size());\n  decodeCompr_[i]->add(size.compressed);\n  CHECK(i < decodeUncompr_.size());\n  decodeUncompr_[i]->add(size.uncompressed);\n}\n\nvoid TLHeaderCodecStats::recordDecodeError(HeaderCodec::Type type) {\n  auto i = (uint32_t)type;\n  CHECK(i < decodeErrors_.size());\n  decodeErrors_[i].add(1);\n}\n\nvoid TLHeaderCodecStats::recordDecodeTooLarge(HeaderCodec::Type type) {\n  auto i = (uint32_t)type;\n  CHECK(i < decodeTooLarge_.size());\n  decodeTooLarge_[i].add(1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HeaderCodecStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/compress/HeaderCodec.h>\n#include <proxygen/lib/stats/StatsWrapper.h>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n\nclass TLHeaderCodecStats : public HeaderCodec::Stats {\n public:\n  explicit TLHeaderCodecStats(const std::string& prefix);\n  ~TLHeaderCodecStats() override = default;\n  TLHeaderCodecStats(const TLHeaderCodecStats&) = delete;\n  TLHeaderCodecStats& operator=(const TLHeaderCodecStats&) = delete;\n\n  void recordEncode(HeaderCodec::Type type, HTTPHeaderSize& size) override;\n  void recordDecode(HeaderCodec::Type type, HTTPHeaderSize& size) override;\n  void recordDecodeError(HeaderCodec::Type type) override;\n  void recordDecodeTooLarge(HeaderCodec::Type type) override;\n\n private:\n  std::vector<std::unique_ptr<StatsWrapper::TLHistogram>> encodeCompr_;\n  std::vector<std::unique_ptr<StatsWrapper::TLHistogram>> encodeUncompr_;\n  std::vector<std::unique_ptr<StatsWrapper::TLHistogram>> decodeCompr_;\n  std::vector<std::unique_ptr<StatsWrapper::TLHistogram>> decodeUncompr_;\n  std::vector<StatsWrapper::TLTimeseries> encodes_;\n  std::vector<StatsWrapper::TLTimeseries> decodes_;\n  std::vector<StatsWrapper::TLTimeseries> decodeErrors_;\n  std::vector<StatsWrapper::TLTimeseries> decodeTooLarge_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/HttpServerStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <cstddef>\n\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n\nnamespace proxygen {\n\nclass HTTPMessage;\n\nclass HttpServerStatsIf {\n public:\n  virtual ~HttpServerStatsIf() = default;\n  virtual void recordRequest(const HTTPMessage& msg) = 0;\n  virtual void recordResponse(const HTTPMessage& msg) = 0;\n  virtual void recordAbort() = 0;\n  virtual void recordRequestComplete(std::chrono::milliseconds latency,\n                                     ProxygenError err,\n                                     size_t requestBodyBytes,\n                                     size_t responseBodyBytes) = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ResponseCodeStatsMinute.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/stats/ResponseCodeStatsMinute.h>\n\nusing facebook::fb303::COUNT;\n\nnamespace proxygen {\n\nResponseCodeStatsMinute::ResponseCodeStatsMinute(const std::string& name)\n    : statusOther(name + \"other\", COUNT),\n      status1xx(name + \"1xx\", COUNT),\n      status2xx(name + \"2xx\", COUNT),\n      status3xx(name + \"3xx\", COUNT),\n      status4xx(name + \"4xx\", COUNT),\n      status5xx(name + \"5xx\", COUNT) {\n}\n\nvoid ResponseCodeStatsMinute::addStatus(int status) {\n  if (status < 100) {\n    statusOther.add(1);\n  } else if (status < 200) {\n    status1xx.add(1);\n  } else if (status < 300) {\n    status2xx.add(1);\n  } else if (status < 400) {\n    status3xx.add(1);\n  } else if (status < 500) {\n    status4xx.add(1);\n  } else if (status < 600) {\n    status5xx.add(1);\n  } else {\n    statusOther.add(1);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ResponseCodeStatsMinute.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/stats/StatsWrapper.h>\n\nnamespace proxygen {\n\nstruct ResponseCodeStatsMinute {\n  explicit ResponseCodeStatsMinute(const std::string& name);\n\n  void addStatus(int status);\n\n  StatsWrapper::TLTimeseriesMinute statusOther;\n  StatsWrapper::TLTimeseriesMinute status1xx;\n  StatsWrapper::TLTimeseriesMinute status2xx;\n  StatsWrapper::TLTimeseriesMinute status3xx;\n  StatsWrapper::TLTimeseriesMinute status4xx;\n  StatsWrapper::TLTimeseriesMinute status5xx;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/TLResponseCodeStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/stats/TLResponseCodeStats.h\"\n\nusing facebook::fb303::SUM;\n\nnamespace proxygen {\n\nTLResponseCodeStats::TLResponseCodeStats(const std::string& name,\n                                         uint8_t verbosity)\n    : statusNone(name + \"nostatus\", SUM),\n      statusOther(name + \"other\", SUM),\n      status1xx(name + \"1xx\", SUM),\n      status2xx(name + \"2xx\", SUM),\n      status3xx(name + \"3xx\", SUM),\n      status4xx(name + \"4xx\", SUM),\n      status5xx(name + \"5xx\", SUM) {\n  if (verbosity > 8) {\n    status39x.emplace(name + \"39x\", SUM);\n    status200.emplace(name + \"200\", SUM);\n    status206.emplace(name + \"206\", SUM);\n    status301.emplace(name + \"301\", SUM);\n    status302.emplace(name + \"302\", SUM);\n    status303.emplace(name + \"303\", SUM);\n    status304.emplace(name + \"304\", SUM);\n    status307.emplace(name + \"307\", SUM);\n    status395.emplace(name + \"395\", SUM);\n    status396.emplace(name + \"396\", SUM);\n    status397.emplace(name + \"397\", SUM);\n    status398.emplace(name + \"398\", SUM);\n    status399.emplace(name + \"399\", SUM);\n    status400.emplace(name + \"400\", SUM);\n    status401.emplace(name + \"401\", SUM);\n    status403.emplace(name + \"403\", SUM);\n    status404.emplace(name + \"404\", SUM);\n    status408.emplace(name + \"408\", SUM);\n    status429.emplace(name + \"429\", SUM);\n    status500.emplace(name + \"500\", SUM);\n    status501.emplace(name + \"501\", SUM);\n    status502.emplace(name + \"502\", SUM);\n    status503.emplace(name + \"503\", SUM);\n    status504.emplace(name + \"504\", SUM);\n  }\n}\n\nvoid TLResponseCodeStats::addStatus(int status) {\n  switch (status) {\n    case 200:\n      status2xx.add(1);\n      BaseStats::addToOptionalStat(status200, 1);\n      return;\n    case 404:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status404, 1);\n      return;\n    case 206:\n      status2xx.add(1);\n      BaseStats::addToOptionalStat(status206, 1);\n      return;\n    case 301:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status301, 1);\n      return;\n    case 302:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status302, 1);\n      return;\n    case 303:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status303, 1);\n      return;\n    case 304:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status304, 1);\n      return;\n    case 307:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status307, 1);\n      return;\n    case 390:\n    case 391:\n    case 392:\n    case 393:\n    case 394:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      return;\n    case 395:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      BaseStats::addToOptionalStat(status395, 1);\n      return;\n    case 396:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      BaseStats::addToOptionalStat(status396, 1);\n      return;\n    case 397:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      BaseStats::addToOptionalStat(status397, 1);\n      return;\n    case 398:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      BaseStats::addToOptionalStat(status398, 1);\n      return;\n    case 399:\n      status3xx.add(1);\n      BaseStats::addToOptionalStat(status39x, 1);\n      BaseStats::addToOptionalStat(status399, 1);\n      return;\n    case 400:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status400, 1);\n      return;\n    case 401:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status401, 1);\n      return;\n    case 403:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status403, 1);\n      return;\n    case 408:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status408, 1);\n      return;\n    case 429:\n      status4xx.add(1);\n      BaseStats::addToOptionalStat(status429, 1);\n      return;\n    case 500:\n      status5xx.add(1);\n      BaseStats::addToOptionalStat(status500, 1);\n      return;\n    case 501:\n      status5xx.add(1);\n      BaseStats::addToOptionalStat(status501, 1);\n      return;\n    case 502:\n      status5xx.add(1);\n      BaseStats::addToOptionalStat(status502, 1);\n      return;\n    case 503:\n      status5xx.add(1);\n      BaseStats::addToOptionalStat(status503, 1);\n      return;\n    case 504:\n      status5xx.add(1);\n      BaseStats::addToOptionalStat(status504, 1);\n      return;\n    case 555:\n      // 555 is returned on healthcheck failures. Skip counting it in exported\n      // stats.\n      return;\n    default:\n      break;\n  }\n\n  if (status < 0) {\n    statusNone.add(1);\n  } else if (status < 100) {\n    statusOther.add(1);\n  } else if (status < 200) {\n    status1xx.add(1);\n  } else if (status < 300) {\n    status2xx.add(1);\n  } else if (status < 400) {\n    status3xx.add(1);\n  } else if (status < 500) {\n    status4xx.add(1);\n  } else if (status < 600) {\n    status5xx.add(1);\n  } else {\n    statusOther.add(1);\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/TLResponseCodeStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/stats/StatsWrapper.h>\n\nnamespace proxygen {\n\nstruct TLResponseCodeStats {\n  explicit TLResponseCodeStats(const std::string& name, uint8_t verbosity);\n\n  void addStatus(int status);\n\n  StatsWrapper::TLTimeseries statusNone;\n  StatsWrapper::TLTimeseries statusOther;\n  StatsWrapper::TLTimeseries status1xx;\n  StatsWrapper::TLTimeseries status2xx;\n  StatsWrapper::TLTimeseries status3xx;\n  StatsWrapper::TLTimeseries status4xx;\n  StatsWrapper::TLTimeseries status5xx;\n\n  // TODO: all the counters below are marked for deprecation.\n\n  std::optional<StatsWrapper::TLTimeseries> status39x;\n\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status200;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status206;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status301;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status302;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status303;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status304;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status307;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status395;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status396;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status397;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status398;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status399;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status400;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status401;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status403;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status404;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status408;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status429;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status500;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status501;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status502;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status503;\n  std::optional<StatsWrapper::TLTimeseriesMinuteAndAllTime> status504;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ThreadLocalHTTPSessionStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/stats/ThreadLocalHTTPSessionStats.h>\n\nnamespace proxygen {\n\nTLHTTPSessionStats::TLHTTPSessionStats(const std::string& prefix)\n    : txnsOpen(prefix + \"_transactions_open\"),\n      pendingBufferedReadBytes(prefix + \"_pending_buffered_read_bytes\"),\n      pendingBufferedWriteBytes(prefix + \"_pending_buffered_write_bytes\"),\n      txnsOpened(prefix + \"_txn_opened\", facebook::fb303::SUM),\n      txnsFromSessionReuse(prefix + \"_txn_session_reuse\", facebook::fb303::SUM),\n      txnsTransactionStalled(prefix + \"_txn_transaction_stall\",\n                             facebook::fb303::SUM),\n      txnsSessionStalled(prefix + \"_txn_session_stall\", facebook::fb303::SUM),\n      egressContentLengthMismatches(\n          prefix + \"_egress_content_length_mismatches\", facebook::fb303::SUM),\n      sessionPeriodicPingProbeTimeout(\n          prefix + \"_session_periodic_ping_probe_timeout\",\n          facebook::fb303::SUM),\n      presendIoSplit(prefix + \"_presend_io_split\", facebook::fb303::SUM),\n      presendExceedLimit(prefix + \"_presend_exceed_limit\",\n                         facebook::fb303::SUM),\n      ttlbaTracked(prefix + \"_ttlba_tracked\", facebook::fb303::SUM),\n      ttlbaReceived(prefix + \"_ttlba_received\", facebook::fb303::SUM),\n      ttlbaTimeout(prefix + \"_ttlba_timeout\", facebook::fb303::SUM),\n      ttlbaNotFound(prefix + \"_ttlba_not_found\", facebook::fb303::SUM),\n      ttlbaExceedLimit(prefix + \"_ttlba_exceed_limit\", facebook::fb303::SUM),\n      ttbtxTracked(prefix + \"_ttbtx_tracked\", facebook::fb303::SUM),\n      ttbtxReceived(prefix + \"_ttbtx_received\", facebook::fb303::SUM),\n      ttbtxTimeout(prefix + \"_ttbtx_timeout\", facebook::fb303::SUM),\n      ttbtxNotFound(prefix + \"_ttbtx_not_found\", facebook::fb303::SUM),\n      ttbtxExceedLimit(prefix + \"_ttbtx_exceed_limit\", facebook::fb303::SUM),\n      ctrlMsgsRateLimited(prefix + \"_ctrl_msgs_rate_limited\",\n                          facebook::fb303::SUM),\n      headersRateLimited(prefix + \"_headers_rate_limited\",\n                         facebook::fb303::SUM),\n      resetsRateLimited(prefix + \"_resets_rate_limited\", facebook::fb303::SUM),\n      txnsPerSession(prefix + \"_txn_per_session\",\n                     1,\n                     0,\n                     999,\n                     facebook::fb303::AVG,\n                     50,\n                     95,\n                     99),\n      sessionIdleTime(prefix + \"_session_idle_time\",\n                      1,\n                      0,\n                      150,\n                      facebook::fb303::AVG,\n                      50,\n                      75,\n                      95,\n                      99,\n                      100),\n      ctrlMsgsInInterval(\n          prefix + \"_ctrl_msgs_in_interval\",\n          1 /* bucketWidth */,\n          0 /* min */,\n          500 /* max, keep in sync with kDefaultMaxControlMsgsPerInterval */,\n          facebook::fb303::AVG,\n          50,\n          99,\n          100),\n      headersInInterval(\n          prefix + \"_headers_in_interval\",\n          1 /* bucketWidth */,\n          0 /* min */,\n          500 /* max, keep in sync with kDefaultMaxHeadersMsgsPerInterval */,\n          facebook::fb303::AVG,\n          50,\n          99,\n          100),\n      resetsInInterval(prefix + \"_resets_in_interval\",\n                       1 /* bucketWidth */,\n                       0 /* min */,\n                       200 /* max, keep in sync with\n                              RestsRateLimitFilter::kDefaultMaxEventsPerInterval\n                            */\n                       ,\n                       facebook::fb303::AVG,\n                       50,\n                       99,\n                       100),\n      readsPerLoopExceeded(prefix + \"_reads_per_loop_exceeded_limit\",\n                           facebook::fb303::SUM) {\n}\n\nvoid TLHTTPSessionStats::recordTransactionOpened() noexcept {\n  txnsOpen.incrementValue(1);\n  txnsOpened.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTransactionClosed() noexcept {\n  txnsOpen.incrementValue(-1);\n}\n\nvoid TLHTTPSessionStats::recordSessionReused() noexcept {\n  txnsFromSessionReuse.add(1);\n}\n\nvoid TLHTTPSessionStats::recordPresendIOSplit() noexcept {\n  presendIoSplit.add(1);\n}\n\nvoid TLHTTPSessionStats::recordPresendExceedLimit() noexcept {\n  presendExceedLimit.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTLBAExceedLimit() noexcept {\n  ttlbaExceedLimit.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTLBANotFound() noexcept {\n  ttlbaNotFound.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTLBAReceived() noexcept {\n  ttlbaReceived.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTLBATimeout() noexcept {\n  ttlbaTimeout.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTLBATracked() noexcept {\n  ttlbaTracked.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTBTXExceedLimit() noexcept {\n  ttbtxExceedLimit.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTBTXReceived() noexcept {\n  ttbtxReceived.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTBTXTimeout() noexcept {\n  ttbtxTimeout.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTBTXNotFound() noexcept {\n  ttbtxNotFound.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTTBTXTracked() noexcept {\n  ttbtxTracked.add(1);\n}\n\nvoid TLHTTPSessionStats::recordTransactionsServed(uint64_t num) noexcept {\n  txnsPerSession.add(num);\n}\n\nvoid TLHTTPSessionStats::recordSessionIdleTime(\n    std::chrono::seconds idleTime) noexcept {\n  sessionIdleTime.add(idleTime.count());\n}\n\nvoid TLHTTPSessionStats::recordTransactionStalled() noexcept {\n  txnsTransactionStalled.add(1);\n}\n\nvoid TLHTTPSessionStats::recordSessionStalled() noexcept {\n  txnsSessionStalled.add(1);\n}\n\nvoid TLHTTPSessionStats::recordEgressContentLengthMismatches() noexcept {\n  egressContentLengthMismatches.add(1);\n}\n\nvoid TLHTTPSessionStats::recordSessionPeriodicPingProbeTimeout() noexcept {\n  sessionPeriodicPingProbeTimeout.add(1);\n}\n\nvoid TLHTTPSessionStats::recordPendingBufferedReadBytes(\n    int64_t amount) noexcept {\n  pendingBufferedReadBytes.incrementValue(amount);\n}\n\nvoid TLHTTPSessionStats::recordPendingBufferedWriteBytes(\n    int64_t amount) noexcept {\n  pendingBufferedWriteBytes.incrementValue(amount);\n}\n\nvoid TLHTTPSessionStats::recordControlMsgsInInterval(\n    int64_t quantity) noexcept {\n  ctrlMsgsInInterval.add(quantity);\n}\n\nvoid TLHTTPSessionStats::recordControlMsgRateLimited() noexcept {\n  ctrlMsgsRateLimited.add(1);\n}\n\nvoid TLHTTPSessionStats::recordHeadersInInterval(int64_t quantity) noexcept {\n  headersInInterval.add(quantity);\n}\n\nvoid TLHTTPSessionStats::recordHeadersRateLimited() noexcept {\n  headersRateLimited.add(1);\n}\n\nvoid TLHTTPSessionStats::recordResetsInInterval(int64_t quantity) noexcept {\n  resetsInInterval.add(quantity);\n}\n\nvoid TLHTTPSessionStats::recordResetsRateLimited() noexcept {\n  resetsRateLimited.add(1);\n}\n\nvoid TLHTTPSessionStats::recordReadPerLoopLimitExceeded() noexcept {\n  readsPerLoopExceeded.add(1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/stats/ThreadLocalHTTPSessionStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/session/HTTPSessionStats.h>\n#include <proxygen/lib/stats/StatsWrapper.h>\n#include <string>\n\nnamespace proxygen {\n\nclass TLHTTPSessionStats : public HTTPSessionStats {\n public:\n  explicit TLHTTPSessionStats(const std::string& prefix);\n\n  void recordTransactionOpened() noexcept override;\n  void recordTransactionClosed() noexcept override;\n  void recordPresendIOSplit() noexcept override;\n  void recordPresendExceedLimit() noexcept override;\n  void recordTTLBAExceedLimit() noexcept override;\n  void recordTTLBANotFound() noexcept override;\n  void recordTTLBAReceived() noexcept override;\n  void recordTTLBATimeout() noexcept override;\n  void recordTTLBATracked() noexcept override;\n  void recordTTBTXReceived() noexcept override;\n  void recordTTBTXTimeout() noexcept override;\n  void recordTTBTXNotFound() noexcept override;\n  void recordTTBTXTracked() noexcept override;\n  void recordTTBTXExceedLimit() noexcept override;\n  void recordTransactionsServed(uint64_t) noexcept override;\n  void recordSessionReused() noexcept override;\n  void recordSessionIdleTime(std::chrono::seconds) noexcept override;\n  void recordTransactionStalled() noexcept override;\n  void recordSessionStalled() noexcept override;\n  void recordPendingBufferedReadBytes(int64_t amount) noexcept override;\n  void recordPendingBufferedWriteBytes(int64_t amount) noexcept override;\n  void recordEgressContentLengthMismatches() noexcept override;\n  void recordSessionPeriodicPingProbeTimeout() noexcept override;\n\n  void recordControlMsgsInInterval(int64_t quantity) noexcept override;\n  void recordControlMsgRateLimited() noexcept override;\n  void recordHeadersInInterval(int64_t quantity) noexcept override;\n  void recordHeadersRateLimited() noexcept override;\n  void recordResetsInInterval(int64_t quantity) noexcept override;\n  void recordResetsRateLimited() noexcept override;\n  void recordReadPerLoopLimitExceeded() noexcept override;\n\n  StatsWrapper::TLCounter txnsOpen;\n  StatsWrapper::TLCounter pendingBufferedReadBytes;\n  StatsWrapper::TLCounter pendingBufferedWriteBytes;\n  StatsWrapper::TLTimeseries txnsOpened;\n  StatsWrapper::TLTimeseries txnsFromSessionReuse;\n  StatsWrapper::TLTimeseries txnsTransactionStalled;\n  StatsWrapper::TLTimeseries txnsSessionStalled;\n  StatsWrapper::TLTimeseries egressContentLengthMismatches;\n  StatsWrapper::TLTimeseries sessionPeriodicPingProbeTimeout;\n  // Time to Last Byte Ack (TTLBA)\n  StatsWrapper::TLTimeseries presendIoSplit;\n  StatsWrapper::TLTimeseries presendExceedLimit;\n  StatsWrapper::TLTimeseries ttlbaTracked;\n  StatsWrapper::TLTimeseries ttlbaReceived;\n  StatsWrapper::TLTimeseries ttlbaTimeout;\n  StatsWrapper::TLTimeseries ttlbaNotFound;\n  StatsWrapper::TLTimeseries ttlbaExceedLimit;\n  StatsWrapper::TLTimeseries ttbtxTracked;\n  StatsWrapper::TLTimeseries ttbtxReceived;\n  StatsWrapper::TLTimeseries ttbtxTimeout;\n  StatsWrapper::TLTimeseries ttbtxNotFound;\n  StatsWrapper::TLTimeseries ttbtxExceedLimit;\n  StatsWrapper::TLTimeseries ctrlMsgsRateLimited;\n  StatsWrapper::TLTimeseries headersRateLimited;\n  StatsWrapper::TLTimeseries resetsRateLimited;\n  StatsWrapper::TLHistogram txnsPerSession;\n  StatsWrapper::TLHistogram sessionIdleTime;\n  StatsWrapper::TLHistogram ctrlMsgsInInterval;\n  StatsWrapper::TLHistogram headersInInterval;\n  StatsWrapper::TLHistogram resetsInInterval;\n  StatsWrapper::TLTimeseries readsPerLoopExceeded;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_structuredheaders_structured_headers\n  SRCS\n    StructuredHeadersBuffer.cpp\n  DEPS\n    Folly::folly_base64\n    Folly::folly_conv\n    Folly::folly_try\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_http_structuredheaders_utils\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_http_structuredheaders_encoder\n  SRCS\n    StructuredHeadersEncoder.cpp\n  DEPS\n    Folly::folly_base64\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_http_structuredheaders_utils\n)\n\nproxygen_add_library(proxygen_http_structuredheaders_decoder\n  SRCS\n    StructuredHeadersDecoder.cpp\n  EXPORTED_DEPS\n    proxygen_http_structuredheaders_structured_headers\n)\n\nproxygen_add_library(proxygen_http_structuredheaders_utils\n  SRCS\n    StructuredHeadersConstants.cpp\n    StructuredHeadersUtilities.cpp\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersBuffer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersBuffer.h>\n\n#include <cctype>\n#include <folly/Conv.h>\n#include <folly/Try.h>\n#include <folly/base64.h>\n#include <glog/logging.h>\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.h>\n\nnamespace proxygen {\n\nusing namespace StructuredHeaders;\n\nDecodeError StructuredHeadersBuffer::parseItem(StructuredHeaderItem& result) {\n  removeOptionalWhitespace();\n\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  } else {\n    char firstCharacter = peek();\n    if (firstCharacter == '\"') {\n      return parseString(result);\n    } else if (firstCharacter == '*') {\n      return parseBinaryContent(result);\n    } else if (std::isdigit(firstCharacter) || firstCharacter == '-') {\n      return parseNumber(result);\n    } else if (firstCharacter == '?') {\n      return parseBoolean(result);\n    } else {\n      return handleDecodeError(DecodeError::INVALID_CHARACTER);\n    }\n  }\n}\n\nDecodeError StructuredHeadersBuffer::parseNumber(StructuredHeaderItem& result) {\n  auto type = StructuredHeaderItem::Type::INT64;\n\n  bool positive = true;\n  std::string input;\n\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  if (peek() == '-') {\n    advanceCursor();\n    positive = false;\n    input.push_back('-');\n  }\n\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  if (!std::isdigit(peek())) {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  }\n\n  while (!isEmpty()) {\n    char current = peek();\n    if (std::isdigit(current)) {\n      input.push_back(current);\n      advanceCursor();\n    } else if (type == StructuredHeaderItem::Type::INT64 && current == '.') {\n      type = StructuredHeaderItem::Type::DOUBLE;\n      input.push_back(current);\n      advanceCursor();\n    } else {\n      break;\n    }\n\n    int numDigits = input.length() - (positive ? 0 : 1);\n    if (type == StructuredHeaderItem::Type::INT64 &&\n        numDigits > StructuredHeaders::kMaxValidIntegerLength) {\n      return handleDecodeError(DecodeError::VALUE_TOO_LONG);\n    } else if (type == StructuredHeaderItem::Type::DOUBLE &&\n               numDigits > StructuredHeaders::kMaxValidFloatLength) {\n      return handleDecodeError(DecodeError::VALUE_TOO_LONG);\n    }\n  }\n\n  if (type == StructuredHeaderItem::Type::INT64) {\n    return parseInteger(input, result);\n  } else if (input.back() == '.') {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  } else {\n    return parseFloat(input, result);\n  }\n}\n\nDecodeError StructuredHeadersBuffer::parseBoolean(\n    StructuredHeaderItem& result) {\n  if (removeSymbol(\"?\", true) != DecodeError::OK) {\n    CHECK(false) << \"Only invoked after peeking a '?'\";\n  }\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  auto ch = peek();\n  if (ch != '0' && ch != '1') {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  }\n\n  result.tag = StructuredHeaderItem::Type::BOOLEAN;\n  result.value = static_cast<bool>(ch - '0');\n  advanceCursor();\n  if (!isEmpty()) {\n    return handleDecodeError(DecodeError::VALUE_TOO_LONG);\n  }\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::parseBinaryContent(\n    StructuredHeaderItem& result) {\n  std::string outputString;\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  if (peek() != '*') {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  }\n\n  advanceCursor();\n\n  while (!isEmpty()) {\n    char current = peek();\n    advanceCursor();\n    if (current == '*') {\n      if (!isValidEncodedBinaryContent(outputString)) {\n        return handleDecodeError(DecodeError::UNDECODEABLE_BINARY_CONTENT);\n      }\n\n      auto decodedContent = folly::makeTryWith(\n          [&outputString] { return folly::base64Decode(outputString); });\n      if (decodedContent.hasException()) {\n        return handleDecodeError(DecodeError::UNDECODEABLE_BINARY_CONTENT);\n      }\n\n      result.value = std::move(*decodedContent);\n      result.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n      return DecodeError::OK;\n    } else if (!isValidEncodedBinaryContentChar(current)) {\n      return handleDecodeError(DecodeError::INVALID_CHARACTER);\n    } else {\n      outputString.push_back(current);\n    }\n  }\n\n  return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n}\n\nDecodeError StructuredHeadersBuffer::parseIdentifier(\n    StructuredHeaderItem& result) {\n  std::string outputString;\n\n  auto err = parseIdentifier(outputString);\n  if (err != DecodeError::OK) {\n    return err;\n  }\n\n  result.value = outputString;\n  result.tag = StructuredHeaderItem::Type::IDENTIFIER;\n\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::parseIdentifier(std::string& result) {\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  if (!isLcAlpha(peek())) {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  }\n\n  while (!isEmpty()) {\n    char current = peek();\n    if (!isValidIdentifierChar(current)) {\n      break;\n    } else {\n      advanceCursor();\n      result.push_back(current);\n    }\n  }\n\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::parseInteger(\n    const std::string& input, StructuredHeaderItem& result) {\n  auto maybeInt = folly::tryTo<int64_t>(input);\n  if (maybeInt.hasError()) {\n    return handleDecodeError(DecodeError::UNPARSEABLE_NUMERIC_TYPE);\n  }\n  result.value = maybeInt.value();\n  result.tag = StructuredHeaderItem::Type::INT64;\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::parseFloat(const std::string& input,\n                                                StructuredHeaderItem& result) {\n  auto maybeDouble = folly::tryTo<double>(input);\n  if (maybeDouble.hasError()) {\n    return handleDecodeError(DecodeError::UNPARSEABLE_NUMERIC_TYPE);\n  }\n  result.value = maybeDouble.value();\n  result.tag = StructuredHeaderItem::Type::DOUBLE;\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::parseString(StructuredHeaderItem& result) {\n  std::string outputString;\n\n  if (isEmpty()) {\n    return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n  }\n\n  if (peek() != '\"') {\n    return handleDecodeError(DecodeError::INVALID_CHARACTER);\n  }\n\n  advanceCursor();\n\n  while (!isEmpty()) {\n    char current = peek();\n    if (current == '\\\\') {\n      advanceCursor();\n      if (isEmpty()) {\n        return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n      } else {\n        char nextChar = peek();\n        advanceCursor();\n        if (nextChar != '\"' && nextChar != '\\\\') {\n          return handleDecodeError(DecodeError::INVALID_CHARACTER);\n        }\n        outputString.push_back(nextChar);\n      }\n    } else if (current == '\"') {\n      advanceCursor();\n      result.value = outputString;\n      result.tag = StructuredHeaderItem::Type::STRING;\n      return DecodeError::OK;\n    } else if (!isValidStringChar(current)) {\n      return handleDecodeError(DecodeError::INVALID_CHARACTER);\n    } else {\n      advanceCursor();\n      outputString.push_back(current);\n    }\n  }\n\n  return handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n}\n\nDecodeError StructuredHeadersBuffer::removeOptionalWhitespace() {\n  while (!isEmpty() && (peek() == ' ' || peek() == '\\t')) {\n    advanceCursor();\n  }\n  return DecodeError::OK;\n}\n\nDecodeError StructuredHeadersBuffer::removeSymbol(const std::string& symbol,\n                                                  bool strict) {\n  if (content_.startsWith(symbol)) {\n    content_.advance(symbol.length());\n    return DecodeError::OK;\n  } else {\n    if (strict) {\n      // Do some error logging\n      return handleDecodeError(DecodeError::INVALID_CHARACTER);\n    }\n    return DecodeError::INVALID_CHARACTER;\n  }\n}\n\nDecodeError StructuredHeadersBuffer::handleDecodeError(const DecodeError& err) {\n  LOG_EVERY_N(ERROR, 1000)\n      << \"Error message: \" << decodeErrToString(err)\n      << \". Number of characters parsed before error:\" << getNumCharsParsed()\n      << \". Header Content:\" << originalContent_.str();\n  return err;\n}\n\nchar StructuredHeadersBuffer::peek() {\n  return *content_.begin();\n}\n\nvoid StructuredHeadersBuffer::advanceCursor() {\n  content_.advance(1);\n}\n\nbool StructuredHeadersBuffer::isEmpty() {\n  return content_.begin() == content_.end();\n}\n\nint32_t StructuredHeadersBuffer::getNumCharsParsed() {\n  return std::distance(originalContent_.begin(), content_.begin());\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersBuffer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Range.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <string>\n\nnamespace proxygen {\n\nusing namespace StructuredHeaders;\n\nclass StructuredHeadersBuffer {\n public:\n  explicit StructuredHeadersBuffer(const std::string& s)\n      : content_(s), originalContent_(s) {\n  }\n\n  explicit StructuredHeadersBuffer(folly::StringPiece s)\n      : content_(s), originalContent_(s) {\n  }\n\n  /*\n   * helper functions used to extract various lower-level items from a sequence\n   * of bytes. These will be called from higher level functions which parse\n   * dictionaries, lists, and other data structures.\n   */\n\n  StructuredHeaders::DecodeError parseIdentifier(StructuredHeaderItem& result);\n\n  StructuredHeaders::DecodeError parseIdentifier(std::string& result);\n\n  StructuredHeaders::DecodeError parseItem(StructuredHeaderItem& result);\n\n  DecodeError removeSymbol(const std::string& symbol, bool strict);\n\n  DecodeError removeOptionalWhitespace();\n\n  bool isEmpty();\n\n  DecodeError handleDecodeError(const DecodeError& err);\n\n private:\n  DecodeError parseBinaryContent(StructuredHeaderItem& result);\n\n  DecodeError parseNumber(StructuredHeaderItem& result);\n\n  DecodeError parseBoolean(StructuredHeaderItem& result);\n\n  DecodeError parseString(StructuredHeaderItem& result);\n\n  DecodeError parseInteger(const std::string& input,\n                           StructuredHeaderItem& result);\n\n  DecodeError parseFloat(const std::string& input,\n                         StructuredHeaderItem& result);\n\n  char peek();\n\n  void advanceCursor();\n\n  int32_t getNumCharsParsed();\n\n  folly::StringPiece content_;\n  folly::StringPiece originalContent_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersConstants.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <string_view>\n\nnamespace proxygen::StructuredHeaders {\n\nstd::string_view decodeErrToString(DecodeError err) {\n  switch (err) {\n    case DecodeError::OK: {\n      return \"No error\";\n    }\n    case DecodeError::VALUE_TOO_LONG: {\n      return \"Numeric value is too long\";\n    }\n    case DecodeError::INVALID_CHARACTER: {\n      return \"Invalid character\";\n    }\n    case DecodeError::UNDECODEABLE_BINARY_CONTENT: {\n      return \"Undecodable binary content\";\n    }\n    case DecodeError::UNEXPECTED_END_OF_BUFFER: {\n      return \"Unexpected end of buffer\";\n    }\n    case DecodeError::UNPARSEABLE_NUMERIC_TYPE: {\n      return \"Unparseable numeric type\";\n    }\n    case DecodeError::DUPLICATE_KEY: {\n      return \"Duplicate key found\";\n    }\n    default:\n      return \"Unknown error\";\n  }\n}\n\nstd::string_view encodeErrToString(EncodeError err) {\n  switch (err) {\n    case EncodeError::OK: {\n      return \"No error\";\n    }\n    case EncodeError::EMPTY_DATA_STRUCTURE: {\n      return \"Empty data structure\";\n    }\n    case EncodeError::BAD_IDENTIFIER: {\n      return \"Bad identifier\";\n    }\n    case EncodeError::BAD_STRING: {\n      return \"Bad string\";\n    }\n    case EncodeError::ITEM_TYPE_MISMATCH: {\n      return \"Item type mismatch\";\n    }\n    case EncodeError::ENCODING_NULL_ITEM: {\n      return \"Tried to encode null item\";\n    }\n    default:\n      return \"Unknown error\";\n  }\n}\n\n} // namespace proxygen::StructuredHeaders\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <string>\n#include <unordered_map>\n#include <variant>\n#include <vector>\n\nnamespace proxygen::StructuredHeaders {\n\nstatic const int kMaxValidIntegerLength = 19;\nstatic const int kMaxValidFloatLength = 16;\n\n/* tagged union for an item in a structured header */\nclass StructuredHeaderItem {\n public:\n  enum class Type {\n    NONE,\n    STRING,\n    BINARYCONTENT,\n    IDENTIFIER,\n    DOUBLE,\n    INT64,\n    BOOLEAN\n  };\n\n  using VariantType = std::variant<bool, int64_t, double, std::string>;\n\n  StructuredHeaderItem() = default;\n  StructuredHeaderItem(Type tagIn, VariantType valueIn)\n      : tag(tagIn), value(std::move(valueIn)) {\n  }\n\n  template <typename T>\n  bool operator!=(const T& other) const {\n    return !operator==(other);\n  }\n\n  template <typename T>\n  bool operator==(const T& other) const {\n    try {\n      return std::get<T>(value) == other;\n    } catch (std::bad_variant_access&) {\n      return false;\n    }\n  }\n\n  template <typename T>\n  T get() const {\n    return std::get<T>(value);\n  }\n\n  Type tag;\n  VariantType value;\n};\n\nstruct ParameterisedIdentifier {\n  std::string identifier;\n  std::unordered_map<std::string, StructuredHeaderItem> parameterMap;\n};\n\nusing ParameterisedList =\n    std::vector<StructuredHeaders::ParameterisedIdentifier>;\n\nusing Dictionary = std::unordered_map<std::string, StructuredHeaderItem>;\n\nenum class DecodeError : uint8_t {\n  OK = 0,\n  VALUE_TOO_LONG = 1,\n  INVALID_CHARACTER = 2,\n  UNDECODEABLE_BINARY_CONTENT = 3,\n  UNEXPECTED_END_OF_BUFFER = 4,\n  UNPARSEABLE_NUMERIC_TYPE = 5,\n  DUPLICATE_KEY = 6\n};\n\nenum class EncodeError : uint8_t {\n  OK = 0,\n  EMPTY_DATA_STRUCTURE = 1,\n  BAD_IDENTIFIER = 2,\n  BAD_STRING = 3,\n  ITEM_TYPE_MISMATCH = 4,\n  ENCODING_NULL_ITEM = 5\n};\n\nstd::string_view decodeErrToString(DecodeError err);\nstd::string_view encodeErrToString(EncodeError err);\n\n} // namespace proxygen::StructuredHeaders\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n\nnamespace proxygen {\n\nusing namespace StructuredHeaders;\n\nDecodeError StructuredHeadersDecoder::decodeItem(StructuredHeaderItem& result) {\n  auto err = buf_.parseItem(result);\n  if (err != DecodeError::OK) {\n    return err;\n  }\n  return buf_.isEmpty()\n             ? DecodeError::OK\n             : buf_.handleDecodeError(DecodeError::INVALID_CHARACTER);\n}\n\nDecodeError StructuredHeadersDecoder::decodeList(\n    std::vector<StructuredHeaderItem>& result) {\n\n  while (!buf_.isEmpty()) {\n\n    StructuredHeaderItem item;\n    auto err = buf_.parseItem(item);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    result.push_back(item);\n\n    buf_.removeOptionalWhitespace();\n\n    if (buf_.isEmpty()) {\n      return DecodeError::OK;\n    }\n\n    err = buf_.removeSymbol(\",\", true);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    buf_.removeOptionalWhitespace();\n\n    if (buf_.isEmpty()) {\n      return buf_.handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n    }\n  }\n\n  return buf_.handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n}\n\nDecodeError StructuredHeadersDecoder::decodeDictionary(Dictionary& result) {\n  return decodeMap(result, MapType::DICTIONARY);\n}\n\nDecodeError StructuredHeadersDecoder::decodeParameterisedList(\n    ParameterisedList& result) {\n\n  while (!buf_.isEmpty()) {\n\n    ParameterisedIdentifier primaryIdentifier;\n\n    auto err = buf_.parseIdentifier(primaryIdentifier.identifier);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    buf_.removeOptionalWhitespace();\n\n    err = decodeMap(primaryIdentifier.parameterMap, MapType::PARAMETERISED_MAP);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    result.emplace_back(primaryIdentifier);\n\n    buf_.removeOptionalWhitespace();\n\n    if (buf_.isEmpty()) {\n      return DecodeError::OK;\n    }\n\n    err = buf_.removeSymbol(\",\", true);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    buf_.removeOptionalWhitespace();\n  }\n\n  return buf_.handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n}\n\nDecodeError StructuredHeadersDecoder::decodeMap(\n    std::unordered_map<std::string, StructuredHeaderItem>& result,\n    MapType mapType) {\n\n  std::string delimiter = (mapType == MapType::PARAMETERISED_MAP) ? \";\" : \",\";\n\n  buf_.removeOptionalWhitespace();\n\n  if ((mapType == MapType::PARAMETERISED_MAP) &&\n      (buf_.removeSymbol(delimiter, false) != DecodeError::OK)) {\n    return DecodeError::OK;\n  }\n\n  while (!buf_.isEmpty()) {\n\n    buf_.removeOptionalWhitespace();\n\n    std::string thisKey;\n    auto err = buf_.parseIdentifier(thisKey);\n    if (err != DecodeError::OK) {\n      return err;\n    }\n\n    if (result.find(thisKey) != result.end()) {\n      return buf_.handleDecodeError(DecodeError::DUPLICATE_KEY);\n    }\n\n    err = buf_.removeSymbol(\"=\", false /* strict */);\n    if (err != DecodeError::OK) {\n      StructuredHeaderItem value;\n      value.tag = StructuredHeaderItem::Type::BOOLEAN;\n      value.value = true;\n      result[thisKey] = value;\n    } else {\n      StructuredHeaderItem value;\n      err = buf_.parseItem(value);\n      if (err != DecodeError::OK) {\n        return err;\n      }\n\n      result[thisKey] = value;\n    }\n\n    if (buf_.isEmpty()) {\n      return DecodeError::OK;\n    }\n\n    buf_.removeOptionalWhitespace();\n\n    err = buf_.removeSymbol(delimiter, mapType == MapType::DICTIONARY);\n    if (err != DecodeError::OK) {\n      if (mapType == MapType::PARAMETERISED_MAP) {\n        return DecodeError::OK;\n      } else {\n        return err;\n      }\n    }\n  }\n\n  return buf_.handleDecodeError(DecodeError::UNEXPECTED_END_OF_BUFFER);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersBuffer.h>\n#include <unordered_map>\n#include <vector>\n\nnamespace proxygen {\n\nclass StructuredHeadersDecoder {\n public:\n  explicit StructuredHeadersDecoder(const std::string& s) : buf_(s) {\n  }\n\n  explicit StructuredHeadersDecoder(folly::StringPiece s) : buf_(s) {\n  }\n\n  StructuredHeaders::DecodeError decodeItem(StructuredHeaderItem& result);\n\n  StructuredHeaders::DecodeError decodeList(\n      std::vector<StructuredHeaderItem>& result);\n\n  StructuredHeaders::DecodeError decodeDictionary(Dictionary& result);\n\n  StructuredHeaders::DecodeError decodeParameterisedList(\n      ParameterisedList& result);\n\n private:\n  enum class MapType { DICTIONARY = 0, PARAMETERISED_MAP = 1 };\n\n  StructuredHeaders::DecodeError decodeMap(\n      std::unordered_map<std::string, StructuredHeaderItem>& result,\n      MapType mapType);\n\n  StructuredHeadersBuffer buf_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/base64.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.h>\n\nnamespace proxygen {\n\nusing namespace StructuredHeaders;\n\nEncodeError StructuredHeadersEncoder::encodeList(\n    const std::vector<StructuredHeaderItem>& input) {\n\n  if (input.empty()) {\n    return handleEncodeError(EncodeError::EMPTY_DATA_STRUCTURE);\n  }\n\n  for (auto it = input.begin(); it != input.end(); it++) {\n    auto err = encodeItem(*it);\n    if (err != EncodeError::OK) {\n      return err;\n    }\n\n    if (std::next(it, 1) != input.end()) {\n      outputStream_ << \", \";\n    }\n  }\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeDictionary(\n    const Dictionary& input) {\n\n  if (input.empty()) {\n    return handleEncodeError(EncodeError::EMPTY_DATA_STRUCTURE);\n  }\n\n  for (auto it = input.begin(); it != input.end(); it++) {\n    auto err = encodeIdentifier(it->first);\n    if (err != EncodeError::OK) {\n      return err;\n    }\n\n    if (!itemTypeMatchesContent(it->second)) {\n      return handleEncodeError(EncodeError::ITEM_TYPE_MISMATCH);\n    }\n    if (!skipBoolean(it->second)) {\n      outputStream_ << \"=\";\n      err = encodeItem(it->second);\n      if (err != EncodeError::OK) {\n        return err;\n      }\n    }\n\n    if (std::next(it, 1) != input.end()) {\n      outputStream_ << \", \";\n    }\n  }\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeParameterisedList(\n    const ParameterisedList& input) {\n\n  if (input.empty()) {\n    return handleEncodeError(EncodeError::EMPTY_DATA_STRUCTURE);\n  }\n\n  for (auto it1 = input.begin(); it1 != input.end(); it1++) {\n    auto err = encodeIdentifier(it1->identifier);\n    if (err != EncodeError::OK) {\n      return err;\n    }\n\n    for (auto it2 = it1->parameterMap.begin(); it2 != it1->parameterMap.end();\n         it2++) {\n\n      outputStream_ << \"; \";\n\n      err = encodeIdentifier(it2->first);\n      if (err != EncodeError::OK) {\n        return err;\n      }\n\n      if (it2->second.tag != StructuredHeaderItem::Type::NONE) {\n        if (!itemTypeMatchesContent(it2->second)) {\n          return handleEncodeError(EncodeError::ITEM_TYPE_MISMATCH);\n        }\n        if (!skipBoolean(it2->second)) {\n          outputStream_ << \"=\";\n          err = encodeItem(it2->second);\n        }\n\n        if (err != EncodeError::OK) {\n          return err;\n        }\n      }\n    }\n\n    if (std::next(it1, 1) != input.end()) {\n      outputStream_ << \", \";\n    }\n  }\n\n  return EncodeError::OK;\n}\n\nStructuredHeadersEncoder::StructuredHeadersEncoder() {\n  outputStream_.precision(kMaxValidFloatLength - 1);\n}\n\nEncodeError StructuredHeadersEncoder::encodeItem(\n    const StructuredHeaderItem& input) {\n\n  if (!itemTypeMatchesContent(input)) {\n    return handleEncodeError(EncodeError::ITEM_TYPE_MISMATCH);\n  }\n\n  switch (input.tag) {\n    case StructuredHeaderItem::Type::STRING:\n      return encodeString(std::get<std::string>(input.value));\n    case StructuredHeaderItem::Type::INT64:\n      return encodeInteger(std::get<int64_t>(input.value));\n    case StructuredHeaderItem::Type::BOOLEAN:\n      return encodeBoolean(std::get<bool>(input.value));\n    case StructuredHeaderItem::Type::DOUBLE:\n      return encodeFloat(std::get<double>(input.value));\n    case StructuredHeaderItem::Type::BINARYCONTENT:\n      return encodeBinaryContent(std::get<std::string>(input.value));\n    default:\n      return handleEncodeError(EncodeError::ENCODING_NULL_ITEM);\n  }\n}\n\nEncodeError StructuredHeadersEncoder::encodeBinaryContent(\n    const std::string& input) {\n\n  outputStream_ << \"*\";\n  outputStream_ << folly::base64Encode(input);\n  outputStream_ << \"*\";\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeString(const std::string& input) {\n\n  if (!isValidString(input)) {\n    return handleEncodeError(EncodeError::BAD_STRING, input);\n  }\n\n  outputStream_ << \"\\\"\";\n\n  size_t l = 0;\n  size_t r = 0;\n  for (char c : input) {\n    if (c == '\"' || c == '\\\\') {\n      if (r > l) {\n        outputStream_ << input.substr(l, r - l) << \"\\\\\" << c;\n      } else {\n        outputStream_ << \"\\\\\" << c;\n      }\n      ++r;\n      l = r;\n    } else {\n      ++r;\n    }\n  }\n  if (l < r) {\n    outputStream_ << input.substr(l, r - l + 1);\n  }\n\n  outputStream_ << \"\\\"\";\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeInteger(int64_t input) {\n\n  outputStream_ << input;\n\n  return EncodeError::OK;\n}\n\nbool StructuredHeadersEncoder::skipBoolean(const StructuredHeaderItem& input) {\n  return input.tag == StructuredHeaderItem::Type::BOOLEAN &&\n         std::get<bool>(input.value);\n}\n\nEncodeError StructuredHeadersEncoder::encodeBoolean(bool input) {\n\n  outputStream_ << '?' << (input ? '1' : '0');\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeFloat(double input) {\n\n  outputStream_ << input;\n\n  return EncodeError::OK;\n}\n\nEncodeError StructuredHeadersEncoder::encodeIdentifier(\n    const std::string& input) {\n\n  if (!isValidIdentifier(input)) {\n    return handleEncodeError(EncodeError::BAD_IDENTIFIER, input);\n  }\n  outputStream_ << input;\n  return EncodeError::OK;\n}\n\n// Used to print an error when a string type (eg: a string or binary content)\n// was involved in the error\nEncodeError StructuredHeadersEncoder::handleEncodeError(\n    EncodeError err, const std::string& culprit) {\n  LOG_EVERY_N(ERROR, 1000) << \"Error message: \" << encodeErrToString(err)\n                           << \", culprit: \" << culprit;\n  return err;\n}\n\n// Used to print more general error messages (eg: empty data structure)\nEncodeError StructuredHeadersEncoder::handleEncodeError(EncodeError err) {\n  LOG_EVERY_N(ERROR, 1000) << \"Error message: \" << encodeErrToString(err);\n  return err;\n}\n\nstd::string StructuredHeadersEncoder::get() {\n  return outputStream_.str();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <sstream>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n\nusing namespace StructuredHeaders;\n\nclass StructuredHeadersEncoder {\n\n public:\n  StructuredHeadersEncoder();\n\n  EncodeError encodeParameterisedList(const ParameterisedList& input);\n\n  EncodeError encodeDictionary(const Dictionary& input);\n\n  EncodeError encodeList(const std::vector<StructuredHeaderItem>& input);\n\n  EncodeError encodeItem(const StructuredHeaderItem& input);\n\n  EncodeError encodeIdentifier(const std::string& input);\n\n  std::string get();\n\n private:\n  EncodeError encodeBinaryContent(const std::string& input);\n\n  EncodeError encodeString(const std::string& input);\n\n  EncodeError encodeInteger(int64_t input);\n\n  EncodeError encodeBoolean(bool input);\n\n  bool skipBoolean(const StructuredHeaderItem& input);\n\n  EncodeError encodeFloat(double input);\n\n  EncodeError handleEncodeError(EncodeError err, const std::string& badContent);\n\n  EncodeError handleEncodeError(EncodeError err);\n\n  std::ostringstream outputStream_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.h>\n\nnamespace proxygen::StructuredHeaders {\n\nbool isAlpha(uint8_t c) {\n  return ((unsigned int)(c | 0x20) - 'a') < 26U;\n}\n\nbool isLcAlpha(char c) {\n  return c >= 'a' && c <= 'z';\n}\n\nbool isDigit(char c) {\n  return c >= '0' && c <= '9';\n}\n\nbool isValidIdentifierChar(char c) {\n  return isLcAlpha(c) || isDigit(c) || c == '_' || c == '-' || c == '*' ||\n         c == '/';\n}\n\nbool isValidEncodedBinaryContentChar(char c) {\n  return isAlpha(c) || isDigit(c) || c == '+' || c == '/' || c == '=';\n}\n\nbool isValidStringChar(char c) {\n  /*\n   * The difference between the character restriction here and that mentioned\n   * in section 3.7 of version 6 of the Structured Headers draft is that this\n   * function accepts \\ and DQUOTE characters. These characters are allowed\n   * as long as they are present as a part of an escape sequence, which is\n   * checked for in the parseString() function in the StructuredHeadersBuffer.\n   */\n  return c >= 0x20 && c <= 0x7E;\n}\n\nbool isValidIdentifier(const std::string& s) {\n  if (s.size() == 0 || !isLcAlpha(s[0])) {\n    return false;\n  }\n\n  for (char c : s) {\n    if (!isValidIdentifierChar(c)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nbool isValidString(const std::string& s) {\n  for (char c : s) {\n    if (!isValidStringChar(c)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool isValidEncodedBinaryContent(const std::string& s) {\n\n  if (s.size() % 4 != 0) {\n    return false;\n  }\n\n  bool equalSeen = false;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    if (*it == '=') {\n      equalSeen = true;\n    } else if (equalSeen || !isValidEncodedBinaryContentChar(*it)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nbool itemTypeMatchesContent(const StructuredHeaderItem& input) {\n  switch (input.tag) {\n    case StructuredHeaderItem::Type::BINARYCONTENT:\n    case StructuredHeaderItem::Type::IDENTIFIER:\n    case StructuredHeaderItem::Type::STRING:\n      return std::holds_alternative<std::string>(input.value);\n    case StructuredHeaderItem::Type::INT64:\n      return std::holds_alternative<int64_t>(input.value);\n    case StructuredHeaderItem::Type::BOOLEAN:\n      return std::holds_alternative<bool>(input.value);\n    case StructuredHeaderItem::Type::DOUBLE:\n      return std::holds_alternative<double>(input.value);\n    case StructuredHeaderItem::Type::NONE:\n      return true;\n  }\n\n  return false;\n}\n\n} // namespace proxygen::StructuredHeaders\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <string>\n\n#pragma once\n\nnamespace proxygen::StructuredHeaders {\n\nbool isLcAlpha(char c);\n\nbool isValidIdentifierChar(char c);\n\nbool isValidEncodedBinaryContentChar(char c);\n\nbool isValidStringChar(char c);\n\nbool isValidIdentifier(const std::string& s);\n\nbool isValidString(const std::string& s);\n\nbool isValidEncodedBinaryContent(const std::string& s);\n\nbool itemTypeMatchesContent(const StructuredHeaderItem& input);\n\nstd::string decodeBase64(const std::string& encoded);\n\nstd::string encodeBase64(const std::string& input);\n\n} // namespace proxygen::StructuredHeaders\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET StructuredHeadersTests\n  SOURCES\n    StructuredHeadersBufferTest.cpp\n    StructuredHeadersDecoderTest.cpp\n    StructuredHeadersEncoderTest.cpp\n    StructuredHeadersStandardTest.cpp\n    StructuredHeadersUtilitiesTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/StructuredHeadersBufferTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersBuffer.h>\n\n#include <folly/Conv.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersConstants.h>\n#include <string>\n\nnamespace proxygen {\n\nclass StructuredHeadersBufferTest : public testing::Test {};\n\nTEST_F(StructuredHeadersBufferTest, TestBinaryContent) {\n  std::string input = \"*bWF4aW0gaXMgdGhlIGJlc3Q=*\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::BINARYCONTENT);\n  EXPECT_EQ(output, std::string(\"maxim is the best\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBinaryContentIllegalCharacters) {\n  std::string input = \"*()645\\t  this is not a b64 encoded string ((({]}}}))*\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBinaryContentNoEndingAsterisk) {\n  std::string input = \"*seattle\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBinaryContentEmpty) {\n  std::string input = \"**\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::BINARYCONTENT);\n  EXPECT_EQ(output, std::string(\"\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIdentifier) {\n  std::string input = \"abcdefg\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseIdentifier(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::IDENTIFIER);\n  EXPECT_EQ(output, std::string(\"abcdefg\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIdentifierAllLegalCharacters) {\n  std::string input = \"a0_-*/\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseIdentifier(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::IDENTIFIER);\n  EXPECT_EQ(output, std::string(\"a0_-*/\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIdentifierBeginningUnderscore) {\n  std::string input = \"_af09d____****\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseIdentifier(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestString) {\n  std::string input = \"\\\"fsdfsdf\\\"sdfsdf\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, std::string(\"fsdfsdf\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestStringEscapedQuote) {\n  std::string input = \"\\\"abc\\\\\\\"def\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, std::string(\"abc\\\"def\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestStringEscapedBackslash) {\n  std::string input = \"\\\"abc\\\\\\\\def\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, std::string(\"abc\\\\def\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestStringStrayBackslash) {\n  std::string input = \"\\\"abc\\\\def\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestStringInvalidCharacter) {\n  std::string input = \"\\\"abcdefg\\thij\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestStringParsingRepeated) {\n  std::string input = \"\\\"proxy\\\"\\\"gen\\\"\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, std::string(\"proxy\"));\n\n  err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, std::string(\"gen\"));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestInteger) {\n  std::string input = \"843593\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, int64_t(843593));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerTwoNegatives) {\n  std::string input = \"--843593\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerEmptyAfterNegative) {\n  std::string input = \"-\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerNegative) {\n  std::string input = \"-843593\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, int64_t(-843593));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerOverflow) {\n  std::string input = \"9223372036854775808\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerHighBorderline) {\n  std::string input = \"9223372036854775807\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, std::numeric_limits<int64_t>::max());\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerLowBorderline) {\n  std::string input = \"-9223372036854775808\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, std::numeric_limits<int64_t>::min());\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerUnderflow) {\n  std::string input = \"-9223372036854775809\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBool) {\n  for (auto i = 0; i < 2; i++) {\n    auto input = folly::to<std::string>(\"?\", i);\n    StructuredHeadersBuffer shd(input);\n    StructuredHeaderItem output;\n    auto err = shd.parseItem(output);\n    EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n    EXPECT_EQ(output.tag, StructuredHeaderItem::Type::BOOLEAN);\n    bool expected = i;\n    EXPECT_EQ(output.get<bool>(), expected);\n  }\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBoolInvalidChars) {\n  std::string input = \"?2\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::INVALID_CHARACTER);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBoolWrongLength) {\n  std::vector<std::string> inputs{\"?\", \"?10\"};\n  for (auto& input : inputs) {\n    StructuredHeadersBuffer shd(input);\n    StructuredHeaderItem output;\n    auto err = shd.parseItem(output);\n    EXPECT_EQ(err,\n              (input.length() > 2\n                   ? StructuredHeaders::DecodeError::VALUE_TOO_LONG\n                   : StructuredHeaders::DecodeError::UNEXPECTED_END_OF_BUFFER));\n  }\n}\n\nTEST_F(StructuredHeadersBufferTest, TestBool2) {\n\n  std::string input = \"?2\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::INVALID_CHARACTER);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloat) {\n  std::string input = \"3.1415926536\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(output, 3.1415926536);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloatPrecedingWhitespace) {\n  std::string input = \"         \\t\\t    66000.5645\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(output, 66000.5645);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloatNoDigitPrecedingDecimal) {\n  std::string input = \".1415926536\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestIntegerTooManyChars) {\n  std::string input = \"10000000000000000000\"; // has 20 characters\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloatTooManyChars) {\n  std::string input = \"111111111.1111111\"; // has 17 characters\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloatBorderlineNumChars) {\n  std::string input = \"111111111.111111\"; // has 16 characters\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestFloatEndsWithDecimal) {\n  std::string input = \"100.\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.parseItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestConsumeComma) {\n  std::string input = \",5345346\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  shd.removeSymbol(\",\", true);\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, int64_t(5345346));\n}\n\nTEST_F(StructuredHeadersBufferTest, TestConsumeEquals) {\n  std::string input = \"=456346.646\";\n  StructuredHeadersBuffer shd(input);\n  StructuredHeaderItem output;\n  shd.removeSymbol(\"=\", true);\n  auto err = shd.parseItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(output, 456346.646);\n}\n\nTEST_F(StructuredHeadersBufferTest, TestConsumeMessy) {\n  std::string input = \"asfgsdfg,asfgsdfg,\";\n  StructuredHeadersBuffer shd(input);\n  for (int i = 0; i < 2; i++) {\n    StructuredHeaderItem output;\n    auto err = shd.parseIdentifier(output);\n    EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n    EXPECT_EQ(output.tag, StructuredHeaderItem::Type::IDENTIFIER);\n    EXPECT_EQ(output, std::string(\"asfgsdfg\"));\n    shd.removeSymbol(\",\", true);\n  }\n}\n\nTEST_F(StructuredHeadersBufferTest, TestInequalityOperator) {\n  StructuredHeaderItem integerItem;\n  integerItem.value = int64_t(999);\n\n  StructuredHeaderItem doubleItem;\n  doubleItem.value = 11.43;\n\n  StructuredHeaderItem stringItem;\n  stringItem.value = std::string(\"hi\");\n\n  EXPECT_NE(integerItem, int64_t(998));\n  EXPECT_NE(doubleItem, double(11.44));\n  EXPECT_NE(stringItem, std::string(\"bye\"));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/StructuredHeadersDecoderTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n\n#include <folly/portability/GTest.h>\n#include <string>\n\nnamespace proxygen {\n\nclass StructuredHeadersDecoderTest : public testing::Test {};\n\nTEST_F(StructuredHeadersDecoderTest, TestItem) {\n  std::string input = \"645643\";\n  StructuredHeadersDecoder shd(input);\n\n  StructuredHeaderItem item;\n  shd.decodeItem(item);\n\n  EXPECT_EQ(item.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(item, int64_t(645643));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestList) {\n  std::string input = \"\\\"cookies\\\", 3.1415    , 74657\";\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  shd.decodeList(v);\n  EXPECT_EQ(v.size(), 3);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(v[2].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], std::string(\"cookies\"));\n  EXPECT_EQ(v[1], 3.1415);\n  EXPECT_EQ(v[2], int64_t(74657));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestListBeginningWhitespace) {\n  std::string input = \"   19   , 95\";\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  shd.decodeList(v);\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(19));\n  EXPECT_EQ(v[1], int64_t(95));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestListEndingWhitespace) {\n  std::string input = \"19   , 95    \";\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  shd.decodeList(v);\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(19));\n  EXPECT_EQ(v[1], int64_t(95));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestListNoWhitespace) {\n  std::string input = \"19,95\";\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  shd.decodeList(v);\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(19));\n  EXPECT_EQ(v[1], int64_t(95));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestListOneItem) {\n  std::string input = \"*Zm9vZA==*\";\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  shd.decodeList(v);\n  EXPECT_EQ(v.size(), 1);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::BINARYCONTENT);\n\n  EXPECT_EQ(v[0], std::string(\"food\"));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestDictionaryManyElts) {\n  std::string input = \"age=87  ,  weight=150.8 ,   name=\\\"John Doe\\\"\";\n  StructuredHeadersDecoder shd(input);\n\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n  shd.decodeDictionary(m);\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m[\"age\"].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(m[\"weight\"].tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(m[\"name\"].tag, StructuredHeaderItem::Type::STRING);\n\n  EXPECT_EQ(m[\"age\"], int64_t(87));\n  EXPECT_EQ(m[\"weight\"], 150.8);\n  EXPECT_EQ(m[\"name\"], std::string(\"John Doe\"));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestDictionaryOneElt) {\n  std::string input = \"bagel=*YXZvY2Fkbw==*\";\n  StructuredHeadersDecoder shd(input);\n\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n  shd.decodeDictionary(m);\n  EXPECT_EQ(m.size(), 1);\n\n  EXPECT_EQ(m[\"bagel\"].tag, StructuredHeaderItem::Type::BINARYCONTENT);\n  EXPECT_EQ(m[\"bagel\"], std::string(\"avocado\"));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestParamListOneElt) {\n  std::string input = \"abc_123;a=1;b=2\";\n  StructuredHeadersDecoder shd(input);\n\n  ParameterisedList pl;\n  shd.decodeParameterisedList(pl);\n  EXPECT_EQ(pl.size(), 1);\n  EXPECT_EQ(pl[0].identifier, \"abc_123\");\n  EXPECT_EQ(pl[0].parameterMap.size(), 2);\n  EXPECT_EQ(pl[0].parameterMap[\"a\"], int64_t(1));\n  EXPECT_EQ(pl[0].parameterMap[\"b\"], int64_t(2));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestParamListManyElts) {\n  std::string input = \"a_13;a=1;b=2; c_4, ghi;q=\\\"9\\\";r=*bWF4IGlzIGF3ZXNvbWU=*\";\n  StructuredHeadersDecoder shd(input);\n\n  ParameterisedList pl;\n  shd.decodeParameterisedList(pl);\n  EXPECT_EQ(pl.size(), 2);\n\n  EXPECT_EQ(pl[0].identifier, \"a_13\");\n  EXPECT_EQ(pl[0].parameterMap.size(), 3);\n  EXPECT_EQ(pl[0].parameterMap[\"a\"].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(pl[0].parameterMap[\"b\"].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(pl[0].parameterMap[\"c_4\"].tag, StructuredHeaderItem::Type::BOOLEAN);\n  EXPECT_EQ(pl[0].parameterMap[\"c_4\"], true);\n  EXPECT_EQ(pl[0].parameterMap[\"a\"], int64_t(1));\n  EXPECT_EQ(pl[0].parameterMap[\"b\"], int64_t(2));\n\n  EXPECT_EQ(pl[1].identifier, \"ghi\");\n  EXPECT_EQ(pl[1].parameterMap.size(), 2);\n  EXPECT_EQ(pl[1].parameterMap[\"q\"].tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(pl[1].parameterMap[\"r\"].tag,\n            StructuredHeaderItem::Type::BINARYCONTENT);\n  EXPECT_EQ(pl[1].parameterMap[\"q\"], std::string(\"9\"));\n  EXPECT_EQ(pl[1].parameterMap[\"r\"], std::string(\"max is awesome\"));\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestParamListNoParams) {\n  std::string input = \"apple12, cat14, dog22\";\n  StructuredHeadersDecoder shd(input);\n\n  ParameterisedList pl;\n  shd.decodeParameterisedList(pl);\n  EXPECT_EQ(pl.size(), 3);\n  EXPECT_EQ(pl[0].identifier, \"apple12\");\n  EXPECT_EQ(pl[0].parameterMap.size(), 0);\n\n  EXPECT_EQ(pl[1].identifier, \"cat14\");\n  EXPECT_EQ(pl[1].parameterMap.size(), 0);\n\n  EXPECT_EQ(pl[2].identifier, \"dog22\");\n  EXPECT_EQ(pl[2].parameterMap.size(), 0);\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestParamListWhitespace) {\n  std::string input = \"am_95    ;    abc=11.8   ,    foo      \";\n  StructuredHeadersDecoder shd(input);\n\n  ParameterisedList pl;\n  shd.decodeParameterisedList(pl);\n  EXPECT_EQ(pl.size(), 2);\n\n  EXPECT_EQ(pl[0].identifier, \"am_95\");\n  EXPECT_EQ(pl[0].parameterMap.size(), 1);\n  EXPECT_EQ(pl[0].parameterMap[\"abc\"].tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(pl[0].parameterMap[\"abc\"], 11.8);\n\n  EXPECT_EQ(pl[1].identifier, \"foo\");\n  EXPECT_EQ(pl[1].parameterMap.size(), 0);\n}\n\nTEST_F(StructuredHeadersDecoderTest, TestParamListNullValues) {\n  std::string input = \"beverages;water;juice, food;pizza;burger\";\n  StructuredHeadersDecoder shd(input);\n\n  ParameterisedList pl;\n  shd.decodeParameterisedList(pl);\n  EXPECT_EQ(pl.size(), 2);\n\n  EXPECT_EQ(pl[0].identifier, \"beverages\");\n  EXPECT_EQ(pl[0].parameterMap.size(), 2);\n  EXPECT_EQ(pl[0].parameterMap[\"water\"].tag,\n            StructuredHeaderItem::Type::BOOLEAN);\n  EXPECT_EQ(pl[0].parameterMap[\"water\"], true);\n  EXPECT_EQ(pl[0].parameterMap[\"juice\"].tag,\n            StructuredHeaderItem::Type::BOOLEAN);\n  EXPECT_EQ(pl[0].parameterMap[\"juice\"], true);\n\n  EXPECT_EQ(pl[1].identifier, \"food\");\n  EXPECT_EQ(pl[1].parameterMap.size(), 2);\n  EXPECT_EQ(pl[1].parameterMap[\"pizza\"].tag,\n            StructuredHeaderItem::Type::BOOLEAN);\n  EXPECT_EQ(pl[1].parameterMap[\"burger\"].tag,\n            StructuredHeaderItem::Type::BOOLEAN);\n}\n\nTEST_F(StructuredHeadersDecoderTest, PriorityWithIncremental) {\n  StructuredHeaders::Dictionary dict;\n  std::array<char, 6> input = {'u', '=', '5', ',', ' ', 'i'};\n  StructuredHeadersDecoder decoder(\n      folly::StringPiece(input.data(), input.size()));\n  auto ret = decoder.decodeDictionary(dict);\n  EXPECT_EQ(ret, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(2, dict.size());\n  EXPECT_EQ(dict[\"u\"].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(dict[\"u\"], (int64_t)5);\n  EXPECT_EQ(dict[\"i\"].tag, StructuredHeaderItem::Type::BOOLEAN);\n  EXPECT_EQ(dict[\"i\"], true);\n}\n\nTEST_F(StructuredHeadersDecoderTest, PriorityWithoutIncremental) {\n  std::string priority = \"u=5\";\n  StructuredHeadersDecoder decoder(priority);\n  StructuredHeaders::Dictionary dict;\n  auto ret = decoder.decodeDictionary(dict);\n  EXPECT_EQ(ret, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(1, dict.size());\n  EXPECT_EQ(dict[\"u\"].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(dict[\"u\"], (int64_t)5);\n  EXPECT_EQ(dict.find(\"i\"), dict.end());\n}\n\nTEST_F(StructuredHeadersDecoderTest, PriorityWithoutIncrementalHasComma) {\n  std::string priority = \"u=5,\";\n  StructuredHeadersDecoder decoder(priority);\n  StructuredHeaders::Dictionary dict;\n  auto ret = decoder.decodeDictionary(dict);\n  EXPECT_NE(ret, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_F(StructuredHeadersDecoderTest, SpaceOnlyNoCrash) {\n  StructuredHeaders::Dictionary dict;\n  std::array<char, 1> input = {' '};\n  StructuredHeadersDecoder decoder(\n      folly::StringPiece(input.data(), input.size()));\n  // NO crash:\n  (void)decoder.decodeDictionary(dict);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/StructuredHeadersEncoderTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h>\n\n#include <folly/Conv.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <unordered_map>\n\nusing namespace testing;\n\nnamespace proxygen {\n\nclass StructuredHeadersEncoderTest : public testing::Test {};\n\nTEST_F(StructuredHeadersEncoderTest, TestInteger) {\n  StructuredHeaderItem item;\n  int64_t val = 2018;\n  item.tag = StructuredHeaderItem::Type::INT64;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"2018\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestIntegerNegative) {\n  StructuredHeaderItem item;\n  int64_t val = -2018;\n  item.tag = StructuredHeaderItem::Type::INT64;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"-2018\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestBoolean) {\n  StructuredHeaderItem item;\n  for (auto i = 0; i < 2; i++) {\n    bool val = i;\n    item.tag = StructuredHeaderItem::Type::BOOLEAN;\n    item.value = val;\n\n    StructuredHeadersEncoder encoder;\n    auto err = encoder.encodeItem(item);\n\n    EXPECT_EQ(err, EncodeError::OK);\n    EXPECT_EQ(encoder.get(), folly::to<std::string>(\"?\", (val ? \"1\" : \"0\")));\n  }\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestFloat) {\n  StructuredHeaderItem item;\n  double val = 3.1415926535;\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"3.1415926535\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestFloatTooMuchPrecision) {\n  StructuredHeadersEncoder encoder;\n  StructuredHeaderItem item;\n  double val = 100000.8392758372647; // has 20 characters\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  item.value = val;\n\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"100000.839275837\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestFloatNegative) {\n  StructuredHeaderItem item;\n  double val = -3.141;\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"-3.141\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestString) {\n  StructuredHeaderItem item;\n  std::string val = \"seattle is the best\";\n  item.tag = StructuredHeaderItem::Type::STRING;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"\\\"seattle is the best\\\"\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestStringBadContent) {\n  StructuredHeaderItem item;\n  std::string val = \"seattle \\n is the best\";\n  item.tag = StructuredHeaderItem::Type::STRING;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestStringBackslash) {\n  StructuredHeaderItem item;\n  std::string val = \"seattle \\\\is the best\";\n  item.tag = StructuredHeaderItem::Type::STRING;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"\\\"seattle \\\\\\\\is the best\\\"\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestStringQuote) {\n  StructuredHeaderItem item;\n  std::string val = \"seattle \\\"is the best\";\n  item.tag = StructuredHeaderItem::Type::STRING;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"\\\"seattle \\\\\\\"is the best\\\"\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestBinaryContent) {\n  StructuredHeaderItem item;\n  std::string val = \"seattle <3\";\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"*c2VhdHRsZSA8Mw==*\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestIdentifier) {\n  std::string result;\n  std::string val = \"abc_00123\";\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeIdentifier(val);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"abc_00123\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestIdentifierBadContent) {\n  std::string result;\n  std::string val = \"_abc_00123\";\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeIdentifier(val);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestWrongType) {\n  StructuredHeaderItem item;\n  double val = 3.1415;\n  item.tag = StructuredHeaderItem::Type::INT64;\n  item.value = val;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeItem(item);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestListManyElts) {\n  std::vector<StructuredHeaderItem> vec;\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  double val1 = 3.14;\n  item.value = val1;\n  vec.push_back(item);\n\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  std::string val2 = \"pizza\";\n  item.value = val2;\n  vec.push_back(item);\n\n  item.tag = StructuredHeaderItem::Type::INT64;\n  int64_t val3 = 65;\n  item.value = val3;\n  vec.push_back(item);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeList(vec);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"3.14, *cGl6emE=*, 65\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestListOneElt) {\n  std::vector<StructuredHeaderItem> vec;\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"hello world\";\n  item.value = val1;\n  vec.push_back(item);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeList(vec);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"\\\"hello world\\\"\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestListEmpty) {\n  std::vector<StructuredHeaderItem> vec;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeList(vec);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestListBadItem) {\n  std::vector<StructuredHeaderItem> vec;\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"hello \\x10world\";\n  item.value = val1;\n  vec.push_back(item);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeList(vec);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryOneElt) {\n  StructuredHeaders::Dictionary dict;\n\n  StructuredHeaderItem item1;\n  item1.tag = StructuredHeaderItem::Type::DOUBLE;\n  double val1 = 2.71;\n  item1.value = val1;\n\n  dict[\"e\"] = item1;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeDictionary(dict);\n\n  EXPECT_EQ(err, EncodeError::OK);\n  EXPECT_EQ(encoder.get(), \"e=2.71\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryWithTrueBoolean) {\n  StructuredHeaders::Dictionary dict;\n  StructuredHeaderItem item;\n  item.tag = StructuredHeaderItem::Type::BOOLEAN;\n  item.value = true;\n\n  dict[\"u\"] = item;\n\n  StructuredHeadersEncoder encoder;\n  EXPECT_EQ(EncodeError::OK, encoder.encodeDictionary(dict));\n  EXPECT_EQ(encoder.get(), \"u\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryWithFalseBoolean) {\n  StructuredHeaders::Dictionary dict;\n  StructuredHeaderItem item;\n  item.tag = StructuredHeaderItem::Type::BOOLEAN;\n  item.value = false;\n\n  dict[\"u\"] = item;\n\n  StructuredHeadersEncoder encoder;\n  EXPECT_EQ(EncodeError::OK, encoder.encodeDictionary(dict));\n  EXPECT_EQ(encoder.get(), \"u=?0\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryManyElts) {\n  StructuredHeaders::Dictionary dict;\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::INT64;\n  int64_t val1 = 87;\n  item.value = val1;\n  dict[\"age\"] = item;\n\n  item.tag = StructuredHeaderItem::Type::STRING;\n  std::string val2 = \"John Doe\";\n  item.value = val2;\n  dict[\"name\"] = item;\n\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  std::string val3 = \"password\";\n  item.value = val3;\n  dict[\"password\"] = item;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeDictionary(dict);\n\n  EXPECT_EQ(err, EncodeError::OK);\n\n  // A dictionary is an unordered mapping, so the ordering of specific elements\n  // within the dictionary doesn't matter\n  EXPECT_THAT(encoder.get(),\n              AnyOf(Eq(\"age=87, name=\\\"John Doe\\\", password=*cGFzc3dvcmQ=*\"),\n                    Eq(\"age=87, password=*cGFzc3dvcmQ=*, name=\\\"John Doe\\\"\"),\n                    Eq(\"name=\\\"John Doe\\\", age=87, password=*cGFzc3dvcmQ=*\"),\n                    Eq(\"name=\\\"John Doe\\\", password=*cGFzc3dvcmQ=*, age=87\"),\n                    Eq(\"password=*cGFzc3dvcmQ=*, name=\\\"John Doe\\\", age=87\"),\n                    Eq(\"password=*cGFzc3dvcmQ=*, age=87, name=\\\"John Doe\\\"\")));\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryEmpty) {\n  StructuredHeaders::Dictionary dict;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeDictionary(dict);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryBadItem) {\n  StructuredHeaders::Dictionary dict;\n\n  StructuredHeaderItem item1;\n  item1.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"hi\\nmy name is bob\";\n  item1.value = val1;\n\n  dict[\"e\"] = item1;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeDictionary(dict);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestDictionaryBadIdentifier) {\n  StructuredHeaders::Dictionary dict;\n\n  StructuredHeaderItem item1;\n  item1.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"hi\";\n  item1.value = val1;\n\n  dict[\"_bad_identifier\"] = item1;\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeDictionary(dict);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListOneElt) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::INT64;\n  int64_t val1 = 1;\n  item.value = val1;\n  m[\"abc\"] = item;\n\n  ParameterisedIdentifier pident = {.identifier = \"foo\", .parameterMap = m};\n\n  pl.emplace_back(pident);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_EQ(err, EncodeError::OK);\n\n  EXPECT_EQ(encoder.get(), \"foo; abc=1\");\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListSuccessiveNulls) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::NONE;\n  m[\"a\"] = item;\n  m[\"b\"] = item;\n\n  ParameterisedIdentifier pident = {.identifier = \"foo\", .parameterMap = m};\n\n  pl.emplace_back(pident);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_EQ(err, EncodeError::OK);\n\n  EXPECT_THAT(encoder.get(), AnyOf(Eq(\"foo; a; b\"), Eq(\"foo; b; a\")));\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListManyElts) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m1;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  double val1 = 4234.234;\n  item.value = val1;\n  m1[\"foo\"] = item;\n\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  std::string val2 = \"+++!\";\n  item.value = val2;\n  m1[\"goo\"] = item;\n\n  ParameterisedIdentifier pident1 = {.identifier = \"bar\", .parameterMap = m1};\n\n  pl.emplace_back(pident1);\n\n  std::unordered_map<std::string, StructuredHeaderItem> m2;\n\n  item.tag = StructuredHeaderItem::Type::NONE;\n  m2[\"foo\"] = item;\n\n  item.tag = StructuredHeaderItem::Type::INT64;\n  int64_t val4 = 100;\n  item.value = val4;\n  m2[\"goo\"] = item;\n\n  ParameterisedIdentifier pident2 = {.identifier = \"far\", .parameterMap = m2};\n\n  pl.emplace_back(pident2);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_EQ(err, EncodeError::OK);\n\n  // The order of the parameters of a particular identifier doesn't matter,\n  // so any of these permutations is acceptable\n  EXPECT_THAT(\n      encoder.get(),\n      AnyOf(Eq(\"bar; foo=4234.234; goo=*KysrIQ==*, far; foo; goo=100\"),\n            Eq(\"bar; foo=4234.234; goo=*KysrIQ==*, far; goo=100; foo\"),\n            Eq(\"bar; goo=*KysrIQ==*; foo=4234.234, far; foo; goo=100\"),\n            Eq(\"bar; goo=*KysrIQ==*; foo=4234.234, far; goo=100; foo\"),\n            Eq(\"far; foo; goo=100, bar; foo=4234.234; goo=*KysrIQ==*\"),\n            Eq(\"far; foo; goo=100, bar; goo=*KysrIQ==*; foo=4234.234\"),\n            Eq(\"far; goo=100; foo, bar; foo=4234.234; goo=*KysrIQ==*\"),\n            Eq(\"far; goo=100; foo, bar; goo=*KysrIQ==*; foo=4234.234\")));\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListEmpty) {\n  ParameterisedList pl;\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListBadSecondaryIdentifier) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"ABC\";\n  item.value = val1;\n  m[\"\\nbbb\"] = item;\n\n  ParameterisedIdentifier pident = {.identifier = \"foo\", .parameterMap = m};\n\n  pl.emplace_back(pident);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListBadPrimaryIdentifier) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::INT64;\n  int64_t val1 = 143;\n  item.value = val1;\n  m[\"abc\"] = item;\n\n  ParameterisedIdentifier pident = {.identifier = \"a+++\", .parameterMap = m};\n\n  pl.emplace_back(pident);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\nTEST_F(StructuredHeadersEncoderTest, TestParamListBadItems) {\n  ParameterisedList pl;\n  std::unordered_map<std::string, StructuredHeaderItem> m;\n\n  StructuredHeaderItem item;\n\n  item.tag = StructuredHeaderItem::Type::STRING;\n  std::string val1 = \"AB\\nC\";\n  item.value = val1;\n  m[\"bbb\"] = item;\n\n  ParameterisedIdentifier pident = {.identifier = \"foo\", .parameterMap = m};\n\n  pl.emplace_back(pident);\n\n  StructuredHeadersEncoder encoder;\n  auto err = encoder.encodeParameterisedList(pl);\n\n  EXPECT_NE(err, EncodeError::OK);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/StructuredHeadersStandardTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <algorithm>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h>\n\nnamespace proxygen {\n\n/*\n * These test cases were obtained from\n * https://github.com/httpwg/structured-header-tests/commits/master, with a\n * commit hash of 1881ee28edf4cd81a423ef7f2ca9252a48acb709\n */\n\nstatic const std::vector<std::pair<std::string, std::string>>\n    kLegalStringTests = {{\"\\\"foo\\\"\", \"foo\"},\n                         {\"\\\"foo \\\\\\\"bar\\\\\\\"\\\"\", \"foo \\\"bar\\\"\"}};\n\nstatic const std::vector<std::pair<std::string, std::string>>\n    kLegalBinContentTests = {{\"*aGVsbG8=*\", \"NBSWY3DP\"}, {\"**\", \"\"}};\n\nstatic const std::vector<std::pair<std::string, int64_t>> kLegalIntTests = {\n    {\"42\", 42},\n    {\"0\", 0},\n    {\"00\", 0},\n    {\"-0\", 0},\n    {\"-42\", -42},\n    {\"042\", 42},\n    {\"-042\", -42},\n    {\"9223372036854775807\", std::numeric_limits<int64_t>::max()},\n    {\"-9223372036854775808\", std::numeric_limits<int64_t>::min()}};\n\nstatic const std::vector<std::pair<std::string, double>> kLegalFloatTests = {\n    {\"1.23\", 1.23}, {\"-1.23\", -1.23}};\n\nstatic const std::vector<std::string> kIllegalItemTests = {\n    \"'foo'\",\n    \"\\\"foo\",\n    \"\\\"foo \\\\,\\\"\",\n    \"\\\"foo \\\\\",\n    \"*aGVsbG8*\",\n    \"*aGVsbG8=\",\n    \"*aGVsb G8=*\",\n    \"*aGVsbG!8=*\",\n    \"*aGVsbG!8=!*\",\n    \"*iZ==*\",\n    \"a23\",\n    \"2,3\",\n    \"-a23\",\n    \"4-2\",\n    \"9223372036854775808\",\n    \"-9223372036854775809\",\n    \"1.5.4\",\n    \"1..4\"};\n\nstatic const std::vector<std::string> kIllegalListTests = {\"1, 42,\", \"1,,42\"};\n\nclass StructuredHeadersStandardTest : public testing::Test {\n public:\n  bool decode32(std::string input, std::string& output) {\n\n    uint32_t numBlocks = input.length() / 8;\n    uint32_t blockRemainder = input.length() % 8;\n\n    std::string outputBuffer(5, '\\0');\n\n    // decode each whole block of the input\n    for (uint32_t j = 0; j < numBlocks; j++) {\n      if (!decode32Block(input, j, outputBuffer)) {\n        return false;\n      }\n      output += outputBuffer;\n    }\n\n    std::string padding(8, '\\0');\n    // set the initial bytes of the padding to be the trailing bytes of the\n    // input string that have been left over\n    for (uint32_t i = 0; i < blockRemainder; i++) {\n      padding[i] = input[input.size() - blockRemainder + i];\n    }\n\n    if (!decode32Block(padding, 0, outputBuffer)) {\n      return false;\n    }\n\n    // The second argument to the function is the size of the decoded content,\n    // where the encoded content is of size (blockRemainder * 5) / 8\n    outputBuffer = outputBuffer.substr(0, (blockRemainder * 5) / 8);\n    output += outputBuffer;\n\n    return true;\n  }\n\n private:\n  /*\n   * Applies the following transformation to the input to produce output:\n   * for each character in the input, convert it to the byte value of that\n   * character as per the base32 alphabet outlined in rfc4648\n   */\n  std::string convertBase32ToBinary(const std::string& input) {\n    std::string base32CharSet(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\");\n    std::string output;\n\n    for (char c : input) {\n      auto it = base32CharSet.find(c);\n      if (it == std::string::npos) {\n        return \"\";\n      } else {\n        // foundCharacter is the position within the base32CharSet where c\n        // was found\n        char foundCharacter = char(it);\n        output.push_back(foundCharacter);\n      }\n    }\n\n    return output;\n  }\n\n  /*\n   * Given an input string with length at least 8, and a block number\n   * specifying which specific block of the string we are decoding,\n   * it sets the outputBuffer to contain the decoded content (which is 5\n   * bytes in length). The function assumes that outputBuffer is a string of\n   * length 5.\n   */\n  bool decode32Block(std::string input,\n                     uint32_t blockNum,\n                     std::string& outputBuffer) {\n    CHECK_GE(input.size(), (blockNum + 1) * 8);\n    // Remove any padding and make each character of the input represent the\n    // byte value of that character, as per the rfc4648 encoding\n    input.erase(std::find_if(input.rbegin(),\n                             input.rend(),\n                             [](char c) { return c != '='; })\n                    .base(),\n                input.end());\n    input = convertBase32ToBinary(input);\n    if (input.empty()) {\n      return false;\n    }\n\n    // 8 byte buffer\n    int64_t buffer = 0;\n\n    for (int i = 0; i < 8; i++) {\n\n      if (input[i + blockNum * 8] >= 32) {\n        // a base32 value cannot be greater than or equal to 32\n        return false;\n      }\n\n      buffer = (buffer << 5);\n      buffer = buffer | input[i + blockNum * 8];\n    }\n\n    // set the contents of outputBuffer to contain the contents of buffer\n    for (int i = 0; i < 5; i++) {\n      outputBuffer[i] = char(buffer & 0xFF);\n      buffer = buffer >> 8;\n    }\n    // we perform a reversal because outputBuffer has the contents of buffer\n    // but in reverse order\n    std::reverse(outputBuffer.begin(), outputBuffer.end());\n\n    return true;\n  }\n};\n\nclass LegalStringTests\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<\n          std::pair<std::string, std::string>> {};\n\nclass LegalBinaryContentTests\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<\n          std::pair<std::string, std::string>> {};\n\nclass LegalIntegerTests\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<std::pair<std::string, int64_t>> {};\n\nclass LegalFloatTests\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<std::pair<std::string, double>> {};\n\nclass IllegalItemTest\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<std::string> {};\n\nclass IllegalListTest\n    : public StructuredHeadersStandardTest\n    , public ::testing::WithParamInterface<std::string> {};\n\nTEST_P(LegalStringTests, LegalStrings) {\n  std::string input(GetParam().first);\n  StructuredHeadersDecoder shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.decodeItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(output, GetParam().second);\n}\n\nTEST_P(LegalBinaryContentTests, LegalBinaryContent) {\n  std::string input(GetParam().first);\n  std::string expectedOutputInBase32(GetParam().second);\n  std::string expectedOutput;\n  decode32(expectedOutputInBase32, expectedOutput);\n\n  StructuredHeadersDecoder shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.decodeItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::BINARYCONTENT);\n  EXPECT_EQ(output, expectedOutput);\n}\n\nTEST_P(LegalIntegerTests, LegalIntegers) {\n  std::string input(GetParam().first);\n  StructuredHeadersDecoder shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.decodeItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(output, GetParam().second);\n}\n\nTEST_P(LegalFloatTests, LegalFloats) {\n  std::string input(GetParam().first);\n  StructuredHeadersDecoder shd(input);\n  StructuredHeaderItem output;\n  auto err = shd.decodeItem(output);\n  EXPECT_EQ(err, StructuredHeaders::DecodeError::OK);\n  EXPECT_EQ(output.tag, StructuredHeaderItem::Type::DOUBLE);\n  EXPECT_EQ(output, GetParam().second);\n}\n\nTEST_P(IllegalItemTest, IllegalItem) {\n  StructuredHeadersDecoder shd(GetParam());\n  StructuredHeaderItem output;\n  auto err = shd.decodeItem(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nTEST_P(IllegalListTest, IllegalList) {\n  StructuredHeadersDecoder shd(GetParam());\n  std::vector<StructuredHeaderItem> output;\n  auto err = shd.decodeList(output);\n  EXPECT_NE(err, StructuredHeaders::DecodeError::OK);\n}\n\nINSTANTIATE_TEST_SUITE_P(TestLegalStrings,\n                         LegalStringTests,\n                         ::testing::ValuesIn(kLegalStringTests));\n\nINSTANTIATE_TEST_SUITE_P(TestLegalBinaryContent,\n                         LegalBinaryContentTests,\n                         ::testing::ValuesIn(kLegalBinContentTests));\n\nINSTANTIATE_TEST_SUITE_P(TestLegalInts,\n                         LegalIntegerTests,\n                         ::testing::ValuesIn(kLegalIntTests));\n\nINSTANTIATE_TEST_SUITE_P(TestLegalFloats,\n                         LegalFloatTests,\n                         ::testing::ValuesIn(kLegalFloatTests));\n\nINSTANTIATE_TEST_SUITE_P(TestIllegalItems,\n                         IllegalItemTest,\n                         ::testing::ValuesIn(kIllegalItemTests));\n\nINSTANTIATE_TEST_SUITE_P(TestIllegalLists,\n                         IllegalListTest,\n                         ::testing::ValuesIn(kIllegalListTests));\n\nTEST_F(StructuredHeadersStandardTest, TestBasicList) {\n  std::string input(\"1, 42\");\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  auto err = shd.decodeList(v);\n  EXPECT_EQ(err, DecodeError::OK);\n\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(1));\n  EXPECT_EQ(v[1], int64_t(42));\n}\n\nTEST_F(StructuredHeadersStandardTest, TestSingleItemList) {\n  std::string input(\"42\");\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  auto err = shd.decodeList(v);\n  EXPECT_EQ(err, DecodeError::OK);\n\n  EXPECT_EQ(v.size(), 1);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(42));\n}\n\nTEST_F(StructuredHeadersStandardTest, TestNoWhitespaceList) {\n  std::string input(\"1,42\");\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  auto err = shd.decodeList(v);\n  EXPECT_EQ(err, DecodeError::OK);\n\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(1));\n  EXPECT_EQ(v[1], int64_t(42));\n}\n\nTEST_F(StructuredHeadersStandardTest, TestExtraWhitespaceList) {\n  std::string input(\"1 , 42\");\n  StructuredHeadersDecoder shd(input);\n\n  std::vector<StructuredHeaderItem> v;\n  auto err = shd.decodeList(v);\n  EXPECT_EQ(err, DecodeError::OK);\n\n  EXPECT_EQ(v.size(), 2);\n\n  EXPECT_EQ(v[0].tag, StructuredHeaderItem::Type::INT64);\n  EXPECT_EQ(v[1].tag, StructuredHeaderItem::Type::INT64);\n\n  EXPECT_EQ(v[0], int64_t(1));\n  EXPECT_EQ(v[1], int64_t(42));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/structuredheaders/test/StructuredHeadersUtilitiesTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersUtilities.h>\n\n#include <folly/portability/GTest.h>\n#include <string>\n\nnamespace proxygen::StructuredHeaders {\n\nclass StructuredHeadersUtilitiesTest : public testing::Test {};\n\nTEST_F(StructuredHeadersUtilitiesTest, TestLcalpha) {\n  for (uint32_t i = 0; i < 256; i++) {\n    auto c = (uint8_t)i;\n    if (c >= 'a' && c <= 'z') {\n      EXPECT_TRUE(isLcAlpha(c));\n    } else {\n      EXPECT_FALSE(isLcAlpha(c));\n    }\n  }\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidIdentifierChar) {\n  for (uint32_t i = 0; i < 256; i++) {\n    auto c = (uint8_t)i;\n    if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||\n        (c == '_' || c == '-' || c == '*' || c == '/')) {\n      EXPECT_TRUE(isValidIdentifierChar(c));\n    } else {\n      EXPECT_FALSE(isValidIdentifierChar(c));\n    }\n  }\n}\n\nTEST_F(StructuredHeadersUtilitiesTest,\n       test_isValidEncodedBinaryContentChar_alphanumeric) {\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('a'));\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('Z'));\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('0'));\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('9'));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest,\n       test_isValidEncodedBinaryContentChar_allowed_symbols) {\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('+'));\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('/'));\n  EXPECT_TRUE(isValidEncodedBinaryContentChar('='));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest,\n       test_isValidEncodedBinaryContentChar_disallowed_symbols) {\n  EXPECT_FALSE(isValidEncodedBinaryContentChar('*'));\n  EXPECT_FALSE(isValidEncodedBinaryContentChar('_'));\n  EXPECT_FALSE(isValidEncodedBinaryContentChar('-'));\n  EXPECT_FALSE(isValidEncodedBinaryContentChar(' '));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidStringCharAllowed) {\n  EXPECT_TRUE(isValidStringChar(' '));\n  EXPECT_TRUE(isValidStringChar('~'));\n  EXPECT_TRUE(isValidStringChar('\\\\'));\n  EXPECT_TRUE(isValidStringChar('\\\"'));\n  EXPECT_TRUE(isValidStringChar('a'));\n  EXPECT_TRUE(isValidStringChar('0'));\n  EXPECT_TRUE(isValidStringChar('A'));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidStringCharDisallowed) {\n  EXPECT_FALSE(isValidStringChar('\\0'));\n  EXPECT_FALSE(isValidStringChar(0x1F));\n  EXPECT_FALSE(isValidStringChar(0x7F));\n  EXPECT_FALSE(isValidStringChar('\\t'));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidIdentifierAllowed) {\n  EXPECT_TRUE(isValidIdentifier(\"a\"));\n  EXPECT_TRUE(isValidIdentifier(\"a_0-*/\"));\n  EXPECT_TRUE(isValidIdentifier(\"abc___xyz\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidIdentifierDisallowed) {\n  EXPECT_FALSE(isValidIdentifier(\"aAAA\"));\n  EXPECT_FALSE(isValidIdentifier(\"_aa\"));\n  EXPECT_FALSE(isValidIdentifier(\"0abc\"));\n  EXPECT_FALSE(isValidIdentifier(\"\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidStringAllowed) {\n  EXPECT_TRUE(isValidString(\"a cat.\"));\n  EXPECT_TRUE(isValidString(\"!~)($@^^) g\"));\n  EXPECT_TRUE(isValidString(\"\\\\\\\"\\\"\\\\\"));\n  EXPECT_TRUE(isValidString(\"\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestIsValidStringDisallowed) {\n  EXPECT_FALSE(isValidString(\"a\\tcat.\"));\n  EXPECT_FALSE(isValidString(\"\\x10 aaaaaaa\"));\n  EXPECT_FALSE(isValidString(\"chocolate\\x11\"));\n  EXPECT_FALSE(isValidString(\"pota\\nto\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestGoodBinaryContent) {\n  EXPECT_TRUE(isValidEncodedBinaryContent(\"aGVsbG8=\"));\n  EXPECT_TRUE(isValidEncodedBinaryContent(\"ZGZzZGZmc2Rm\"));\n  EXPECT_TRUE(isValidEncodedBinaryContent(\"ZA==\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestBadBinaryContent) {\n  EXPECT_FALSE(isValidEncodedBinaryContent(\"aGVsbG8\"));\n  EXPECT_FALSE(isValidEncodedBinaryContent(\"aGVsb G8=\"));\n  EXPECT_FALSE(isValidEncodedBinaryContent(\"aGVsbG!8=\"));\n  EXPECT_FALSE(isValidEncodedBinaryContent(\"=aGVsbG8\"));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestItemTypeMatchesContentGood) {\n  StructuredHeaderItem item;\n  item.value = std::string(\"\\\"potato\\\"\");\n  item.tag = StructuredHeaderItem::Type::STRING;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n\n  item.value = std::string(\"a_800\");\n  item.tag = StructuredHeaderItem::Type::IDENTIFIER;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n\n  item.tag = StructuredHeaderItem::Type::NONE;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n\n  item.value = std::string(\"hello\");\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n\n  item.value = int64_t(88);\n  item.tag = StructuredHeaderItem::Type::INT64;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n\n  item.value = double(88.8);\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  EXPECT_TRUE(itemTypeMatchesContent(item));\n}\n\nTEST_F(StructuredHeadersUtilitiesTest, TestItemTypeMatchesContentBad) {\n  StructuredHeaderItem item;\n\n  item.value = std::string(\"hello\");\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::INT64;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n\n  item.value = int64_t(68);\n  item.tag = StructuredHeaderItem::Type::DOUBLE;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::STRING;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::IDENTIFIER;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n\n  item.value = double(68.8);\n  item.tag = StructuredHeaderItem::Type::INT64;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::IDENTIFIER;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::STRING;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n  item.tag = StructuredHeaderItem::Type::BINARYCONTENT;\n  EXPECT_FALSE(itemTypeMatchesContent(item));\n}\n\n} // namespace proxygen::StructuredHeaders\n"
  },
  {
    "path": "proxygen/lib/http/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET LibHTTPTests\n  SOURCES\n    HTTPCommonHeadersTests.cpp\n    HTTPConnectorWithFizzTest.cpp\n    HTTPMessageTest.cpp\n    HTTPPriorityFunctionsTest.cpp\n    ProxyStatusTest.cpp\n    RFC2616Test.cpp\n    WindowTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPCommonHeadersTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n\nusing namespace proxygen;\n\nclass HTTPCommonHeadersTests : public testing::Test {};\n\nTEST_F(HTTPCommonHeadersTests, TestHashing) {\n  std::string common1(\"Content-Length\");\n  std::string common2(\"content-length\");\n  std::string uncommon(\"Uncommon\");\n\n  HTTPHeaderCode commonCode1 = HTTPCommonHeaders::hash(common1);\n  HTTPHeaderCode commonCode2 = HTTPCommonHeaders::hash(common2);\n  HTTPHeaderCode uncommonCode = HTTPCommonHeaders::hash(uncommon);\n\n  EXPECT_EQ(uncommonCode, HTTPHeaderCode::HTTP_HEADER_OTHER);\n  EXPECT_NE(commonCode1, HTTPHeaderCode::HTTP_HEADER_OTHER);\n\n  EXPECT_EQ(commonCode1, commonCode2);\n}\n\nTEST_F(HTTPCommonHeadersTests, TestTwoTablesInitialized) {\n  std::string common(\"Content-Length\");\n  HTTPHeaderCode code = HTTPCommonHeaders::hash(common);\n\n  EXPECT_EQ(*HTTPCommonHeaders::getPointerToName(code), \"Content-Length\");\n  EXPECT_EQ(*HTTPCommonHeaders::getPointerToName(\n                code, HTTPCommonHeaderTableType::TABLE_LOWERCASE),\n            \"content-length\");\n}\n\nTEST_F(HTTPCommonHeadersTests, TestIsCommonHeaderNameFromTable) {\n  // The first two hardcoded headers are not considered actual common headers\n  EXPECT_FALSE(HTTPCommonHeaders::isNameFromTable(\n      HTTPCommonHeaders::getPointerToName(HTTP_HEADER_NONE),\n      HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n  EXPECT_FALSE(HTTPCommonHeaders::isNameFromTable(\n      HTTPCommonHeaders::getPointerToName(HTTP_HEADER_OTHER),\n      HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n\n  // Verify that the first actual common header in the address table checks out\n  // Assuming there is at least one common header in the table (first two\n  // entries are HTTP_HEADER_NONE and HTTP_HEADER_OTHER)\n  if (HTTPCommonHeaders::num_codes > HTTPHeaderCodeCommonOffset) {\n    EXPECT_TRUE(HTTPCommonHeaders::isNameFromTable(\n        HTTPCommonHeaders::getPointerToName(\n            static_cast<HTTPHeaderCode>(HTTPHeaderCodeCommonOffset + 1)),\n        HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n\n    // Verify that the last header in the common address table checks out\n    EXPECT_TRUE(HTTPCommonHeaders::isNameFromTable(\n        HTTPCommonHeaders::getPointerToName(\n            static_cast<HTTPHeaderCode>(HTTPCommonHeaders::num_codes - 1)),\n        HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n  }\n\n  // Verify that a random header is not identified as being part of the common\n  // address table\n  std::string externalHeader = \"externalHeader\";\n  EXPECT_FALSE(HTTPCommonHeaders::isNameFromTable(\n      &externalHeader, HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n}\n\nTEST_F(HTTPCommonHeadersTests, TestGetHeaderCodeFromTableCommonHeaderName) {\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    auto code = static_cast<HTTPHeaderCode>(j);\n    EXPECT_TRUE(code == HTTPCommonHeaders::getCodeFromTableName(\n                            HTTPCommonHeaders::getPointerToName(code),\n                            HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n  }\n  std::string externalHeader = \"externalHeader\";\n  EXPECT_TRUE(HTTP_HEADER_OTHER ==\n              HTTPCommonHeaders::getCodeFromTableName(\n                  &externalHeader, HTTPCommonHeaderTableType::TABLE_CAMELCASE));\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPConnectorWithFizzTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n\n#include <fizz/protocol/test/CertUtil.h>\n#include <fizz/server/AsyncFizzServer.h>\n#include <fizz/server/test/Mocks.h>\n#include <fizz/server/test/Utils.h>\n#include <folly/io/async/AsyncServerSocket.h>\n#include <proxygen/lib/http/HTTPConnectorWithFizz.h>\n#include <proxygen/lib/http/session/HTTPUpstreamSession.h>\n\nusing namespace proxygen;\nusing namespace testing;\nusing namespace fizz::server;\n\nclass MockHTTPConnectorCallback : public HTTPConnector::Callback {\n public:\n  ~MockHTTPConnectorCallback() override = default;\n  MOCK_METHOD(void, connectSuccess, (HTTPUpstreamSession * session));\n  MOCK_METHOD(void, connectError, (const folly::AsyncSocketException& ex));\n  MOCK_METHOD(void, preConnect, (folly::AsyncTransport * transport));\n};\n\nclass HTTPConnectorWithFizzTest : public testing::Test {\n public:\n  HTTPConnectorWithFizzTest()\n      : evb_(true), factory_(&handshakeCb_), server_(evb_, &factory_) {\n  }\n\n  void SetUp() override {\n    timer_ = folly::HHWheelTimer::newTimer(\n        &evb_,\n        std::chrono::milliseconds(folly::HHWheelTimer::DEFAULT_TICK_INTERVAL),\n        folly::AsyncTimeout::InternalEnum::NORMAL,\n        std::chrono::milliseconds(5000));\n\n    EXPECT_CALL(cb_, preConnect(_))\n        .WillOnce(Invoke([](folly::AsyncTransport* t) {\n          EXPECT_NE(t, nullptr);\n          EXPECT_NE(t->getUnderlyingTransport<fizz::client::AsyncFizzClient>(),\n                    nullptr);\n        }));\n  }\n\n protected:\n  class DummyCallbackFactory\n      : public fizz::server::test::FizzTestServer::CallbackFactory {\n   public:\n    explicit DummyCallbackFactory(fizz::server::test::MockHandshakeCallback* cb)\n        : cb_(cb) {\n    }\n\n    AsyncFizzServer::HandshakeCallback* getCallback(\n        std::shared_ptr<AsyncFizzServer> srv) override {\n      // Keep connection alive\n      conn_ = srv;\n      return cb_;\n    }\n\n   private:\n    AsyncFizzServer::HandshakeCallback* cb_;\n    std::shared_ptr<AsyncFizzServer> conn_;\n  };\n  void SetupFailureCallbacks() {\n    ON_CALL(handshakeCb_, _fizzHandshakeError(_))\n        .WillByDefault(Invoke([&](folly::exception_wrapper ex) {\n          evb_.terminateLoopSoon();\n          if (ex.what().toStdString().find(\"readEOF()\") == std::string::npos) {\n            FAIL() << \"Server error handler called: \"\n                   << ex.what().toStdString();\n          }\n        }));\n    ON_CALL(cb_, connectError(_))\n        .WillByDefault(Invoke([&](const folly::AsyncSocketException& ex) {\n          evb_.terminateLoopSoon();\n          FAIL() << \"Client error handler called: \" << ex.what();\n        }));\n  }\n  folly::EventBase evb_;\n  fizz::server::test::MockHandshakeCallback handshakeCb_;\n  DummyCallbackFactory factory_;\n  fizz::server::test::FizzTestServer server_;\n  folly::HHWheelTimer::UniquePtr timer_;\n  MockHTTPConnectorCallback cb_;\n};\n\nTEST_F(HTTPConnectorWithFizzTest, TestFizzConnect) {\n  SetupFailureCallbacks();\n  HTTPConnectorWithFizz connector(&cb_, timer_.get());\n  proxygen::HTTPUpstreamSession* session = nullptr;\n  EXPECT_CALL(cb_, connectSuccess(_))\n      .WillOnce(\n          Invoke([&](proxygen::HTTPUpstreamSession* sess) { session = sess; }));\n\n  auto context = std::make_shared<fizz::client::FizzClientContext>();\n  connector.connectFizz(&evb_, server_.getAddress(), context, nullptr);\n  EXPECT_CALL(handshakeCb_, _fizzHandshakeSuccess()).WillOnce(Invoke([&]() {\n    evb_.terminateLoopSoon();\n  }));\n  evb_.loop();\n  if (session) {\n    session->dropConnection();\n  }\n}\n\nTEST_F(HTTPConnectorWithFizzTest, TestFizzConnectFailure) {\n  HTTPConnectorWithFizz connector(&cb_, timer_.get());\n\n  auto serverContext = server_.getFizzContext();\n  serverContext->setSupportedCiphers(\n      {{fizz::CipherSuite::TLS_AES_128_GCM_SHA256}});\n\n  auto context = std::make_shared<fizz::client::FizzClientContext>();\n  context->setSupportedCiphers({fizz::CipherSuite::TLS_AES_256_GCM_SHA384});\n\n  connector.connectFizz(&evb_, server_.getAddress(), context, nullptr);\n  EXPECT_CALL(handshakeCb_, _fizzHandshakeError(_));\n  EXPECT_CALL(cb_, connectError(_)).WillOnce(InvokeWithoutArgs([&]() {\n    evb_.terminateLoopSoon();\n  }));\n  evb_.loop();\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPHeadersBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <algorithm>\n#include <folly/Benchmark.h>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n\nusing namespace proxygen;\n\n// buck build @mode/opt proxygen/lib/http/test:http_headers_benchmark\n// ./buck-out/gen/proxygen/lib/http/test/http_headers_benchmark -bm_min_iters\n// 100\n// ============================================================================\n// proxygen/lib/http/test/HTTPHeadersBenchmark.cpp relative  time/iter  iters/s\n// ============================================================================\n// HTTPCommonHeadersHash                                        3.50us  285.82K\n// HTTPCommonHeadersGetHeaderCodeFromTableCommonHeaderName    161.70ns    6.18M\n// memchr                                                       1.02us  976.02K\n// stdFind                                                      5.59us  178.94K\n// ============================================================================\n\nnamespace {\n\nstd::vector<HTTPHeaderCode> getTestHeaderCodes() {\n  std::vector<HTTPHeaderCode> testHeaderCodes;\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    testHeaderCodes.push_back(static_cast<HTTPHeaderCode>(j));\n  }\n  return testHeaderCodes;\n}\n\nstd::vector<const std::string*> getTestHeaderStrings() {\n  std::vector<const std::string*> testHeaderStrings;\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    testHeaderStrings.push_back(\n        HTTPCommonHeaders::getPointerToName(static_cast<HTTPHeaderCode>(j)));\n  }\n  return testHeaderStrings;\n}\n\nstatic const std::string* testHeaderNames =\n    HTTPCommonHeaders::getPointerToName(HTTP_HEADER_NONE);\n\nstatic const std::vector<HTTPHeaderCode> testHeaderCodes = getTestHeaderCodes();\n\nstatic const std::vector<const std::string*> testHeaderStrings =\n    getTestHeaderStrings();\n\n} // namespace\n\nvoid HTTPCommonHeadersHashBench(int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& testHeaderString : testHeaderStrings) {\n      HTTPCommonHeaders::hash(*testHeaderString);\n    }\n  }\n}\n\nvoid HTTPCommonHeadersGetHeaderCodeFromTableCommonHeaderNameBench(int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (uint64_t j = HTTPHeaderCodeCommonOffset;\n         j < HTTPCommonHeaders::num_codes;\n         ++j) {\n      HTTPCommonHeaders::getCodeFromTableName(\n          &testHeaderNames[j], HTTPCommonHeaderTableType::TABLE_CAMELCASE);\n    }\n  }\n}\n\nBENCHMARK(HTTPCommonHeadersHash, iters) {\n  HTTPCommonHeadersHashBench(iters);\n}\n\nBENCHMARK(HTTPCommonHeadersGetHeaderCodeFromTableCommonHeaderName, iters) {\n  HTTPCommonHeadersGetHeaderCodeFromTableCommonHeaderNameBench(iters);\n}\n\nvoid memchrBench(int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (uint64_t j = HTTPHeaderCodeCommonOffset;\n         j < HTTPCommonHeaders::num_codes;\n         ++j) {\n      CHECK(memchr((void*)testHeaderCodes.data(),\n                   static_cast<HTTPHeaderCode>(j),\n                   testHeaderCodes.size()) != nullptr);\n    }\n  }\n}\n\nvoid stdFindBench(int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (uint64_t j = HTTPHeaderCodeCommonOffset;\n         j < HTTPCommonHeaders::num_codes;\n         ++j) {\n      auto address =\n          HTTPCommonHeaders::getPointerToName(static_cast<HTTPHeaderCode>(j));\n      CHECK(std::find(testHeaderStrings.begin(),\n                      testHeaderStrings.end(),\n                      address) != testHeaderStrings.end());\n    }\n  }\n}\n\nBENCHMARK(memchr, iters) {\n  memchrBench(iters);\n}\n\nBENCHMARK(stdFind, iters) {\n  stdFindBench(iters);\n}\n\nvoid addCodeBench(int nHeaders, int hdrSize, int iters) {\n  std::string value(hdrSize, 'a');\n  for (int i = 0; i < iters; ++i) {\n    HTTPHeaders headers;\n    for (int j = 0; j < nHeaders; ++j) {\n      headers.add(HTTP_HEADER_HOST, value);\n    }\n  }\n}\n\nBENCHMARK(addCode4_headers_8_length, iters) {\n  addCodeBench(4, 8, iters);\n}\n\nBENCHMARK(addCode4_headers_32_length, iters) {\n  addCodeBench(4, 32, iters);\n}\n\nBENCHMARK(addCode16_headers_8_length, iters) {\n  addCodeBench(16, 8, iters);\n}\n\nBENCHMARK(addCode16_headers_32_length, iters) {\n  addCodeBench(16, 32, iters);\n}\n\nBENCHMARK(addCode24_headers_8_length, iters) {\n  addCodeBench(24, 8, iters);\n}\n\nBENCHMARK(addCode24_headers_32_length, iters) {\n  addCodeBench(24, 32, iters);\n}\n\nint main(int argc, char** argv) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  folly::runBenchmarks();\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPMessageFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPMessageFilters.h>\n#include <proxygen/lib/http/session/test/HTTPTransactionMocks.h>\n#include <proxygen/lib/http/sink/HTTPTransactionSink.h>\n#include <proxygen/lib/http/test/MockHTTPMessageFilter.h>\n\nusing namespace proxygen;\n\nclass TestFilter : public HTTPMessageFilter {\n  std::unique_ptr<HTTPMessageFilter> clone() noexcept override {\n    return nullptr;\n  }\n};\n\nTEST(HTTPMessageFilter, TestFilterPauseResumePropagatedToFilter) {\n  //              prev               prev\n  // testFilter2 -----> testFilter1 -----> mockFilter\n\n  TestFilter testFilter1;\n  TestFilter testFilter2;\n  MockHTTPMessageFilter mockFilter;\n\n  testFilter2.setPrevFilter(&testFilter1);\n  testFilter1.setPrevFilter(&mockFilter);\n\n  EXPECT_CALL(mockFilter, pause());\n  testFilter2.pause();\n\n  EXPECT_CALL(mockFilter, resume(10));\n  testFilter2.resume(10);\n\n  // Pause the chain\n  EXPECT_CALL(mockFilter, pause());\n  testFilter2.pause();\n\n  MockHTTPMessageFilter mockFilter2;\n\n  // Switch to a new handler, should get pauseIngress\n  EXPECT_CALL(mockFilter2, pause());\n  testFilter1.setPrevFilter(&mockFilter2);\n}\n\nTEST(HTTPMessageFilter, TestFilterPauseResumePropagatedToTxn) {\n  //              prev               prev\n  // testFilter2 -----> testFilter1 -----> mockFilter\n\n  TestFilter testFilter1;\n  TestFilter testFilter2;\n\n  HTTP2PriorityQueue q;\n  MockHTTPTransaction mockTxn(TransportDirection::UPSTREAM, 1, 0, q);\n\n  auto sink = std::make_unique<HTTPTransactionSink>(&mockTxn);\n  testFilter2.setPrevFilter(&testFilter1);\n  testFilter1.setPrevSink(sink.get());\n\n  EXPECT_CALL(mockTxn, pauseIngress());\n  testFilter2.pause();\n\n  EXPECT_CALL(mockTxn, resumeIngress());\n  testFilter2.resume(10);\n\n  // Pause the chain\n  EXPECT_CALL(mockTxn, pauseIngress());\n  testFilter2.pause();\n\n  MockHTTPTransaction mockTxn2(TransportDirection::UPSTREAM, 2, 0, q);\n  auto sink2 = std::make_unique<HTTPTransactionSink>(&mockTxn2);\n\n  // Switch to a new handler, should get pauseIngress\n  EXPECT_CALL(mockTxn2, pauseIngress());\n  testFilter1.setPrevSink(sink2.get());\n}\n\nTEST(HTTPMessageFilter, TestFilterOnBodyDataTracking) {\n  //             next\n  // testFilter -----> mockFilter\n\n  TestFilter testFilter;\n  MockHTTPMessageFilter mockFilter;\n  mockFilter.setTrackDataPassedThrough(true);\n\n  testFilter.setNextTransactionHandler(&mockFilter);\n\n  EXPECT_CALL(mockFilter, onBody(testing::_)).Times(1);\n\n  std::string bodyContent = \"Hello\";\n  auto body = folly::IOBuf::copyBuffer(bodyContent);\n  testFilter.onBody(std::move(body));\n  auto dataPassedToNext = mockFilter.bodyDataSinceLastCheck();\n  dataPassedToNext->coalesce();\n  auto len = dataPassedToNext->computeChainDataLength();\n  EXPECT_EQ(bodyContent.size(), len);\n  const char* p = reinterpret_cast<const char*>(dataPassedToNext->data());\n  EXPECT_EQ(bodyContent, std::string(p, len));\n\n  EXPECT_CALL(mockFilter, onBody(testing::_)).Times(1);\n\n  bodyContent = \"World\";\n  body = folly::IOBuf::copyBuffer(bodyContent);\n  testFilter.onBody(std::move(body));\n  dataPassedToNext = mockFilter.bodyDataSinceLastCheck();\n  dataPassedToNext->coalesce();\n  len = dataPassedToNext->computeChainDataLength();\n  EXPECT_EQ(bodyContent.size(), len);\n  p = reinterpret_cast<const char*>(dataPassedToNext->data());\n  EXPECT_EQ(bodyContent, std::string(p, len));\n}\n\nTEST(HTTPMessageFilter, TestFilterPauseResumeAfterTxnDetached) {\n  //              prev               prev               prev\n  // testFilter2 -----> mockFilter -----> testFilter1 -----> mockTxn\n\n  TestFilter testFilter1;\n  TestFilter testFilter2;\n  MockHTTPMessageFilter mockFilter;\n\n  HTTP2PriorityQueue q;\n  MockHTTPTransaction mockTxn(TransportDirection::UPSTREAM, 1, 0, q);\n\n  auto sink = std::make_unique<HTTPTransactionSink>(&mockTxn);\n  testFilter2.setPrevFilter(&mockFilter);\n  mockFilter.setPrevFilter(&testFilter1);\n  testFilter1.setPrevSink(sink.get());\n\n  testFilter1.setNextTransactionHandler(&mockFilter);\n  mockFilter.setNextTransactionHandler(&testFilter2);\n\n  testFilter1.detachTransaction();\n\n  EXPECT_CALL(mockTxn, pauseIngress()).Times(0);\n  EXPECT_CALL(mockFilter, pause());\n  testFilter2.pause();\n\n  EXPECT_CALL(mockTxn, resumeIngress()).Times(0);\n  EXPECT_CALL(mockFilter, resume(10));\n  testFilter2.resume(10);\n}\n\nTEST(HTTPMessageFilter, TestFilterDetachHandlerFromTransaction) {\n  //                prev\n  // testFilter1 -----> mockTxn\n\n  TestFilter testFilter1;\n\n  HTTP2PriorityQueue q;\n  MockHTTPTransaction mockTxn(TransportDirection::UPSTREAM, 1, 0, q);\n\n  auto sink = std::make_unique<HTTPTransactionSink>(&mockTxn);\n  testFilter1.setPrevSink(sink.get());\n\n  EXPECT_CALL(mockTxn, setHandler(nullptr));\n  testFilter1.detachHandlerFromSink(std::move(sink));\n}\n\nTEST(HTTPMessageFilter, TestFilterDetachHandlerNoTransaction) {\n  TestFilter testFilter1;\n  // detach when filter does not hold a pointer to transaction\n  EXPECT_NO_THROW(testFilter1.detachHandlerFromSink(nullptr));\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPMessageTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/HTTPMessage.h>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n#include <proxygen/lib/utils/TestUtils.h>\n#include <string.h>\n\nusing namespace proxygen;\nusing namespace std;\n\nTEST(HTTPMessage, TestParseCookiesSimple) {\n  HTTPMessage msg;\n\n  msg.getHeaders().add(\"Cookie\", \"id=1256679245; data=0:1234567\");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"0:1234567\");\n  EXPECT_EQ(msg.getCookie(\"mising\"), \"\");\n}\n\nTEST(HTTPMessage, TestParseCookiesSpaces) {\n  HTTPMessage msg;\n\n  msg.getHeaders().add(\"Cookie\", \" id=1256679245 ;   data=0:1234567  ;\");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"0:1234567\");\n}\n\nTEST(HTTPMessage, TestParseCookiesSingleCookie) {\n  HTTPMessage msg;\n\n  msg.getHeaders().add(\"Cookie\", \"   user_id=1256679245  \");\n  EXPECT_EQ(msg.getCookie(\"user_id\"), \"1256679245\");\n}\n\nTEST(HTTPMessage, TestParseCookiesMultipleCookies) {\n  HTTPMessage msg;\n\n  msg.getHeaders().add(\"Cookie\",\n                       \"id=1256679245; data=0:1234567; same=Always; Name\");\n  msg.getHeaders().add(\"Cookie\",\n                       \"id2=1256679245; data2=0:1234567; same=Always; \");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"id2\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"0:1234567\");\n  EXPECT_EQ(msg.getCookie(\"data2\"), \"0:1234567\");\n  EXPECT_EQ(msg.getCookie(\"same\"), \"Always\");\n  EXPECT_EQ(msg.getCookie(\"Name\"), \"\");\n}\n\nTEST(HTTPMessage, TestRemoveCookie) {\n  HTTPMessage msg;\n\n  // Test removing a cookie that exists\n  msg.getHeaders().set(\"Cookie\",\n                       \"id=1256679245; data=0:1234567; session=abc123\");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"0:1234567\");\n  EXPECT_EQ(msg.getCookie(\"session\"), \"abc123\");\n\n  // Remove middle cookie\n  msg.removeCookie(\"data\");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"\");\n  EXPECT_EQ(msg.getCookie(\"session\"), \"abc123\");\n\n  // Verify Cookie header was reconstructed properly\n  const std::string& cookieHeader = msg.getHeaders().getSingleOrEmpty(\"Cookie\");\n  EXPECT_TRUE(cookieHeader.find(\"id=1256679245\") != std::string::npos);\n  EXPECT_TRUE(cookieHeader.find(\"session=abc123\") != std::string::npos);\n  EXPECT_TRUE(cookieHeader.find(\"data=0:1234567\") == std::string::npos);\n\n  // Remove first cookie\n  msg.removeCookie(\"id\");\n  EXPECT_EQ(msg.getCookie(\"id\"), \"\");\n  EXPECT_EQ(msg.getCookie(\"session\"), \"abc123\");\n\n  // Remove last remaining cookie - should remove Cookie header entirely\n  msg.removeCookie(\"session\");\n  EXPECT_EQ(msg.getCookie(\"session\"), \"\");\n  EXPECT_FALSE(msg.getHeaders().exists(\"Cookie\"));\n}\n\nTEST(HTTPMessage, TestRemoveCookieNonExistent) {\n  HTTPMessage msg;\n\n  // Test removing a cookie that doesn't exist\n  msg.getHeaders().set(\"Cookie\", \"id=1256679245; data=0:1234567\");\n\n  // Try to remove non-existent cookie\n  msg.removeCookie(\"nonexistent\");\n\n  // Original cookies should still be there\n  EXPECT_EQ(msg.getCookie(\"id\"), \"1256679245\");\n  EXPECT_EQ(msg.getCookie(\"data\"), \"0:1234567\");\n\n  // Cookie header should be unchanged\n  const std::string& cookieHeader = msg.getHeaders().getSingleOrEmpty(\"Cookie\");\n  EXPECT_TRUE(cookieHeader.find(\"id=1256679245\") != std::string::npos);\n  EXPECT_TRUE(cookieHeader.find(\"data=0:1234567\") != std::string::npos);\n}\n\nTEST(HTTPMessage, TestRemoveCookieEmptyHeader) {\n  HTTPMessage msg;\n\n  // Test removing cookie when no Cookie header exists\n  msg.removeCookie(\"nonexistent\");\n\n  EXPECT_FALSE(msg.getHeaders().exists(\"Cookie\"));\n  EXPECT_EQ(msg.getCookie(\"nonexistent\"), \"\");\n}\n\nTEST(HTTPMessage, TestParseQueryParamsSimple) {\n  HTTPMessage msg;\n  string url =\n      \"/test?seq=123456&userid=1256679245&dup=1&dup=2&helloWorld\"\n      \"&second=was+it+clear+%28already%29%3F\";\n\n  msg.setURL(url);\n  EXPECT_EQ(msg.getQueryParam(\"seq\"), \"123456\");\n  EXPECT_EQ(msg.getQueryParam(\"userid\"), \"1256679245\");\n  EXPECT_EQ(msg.getQueryParam(\"dup\"), \"2\");\n  EXPECT_EQ(msg.getQueryParam(\"helloWorld\"), \"\");\n  EXPECT_EQ(msg.getIntQueryParam(\"dup\", 5), 2);\n  EXPECT_EQ(msg.getIntQueryParam(\"abc\", 5), 5);\n  EXPECT_EQ(msg.getDecodedQueryParam(\"second\"), \"was it clear (already)?\");\n  EXPECT_EQ(msg.getDecodedQueryParam(\"seq\"), \"123456\");\n  EXPECT_EQ(msg.hasQueryParam(\"seq\"), true);\n  EXPECT_EQ(msg.hasQueryParam(\"seq1\"), false);\n  EXPECT_EQ(msg.getIntQueryParam(\"dup\"), 2);\n  EXPECT_ANY_THROW(msg.getIntQueryParam(\"abc\"));\n  EXPECT_ANY_THROW(msg.getIntQueryParam(\"second\"));\n\n  const auto& param = msg.getQueryParam(\"seq\");\n  msg.setQueryParam(\"foo\", \"bar\");\n  EXPECT_EQ(param, \"123456\");\n  msg.removeQueryParam(\"foo\");\n  EXPECT_EQ(param, \"123456\");\n}\n\nTEST(HTTPMessage, TestParseQueryParamsComplex) {\n  HTTPMessage msg;\n  std::vector<std::vector<std::string>> input = {\n      {\"\", \"\", \"\"},\n      {\"key_and_equal_but_no_value\", \"=\", \"\"},\n      {\"only_key\", \"\", \"\"},\n      {\"key_and_value\", \"=\", \"value\"},\n      {\"key_and_value_2\", \"=\", \"value2\"},\n      {\"key_and_value_3\", \"=\", \"value3\"}};\n\n  for (int i = 0; i < (1 << input.size()); ++i) {\n    std::vector<std::vector<std::string>> sub;\n    for (size_t j = 0; j < input.size(); ++j) {\n      if ((i & (1 << j))) {\n        sub.push_back(input[j]);\n      }\n    }\n\n    sort(sub.begin(), sub.end());\n    do {\n      bool first = true;\n      std::string url = \"/test?\";\n      for (const auto& val : sub) {\n        if (first) {\n          first = false;\n        } else {\n          url += \"&\";\n        }\n\n        url += val[0] + val[1] + val[2];\n      }\n\n      msg.setURL(url);\n      for (const auto& val : sub) {\n        if (val[0].empty()) {\n          continue;\n        }\n\n        EXPECT_EQ(val[2], msg.getQueryParam(val[0]));\n      }\n\n    } while (next_permutation(sub.begin(), sub.end()));\n  }\n}\n\nTEST(HTTPMessage, SetInvalidURL) {\n  HTTPMessage msg;\n\n  msg.setURL(\"http://www.foooooooooooooooooooo.com/bar\");\n  EXPECT_EQ(msg.getPathAsStringPiece(), \"/bar\");\n  msg.setURL(\"/\\t/?tbtkkukgrenncdlvlgbigerblcgjbkgb=1\");\n  EXPECT_EQ(msg.getPathAsStringPiece(), \"\");\n}\n\nTEST(HTTPMessage, SetURLEmpty) {\n  HTTPMessage msg;\n\n  auto res = msg.setURL(\"\");\n  EXPECT_FALSE(res.valid());\n  EXPECT_EQ(msg.getPathAsStringPiece(), \"\");\n}\n\nTEST(HTTPMessage, SetAbsoluteURLNoPath) {\n  HTTPMessage msg;\n\n  auto res = msg.setURL(\"http://www.foo.com\");\n  EXPECT_TRUE(res.valid());\n  EXPECT_EQ(msg.getPathAsStringPiece(), \"/\");\n  EXPECT_EQ(msg.getPath(), msg.getPathAsStringPiece());\n  // getPathAsStringPiece points to constant string and getPath points to copy\n  EXPECT_NE(msg.getPath().data(), msg.getPathAsStringPiece().begin());\n}\n\nTEST(HTTPMessage, TestHeaderPreservation) {\n  HTTPMessage msg;\n  HTTPHeaders& hdrs = msg.getHeaders();\n\n  hdrs.add(\"Jojo\", \"1\");\n  hdrs.add(\"Binky\", \"2\");\n  hdrs.add(\"jOJo\", \"3\");\n  hdrs.add(\"bINKy\", \"4\");\n\n  EXPECT_EQ(hdrs.size(), 4);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"jojo\"), 2);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"binky\"), 2);\n}\n\nTEST(HTTPMessage, TestHeaderRemove) {\n  HTTPMessage msg;\n  HTTPHeaders& hdrs = msg.getHeaders();\n\n  hdrs.add(\"Jojo\", \"1\");\n  hdrs.add(\"Binky\", \"2\");\n  hdrs.add(\"jOJo\", \"3\");\n  hdrs.add(\"bINKy\", \"4\");\n  hdrs.remove(\"jojo\");\n\n  EXPECT_EQ(hdrs.size(), 2);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"binky\"), 2);\n}\n\nTEST(HTTPMessage, TestAllVersionsHeaderRemove) {\n  HTTPMessage msg;\n  HTTPHeaders& hdrs = msg.getHeaders();\n\n  hdrs.add(\"Jojo_1_2\", \"1\");\n  hdrs.add(\"Binky\", \"2\");\n  hdrs.add(\"Jojo_1-2\", \"3\");\n  hdrs.add(\"Jojo-1_2\", \"4\");\n  hdrs.add(\"Jojo-1-2\", \"4\");\n  hdrs.removeAllVersions(HTTP_HEADER_NONE, \"Jojo_1_2\");\n  EXPECT_EQ(hdrs.size(), 1);\n\n  hdrs.add(\"Content-Length\", \"1\");\n  hdrs.add(\"Content_Length\", \"2\");\n  hdrs.removeAllVersions(HTTP_HEADER_CONTENT_LENGTH, \"Content-Length\");\n  EXPECT_EQ(hdrs.size(), 1);\n}\n\nTEST(HTTPMessage, TestSetHeader) {\n  HTTPMessage msg;\n  HTTPHeaders& hdrs = msg.getHeaders();\n\n  hdrs.set(\"Jojo\", \"1\");\n  EXPECT_EQ(hdrs.size(), 1);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"Jojo\"), 1);\n\n  hdrs.add(\"jojo\", \"2\");\n  hdrs.add(\"jojo\", \"3\");\n  hdrs.add(\"bar\", \"4\");\n  EXPECT_EQ(hdrs.size(), 4);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"Jojo\"), 3);\n\n  hdrs.set(\"joJO\", \"5\");\n  EXPECT_EQ(hdrs.size(), 2);\n  EXPECT_EQ(hdrs.getNumberOfValues(\"Jojo\"), 1);\n}\n\nTEST(HTTPMessage, TestCombine) {\n  HTTPMessage msg;\n  HTTPHeaders& headers = msg.getHeaders();\n\n  EXPECT_EQ(headers.combine(\"Combine\"), \"\");\n\n  headers.add(\"Combine\", \"first value\");\n  EXPECT_EQ(headers.combine(\"Combine\"), \"first value\");\n\n  headers.add(\"Combine\", \"second value\");\n  EXPECT_EQ(headers.combine(\"Combine\"), \"first value, second value\");\n\n  headers.add(\"Combine\", \"third value\");\n  EXPECT_EQ(headers.combine(\"Combine\"),\n            \"first value, second value, third value\");\n  VLOG(4) << msg;\n}\n\nTEST(HTTPMessage, TestProxification) {\n  HTTPMessage msg;\n\n  folly::SocketAddress dstAddr(\"192.168.1.1\", 1887);\n  msg.setDstAddress(dstAddr);\n  msg.setLocalIp(\"10.0.0.1\");\n\n  msg.ensureHostHeader();\n  msg.setWantsKeepalive(false);\n\n  HTTPHeaders& hdrs = msg.getHeaders();\n  EXPECT_EQ(\"192.168.1.1\", hdrs.getSingleOrEmpty(HTTP_HEADER_HOST));\n  EXPECT_FALSE(msg.wantsKeepalive());\n}\n\nTEST(HTTPMessage, TestSetClientAddress) {\n  HTTPMessage msg;\n\n  folly::SocketAddress clientAddr(\"74.125.127.9\", 1987);\n  msg.setClientAddress(clientAddr);\n  EXPECT_EQ(msg.getClientIP(), \"74.125.127.9\");\n  EXPECT_EQ(msg.getClientPort(), \"1987\");\n  // Now try cached path\n  EXPECT_EQ(msg.getClientIP(), \"74.125.127.9\");\n  EXPECT_EQ(msg.getClientPort(), \"1987\");\n\n  // Test updating client address\n  folly::SocketAddress clientAddr2(\"74.125.127.8\", 1988);\n  msg.setClientAddress(clientAddr2);\n  EXPECT_EQ(msg.getClientIP(), \"74.125.127.8\");\n  EXPECT_EQ(msg.getClientPort(), \"1988\");\n}\n\nTEST(HTTPMessage, BizarreVersions) {\n  HTTPMessage msg;\n\n  msg.setHTTPVersion(0, 22);\n  EXPECT_EQ(msg.getVersionString(), \"0.22\");\n  msg.setHTTPVersion(10, 1);\n  EXPECT_EQ(msg.getVersionString(), \"10.1\");\n}\n\nTEST(HTTPMessage, TestKeepaliveCheck) {\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 0);\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    EXPECT_TRUE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"close\");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"ClOsE\");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"foo,bar\");\n    EXPECT_TRUE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"foo,bar\");\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"abc,CLOSE,def\");\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"xyz\");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"foo,bar\");\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"abc ,  CLOSE , def\");\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"xyz\");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"  close \");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \",  close \");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 1);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"  close , \");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 0);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"Keep-Alive\");\n    EXPECT_TRUE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 0);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"keep-alive\");\n    EXPECT_TRUE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 0);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"keep-alive\");\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"close\");\n    EXPECT_FALSE(msg.computeKeepalive());\n  }\n\n  {\n    HTTPMessage msg;\n    msg.setHTTPVersion(1, 0);\n    msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"keep-alive\");\n    msg.stripPerHopHeaders();\n    EXPECT_TRUE(msg.computeKeepalive());\n  }\n}\n\nTEST(HTTPMessage, TestHeaderStripPerHop) {\n  HTTPMessage msg;\n\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"a, b, c\");\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \"d\");\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \",,,,\");\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \" , , , ,\");\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \", e\");\n  msg.getHeaders().add(HTTP_HEADER_CONNECTION, \" f ,\\tg\\t, \\r\\n\\th \");\n  msg.getHeaders().add(\"Keep-Alive\", \"true\");\n  msg.getHeaders().add(\"Priority\", \"u=5, i\");\n\n  msg.getHeaders().add(\"a\", \"1\");\n  msg.getHeaders().add(\"b\", \"2\");\n  msg.getHeaders().add(\"c\", \"3\");\n  msg.getHeaders().add(\"d\", \"4\");\n  msg.getHeaders().add(\"e\", \"5\");\n  msg.getHeaders().add(\"f\", \"6\");\n  msg.getHeaders().add(\"g\", \"7\");\n  msg.getHeaders().add(\"h\", \"8\");\n\n  EXPECT_EQ(msg.getHeaders().size(), 16);\n  msg.stripPerHopHeaders();\n  EXPECT_EQ(msg.getHeaders().size(), 1);\n  EXPECT_EQ(msg.getStrippedPerHopHeaders().size(), 15);\n  msg.stripPerHopHeaders(/* stripPriority */ true);\n  EXPECT_EQ(msg.getHeaders().size(), 0);\n  EXPECT_EQ(msg.getStrippedPerHopHeaders().size(), 1);\n}\n\nTEST(HTTPMessage, TestEmptyName) {\n  HTTPMessage msg;\n  EXPECT_DEATH_NO_CORE(msg.getHeaders().set(\"\", \"empty name?!\"), \".*\");\n}\n\nTEST(HTTPMessage, TestMethod) {\n  HTTPMessage msg;\n\n  msg.setMethod(HTTPMethod::GET);\n  EXPECT_EQ(\"GET\", msg.getMethodString());\n  EXPECT_EQ(HTTPMethod::GET == msg.getMethod(), true);\n\n  msg.setMethod(\"FOO\");\n  EXPECT_EQ(\"FOO\", msg.getMethodString());\n  EXPECT_EQ(folly::none, msg.getMethod());\n\n  msg.setMethod(HTTPMethod::CONNECT);\n  EXPECT_EQ(\"CONNECT\", msg.getMethodString());\n  EXPECT_EQ(HTTPMethod::CONNECT == msg.getMethod(), true);\n}\n\nvoid testPathAndQuery(const string& url,\n                      const string& expectedPath,\n                      const string& expectedQuery) {\n  HTTPMessage msg;\n  msg.setURL(url);\n\n  EXPECT_EQ(msg.getURL(), url);\n  EXPECT_EQ(msg.getPathAsStringPiece(), expectedPath);\n  EXPECT_EQ(msg.getPath(), expectedPath);\n  EXPECT_EQ(msg.getQueryStringAsStringPiece(), expectedQuery);\n  EXPECT_EQ(msg.getQueryString(), expectedQuery);\n}\n\nTEST(GetPathAndQuery, ParseURL) {\n  testPathAndQuery(\"http://localhost:80/foo?bar#qqq\", \"/foo\", \"bar\");\n  testPathAndQuery(\"localhost:80/foo?bar#qqq\", \"/foo\", \"bar\");\n  testPathAndQuery(\"localhost\", \"/\", \"\");\n  testPathAndQuery(\"/f/o/o?bar#qqq\", \"/f/o/o\", \"bar\");\n  testPathAndQuery(\"#?hello\", \"\", \"\");\n}\n\nTEST(HTTPMessage, CheckHeaderForToken) {\n  HTTPMessage msg;\n  std::vector<std::pair<std::string, bool>> tests{{\"close\", true},\n                                                  {\"xclose\", false},\n                                                  {\"closex\", false},\n                                                  {\"close, foo\", true},\n                                                  {\"close, \", true},\n                                                  {\"foo, close\", true},\n                                                  {\", close\", true},\n                                                  {\"close, close, \", true}};\n\n  for (auto& test : tests) {\n    msg.getHeaders().set(HTTP_HEADER_CONNECTION, test.first);\n    EXPECT_TRUE(msg.checkForHeaderToken(\n                    HTTP_HEADER_CONNECTION, \"close\", false) == test.second);\n  }\n}\n\nTEST(HTTPHeaders, AddStringPiece) {\n  constexpr const char* foo = \"name:value\";\n  HTTPHeaders headers;\n\n  folly::StringPiece str(foo);\n  folly::StringPiece name = str.split_step(':');\n  headers.add(name, str);\n  EXPECT_EQ(\"value\", headers.getSingleOrEmpty(\"name\"));\n}\n\nTEST(HTTPHeaders, InitializerList) {\n  HTTPHeaders hdrs;\n\n  hdrs.add({{\"name\", \"value\"}});\n  hdrs.add({{HTTP_HEADER_CONNECTION, \"close\"}});\n  hdrs.add(\n      {{\"a\", \"b\"}, {HTTP_HEADER_CONNECTION, \"foo\"}, {HTTP_HEADER_SERVER, \"x\"}});\n\n  EXPECT_EQ(\"value\", hdrs.getSingleOrEmpty(\"name\"));\n  EXPECT_EQ(\"close, foo\", hdrs.combine(HTTP_HEADER_CONNECTION));\n  EXPECT_EQ(\"x\", hdrs.getSingleOrEmpty(HTTP_HEADER_SERVER));\n}\n\nTEST(HTTPHeaders, InitializerListStringView) {\n  HTTPHeaders hdrs;\n\n  const char* foo = \"name:value\";\n  folly::StringPiece str(foo);\n  std::string_view name = str.split_step(':');\n  hdrs.add({{name, str}, {HTTP_HEADER_CONNECTION, str}});\n\n  EXPECT_EQ(\"value\", hdrs.getSingleOrEmpty(\"name\"));\n  EXPECT_EQ(\"value\", hdrs.getSingleOrEmpty(HTTP_HEADER_CONNECTION));\n}\n\nvoid testRemoveQueryParam(const string& url,\n                          const string& queryParam,\n                          const string& expectedUrl,\n                          const string& expectedQuery) {\n  HTTPMessage msg;\n  msg.setURL(url);\n  bool expectedChange = (url != expectedUrl);\n  EXPECT_EQ(msg.removeQueryParam(queryParam), expectedChange);\n\n  EXPECT_EQ(msg.getURL(), expectedUrl);\n  EXPECT_EQ(msg.getQueryStringAsStringPiece(), expectedQuery);\n}\n\nTEST(HTTPMessage, RemoveQueryParamTests) {\n  // Query param present\n  testRemoveQueryParam(\"http://localhost:80/foo?param1=a&param2=b#qqq\",\n                       \"param2\",\n                       \"http://localhost:80/foo?param1=a#qqq\",\n                       \"param1=a\");\n  // Query param not present\n  testRemoveQueryParam(\"http://localhost/foo?param1=a&param2=b#qqq\",\n                       \"param3\",\n                       \"http://localhost/foo?param1=a&param2=b#qqq\",\n                       \"param1=a&param2=b\");\n  // No scheme\n  testRemoveQueryParam(\"localhost:80/foo?param1=a&param2=b#qqq\",\n                       \"param2\",\n                       \"localhost:80/foo?param1=a#qqq\",\n                       \"param1=a\");\n  // Just hostname as URL and empty query param\n  testRemoveQueryParam(\"localhost\", \"param2\", \"localhost\", \"\");\n  testRemoveQueryParam(\"localhost\", \"\", \"localhost\", \"\");\n  // Just path as URL\n  testRemoveQueryParam(\"/f/o/o?bar#qqq\", \"bar\", \"/f/o/o#qqq\", \"\");\n}\n\nvoid testSetQueryParam(const string& url,\n                       const string& queryParam,\n                       const string& paramValue,\n                       const string& expectedUrl,\n                       const string& expectedQuery) {\n  HTTPMessage msg;\n  msg.setURL(url);\n  bool expectedChange = (url != expectedUrl);\n  EXPECT_EQ(msg.setQueryParam(queryParam, paramValue), expectedChange);\n\n  EXPECT_TRUE(urlsAreEquivalent(msg.getURL(), expectedUrl));\n  EXPECT_TRUE(queryStringsAreEquivalent(msg.getQueryString(), expectedQuery));\n}\n\nTEST(HTTPMessage, SetQueryParamTests) {\n  // Overwrite existing parameter\n  testSetQueryParam(\"http://localhost:80/foo?param1=a&param2=b#qqq\",\n                    \"param2\",\n                    \"true\",\n                    \"http://localhost:80/foo?param1=a&param2=true#qqq\",\n                    \"param1=a&param2=true\");\n  // Add a query parameter\n  testSetQueryParam(\"http://localhost/foo?param1=a&param2=b#qqq\",\n                    \"param3\",\n                    \"true\",\n                    \"http://localhost/foo?param1=a&param2=b&param3=true#qqq\",\n                    \"param1=a&param2=b&param3=true\");\n  // Add a query parameter, should be alphabetical order\n  testSetQueryParam(\"localhost:80/foo?param1=a&param3=c#qqq\",\n                    \"param2\",\n                    \"b\",\n                    \"localhost:80/foo?param1=a&param2=b&param3=c#qqq\",\n                    \"param1=a&param2=b&param3=c\");\n  // Add a query parameter when no query parameters exist\n  testSetQueryParam(\"localhost:80/foo#qqq\",\n                    \"param2\",\n                    \"b\",\n                    \"localhost:80/foo?param2=b#qqq\",\n                    \"param2=b\");\n}\n\nTEST(HTTPMessage, TestCheckForHeaderToken) {\n  HTTPMessage msg;\n  HTTPHeaders& headers = msg.getHeaders();\n\n  headers.add(HTTP_HEADER_CONNECTION, \"HTTP2-Settings\");\n  EXPECT_TRUE(\n      msg.checkForHeaderToken(HTTP_HEADER_CONNECTION, \"HTTP2-Settings\", false));\n  EXPECT_FALSE(\n      msg.checkForHeaderToken(HTTP_HEADER_CONNECTION, \"http2-settings\", true));\n}\n\nTEST(HTTPMessage, TestProtocolStringHTTPVersion) {\n  HTTPMessage msg;\n  msg.setHTTPVersion(1, 1);\n\n  EXPECT_EQ(msg.getProtocolString(), \"1.1\");\n}\n\nTEST(HTTPMessage, TestProtocolStringAdvancedProtocol) {\n  HTTPMessage msg;\n  std::string advancedProtocol = \"h2\";\n  msg.setAdvancedProtocolString(advancedProtocol);\n  EXPECT_EQ(msg.getProtocolString(), advancedProtocol);\n}\n\nTEST(HTTPMessage, TestExtractTrailers) {\n  HTTPMessage msg;\n  auto trailers = std::make_unique<HTTPHeaders>();\n  HTTPHeaders* rawPointer = trailers.get();\n  trailers->add(\"The-trailer\", \"something\");\n  msg.setTrailers(std::move(trailers));\n  auto trailers2 = msg.extractTrailers();\n  EXPECT_EQ(rawPointer, trailers2.get());\n  EXPECT_EQ(nullptr, msg.getTrailers());\n}\n\nTEST(HTTPMessage, TestMoveCopy) {\n  HTTPMessage m1;\n  m1.setURL(std::string(32, 'a'));\n  HTTPMessage m2;\n  m2.setURL(std::string(32, 'b'));\n  m2 = m1;\n  m2 = std::move(m1);\n}\n\nnamespace {\nconst size_t kInitialVectorReserve = 16;\n}\n\nTEST(HTTPHeaders, GrowTest) {\n  HTTPHeaders headers;\n  for (size_t i = 0; i < kInitialVectorReserve * 2; i++) {\n    headers.add(folly::to<std::string>(i), std::string(50, 'a' + i));\n  }\n  EXPECT_EQ(headers.getSingleOrEmpty(\"0\")[0], 'a');\n  EXPECT_EQ(headers.getSingleOrEmpty(\"25\")[0], 'z');\n}\n\nTEST(HTTPHeaders, ClearTest) {\n  HTTPHeaders headers;\n  for (size_t i = 0; i < kInitialVectorReserve * 2; i++) {\n    headers.add(folly::to<std::string>(i), std::string(50, 'a' + i));\n  }\n  EXPECT_EQ(headers.getSingleOrEmpty(\"25\")[0], 'z');\n  headers.removeAll();\n  EXPECT_EQ(headers.size(), 0);\n  for (size_t i = 0; i < kInitialVectorReserve * 2; i++) {\n    headers.add(folly::to<std::string>(i), std::string(50, 'A' + i));\n  }\n  EXPECT_EQ(headers.getSingleOrEmpty(\"25\")[0], 'Z');\n}\n\nvoid copyAndMoveTest(size_t multiplier) {\n  HTTPHeaders headers;\n  for (size_t i = 0; i < kInitialVectorReserve * multiplier; i++) {\n    headers.add(folly::to<std::string>(i), std::string(50, 'a' + i));\n  }\n  HTTPHeaders headers2(headers);\n  EXPECT_EQ(headers2.getSingleOrEmpty(\"3\")[0], 'd');\n  HTTPHeaders headers3;\n  headers3.add(\"blown\", \"away\");\n  headers3 = headers2;\n  EXPECT_EQ(headers2.getSingleOrEmpty(\"4\")[0], 'e');\n  HTTPHeaders headers4(std::move(headers3));\n  EXPECT_EQ(headers4.getSingleOrEmpty(\"5\")[0], 'f');\n  HTTPHeaders headers5;\n  headers5.add(\"blown\", \"away\");\n  headers5 = std::move(headers4);\n  EXPECT_EQ(headers5.getSingleOrEmpty(\"6\")[0], 'g');\n}\n\nTEST(HTTPHeaders, CopyAndMoveTest) {\n  copyAndMoveTest(1);\n  copyAndMoveTest(2);\n  copyAndMoveTest(3);\n}\n\nvoid checkPrettyPrint(const HTTPHeaders& h) {\n  EXPECT_EQ(h.size(), 2);\n}\n\nTEST(HTTPHeaders, PrettyPrint) {\n  HTTPHeaders headers;\n  headers.add(HTTP_HEADER_HOST, \"www.facebook.com\");\n  headers.add(HTTP_HEADER_CONNECTION, \"close\");\n  headers.add(\"Foo\", \"Bar\");\n  headers.remove(HTTP_HEADER_CONNECTION);\n  checkPrettyPrint(headers);\n}\n\nvoid checkPrettyPrintMsg(const HTTPMessage& m) {\n  EXPECT_EQ(m.getMethodString(), \"SPECIAL\");\n}\n\nTEST(HTTPMessage, PrettyPrint) {\n  HTTPMessage request;\n  request.setURL(\"/foo/bar.php?n=v\");\n  request.setHTTPVersion(1, 1);\n  request.setMethod(\"SPECIAL\");\n  request.setIsChunked(true);\n  folly::SocketAddress addr;\n  addr.setFromHostPort(\"localhost\", 9999);\n  request.setClientAddress(addr);\n  request.getClientIP();\n  request.getClientPort();\n  request.getHeaders().add(HTTP_HEADER_HOST, \"www.intstagram.com\");\n  request.getHeaders().add(\"Bar\", \"Foo\");\n  checkPrettyPrintMsg(request);\n\n  HTTPMessage response;\n  response.setStatusCode(418);\n  response.setStatusMessage(\"I am a teapot\");\n  response.setHTTPVersion(3, 0);\n  response.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, \"0\");\n  response.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, \"text/plain\");\n  checkPrettyPrint(response.getHeaders());\n}\n\nTEST(HTTPMessage, NoDefaultHTTPPriority) {\n  HTTPMessage message;\n  EXPECT_FALSE(httpPriorityFromHTTPMessage(message).hasValue());\n}\n\nTEST(HTTPMessage, HTTPPrioritySetGet) {\n  HTTPMessage message;\n  message.setHTTPPriority(HTTPPriority(0, true));\n  EXPECT_EQ(0, httpPriorityFromHTTPMessage(message)->urgency);\n  EXPECT_TRUE(httpPriorityFromHTTPMessage(message)->incremental);\n  auto& priHeader = message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY);\n  EXPECT_EQ(\"u=0,i\", priHeader);\n  auto priHeaderViaGetter = message.getHTTPPriority();\n  EXPECT_EQ(0, priHeaderViaGetter->urgency);\n  EXPECT_TRUE(priHeaderViaGetter->incremental);\n\n  message.setHTTPPriority(1, false);\n  EXPECT_EQ(1, httpPriorityFromHTTPMessage(message)->urgency);\n  EXPECT_FALSE(httpPriorityFromHTTPMessage(message)->incremental);\n  EXPECT_EQ(\"u=1\", message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY));\n  priHeaderViaGetter = message.getHTTPPriority();\n  EXPECT_EQ(1, priHeaderViaGetter->urgency);\n  EXPECT_FALSE(priHeaderViaGetter->incremental);\n}\n\nTEST(HTTPMessage, HTTPPrioritySetOutRangeUrgency) {\n  HTTPMessage message;\n  message.setHTTPPriority(HTTPPriority(kMaxPriority + 10, true));\n  EXPECT_EQ(kMaxPriority, httpPriorityFromHTTPMessage(message)->urgency);\n  EXPECT_TRUE(httpPriorityFromHTTPMessage(message)->incremental);\n  auto& priHeader = message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY);\n  EXPECT_EQ(\"u=7,i\", priHeader);\n  auto priHeaderViaGetter = message.getHTTPPriority();\n  EXPECT_EQ(7, priHeaderViaGetter->urgency);\n  EXPECT_TRUE(priHeaderViaGetter->incremental);\n}\n\nTEST(HTTPMessage, HTTPPriorityOrderIdSetGet) {\n  HTTPMessage message;\n  message.setHTTPPriority(HTTPPriority(7, false, 10));\n  EXPECT_EQ(httpPriorityFromHTTPMessage(message)->orderId, 10);\n  auto& priHeader = message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY);\n  EXPECT_EQ(\"u=7,o=10\", priHeader);\n  auto priHeaderViaGetter = message.getHTTPPriority();\n  EXPECT_EQ(priHeaderViaGetter->urgency, 7);\n  EXPECT_EQ(priHeaderViaGetter->orderId, 10);\n}\n\nTEST(HTTPMessage, HTTPPriorityPausedSetGet) {\n  HTTPMessage message;\n  message.setHTTPPriority(HTTPPriority(7, false, 0, true /* paused */));\n  EXPECT_TRUE(httpPriorityFromHTTPMessage(message)->paused);\n  auto& priHeader = message.getHeaders().getSingleOrEmpty(HTTP_HEADER_PRIORITY);\n  EXPECT_EQ(\"u=7,p\", priHeader);\n}\n\nTEST(HTTPHeaders, GetSetOnResize) {\n  HTTPHeaders headers;\n  for (size_t i = 0; i < kInitialVectorReserve - 1; i++) {\n    headers.add(HTTP_HEADER_CONNECTION, \"token\");\n  }\n  std::string value(32, 'a');\n  headers.add(HTTP_HEADER_SERVER, value);\n  EXPECT_EQ(headers.size(), kInitialVectorReserve);\n  auto& v = headers.getSingleOrEmpty(HTTP_HEADER_SERVER);\n  headers.set(HTTP_HEADER_SERVER, v);\n\n  EXPECT_EQ(headers.getSingleOrEmpty(HTTP_HEADER_SERVER), value);\n}\n\nTEST(HTTPHeaders, MoveFromTest) {\n  HTTPHeaders h1;\n  HTTPHeaders h2(std::move(h1));\n  EXPECT_FALSE(h1.exists(HTTP_HEADER_CONNECTION));\n  h1.forEachValueOfHeader(HTTP_HEADER_HOST, [](const std::string&) -> bool {\n    CHECK(false) << \"Unreachable\";\n  });\n  h1.add(HTTP_HEADER_CONNECTION, \"close\");\n\n  HTTPHeaders h3;\n  h3 = std::move(h1); // move assignment\n  EXPECT_EQ(h1.size(), 0);\n  EXPECT_EQ(h3.size(), 1);\n}\n\nTEST(HTTPMessage, DefaultSchemeHttp) {\n  HTTPMessage message;\n  EXPECT_EQ(message.getScheme(), \"http\");\n  EXPECT_FALSE(message.isSecure());\n}\n\nTEST(HTTPMessage, SchemeHttps) {\n  HTTPMessage message;\n  message.setSecure(true);\n  EXPECT_EQ(message.getScheme(), \"https\");\n  EXPECT_TRUE(message.isSecure());\n  message.setSecure(false);\n  EXPECT_EQ(message.getScheme(), \"http\");\n  EXPECT_FALSE(message.isSecure());\n}\n\nTEST(HTTPMessage, SchemeMasque) {\n  HTTPMessage message;\n  message.setMasque();\n  EXPECT_EQ(message.getScheme(), \"masque\");\n  EXPECT_TRUE(message.isSecure());\n  // Masque is already secure, setting secure again has no effect\n  message.setSecure(true);\n  EXPECT_EQ(message.getScheme(), \"masque\");\n  EXPECT_TRUE(message.isSecure());\n  // Masque must be secure, so unsetting secure falls back to http://\n  message.setSecure(false);\n  EXPECT_EQ(message.getScheme(), \"http\");\n  EXPECT_FALSE(message.isSecure());\n}\n\nTEST(HTTPMessage, StrictMode) {\n  HTTPMessage message;\n  EXPECT_FALSE(message.setURL(\"/foo\\xff\", /*strict=*/true));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/foo\\xff\"));\n  EXPECT_EQ(message.getPath(), \"\");\n\n  message.setURL(\"/\");\n  // Not strict mode, high ascii OK\n  EXPECT_TRUE(message.setQueryString(\"a=b\\xff\"));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/?a=b\\xff\"));\n  EXPECT_EQ(message.getPath(), \"/\");\n  EXPECT_EQ(message.getQueryString(), \"a=b\\xff\");\n\n  EXPECT_TRUE(message.setQueryString(\"a=b\"));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/?a=b\"));\n  EXPECT_FALSE(message.setQueryString(\"a=b\\xff\", /*strict=*/true));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/?a=b\\xff\"));\n  EXPECT_EQ(message.getQueryString(), \"\");\n\n  EXPECT_TRUE(message.setQueryString(\"a=b\"));\n  EXPECT_FALSE(message.setQueryParam(\"c\", \"d\\xff\", /*strict=*/true));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/?a=b&c=d\\xff\"));\n  EXPECT_TRUE(message.setQueryParam(\"c\", \"d\\xff\", /*strict=*/false));\n  EXPECT_TRUE(urlsAreEquivalent(message.getURL(), \"/?a=b&c=d\\xff\"));\n\n  // Can remove a param without failing the strict parsing\n  EXPECT_TRUE(message.setURL(\"/?a=b&c=d\\xff\"));\n  EXPECT_TRUE(message.removeQueryParam(\"a\"));\n  EXPECT_TRUE(message.removeQueryParam(\"c\"));\n}\n\nTEST(HTTPMessage, GetSingleOrNullptr) {\n  HTTPMessage msg;\n  auto& headers = msg.getHeaders();\n  headers.add(HTTP_HEADER_HOST, \"facebook.com\");\n  headers.add(\"test\", \"test\");\n\n  // no \"missing\" header\n  auto missingLookup = headers.getSingleOrNullptr(\"missing\");\n  EXPECT_FALSE(missingLookup);       // exists\n  EXPECT_FALSE(missingLookup.value); // no longer a singular header\n  EXPECT_EQ(*missingLookup, \"\");\n  EXPECT_TRUE(missingLookup->empty());\n\n  // single \"host\" header exists,\n  auto hostLookup = headers.getSingleOrNullptr(HTTP_HEADER_HOST);\n  EXPECT_TRUE(hostLookup);       // exists\n  EXPECT_TRUE(hostLookup.value); // singular header\n  EXPECT_EQ(*hostLookup, \"facebook.com\");\n\n  // single \"test\" header exists\n  auto testLookup = headers.getSingleOrNullptr(\"test\");\n  EXPECT_TRUE(testLookup);       // exists\n  EXPECT_TRUE(testLookup.value); // singular header\n  EXPECT_EQ(*testLookup, \"test\");\n\n  // add another \"test\" header\n  headers.add(\"test\", \"test\");\n  testLookup = headers.getSingleOrNullptr(\"test\");\n  EXPECT_TRUE(testLookup);        // exists\n  EXPECT_FALSE(testLookup.value); // no longer a singular header\n  EXPECT_EQ(*testLookup, \"\");\n  EXPECT_TRUE(testLookup->empty());\n}\n\n#ifdef NDEBUG\n// This fails DCHECKs in debug mode, throws in opt\nTEST(HTTPMessage, BadAPIUsage) {\n  HTTPMessage req;\n  req.setURL(\"/\");\n  EXPECT_THROW(req.getStatusCode(), std::runtime_error);\n  EXPECT_EQ(req.getURL(), \"/\");\n\n  HTTPMessage resp;\n  resp.setStatusCode(200);\n  EXPECT_THROW(resp.getQueryString(), std::runtime_error);\n  EXPECT_EQ(resp.getStatusCode(), 200);\n}\n#endif\n"
  },
  {
    "path": "proxygen/lib/http/test/HTTPPriorityFunctionsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/HTTPPriorityFunctions.h>\n\nusing namespace proxygen;\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderUrgencyOnly) {\n  HTTPMessage req;\n\n  // urgency only\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=5\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 5);\n  EXPECT_FALSE(priority->incremental);\n\n  // urgency with other random fields\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"u=3, true\");\n  priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_FALSE(priority->incremental);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderIncrementalOnly) {\n  HTTPMessage req;\n\n  // incremental only\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"i\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_TRUE(priority->incremental);\n\n  // incremental with other random fields\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"i, true\");\n  priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_TRUE(priority->incremental);\n\n  // incremental with int \"truthy\" value\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"i=?1\");\n  priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_TRUE(priority->incremental);\n\n  // incremental with int \"falsy\" value\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"i=?0\");\n  priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_FALSE(priority->incremental);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderWhiteSpace) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"    \");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"u=4,  i \");\n  priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 4);\n  EXPECT_TRUE(priority->incremental);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderUrgencyAndIncremental) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=4,i\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 4);\n  EXPECT_TRUE(priority->incremental);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderUrgencyAndIncrementalUppercase) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"U=4, I\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderBadUrgency) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"x=3\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n\n  // same as above but with incremental flag\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"x=2, i\");\n  priority = httpPriorityFromHTTPMessage(req);\n  // default to u=3 if urgency is missing but incremental is present\n  ASSERT_TRUE(priority.hasValue());\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_TRUE(priority->incremental);\n\n  // urgency > kMaxPriority should fail\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"u=8\");\n  priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n\n  // urgency < kMinPriority should fail\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"u=-2\");\n  priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n\n  // urgency non-integral type should fail\n  req.getHeaders().set(HTTP_HEADER_PRIORITY, \"u=banana,i\");\n  priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderBadIncremental) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, i=0\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderBadPaused) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, p=0\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority.hasValue());\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderDefaultOrderId) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_EQ(priority->orderId, 0);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderCustomOrderId) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, o=100\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_EQ(priority->urgency, 3);\n  EXPECT_EQ(priority->orderId, 100);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderDefaultUnpaused) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_FALSE(priority->paused);\n}\n\nTEST(HTTPPriorityFunctionsTest, PriorityHeaderPaused) {\n  HTTPMessage req;\n  req.getHeaders().add(HTTP_HEADER_PRIORITY, \"u=3, p\");\n  auto priority = httpPriorityFromHTTPMessage(req);\n  EXPECT_TRUE(priority->paused);\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/MockHTTPHeaders.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n\n#include <folly/Optional.h>\n#include <proxygen/lib/http/HTTPHeaders.h>\n\nnamespace proxygen {\n\n/**\n * A gmock matcher for HTTPHeader objects.\n *\n * This can be used for check for the existence of a header, or for a header\n * with a specific value. To check for multiple headers, or more complicated\n * predicate logic, use ::testing::AllOf() and friends from gmock.\n *\n * Use the factory functions below in tests rather than using this class\n * directly.\n */\nclass HasHTTPHeaderMatcherImpl\n    : public ::testing::MatcherInterface<const HTTPHeaders&> {\n public:\n  explicit HasHTTPHeaderMatcherImpl(std::string name) : name_(name) {\n  }\n\n  HasHTTPHeaderMatcherImpl(std::string name, std::string value)\n      : name_(name), value_(value) {\n  }\n\n  bool MatchAndExplain(\n      const HTTPHeaders& headers,\n      ::testing::MatchResultListener* /*listener*/) const override {\n    bool matches = false;\n    headers.forEach([&](const std::string& name, const std::string& value) {\n      if (name_ != name) {\n        return;\n      }\n\n      matches = matches || !value_ || *value_ == value;\n    });\n\n    return matches;\n  }\n\n  void DescribeTo(::std::ostream* os) const override {\n    if (!value_) {\n      *os << \"has the '\" << name_ << \"' header\";\n    } else {\n      *os << \"has the header '\" << name_ << \"' equal to '\" << *value_ << \"'\";\n    }\n  }\n\n  void DescribeNegationTo(::std::ostream* os) const override {\n    if (!value_) {\n      *os << \"does not have the '\" << name_ << \"' header\";\n    } else {\n      *os << \"does not have the '\" << name_ << \"' header \" << \"equal to '\"\n          << *value_ << \"'\";\n    }\n  }\n\n private:\n  const std::string name_;\n  const folly::Optional<std::string> value_;\n};\n\n// Factory function for matching an HTTPHeaders that contains the given header\ninline ::testing::Matcher<const HTTPHeaders&> HasHTTPHeader(std::string name) {\n  return ::testing::MakeMatcher(new HasHTTPHeaderMatcherImpl(name));\n}\n\n// Factory function for matching an HTTPHeaders that contains the given header\n// and has it set to the specified value\ninline ::testing::Matcher<const HTTPHeaders&> HasHTTPHeader(std::string name,\n                                                            std::string value) {\n  return ::testing::MakeMatcher(new HasHTTPHeaderMatcherImpl(name, value));\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/test/MockHTTPMessageFilter.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <gmock/gmock.h>\n\n#include \"proxygen/facebook/revproxy/caching/filter/FilterNames.h\"\n#include <proxygen/lib/http/HTTPMessageFilters.h>\n\nnamespace proxygen {\n\nstatic const std::string kMockFilterName = \"MockFilter\";\n\nclass MockHTTPMessageFilter : public HTTPMessageFilter {\n public:\n  MOCK_METHOD((void),\n              onHeadersComplete,\n              (std::shared_ptr<HTTPMessage>),\n              (noexcept));\n  MOCK_METHOD((void), onBody, (std::shared_ptr<folly::IOBuf>), (noexcept));\n  MOCK_METHOD((void), pause, (), (noexcept));\n  MOCK_METHOD((void), onChunkHeader, (size_t), (noexcept));\n  MOCK_METHOD((void), resume, (uint64_t), (noexcept));\n  MOCK_METHOD((void), onChunkComplete, (), (noexcept));\n  MOCK_METHOD((void), onTrailers, (std::shared_ptr<HTTPHeaders>), (noexcept));\n  MOCK_METHOD((void), onEOM, (), (noexcept));\n  MOCK_METHOD((void), onUpgrade, (UpgradeProtocol), (noexcept));\n  MOCK_METHOD((void), onError, (const HTTPException&), (noexcept));\n\n  void onHeadersComplete(std::unique_ptr<HTTPMessage> msg) noexcept override {\n    if (trackHeadersPassedThrough_) {\n      requestHeadersCopy_ =\n          std::make_shared<const HTTPHeaders>(msg->getHeaders());\n    }\n    onHeadersComplete(std::shared_ptr<HTTPMessage>(msg.release()));\n  }\n\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override {\n    if (trackDataPassedThrough_) {\n      bodyDataReceived_.append(chain->clone());\n    }\n    onBody(std::shared_ptr<folly::IOBuf>(chain.release()));\n  }\n\n  void onTrailers(std::unique_ptr<HTTPHeaders> trailers) noexcept override {\n    if (trackTrailersPassedThrough_) {\n      requestTrailersCopy_ =\n          trailers ? std::make_shared<const HTTPHeaders>(*trailers.get())\n                   : nullptr;\n    }\n    onTrailers(std::shared_ptr<HTTPHeaders>(trailers.release()));\n  }\n\n  void nextOnHeadersCompletePublic(std::shared_ptr<HTTPMessage> msg) {\n    std::unique_ptr<HTTPMessage> msgU(new HTTPMessage(*msg));\n    nextOnHeadersComplete(std::move(msgU));\n  }\n\n  std::string_view getFilterName() const noexcept override {\n    return kMockFilterName;\n  }\n\n  std::variant<HTTPMessageFilter*, HTTPSink*> getPrevElement() {\n    return prev_;\n  }\n\n  [[noreturn]] std::unique_ptr<HTTPMessageFilter> clone() noexcept override {\n    LOG(FATAL) << \"clone() not implemented for filter: \"\n               << this->getFilterName();\n  };\n\n  void nextOnEOMPublic() {\n    nextOnEOM();\n  }\n\n  std::unique_ptr<folly::IOBuf> bodyDataSinceLastCheck() {\n    return bodyDataReceived_.move();\n  }\n\n  void setTrackDataPassedThrough(bool track) {\n    trackDataPassedThrough_ = track;\n  }\n\n  void setTrackHeadersPassedThrough(bool track = true) {\n    trackHeadersPassedThrough_ = track;\n  }\n\n  void setTrackTrailersPassedThrough(bool track = true) {\n    trackTrailersPassedThrough_ = track;\n  }\n\n  std::shared_ptr<const HTTPHeaders> getRequestHeadersCopy() {\n    return requestHeadersCopy_;\n  }\n\n  std::shared_ptr<const HTTPHeaders> getRequestTrailersCopy() {\n    return requestTrailersCopy_;\n  }\n\n private:\n  folly::IOBufQueue bodyDataReceived_{folly::IOBufQueue::cacheChainLength()};\n  bool trackDataPassedThrough_{false};\n  bool trackHeadersPassedThrough_{false};\n  bool trackTrailersPassedThrough_{false};\n  std::shared_ptr<const HTTPHeaders> requestHeadersCopy_;\n  std::shared_ptr<const HTTPHeaders> requestTrailersCopy_;\n};\n\n// A mock filter that returns kWritebackFilterName for testing\n// the consecutive WritebackFilter check.\nclass MockWritebackFilter : public HTTPMessageFilter {\n public:\n  [[nodiscard]] std::string_view getFilterName() const noexcept override {\n    return filter::kWritebackFilterName;\n  }\n\n  [[noreturn]] std::unique_ptr<HTTPMessageFilter> clone() noexcept override {\n    LOG(FATAL) << \"clone() not implemented for MockWritebackFilter\";\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/test/ProxyStatusTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/ProxyStatus.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n\n#include <folly/portability/GTest.h>\n\n#include <string>\n\nusing namespace proxygen;\n\nTEST(ProxyStatusTest, TestUpdatingStatusType) {\n  ProxyStatus proxy_status{};\n  EXPECT_EQ(proxy_status.getStatusType(), StatusType::ENUM_COUNT);\n  EXPECT_TRUE(proxy_status.isEmpty());\n\n  proxy_status.setStatusType(StatusType::connection_timeout);\n  EXPECT_EQ(proxy_status.getStatusType(), StatusType::connection_timeout);\n  EXPECT_FALSE(proxy_status.isEmpty());\n\n  proxy_status.setStatusType(StatusType::http_protocol_error);\n  EXPECT_EQ(proxy_status.getStatusType(), StatusType::http_protocol_error);\n  EXPECT_FALSE(proxy_status.isEmpty());\n\n  proxy_status.setStatusType(StatusType::proxy_internal_error);\n  EXPECT_EQ(proxy_status.getStatusType(), StatusType::proxy_internal_error);\n  EXPECT_FALSE(proxy_status.isEmpty());\n\n  proxy_status.setStatusType(StatusType::ENUM_COUNT);\n  EXPECT_EQ(proxy_status.getStatusType(), StatusType::ENUM_COUNT);\n  EXPECT_TRUE(proxy_status.isEmpty());\n}\n\nTEST(ProxyStatusTest, TestStatusSerialization) {\n  ProxyStatus proxy_status{StatusType::connection_timeout};\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_STREQ(str.c_str(), \"connection_timeout\");\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"connection_timeout\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 0);\n}\n\nTEST(ProxyStatusTest, TestEmpty) {\n  ProxyStatus proxy_status{};\n  EXPECT_TRUE(proxy_status.isEmpty());\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_STREQ(str.c_str(), \"\");\n  EXPECT_EQ(parameterisedList.size(), 0);\n}\n\nTEST(ProxyStatusTest, TestMissingStatus) {\n  ProxyStatus proxy_status{};\n  EXPECT_TRUE(proxy_status.isEmpty());\n  proxy_status.setProxyStatusParameter(\"a\", \"1\");\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_STREQ(str.c_str(), \"\");\n  EXPECT_EQ(parameterisedList.size(), 0);\n}\n\nTEST(ProxyStatusTest, TestUpstreamIP) {\n  ProxyStatus proxy_status{StatusType::http_protocol_error};\n  EXPECT_FALSE(proxy_status.hasUpstreamIP());\n  proxy_status.setUpstreamIP(\"upstreamIP\");\n  EXPECT_TRUE(proxy_status.hasUpstreamIP());\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"http_protocol_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 1);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_upip\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_upip\"],\n            std::string(\"upstreamIP\"));\n}\n\nTEST(ProxyStatusTest, TestProxy) {\n  ProxyStatus proxy_status{StatusType::proxy_internal_error};\n  proxy_status.setProxy(\"proxy\");\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"proxy_internal_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 1);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_proxy\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_proxy\"], std::string(\"proxy\"));\n}\n\nTEST(ProxyStatusTest, TestSerialization) {\n  ProxyStatus proxy_status{StatusType::proxy_internal_error};\n  EXPECT_FALSE(proxy_status.hasUpstreamIP());\n  proxy_status.setUpstreamIP(\"upstreamIP\");\n  EXPECT_TRUE(proxy_status.hasUpstreamIP());\n  proxy_status.setProxy(\"proxy\");\n  proxy_status.setProxyStatusParameter(\"a\", \"1\");\n  proxy_status.setProxyStatusParameter(\"b\", \"\");\n  proxy_status.setStatusType(StatusType::http_protocol_error);\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"http_protocol_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 3);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_upip\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_proxy\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"a\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_upip\"],\n            std::string(\"upstreamIP\"));\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_proxy\"], std::string(\"proxy\"));\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"a\"], std::string(\"1\"));\n}\n\nTEST(ProxyStatusTest, TestSetProxyError) {\n  ProxyStatus proxy_status{StatusType::proxy_internal_error};\n  proxy_status.setProxyError(false);\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"proxy_internal_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 1);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isproxyerr\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isproxyerr\"],\n            std::string(\"false\"));\n}\n\nTEST(ProxyStatusTest, TestSetServerError) {\n  ProxyStatus proxy_status{StatusType::proxy_internal_error};\n  proxy_status.setServerError(true);\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"proxy_internal_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 1);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isservererr\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isservererr\"],\n            std::string(\"true\"));\n}\n\nTEST(ProxyStatusTest, TestSetClientError) {\n  ProxyStatus proxy_status{StatusType::proxy_internal_error};\n  proxy_status.setClientError(true);\n\n  auto str = proxy_status.toString();\n  StructuredHeadersDecoder decoder(str);\n  StructuredHeaders::ParameterisedList parameterisedList;\n  decoder.decodeParameterisedList(parameterisedList);\n\n  EXPECT_EQ(parameterisedList.size(), 1);\n  EXPECT_EQ(parameterisedList[0].identifier, \"proxy_internal_error\");\n  EXPECT_EQ(parameterisedList[0].parameterMap.size(), 1);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isclienterr\"].tag,\n            StructuredHeaderItem::Type::STRING);\n  EXPECT_EQ(parameterisedList[0].parameterMap[\"e_isclienterr\"],\n            std::string(\"true\"));\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/RFC2616Test.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/RFC2616.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nusing RFC2616::parseByteRangeSpec;\nusing std::string;\n\nTEST(QvalueTest, Basic) {\n\n  RFC2616::TokenPairVec output;\n\n  {\n    string test(\"iso-8859-5, unicode-1-1;q=0.8\");\n    EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 2);\n    EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"iso-8859-5\")), 0);\n    EXPECT_DOUBLE_EQ(output[0].second, 1);\n    EXPECT_EQ(output[1].first.compare(folly::StringPiece(\"unicode-1-1\")), 0);\n    EXPECT_DOUBLE_EQ(output[1].second, 0.8);\n    output.clear();\n  }\n\n  {\n    string test(\"compress, gzip\");\n    EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 2);\n    EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"compress\")), 0);\n    EXPECT_DOUBLE_EQ(output[0].second, 1);\n    EXPECT_EQ(output[1].first.compare(folly::StringPiece(\"gzip\")), 0);\n    EXPECT_DOUBLE_EQ(output[1].second, 1);\n    output.clear();\n  }\n\n  {\n    string test;\n    // The spec says a blank one is ok but empty headers are disallowed in SPDY?\n    EXPECT_FALSE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 0);\n  }\n\n  {\n    string test(\" \");\n    // The spec says a blank one is ok but empty headers are disallowed in SPDY?\n    EXPECT_FALSE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 0);\n  }\n\n  {\n    string test(\"compress;q=0.5, gzip;q=1.0\");\n    EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 2);\n    EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"compress\")), 0);\n    EXPECT_DOUBLE_EQ(output[0].second, 0.5);\n    EXPECT_EQ(output[1].first.compare(folly::StringPiece(\"gzip\")), 0);\n    EXPECT_DOUBLE_EQ(output[1].second, 1.0);\n    output.clear();\n  }\n\n  {\n    string test(\"gzip;q=1.0, identity; q=0.5, *;q=0\");\n    EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 3);\n    EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"gzip\")), 0);\n    EXPECT_DOUBLE_EQ(output[0].second, 1);\n    EXPECT_EQ(output[1].first.compare(folly::StringPiece(\"identity\")), 0);\n    EXPECT_DOUBLE_EQ(output[1].second, 0.5);\n    EXPECT_EQ(output[2].first.compare(folly::StringPiece(\"*\")), 0);\n    EXPECT_DOUBLE_EQ(output[2].second, 0);\n    output.clear();\n  }\n\n  {\n    string test(\"da, en-gb;q=0.8, en;q=0.7\");\n    EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n    EXPECT_EQ(output.size(), 3);\n    EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"da\")), 0);\n    EXPECT_DOUBLE_EQ(output[0].second, 1);\n    EXPECT_EQ(output[1].first.compare(folly::StringPiece(\"en-gb\")), 0);\n    EXPECT_DOUBLE_EQ(output[1].second, 0.8);\n    EXPECT_EQ(output[2].first.compare(folly::StringPiece(\"en\")), 0);\n    EXPECT_DOUBLE_EQ(output[2].second, 0.7);\n    output.clear();\n  }\n}\n\nTEST(QvalueTest, Extras) {\n\n  RFC2616::TokenPairVec output;\n\n  string test(\"gzip\");\n  EXPECT_TRUE(RFC2616::parseQvalues(test, output));\n  EXPECT_EQ(output.size(), 1);\n  EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"gzip\")), 0);\n  EXPECT_DOUBLE_EQ(output[0].second, 1);\n  output.clear();\n}\n\nTEST(QvalueTest, Invalids) {\n\n  RFC2616::TokenPairVec output;\n\n  string test1(\",,,\");\n  EXPECT_FALSE(RFC2616::parseQvalues(test1, output));\n  EXPECT_EQ(output.size(), 0);\n  output.clear();\n\n  string test2(\"  ; q=0.1\");\n  EXPECT_FALSE(RFC2616::parseQvalues(test2, output));\n  EXPECT_EQ(output.size(), 0);\n  output.clear();\n\n  string test3(\"gzip; q=uietplease\");\n  EXPECT_FALSE(RFC2616::parseQvalues(test3, output));\n  EXPECT_EQ(output.size(), 1);\n  EXPECT_EQ(output[0].first.compare(folly::StringPiece(\"gzip\")), 0);\n  EXPECT_DOUBLE_EQ(output[0].second, 1);\n  output.clear();\n}\n\nTEST(ParseEncodingTest, Simple) {\n  string test(\"zstd;q=1.0;wl=20,gzip;q=0.0\");\n  auto encodings = RFC2616::parseEncoding(test);\n  EXPECT_FALSE(encodings.hasException());\n  EXPECT_EQ(encodings.value(),\n            (RFC2616::EncodingList{{\"zstd\", {{\"q\", \"1.0\"}, {\"wl\", \"20\"}}},\n                                   {\"gzip\", {{\"q\", \"0.0\"}}}}));\n}\n\nTEST(ParseEncodingTest, Whitespace) {\n  string test(\"zstd ; q =\\t1.0 ; wl = 20 , gzip ;\\t q = 0.0\");\n  auto encodings = RFC2616::parseEncoding(test);\n  EXPECT_FALSE(encodings.hasException());\n  EXPECT_EQ(encodings.value(),\n            (RFC2616::EncodingList{{\"zstd\", {{\"q\", \"1.0\"}, {\"wl\", \"20\"}}},\n                                   {\"gzip\", {{\"q\", \"0.0\"}}}}));\n}\n\nTEST(ParseEncodingTest, Invalid) {\n  EXPECT_TRUE(RFC2616::parseEncoding(\"\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\",\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" ,\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\", \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" , \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" , , \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\";\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" ;\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\"; \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" ; \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" ; ; \").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\" ; ; , ; ;\").hasException());\n\n  EXPECT_TRUE(RFC2616::parseEncoding(\"zstd;=,gzip\").hasException());\n  EXPECT_TRUE(RFC2616::parseEncoding(\"zstd;=wat,gzip\").hasException());\n}\n\nTEST(AcceptEncodingTest, Simple) {\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"zstd\", \"zstd\"));\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"zstd, gzip\", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"gzip\", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"*\", \"zstd\"));\n}\n\nTEST(AcceptEncodingTest, QValues) {\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"zstd\", \"zstd\"));\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"zstd;q=1.0\", \"zstd\"));\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"zstd;q=wat\", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"zstd;q=0.0\", \"zstd\"));\n}\n\nTEST(AcceptEncodingTest, Whitespace) {\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\" zstd \", \"zstd\"));\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\" zstd ,\", \"zstd\"));\n  EXPECT_TRUE(RFC2616::acceptsEncoding(\"gzip, br \\t, \\tzstd \", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"gzip, \\tdeflate, br \\t \", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"zstd;q=0.0\", \"zstd\"));\n  EXPECT_FALSE(RFC2616::acceptsEncoding(\"zstd; q = 0.0 \", \"zstd\"));\n}\n\nTEST(ByteRangeSpecTest, Valids) {\n  unsigned long firstByte = ULONG_MAX;\n  unsigned long lastByte = ULONG_MAX;\n  unsigned long instanceLength = ULONG_MAX;\n\n  ASSERT_TRUE(parseByteRangeSpec(\n      \"bytes 0-10/100\", firstByte, lastByte, instanceLength));\n  EXPECT_EQ(0, firstByte);\n  EXPECT_EQ(10, lastByte);\n  EXPECT_EQ(100, instanceLength);\n\n  ASSERT_TRUE(\n      parseByteRangeSpec(\"bytes */100\", firstByte, lastByte, instanceLength));\n  EXPECT_EQ(0, firstByte);\n  EXPECT_EQ(ULONG_MAX, lastByte);\n  EXPECT_EQ(100, instanceLength);\n\n  ASSERT_TRUE(\n      parseByteRangeSpec(\"bytes 0-10/*\", firstByte, lastByte, instanceLength));\n  EXPECT_EQ(0, firstByte);\n  EXPECT_EQ(10, lastByte);\n  EXPECT_EQ(ULONG_MAX, instanceLength);\n}\n\nTEST(ByteRangeSpecTest, Invalids) {\n  unsigned long dummy;\n\n  EXPECT_FALSE(parseByteRangeSpec(\"0-10/100\", dummy, dummy, dummy))\n      << \"Spec must start with 'bytes '\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 10/100\", dummy, dummy, dummy))\n      << \"Spec missing initial range\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 10-/100\", dummy, dummy, dummy))\n      << \"Spec missing last byte in initial range\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 0-10 100\", dummy, dummy, dummy))\n      << \"Spec missing '/' separator\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 0-10/100Q\", dummy, dummy, dummy))\n      << \"Spec has trailing garbage\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 10-1/100\", dummy, dummy, dummy))\n      << \"Spec initial range is invalid\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 10-90/50\", dummy, dummy, dummy))\n      << \"Spec initial range is invalid too large\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes x/100\", dummy, dummy, dummy))\n      << \"Spec initial range has invalid first byte\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 0-x/100\", dummy, dummy, dummy))\n      << \"Spec initial range has invalid last bytek\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes *-10/100\", dummy, dummy, dummy))\n      << \"Spec cannot contain wildcard in initial range\";\n  EXPECT_FALSE(parseByteRangeSpec(\"bytes 0-*/100\", dummy, dummy, dummy))\n      << \"Spec cannot contain wildcard in initial range\";\n\n  folly::StringPiece sp(\"bytes 0-10/100\");\n  sp.subtract(3);\n  EXPECT_FALSE(parseByteRangeSpec(sp, dummy, dummy, dummy))\n      << \"Spec StringPiece ends before instance length\";\n  sp.subtract(1);\n  EXPECT_FALSE(parseByteRangeSpec(sp, dummy, dummy, dummy))\n      << \"Spec StringPiece ends before '/' character\";\n  sp.subtract(2);\n  EXPECT_FALSE(parseByteRangeSpec(sp, dummy, dummy, dummy))\n      << \"Spec StringPiece ends before last byte in initial byte range\";\n  sp.subtract(1);\n  EXPECT_FALSE(parseByteRangeSpec(sp, dummy, dummy, dummy))\n      << \"Spec StringPiece ends before '-' in initial byte range\";\n  sp.subtract(2);\n  EXPECT_FALSE(parseByteRangeSpec(sp, dummy, dummy, dummy))\n      << \"Spec StringPiece ends before first byte in initial byte range\";\n}\n"
  },
  {
    "path": "proxygen/lib/http/test/WindowTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/Window.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nTEST(WindowTest, Basic) {\n  Window w(100);\n  ASSERT_TRUE(w.free(10));\n  ASSERT_EQ(w.getSize(), 110);\n  ASSERT_EQ(w.getCapacity(), 100);\n  ASSERT_TRUE(w.reserve(20));\n  ASSERT_EQ(w.getSize(), 90);\n  ASSERT_TRUE(w.reserve(90));\n  ASSERT_EQ(w.getSize(), 0);\n  ASSERT_TRUE(w.free(5));\n  ASSERT_EQ(w.getSize(), 5);\n  ASSERT_TRUE(w.free(0));\n  ASSERT_EQ(w.getSize(), 5);\n}\n\nTEST(WindowTest, ChangeCapacity) {\n  Window w(100);\n  ASSERT_TRUE(w.setCapacity(10));\n  ASSERT_EQ(w.getSize(), 10);\n  ASSERT_EQ(w.getCapacity(), 10);\n  ASSERT_TRUE(w.reserve(7));\n  ASSERT_TRUE(w.setCapacity(5));\n  ASSERT_EQ(w.getCapacity(), 5);\n  ASSERT_EQ(w.getSize(), -2);\n  ASSERT_TRUE(w.free(1));\n  ASSERT_EQ(w.getSize(), -1);\n  ASSERT_TRUE(w.setCapacity(6));\n  ASSERT_EQ(w.getSize(), 0);\n  ASSERT_EQ(w.getCapacity(), 6);\n  ASSERT_TRUE(w.setCapacity(7));\n  ASSERT_EQ(w.getSize(), 1);\n  ASSERT_EQ(w.getCapacity(), 7);\n  ASSERT_EQ(w.getOutstanding(), 6);\n}\n\nTEST(WindowTest, ExceedWindow) {\n  Window w(100);\n  ASSERT_TRUE(w.reserve(50));\n  ASSERT_TRUE(w.reserve(40));\n  ASSERT_EQ(w.getSize(), 10);\n  ASSERT_FALSE(w.reserve(20));\n}\n\nTEST(WindowTest, Overflow) {\n  Window w(0);\n  ASSERT_FALSE(w.reserve(std::numeric_limits<int32_t>::max()));\n}\n\nTEST(WindowTest, Underflow) {\n  Window w(100);\n  ASSERT_TRUE(w.free(100)); // You can manually bump up the window\n  ASSERT_TRUE(w.free(100)); // You can manually bump up the window\n  ASSERT_EQ(w.getSize(), 300);\n  ASSERT_FALSE(w.free(std::numeric_limits<int32_t>::max())); // to a point\n}\n\nTEST(WindowTest, HugeReserve) {\n  Window w(100);\n  ASSERT_FALSE(w.reserve(std::numeric_limits<uint32_t>::max()));\n}\n\nTEST(WindowTest, HugeFree) {\n  Window w1(0);\n  ASSERT_TRUE(w1.free(std::numeric_limits<int32_t>::max()));\n  Window w2(1);\n  ASSERT_FALSE(w2.free(std::numeric_limits<int32_t>::max()));\n}\n\nTEST(WindowTest, HugeFree2) {\n  for (unsigned i = 0; i < 10; ++i) {\n    Window w(i);\n    ASSERT_TRUE(w.free(std::numeric_limits<int32_t>::max() - i));\n    ASSERT_FALSE(w.free(1));\n  }\n}\n\nTEST(WindowTest, BytesOutstanding) {\n  Window w(100);\n  ASSERT_EQ(w.getOutstanding(), 0);\n  ASSERT_TRUE(w.reserve(20));\n  ASSERT_EQ(w.getOutstanding(), 20);\n  ASSERT_TRUE(w.free(30));\n  // outstanding bytes is -10, but the API clamps this to 0\n  ASSERT_EQ(w.getOutstanding(), 0);\n}\n\nTEST(WindowTest, BytesOutstandingAfterFail) {\n  Window w(100);\n  ASSERT_EQ(w.getOutstanding(), 0);\n  ASSERT_FALSE(w.reserve(110));\n  ASSERT_EQ(w.getOutstanding(), 0);\n}\n\nTEST(WindowTest, NonStrict) {\n  Window w(100);\n  ASSERT_TRUE(w.reserve(110, false));\n  ASSERT_EQ(w.getOutstanding(), 110);\n  ASSERT_EQ(w.getSize(), -10);\n}\n\nTEST(WindowTest, NewCapacityOverflow) {\n  Window w(0);\n  ASSERT_TRUE(w.free(10));\n  ASSERT_EQ(w.getOutstanding(), 0);\n  ASSERT_EQ(w.getSize(), 10);\n  ASSERT_TRUE(w.setCapacity(std::numeric_limits<int32_t>::max() - 10));\n  ASSERT_EQ(w.getSize(), std::numeric_limits<int32_t>::max());\n  ASSERT_FALSE(w.setCapacity(std::numeric_limits<int32_t>::max() - 9));\n}\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_http_webtransport\n  SRCS\n    WebTransport.cpp\n  EXPORTED_DEPS\n    mvfst::mvfst_api_quic_callbacks\n    mvfst::mvfst_api_transport_info\n    mvfst::mvfst_priority_priority_queue\n    Folly::folly_cancellation_token\n    Folly::folly_expected\n    Folly::folly_futures_core\n    Folly::folly_io_iobuf\n    Folly::folly_network_address\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_http_webtransport_httpwebtransport\n  SRCS\n    HTTPWebTransport.cpp\n  DEPS\n    proxygen_http_message\n    proxygen_http_structuredheaders_decoder\n    proxygen_http_structuredheaders_encoder\n  EXPORTED_DEPS\n    Folly::folly_expected\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_http_webtransport_flow_controller)\n\nproxygen_add_library(proxygen_http_webtransport_webtransportimpl\n  SRCS\n    WebTransportImpl.cpp\n  DEPS\n    glog::glog\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    proxygen_http_webtransport\n    proxygen_http_webtransport_flow_controller\n    mvfst::mvfst_constants\n)\n\nproxygen_add_library(proxygen_http_webtransport_quicwebtransport\n  SRCS\n    QuicWebTransport.cpp\n  DEPS\n    mvfst::mvfst_priority_http_priority_queue\n  EXPORTED_DEPS\n    proxygen_http_webtransport_webtransportimpl\n    mvfst::mvfst_api_transport\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_webtransport_quic_wt_session\n  SRCS\n    QuicWtSession.cpp\n  EXPORTED_DEPS\n    proxygen_http_webtransport\n    proxygen_http_webtransport_wt_stream_manager\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_priority_http_priority_queue\n    Folly::folly_logging_logging\n)\n\nproxygen_add_library(proxygen_http_webtransport_wt_stream_manager\n  SRCS\n    WtEgressContainer.cpp\n    WtStreamManager.cpp\n    WtUtils.cpp\n  DEPS\n    proxygen_http_codec_codec_common\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_codec_webtransport_webtransport_capsule_codec\n    proxygen_http_webtransport\n    proxygen_http_webtransport_flow_controller\n    mvfst::mvfst_priority_priority_queue\n    Folly::folly_container_f14_hash\n    Folly::folly_io_iobuf\n)\n\nproxygen_add_library(proxygen_http_webtransport_wt_egress_container\n  SRCS\n    WtEgressContainer.cpp\n  DEPS\n    Folly::folly_logging_logging\n  EXPORTED_DEPS\n    proxygen_http_webtransport\n    proxygen_http_webtransport_flow_controller\n    Folly::folly_io_iobuf\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/FlowController.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <limits>\n\n// QUIC style flow controller using offsets\n\nclass FlowController {\n public:\n  FlowController(uint64_t initialMax = 0) : maxOffset_(initialMax) {\n  }\n\n  bool reserve(uint64_t length) {\n    if (length == 0) {\n      return true;\n    }\n\n    if (currentOffset_ >= std::numeric_limits<uint64_t>::max() - length) {\n      return false;\n    }\n\n    uint64_t newOffset = currentOffset_ + length;\n    if (newOffset > maxOffset_) {\n      return false;\n    }\n    currentOffset_ = newOffset;\n    return true;\n  }\n\n  bool grant(uint64_t offset) {\n    if (offset <= maxOffset_) {\n      return false;\n    }\n    maxOffset_ = offset;\n    return true;\n  }\n\n  [[nodiscard]] bool isBlocked() const {\n    return currentOffset_ >= maxOffset_;\n  }\n\n  uint64_t getCurrentOffset() const {\n    return currentOffset_;\n  }\n  uint64_t getMaxOffset() const {\n    return maxOffset_;\n  }\n\n  uint64_t getAvailable() const {\n    return maxOffset_ - currentOffset_;\n  }\n\n private:\n  uint64_t currentOffset_{0};\n  uint64_t maxOffset_;\n};\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/HTTPWebTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/webtransport/HTTPWebTransport.h>\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersDecoder.h>\n#include <proxygen/lib/http/structuredheaders/StructuredHeadersEncoder.h>\n\nnamespace proxygen {\n\nusing WTProtocolError = HTTPWebTransport::WTProtocolError;\n\n/*static*/ bool HTTPWebTransport::isConnectMessage(const HTTPMessage& msg) {\n  constexpr std::string_view kWebTransport{\"webtransport\"};\n  return msg.isRequest() && msg.getMethod() == proxygen::HTTPMethod::CONNECT &&\n         msg.getUpgradeProtocol() && *msg.getUpgradeProtocol() == kWebTransport;\n}\n\n/*static*/ void HTTPWebTransport::setWTAvailableProtocols(\n    HTTPMessage& msg, const std::vector<std::string>& protocols) {\n  std::vector<StructuredHeaders::StructuredHeaderItem> items;\n  items.reserve(protocols.size());\n  for (const auto& protocol : protocols) {\n    items.emplace_back(StructuredHeaderItem::Type::STRING, protocol);\n  }\n\n  if (!items.empty()) {\n    StructuredHeadersEncoder encoder;\n    encoder.encodeList(items);\n    msg.getHeaders().set(headers::kWTAvailableProtocols, encoder.get());\n  }\n}\n\n/*static*/ void HTTPWebTransport::setWTProtocol(HTTPMessage& msg,\n                                                std::string protocol) {\n  StructuredHeadersEncoder encoder;\n  StructuredHeaderItem item(StructuredHeaderItem::Type::STRING, protocol);\n  encoder.encodeItem(item);\n  msg.getHeaders().set(headers::kWTProtocol, encoder.get());\n}\n\n/*static*/ folly::Expected<std::vector<std::string>, WTProtocolError>\nHTTPWebTransport::getWTAvailableProtocols(const HTTPMessage& msg) {\n  auto header = msg.getHeaders().combine(headers::kWTAvailableProtocols);\n  if (header.empty()) {\n    return folly::makeUnexpected(WTProtocolError::HeaderMissing);\n  }\n\n  StructuredHeadersDecoder decoder(header);\n  std::vector<StructuredHeaderItem> list;\n\n  if (decoder.decodeList(list) != DecodeError::OK) {\n    return folly::makeUnexpected(WTProtocolError::ParseFailed);\n  }\n\n  std::vector<std::string> protocols;\n  for (const auto& item : list) {\n    if (item.tag != StructuredHeaders::StructuredHeaderItem::Type::STRING) {\n      return folly::makeUnexpected(WTProtocolError::ParseFailed);\n    }\n    protocols.emplace_back(item.get<std::string>());\n  }\n\n  if (protocols.empty()) {\n    return folly::makeUnexpected(WTProtocolError::EmptyList);\n  }\n\n  return protocols;\n}\n\n/*static*/ folly::Expected<std::string, WTProtocolError>\nHTTPWebTransport::getWTProtocol(const HTTPMessage& msg) {\n  auto header = msg.getHeaders().getSingleOrEmpty(headers::kWTProtocol);\n  if (header.empty()) {\n    return folly::makeUnexpected(WTProtocolError::HeaderMissing);\n  }\n\n  StructuredHeadersDecoder decoder(header);\n  StructuredHeaderItem item;\n\n  if (decoder.decodeItem(item) != DecodeError::OK) {\n    return folly::makeUnexpected(WTProtocolError::ParseFailed);\n  }\n  if (item.tag != StructuredHeaders::StructuredHeaderItem::Type::STRING) {\n    return folly::makeUnexpected(WTProtocolError::ParseFailed);\n  }\n\n  return item.get<std::string>();\n}\n\n/*static*/ folly::Optional<std::string> HTTPWebTransport::negotiateWTProtocol(\n    const std::vector<std::string>& wtAvailableProtocols,\n    const std::vector<std::string>& supportedProtocols) {\n  for (const auto& protocol : supportedProtocols) {\n    if (std::find(wtAvailableProtocols.begin(),\n                  wtAvailableProtocols.end(),\n                  protocol) != wtAvailableProtocols.end()) {\n      return protocol;\n    }\n  }\n\n  return folly::none;\n}\n\n}; // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/HTTPWebTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <folly/Optional.h>\n\nnamespace proxygen {\n\nclass HTTPMessage;\n\nclass HTTPWebTransport {\n public:\n  enum class WTProtocolError : uint8_t {\n    HeaderMissing,\n    ParseFailed,\n    EmptyList\n  };\n\n  static bool isConnectMessage(const proxygen::HTTPMessage& msg);\n\n  static void setWTAvailableProtocols(\n      HTTPMessage& msg, const std::vector<std::string>& protocols);\n\n  static void setWTProtocol(HTTPMessage& msg, std::string protocol);\n\n  static folly::Expected<std::vector<std::string>, WTProtocolError>\n  getWTAvailableProtocols(const HTTPMessage& msg);\n\n  static folly::Expected<std::string, WTProtocolError> getWTProtocol(\n      const HTTPMessage& msg);\n\n  static folly::Optional<std::string> negotiateWTProtocol(\n      const std::vector<std::string>& wtAvailableProtocols,\n      const std::vector<std::string>& supportedProtocols);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/QuicWebTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/webtransport/QuicWebTransport.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nusing FCState = proxygen::WebTransport::FCState;\n\nnamespace proxygen {\n\nvoid QuicWebTransport::onFlowControlUpdate(quic::StreamId /*id*/) noexcept {\n}\n\nvoid QuicWebTransport::onNewBidirectionalStream(quic::StreamId id) noexcept {\n  XCHECK(quicSocket_);\n  if (!handler_) {\n    resetWebTransportEgress(id, WebTransport::kInternalError);\n    // resetWebTransportEgress may have closed the session\n    if (quicSocket_) {\n      stopReadingWebTransportIngress(id, WebTransport::kInternalError);\n    }\n    return;\n  }\n  auto handle = WebTransportImpl::onWebTransportBidiStream(id);\n  handler_->onNewBidiStream(WebTransport::BidiStreamHandle(\n      {.readHandle = handle.readHandle, .writeHandle = handle.writeHandle}));\n  // Handler callback may have closed the session, check socket is still valid\n  if (quicSocket_) {\n    quicSocket_->setReadCallback(id, handle.readHandle);\n  }\n}\n\nvoid QuicWebTransport::onNewUnidirectionalStream(quic::StreamId id) noexcept {\n  XCHECK(quicSocket_);\n  if (!handler_) {\n    LOG(ERROR) << \"Handler not set\";\n    stopReadingWebTransportIngress(id, WebTransport::kInternalError);\n    return;\n  }\n  auto readHandle = WebTransportImpl::onWebTransportUniStream(id);\n  handler_->onNewUniStream(readHandle);\n  // Handler callback may have closed the session, check socket is still valid\n  if (quicSocket_) {\n    quicSocket_->setReadCallback(id, readHandle);\n  }\n}\n\nvoid QuicWebTransport::onStopSending(\n    quic::StreamId id, quic::ApplicationErrorCode errorCode) noexcept {\n  onWebTransportStopSending(id, static_cast<uint32_t>(errorCode));\n}\n\nvoid QuicWebTransport::onConnectionEnd() noexcept {\n  onConnectionEndImpl(folly::none);\n}\n\nvoid QuicWebTransport::onConnectionError(quic::QuicError error) noexcept {\n  onConnectionEndImpl(error);\n}\nvoid QuicWebTransport::onConnectionEnd(quic::QuicError error) noexcept {\n  onConnectionEndImpl(error);\n}\n\nvoid QuicWebTransport::onConnectionEndImpl(\n    folly::Optional<quic::QuicError> error) {\n  destroy();\n  folly::Optional<uint32_t> wtError;\n  if (error) {\n    if (error->code.type() == quic::QuicErrorCode::Type::ApplicationErrorCode) {\n      wtError = static_cast<uint32_t>(*error->code.asApplicationErrorCode());\n    } else {\n      XLOG(DBG2) << \"QUIC Connection Error: \" << *error;\n      wtError = std::numeric_limits<uint32_t>::max();\n    }\n  }\n  clearQuicSocket();\n  if (auto handler = std::exchange(handler_, nullptr)) {\n    handler->onSessionEnd(wtError);\n  }\n}\n\nfolly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\nQuicWebTransport::newWebTransportBidiStream() {\n  XCHECK(quicSocket_);\n  auto id = quicSocket_->createBidirectionalStream();\n  if (id.hasError()) {\n    return folly::makeUnexpected(ErrorCode::GENERIC_ERROR);\n  }\n  return id.value();\n}\n\nfolly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\nQuicWebTransport::newWebTransportUniStream() {\n  XCHECK(quicSocket_);\n  auto id = quicSocket_->createUnidirectionalStream();\n  if (id.hasError()) {\n    return folly::makeUnexpected(ErrorCode::GENERIC_ERROR);\n  }\n  return id.value();\n}\n\nfolly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\nQuicWebTransport::sendWebTransportStreamData(\n    HTTPCodec::StreamID id,\n    std::unique_ptr<folly::IOBuf> data,\n    bool eof,\n    ByteEventCallback* deliveryCallback) {\n  XCHECK(quicSocket_);\n  auto res =\n      quicSocket_->writeChain(id, std::move(data), eof, deliveryCallback);\n  if (!res) {\n    LOG(ERROR) << \"Failed to write WT stream data, res=\" << res.error();\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  auto flowControl = quicSocket_->getStreamFlowControl(id);\n  if (!flowControl) {\n    LOG(ERROR) << \"Failed to get flow control\";\n    return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n  }\n  if (!eof && flowControl->sendWindowAvailable == 0) {\n    VLOG(4) << \"fc window closed\";\n    return FCState::BLOCKED;\n  } else {\n    return FCState::UNBLOCKED;\n  }\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::notifyPendingWriteOnStream(HTTPCodec::StreamID id,\n                                             quic::StreamWriteCallback* wcb) {\n  XCHECK(quicSocket_);\n  quicSocket_->notifyPendingWriteOnStream(id, wcb);\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::resetWebTransportEgress(HTTPCodec::StreamID id,\n                                          uint32_t errorCode) {\n  XCHECK(quicSocket_);\n  auto res = quicSocket_->resetStream(id, errorCode);\n  if (!res) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::setWebTransportStreamPriority(\n    HTTPCodec::StreamID id, quic::PriorityQueue::Priority pri) {\n  XCHECK(quicSocket_);\n  auto res = quicSocket_->setStreamPriority(id, pri);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::setWebTransportPriorityQueue(\n    std::unique_ptr<quic::PriorityQueue> queue) noexcept {\n  XCHECK(quicSocket_);\n  auto res = quicSocket_->setPriorityQueue(std::move(queue));\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::pauseWebTransportIngress(HTTPCodec::StreamID id) {\n  XCHECK(quicSocket_);\n  auto res = quicSocket_->pauseRead(id);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::resumeWebTransportIngress(HTTPCodec::StreamID id) {\n  XCHECK(quicSocket_);\n  auto res = quicSocket_->resumeRead(id);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::stopReadingWebTransportIngress(\n    HTTPCodec::StreamID id, folly::Optional<uint32_t> errorCode) {\n  if (!quicSocket_) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  quic::Optional<quic::ApplicationErrorCode> quicErrorCode;\n  if (errorCode) {\n    quicErrorCode = quic::ApplicationErrorCode(*errorCode);\n  }\n  auto res = quicSocket_->setReadCallback(id, nullptr, quicErrorCode);\n  if (res.hasError()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::sendDatagram(std::unique_ptr<folly::IOBuf> datagram) {\n  XCHECK(quicSocket_);\n  auto writeRes = quicSocket_->writeDatagram(std::move(datagram));\n  if (writeRes.hasError()) {\n    LOG(ERROR) << \"Failed to send datagram, error code: \" << writeRes.error();\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::sendWTMaxData(uint64_t /*maxData*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::sendWTMaxStreams(uint64_t /*maxStreams*/, bool /*isBidi*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::sendWTStreamsBlocked(uint64_t /*maxStreams*/,\n                                       bool /*isBidi*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::sendWTDataBlocked(uint64_t /*maxData*/) {\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWebTransport::closeSessionImpl(folly::Optional<uint32_t> error) {\n  handler_ = nullptr; // no need to deliver callbacks/onSessionEnd\n  if (quicSocket_) {\n    if (error) {\n      quicSocket_->close(quic::QuicError(quic::ApplicationErrorCode(*error)));\n    } else {\n      quicSocket_->close(quic::QuicError(quic::ApplicationErrorCode(0)));\n    }\n    clearQuicSocket();\n  } // else we came from connectionEnd/Error and quicSocket_ is reset\n  return folly::unit;\n}\n\nvoid QuicWebTransport::onUnidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (numStreamsAvailable > 0 && waitingForUniStreams_) {\n    waitingForUniStreams_->setValue(folly::unit);\n    waitingForUniStreams_.reset();\n  }\n}\n\nfolly::SemiFuture<folly::Unit> QuicWebTransport::awaitUniStreamCredit() {\n  XCHECK(quicSocket_);\n  auto numOpenable = quicSocket_->getNumOpenableUnidirectionalStreams();\n  if (numOpenable > 0) {\n    return folly::makeFuture(folly::unit);\n  }\n  CHECK(!waitingForUniStreams_);\n  auto [promise, future] = folly::makePromiseContract<folly::Unit>();\n  waitingForUniStreams_ = std::move(promise);\n  return std::move(future);\n}\n\nvoid QuicWebTransport::onBidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (numStreamsAvailable > 0 && waitingForBidiStreams_) {\n    waitingForBidiStreams_->setValue(folly::unit);\n    waitingForBidiStreams_.reset();\n  }\n}\n\nfolly::SemiFuture<folly::Unit> QuicWebTransport::awaitBidiStreamCredit() {\n  XCHECK(quicSocket_);\n  auto numOpenable = quicSocket_->getNumOpenableBidirectionalStreams();\n  if (numOpenable > 0) {\n    return folly::makeFuture(folly::unit);\n  }\n  CHECK(!waitingForBidiStreams_);\n  auto [promise, future] = folly::makePromiseContract<folly::Unit>();\n  waitingForBidiStreams_ = std::move(promise);\n  return std::move(future);\n}\n\nbool QuicWebTransport::canCreateUniStream() {\n  XCHECK(quicSocket_);\n  return quicSocket_->getNumOpenableUnidirectionalStreams() > 0;\n}\n\nbool QuicWebTransport::canCreateBidiStream() {\n  XCHECK(quicSocket_);\n  return quicSocket_->getNumOpenableBidirectionalStreams() > 0;\n}\n\nvoid QuicWebTransport::onDatagramsAvailable() noexcept {\n  XCHECK(quicSocket_);\n  auto result = quicSocket_->readDatagramBufs();\n  if (result.hasError()) {\n    LOG(ERROR) << \"Got error while reading datagrams: error=\"\n               << toString(result.error());\n    closeSession(0);\n    return;\n  }\n  VLOG(4) << \"Received \" << result.value().size() << \" datagrams\";\n  if (handler_) {\n    for (auto& datagram : result.value()) {\n      handler_->onDatagram(std::move(datagram));\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/QuicWebTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/webtransport/WebTransportImpl.h>\n#include <quic/api/QuicSocket.h>\n\nnamespace proxygen {\n\nclass QuicWebTransport\n    : private WebTransportImpl::TransportProvider\n    , private WebTransportImpl::SessionProvider\n    , private quic::QuicSocket::ConnectionCallback\n    , private quic::QuicSocket::DatagramCallback\n    , public WebTransportImpl {\n\n public:\n  explicit QuicWebTransport(std::shared_ptr<quic::QuicSocket> quicSocket)\n      : WebTransportImpl(\n            static_cast<WebTransportImpl::TransportProvider&>(*this),\n            static_cast<WebTransportImpl::SessionProvider&>(*this)),\n        quicSocket_(std::move(quicSocket)) {\n    quicSocket_->setConnectionCallback(this);\n    quicSocket_->setDatagramCallback(this);\n  }\n\n  ~QuicWebTransport() override {\n    // close session is a no-op if closeSession or onConnectionEnd/Error has\n    // been called\n    closeSessionImpl(folly::none);\n  }\n\n  void setHandler(WebTransportHandler* handler) {\n    handler_ = handler;\n  }\n\n  [[nodiscard]] quic::TransportInfo getTransportInfo() const override {\n    XCHECK(quicSocket_);\n    return quicSocket_->getTransportInfo();\n  }\n\n  [[nodiscard]] quic::Expected<quic::QuicSocketLite::FlowControlState,\n                               quic::LocalErrorCode>\n  getConnectionFlowControl() const {\n    XCHECK(quicSocket_);\n    return quicSocket_->getConnectionFlowControl();\n  }\n\n private:\n  void onFlowControlUpdate(quic::StreamId /*id*/) noexcept override;\n\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override;\n\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override;\n\n  void onStopSending(quic::StreamId id,\n                     quic::ApplicationErrorCode error) noexcept override;\n\n  void onConnectionEnd() noexcept override;\n  void onConnectionError(quic::QuicError code) noexcept override;\n  void onConnectionEnd(quic::QuicError /* error */) noexcept override;\n  void onBidirectionalStreamsAvailable(\n      uint64_t /*numStreamsAvailable*/) noexcept override;\n\n  void onUnidirectionalStreamsAvailable(\n      uint64_t /*numStreamsAvailable*/) noexcept override;\n\n  folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n  newWebTransportBidiStream() override;\n\n  folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n  newWebTransportUniStream() override;\n\n  folly::SemiFuture<folly::Unit> awaitUniStreamCredit() override;\n\n  folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() override;\n\n  bool canCreateUniStream() override;\n\n  bool canCreateBidiStream() override;\n\n  folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n  sendWebTransportStreamData(\n      HTTPCodec::StreamID /*id*/,\n      std::unique_ptr<folly::IOBuf> /*data*/,\n      bool /*eof*/,\n      ByteEventCallback* /* deliveryCallback */) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  notifyPendingWriteOnStream(HTTPCodec::StreamID,\n                             quic::StreamWriteCallback* wcb) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> resetWebTransportEgress(\n      HTTPCodec::StreamID /*id*/, uint32_t /*errorCode*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n      setWebTransportStreamPriority(\n          HTTPCodec::StreamID /*id*/,\n          quic::PriorityQueue::Priority /*pri*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n      setWebTransportPriorityQueue(\n          std::unique_ptr<quic::PriorityQueue> /*queue*/) noexcept override;\n\n  folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                  WebTransport::ErrorCode>\n  readWebTransportData(HTTPCodec::StreamID id, size_t max) override {\n    auto res = quicSocket_->read(id, max);\n    if (res) {\n      return std::move(res.value());\n    } else {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  initiateReadOnBidiStream(HTTPCodec::StreamID id,\n                           quic::StreamReadCallback* readCallback) override {\n    auto res = quicSocket_->setReadCallback(id, readCallback);\n    if (res) {\n      return folly::unit;\n    } else {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n      pauseWebTransportIngress(HTTPCodec::StreamID /*id*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n      resumeWebTransportIngress(HTTPCodec::StreamID /*id*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n      stopReadingWebTransportIngress(\n          HTTPCodec::StreamID /*id*/,\n          folly::Optional<uint32_t> /*errorCode*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> /*datagram*/) override;\n\n  const folly::SocketAddress& getLocalAddress() const override {\n    return quicSocket_->getLocalAddress();\n  }\n\n  const folly::SocketAddress& getPeerAddress() const override {\n    return quicSocket_->getPeerAddress();\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n      uint64_t maxData) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxStreams(\n      uint64_t /*maxStreams*/, bool /*isBidi*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTStreamsBlocked(\n      uint64_t /*maxStreams*/, bool /*isBidi*/) override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTDataBlocked(\n      uint64_t /*maxData*/) override;\n\n  bool usesEncodedApplicationErrorCodes() override {\n    return false;\n  }\n\n  bool isPeerInitiatedStream(HTTPCodec::StreamID id) override {\n    return quicSocket_->getStreamInitiator(id) == quic::StreamInitiator::Remote;\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> closeSession(\n      folly::Optional<uint32_t> error) override {\n    return closeSessionImpl(std::move(error));\n  }\n\n  void onDatagramsAvailable() noexcept override;\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> closeSessionImpl(\n      folly::Optional<uint32_t> error);\n  void onConnectionEndImpl(folly::Optional<quic::QuicError> error);\n\n  void clearQuicSocket() {\n    if (quicSocket_) {\n      // This should be a no-op if the transport correctly reset all callbacks\n      terminateSessionStreams(WebTransport::kInternalError, \"socket teardown\");\n      quicSocket_->setConnectionCallback(nullptr);\n      quicSocket_->setDatagramCallback(nullptr);\n      quicSocket_.reset();\n    }\n  }\n\n  std::shared_ptr<quic::QuicSocket> quicSocket_;\n  WebTransportHandler* handler_{nullptr};\n  folly::Optional<folly::Promise<folly::Unit>> waitingForUniStreams_;\n  folly::Optional<folly::Promise<folly::Unit>> waitingForBidiStreams_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/QuicWtSession.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/webtransport/QuicWtSession.h>\n\nusing FCState = proxygen::WebTransport::FCState;\n\nnamespace proxygen {\n\nnamespace {\nstatic constexpr uint64_t kMaxWtIngressBuf = 65'535;\n\ndetail::WtStreamManager::WtConfig createConfig() {\n  detail::WtStreamManager::WtConfig config;\n  config.selfMaxStreamsBidi = detail::kMaxVarint;\n  config.selfMaxStreamsUni = detail::kMaxVarint;\n  config.selfMaxConnData = detail::kMaxVarint;\n  config.selfMaxStreamDataBidi = detail::kMaxVarint;\n  config.selfMaxStreamDataUni = detail::kMaxVarint;\n  config.peerMaxStreamsBidi = detail::kMaxVarint;\n  config.peerMaxStreamsUni = detail::kMaxVarint;\n  config.peerMaxConnData = detail::kMaxVarint;\n  config.peerMaxStreamDataBidi = detail::kMaxVarint;\n  config.peerMaxStreamDataUni = detail::kMaxVarint;\n  return config;\n}\n\nstruct WtEventVisitor {\n  std::shared_ptr<quic::QuicSocket>& quicSocket;\n\n  void operator()(const detail::WtStreamManager::ResetStream& ev) const {\n    quicSocket->resetStream(ev.streamId, ev.err);\n  }\n\n  void operator()(const detail::WtStreamManager::StopSending& ev) const {\n    quicSocket->setReadCallback(ev.streamId, nullptr, ev.err);\n  }\n\n  void operator()(const detail::WtStreamManager::CloseSession& /*ev*/) const {\n    // Don't call wtHandler_->onSessionEnd here, it's handled in\n    // onConnectionEndImpl. WtStreamManager generates CloseSession when\n    // we call shutdown(), which already means we're closing the session.\n  }\n\n  void operator()(const detail::WtStreamManager::MaxConnData& /*ev*/) const {\n  }\n  void operator()(const detail::WtStreamManager::MaxStreamData& /*ev*/) const {\n  }\n  void operator()(const detail::WtStreamManager::MaxStreamsBidi& /*ev*/) const {\n  }\n  void operator()(const detail::WtStreamManager::MaxStreamsUni& /*ev*/) const {\n  }\n  void operator()(const detail::WtStreamManager::DrainSession& /*ev*/) const {\n  }\n};\n\n} // namespace\n\nQuicWtSession::QuicWtSession(std::shared_ptr<quic::QuicSocket> quicSocket,\n                             std::unique_ptr<WebTransportHandler> wtHandler)\n    : WtSessionBase(nullptr, sm_),\n      quicSocket_(std::move(quicSocket)),\n      wtHandler_(std::move(wtHandler)),\n      priorityQueue_(std::make_unique<quic::HTTPPriorityQueue>()),\n      sm_{quicSocket_->getState()->nodeType == quic::QuicNodeType::Server\n              ? detail::WtDir::Server\n              : detail::WtDir::Client,\n          createConfig(),\n          *this,\n          *this,\n          *priorityQueue_} {\n  quicSocket_->setConnectionCallback(this);\n  quicSocket_->setDatagramCallback(this);\n}\n\nQuicWtSession::~QuicWtSession() {\n  closeSessionImpl(folly::none);\n  quicSocket_.reset();\n  wtHandler_.reset();\n}\n\nfolly::Expected<WebTransport::StreamWriteHandle*, WebTransport::ErrorCode>\nQuicWtSession::createUniStream() noexcept {\n  XCHECK(quicSocket_);\n  auto id = quicSocket_->createUnidirectionalStream();\n  if (id.hasError()) {\n    return folly::makeUnexpected(ErrorCode::STREAM_CREATION_ERROR);\n  }\n  return CHECK_NOTNULL(sm_.getOrCreateEgressHandle(*id));\n}\n\nfolly::Expected<WebTransport::BidiStreamHandle, WebTransport::ErrorCode>\nQuicWtSession::createBidiStream() noexcept {\n  XCHECK(quicSocket_);\n  auto id = quicSocket_->createBidirectionalStream();\n  if (id.hasError()) {\n    return folly::makeUnexpected(ErrorCode::STREAM_CREATION_ERROR);\n  }\n  auto bidiHandle = sm_.getOrCreateBidiHandle(*id);\n  XCHECK(bidiHandle.readHandle && bidiHandle.writeHandle);\n  sm_.setReadCb(*bidiHandle.readHandle, this);\n  quicSocket_->setReadCallback(*id, this);\n  return bidiHandle;\n}\n\nfolly::SemiFuture<folly::Unit> QuicWtSession::awaitUniStreamCredit() noexcept {\n  XCHECK(quicSocket_);\n  if (quicSocket_->getNumOpenableUnidirectionalStreams() > 0) {\n    return folly::makeFuture(folly::unit);\n  }\n  auto [promise, future] = folly::makePromiseContract<folly::Unit>();\n  uniCreditPromise() = std::move(promise);\n  return std::move(future);\n}\n\nfolly::SemiFuture<folly::Unit> QuicWtSession::awaitBidiStreamCredit() noexcept {\n  XCHECK(quicSocket_);\n  if (quicSocket_->getNumOpenableBidirectionalStreams() > 0) {\n    return folly::makeFuture(folly::unit);\n  }\n  auto [promise, future] = folly::makePromiseContract<folly::Unit>();\n  bidiCreditPromise() = std::move(promise);\n  return std::move(future);\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWtSession::sendDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept {\n  XCHECK(quicSocket_);\n  auto writeRes = quicSocket_->writeDatagram(std::move(datagram));\n  if (writeRes.hasError()) {\n    XLOG(ERR) << \"Failed to send datagram, err= \" << writeRes.error();\n    return folly::makeUnexpected(ErrorCode::GENERIC_ERROR);\n  }\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWtSession::closeSession(folly::Optional<uint32_t> error) noexcept {\n  return closeSessionImpl(std::move(error));\n}\n\n// -- QuicSocket::ReadCallback overrides --\nvoid QuicWtSession::readAvailable(quic::StreamId id) noexcept {\n  XCHECK(quicSocket_);\n  auto* rh = sm_.getBidiHandle(id).readHandle;\n  if (!rh) {\n    XLOG(ERR) << \"Read handle not found for stream \" << id;\n    return;\n  }\n  auto canRead =\n      kMaxWtIngressBuf - std::min(sm_.bufferedBytes(*rh), kMaxWtIngressBuf);\n  if (canRead == 0) {\n    maybePauseIngress(id);\n    return;\n  }\n  auto readRes = quicSocket_->read(id, canRead);\n  if (readRes.hasError()) {\n    XLOG(ERR) << \"Read error for stream \" << id;\n    return;\n  }\n  auto& [data, eof] = readRes.value();\n  auto res = sm_.enqueue(\n      *rh, WebTransport::StreamData{.data = std::move(data), .fin = eof});\n  XCHECK_NE(res, detail::WtStreamManager::Result::Fail);\n  maybePauseIngress(id);\n}\n\nvoid QuicWtSession::readError(quic::StreamId id,\n                              quic::QuicError error) noexcept {\n  XLOG(ERR) << \"Read error on stream \" << id << \": \" << error;\n  sm_.onResetStream(detail::WtStreamManager::ResetStream{\n      id, *error.code.asApplicationErrorCode()});\n}\n\n// -- QuicSocket::ConnectionCallback overrides --\nvoid QuicWtSession::onNewBidirectionalStream(quic::StreamId id) noexcept {\n  XCHECK(wtHandler_);\n  auto bidiHandle = sm_.getOrCreateBidiHandle(id);\n  XCHECK(bidiHandle.readHandle && bidiHandle.writeHandle);\n  sm_.setReadCb(*bidiHandle.readHandle, this);\n  quicSocket_->setReadCallback(id, this);\n  wtHandler_->onNewBidiStream(bidiHandle);\n}\n\nvoid QuicWtSession::onNewUnidirectionalStream(quic::StreamId id) noexcept {\n  XCHECK(wtHandler_);\n  auto* rh = CHECK_NOTNULL(sm_.getOrCreateIngressHandle(id));\n  sm_.setReadCb(*rh, this);\n  quicSocket_->setReadCallback(id, this);\n  wtHandler_->onNewUniStream(rh);\n}\n\nvoid QuicWtSession::onStopSending(\n    quic::StreamId id, quic::ApplicationErrorCode errorCode) noexcept {\n  sm_.onStopSending({.streamId = id, .err = errorCode});\n}\n\nvoid QuicWtSession::onConnectionEnd() noexcept {\n  onConnectionEndImpl(folly::none);\n}\n\nvoid QuicWtSession::onConnectionEnd(quic::QuicError error) noexcept {\n  onConnectionEndImpl(error);\n}\n\nvoid QuicWtSession::onConnectionError(quic::QuicError error) noexcept {\n  onConnectionEndImpl(error);\n}\n\nvoid QuicWtSession::onBidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (numStreamsAvailable > 0) {\n    onBidiStreamCreditAvail();\n  }\n}\n\nvoid QuicWtSession::onUnidirectionalStreamsAvailable(\n    uint64_t numStreamsAvailable) noexcept {\n  if (numStreamsAvailable > 0) {\n    onUniStreamCreditAvail();\n  }\n}\n\n// -- QuicSocket::DatagramCallback overrides --\nvoid QuicWtSession::onDatagramsAvailable() noexcept {\n  XCHECK(quicSocket_);\n  auto result = quicSocket_->readDatagramBufs();\n  if (result.hasError()) {\n    XLOG(ERR) << \"Got error while reading datagrams, err=\"\n              << toString(result.error());\n    closeSession(WebTransport::kInternalError);\n    return;\n  }\n  XLOG(DBG4) << \"Received \" << result.value().size() << \" datagrams\";\n  XCHECK(wtHandler_);\n  for (auto& datagram : result.value()) {\n    if (wtHandler_) {\n      wtHandler_->onDatagram(std::move(datagram));\n    }\n  }\n}\n\n// -- WtStreamManager::ReadCallback overrides --\nvoid QuicWtSession::readReady(\n    detail::WtStreamManager::WtReadHandle& rh) noexcept {\n  maybeResumeIngress(rh.getID());\n}\n\n// -- WtStreamManager::EgressCallback overrides --\nvoid QuicWtSession::eventsAvailable() noexcept {\n  XCHECK(quicSocket_);\n\n  // process control events first\n  auto events = sm_.moveEvents();\n  for (auto& event : events) {\n    std::visit(WtEventVisitor{quicSocket_}, event);\n  }\n  // then process writable streams\n  while (!priorityQueue_->empty()) {\n    auto id = priorityQueue_->getNextScheduledID(std::nullopt);\n    if (!id.isStreamID()) { // skip datagrams\n      break;\n    }\n    auto streamId = id.asStreamID();\n    auto maxData = quicSocket_->getMaxWritableOnStream(streamId);\n    auto* wh = sm_.getBidiHandle(streamId).writeHandle;\n    if (!wh || !maxData) {\n      XLOG(DBG4) << \"Write handle or stream not found for \" << streamId;\n      priorityQueue_->erase(id);\n      continue;\n    }\n    if (*maxData == 0) {\n      XLOG(DBG4) << \"Blocked on QUIC flow control for stream \" << streamId;\n      priorityQueue_->erase(id);\n      quicSocket_->notifyPendingWriteOnStream(streamId, this);\n      continue;\n    }\n    auto streamData = sm_.dequeue(*wh, *maxData);\n    if (streamData.data || streamData.fin) {\n      auto res = quicSocket_->writeChain(streamId,\n                                         std::move(streamData.data),\n                                         streamData.fin,\n                                         streamData.deliveryCallback);\n      if (res.hasError()) {\n        XLOG(ERR) << \"Failed to write to stream \" << streamId;\n        wh->resetStream(WebTransport::kInternalError);\n      }\n    }\n  }\n}\n\n// -- WtStreamManager::IngressCallback overrides --\nvoid QuicWtSession::onNewPeerStream(uint64_t /*streamId*/) noexcept {\n}\n\n// -- StreamWriteCallback overrides --\nvoid QuicWtSession::onStreamWriteReady(quic::StreamId streamId,\n                                       uint64_t /*maxToSend*/) noexcept {\n  auto* wh = sm_.getBidiHandle(streamId).writeHandle;\n  if (wh) {\n    priorityQueue_->insertOrUpdate(\n        quic::PriorityQueue::Identifier::fromStreamID(wh->getID()),\n        wh->getPriority());\n    eventsAvailable();\n  }\n}\n\nvoid QuicWtSession::onStreamWriteError(quic::StreamId streamId,\n                                       quic::QuicError error) noexcept {\n  XLOG(ERR) << \"Write error on stream \" << streamId << \": \" << error;\n  auto* wh = sm_.getBidiHandle(streamId).writeHandle;\n  if (wh) {\n    wh->resetStream(WebTransport::kInternalError);\n  }\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nQuicWtSession::closeSessionImpl(folly::Optional<uint32_t> error) {\n  XCHECK(quicSocket_);\n  sm_.shutdown({.err = error.value_or(0), .msg = \"closeSession\"});\n  quicSocket_->close(\n      quic::QuicError(quic::ApplicationErrorCode(error.value_or(0))));\n  quicSocket_->setConnectionCallback(nullptr);\n  quicSocket_->setDatagramCallback(nullptr);\n  return folly::unit;\n}\n\nvoid QuicWtSession::onConnectionEndImpl(\n    const folly::Optional<quic::QuicError>& error) {\n  XCHECK(quicSocket_);\n  XCHECK(wtHandler_);\n  folly::Optional<uint32_t> errCodeOpt;\n  if (error && error->code.asApplicationErrorCode()) {\n    errCodeOpt = *error->code.asApplicationErrorCode();\n  }\n  sm_.shutdown({.err = errCodeOpt.value_or(0), .msg = \"\"});\n  quicSocket_->setConnectionCallback(nullptr);\n  quicSocket_->setDatagramCallback(nullptr);\n  wtHandler_->onSessionEnd(errCodeOpt);\n}\n\nvoid QuicWtSession::maybePauseIngress(uint64_t id) noexcept {\n  XCHECK(quicSocket_);\n  auto* handle = sm_.getBidiHandle(id).readHandle;\n  if (!handle) {\n    XLOG(DBG4) << \"No read handle created for stream \" << id;\n    return;\n  }\n  if (sm_.bufferedBytes(*handle) >= kMaxWtIngressBuf) {\n    XLOG(DBG4) << \"Pausing ingress for stream \" << id;\n    auto res = quicSocket_->pauseRead(id);\n    if (res.hasError()) {\n      XLOG(ERR) << \"Failed to pause read for stream \" << id;\n      return;\n    }\n  }\n}\n\nvoid QuicWtSession::maybeResumeIngress(uint64_t id) noexcept {\n  XCHECK(quicSocket_);\n  auto* handle = sm_.getBidiHandle(id).readHandle;\n  if (!handle) {\n    XLOG(DBG4) << \"No read handle created for stream \" << id;\n    return;\n  }\n  if (sm_.bufferedBytes(*handle) < kMaxWtIngressBuf) {\n    XLOG(DBG4) << \"Resuming ingress for stream \" << id;\n    auto res = quicSocket_->resumeRead(id);\n    if (res.hasError()) {\n      XLOG(ERR) << \"Failed to resume read for stream \" << id;\n    }\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/QuicWtSession.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n#include <proxygen/lib/http/webtransport/WtUtils.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nnamespace proxygen {\n\nclass QuicWtSession\n    : public detail::WtSessionBase\n    , private quic::QuicSocket::ReadCallback\n    , private quic::QuicSocket::ConnectionCallback\n    , private quic::QuicSocket::DatagramCallback\n    , private quic::StreamWriteCallback\n    , private detail::WtStreamManager::ReadCallback\n    , public detail::WtStreamManager::EgressCallback\n    , public detail::WtStreamManager::IngressCallback {\n\n public:\n  explicit QuicWtSession(std::shared_ptr<quic::QuicSocket> quicSocket,\n                         std::unique_ptr<WebTransportHandler> wtHandler);\n\n  ~QuicWtSession() override;\n\n  [[nodiscard]] quic::TransportInfo getTransportInfo() const noexcept override {\n    XCHECK(quicSocket_);\n    return quicSocket_->getTransportInfo();\n  }\n\n  [[nodiscard]] quic::Expected<quic::QuicSocketLite::FlowControlState,\n                               quic::LocalErrorCode>\n  getConnectionFlowControl() const {\n    XCHECK(quicSocket_);\n    return quicSocket_->getConnectionFlowControl();\n  }\n\n  folly::Expected<StreamWriteHandle*, ErrorCode> createUniStream() noexcept\n      override;\n  folly::Expected<BidiStreamHandle, ErrorCode> createBidiStream() noexcept\n      override;\n  folly::SemiFuture<folly::Unit> awaitUniStreamCredit() noexcept override;\n  folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() noexcept override;\n\n  folly::Expected<folly::Unit, ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) noexcept override;\n\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return quicSocket_->getLocalAddress();\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return quicSocket_->getPeerAddress();\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> closeSession(\n      folly::Optional<uint32_t> error) noexcept override;\n\n private:\n  // -- quic::QuicSocket::ReadCallback overrides --\n  void readAvailable(quic::StreamId streamId) noexcept override;\n  void readError(quic::StreamId streamId,\n                 quic::QuicError error) noexcept override;\n\n  // -- QuicSocket::ConnectionCallback overrides --\n  void onNewBidirectionalStream(quic::StreamId id) noexcept override;\n  void onNewUnidirectionalStream(quic::StreamId id) noexcept override;\n  void onStopSending(quic::StreamId id,\n                     quic::ApplicationErrorCode error) noexcept override;\n  void onConnectionEnd() noexcept override;\n  void onConnectionEnd(quic::QuicError /* error */) noexcept override;\n  void onConnectionError(quic::QuicError code) noexcept override;\n  void onBidirectionalStreamsAvailable(\n      uint64_t /*numStreamsAvailable*/) noexcept override;\n  void onUnidirectionalStreamsAvailable(\n      uint64_t /*numStreamsAvailable*/) noexcept override;\n\n  // -- QuicSocket::DatagramCallback overrides --\n  void onDatagramsAvailable() noexcept override;\n\n  // -- StreamWriteCallback overrides --\n  void onStreamWriteReady(quic::StreamId id,\n                          uint64_t maxToSend) noexcept override;\n  void onStreamWriteError(quic::StreamId id,\n                          quic::QuicError error) noexcept override;\n\n  // -- WtStreamManager::ReadCallback overrides --\n  void readReady(detail::WtStreamManager::WtReadHandle& rh) noexcept override;\n\n  // -- WtStreamManager::EgressCallback overrides --\n  void eventsAvailable() noexcept override;\n\n  // -- WtStreamManager::IngressCallback overrides --\n  void onNewPeerStream(uint64_t streamId) noexcept override;\n\n  folly::Expected<folly::Unit, ErrorCode> closeSessionImpl(\n      folly::Optional<uint32_t> error);\n  void onConnectionEndImpl(const folly::Optional<quic::QuicError>& error);\n  void maybePauseIngress(uint64_t id) noexcept;\n  void maybeResumeIngress(uint64_t id) noexcept;\n\n  std::shared_ptr<quic::QuicSocket> quicSocket_{nullptr};\n  std::unique_ptr<WebTransportHandler> wtHandler_;\n  std::unique_ptr<quic::HTTPPriorityQueue> priorityQueue_;\n  detail::WtStreamManager sm_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/StreamPriorityQueue.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <optional>\n#include <quic/priority/PriorityQueue.h>\n\nnamespace proxygen::detail {\n\n/**\n * A thin wrapper around quic::PriorityQueue for managing stream egress.\n *\n * This class:\n * - Takes uint64_t streamId instead of quic::PriorityQueue::Identifier\n * - Tracks the count of streams in the queue\n * - Provides a simpler interface for stream-only operations\n *\n * The underlying PriorityQueue may be shared with other users (e.g.,\n * datagrams), so this wrapper only tracks streams inserted through its own\n * interface.\n */\nclass StreamPriorityQueue {\n public:\n  using Priority = quic::PriorityQueue::Priority;\n\n  explicit StreamPriorityQueue(quic::PriorityQueue& queue) noexcept\n      : queue_(queue) {\n  }\n\n  /**\n   * Insert a stream into the queue or update its priority if already present.\n   * Increments the stream count if the stream was not already in the queue.\n   */\n  void insert(uint64_t streamId, Priority priority) {\n    auto id = quic::PriorityQueue::Identifier::fromStreamID(streamId);\n    if (!queue_.contains(id)) {\n      count_++;\n    }\n    queue_.insertOrUpdate(id, priority);\n  }\n\n  /**\n   * Update the priority of a stream if it exists in the queue.\n   * Does not change the stream count.\n   */\n  void update(uint64_t streamId, Priority priority) {\n    auto id = quic::PriorityQueue::Identifier::fromStreamID(streamId);\n    queue_.updateIfExist(id, priority);\n  }\n\n  /**\n   * Remove a stream from the queue.\n   * Decrements the stream count if the stream was in the queue.\n   */\n  void erase(uint64_t streamId) {\n    auto id = quic::PriorityQueue::Identifier::fromStreamID(streamId);\n    if (queue_.contains(id)) {\n      count_--;\n    }\n    queue_.erase(id);\n  }\n\n  /**\n   * Consume bytes for fairness accounting.\n   * Pass-through to the underlying queue.\n   */\n  void consume(uint64_t bytes) {\n    queue_.consume(bytes);\n  }\n\n  /**\n   * Returns the number of streams currently in the queue.\n   */\n  [[nodiscard]] uint64_t count() const noexcept {\n    return count_;\n  }\n\n  /**\n   * Returns true if there are streams in the queue.\n   */\n  [[nodiscard]] bool hasStreams() const noexcept {\n    return count_ > 0;\n  }\n\n  /**\n   * Peek at the next scheduled stream ID without modifying state.\n   * Returns std::nullopt if the queue is empty or if the head of the queue\n   * is not a stream (e.g., a datagram).\n   */\n  [[nodiscard]] std::optional<uint64_t> peek() const noexcept {\n    if (queue_.empty()) {\n      return std::nullopt;\n    }\n    auto id = queue_.peekNextScheduledID();\n    if (!id.isStreamID()) {\n      return std::nullopt;\n    }\n    return id.asStreamID();\n  }\n\n private:\n  quic::PriorityQueue& queue_;\n  uint64_t count_{0};\n};\n\n} // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WebTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/http/webtransport/WebTransport.h\"\n\nnamespace {\n\n// read handle is invalid after reading a terminal ev (exc or eom)\nusing StreamData = proxygen::WebTransport::StreamData;\nbool isTerminalEv(const folly::Try<StreamData>& ev) {\n  return ev.hasException() || ev->fin;\n}\n\n} // namespace\n\nnamespace proxygen {\n\nWebTransport::Exception::Exception(uint32_t inError)\n    : std::runtime_error(folly::to<std::string>(\n          \"Peer reset or abandoned stream with error=\", inError)),\n      error(inError) {\n}\n\nWebTransport::Exception::Exception(uint32_t inError, const std::string& msg)\n    : std::runtime_error(folly::to<std::string>(msg, \" with error=\", inError)),\n      error(inError) {\n}\n\n/*static*/ folly::Expected<uint32_t, WebTransport::ErrorCode>\nWebTransport::toApplicationErrorCode(uint64_t h) {\n  if (!isEncodedApplicationErrorCode(h)) {\n    // This is not for us\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n  uint64_t shifted = h - kFirstErrorCode;\n  uint64_t appErrorCode = shifted - (shifted / 0x1f);\n  DCHECK_LE(appErrorCode, std::numeric_limits<uint32_t>::max());\n  return static_cast<uint32_t>(appErrorCode);\n}\n\nvoid WebTransport::StreamReadHandle::awaitNextRead(\n    folly::Executor* exec,\n    ReadStreamDataFn readCb,\n    folly::Optional<std::chrono::milliseconds> timeout) {\n  auto id = getID();\n  auto fut = readStreamData();\n  if (timeout) {\n    fut = std::move(fut).within(*timeout);\n  }\n  std::move(fut).via(exec).thenTry(\n      [this, id, cb = std::move(readCb)](auto streamData) {\n        auto* handle = isTerminalEv(streamData) ? nullptr : this;\n        cb(handle, id, std::move(streamData));\n      });\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WebTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/CancellationToken.h>\n#include <folly/Expected.h>\n#include <folly/Optional.h>\n#include <folly/SocketAddress.h>\n#include <folly/futures/Future.h>\n#include <folly/io/IOBuf.h>\n#include <quic/api/QuicCallbacks.h>\n#include <quic/api/TransportInfo.h>\n#include <quic/priority/PriorityQueue.h>\n\nnamespace proxygen {\n\n// Generic WebTransport interface\n//\n// Principles:\n//\n// 1. It should be easy to write simple applications\n// 2. The backpressure and error handling APIs should be understandable\n// 3. The same generic API should work for proxygen/lib and proxygen::coro\n//\n// Futures is the best way to implement #3 because they can be easily\n// awaited in a coroutine.\n//\n// Note there are no APIs to await new streams opened by the peer,\n// datagrams sent by the peer, or session closure.\n//  => These signals are delivered via the HTTP API\n\nclass WebTransport {\n public:\n  virtual ~WebTransport() = default;\n\n  // Errors that can be returned from API\n  enum class ErrorCode {\n    GENERIC_ERROR = 0x00,\n    INVALID_STREAM_ID,\n    STREAM_CREATION_ERROR,\n    SEND_ERROR,\n    STOP_SENDING,\n    SESSION_TERMINATED,\n    BLOCKED\n  };\n\n  static constexpr uint64_t kFirstErrorCode = 0x52e4a40fa8db;\n  static constexpr uint64_t kLastErrorCode = 0x52e5ac983162;\n  static constexpr uint32_t kSessionGone = 0x170d7b68;\n  static constexpr uint32_t kInternalError =\n      std::numeric_limits<uint32_t>::max();\n\n  static uint64_t toHTTPErrorCode(uint32_t n) {\n    return kFirstErrorCode + n + (n / 0x1e);\n  }\n\n  static bool isEncodedApplicationErrorCode(uint64_t x) {\n    return x >= kFirstErrorCode && x <= kLastErrorCode &&\n           ((x - 0x21) % 0x1f) != 0;\n  }\n\n  static folly::Expected<uint32_t, WebTransport::ErrorCode>\n  toApplicationErrorCode(uint64_t h);\n\n  class Exception : public std::runtime_error {\n   public:\n    explicit Exception(uint32_t inError);\n    Exception(uint32_t inError, const std::string& msg);\n    uint32_t error;\n  };\n\n  // The result of a read() operation\n  struct StreamData {\n    std::unique_ptr<folly::IOBuf> data{nullptr};\n    bool fin{false};\n  };\n\n  // Base class for StreamReadHandle / StreamWriteHandle\n  class StreamHandleBase {\n   public:\n    virtual ~StreamHandleBase() = default;\n\n    [[nodiscard]] uint64_t getID() const {\n      return id_;\n    }\n\n    // The caller may register a CancellationCallback on this token to be\n    // notified of asynchronous cancellation of the stream by the peer.\n    //\n    // For StreamWriteHandle in particular, the handle is still valid in a\n    // CancellationCallback, but not after that.  If the app doesn't terminate\n    // the stream from the callback, the stream will be reset automatically.\n    [[nodiscard]] folly::CancellationToken getCancelToken() const {\n      return cs_.getToken();\n    }\n\n    [[nodiscard]] const Exception* exception() const {\n      return ex_.get_exception<Exception>();\n    }\n\n   protected:\n    StreamHandleBase(uint64_t id) : id_(id) {\n    }\n\n    const uint64_t id_;\n    folly::CancellationSource cs_;\n    folly::exception_wrapper ex_;\n  };\n\n  // Handle for read streams\n  class StreamReadHandle : public StreamHandleBase {\n   public:\n    using StreamHandleBase::StreamHandleBase;\n    ~StreamReadHandle() override = default;\n\n    // Wait for data to be delivered on the stream.  If the stream is reset by\n    // the peer, a StreamReadHandle::Exception will be raised in the Future with\n    // the error code.  The Future may observe other exceptions such as\n    // folly::OperationCancelled if the session was closed, etc.\n    //\n    // The StreamReadHandle is invalid after reading StreamData with fin=true,\n    // or an exception.\n    virtual folly::SemiFuture<StreamData> readStreamData() = 0;\n\n    using ReadStreamDataFn = std::function<void(\n        StreamReadHandle*, uint64_t streamId, folly::Try<StreamData>)>;\n    void awaitNextRead(\n        folly::Executor* exec,\n        ReadStreamDataFn readCb,\n        folly::Optional<std::chrono::milliseconds> timeout = folly::none);\n\n    // Notify the peer to stop sending data.  The StreamReadHandle is invalid\n    // after this API call.\n    virtual folly::Expected<folly::Unit, ErrorCode> stopSending(\n        uint32_t error) = 0;\n  };\n\n  class ByteEventCallback : public quic::ByteEventCallback {\n   public:\n    ByteEventCallback() = default;\n\n    ~ByteEventCallback() override = default;\n\n    virtual void onByteEvent(quic::StreamId id, uint64_t offset) noexcept = 0;\n\n    virtual void onByteEventCanceled(quic::StreamId id,\n                                     uint64_t offset) noexcept = 0;\n\n    // setWritePrefaceSize is called by the WebTransport layer, and doesn't need\n    // to be called by the user.\n    // The reason this is present is that some WebTransport implementations\n    // write a preface to the stream once it is created. We want to pass the\n    // correct value of offset to the onByteEvent and onByteEventCanceled\n    // callbacks.\n    void setWritePrefaceSize(uint32_t writePrefaceSize) {\n      writePrefaceSize_ = writePrefaceSize;\n    }\n\n   private:\n    void onByteEvent(quic::ByteEvent byteEvent) final {\n      onByteEvent(byteEvent.id, byteEvent.offset - writePrefaceSize_);\n    }\n\n    void onByteEventCanceled(quic::ByteEventCancellation cancellation) final {\n      onByteEventCanceled(cancellation.id,\n                          cancellation.offset - writePrefaceSize_);\n    }\n\n   private:\n    uint32_t writePrefaceSize_{0};\n  };\n\n  enum class FCState { BLOCKED, UNBLOCKED, SESSION_CLOSED };\n  // Handle for write streams\n  class StreamWriteHandle : public StreamHandleBase {\n   public:\n    using StreamHandleBase::StreamHandleBase;\n    ~StreamWriteHandle() override = default;\n\n    // Write the data and optional fin to the stream.\n    //\n    // The StreamWriteHandle becomes invalid after calling writeStreamData with\n    // fin=true or calling resetStream.\n    //\n    // If the peer sends a STOP_SENDING, the app is notified via the\n    // CancellationToken for this handle, and the code can be queried via\n    // stopSendingErrorCode.  The app SHOULD reset the stream from a\n    // CancellationCallback.  Calling writeStreamData from the callback will\n    // fail with a WebTransport::Exception with the stopSendingErrorCode.\n    // After the cancellation callback, the StreamWriteHandle is invalid.\n    virtual folly::Expected<FCState, ErrorCode> writeStreamData(\n        std::unique_ptr<folly::IOBuf> data,\n        bool fin,\n        ByteEventCallback* byteEventCallback) = 0;\n\n    // Reset the stream with the given error\n    virtual folly::Expected<folly::Unit, ErrorCode> resetStream(\n        uint32_t error) = 0;\n\n    virtual folly::Expected<folly::Unit, ErrorCode> setPriority(\n        quic::PriorityQueue::Priority priority) {\n      priority_ = priority;\n      return folly::unit;\n    }\n\n    [[nodiscard]] quic::PriorityQueue::Priority getPriority() const {\n      return priority_;\n    }\n\n    // The returned Future will complete when the stream is available for more\n    // writes.\n    virtual folly::Expected<folly::SemiFuture<uint64_t>, ErrorCode>\n    awaitWritable() = 0;\n\n   private:\n    quic::PriorityQueue::Priority priority_;\n  };\n\n  // Handle for bidirectional streams\n  struct BidiStreamHandle {\n    StreamReadHandle* readHandle{nullptr};\n    StreamWriteHandle* writeHandle{nullptr};\n  };\n\n  // Create a new unidirectional stream\n  //\n  // Returns a StreamWriteHandle to the new stream if successful, or ErrorCode,\n  // including in cases where stream credit is exhausted\n  virtual folly::Expected<StreamWriteHandle*, ErrorCode> createUniStream() = 0;\n\n  // Create a new bididirectional stream\n  //\n  // Returns a BidiStreamHandle to the new stream if successful, or ErrorCode,\n  // including in cases where stream credit is exhausted.  Note the application\n  // needs to call readStreamData to read from the read half.\n  virtual folly::Expected<BidiStreamHandle, ErrorCode> createBidiStream() = 0;\n\n  // Wait for credit to create a stream of the given type.  If stream credit\n  // is available, will immediately return a ready SemiFuture.\n  virtual folly::SemiFuture<folly::Unit> awaitUniStreamCredit() = 0;\n  virtual folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() = 0;\n\n  // API using stream IDs\n  // These methods may be used if the app wants to manipulate open streams\n  // without holding their handles\n  virtual folly::Expected<folly::SemiFuture<StreamData>,\n                          WebTransport::ErrorCode>\n  readStreamData(uint64_t id) = 0;\n  virtual folly::Expected<FCState, ErrorCode> writeStreamData(\n      uint64_t id,\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      ByteEventCallback* deliveryCallback) = 0;\n  virtual folly::Expected<folly::Unit, ErrorCode> resetStream(\n      uint64_t streamId, uint32_t error) = 0;\n  virtual folly::Expected<folly::Unit, ErrorCode> setPriority(\n      uint64_t streamId, quic::PriorityQueue::Priority priority) = 0;\n  virtual folly::Expected<folly::Unit, ErrorCode> setPriorityQueue(\n      std::unique_ptr<quic::PriorityQueue> queue) noexcept = 0;\n  virtual folly::Expected<folly::SemiFuture<uint64_t>, ErrorCode> awaitWritable(\n      uint64_t streamId) = 0;\n\n  virtual folly::Expected<folly::Unit, ErrorCode> stopSending(\n      uint64_t streamId, uint32_t error) = 0;\n\n  // Sends the buffer as a datagram\n  virtual folly::Expected<folly::Unit, ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) = 0;\n\n  // Get the local and peer socket addresses\n  [[nodiscard]] virtual const folly::SocketAddress& getLocalAddress() const = 0;\n  [[nodiscard]] virtual const folly::SocketAddress& getPeerAddress() const = 0;\n\n  // Close the WebTransport session, with an optional error\n  //\n  // Any pending futures will complete with a folly::OperationCancelled\n  // exception\n  // Return QUIC transport statistics similar to TCPInfo\n  [[nodiscard]] virtual quic::TransportInfo getTransportInfo() const = 0;\n\n  virtual folly::Expected<folly::Unit, ErrorCode> closeSession(\n      folly::Optional<uint32_t> error = folly::none) = 0;\n};\n\n// WebTransportHandler is a virtual interface for handling events that come\n// from web transport that are not tied to an existing stream\n//\n//  * New streams\n//  * Datagrams\n//  * The end of of session\nclass WebTransportHandler {\n public:\n  using Ptr = std::unique_ptr<WebTransportHandler>;\n  virtual ~WebTransportHandler() noexcept = default;\n\n  virtual void onNewUniStream(\n      WebTransport::StreamReadHandle* readHandle) noexcept = 0;\n  virtual void onNewBidiStream(\n      WebTransport::BidiStreamHandle bidiHandle) noexcept = 0;\n\n  virtual void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept = 0;\n  virtual void onSessionEnd(folly::Optional<uint32_t> error) noexcept = 0;\n  virtual void onSessionDrain() noexcept = 0;\n\n  // TODO(@damlaj): hmm likely needs a non-virtual fn to unconditionally set a\n  // member WebTransportPtr here (then defer to an overridable fn, e.g.\n  // onWebTransportSessionImpl); and unsetting said member via ::onSessionEnd\n  virtual void onWebTransportSession(std::shared_ptr<WebTransport>) noexcept {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WebTransportImpl.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/webtransport/WebTransportImpl.h>\n\n#include <glog/logging.h>\n\nnamespace {\n\nconstexpr uint64_t kMaxWTIngressBuf = 65535;\nconstexpr uint64_t kMaxWTStreams = 1ULL << 60;\n\nusing StreamData = proxygen::WebTransport::StreamData;\nusing ReadPromiseT = folly::Promise<StreamData>;\nReadPromiseT emptyReadPromise() {\n  return ReadPromiseT::makeEmpty();\n}\n\nusing WritePromiseT = folly::Promise<uint64_t>;\nWritePromiseT emptyWritePromise() {\n  return WritePromiseT::makeEmpty();\n}\n\n} // namespace\n\nnamespace proxygen {\n\nvoid WebTransportImpl::destroy() {\n  terminateSessionStreams(WebTransport::kInternalError, \"\");\n}\n\nvoid WebTransportImpl::terminateSessionStreams(uint32_t errorCode,\n                                               const std::string& reason) {\n  auto wtIngressStreams = std::move(wtIngressStreams_);\n  for (auto& [id, stream] : wtIngressStreams) {\n    // Deliver an error to the application if needed\n    VLOG(4) << \"aborting wt ingress id=\" << id << \" err=\" << errorCode\n            << \"; open=\" << int(stream.open());\n    if (stream.open()) {\n      stream.deliverReadError(WebTransport::Exception(errorCode, reason));\n      stopReadingWebTransportIngress(id, errorCode);\n    }\n  }\n\n  auto wtEgressStreams = std::move(wtEgressStreams_);\n  for (auto& [id, stream] : wtEgressStreams) {\n    // Deliver an error to the application\n    stream.onStopSending(errorCode);\n    resetWebTransportEgress(id, errorCode);\n  }\n}\n\nvoid WebTransportImpl::onMaxData(uint64_t maxData) noexcept {\n  if (sendFlowController_.grant(maxData) &&\n      sendFlowController_.getAvailable() > 0) {\n    for (auto it = wtEgressStreams_.begin();\n         it != wtEgressStreams_.end() &&\n         sendFlowController_.getAvailable() > 0;) {\n      // Increment here in case we delete the stream in\n      // flushBufferedWrites -> sendWebTransportStreamData\n      auto currIt = it++;\n      currIt->second.flushBufferedWrites();\n    }\n  } else {\n    VLOG(4) << __func__ << \" failed to grant maxData=\" << maxData;\n  }\n}\n\nvoid WebTransportImpl::onMaxStreams(uint64_t maxStreams, bool isBidi) noexcept {\n  auto& flowControl =\n      isBidi ? selfBidiStreamFlowControl_ : selfUniStreamFlowControl_;\n  if (maxStreams < flowControl.maxStreamID || maxStreams > kMaxWTStreams) {\n    // TODO(@joannajo): Return a protocol error once it's supported in the\n    // draft.\n    return;\n  }\n  flowControl.maxStreamID = maxStreams;\n}\n\nvoid WebTransportImpl::onStreamsBlocked(uint64_t maxStreams,\n                                        bool isBidi) noexcept {\n  if (shouldGrantStreamCredit(isBidi)) {\n    LOG(ERROR) << __func__ << \" maxStreams=\" << maxStreams\n               << \"; shouldGrantStreamCredit\";\n    auto& flowControl =\n        isBidi ? peerBidiStreamFlowControl_ : peerUniStreamFlowControl_;\n    flowControl.maxStreamID += flowControl.targetConcurrentStreams / 2;\n    tp_.sendWTMaxStreams(flowControl.maxStreamID, isBidi);\n  }\n}\n\nvoid WebTransportImpl::onDataBlocked(uint64_t maxData) noexcept {\n  if (maxData > recvFlowController_.getMaxOffset()) {\n    return;\n  }\n  if (shouldGrantFlowControl()) {\n    LOG(ERROR) << __func__ << \" maxData=\" << maxData\n               << \"; shouldGrantFlowControl\";\n    auto newMaxData =\n        recvFlowController_.getMaxOffset() + kDefaultWTReceiveWindow;\n    recvFlowController_.grant(newMaxData);\n    tp_.sendWTMaxData(newMaxData);\n  }\n}\n\nfolly::Expected<WebTransport::StreamWriteHandle*, WebTransport::ErrorCode>\nWebTransportImpl::newWebTransportUniStream() {\n  if (sessionCloseError_.has_value()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::SESSION_TERMINATED);\n  }\n  if (!tp_.canCreateUniStream()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::BLOCKED);\n  }\n  auto id = tp_.newWebTransportUniStream();\n  if (!id) {\n    tp_.sendWTStreamsBlocked(selfUniStreamFlowControl_.maxStreamID, false);\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  auto res = wtEgressStreams_.emplace(std::piecewise_construct,\n                                      std::forward_as_tuple(*id),\n                                      std::forward_as_tuple(*this, *id));\n  sp_.refreshTimeout();\n  return &res.first->second;\n}\n\nfolly::Expected<WebTransport::BidiStreamHandle, WebTransport::ErrorCode>\nWebTransportImpl::newWebTransportBidiStream() {\n  if (sessionCloseError_.has_value()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::SESSION_TERMINATED);\n  }\n  if (!tp_.canCreateBidiStream()) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::BLOCKED);\n  }\n  auto id = tp_.newWebTransportBidiStream();\n  if (!id) {\n    tp_.sendWTStreamsBlocked(selfBidiStreamFlowControl_.maxStreamID, true);\n    return folly::makeUnexpected(\n        WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  }\n  auto ingressRes =\n      wtIngressStreams_.emplace(std::piecewise_construct,\n                                std::forward_as_tuple(*id),\n                                std::forward_as_tuple(*this, *id));\n  auto readHandle = &ingressRes.first->second;\n  tp_.initiateReadOnBidiStream(*id, readHandle);\n  sp_.refreshTimeout();\n  auto egressRes = wtEgressStreams_.emplace(std::piecewise_construct,\n                                            std::forward_as_tuple(*id),\n                                            std::forward_as_tuple(*this, *id));\n  return WebTransport::BidiStreamHandle(\n      {.readHandle = readHandle, .writeHandle = &egressRes.first->second});\n}\n\nWebTransportImpl::BidiStreamHandle WebTransportImpl::onWebTransportBidiStream(\n    HTTPCodec::StreamID id) {\n  auto ingRes = wtIngressStreams_.emplace(std::piecewise_construct,\n                                          std::forward_as_tuple(id),\n                                          std::forward_as_tuple(*this, id));\n\n  auto egRes = wtEgressStreams_.emplace(std::piecewise_construct,\n                                        std::forward_as_tuple(id),\n                                        std::forward_as_tuple(*this, id));\n  return WebTransportImpl::BidiStreamHandle(\n      {.readHandle = &ingRes.first->second,\n       .writeHandle = &egRes.first->second});\n}\n\nWebTransportImpl::StreamReadHandle* WebTransportImpl::onWebTransportUniStream(\n    HTTPCodec::StreamID id) {\n  auto ingRes = wtIngressStreams_.emplace(std::piecewise_construct,\n                                          std::forward_as_tuple(id),\n                                          std::forward_as_tuple(*this, id));\n\n  return &ingRes.first->second;\n}\n\nfolly::Expected<WebTransportImpl::WebTransport::FCState,\n                WebTransport::ErrorCode>\nWebTransportImpl::sendWebTransportStreamData(\n    HTTPCodec::StreamID id,\n    std::unique_ptr<folly::IOBuf> data,\n    bool eof,\n    ByteEventCallback* deliveryCallback) {\n  auto dataLen = data ? data->computeChainDataLength() : 0;\n  // WebTransportImpl::sendWebTransportStreamData will only be called when\n  // dataLen <= available window\n  bool blocked = !sendFlowController_.reserve(dataLen);\n  auto res = tp_.sendWebTransportStreamData(\n      id, std::move(data), eof, deliveryCallback);\n  if (blocked) {\n    // call sendWTDataBlocked() here?\n    res = WebTransport::FCState::BLOCKED;\n  }\n  if (eof || res.hasError()) {\n    closeEgressStream(id);\n  }\n  sp_.refreshTimeout();\n  return res;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nWebTransportImpl::resetWebTransportEgress(HTTPCodec::StreamID id,\n                                          uint32_t errorCode) {\n  auto res = tp_.resetWebTransportEgress(id, errorCode);\n  closeEgressStream(id);\n  sp_.refreshTimeout();\n  return res;\n}\n\nfolly::Expected<folly::Unit, WebTransport::ErrorCode>\nWebTransportImpl::stopReadingWebTransportIngress(\n    HTTPCodec::StreamID id, folly::Optional<uint32_t> errorCode) {\n  auto res = tp_.stopReadingWebTransportIngress(id, errorCode);\n  sp_.refreshTimeout();\n  return res;\n}\n\n// -- StreamWriteHandle & StreamReadHandle functions below --\n\nWebTransportImpl::StreamWriteHandle::StreamWriteHandle(WebTransportImpl& tp,\n                                                       HTTPCodec::StreamID id)\n    : WebTransport::StreamWriteHandle(id),\n      impl_(tp),\n      writePromise_(emptyWritePromise()) {\n}\n\nfolly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\nWebTransportImpl::StreamWriteHandle::writeStreamData(\n    std::unique_ptr<folly::IOBuf> data,\n    bool fin,\n    ByteEventCallback* deliveryCallback) {\n  VLOG(4) << __func__ << \" data=\" << data.get() << \" fin=\" << fin\n          << \" deliveryCallback=\" << deliveryCallback << \" ex=\" << bool(ex_)\n          << \" bufferedWrites_.size()=\" << bufferedWrites_.size();\n\n  if (ex_) {\n    return folly::makeUnexpected(WebTransport::ErrorCode::STOP_SENDING);\n  }\n  if (!data && !fin) {\n    LOG(ERROR) << \"Empty write with no FIN\";\n    return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n  }\n\n  impl_.sp_.refreshTimeout();\n\n  if (bufferedWrites_.empty()) {\n    VLOG(4) << __func__ << \" Emplacing new buffered write (empty queue)\";\n    bufferedWrites_.emplace_back(std::move(data), deliveryCallback, fin);\n  } else {\n    auto& last = bufferedWrites_.back();\n    if (last.fin) {\n      LOG(ERROR) << __func__\n                 << \" Last buffered write has FIN, ret=GENERIC_ERROR\";\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    if (last.deliveryCallback == nullptr) {\n      VLOG(4) << __func__ << \" Updating last buffered write\";\n      if (data) {\n        last.buf.append(std::move(data));\n      }\n      last.deliveryCallback = deliveryCallback;\n      last.fin = fin;\n    } else {\n      VLOG(4) << __func__ << \" Emplacing new buffered write\";\n      bufferedWrites_.emplace_back(std::move(data), deliveryCallback, fin);\n    }\n  }\n\n  VLOG(4) << __func__\n          << \" Flushing buffered writes, size=\" << bufferedWrites_.size();\n  return flushBufferedWrites();\n}\n\nfolly::Expected<folly::SemiFuture<uint64_t>, WebTransport::ErrorCode>\nWebTransportImpl::StreamWriteHandle::awaitWritable() {\n  CHECK(!writePromise_.valid()) << \"awaitWritable already called\";\n  auto contract = folly::makePromiseContract<uint64_t>();\n  writePromise_ = std::move(contract.promise);\n  writePromise_.setInterruptHandler([this](const folly::exception_wrapper& ex) {\n    VLOG(4) << \"Exception from interrupt handler ex=\" << ex.what();\n    // if awaitWritable is cancelled, just reset it\n    CHECK(ex.with_exception([this](const folly::FutureCancellation& ex) {\n      VLOG(5) << \"Setting exception ex=\" << ex.what();\n      writePromise_.setException(ex);\n      writePromise_ = emptyWritePromise();\n    })) << \"Unexpected exception type\";\n  });\n  impl_.tp_.notifyPendingWriteOnStream(id_, this);\n  return std::move(contract.future);\n}\n\nvoid WebTransportImpl::onWebTransportStopSending(HTTPCodec::StreamID id,\n                                                 uint32_t errorCode) {\n  // The caller already decodes errorCode, if necessary\n  auto it = wtEgressStreams_.find(id);\n  if (it != wtEgressStreams_.end()) {\n    it->second.onStopSending(errorCode);\n  }\n}\n\nvoid WebTransportImpl::StreamWriteHandle::onStopSending(uint32_t errorCode) {\n  // The caller already decodes errorCode, if necessary\n  if (writePromise_.valid()) {\n    writePromise_.setException(WebTransport::Exception(errorCode));\n    writePromise_ = emptyWritePromise();\n  } else if (!ex_) {\n    ex_ = folly::make_exception_wrapper<WebTransport::Exception>(errorCode);\n  }\n\n  cs_.requestCancellation();\n}\n\nvoid WebTransportImpl::StreamWriteHandle::onStreamWriteReady(\n    quic::StreamId, uint64_t maxToSend) noexcept {\n  streamWriteReady_ = true;\n  flushBufferedWrites();\n  maybeResolveWritePromise(maxToSend);\n}\n\nfolly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\nWebTransportImpl::StreamWriteHandle::flushBufferedWrites() {\n  auto availableSpace = impl_.sendFlowController_.getAvailable();\n  VLOG(4) << __func__ << \" availableSpace=\" << availableSpace\n          << \" bufferedWrites_.size()=\" << bufferedWrites_.size();\n\n  while (availableSpace > 0 && !bufferedWrites_.empty()) {\n    auto& frontEntry = bufferedWrites_.front();\n    VLOG(4) << __func__ << \" Processing frontEntry, buf.len=\"\n            << frontEntry.buf.chainLength() << \" fin=\" << frontEntry.fin\n            << \" deliveryCallback=\" << frontEntry.deliveryCallback;\n    // Prefer move when we can send the entire buffer; avoid computing chain\n    // length\n    std::unique_ptr<folly::IOBuf> bufToSend;\n    size_t bufToSendLen = frontEntry.buf.chainLength();\n    if (availableSpace >= frontEntry.buf.chainLength()) {\n      bufToSend = frontEntry.buf.move(); // move whole buffer\n    } else {\n      bufToSendLen = availableSpace;\n      bufToSend = frontEntry.buf.splitAtMost(availableSpace); // partial send\n    }\n\n    VLOG(4) << __func__ << \" bufToSendLen=\" << bufToSendLen;\n    availableSpace -= bufToSendLen;\n    ByteEventCallback* sendDeliveryCallback = nullptr;\n    bool sendFin = false;\n\n    if (frontEntry.buf.empty()) {\n      sendDeliveryCallback = frontEntry.deliveryCallback;\n      sendFin = frontEntry.fin;\n      VLOG(4) << __func__ << \" frontEntry.buf empty, sendFin=\" << sendFin;\n      bufferedWrites_.pop_front();\n    }\n\n    VLOG(4) << __func__ << \" Sending data on stream id=\" << id_\n            << \" sendFin=\" << sendFin\n            << \" sendDeliveryCallback=\" << sendDeliveryCallback;\n    auto res = impl_.sendWebTransportStreamData(\n        id_, std::move(bufToSend), sendFin, sendDeliveryCallback);\n\n    if (res.hasError()) {\n      VLOG(4) << __func__\n              << \" sendWebTransportStreamData error=\" << uint32_t(res.error());\n      return folly::makeUnexpected(res.error());\n    }\n\n    if (sendFin || *res == WebTransport::FCState::BLOCKED) {\n      VLOG(4) << __func__ << \" sendFin=\" << sendFin\n              << \" or FCState::BLOCKED, ret=\" << uint32_t(*res);\n      return *res;\n    }\n  }\n\n  VLOG(4) << __func__\n          << \" Done, bufferedWrites_.empty()=\" << bufferedWrites_.empty();\n\n  if (!bufferedWrites_.empty()) {\n    impl_.tp_.sendWTDataBlocked(impl_.sendFlowController_.getMaxOffset());\n    return WebTransport::FCState::BLOCKED;\n  }\n\n  return WebTransport::FCState::UNBLOCKED;\n}\n\n/**\n * A write can be blocked for two separate reasons:\n * 1. The QUIC transport has no room\n * 2. There's no room as per the WT_MAX_DATA sent to us by the peer\n *\n * This sets writePromise_ only if there's room in the QUIC transport\n * and there's room as per the WT_MAX_DATA sent to us by the peer, as well as no\n * pending writes.\n */\nvoid WebTransportImpl::StreamWriteHandle::maybeResolveWritePromise(\n    uint64_t maxToSend) {\n  if (!writePromise_.valid()) {\n    return;\n  }\n\n  if (impl_.sendFlowController_.getAvailable() > 0 && streamWriteReady_ &&\n      bufferedWrites_.empty()) {\n    writePromise_.setValue(maxToSend);\n    writePromise_ = emptyWritePromise();\n    streamWriteReady_ = false;\n  }\n}\n\nWebTransportImpl::StreamReadHandle::StreamReadHandle(WebTransportImpl& impl,\n                                                     HTTPCodec::StreamID id)\n    : WebTransport::StreamReadHandle(id),\n      impl_(impl),\n      readPromise_(emptyReadPromise()) {\n}\n\nfolly::SemiFuture<StreamData>\nWebTransportImpl::StreamReadHandle::readStreamData() {\n  VLOG(4) << __func__;\n  CHECK(!readPromise_.valid()) << \"One read at a time\";\n  if (ex_) {\n    auto ex = ex_;\n    impl_.closeIngressStream(getID());\n    return folly::makeSemiFuture<StreamData>(std::move(ex));\n  } else if (buf_.empty() && !eof_) {\n    VLOG(4) << __func__ << \" waiting for data\";\n    auto contract = folly::makePromiseContract<StreamData>();\n    readPromise_ = std::move(contract.promise);\n    readPromise_.setInterruptHandler(\n        [this](const folly::exception_wrapper& ex) {\n          VLOG(4) << \"Exception from interrupt handler ex=\" << ex.what();\n          CHECK(ex.with_exception([this](const folly::FutureCancellation& ex) {\n            // TODO: allow app to configure the reset code on cancellation?\n            impl_.tp_.stopReadingWebTransportIngress(\n                id_, WebTransport::kInternalError);\n            deliverReadError(ex);\n          })) << \"Unexpected exception type\";\n        });\n    return std::move(contract.future);\n  } else {\n    VLOG(4) << __func__ << \" returning data len=\" << buf_.chainLength();\n    auto bufLen = buf_.chainLength();\n    StreamData streamData({.data = buf_.move(), .fin = eof_});\n    impl_.maybeGrantFlowControl(bufLen);\n\n    if (eof_) {\n      // unregister the read callback, but don't send STOP_SENDING\n      impl_.stopReadingWebTransportIngress(id_, folly::none);\n    } else if (bufLen >= kMaxWTIngressBuf) {\n      VLOG(4) << __func__ << \" resuming reads\";\n      impl_.tp_.resumeWebTransportIngress(getID());\n    }\n    return folly::makeFuture(std::move(streamData));\n  }\n}\n\nvoid WebTransportImpl::StreamReadHandle::readAvailable(\n    quic::StreamId id) noexcept {\n  impl_.sp_.refreshTimeout();\n  auto readRes = impl_.tp_.readWebTransportData(id, 65535);\n  if (readRes.hasError()) {\n    LOG(ERROR) << \"Got synchronous read error=\" << uint32_t(readRes.error());\n    readError(id,\n              quic::QuicError(quic::LocalErrorCode::INTERNAL_ERROR,\n                              \"sync read error\"));\n    impl_.closeIngressStream(id);\n    return;\n  }\n  quic::BufPtr data = std::move(readRes.value().first);\n  bool eof = readRes.value().second;\n  // deliver data, eof\n  auto state = dataAvailable(std::move(data), eof);\n  if (state == WebTransport::FCState::SESSION_CLOSED) {\n    impl_.terminateSession(WebTransport::kInternalError);\n    return;\n  }\n  if (state == WebTransport::FCState::BLOCKED && !eof) {\n    VLOG(4) << __func__ << \" pausing reads\";\n    impl_.tp_.pauseWebTransportIngress(id);\n  }\n}\n\nWebTransport::FCState WebTransportImpl::StreamReadHandle::dataAvailable(\n    std::unique_ptr<folly::IOBuf> data, bool eof) {\n  VLOG(4)\n      << \"dataAvailable buflen=\" << (data ? data->computeChainDataLength() : 0)\n      << \" eof=\" << uint64_t(eof);\n\n  auto len = data ? data->computeChainDataLength() : 0;\n  if (!impl_.recvFlowController_.reserve(len)) {\n    return WebTransport::FCState::SESSION_CLOSED;\n  }\n\n  eof_ = eof;\n  if (readPromise_.valid()) {\n    impl_.maybeGrantFlowControl(len);\n    readPromise_.setValue(StreamData({.data = std::move(data), .fin = eof}));\n    readPromise_ = emptyReadPromise();\n    if (eof) {\n      // unregister the read callback, but don't send STOP_SENDING\n      impl_.stopReadingWebTransportIngress(getID(), folly::none);\n      return WebTransport::FCState::UNBLOCKED;\n    }\n  } else {\n    buf_.append(std::move(data)); // ok if nullptr\n  }\n  VLOG(4) << \"dataAvailable buflen=\" << buf_.chainLength();\n  return (eof || buf_.chainLength() < kMaxWTIngressBuf)\n             ? WebTransport::FCState::UNBLOCKED\n             : WebTransport::FCState::BLOCKED;\n}\n\nvoid WebTransportImpl::StreamReadHandle::readError(\n    quic::StreamId id, quic::QuicError error) noexcept {\n  // Do I need to setReadCallback(id, nullptr);\n  impl_.sp_.refreshTimeout();\n  auto quicAppErrorCode = error.code.asApplicationErrorCode();\n  if (quicAppErrorCode) {\n    folly::Expected<uint32_t, WebTransport::ErrorCode> appErrorCode{\n        *quicAppErrorCode};\n    if (impl_.tp_.usesEncodedApplicationErrorCodes()) {\n      appErrorCode =\n          proxygen::WebTransport::toApplicationErrorCode(*quicAppErrorCode);\n      if (!appErrorCode) {\n        deliverReadError(WebTransport::Exception(\n            *quicAppErrorCode, \"received invalid reset_stream\"));\n        return;\n      }\n    }\n    deliverReadError(\n        WebTransport::Exception(*appErrorCode, \"received reset_stream\"));\n    return;\n  } else {\n    VLOG(4) << error;\n  }\n  // any other error\n  deliverReadError(WebTransport::Exception(\n      proxygen::WebTransport::kInternalError, \"quic error\"));\n}\n\nvoid WebTransportImpl::StreamReadHandle::deliverReadError(\n    const folly::exception_wrapper& ex) {\n  ex_ = ex;\n  if (readPromise_.valid()) {\n    readPromise_.setException(ex);\n    readPromise_ = emptyReadPromise();\n    impl_.closeIngressStream(getID());\n  }\n}\n\nvoid WebTransportImpl::maybeGrantFlowControl(uint64_t bytesRead) {\n  bytesRead_ += bytesRead;\n  if (bytesRead && shouldGrantFlowControl()) {\n    auto newMaxData = bytesRead_ + kDefaultWTReceiveWindow;\n    recvFlowController_.grant(newMaxData);\n    tp_.sendWTMaxData(newMaxData);\n  }\n}\n\nvoid WebTransportImpl::maybeGrantStreamCredit(HTTPCodec::StreamID id,\n                                              bool closingReadHandle,\n                                              bool closingWriteHandle) {\n  if (isSessionTerminated()) {\n    return;\n  }\n  CHECK(closingReadHandle || closingWriteHandle);\n  bool isBidi = isBidirectional(id);\n  bool canGrantCredit = false;\n\n  if (isBidi) {\n    bool hasOppositeHandle = closingReadHandle ? wtEgressStreams_.contains(id)\n                                               : wtIngressStreams_.contains(id);\n    canGrantCredit = !hasOppositeHandle;\n  } else {\n    canGrantCredit = closingReadHandle && wtIngressStreams_.contains(id);\n  }\n\n  if (canGrantCredit && tp_.isPeerInitiatedStream(id)) {\n    auto& streamLimits =\n        isBidi ? peerBidiStreamFlowControl_ : peerUniStreamFlowControl_;\n    streamLimits.numClosedStreams++;\n    if (shouldGrantStreamCredit(isBidi)) {\n      streamLimits.maxStreamID += streamLimits.targetConcurrentStreams / 2;\n      tp_.sendWTMaxStreams(streamLimits.maxStreamID, isBidi);\n    }\n  }\n}\n\nbool WebTransportImpl::shouldGrantFlowControl() const {\n  auto bufferedBytes = recvFlowController_.getCurrentOffset() - bytesRead_;\n  return bufferedBytes < kDefaultWTReceiveWindow / 2;\n}\n\nbool WebTransportImpl::shouldGrantStreamCredit(bool isBidi) const {\n  auto& streamLimits =\n      isBidi ? peerBidiStreamFlowControl_ : peerUniStreamFlowControl_;\n  CHECK(streamLimits.numClosedStreams <= streamLimits.maxStreamID);\n  return streamLimits.maxStreamID - streamLimits.numClosedStreams <\n         (streamLimits.targetConcurrentStreams / 2);\n}\n\nvoid WebTransportImpl::closeEgressStream(HTTPCodec::StreamID id) {\n  maybeGrantStreamCredit(id, false, true);\n  wtEgressStreams_.erase(id);\n}\n\nvoid WebTransportImpl::closeIngressStream(HTTPCodec::StreamID id) {\n  maybeGrantStreamCredit(id, true, false);\n  wtIngressStreams_.erase(id);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WebTransportImpl.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/HTTPCodec.h>\n#include <proxygen/lib/http/webtransport/FlowController.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <quic/QuicConstants.h>\n\nnamespace {\nconstexpr uint64_t kDefaultWTReceiveWindow = 1'048'576;\nconstexpr uint64_t kDefaultTargetConcurrentStreams = 50;\nconstexpr double kFlowControlThreshold = 0.5;\n} // namespace\n\nnamespace proxygen {\n\nclass WebTransportImpl : public WebTransport {\n public:\n  class TransportProvider {\n   public:\n    virtual ~TransportProvider() = default;\n\n    virtual folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportBidiStream() = 0;\n\n    virtual folly::Expected<HTTPCodec::StreamID, WebTransport::ErrorCode>\n    newWebTransportUniStream() = 0;\n\n    virtual folly::SemiFuture<folly::Unit> awaitUniStreamCredit() = 0;\n\n    virtual folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() = 0;\n\n    virtual bool canCreateUniStream() = 0;\n\n    virtual bool canCreateBidiStream() = 0;\n\n    virtual folly::Expected<FCState, WebTransport::ErrorCode>\n    sendWebTransportStreamData(HTTPCodec::StreamID /*id*/,\n                               std::unique_ptr<folly::IOBuf> /*data*/,\n                               bool /*eof*/,\n                               ByteEventCallback* /* byteEventCallback */) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode> sendWTMaxData(\n        uint64_t maxData) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    sendWTMaxStreams(uint64_t maxStreams, bool isBidi) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    sendWTStreamsBlocked(uint64_t maxStreams, bool isBidi) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    sendWTDataBlocked(uint64_t maxData) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    notifyPendingWriteOnStream(HTTPCodec::StreamID,\n                               quic::StreamWriteCallback* wcb) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    resetWebTransportEgress(HTTPCodec::StreamID /*id*/,\n                            uint32_t /*errorCode*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        setWebTransportStreamPriority(\n            HTTPCodec::StreamID /*id*/,\n            quic::PriorityQueue::Priority /*pri*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        setWebTransportPriorityQueue(\n            std::unique_ptr<quic::PriorityQueue> /*queue*/) noexcept = 0;\n\n    virtual folly::Expected<std::pair<std::unique_ptr<folly::IOBuf>, bool>,\n                            WebTransport::ErrorCode>\n    readWebTransportData(HTTPCodec::StreamID /*id*/, size_t /*max*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n    initiateReadOnBidiStream(HTTPCodec::StreamID /*id*/,\n                             quic::StreamReadCallback* /*readCallback*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        pauseWebTransportIngress(HTTPCodec::StreamID /*id*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        resumeWebTransportIngress(HTTPCodec::StreamID /*id*/) = 0;\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode>\n        stopReadingWebTransportIngress(\n            HTTPCodec::StreamID /*id*/,\n            folly::Optional<uint32_t> /*errorCode*/) = 0;\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n        std::unique_ptr<folly::IOBuf> /*datagram*/) = 0;\n\n    [[nodiscard]] virtual const folly::SocketAddress& getLocalAddress()\n        const = 0;\n    [[nodiscard]] virtual const folly::SocketAddress& getPeerAddress()\n        const = 0;\n\n    [[nodiscard]] virtual quic::TransportInfo getTransportInfo() const {\n      return {};\n    }\n\n    virtual bool usesEncodedApplicationErrorCodes() = 0;\n\n    [[nodiscard]] virtual bool isPeerInitiatedStream(\n        HTTPCodec::StreamID id) = 0;\n\n    [[nodiscard]] virtual uint64_t getWTInitialSendWindow() const {\n      return quic::kMaxVarInt;\n    }\n  };\n\n  class SessionProvider {\n   public:\n    virtual ~SessionProvider() = default;\n\n    virtual void refreshTimeout() {\n    }\n\n    virtual folly::Expected<folly::Unit, WebTransport::ErrorCode> closeSession(\n        folly::Optional<uint32_t> /*error*/) = 0;\n  };\n\n  WebTransportImpl(TransportProvider& tp, SessionProvider& sp)\n      : tp_(tp), sp_(sp), sendFlowController_(tp_.getWTInitialSendWindow()) {\n  }\n\n  ~WebTransportImpl() override = default;\n\n  void destroy();\n\n  // WT API\n  folly::Expected<WebTransport::StreamWriteHandle*, WebTransport::ErrorCode>\n  createUniStream() override {\n    return newWebTransportUniStream();\n  }\n\n  folly::Expected<WebTransport::BidiStreamHandle, WebTransport::ErrorCode>\n  createBidiStream() override {\n    return newWebTransportBidiStream();\n  }\n  folly::SemiFuture<folly::Unit> awaitUniStreamCredit() override {\n    return tp_.awaitUniStreamCredit();\n  }\n  folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() override {\n    return tp_.awaitBidiStreamCredit();\n  }\n  folly::Expected<folly::SemiFuture<StreamData>, WebTransport::ErrorCode>\n  readStreamData(uint64_t id) override {\n    auto it = wtIngressStreams_.find(id);\n    if (it == wtIngressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.readStreamData();\n  }\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> stopSending(\n      uint64_t id, uint32_t error) override {\n    auto it = wtIngressStreams_.find(id);\n    if (it == wtIngressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.stopSending(error);\n  }\n  folly::Expected<FCState, ErrorCode> writeStreamData(\n      uint64_t id,\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      ByteEventCallback* deliveryCallback) override {\n    auto it = wtEgressStreams_.find(id);\n    if (it == wtEgressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.writeStreamData(std::move(data), fin, deliveryCallback);\n  }\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> resetStream(\n      uint64_t id, uint32_t error) override {\n    auto it = wtEgressStreams_.find(id);\n    if (it == wtEgressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.resetStream(error);\n  }\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> setPriority(\n      uint64_t id, quic::PriorityQueue::Priority priority) override {\n    auto it = wtEgressStreams_.find(id);\n    if (it == wtEgressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.setPriority(priority);\n  }\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> setPriorityQueue(\n      std::unique_ptr<quic::PriorityQueue> queue) noexcept override {\n    return tp_.setWebTransportPriorityQueue(std::move(queue));\n  }\n  folly::Expected<folly::SemiFuture<uint64_t>, ErrorCode> awaitWritable(\n      uint64_t streamId) override {\n    auto it = wtEgressStreams_.find(streamId);\n    if (it == wtEgressStreams_.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::INVALID_STREAM_ID);\n    }\n    return it->second.awaitWritable();\n  }\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) override {\n    if (sessionCloseError_.has_value()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SESSION_TERMINATED);\n    }\n    // This can bypass the size and state machine checks in\n    // HTTPTransaction::sendDatagram\n    if (!tp_.sendDatagram(std::move(datagram))) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::SEND_ERROR);\n    }\n    return folly::unit;\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getLocalAddress() const override {\n    return tp_.getLocalAddress();\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress() const override {\n    return tp_.getPeerAddress();\n  }\n\n  [[nodiscard]] quic::TransportInfo getTransportInfo() const override {\n    return tp_.getTransportInfo();\n  }\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> closeSession(\n      folly::Optional<uint32_t> error) override {\n    return sp_.closeSession(error);\n  }\n\n  class StreamWriteHandle\n      : public WebTransport::StreamWriteHandle\n      , public quic::StreamWriteCallback {\n   public:\n    StreamWriteHandle(WebTransportImpl& tp, HTTPCodec::StreamID id);\n\n    ~StreamWriteHandle() override {\n      cs_.requestCancellation();\n    }\n\n    folly::Expected<FCState, WebTransport::ErrorCode> writeStreamData(\n        std::unique_ptr<folly::IOBuf> data,\n        bool fin,\n        ByteEventCallback* deliveryCallback) override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> resetStream(\n        uint32_t errorCode) override {\n      return impl_.resetWebTransportEgress(id_, errorCode);\n    }\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> setPriority(\n        quic::PriorityQueue::Priority priority) override {\n      WebTransport::StreamWriteHandle::setPriority(priority);\n      return impl_.tp_.setWebTransportStreamPriority(getID(), priority);\n    }\n    folly::Expected<folly::SemiFuture<uint64_t>, ErrorCode> awaitWritable()\n        override;\n\n    void onStopSending(uint32_t errorCode);\n    folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n    flushBufferedWrites();\n    void maybeResolveWritePromise(uint64_t maxToSend);\n\n    // TODO: what happens to promise_ if this stream is reset or the\n    // conn closes\n\n   private:\n    /*\n     * Used to store buffered writes when we have insufficient flow control.\n     */\n    struct BufferedWrite {\n      folly::IOBufQueue buf;\n      ByteEventCallback* deliveryCallback;\n      bool fin;\n\n      BufferedWrite(std::unique_ptr<folly::IOBuf> data,\n                    ByteEventCallback* callback,\n                    bool fin)\n          : buf(folly::IOBufQueue::cacheChainLength()),\n            deliveryCallback(callback),\n            fin(fin) {\n        if (data) {\n          buf.append(std::move(data));\n        }\n      }\n    };\n\n    void onStreamWriteReady(quic::StreamId id, uint64_t) noexcept override;\n\n    WebTransportImpl& impl_;\n    folly::Promise<uint64_t> writePromise_;\n    std::list<BufferedWrite> bufferedWrites_;\n    bool streamWriteReady_{false};\n  };\n\n  class StreamReadHandle\n      : public WebTransport::StreamReadHandle\n      , public quic::StreamReadCallback {\n   public:\n    StreamReadHandle(WebTransportImpl& impl, HTTPCodec::StreamID id);\n\n    ~StreamReadHandle() override {\n      cs_.requestCancellation();\n    }\n\n    folly::SemiFuture<WebTransport::StreamData> readStreamData() override;\n\n    folly::Expected<folly::Unit, WebTransport::ErrorCode> stopSending(\n        uint32_t error) override {\n      return impl_.stopReadingWebTransportIngress(id_, error);\n    }\n\n    FCState dataAvailable(std::unique_ptr<folly::IOBuf> data, bool eof);\n    void deliverReadError(const folly::exception_wrapper& ex);\n    [[nodiscard]] bool open() const {\n      return !eof_ && !ex_;\n    }\n\n    // quic::StreamReadCallback overrides\n    void readAvailable(quic::StreamId id) noexcept override;\n    void readError(quic::StreamId id, quic::QuicError error) noexcept override;\n\n   private:\n    WebTransportImpl& impl_;\n    folly::Promise<WebTransport::StreamData> readPromise_;\n    folly::IOBufQueue buf_{folly::IOBufQueue::cacheChainLength()};\n    bool eof_{false};\n  };\n\n private:\n  folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>\n  sendWebTransportStreamData(HTTPCodec::StreamID id,\n                             std::unique_ptr<folly::IOBuf> data,\n                             bool eof,\n                             ByteEventCallback* deliveryCallback);\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode> resetWebTransportEgress(\n      HTTPCodec::StreamID id, uint32_t errorCode);\n\n  folly::Expected<folly::Unit, WebTransport::ErrorCode>\n  stopReadingWebTransportIngress(HTTPCodec::StreamID id,\n                                 folly::Optional<uint32_t> errorCode);\n\n  folly::Expected<WebTransport::BidiStreamHandle, WebTransport::ErrorCode>\n  newWebTransportBidiStream();\n\n  folly::Expected<WebTransport::StreamWriteHandle*, WebTransport::ErrorCode>\n  newWebTransportUniStream();\n\n  TransportProvider& tp_;\n  SessionProvider& sp_;\n  using WTEgressStreamMap = std::map<HTTPCodec::StreamID, StreamWriteHandle>;\n  using WTIngressStreamMap = std::map<HTTPCodec::StreamID, StreamReadHandle>;\n  WTEgressStreamMap wtEgressStreams_;\n  WTIngressStreamMap wtIngressStreams_;\n  // Initialize flow controllers with max value for backward compatibility and\n  // to ensure no functional change yet.\n  FlowController sendFlowController_{quic::kMaxVarInt};\n  FlowController recvFlowController_{quic::kMaxVarInt};\n  folly::Optional<uint32_t> sessionCloseError_;\n  uint64_t bytesRead_{};\n\n  struct StreamFlowControl {\n    uint64_t maxStreamID{quic::kMaxVarInt};\n    uint64_t numClosedStreams{};\n    uint64_t targetConcurrentStreams{kDefaultTargetConcurrentStreams};\n  };\n\n  // Peer flow control: limits for streams peer can create (set by us)\n  StreamFlowControl peerUniStreamFlowControl_;\n  StreamFlowControl peerBidiStreamFlowControl_;\n\n  // Self flow control: limits for streams we can create (set by peer)\n  // Initialize to kMaxVarInt for backward compatibility when flow control is\n  // not used\n  StreamFlowControl selfUniStreamFlowControl_;\n  StreamFlowControl selfBidiStreamFlowControl_;\n\n public:\n  [[nodiscard]] bool isSessionTerminated() const {\n    return sessionCloseError_.has_value();\n  }\n\n  [[nodiscard]] folly::Optional<uint32_t> getSessionCloseError() const {\n    return sessionCloseError_;\n  }\n\n  void terminateSession(const folly::Optional<uint32_t>& error = folly::none) {\n    sessionCloseError_ =\n        error.has_value() ? error : folly::Optional<uint32_t>(0);\n    terminateSessionStreams(WebTransport::kSessionGone, \"session terminated\");\n  }\n\n protected:\n  void terminateSessionStreams(uint32_t errorCode, const std::string& reason);\n\n public:\n  // Calls from Transport\n  struct BidiStreamHandle {\n    StreamReadHandle* readHandle;\n    StreamWriteHandle* writeHandle;\n  };\n  BidiStreamHandle onWebTransportBidiStream(HTTPCodec::StreamID id);\n\n  WebTransportImpl::StreamReadHandle* onWebTransportUniStream(\n      HTTPCodec::StreamID id);\n\n  void onWebTransportStopSending(HTTPCodec::StreamID id, uint32_t errorCode);\n\n  void onMaxData(uint64_t maxData) noexcept;\n  void onMaxStreams(uint64_t maxStreams, bool isBidi) noexcept;\n  void onStreamsBlocked(uint64_t maxStreams, bool isBidi) noexcept;\n  void onDataBlocked(uint64_t maxData) noexcept;\n\n  void maybeGrantFlowControl(uint64_t bytesRead);\n  void maybeGrantStreamCredit(HTTPCodec::StreamID id,\n                              bool closingReadHandle,\n                              bool closingWriteHandle);\n  [[nodiscard]] bool shouldGrantFlowControl() const;\n  [[nodiscard]] bool shouldGrantStreamCredit(bool isBidi) const;\n\n  void closeEgressStream(HTTPCodec::StreamID id);\n  void closeIngressStream(HTTPCodec::StreamID id);\n\n  [[nodiscard]] bool isBidirectional(HTTPCodec::StreamID id) const {\n    return (id & 0b10) == 0;\n  }\n\n  void setFlowControlLimits(uint64_t sendWindow = quic::kMaxVarInt,\n                            uint64_t recvWindow = quic::kMaxVarInt) {\n    sendFlowController_ = FlowController(sendWindow);\n    recvFlowController_ = FlowController(recvWindow);\n  }\n\n  void setUniStreamFlowControl(uint64_t maxStreamId,\n                               uint64_t targetConcurrentStreams) {\n    peerUniStreamFlowControl_.maxStreamID = maxStreamId;\n    peerUniStreamFlowControl_.targetConcurrentStreams = targetConcurrentStreams;\n  }\n\n  void setBidiStreamFlowControl(uint64_t maxStreamId,\n                                uint64_t targetConcurrentStreams) {\n    peerBidiStreamFlowControl_.maxStreamID = maxStreamId;\n    peerBidiStreamFlowControl_.targetConcurrentStreams =\n        targetConcurrentStreams;\n  }\n\n  // Test-only methods to set self stream flow control limits\n  void setSelfUniStreamFlowControl(uint64_t maxStreamId) {\n    selfUniStreamFlowControl_.maxStreamID = maxStreamId;\n  }\n\n  void setSelfBidiStreamFlowControl(uint64_t maxStreamId) {\n    selfBidiStreamFlowControl_.maxStreamID = maxStreamId;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtEgressContainer.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/webtransport/WtEgressContainer.h>\n\nnamespace proxygen::detail {\n\nWtBufferedStreamData::PendingWrite::PendingWrite(\n    std::unique_ptr<folly::IOBuf> data,\n    proxygen::WebTransport::ByteEventCallback* callback,\n    uint64_t offset,\n    bool finFlag) noexcept\n    : buf(folly::IOBufQueue::cacheChainLength()),\n      deliveryCallback(callback),\n      offset(offset),\n      fin(finFlag) {\n  buf.append(std::move(data)); // ok if nullptr\n}\n\nWtBufferedStreamData::FcRes WtBufferedStreamData::enqueue(\n    std::unique_ptr<folly::IOBuf> data,\n    bool fin,\n    proxygen::WebTransport::ByteEventCallback* callback) noexcept {\n  XCHECK(pendingWrites_.empty() || !pendingWrites_.back().fin)\n      << \"enqueue after fin\";\n\n  auto len = data ? data->computeChainDataLength() : 0;\n  uint64_t offset = window_.getBufferedOffset() + (len ? len - 1 : 0);\n  auto* lastWrite = pendingWrites_.empty() ? nullptr : &pendingWrites_.back();\n\n  // If last write had no callback and no fin, coalesce\n  if (lastWrite && !lastWrite->deliveryCallback) {\n    lastWrite->buf.append(std::move(data));\n    lastWrite->deliveryCallback = callback;\n    lastWrite->offset = offset;\n    lastWrite->fin = fin;\n  } else {\n    pendingWrites_.emplace_back(std::move(data), callback, offset, fin);\n  }\n\n  return window_.buffer(len);\n}\n\nWtBufferedStreamData::DequeueResult WtBufferedStreamData::dequeue(\n    uint64_t atMost) noexcept {\n  // min of maxBytes and how many bytes remaining in egress window\n  atMost = std::min({atMost, window_.getAvailable()});\n  DequeueResult res;\n  if (atMost == 0 && !onlyFinPending()) {\n    return res;\n  }\n\n  folly::IOBufQueue resQueue(folly::IOBufQueue::cacheChainLength());\n  // Dequeue data from pending writes until we've dequeued atMost bytes\n  // or completed a write that has a callback\n  while (!pendingWrites_.empty() && !res.deliveryCallback) {\n    auto& frontWrite = pendingWrites_.front();\n\n    if (auto rem = atMost - resQueue.chainLength()) {\n      resQueue.append(frontWrite.buf.splitAtMost(rem));\n    }\n\n    if (!frontWrite.buf.empty()) {\n      break;\n    }\n\n    res.deliveryCallback = frontWrite.deliveryCallback;\n    res.fin = frontWrite.fin;\n    pendingWrites_.pop_front();\n  }\n\n  window_.commit(resQueue.chainLength());\n  res.lastByteStreamOffset =\n      std::max<uint64_t>(window_.getCurrentOffset(), 1) - 1;\n  res.data = resQueue.move();\n  return res;\n}\n\nbool WtBufferedStreamData::onlyFinPending() const {\n  const auto* front =\n      pendingWrites_.empty() ? nullptr : &pendingWrites_.front();\n  return front && front->buf.empty() && front->fin;\n}\n\nvoid WtBufferedStreamData::clear(uint64_t id) noexcept {\n  auto pendingWrites = std::move(pendingWrites_);\n  for (auto& write : pendingWrites) {\n    if (write.deliveryCallback) {\n      write.deliveryCallback->onByteEventCanceled(id, write.offset);\n    }\n  }\n}\n\n}; // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtEgressContainer.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/webtransport/FlowController.h>\n// TODO(@joannajo): Remove WebTransport.h dependency. Consider extracting\n// ByteEventCallback to a separate header.\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n\n#include <folly/io/IOBufQueue.h>\n#include <list>\n\nnamespace proxygen::detail {\n\n/**\n * This is needed due to the asynchronous nature of CoroWtSession in\n * proxygen::coro – when writing to an egress handle, data is flushed to the\n * transport at a later time (e.g. next evb loop). This is an extremely thin\n * wrapper around proxygen/lib/http/webtransport/FlowController.h; simple keeps\n * track of an additional bufferedOffset_ (invariant bufferedOffset_ >=\n * currentOffset_). We apply egress backpressure (e.g. return Blocked) if the\n * the application has buffered more than 64KiB bytes.\n */\nstruct BufferedFlowController {\n  explicit BufferedFlowController(uint64_t initMax = 0) : window_(initMax) {\n  }\n\n  // advances the bufferedOffset_ by len bytes – returns true if buffering len\n  // bytes has exceeded send window\n  enum FcRes : uint8_t { Unblocked = 0, Blocked = 1 };\n  [[nodiscard]] FcRes buffer(uint64_t len) {\n    bufferedOffset_ += len; // TODO(@damlaj) overflow\n    return isBlocked() ? FcRes::Blocked : FcRes::Unblocked;\n  }\n\n  // adv currentOffset_ by len bytes (must be <= bufferedOffset_ after\n  // increment)\n  void commit(uint64_t len) {\n    window_.reserve(len);\n    CHECK_LE(getCurrentOffset(), getBufferedOffset());\n  }\n\n  [[nodiscard]] bool grant(uint64_t offset) {\n    return window_.grant(offset);\n  }\n\n  [[nodiscard]] bool isBlocked() const {\n    return getBufferAvailable() == 0;\n  }\n\n  [[nodiscard]] uint64_t getBufferedOffset() const {\n    return bufferedOffset_;\n  }\n\n  [[nodiscard]] uint64_t getCurrentOffset() const {\n    return window_.getCurrentOffset();\n  }\n\n  [[nodiscard]] uint64_t getMaxOffset() const {\n    return window_.getMaxOffset();\n  }\n\n  [[nodiscard]] uint64_t getBufferAvailable() const {\n    const auto bufferedBytes = bufferedOffset_ - getCurrentOffset();\n    return bufferedBytes >= kMaxEgressBuf ? 0 : (kMaxEgressBuf - bufferedBytes);\n  }\n\n  [[nodiscard]] uint64_t getAvailable() const {\n    return window_.getAvailable();\n  }\n\n private:\n  static constexpr auto kMaxEgressBuf = std::numeric_limits<uint16_t>::max();\n  FlowController window_;\n  uint64_t bufferedOffset_{0};\n};\n\nclass WtBufferedStreamData {\n public:\n  explicit WtBufferedStreamData(uint64_t initMax = 0) : window_(initMax) {\n  }\n\n  const BufferedFlowController& window() {\n    return window_;\n  }\n\n  /**\n   * enqueues data into the container's buffer – returns FcRes::Blocked if the\n   * egress is now flow control blocked, FcRes::Unblocked otherwise\n   *\n   * Each write is tracked separately with its callback. Consecutive writes\n   * without callbacks are merged.\n   */\n  using FcRes = BufferedFlowController::FcRes;\n  FcRes enqueue(std::unique_ptr<folly::IOBuf> data,\n                bool fin,\n                WebTransport::ByteEventCallback* callback = nullptr) noexcept;\n\n  struct DequeueResult {\n    std::unique_ptr<folly::IOBuf> data;\n    bool fin{false};\n    WebTransport::ByteEventCallback* deliveryCallback{nullptr};\n    uint64_t lastByteStreamOffset{0};\n  };\n\n  /**\n   * Dequeues data from the container's buffer, returning min(atMost,\n   * window_available, bytes_enqueued) bytes.\n   */\n  DequeueResult dequeue(uint64_t atMost) noexcept;\n\n  // returns true if there's only a pending fin\n  [[nodiscard]] bool onlyFinPending() const;\n\n  bool grant(uint64_t offset) noexcept {\n    return window_.grant(offset);\n  }\n\n  [[nodiscard]] bool hasData() const {\n    return !pendingWrites_.empty();\n  }\n\n  // we can send data if there is either data & available stream fc, or only fin\n  // pending\n  [[nodiscard]] bool canSendData() const {\n    return (hasData() && window_.getAvailable()) || onlyFinPending();\n  }\n\n  void clear(uint64_t id) noexcept;\n\n private:\n  /**\n   * Stores a single buffered write with its associated callback.\n   * Consecutive writes without callbacks can be merged into the previous\n   * PendingWrite (see enqueue implementation).\n   */\n  struct PendingWrite {\n    folly::IOBufQueue buf;\n    proxygen::WebTransport::ByteEventCallback* deliveryCallback;\n    uint64_t offset;\n    bool fin;\n\n    PendingWrite(std::unique_ptr<folly::IOBuf> data,\n                 proxygen::WebTransport::ByteEventCallback* callback,\n                 uint64_t offset,\n                 bool finFlag) noexcept;\n  };\n\n  BufferedFlowController window_;\n  std::list<PendingWrite> pendingWrites_;\n};\n\n} // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtStreamManager.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/**\n * This comment block documents the implementation details of WtStreamManager\n * (i.e. the how).\n *\n * WriteHandle & ReadHandle (concrete implementations of\n * WebTransport::WriteHandle and WebTransport::ReadHandle respsectively, defined\n * below) are anonymous and strictly scoped to this TU. This requires\n * downcasting in all apis that receive a pointer to those pure virtual classes.\n * To prevent leaking implementation details by marking methods in\n * WtStreamManager public just for internal use by WriteHandle & ReadHandle, we\n * utilize a friend struct Accessor (also anonymously defined and strictly\n * scoped to this TU).\n *\n * First thing to note is that we always allocate both a WriteHandle and\n * ReadHandle regardless of the underlying properities of the stream (e.g. if\n * it's a unidirectional stream). This makes things extremely easier to reason\n * about, as there's a single map from [id]->[bidi_handle]. The public api in\n * WtStreamManager is extremely restrictive, and we do not hand out a pointer to\n * an invalid handle. For example, if a client WtStreamManager attempts to\n * retrieve a EgressHandle for a server-initiated unidirectional stream (e.g.\n * id=0x03), the state for such a handle exists but we return nullptr – it's\n * effectively invisible to the user.\n *\n * A note about flow control:\n * – Ingress flow control is strict, as a peer exceeding the advertised max\n *   offset is a connection- or stream-level flow control.\n *\n * – Egress flow control is not strict, as an application can enqueue too much\n *   data for any number of reasons (large write, ignoring backpressure, etc.).\n *   We buffer this data in WriteHandle and is subsequently dequeued by the\n *   transport whenever the stream is writable (hence the need for\n *   WtEgressContainer to manage this small complexity). Applications should\n *   respect backpressure signalled by returning FcState::BLOCKED from\n *   WriteHandle::writeStreamData\n *\n * A note about stream states:\n * – An egress handle transitions from [HandleState::Open] ->\n *   [HandleState::Closed] in three cases: fin is dequeued from WriteHandle via\n *   ::dequeue, the application resets the stream (i.e.\n *   WebTransport::WriteHandle::resetStream), or the transport receives a\n *   stop_sending and WtStreamManager::onStopSending is invoked.\n *\n * – An ingress handle transitions from [HandleState::Open] ->\n *   [HandleState::Closed] in three cases: fin is read via ::readStreamData(),\n *   the application is no longer interested in ingress (i.e.\n *   WebTransport::ReadHandle::stopSending), or the transport receives a\n *   reset_stream and WtStreamManager::onResetStream is invoked.\n *\n * – An invalid handle (e.g. client egress handle for a\n *   server-initiated unidirectional stream) starts in the HandleState::Closed.\n *\n * – Once both the ingress & egress handles have reached the HandleState::Closed\n *   state, we deallocate the state. Since we always allocate both a ReadHandle\n *   and WriteHandle, they're both unconditionally linked together for\n *   simplicity.\n */\n\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n\n#include <folly/logging/xlog.h>\n\n// fwd decls for Accessor\nnamespace {\nstruct ReadHandle;\nstruct WriteHandle;\n\n// helpers to reduce the repetitive static_cast\n#define readhandle_ptr_cast(ptr) (static_cast<ReadHandle*>(ptr))\n#define writehandle_ptr_cast(ptr) (static_cast<WriteHandle*>(ptr))\n#define readhandle_ref_cast(ptr) (static_cast<ReadHandle&>(ptr))\n#define writehandle_ref_cast(ptr) (static_cast<WriteHandle&>(ptr))\n\n}; // namespace\n\nnamespace proxygen::detail {\n\n// Accessor needs to be a complete type before anon namespace below for\n// ReadHandle & WriteHandle. This struct is a friend of WtStreamManager and\n// consequently has access to private members/functions.\nstruct WtStreamManager::Accessor {\n  explicit Accessor(WtStreamManager& sm) : sm_(sm) {\n  }\n  void maybeGrantFc(ReadHandle* rh, uint64_t bytesRead) noexcept;\n  void stopSending(ReadHandle& rh, uint32_t err) noexcept;\n  void resetStream(WriteHandle& wh,\n                   uint32_t err,\n                   uint64_t reliableSize) noexcept;\n  void onStreamWritable(WriteHandle& wh) noexcept;\n  // invoked when write handle or read handle are done\n  void done(WriteHandle& wh) noexcept;\n  void done(ReadHandle& rh) noexcept;\n\n  bool isIngress(uint64_t id) {\n    return sm_.isIngress(id);\n  }\n  bool isEgress(uint64_t id) {\n    return sm_.isEgress(id);\n  }\n  auto& connSend() {\n    return sm_.connSendFc_;\n  }\n  auto& writableStreams() {\n    return sm_.writableStreams_;\n  }\n  auto& connFcBlockedStreams() {\n    return sm_.connFcBlockedStreams_;\n  }\n  WtStreamManager& sm_;\n};\n\n} // namespace proxygen::detail\n\nnamespace {\n\nconstexpr uint8_t kStreamIdInc = 0x04;\n\nstruct BufferedData {\n  folly::IOBuf chain; // head is always empty\n  bool fin{false};\n};\n\nsize_t computeChainLength(std::unique_ptr<folly::IOBuf>& buf) {\n  return buf ? buf->computeChainDataLength() : 0;\n}\n\nusing namespace proxygen::detail;\nusing ReadPromise = WtStreamManager::ReadPromise;\nReadPromise emptyReadPromise() {\n  return ReadPromise::makeEmpty();\n}\n\nusing WritePromise = folly::Promise<uint64_t>;\nWritePromise emptyWritePromise() {\n  return WritePromise::makeEmpty();\n}\n\nusing WtException = WtStreamManager::WtException;\nusing Result = WtStreamManager::Result;\nusing StreamData = WtStreamManager::StreamData;\nusing WebTransport = proxygen::WebTransport;\nusing Accessor = WtStreamManager::Accessor;\nenum HandleState : uint8_t { Closed = 0, Open = 1 };\n\nstruct ReadHandle : public WebTransport::StreamReadHandle {\n  // why doesn't using StreamReadHandle::StreamReadHandle work here?\n  ReadHandle(uint64_t id, uint64_t initRecvWnd, Accessor acc) noexcept;\n  using ErrCode = WebTransport::ErrorCode;\n  // StreamReadHandle overrides\n  folly::SemiFuture<StreamData> readStreamData() override;\n  folly::Expected<folly::Unit, ErrCode> stopSending(uint32_t error) override;\n  Result enqueue(StreamData&& data) noexcept;\n  void cancel(folly::exception_wrapper ex,\n              uint64_t reliableSize = kInvalidVarint) noexcept;\n  ReadPromise resetPromise() noexcept;\n  void finish(bool done) noexcept;\n\n  Accessor smAccessor_;\n  FlowController streamRecvFc_;\n  BufferedData ingress_;\n  WtStreamManager::ReadCallback* rcb_{nullptr};\n  uint64_t bytesRead_{0};\n  ReadPromise promise_{emptyReadPromise()};\n  HandleState state_;\n  WriteHandle* wh_{nullptr}; // ptr to the symmetric wh\n};\nstruct WriteHandle : public WebTransport::StreamWriteHandle {\n  WriteHandle(uint64_t id, uint64_t initSendWnd, Accessor acc) noexcept;\n  using FcState = WebTransport::FCState;\n  using ErrCode = WebTransport::ErrorCode;\n  // StreamWriteHandle overrides\n  folly::Expected<FcState, ErrCode> writeStreamData(\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      WebTransport::ByteEventCallback* byteEventCallback) override;\n  folly::Expected<folly::Unit, ErrCode> resetStream(uint32_t error) override;\n  folly::Expected<folly::Unit, ErrCode> setPriority(\n      quic::PriorityQueue::Priority priority) override;\n  folly::Expected<folly::SemiFuture<uint64_t>, ErrCode> awaitWritable()\n      override;\n\n  WtBufferedStreamData::DequeueResult dequeue(uint64_t atMost) noexcept;\n  Result onMaxData(uint64_t offset);\n  WritePromise resetPromise() noexcept;\n  void cancel(folly::exception_wrapper ex) noexcept;\n  void finish(bool done) noexcept;\n\n  Accessor smAccessor_;\n  WtBufferedStreamData bufferedSendData_;\n  uint64_t err{kInvalidVarint};\n  WritePromise promise_{emptyWritePromise()};\n  HandleState state_;\n  ReadHandle* rh_{nullptr}; // ptr to the symmetric rh\n};\n\n} // namespace\n\nnamespace proxygen::detail {\n\n/**\n * If rh == nullptr, ::maybeGrantFc only releases connection-level flow\n * control. This is set to nullptr if a fin/reset_stream have been received on\n * a stream, as releasing stream-level flow control is no longer\n * necessary/reasonable.\n *\n * Otherwise release both connection- and stream-level flow control\n */\nvoid Accessor::maybeGrantFc(ReadHandle* rh, uint64_t bytesRead) noexcept {\n  // TODO(@damlaj): user-defined flow control values and release when 50% is\n  // read\n  XLOG(DBG6) << __func__ << \"; rh=\" << rh;\n  if (rh) { // handle stream-level flow control\n    uint64_t initStreamRecvFc = sm_.initStreamRecvFc(rh->getID());\n    auto& streamRecv = rh->streamRecvFc_;\n    // if peer has less than rwnd / 2 to send, issue fc\n    bool issueFc = streamRecv.getAvailable() <= (initStreamRecvFc / 2);\n    XLOG(DBG6) << __func__ << \"avail=\" << streamRecv.getAvailable()\n               << \"; issue=\" << issueFc;\n    if (issueFc) {\n      streamRecv.grant(streamRecv.getCurrentOffset() + initStreamRecvFc);\n      sm_.enqueueEvent(MaxStreamData{{streamRecv.getMaxOffset()}, rh->getID()});\n    }\n  }\n  uint64_t initConnRecvFc = sm_.wtConfig_.selfMaxConnData;\n  // if peer has less than rwnd / 2 to send, issue fc\n  auto& connRecv = sm_.connRecvFc_;\n  sm_.connBytesRead_ += bytesRead;\n  bool issueFc =\n      (connRecv.getMaxOffset() - sm_.connBytesRead_) <= (initConnRecvFc / 2);\n  XLOG(DBG6) << __func__ << \"; connMaxOffset=\" << connRecv.getMaxOffset()\n             << \"; connBytesRead_\" << sm_.connBytesRead_\n             << \"; issue=\" << issueFc;\n  if (issueFc) {\n    connRecv.grant(connRecv.getCurrentOffset() + initConnRecvFc);\n    sm_.enqueueEvent(MaxConnData{connRecv.getMaxOffset()});\n  }\n}\n\nvoid Accessor::stopSending(ReadHandle& rh, uint32_t err) noexcept {\n  sm_.enqueueEvent(StopSending{rh.getID(), err});\n}\n\nvoid Accessor::resetStream(WriteHandle& wh,\n                           uint32_t err,\n                           uint64_t reliableSize) noexcept {\n  sm_.enqueueEvent(ResetStream{wh.getID(), err, reliableSize});\n}\n\nvoid Accessor::onStreamWritable(WriteHandle& wh) noexcept {\n  sm_.onStreamWritable(wh);\n}\n\nvoid Accessor::done(WriteHandle& wh) noexcept {\n  XCHECK_EQ(wh.state_, Closed);\n  if (wh.rh_->state_ == Closed) { // bidi done\n    sm_.erase(wh.getID());\n  }\n}\n\nvoid Accessor::done(ReadHandle& rh) noexcept {\n  XCHECK_EQ(rh.state_, Closed);\n  if (rh.wh_->state_ == Closed) { // bidi done\n    sm_.erase(rh.getID());\n  }\n}\n\n/*static*/ auto WtStreamManager::nextStreamIds(WtDir dir) noexcept\n    -> NextStreamIds {\n  return dir == Client ? NextStreamIds{.bidi = 0x00, .uni = 0x02}\n                       : NextStreamIds{.bidi = 0x01, .uni = 0x03};\n}\n\n/**\n * MaxStreams (and StreamsCounter) is indexed by the *type* of the stream,\n * which is derived from the least two significant bits of the stream id, as\n * per rfc9000 (i.e. the array must be created in the following order: client\n * bidi (0x00), server bidi (0x01), client uni (0x02), server uni (0x03))\n */\n/*static*/ auto WtStreamManager::maxStreams(WtDir dir,\n                                            const WtConfig& config) noexcept\n    -> MaxStreamsContainer::Type {\n  MaxStreamsContainer::Type res{config.peerMaxStreamsBidi,\n                                config.selfMaxStreamsBidi,\n                                config.peerMaxStreamsUni,\n                                config.selfMaxStreamsUni};\n  if (dir == Server) {\n    res = {config.selfMaxStreamsBidi,\n           config.peerMaxStreamsBidi,\n           config.selfMaxStreamsUni,\n           config.peerMaxStreamsUni};\n  }\n  return res;\n}\n\n/*static*/ auto WtStreamManager::streamType(uint64_t streamId) noexcept\n    -> StreamType {\n  return static_cast<StreamType>(streamId & 0b11);\n}\n\nauto WtStreamManager::StreamsCounterContainer::getCounter(\n    uint64_t streamId) noexcept -> StreamsCounter& {\n  return streamsCounter_[streamType(streamId)];\n}\n\nauto WtStreamManager::StreamsCounterContainer::getCounter(\n    uint64_t streamId) const noexcept -> const StreamsCounter& {\n  return streamsCounter_[streamType(streamId)];\n}\n\nWtStreamManager::MaxStreamsContainer::MaxStreamsContainer(\n    Type maxStreams) noexcept\n    : maxStreams_(maxStreams) {\n}\n\nuint64_t WtStreamManager::MaxStreamsContainer::getMaxStreams(\n    uint64_t streamId) const noexcept {\n  return maxStreams_[streamType(streamId)];\n}\n\nuint64_t& WtStreamManager::MaxStreamsContainer::getMaxStreams(\n    uint64_t streamId) noexcept {\n  return maxStreams_[streamType(streamId)];\n}\n\nWtStreamManager::WtStreamManager(WtDir dir,\n                                 const WtConfig& config,\n                                 EgressCallback& egressCb,\n                                 IngressCallback& ingressCb,\n                                 quic::PriorityQueue& priorityQueue) noexcept\n    : dir_(dir),\n      nextStreamIds_(nextStreamIds(dir)),\n      maxStreams_(maxStreams(dir, config)),\n      writableStreams_(priorityQueue),\n      wtConfig_(config),\n      connRecvFc_(config.selfMaxConnData),\n      connSendFc_(config.peerMaxConnData),\n      egressCb_(egressCb),\n      ingressCb_(ingressCb) {\n  XCHECK(dir <= WtDir::Server) << \"invalid dir\";\n}\n\nWtStreamManager::~WtStreamManager() noexcept = default;\n\nbool WtStreamManager::isSelf(uint64_t streamId) const {\n  return (streamId & 0x01) == uint8_t(dir_);\n}\nbool WtStreamManager::isPeer(uint64_t streamId) const {\n  return !isSelf(streamId);\n}\n\nbool WtStreamManager::isUni(uint64_t streamId) const {\n  return (streamId & 0x02) != 0;\n}\nbool WtStreamManager::isBidi(uint64_t streamId) const {\n  return !isUni(streamId);\n}\n\nbool WtStreamManager::isIngress(uint64_t streamId) const {\n  return isBidi(streamId) || isPeer(streamId);\n}\nbool WtStreamManager::isEgress(uint64_t streamId) const {\n  return isBidi(streamId) || isSelf(streamId);\n}\n\n/**\n * Enqueues an event into events. If this is the first event to be enqueued,\n * it will invoke the Callback (edge-triggered).\n */\nvoid WtStreamManager::enqueueEvent(Event&& ev) noexcept {\n  bool wasEmpty = !hasEvent();\n  ctrlEvents_.push_back(std::move(ev));\n  if (wasEmpty && hasEvent()) {\n    egressCb_.eventsAvailable();\n  }\n}\n\nWtStreamManager::Result WtStreamManager::onMaxStreams(MaxStreamsBidi bidi) {\n  XCHECK_LE(bidi.maxStreams, kMaxVarint);\n  // the \"StreamType\" is derived from a self bidi id, simply use\n  // nextStreamIds_\n  auto& maxBidi = maxStreams_.getMaxStreams(nextStreamIds_.bidi);\n  bool valid = bidi.maxStreams >= maxBidi;\n  maxBidi = std::max(maxBidi, bidi.maxStreams);\n  return (Result)valid;\n}\n\nWtStreamManager::Result WtStreamManager::onMaxStreams(MaxStreamsUni uni) {\n  XCHECK_LE(uni.maxStreams, kMaxVarint);\n  // the \"StreamType\" is derived from a self uni id, simply use nextStreamIds_\n  auto& maxUni = maxStreams_.getMaxStreams(nextStreamIds_.uni);\n  bool valid = uni.maxStreams >= maxUni;\n  maxUni = std::max(maxUni, uni.maxStreams);\n  return (Result)valid;\n}\n\nstruct WtStreamManager::BidiHandle {\n  WriteHandle wh;\n  ReadHandle rh;\n\n  BidiHandle(uint64_t id, WtStreamManager& sm)\n      : wh(id, sm.initStreamSendFc(id), Accessor{sm}),\n        rh(id, sm.initStreamRecvFc(id), Accessor{sm}) {\n    // link ReadHandle<->WriteHandle\n    wh.rh_ = &rh;\n    rh.wh_ = &wh;\n    if (sm.isPeer(id)) {\n      /**\n       * NOTE: & TODO: This is intentionally invoked now, before the stream is\n       * inserted into the map. This is to prevent the case where an\n       * application bidirectionally resets a stream inline (from\n       * IngressCallback::onNewPeerStream) and causes its subsequent\n       * deallocation while the handle is returned to the backing transport\n       * (e.g. the ::getOrCreateBidiHandle invocation that caused this stream\n       * allocation in the first place).\n       *\n       * The backing transport must accumulate these streams and deliver them\n       * to the application in the next EventBase loop. The more appropriate\n       * fix here is for the containing class to pass in a folly::Executor to\n       * StreamManager, allowing WtStreamManager to defer invoking\n       * ::onNewPeerStream (effectively asynchronous w.r.t stream allocation).\n       */\n      sm.ingressCb_.onNewPeerStream(id);\n    }\n  }\n\n  static std::unique_ptr<BidiHandle> make(uint64_t id, WtStreamManager& sm) {\n    return std::make_unique<BidiHandle>(id, sm);\n  }\n};\n\nbool WtStreamManager::streamLimitExceeded(uint64_t streamId) const noexcept {\n  uint64_t opened = streamsCounter_.getCounter(streamId).opened;\n  uint64_t limit = maxStreams_.getMaxStreams(streamId);\n  bool exceeded = opened >= limit;\n  XLOG_IF(ERR, exceeded) << __func__ << \"; opened=\" << opened\n                         << \"; limit=\" << limit << \"; id=\" << streamId;\n  return exceeded;\n}\n\nWtStreamManager::BidiHandle* WtStreamManager::getOrCreateBidiHandleImpl(\n    uint64_t streamId) noexcept {\n  auto it = streams_.find(streamId);\n  if (it != streams_.end()) {\n    return it->second.get();\n  }\n  // prevent new streams if shutdown or either peer/self limit saturated\n  if (streamLimitExceeded(streamId) || shutdown_) {\n    return nullptr;\n  }\n  streamsCounter_.getCounter(streamId).opened++;\n  it = streams_.emplace(streamId, BidiHandle::make(streamId, *this)).first;\n  return it->second.get();\n}\n\nWebTransport::StreamWriteHandle* WtStreamManager::getOrCreateEgressHandle(\n    uint64_t streamId) noexcept {\n  auto* handle =\n      isEgress(streamId) ? getOrCreateBidiHandleImpl(streamId) : nullptr;\n  return handle ? &handle->wh : nullptr;\n}\n\nWebTransport::StreamReadHandle* WtStreamManager::getOrCreateIngressHandle(\n    uint64_t streamId) noexcept {\n  auto* handle =\n      isIngress(streamId) ? getOrCreateBidiHandleImpl(streamId) : nullptr;\n  return handle ? &handle->rh : nullptr;\n}\n\nWebTransport::BidiStreamHandle WtStreamManager::getOrCreateBidiHandle(\n    uint64_t streamId) noexcept {\n  WebTransport::BidiStreamHandle res{nullptr, nullptr};\n  if (auto* handle = getOrCreateBidiHandleImpl(streamId)) {\n    res.readHandle = isIngress(streamId) ? &handle->rh : nullptr;\n    res.writeHandle = isEgress(streamId) ? &handle->wh : nullptr;\n  }\n  return res;\n}\n\nWebTransport::BidiStreamHandle WtStreamManager::getBidiHandle(\n    uint64_t streamId) const noexcept {\n  WebTransport::BidiStreamHandle res{nullptr, nullptr};\n  auto it = streams_.find(streamId);\n  if (it != streams_.end()) {\n    res.readHandle = isIngress(streamId) ? &it->second->rh : nullptr;\n    res.writeHandle = isEgress(streamId) ? &it->second->wh : nullptr;\n  }\n  return res;\n}\n\nWtStreamManager::WtWriteHandle* WtStreamManager::createEgressHandle() noexcept {\n  auto* handle = getOrCreateEgressHandle(nextStreamIds_.uni);\n  nextStreamIds_.uni += (handle ? kStreamIdInc : 0);\n  return handle;\n}\n\nWebTransport::BidiStreamHandle WtStreamManager::createBidiHandle() noexcept {\n  auto handle = getOrCreateBidiHandle(nextStreamIds_.bidi);\n  if (handle.readHandle || handle.writeHandle) {\n    nextStreamIds_.bidi += kStreamIdInc;\n  }\n  return handle;\n}\n\nvoid WtStreamManager::setReadCb(WtReadHandle& rh, ReadCallback* rcb) noexcept {\n  readhandle_ref_cast(rh).rcb_ = rcb;\n}\n\nuint64_t WtStreamManager::bufferedBytes(const WtReadHandle& rh) const noexcept {\n  return static_cast<const ReadHandle&>(rh)\n      .ingress_.chain.computeChainDataLength();\n}\n\nWtStreamManager::Result WtStreamManager::onMaxData(MaxConnData data) noexcept {\n  XLOG(DBG9) << __func__ << \" maxData=\" << data.maxData;\n  if (!connSendFc_.grant(data.maxData)) {\n    return Fail;\n  }\n\n  bool wasEmpty = !hasEvent();\n  // Re-add all connection-FC-blocked streams to the priority queue\n  auto blockedStreams = std::move(connFcBlockedStreams_);\n  for (auto* wh : blockedStreams) {\n    auto& writeHandle = writehandle_ref_cast(*wh);\n    if (writeHandle.bufferedSendData_.canSendData()) {\n      writableStreams_.insert(writeHandle.getID(), writeHandle.getPriority());\n    } else {\n      XLOG(ERR) << \"Stream \" << writeHandle.getID()\n                << \" in connFcBlockedStreams_ but !canSendData\";\n    }\n  }\n\n  if (wasEmpty && hasEvent()) {\n    egressCb_.eventsAvailable();\n  }\n  return Ok;\n}\n\nWtStreamManager::Result WtStreamManager::onMaxData(\n    MaxStreamData data) noexcept {\n  // TODO(@damlaj): connection-level err if not egress stream?\n  XLOG(DBG9) << __func__ << \"; id=\" << data.streamId\n             << \"; maxData=\" << data.maxData;\n  auto* eh = writehandle_ptr_cast(getEgressHandle(data.streamId));\n  return (eh && eh->onMaxData(data.maxData)) ? Ok : Fail;\n}\n\nWtStreamManager::Result WtStreamManager::onStopSending(\n    StopSending data) noexcept {\n  XLOG(DBG9) << __func__ << \"; id=\" << data.streamId << \"; err=\" << data.err;\n  if (auto* eh = writehandle_ptr_cast(getEgressHandle(data.streamId))) {\n    auto ex = folly::make_exception_wrapper<WtException>(uint32_t(data.err),\n                                                         \"rx stop_sending\");\n    eh->cancel(std::move(ex));\n    return Ok;\n  }\n  return Fail;\n}\n\nWtStreamManager::Result WtStreamManager::onResetStream(\n    ResetStream data) noexcept {\n  XLOG(DBG9) << __func__ << \"; id=\" << data.streamId << \"; err=\" << data.err;\n  if (auto* rh = readhandle_ptr_cast(getIngressHandle(data.streamId))) {\n    auto ex = folly::make_exception_wrapper<WtException>(uint32_t(data.err),\n                                                         \"rx reset_stream\");\n    rh->cancel(std::move(ex), data.reliableSize);\n    return Ok;\n  }\n  return Fail;\n}\n\nvoid WtStreamManager::onDrainSession(DrainSession) noexcept {\n  drain_ = true;\n}\n\nvoid WtStreamManager::onCloseSession(CloseSession close) noexcept {\n  shutdown(std::move(close));\n}\n\nResult WtStreamManager::enqueue(WtReadHandle& rh, StreamData data) noexcept {\n  auto len = computeChainLength(data.data);\n  bool err = !connRecvFc_.reserve(len);\n  return (!err && (readhandle_ref_cast(rh).enqueue(std::move(data)))) ? Ok\n                                                                      : Fail;\n}\n\nWtBufferedStreamData::DequeueResult WtStreamManager::dequeue(\n    WtWriteHandle& wh, uint64_t atMost) noexcept {\n  // we're limited by conn egress fc\n  atMost = std::min(atMost, connSendFc_.getAvailable());\n  auto& writeHandle = writehandle_ref_cast(wh);\n  auto res = writeHandle.dequeue(atMost);\n  // TODO(@damlaj): return len to elide unnecessarily computing chain len\n  auto len = computeChainLength(res.data);\n  // commit len bytes to conn window\n  connSendFc_.commit(len);\n\n  // Connection FC blocked if we have data but conn window is exhausted\n  const bool hasData = !res.fin && writeHandle.bufferedSendData_.hasData();\n  if (connSendFc_.getAvailable() == 0 && hasData) {\n    connFcBlockedStreams_.insert(&wh);\n  }\n\n  XLOG(DBG8) << __func__ << \"; atMost=\" << atMost << \"; len=\" << len\n             << \"; fin=\" << res.fin;\n  return res;\n}\n\nvoid WtStreamManager::onStreamWritable(WtWriteHandle& wh) noexcept {\n  // Don't re-insert if already tracked as conn FC blocked\n  auto& writeHandle = writehandle_ref_cast(wh);\n\n  if (connSendFc_.getAvailable() == 0 &&\n      !writeHandle.bufferedSendData_.onlyFinPending()) {\n    connFcBlockedStreams_.insert(&wh);\n    return;\n  }\n\n  bool wasEmpty = !hasEvent();\n  writableStreams_.insert(writeHandle.getID(), writeHandle.getPriority());\n\n  if (wasEmpty && hasEvent()) {\n    egressCb_.eventsAvailable();\n  }\n}\n\nvoid WtStreamManager::drain() noexcept {\n  drain_ = true;\n  enqueueEvent(DrainSession{});\n}\n\nvoid WtStreamManager::shutdown(CloseSession cs) noexcept {\n  if (shutdown_) {\n    return;\n  }\n  shutdown_ = true;\n  auto ex = folly::make_exception_wrapper<WtException>(cs.err, cs.msg);\n  auto streams = std::move(streams_);\n  for (auto& [_, handle] : streams) {\n    handle->rh.cancel(ex);\n    handle->wh.cancel(ex);\n  }\n  enqueueEvent(std::move(cs));\n}\n\nbool WtStreamManager::canCreateUni() const noexcept {\n  return !streamLimitExceeded(nextStreamIds_.uni);\n}\n\nbool WtStreamManager::canCreateBidi() const noexcept {\n  return !streamLimitExceeded(nextStreamIds_.bidi);\n}\n\nbool WtStreamManager::hasEvent() const noexcept {\n  return writableStreams_.hasStreams() || !ctrlEvents_.empty();\n}\n\nuint64_t WtStreamManager::initStreamRecvFc(uint64_t streamId) const noexcept {\n  return isBidi(streamId) ? wtConfig_.selfMaxStreamDataBidi\n                          : wtConfig_.selfMaxStreamDataUni;\n}\n\nuint64_t WtStreamManager::initStreamSendFc(uint64_t streamId) const noexcept {\n  return isBidi(streamId) ? wtConfig_.peerMaxStreamDataBidi\n                          : wtConfig_.peerMaxStreamDataUni;\n}\n\nvoid WtStreamManager::erase(uint64_t streamId) noexcept {\n  bool erased = streams_.erase(streamId) > 0;\n  if (!erased) { // may not be in map if invoked via ::shutdown\n    return;\n  }\n  auto& counter = streamsCounter_.getCounter(streamId);\n  const uint64_t opened = counter.opened;\n  const uint64_t closed = ++counter.closed;\n  XCHECK_GE(opened, closed);\n  // if peer stream, we may need to advertise additional MaxStreams credit.\n  if (isPeer(streamId)) {\n    // compute the number of peer openable streams; if it is <= half of the\n    // initMaxStreams advertised to peer, we advertise additional MaxStreams\n    // credit\n    const uint64_t initStreamLimit = isBidi(streamId)\n                                         ? wtConfig_.selfMaxStreamsBidi\n                                         : wtConfig_.selfMaxStreamsUni;\n    auto& maxStreams = maxStreams_.getMaxStreams(streamId);\n    const uint64_t openable = maxStreams - closed;\n    XLOG(DBG6) << \"init=\" << initStreamLimit << \"; limit=\" << maxStreams\n               << \"; opened=\" << opened << \"; closed=\" << closed;\n    if (openable <= initStreamLimit / 2) {\n      maxStreams += (initStreamLimit - openable);\n      isBidi(streamId) ? enqueueEvent(MaxStreamsBidi{maxStreams})\n                       : enqueueEvent(MaxStreamsUni{maxStreams});\n    }\n  }\n}\n\nWtStreamManager::WtWriteHandle* WtStreamManager::getEgressHandle(\n    uint64_t streamId) const noexcept {\n  return getBidiHandle(streamId).writeHandle;\n}\n\nWtStreamManager::WtReadHandle* WtStreamManager::getIngressHandle(\n    uint64_t streamId) const noexcept {\n  return getBidiHandle(streamId).readHandle;\n}\n\nWtStreamManager::WtWriteHandle* WtStreamManager::nextWritable() const noexcept {\n  auto streamId = writableStreams_.peek();\n  auto* wh =\n      writehandle_ptr_cast(streamId ? getEgressHandle(*streamId) : nullptr);\n  XLOG(DBG6) << __func__ << \"; wh=\" << wh\n             << \"; connSendFc.avail=\" << connSendFc_.getAvailable();\n  return (wh && (connSendFc_.getAvailable() > 0 ||\n                 wh->bufferedSendData_.onlyFinPending()))\n             ? wh\n             : nullptr;\n}\n\n} // namespace proxygen::detail\n\n/**\n * ReadHandle & WriteHandle implementations here\n */\n\nReadHandle::ReadHandle(uint64_t id, uint64_t initRecvWnd, Accessor acc) noexcept\n    : StreamReadHandle(id),\n      smAccessor_(acc),\n      streamRecvFc_(initRecvWnd),\n      state_(static_cast<HandleState>(acc.isIngress(id))) {\n}\n\nfolly::SemiFuture<StreamData> ReadHandle::readStreamData() {\n  // TODO(@damlaj): hook into interrupt handler, but somehow ensure no UB from\n  // concurrent access\n  XLOG_IF(FATAL, promise_.valid()) << \"one pending read at a time\";\n  if (!ingress_.chain.empty() || ingress_.fin) {\n    auto len = ingress_.chain.computeChainDataLength();\n    bytesRead_ += len;\n    // only issue conn-level fc if we've rx'd fin\n    smAccessor_.maybeGrantFc(ingress_.fin ? nullptr : this, len);\n    auto res = StreamData{ingress_.chain.pop(), ingress_.fin};\n    XLOG(DBG6) << __func__ << \"; len=\" << len << \"; fin=\" << res.fin;\n    if (rcb_) {\n      rcb_->readReady(*this);\n    }\n    finish(ingress_.fin);\n    return folly::makeSemiFuture(std::move(res));\n  }\n  // always deliver buffered data (even if rx fin) prior to delivering err\n  if (auto ex = ex_) {\n    finish(/*done=*/true);\n    return folly::makeSemiFuture<StreamData>(std::move(ex));\n  }\n  auto [p, f] = folly::makePromiseContract<StreamData>();\n  promise_ = std::move(p);\n  return std::move(f);\n}\n\nfolly::Expected<folly::Unit, ReadHandle::ErrCode> ReadHandle::stopSending(\n    uint32_t error) {\n  smAccessor_.stopSending(*this, error);\n  // **beware cancel must be last** (`this` can be deleted immediately after)\n  cancel(folly::make_exception_wrapper<WtException>(error, \"tx stop_sending\"));\n  return folly::unit;\n}\n\nResult ReadHandle::enqueue(StreamData&& data) noexcept {\n  XCHECK(!ingress_.fin) << \"already rx'd eof\";\n  auto len = computeChainLength(data.data);\n  if (!streamRecvFc_.reserve(len)) {\n    return Result::Fail; // error\n  }\n  if (len > 0) {\n    ingress_.chain.appendToChain(std::move(data.data));\n  }\n  ingress_.fin = data.fin;\n  XLOG(DBG6) << __func__ << \"; len=\" << len << \"; fin=\" << data.fin\n             << \"; p.valid()=\" << promise_.valid();\n  if (auto p = resetPromise(); p.valid()) {\n    // only issue conn-level fc if we've rx'd fin\n    smAccessor_.maybeGrantFc(ingress_.fin ? nullptr : this, len);\n    p.setValue(StreamData{ingress_.chain.pop(), ingress_.fin});\n    if (rcb_) {\n      rcb_->readReady(*this);\n    }\n    finish(ingress_.fin);\n  }\n  return Result::Ok;\n}\n\n/**\n * Invoked when peer sends rst_stream or when application calls\n * ReadHandle::stopSending – in either case, we must attempt to release\n * connection-level flow control credit. It is unnecessary to\n * release stream-level flow control because the stream's ingress is complete at\n * this point.\n */\nvoid ReadHandle::cancel(folly::exception_wrapper ex,\n                        uint64_t reliableSize) noexcept {\n  XLOG(DBG8) << __func__ << \"; ex=\" << ex.what();\n  // ensures future reads after reliableSize bytes have been read fail\n  ex_ = std::move(ex);\n  reliableSize = (reliableSize == kInvalidVarint) ? bytesRead_ : reliableSize;\n  if (bytesRead_ < reliableSize) {\n    return;\n  }\n  // any pending reads should be resolved with ex\n  if (auto p = resetPromise(); p.valid()) {\n    p.setException(ex_);\n  }\n  // release conn fc credit to peer\n  auto len = ingress_.chain.computeChainDataLength();\n  smAccessor_.maybeGrantFc(/*rh=*/nullptr, len);\n  cs_.requestCancellation();\n  finish(/*done=*/true);\n}\n\nReadPromise ReadHandle::resetPromise() noexcept {\n  return std::exchange(promise_, emptyReadPromise());\n}\n\nvoid ReadHandle::finish(bool done) noexcept {\n  if (done) {\n    state_ = Closed;\n    smAccessor_.done(*this);\n  }\n}\n\nWriteHandle::WriteHandle(uint64_t id,\n                         uint64_t initSendWnd,\n                         Accessor acc) noexcept\n    : StreamWriteHandle(id),\n      smAccessor_(acc),\n      bufferedSendData_(initSendWnd),\n      state_(static_cast<HandleState>(acc.isEgress(id))) {\n}\n\nfolly::Expected<WriteHandle::FcState, WriteHandle::ErrCode>\nWriteHandle::writeStreamData(\n    std::unique_ptr<folly::IOBuf> data,\n    bool fin,\n    WebTransport::ByteEventCallback* byteEventCallback) {\n  // TODO(@damlaj): handle reset stream; elide unnecessarily recomputing len\n  auto len = computeChainLength(data);\n  XLOG_IF(ERR, !(len || fin)) << \"no-op writeStreamData\";\n  bool connBlocked = smAccessor_.connSend().buffer(len);\n  bool streamBlocked =\n      bufferedSendData_.enqueue(std::move(data), fin, byteEventCallback);\n  XLOG(DBG6) << __func__ << \"; id=\" << id_ << \"; len=\" << len << \"; fin=\" << fin\n             << \"; connBlocked=\" << connBlocked\n             << \"; streamBlocked=\" << streamBlocked;\n  if (bufferedSendData_.canSendData()) {\n    smAccessor_.onStreamWritable(*this); // stream is now writable\n  }\n  return streamBlocked ? FcState::BLOCKED : FcState::UNBLOCKED;\n}\n\nfolly::Expected<folly::Unit, WriteHandle::ErrCode> WriteHandle::resetStream(\n    uint32_t error) {\n  smAccessor_.resetStream(*this, error, /*reliableSize=*/0);\n  // **beware cancel must be last** (`this` can be deleted immediately after)\n  cancel(folly::make_exception_wrapper<WtException>(error, \"tx reset_stream\"));\n  return folly::unit;\n}\n\nfolly::Expected<folly::Unit, WriteHandle::ErrCode> WriteHandle::setPriority(\n    quic::PriorityQueue::Priority priority) {\n  XCHECK_NE(state_, Closed) << \"setPriority after close\";\n\n  StreamWriteHandle::setPriority(priority);\n  smAccessor_.writableStreams().update(getID(), getPriority());\n\n  return folly::unit;\n}\n\nResult WriteHandle::onMaxData(uint64_t offset) {\n  if (!bufferedSendData_.grant(offset)) {\n    return Result::Fail;\n  }\n  if (bufferedSendData_.canSendData()) {\n    smAccessor_.onStreamWritable(*this); // stream is now writable\n  }\n  return Result::Ok;\n}\n\nfolly::Expected<folly::SemiFuture<uint64_t>, WriteHandle::ErrCode>\nWriteHandle::awaitWritable() {\n  XCHECK(!promise_.valid()) << \"at most one pending awaitWritable\";\n  const auto bufferAvailable = bufferedSendData_.window().getBufferAvailable();\n  if (bufferAvailable > 0) {\n    return folly::makeSemiFuture(bufferAvailable);\n  }\n  auto [p, f] = folly::makePromiseContract<uint64_t>();\n  promise_ = std::move(p);\n  return std::move(f);\n}\n\nWritePromise WriteHandle::resetPromise() noexcept {\n  return std::exchange(promise_, emptyWritePromise());\n}\n\n// TODO(@damlaj): StreamData and DequeueResult should be the same struct\nWtBufferedStreamData::DequeueResult WriteHandle::dequeue(\n    uint64_t atMost) noexcept {\n  XCHECK_NE(state_, Closed) << \"dequeue after close\";\n\n  auto res = bufferedSendData_.dequeue(atMost);\n  const auto bufferAvailable = bufferedSendData_.window().getBufferAvailable();\n  if (bufferAvailable > 0) {\n    if (auto p = resetPromise(); p.valid()) {\n      p.setValue(bufferAvailable);\n    }\n  }\n\n  auto bytesDequeued = computeChainLength(res.data);\n  XLOG(DBG6) << __func__ << \"; id=\" << id_ << \"; len=\" << bytesDequeued\n             << \"; fin=\" << res.fin;\n\n  // Erase if blocked (wrote nothing) or done (!canSendData)\n  // Consume if wrote data and still have more\n  if (bytesDequeued > 0 && bufferedSendData_.canSendData()) {\n    smAccessor_.writableStreams().consume(bytesDequeued);\n  } else {\n    smAccessor_.writableStreams().erase(getID());\n  }\n\n  finish(res.fin);\n  return res;\n}\n\nvoid WriteHandle::cancel(folly::exception_wrapper ex) noexcept {\n  XLOG(DBG8) << __func__ << \"; ex=\" << ex.what();\n  ex_ = std::move(ex);\n  bufferedSendData_.clear(id_);\n  // any pending awaitWritable should be resolved with ex\n  if (auto p = resetPromise(); p.valid()) {\n    p.setException(ex_);\n  }\n  smAccessor_.writableStreams().erase(getID());\n  // Also remove from conn FC blocked set if present\n  smAccessor_.connFcBlockedStreams().erase(this);\n  cs_.requestCancellation();\n  // **beware finish must be last** (`this` can be deleted immediately after)\n  finish(/*done=*/true);\n}\n\nvoid WriteHandle::finish(bool done) noexcept {\n  if (done) {\n    state_ = Closed;\n    smAccessor_.done(*this);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtStreamManager.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <variant>\n\n#include <folly/container/F14Set.h>\n#include <proxygen/lib/http/webtransport/StreamPriorityQueue.h>\n\n/**\n * get rid of WebTransport.h dependency, extract StreamReadHandle &\n * StreamWriteHandle into a different TU?\n */\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <proxygen/lib/http/webtransport/WtEgressContainer.h>\n\n/**\n * This comment block documents the api of WtStreamManager (i.e. its behaviour).\n * Please refer to the documentation in WtStreamManager.cpp for the\n * implementation details (i.e. the how).\n *\n * WtStreamManager, as the name suggestions, simply manages the stream state of\n * WebTransport streams. Following good design philosophy, as much as possible\n * is hidden from the user of this class.\n *\n * There are two channels to interact with this class:\n *\n * – The application via the handles (StreamWriteHandle & StreamReadHandle) and\n *   their respective methods (e.g. WriteHandle::writeStreamData,\n *   ReadHandle::stopSending, etc.).\n *\n * – The backing transport (http/2, quic, etc.) via the methods in this class\n *   (e.g. onMaxStreams, onMaxData, ::enqueue(ReadHandle&),\n *   ::dequeue(WriteHandle&), etc.)\n *\n * There are two channels to communicate events to the backing transport, via\n * IngressCallback and EgressCallback.\n *\n *  – IngressCallback lets the backing transport know that a new peer stream is\n *    available to read/write. This is a simple uint64_t, and the backing\n *    transport can invoke ::getOrCreateBidiHandle to inspect whether this is a\n *    bidi or uni stream\n *\n *  – EgressCallback lets the backing transport know that there is an event\n *    available for the write loop to action on – there are two main\n *    EgressCallback events:\n *\n *    – Control frames that need to be serialized/sent by the transport (e.g.\n *      reset_stream, stop_sending, etc.). The transport should invoke\n *      ::moveEvents and install a visitor to ensure all control-frames are\n *      handled appropriately.\n *\n *    – A stream that is now writable. The transport should peek the priority\n *      queue and dequeue data from streams by ID.\n *\n * A note about stream handles – everything is dervied from WtDir (the role of\n * the endpoint, e.g. client or server) and stream id. Any attempt to\n * access/create a handle for an invalid direction will return nullptr. For\n * example, a client invoking ::getEgressHandle for a stream id that is\n * logically a server-initiated unidirectional stream (e.g. id=0x03) will return\n * nullptr.\n */\n\nnamespace proxygen::detail {\n\n// value maps to lsb of stream id as per rfc9000\nenum WtDir : uint8_t { Client = 0, Server = 1 };\n\nconstexpr uint64_t kInvalidVarint = std::numeric_limits<uint64_t>::max();\nconstexpr uint64_t kMaxVarint = (1ull << 62) - 1;\n\nstruct WtStreamManager {\n  /**\n   * This edge-triggered callback (::eventsAvailable) is invoked once control\n   * events are available to be dequeued by the backing transport – (e.g.\n   * connection- and stream-level flow control, reset_stream, stop_sending,\n   * etc.)\n   *\n   * It is also invoked once there is a writable stream (i.e. the priority\n   * queue becomes non-empty)\n   */\n  struct EgressCallback {\n    virtual ~EgressCallback() = default;\n    virtual void eventsAvailable() noexcept = 0;\n  };\n  /**\n   * IngressCallback::onNewPeerStream is invoked whenever a new peer stream has\n   * been allocated. As of now, the backing transport must wait an EventBase\n   * loop before passing the handle to the application (i.e.\n   * WebTransportHandler).\n   */\n  struct IngressCallback {\n    virtual ~IngressCallback() = default;\n    virtual void onNewPeerStream(uint64_t streamId) noexcept = 0;\n  };\n\n  struct WtConfig {\n    static constexpr auto kDefaultFc = std::numeric_limits<uint16_t>::max();\n    // values we've advertised to the peer\n    uint64_t selfMaxStreamsBidi{1};\n    uint64_t selfMaxStreamsUni{1};\n    uint64_t selfMaxConnData{kDefaultFc};\n    uint64_t selfMaxStreamDataBidi{kDefaultFc};\n    uint64_t selfMaxStreamDataUni{kDefaultFc};\n\n    // values peer has advertised to us\n    uint64_t peerMaxStreamsBidi{1};\n    uint64_t peerMaxStreamsUni{1};\n    uint64_t peerMaxConnData{kDefaultFc};\n    uint64_t peerMaxStreamDataBidi{kDefaultFc};\n    uint64_t peerMaxStreamDataUni{kDefaultFc};\n  };\n\n  WtStreamManager(WtDir dir,\n                  const WtConfig& config,\n                  EgressCallback& egressCb,\n                  IngressCallback& ingressCb,\n                  quic::PriorityQueue& priorityQueue) noexcept;\n  ~WtStreamManager() noexcept;\n\n  using WtException = WebTransport::Exception;\n  using WtWriteHandle = WebTransport::StreamWriteHandle;\n  using WtReadHandle = WebTransport::StreamReadHandle;\n  using StreamData = WebTransport::StreamData;\n  using ReadPromise = folly::Promise<StreamData>;\n  using ReadFut = folly::SemiFuture<StreamData>;\n  /**\n   * - Gets the stream if it exists\n   * - Otherwise attempts to create the stream if sufficient stream credit\n   *   exists & not shutdown\n   * - Otherwise returns nullptr\n   */\n  WtWriteHandle* getOrCreateEgressHandle(uint64_t streamId) noexcept;\n  WtReadHandle* getOrCreateIngressHandle(uint64_t streamId) noexcept;\n  WebTransport::BidiStreamHandle getOrCreateBidiHandle(\n      uint64_t streamId) noexcept;\n\n  // Gets a stream if it already exists, otherwise nullptr\n  [[nodiscard]] WebTransport::BidiStreamHandle getBidiHandle(\n      uint64_t streamId) const noexcept;\n  [[nodiscard]] WtWriteHandle* getEgressHandle(\n      uint64_t streamId) const noexcept;\n  [[nodiscard]] WtReadHandle* getIngressHandle(\n      uint64_t streamId) const noexcept;\n\n  /**\n   * Initiators of streams should use this api, attempts to create the next\n   * consecutive egress/bidi handle. This is not applicable to quic as quic\n   * stream ids may not be consecutive w.r.t WebTransport streams. Usage over\n   * quic should first successfully create the quic stream and use the above\n   * getOrCreate(Ingress|Egress)Handle apis to create a handle.\n   */\n  WtWriteHandle* createEgressHandle() noexcept;\n  WebTransport::BidiStreamHandle createBidiHandle() noexcept;\n\n  /**\n   * per-stream callbacks that may be useful to transports that support stream\n   * multiplexing (e.g. quic)\n   */\n  struct ReadCallback {\n    virtual ~ReadCallback() noexcept = default;\n    // invoked when the read handle has available buffer space\n    virtual void readReady(WtReadHandle&) noexcept = 0;\n  };\n  void setReadCb(WtReadHandle& rh, ReadCallback* rcb) noexcept;\n  // ignores wt connection-level flow control (this should be set to kMaxVarint\n  // in quic)\n  [[nodiscard]] bool hasPendingData(const WtWriteHandle& wh) const noexcept;\n  [[nodiscard]] uint64_t bufferedBytes(const WtReadHandle& rh) const noexcept;\n\n  enum Result : uint8_t { Fail = 0, Ok = 1 };\n  /**\n   * invoke when receiving max_streams frame from peer – returns Ok if\n   * successful (monotonically increasing max_streams), Fail otherwise\n   */\n  struct MaxStreams {\n    uint64_t maxStreams{0};\n  };\n  struct MaxStreamsBidi : MaxStreams {};\n  struct MaxStreamsUni : MaxStreams {};\n  Result onMaxStreams(MaxStreamsBidi);\n  Result onMaxStreams(MaxStreamsUni);\n\n  /**\n   * invoke when receiving wt_max_data & wt_max_stream_data (latter in http/2\n   * only) – returns Ok if successful (i.e. monotonically increasing max_data),\n   * Fail otherwise\n   */\n  struct MaxConnData {\n    uint64_t maxData{0};\n  };\n  struct MaxStreamData : MaxConnData {\n    uint64_t streamId{0};\n  };\n  Result onMaxData(MaxConnData) noexcept;\n  Result onMaxData(MaxStreamData) noexcept;\n\n  /**\n   * invoke when receiving a stop_sending – returns bool if stream was found\n   * TODO(@damlaj): enforce sending a rst_stream in response to stop_sending\n   */\n  struct StopSending {\n    uint64_t streamId{0};\n    uint64_t err{0};\n  };\n  Result onStopSending(StopSending) noexcept;\n\n  struct ResetStream {\n    uint64_t streamId{0};\n    uint64_t err{0};\n    uint64_t reliableSize{0};\n  };\n  /**\n   * Invoke when receiving a rst_stream capsule – returns Ok if stream was\n   * found, Fail otherwise\n   *\n   * TODO(@damlaj): implement reliable_offset\n   */\n  Result onResetStream(ResetStream) noexcept;\n\n  /**\n   * Invoke when receiving a drain_session capsule. ::onDrainSession has no\n   * immediate side-effect, all streams are untouched. It does however prevent\n   * the creation of any new streams (both self- and peer-initiated)\n   */\n  struct DrainSession {};\n  void onDrainSession(DrainSession) noexcept;\n\n  /**\n   * Invoke when receiving a close_session capsule. ::onCloseSession has the\n   * immediate side-effect of deallocating every stream. In addition, it\n   * prevents the creation of any new streams (both self- and peer-initiated).\n   */\n  struct CloseSession {\n    uint64_t err{0};\n    std::string msg;\n  };\n  void onCloseSession(CloseSession) noexcept;\n\n  /**\n   * Enqueues data into read handle – returns Fail if recv window overflowed\n   * (either conn or stream) or Ok otherwise. Receive window is considered\n   * \"strict\", and any overflow should be treated as an error.\n   */\n  Result enqueue(WtReadHandle&, StreamData data) noexcept;\n\n  /**\n   * Dequeues buffered data from the WtWriteHandle; if there is no data to be\n   * dequeued, it returns DequeueResult{.data=nullptr, .fin=false,\n   * .deliveryCallback=nullptr}\n   */\n  WtBufferedStreamData::DequeueResult dequeue(WtWriteHandle&,\n                                              uint64_t atMost) noexcept;\n\n  /**\n   * Events are communicated to the backing transport (http/2 or http/3) via\n   * Callback::eventsAvailable – the user can subsequently dequeue all events\n   * using the below ::moveEvents(). All these events are strictly control\n   * frames by design, as they are not flow controlled.\n   */\n  using Event = std::variant<ResetStream,\n                             StopSending,\n                             MaxConnData,\n                             MaxStreamData,\n                             MaxStreamsBidi,\n                             MaxStreamsUni,\n                             DrainSession,\n                             CloseSession>;\n  std::vector<Event> moveEvents() noexcept {\n    return std::move(ctrlEvents_);\n  }\n\n  struct Accessor; // used by Read&Write handle to access private members of\n                   // this class\n  friend struct Accessor;\n\n  /**\n   * DEPRECATED: Use the priority queue directly instead.\n   *\n   * Returns the next writable stream if the head of the priority queue is a\n   * stream. Returns nullptr if:\n   * - The queue is empty\n   * - The head of the queue is not a stream (e.g., a datagram)\n   */\n  [[nodiscard]] WtWriteHandle* nextWritable() const noexcept;\n\n  [[nodiscard]] bool hasStreams() const noexcept {\n    return !streams_.empty();\n  }\n\n  [[nodiscard]] bool isClosed() const noexcept {\n    return shutdown_ && !hasStreams();\n  }\n\n  /**\n   * Locally initiated drain of the WtStreamManager. This will enqueue an event\n   * to send to the peer (of type DrainSession, notified via Callback). Similar\n   * to ::onDrainSession, this will leave existing streams untouched. However,\n   * no new streams can be created following this invocation.\n   */\n  void drain() noexcept;\n\n  /**\n   * Locally initiated shutdown of the WtStreamManager. This will enqueue an\n   * event to send to the peer (of type CloseSession, notified via Callback).\n   * Similar to ::onCloseSession, this has an immediate side-effect of\n   * deallocating every stream and prevents the creation of new streams.\n   */\n  void shutdown(CloseSession) noexcept;\n\n  // returns true iff we can create self-initiated uni/bidi streams\n  [[nodiscard]] bool canCreateUni() const noexcept;\n  [[nodiscard]] bool canCreateBidi() const noexcept;\n\n private:\n  [[nodiscard]] bool isSelf(uint64_t streamId) const;\n  [[nodiscard]] bool isPeer(uint64_t streamId) const;\n  [[nodiscard]] bool isEgress(uint64_t streamId) const;\n  [[nodiscard]] bool isIngress(uint64_t streamId) const;\n  [[nodiscard]] bool isUni(uint64_t streamId) const;\n  [[nodiscard]] bool isBidi(uint64_t streamId) const;\n\n  void enqueueEvent(Event&& ev) noexcept;\n  void onStreamWritable(WtWriteHandle& wh) noexcept;\n  [[nodiscard]] bool hasEvent() const noexcept;\n  struct BidiHandle;\n  friend struct BidiHandle;\n  BidiHandle* getOrCreateBidiHandleImpl(uint64_t streamId) noexcept;\n  [[nodiscard]] uint64_t initStreamRecvFc(uint64_t streamId) const noexcept;\n  [[nodiscard]] uint64_t initStreamSendFc(uint64_t streamId) const noexcept;\n  void erase(uint64_t streamId) noexcept;\n\n  // as per rfc-9000\n  enum StreamType : uint8_t {\n    ClientBidi = 0x00,\n    ServerBidi = 0x01,\n    ClientUni = 0x02,\n    ServerUni = 0x03,\n    Max = 0x04\n  };\n  // value maps to second lsb as per rfc-9000\n  enum WtStreamDir : uint8_t { Bidi = 0x00, Uni = 0x01 };\n  [[nodiscard]] bool streamLimitExceeded(uint64_t streamId) const noexcept;\n\n  const WtDir dir_;\n\n  std::map<uint64_t, std::unique_ptr<BidiHandle>> streams_;\n\n  // used for stream initiators\n  struct NextStreamIds {\n    uint64_t bidi{};\n    uint64_t uni{};\n  } nextStreamIds_;\n\n  // index into arrays using the lower two bits of stream id\n  struct StreamsCounter {\n    uint64_t opened{0};\n    uint64_t closed{0};\n  };\n\n  struct StreamsCounterContainer {\n    using Type = std::array<StreamsCounter, StreamType::Max>;\n    explicit StreamsCounterContainer() noexcept = default;\n    StreamsCounter& getCounter(uint64_t streamId) noexcept;\n    [[nodiscard]] const StreamsCounter& getCounter(\n        uint64_t streamId) const noexcept;\n\n   private:\n    Type streamsCounter_;\n  } streamsCounter_;\n\n  struct MaxStreamsContainer {\n    using Type = std::array<uint64_t, StreamType::Max>;\n    explicit MaxStreamsContainer(Type maxStreams) noexcept;\n    uint64_t& getMaxStreams(uint64_t streamId) noexcept;\n    [[nodiscard]] uint64_t getMaxStreams(uint64_t streamId) const noexcept;\n\n   private:\n    Type maxStreams_;\n  } maxStreams_;\n\n  /**\n   * Priority queue wrapper for scheduling stream egress.\n   * Streams are inserted when they have buffered data & stream-level FC.\n   * Removed when out of data or stream FC.\n   * Connection-FC-blocked streams are tracked separately and re-inserted on\n   * onMaxData.\n   */\n  StreamPriorityQueue writableStreams_;\n\n  /**\n   * Streams that have data + stream FC but are blocked by connection FC.\n   * These are re-inserted into writableStreams_ when connection FC is granted.\n   */\n  folly::F14FastSet<WtWriteHandle*> connFcBlockedStreams_;\n\n  WtConfig wtConfig_;\n  uint64_t connBytesRead_{0};\n  FlowController connRecvFc_;\n  BufferedFlowController connSendFc_;\n  EgressCallback& egressCb_;\n  IngressCallback& ingressCb_;\n  std::vector<Event> ctrlEvents_;\n  bool drain_ : 1 {false};\n  bool shutdown_ : 1 {false};\n\n  // helper functions to compute next stream ids and max streams\n  static NextStreamIds nextStreamIds(WtDir) noexcept;\n  static MaxStreamsContainer::Type maxStreams(WtDir, const WtConfig&) noexcept;\n  static StreamType streamType(uint64_t streamId) noexcept;\n};\n\n} // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/logging/xlog.h>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <proxygen/lib/http/webtransport/WtUtils.h>\n\nnamespace {\nstatic constexpr std::string_view kWtEventVisitor = \"WtEventVisitor\";\nusing WtErrCode = proxygen::WebTransport::ErrorCode;\nusing WtStreamManager = proxygen::detail::WtStreamManager;\nusing StreamWriteHandle = proxygen::WebTransport::StreamWriteHandle;\nusing StreamReadHandle = proxygen::WebTransport::StreamReadHandle;\nusing BidiStreamHandle = proxygen::WebTransport::BidiStreamHandle;\nusing StreamData = proxygen::WebTransport::StreamData;\nusing FCState = proxygen::WebTransport::FCState;\n\ntemplate <class T>\nstruct WtExpected {\n  using Type = folly::Expected<T, proxygen::WebTransport::ErrorCode>;\n};\n\nfolly::Promise<folly::Unit> makeEmptyPromise() {\n  return folly::Promise<folly::Unit>::makeEmpty();\n}\n\nfolly::Promise<folly::Unit> resetPromise(\n    folly::Promise<folly::Unit>& p) noexcept {\n  return std::exchange(p, makeEmptyPromise());\n}\n\n}; // namespace\n\nnamespace proxygen::detail {\n\n// sets default egress h2 wt http settings if ENABLE_CONNECT_PROTOCOL set\nvoid setEgressWtHttpSettings(HTTPSettings* settings) {\n  if (supportsWt({settings})) {\n    // max data\n    constexpr uint64_t kWtInitMaxData = std::numeric_limits<uint16_t>::max();\n    static constexpr auto kMaxDataSettings = {\n        SettingsId::WT_INITIAL_MAX_DATA,\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_UNI,\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_BIDI};\n    for (auto maxDataSetting : kMaxDataSettings) {\n      settings->setIfNotPresent(maxDataSetting, kWtInitMaxData);\n    }\n\n    // max streams\n    constexpr uint64_t kWtInitMaxStreams = 10;\n    static constexpr auto kMaxStreamsSettings = {\n        SettingsId::WT_INITIAL_MAX_STREAMS_UNI,\n        SettingsId::WT_INITIAL_MAX_STREAMS_BIDI,\n    };\n    for (auto maxStreams : kMaxStreamsSettings) {\n      settings->setIfNotPresent(maxStreams, kWtInitMaxStreams);\n    }\n  }\n}\n\nWtStreamManager::WtConfig getWtConfig(const HTTPSettings* ingress,\n                                      const HTTPSettings* egress) {\n  WtStreamManager::WtConfig config;\n  // either both ingress&egress are nullptr (e.g. http/1.1) or both non-nullptr\n  if (egress || ingress) {\n    XCHECK(egress && ingress);\n    // set peer's WtConfig via ingress HTTPSettings\n    config.peerMaxConnData =\n        ingress->getSetting(SettingsId::WT_INITIAL_MAX_DATA, /*defaultVal=*/0);\n    config.peerMaxStreamDataUni = ingress->getSetting(\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_UNI, /*defaultVal=*/0);\n    config.peerMaxStreamDataBidi = ingress->getSetting(\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_BIDI, /*defaultVal=*/0);\n    config.peerMaxStreamsUni =\n        ingress->getSetting(SettingsId::WT_INITIAL_MAX_STREAMS_UNI,\n                            /*defaultVal=*/0);\n    config.peerMaxStreamsBidi =\n        ingress->getSetting(SettingsId::WT_INITIAL_MAX_STREAMS_BIDI,\n                            /*defaultVal=*/0);\n\n    // set self's WtConfig via egress HTTPSettings\n    config.selfMaxConnData =\n        egress->getSetting(SettingsId::WT_INITIAL_MAX_DATA, /*defaultVal=*/0);\n    config.selfMaxStreamDataUni = egress->getSetting(\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_UNI, /*defaultVal=*/0);\n    config.selfMaxStreamDataBidi = egress->getSetting(\n        SettingsId::WT_INITIAL_MAX_STREAM_DATA_BIDI, /*defaultVal=*/0);\n    config.selfMaxStreamsUni =\n        egress->getSetting(SettingsId::WT_INITIAL_MAX_STREAMS_UNI,\n                           /*defaultVal=*/0);\n    config.selfMaxStreamsBidi =\n        egress->getSetting(SettingsId::WT_INITIAL_MAX_STREAMS_BIDI,\n                           /*defaultVal=*/0);\n  }\n\n  XLOG(DBG6) << config.selfMaxStreamsBidi << \"; \" << config.selfMaxStreamsUni\n             << \"; \" << config.selfMaxConnData << \"; \"\n             << config.selfMaxStreamDataBidi << \"; \"\n             << config.selfMaxStreamDataUni << \"; \" << config.peerMaxStreamsBidi\n             << \"; \" << config.peerMaxStreamsUni << \"; \"\n             << config.peerMaxConnData << \"; \" << config.peerMaxStreamDataBidi\n             << \"; \" << config.peerMaxStreamDataUni;\n\n  return config;\n}\n\nbool supportsWt(std::initializer_list<const HTTPSettings*> settings) {\n  constexpr auto kEnableConnectProto = SettingsId::ENABLE_CONNECT_PROTOCOL;\n  constexpr auto kEnableWtMaxSess = SettingsId::WT_MAX_SESSIONS;\n  return std::all_of(settings.begin(), settings.end(), [](auto* settings) {\n    return settings &&\n           settings->getSetting(kEnableConnectProto, /*defaultVal=*/0) &&\n           settings->getSetting(kEnableWtMaxSess, /*defaultVal=*/0);\n  });\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::ResetStream rst) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" rst.id=\" << rst.streamId\n             << \"; rst.err=\" << rst.err;\n  writeWTResetStream(\n      egress,\n      WTResetStreamCapsule{.streamId = rst.streamId,\n                           .appProtocolErrorCode = uint32_t(rst.err),\n                           .reliableSize = rst.reliableSize});\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::StopSending ss) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" ss.id=\" << ss.streamId\n             << \"; ss.err=\" << ss.err;\n  writeWTStopSending(\n      egress,\n      WTStopSendingCapsule{.streamId = ss.streamId,\n                           .appProtocolErrorCode = uint32_t(ss.err)});\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::MaxConnData md) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" md.offset=\" << md.maxData;\n  writeWTMaxData(egress, WTMaxDataCapsule{md.maxData});\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::MaxStreamData msd) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" msd.id=\" << msd.streamId\n             << \" msd.offset=\" << msd.maxData;\n  writeWTMaxStreamData(\n      egress,\n      WTMaxStreamDataCapsule{.streamId = msd.streamId,\n                             .maximumStreamData = msd.maxData});\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::MaxStreamsBidi ms) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" msd.maxStreamsBidi=\" << ms.maxStreams;\n  writeWTMaxStreams(egress,\n                    WTMaxStreamsCapsule{.maximumStreams = ms.maxStreams},\n                    /*isBidi=*/true);\n}\n\nvoid WtEventVisitor::operator()(\n    WtStreamManager::MaxStreamsUni ms) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" msd.maxStreamsUni=\" << ms.maxStreams;\n  writeWTMaxStreams(egress,\n                    WTMaxStreamsCapsule{.maximumStreams = ms.maxStreams},\n                    /*isBidi=*/false);\n}\n\nvoid WtEventVisitor::operator()(WtStreamManager::DrainSession) const noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" ds\";\n  writeDrainWebTransportSession(egress);\n}\n\nvoid WtEventVisitor::operator()(WtStreamManager::CloseSession cs) noexcept {\n  XLOG(DBG6) << kWtEventVisitor << \" cs.err=\" << cs.err << \" cs.msg=\" << cs.msg;\n  sessionClosed = true;\n  writeCloseWebTransportSession(\n      egress,\n      CloseWebTransportSessionCapsule{.applicationErrorCode = uint32_t(cs.err),\n                                      .applicationErrorMessage =\n                                          std::move(cs.msg)});\n}\n\n// WtCapsuleCallback\nvoid WtCapsuleCallback::onPaddingCapsule(PaddingCapsule) noexcept {\n}\n\nvoid WtCapsuleCallback::onWTResetStreamCapsule(\n    WTResetStreamCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; id=\" << c.streamId\n             << \"; err=\" << c.appProtocolErrorCode;\n  sm_.onResetStream(\n      WtStreamManager::ResetStream{.streamId = c.streamId,\n                                   .err = c.appProtocolErrorCode,\n                                   .reliableSize = c.reliableSize});\n}\n\nvoid WtCapsuleCallback::onWTStopSendingCapsule(\n    WTStopSendingCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; id=\" << c.streamId\n             << \"; err=\" << c.appProtocolErrorCode;\n  sm_.onStopSending(WtStreamManager::StopSending{\n      .streamId = c.streamId, .err = c.appProtocolErrorCode});\n}\n\nvoid WtCapsuleCallback::onWTStreamCapsule(WTStreamCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; id=\" << c.streamId;\n  if (auto* rh = sm_.getOrCreateIngressHandle(c.streamId)) {\n    sm_.enqueue(*rh, {std::move(c.streamData), c.fin});\n  }\n}\n\nvoid WtCapsuleCallback::onWTMaxDataCapsule(WTMaxDataCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; offset=\" << c.maximumData;\n  sm_.onMaxData(WtStreamManager::MaxConnData{.maxData = c.maximumData});\n}\n\nvoid WtCapsuleCallback::onWTMaxStreamDataCapsule(\n    WTMaxStreamDataCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; id=\" << c.streamId\n             << \"; offset=\" << c.maximumStreamData;\n  sm_.onMaxData(\n      WtStreamManager::MaxStreamData{{c.maximumStreamData}, c.streamId});\n}\n\nvoid WtCapsuleCallback::onWTMaxStreamsBidiCapsule(\n    WTMaxStreamsCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; max=\" << c.maximumStreams;\n  bool wasAvail = sm_.canCreateBidi();\n  sm_.onMaxStreams(WtStreamManager::MaxStreamsBidi{c.maximumStreams});\n  if (!wasAvail && sm_.canCreateBidi()) {\n    wtSess_.onBidiStreamCreditAvail();\n  }\n}\n\nvoid WtCapsuleCallback::onWTMaxStreamsUniCapsule(\n    WTMaxStreamsCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; max=\" << c.maximumStreams;\n  bool wasAvail = sm_.canCreateUni();\n  sm_.onMaxStreams(WtStreamManager::MaxStreamsUni{c.maximumStreams});\n  if (!wasAvail && sm_.canCreateUni()) {\n    wtSess_.onUniStreamCreditAvail();\n  }\n}\n\nvoid WtCapsuleCallback::onWTDataBlockedCapsule(WTDataBlockedCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n}\n\nvoid WtCapsuleCallback::onWTStreamDataBlockedCapsule(\n    WTStreamDataBlockedCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n}\n\nvoid WtCapsuleCallback::onWTStreamsBlockedBidiCapsule(\n    WTStreamsBlockedCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n}\n\nvoid WtCapsuleCallback::onWTStreamsBlockedUniCapsule(\n    WTStreamsBlockedCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n}\n\nvoid WtCapsuleCallback::onDatagramCapsule(DatagramCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n}\n\nvoid WtCapsuleCallback::onCloseWTSessionCapsule(\n    CloseWebTransportSessionCapsule c) noexcept {\n  XLOG(DBG6) << __func__ << \"; err=\" << c.applicationErrorCode\n             << \"; c.msg=\" << c.applicationErrorMessage;\n  sm_.onCloseSession(WtStreamManager::CloseSession{\n      .err = c.applicationErrorCode, .msg = c.applicationErrorMessage});\n}\n\nvoid WtCapsuleCallback::onDrainWTSessionCapsule(\n    DrainWebTransportSessionCapsule) noexcept {\n  XLOG(DBG6) << __func__;\n  sm_.onDrainSession(WtStreamManager::DrainSession{});\n}\n\nvoid WtCapsuleCallback::onCapsule(uint64_t capsuleType,\n                                  uint64_t capsuleLength) noexcept {\n  XLOG(DBG7) << __func__ << \"; capsuleType=\" << capsuleType\n             << \"; capsuleLength=\" << capsuleLength;\n}\n\nvoid WtCapsuleCallback::onConnectionError(CapsuleCodec::ErrorCode) noexcept {\n  XLOG(DBG6) << __func__;\n  sm_.onCloseSession(\n      WtStreamManager::CloseSession{.err = 0, .msg = \"onConnectionError\"});\n}\n\nWtSessionBase::WtSessionBase(folly::EventBase* evb,\n                             WtStreamManager& sm) noexcept\n    : evb_{evb},\n      sm_{sm},\n      awaitUniCredit_{makeEmptyPromise()},\n      awaitBidiCredit_{makeEmptyPromise()} {\n}\n\nWtExpected<StreamWriteHandle*>::Type WtSessionBase::createUniStream() noexcept {\n  if (auto* res = sm_.createEgressHandle()) {\n    return res;\n  }\n  return folly::makeUnexpected(WtErrCode::STREAM_CREATION_ERROR);\n}\n\nWtExpected<BidiStreamHandle>::Type WtSessionBase::createBidiStream() noexcept {\n  auto res = sm_.createBidiHandle();\n  if (res.readHandle || res.writeHandle) {\n    XCHECK(res.readHandle && res.writeHandle);\n    return BidiStreamHandle{res.readHandle, res.writeHandle};\n  }\n  return folly::makeUnexpected(WtErrCode::STREAM_CREATION_ERROR);\n}\n\nfolly::SemiFuture<folly::Unit> WtSessionBase::awaitUniStreamCredit() noexcept {\n  if (sm_.canCreateUni()) {\n    return folly::makeSemiFuture();\n  }\n  auto [p, f] = folly::makePromiseContract<folly::Unit>();\n  awaitUniCredit_ = std::move(p);\n  return std::move(f);\n}\n\nfolly::SemiFuture<folly::Unit> WtSessionBase::awaitBidiStreamCredit() noexcept {\n  if (sm_.canCreateBidi()) {\n    return folly::makeSemiFuture();\n  }\n  auto [p, f] = folly::makePromiseContract<folly::Unit>();\n  awaitBidiCredit_ = std::move(p);\n  return std::move(f);\n}\n\nWtExpected<folly::SemiFuture<StreamData>>::Type WtSessionBase::readStreamData(\n    StreamId id) noexcept {\n  if (auto* rh = sm_.getBidiHandle(id).readHandle) {\n    return rh->readStreamData();\n  }\n  return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);\n}\n\nWtExpected<FCState>::Type WtSessionBase::writeStreamData(\n    StreamId id,\n    std::unique_ptr<folly::IOBuf> data,\n    bool fin,\n    ByteEventCallback* byteEventCallback) noexcept {\n  if (auto* wh = sm_.getBidiHandle(id).writeHandle) {\n    return wh->writeStreamData(std::move(data), fin, byteEventCallback);\n  }\n  return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::resetStream(\n    StreamId id, uint32_t error) noexcept {\n  if (auto* wh = sm_.getBidiHandle(id).writeHandle) {\n    return wh->resetStream(error);\n  }\n  return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::setPriority(\n    uint64_t streamId, quic::PriorityQueue::Priority priority) noexcept {\n  return folly::unit;\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::setPriorityQueue(\n    std::unique_ptr<quic::PriorityQueue>) noexcept {\n  return folly::unit;\n}\n\nWtExpected<folly::SemiFuture<StreamId>>::Type WtSessionBase::awaitWritable(\n    StreamId id) noexcept {\n  if (auto* wh = sm_.getBidiHandle(id).writeHandle) {\n    return wh->awaitWritable();\n  }\n  return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::stopSending(\n    StreamId id, uint32_t error) noexcept {\n  if (auto* rh = sm_.getBidiHandle(id).readHandle) {\n    return rh->stopSending(error);\n  }\n  return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::sendDatagram(\n    std::unique_ptr<folly::IOBuf>) noexcept {\n  LOG(FATAL) << \"not implemented\";\n}\n\nquic::TransportInfo WtSessionBase::getTransportInfo() const noexcept {\n  return quic::TransportInfo{};\n}\n\nWtExpected<folly::Unit>::Type WtSessionBase::closeSession(\n    folly::Optional<uint32_t> error) noexcept {\n  sm_.shutdown(\n      WtStreamManager::CloseSession{error.value_or(0), \"closeSession\"});\n  return folly::unit;\n}\n\nvoid WtSessionBase::onBidiStreamCreditAvail() noexcept {\n  if (auto p = resetPromise(awaitBidiCredit_); p.valid()) {\n    p.setValue();\n  }\n}\n\nvoid WtSessionBase::onUniStreamCreditAvail() noexcept {\n  if (auto p = resetPromise(awaitUniCredit_); p.valid()) {\n    p.setValue();\n  }\n}\n\n} // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/WtUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/codec/webtransport/WebTransportCapsuleCodec.h>\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n\nnamespace proxygen {\nclass HTTPSettings;\n}\n\nnamespace proxygen::detail {\n\n// if ENABLE_CONNECT_PROTOCOL is set; applies some default h2 wt settings on the\n// HttpSettings (e.g. MaxData, MaxStreamData, etc.)\nvoid setEgressWtHttpSettings(HTTPSettings* settings);\n\n// derives the WtConfig from the ingress & egress HttpSettings of HttpCodec\nWtStreamManager::WtConfig getWtConfig(const HTTPSettings* ingress,\n                                      const HTTPSettings* egress);\n\n/**\n * http/2 wt draft:\n * > In order to indicate support for WebTransport, both the client and the\n * > server MUST send a SETTINGS_WEBTRANSPORT_MAX_SESSIONS value greater than\n * > \"0\" in their SETTINGS frame\n *\n * > An endpoint needs to send both SETTINGS_ENABLE_CONNECT_PROTOCOL and\n * > SETTINGS_WEBTRANSPORT_MAX_SESSIONS for WebTransport to be enabled.\n */\nbool supportsWt(std::initializer_list<const HTTPSettings*> settings);\n\n/**\n * This is a helper utility (applicable to both http/2 and http/3) to visit\n * every Event yielded by WtStreamManager. The visitor simply serializes the\n * capsules in the IOBufQueue in order. This IOBufQueue should be subsequently\n * sent on the CONNECT stream (the stream originally carrying the CONNECT\n * stream).\n */\nstruct WtEventVisitor {\n  folly::IOBufQueue& egress;\n  bool sessionClosed{false};\n\n  void operator()(WtStreamManager::ResetStream rst) const noexcept;\n  void operator()(WtStreamManager::StopSending ss) const noexcept;\n  void operator()(WtStreamManager::MaxConnData md) const noexcept;\n  void operator()(WtStreamManager::MaxStreamData msd) const noexcept;\n  void operator()(WtStreamManager::MaxStreamsBidi ms) const noexcept;\n  void operator()(WtStreamManager::MaxStreamsUni ms) const noexcept;\n  void operator()(WtStreamManager::DrainSession ds) const noexcept;\n  void operator()(WtStreamManager::CloseSession cs) noexcept;\n};\n\n/**\n * This is a helper utility (applicable to both http/2 and http/3) that simply\n * aggregates all newly created peer stream ids into a std::vector for\n * convenience. The library should use these ids to query WtStreamManager and\n * pass the resulting handle into WebTransportHandler.\n */\nstruct WtStreamManagerIngressCallback : WtStreamManager::IngressCallback {\n  ~WtStreamManagerIngressCallback() override = default;\n  void onNewPeerStream(uint64_t streamId) noexcept override {\n    peerStreams.push_back(streamId);\n  }\n  std::vector<uint64_t> peerStreams;\n};\n\n/**\n * This is a helper utility (applicable to both http/2 and http/3) to capsules\n * received on the CONNECT stream to the WtStreamManager. This is pretty much a\n * 1:1 mapping to the WtStreamManager api.\n */\nclass WtSessionBase; // fwd-decl\nstruct WtCapsuleCallback : WebTransportCapsuleCodec::Callback {\n  WtStreamManager& sm_;\n  WtSessionBase& wtSess_;\n\n  explicit WtCapsuleCallback(WtStreamManager& sm,\n                             WtSessionBase& wtSess) noexcept\n      : sm_(sm), wtSess_(wtSess) {\n  }\n  ~WtCapsuleCallback() noexcept override = default;\n  void onPaddingCapsule(PaddingCapsule) noexcept override;\n  void onWTResetStreamCapsule(WTResetStreamCapsule) noexcept override;\n  void onWTStopSendingCapsule(WTStopSendingCapsule) noexcept override;\n  void onWTStreamCapsule(WTStreamCapsule) noexcept override;\n  void onWTMaxDataCapsule(WTMaxDataCapsule) noexcept override;\n  void onWTMaxStreamDataCapsule(WTMaxStreamDataCapsule) noexcept override;\n  void onWTMaxStreamsBidiCapsule(WTMaxStreamsCapsule) noexcept override;\n  void onWTMaxStreamsUniCapsule(WTMaxStreamsCapsule) noexcept override;\n  void onWTDataBlockedCapsule(WTDataBlockedCapsule) noexcept override;\n  void onWTStreamDataBlockedCapsule(\n      WTStreamDataBlockedCapsule) noexcept override;\n  void onWTStreamsBlockedBidiCapsule(WTStreamsBlockedCapsule) noexcept override;\n  void onWTStreamsBlockedUniCapsule(WTStreamsBlockedCapsule) noexcept override;\n  void onDatagramCapsule(DatagramCapsule) noexcept override;\n  void onCloseWTSessionCapsule(\n      CloseWebTransportSessionCapsule) noexcept override;\n  void onDrainWTSessionCapsule(\n      DrainWebTransportSessionCapsule capsule) noexcept override;\n  void onCapsule(uint64_t capsuleType,\n                 uint64_t capsuleLength) noexcept override;\n  void onConnectionError(CapsuleCodec::ErrorCode) noexcept override;\n};\n\ntemplate <class T>\nstruct WtExpected {\n  using Type = folly::Expected<T, WebTransport::ErrorCode>;\n};\nusing StreamId = uint64_t;\nclass WtSessionBase : public WebTransport {\n public:\n  using Ptr = std::shared_ptr<WtSessionBase>;\n  WtSessionBase(folly::EventBase* evb, WtStreamManager& sm) noexcept;\n  ~WtSessionBase() noexcept override = default;\n\n  WtExpected<StreamWriteHandle*>::Type createUniStream() noexcept override;\n  WtExpected<BidiStreamHandle>::Type createBidiStream() noexcept override;\n\n  folly::SemiFuture<folly::Unit> awaitUniStreamCredit() noexcept override;\n  folly::SemiFuture<folly::Unit> awaitBidiStreamCredit() noexcept override;\n\n  WtExpected<folly::SemiFuture<StreamData>>::Type readStreamData(\n      StreamId id) noexcept override;\n\n  WtExpected<FCState>::Type writeStreamData(\n      StreamId id,\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      ByteEventCallback* byteEventCallback) noexcept override;\n\n  WtExpected<folly::Unit>::Type resetStream(StreamId id,\n                                            uint32_t error) noexcept override;\n\n  WtExpected<folly::Unit>::Type setPriority(\n      uint64_t streamId,\n      quic::PriorityQueue::Priority priority) noexcept override;\n\n  folly::Expected<folly::Unit, ErrorCode> setPriorityQueue(\n      std::unique_ptr<quic::PriorityQueue> queue) noexcept override;\n\n  WtExpected<folly::SemiFuture<StreamId>>::Type awaitWritable(\n      StreamId id) noexcept override;\n\n  WtExpected<folly::Unit>::Type stopSending(StreamId id,\n                                            uint32_t error) noexcept override;\n\n  WtExpected<folly::Unit>::Type sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) noexcept override;\n\n  [[nodiscard]] quic::TransportInfo getTransportInfo() const noexcept override;\n\n  WtExpected<folly::Unit>::Type closeSession(\n      folly::Optional<uint32_t> error) noexcept override;\n\n  // fns invoked from WebTransportCapsuleCallback to let CoroWtSession there's\n  // available uni/bidi stream credit\n  void onBidiStreamCreditAvail() noexcept;\n  void onUniStreamCreditAvail() noexcept;\n\n  folly::EventBase* evb() {\n    return evb_;\n  }\n\n protected:\n  folly::Promise<folly::Unit>& uniCreditPromise() {\n    return awaitUniCredit_;\n  }\n  folly::Promise<folly::Unit>& bidiCreditPromise() {\n    return awaitBidiCredit_;\n  }\n\n private:\n  folly::EventBase* evb_;\n  WtStreamManager& sm_;\n  folly::Promise<folly::Unit> awaitUniCredit_;\n  folly::Promise<folly::Unit> awaitBidiCredit_;\n};\n\n} // namespace proxygen::detail\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\nproxygen_add_test(TARGET WebTransportAPITest\n  SOURCES\n    WebTransportAPITest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET QuicWebTransportTest\n  SOURCES\n    QuicWebTransportTest.cpp\n  DEPENDS\n    proxygen_http_webtransport_quicwebtransport\n    mock_quic_socket_driver\n    mvfst::mvfst_common_events_folly_eventbase\n    testmain\n)\n\nproxygen_add_test(TARGET WtStreamManagerTest\n  SOURCES\n    WtStreamManagerTest.cpp\n  DEPENDS\n    codectestutils\n    proxygen_http_webtransport_wt_stream_manager\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/FakeSharedWebTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/ExceptionWrapper.h>\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nnamespace proxygen::test {\n\nusing GenericApiRet = folly::Expected<folly::Unit, WebTransport::ErrorCode>;\n\n// WebTransport stream handle that is both a read and write handle.  It's\n// designed to be held by two FakeSharedWebTransport objects so that writes\n// from one are delivered as reads to the other.\nclass FakeStreamHandle\n    : public WebTransport::StreamReadHandle\n    , public WebTransport::StreamWriteHandle {\n public:\n  explicit FakeStreamHandle(uint64_t inId)\n      : WebTransport::StreamReadHandle(inId),\n        WebTransport::StreamWriteHandle(inId) {\n  }\n\n  ~FakeStreamHandle() {\n    WebTransport::StreamReadHandle::cs_.requestCancellation();\n    WebTransport::StreamWriteHandle::cs_.requestCancellation();\n  }\n\n  uint64_t getID() const {\n    return WebTransport::StreamReadHandle::getID();\n  }\n\n  folly::Optional<uint32_t> getWriteErr() {\n    return writeErr_;\n  }\n\n  auto* writeException() {\n    return WebTransport::StreamWriteHandle::exception();\n  }\n\n  auto* readException() {\n    return WebTransport::StreamReadHandle::exception();\n  }\n\n  folly::SemiFuture<WebTransport::StreamData> readStreamData() override {\n    XCHECK(!promise_) << \"One read at a time\";\n    if (writeErr_) {\n      auto exwrapper =\n          folly::make_exception_wrapper<WebTransport::Exception>(*writeErr_);\n      return folly::makeFuture<WebTransport::StreamData>(exwrapper);\n    } else if (!buf_.empty() || (fin_ && inflightBuf_.empty())) {\n      return folly::makeFuture(WebTransport::StreamData(\n          {buf_.move(), fin_ && inflightBuf_.empty()}));\n    } else {\n      // need a new promise\n      auto [promise, future] =\n          folly::makePromiseContract<WebTransport::StreamData>();\n      promise_ = std::move(promise);\n      return std::move(future);\n    }\n  }\n  GenericApiRet stopSending(uint32_t code) override {\n    auto& ex = WebTransport::StreamWriteHandle::ex_;\n    if (!ex) {\n      ex = folly::make_exception_wrapper<WebTransport::Exception>(code);\n      WebTransport::StreamWriteHandle::cs_.requestCancellation();\n    }\n    if (!writeErr_) {\n      writeErr_.emplace(code);\n    }\n    return folly::unit;\n  }\n\n  void setImmediateDelivery(bool immediateDelivery) {\n    immediateDelivery_ = immediateDelivery;\n  }\n\n  void deliverInflightData(size_t bytes = std::numeric_limits<size_t>::max()) {\n    CHECK_GT(bytes, 0);\n    XLOG(DBG4) << \"deliverInflightData bytes=\" << bytes\n               << \" inflightBuf_ size=\" << inflightBuf_.chainLength()\n               << \" fin=\" << (fin_ ? \"true\" : \"false\");\n    auto buf = inflightBuf_.splitAtMost(bytes);\n    dataDelivered_ += buf->computeChainDataLength();\n    buf_.append(std::move(buf));\n    if (promise_) {\n      promise_->setValue(WebTransport::StreamData(\n          {buf_.move(), fin_ && inflightBuf_.empty()}));\n      promise_.reset();\n    }\n    for (auto it = offsetToDeliveryCallback_.begin();\n         it != offsetToDeliveryCallback_.end();) {\n      if (it->first > dataDelivered_) {\n        break;\n      }\n      for (auto& deliveryCallback : it->second) {\n        deliveryCallback->onByteEvent(getID(), it->first);\n      }\n      it = offsetToDeliveryCallback_.erase(it);\n    }\n  }\n\n  using WriteStreamDataRet =\n      folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>;\n  WriteStreamDataRet writeStreamData(\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      WebTransport::ByteEventCallback* deliveryCallback) override {\n    size_t length = 0;\n    if (data) {\n      length = data->computeChainDataLength();\n      dataWritten_ += length;\n    }\n    if (immediateDelivery_) {\n      if (data) {\n        dataDelivered_ += length;\n        buf_.append(std::move(data));\n      }\n    } else {\n      if (data) {\n        inflightBuf_.append(std::move(data));\n      }\n    }\n    fin_ = fin;\n    if (promise_) {\n      if (immediateDelivery_) {\n        promise_->setValue(WebTransport::StreamData({buf_.move(), fin_}));\n        promise_.reset();\n      }\n    }\n\n    if (deliveryCallback) {\n      if (immediateDelivery_) {\n        deliveryCallback->onByteEvent(getID(), dataWritten_);\n      } else {\n        offsetToDeliveryCallback_[dataWritten_].push_back(deliveryCallback);\n      }\n    }\n    return WebTransport::FCState::UNBLOCKED;\n  }\n\n  folly::Expected<folly::SemiFuture<uint64_t>, WebTransport::ErrorCode>\n  awaitWritable() override {\n    return folly::makeFuture<uint64_t>(0);\n  }\n\n  GenericApiRet resetStream(uint32_t err) override {\n    for (auto& [offset, deliveryCallback] : offsetToDeliveryCallback_) {\n      for (auto& callback : deliveryCallback) {\n        callback->onByteEventCanceled(getID(), offset);\n      }\n    }\n    if (promise_) {\n      promise_->setException(WebTransport::Exception(err));\n      promise_.reset();\n    }\n    writeErr_ = err;\n    return folly::unit;\n  }\n  GenericApiRet setPriority(quic::PriorityQueue::Priority priority) override {\n    quic::HTTPPriorityQueue::Priority httpPri(priority);\n    pri.emplace(std::forward_as_tuple(\n        httpPri->urgency, httpPri->order, httpPri->incremental));\n    return folly::unit;\n  }\n\n  bool open() const {\n    return !fin_ && !writeErr_ && (!promise_ || !promise_->isFulfilled());\n  }\n\n  uint64_t id{0};\n  folly::Optional<folly::Promise<WebTransport::StreamData>> promise_;\n  folly::IOBufQueue buf_{folly::IOBufQueue::cacheChainLength()};\n  uint32_t dataWritten_{0};\n  uint32_t dataDelivered_{0};\n  bool fin_{false};\n  folly::Optional<std::tuple<uint8_t, uint64_t, bool>> pri;\n  folly::Optional<uint32_t> writeErr_;\n\n  // If immediateDelivery_ == false, we stash data in inflightBuf_ until\n  // deliverInflightData() is called.\n  bool immediateDelivery_{true};\n  folly::IOBufQueue inflightBuf_{folly::IOBufQueue::cacheChainLength()};\n  folly::F14FastMap<uint64_t, std::vector<WebTransport::ByteEventCallback*>>\n      offsetToDeliveryCallback_;\n};\n\n// Implementation of WebTransport for testing two connected endpoints.\n//\n// Usage:\n//\n// auto [client, server] = FakeSharedWebTransport::makeSharedWebTransport();\n//\n// Each FakeSharedWebTransport also requires a WebTransportHandler for the peer\n// to deliver new streams, datagrams, and end-of-session events.\n\nclass FakeSharedWebTransport : public WebTransport {\n public:\n  static std::pair<std::unique_ptr<FakeSharedWebTransport>,\n                   std::unique_ptr<FakeSharedWebTransport>>\n  makeSharedWebTransport() {\n    auto a = std::make_unique<FakeSharedWebTransport>();\n    auto b = std::make_unique<FakeSharedWebTransport>();\n    a->setPeer(b.get());\n    b->setPeer(a.get());\n    return {std::move(a), std::move(b)};\n  }\n  FakeSharedWebTransport() = default;\n  ~FakeSharedWebTransport() override {\n    writeHandles.clear();\n    readHandles.clear();\n  }\n  quic::TransportInfo getTransportInfo() const override {\n    return quic::TransportInfo{};\n  }\n\n  const folly::SocketAddress& getPeerAddress() const override {\n    return peerAddress_;\n  }\n\n  const folly::SocketAddress& getLocalAddress() const override {\n    return localAddress_;\n  }\n\n  void setPeer(FakeSharedWebTransport* peer) {\n    peer_ = peer;\n  }\n\n  void setPeerHandler(WebTransportHandler* peerHandler) {\n    peerHandler_ = peerHandler;\n  }\n\n  folly::Expected<StreamWriteHandle*, ErrorCode> createUniStream() override {\n    auto id = nextUniStreamId_;\n    nextUniStreamId_ += 4;\n    auto handle = std::make_shared<FakeStreamHandle>(id);\n    writeHandles.emplace(id, handle);\n    peer_->readHandles.emplace(id, handle);\n    peerHandler_->onNewUniStream(handle.get());\n    return handle.get();\n  }\n  folly::Expected<BidiStreamHandle, ErrorCode> createBidiStream() override {\n    auto id = nextBidiStreamId_;\n    nextBidiStreamId_ += 4;\n    auto readH = std::make_shared<FakeStreamHandle>(id);\n    auto writeH = std::make_shared<FakeStreamHandle>(id);\n    readHandles.emplace(id, readH);\n    writeHandles.emplace(id, writeH);\n    peer_->readHandles.emplace(id, writeH);\n    peer_->writeHandles.emplace(id, readH);\n    peerHandler_->onNewBidiStream({writeH.get(), readH.get()});\n    return BidiStreamHandle({readH.get(), writeH.get()});\n  }\n  using AwaitStreamCreditRet = folly::SemiFuture<folly::Unit>;\n  AwaitStreamCreditRet awaitUniStreamCredit() override {\n    return folly::makeFuture(folly::unit);\n  }\n  AwaitStreamCreditRet awaitBidiStreamCredit() override {\n    return folly::makeFuture(folly::unit);\n  }\n\n  using ReadStreamDataRet =\n      folly::Expected<folly::SemiFuture<StreamData>, WebTransport::ErrorCode>;\n  ReadStreamDataRet readStreamData(uint64_t id) override {\n    auto h = readHandles.find(id);\n    if (h == readHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->readStreamData();\n  }\n\n  folly::Expected<FCState, ErrorCode> writeStreamData(\n      uint64_t id,\n      std::unique_ptr<folly::IOBuf> data,\n      bool fin,\n      WebTransport::ByteEventCallback* deliveryCallback) override {\n    auto h = writeHandles.find(id);\n    if (h == writeHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->writeStreamData(std::move(data), fin, deliveryCallback);\n  }\n\n  folly::Expected<folly::SemiFuture<uint64_t>, ErrorCode> awaitWritable(\n      uint64_t id) override {\n    auto h = writeHandles.find(id);\n    if (h == writeHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->awaitWritable();\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> resetStream(uint64_t streamId,\n                                                      uint32_t error) override {\n    auto h = writeHandles.find(streamId);\n    if (h == writeHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->resetStream(error);\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> setPriority(\n      uint64_t streamId, quic::PriorityQueue::Priority priority) override {\n    auto h = writeHandles.find(streamId);\n    if (h == writeHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->setPriority(priority);\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> setPriorityQueue(\n      std::unique_ptr<quic::PriorityQueue> /*queue*/) noexcept override {\n    return folly::unit;\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> stopSending(uint64_t streamId,\n                                                      uint32_t error) override {\n    auto h = readHandles.find(streamId);\n    if (h == readHandles.end()) {\n      return folly::makeUnexpected(WebTransport::ErrorCode::GENERIC_ERROR);\n    }\n    return h->second->stopSending(error);\n  }\n\n  folly::Expected<folly::Unit, ErrorCode> sendDatagram(\n      std::unique_ptr<folly::IOBuf> datagram) override {\n    peerHandler_->onDatagram(std::move(datagram));\n    return folly::unit;\n  }\n\n  // Close the WebTransport session, with an optional error\n  //\n  // Any pending futures will complete with a folly::OperationCancelled\n  // exception\n  folly::Expected<folly::Unit, ErrorCode> closeSession(\n      folly::Optional<uint32_t> error = folly::none) override {\n    // TODO: This mirrors mvfst and WebTransportImpl behavior but seems wrong.\n    // A local code for stopSending/reset would be better that the input code.\n    auto closeCode = error.value_or(std::numeric_limits<uint32_t>::max());\n    for (auto& h : writeHandles) {\n      if (h.second->open()) {\n        h.second->resetStream(closeCode);\n      }\n    }\n    writeHandles.clear();\n    for (auto& h : readHandles) {\n      h.second->stopSending(closeCode);\n    }\n    readHandles.clear();\n    if (peerHandler_) {\n      peerHandler_->onSessionEnd(error);\n    }\n    sessionClosed_ = true;\n    return folly::unit;\n  }\n\n  bool isSessionClosed() const {\n    return sessionClosed_;\n  }\n\n  std::map<uint64_t, std::shared_ptr<FakeStreamHandle>> writeHandles;\n  std::map<uint64_t, std::shared_ptr<FakeStreamHandle>> readHandles;\n\n private:\n  bool sessionClosed_{false};\n  uint64_t nextBidiStreamId_{0};\n  uint64_t nextUniStreamId_{2};\n  FakeSharedWebTransport* peer_{nullptr};\n  WebTransportHandler* peerHandler_{nullptr};\n  folly::SocketAddress peerAddress_{\"0.0.0.0\", 123};\n  folly::SocketAddress localAddress_{\"0.0.0.0\", 456};\n};\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/FlowControllerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/webtransport/FlowController.h>\n\n#include <gtest/gtest.h>\n\nclass FlowControllerTest : public ::testing::Test {\n protected:\n  FlowController flowController;\n\n  void SetUp() override {\n    flowController = FlowController(100);\n  }\n};\n\nTEST_F(FlowControllerTest, InitialState) {\n  EXPECT_EQ(flowController.getCurrentOffset(), 0);\n  EXPECT_EQ(flowController.getMaxOffset(), 100);\n}\n\nTEST_F(FlowControllerTest, ReserveWithinLimit) {\n  EXPECT_TRUE(flowController.reserve(50));\n  EXPECT_EQ(flowController.getCurrentOffset(), 50);\n  EXPECT_FALSE(flowController.isBlocked());\n}\n\nTEST_F(FlowControllerTest, ReserveExceedsLimit) {\n  EXPECT_FALSE(flowController.reserve(150));\n  EXPECT_EQ(flowController.getCurrentOffset(), 0);\n  EXPECT_FALSE(flowController.isBlocked());\n}\n\nTEST_F(FlowControllerTest, GrantIncreasesMaxOffset) {\n  EXPECT_TRUE(flowController.reserve(100));\n  EXPECT_TRUE(flowController.isBlocked());\n  EXPECT_TRUE(flowController.grant(150));\n  EXPECT_EQ(flowController.getMaxOffset(), 150);\n}\n\nTEST_F(FlowControllerTest, GrantDoesNotDecreaseMaxOffset) {\n  EXPECT_FALSE(flowController.grant(50));\n  EXPECT_EQ(flowController.getMaxOffset(), 100);\n}\n\nTEST_F(FlowControllerTest, GetAvailable) {\n  EXPECT_EQ(flowController.getAvailable(), 100);\n\n  flowController.reserve(50);\n  EXPECT_EQ(flowController.getAvailable(), 50);\n\n  flowController.grant(150);\n  EXPECT_EQ(flowController.getAvailable(), 100);\n}\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/Mocks.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GMock.h>\n#include <proxygen/lib/http/webtransport/WebTransport.h>\n\nnamespace proxygen::test {\n\nusing GenericApiRet = folly::Expected<folly::Unit, WebTransport::ErrorCode>;\n\nclass MockStreamReadHandle : public WebTransport::StreamReadHandle {\n public:\n  explicit MockStreamReadHandle(uint64_t inId)\n      : WebTransport::StreamReadHandle(inId) {\n  }\n\n  using ReadStreamDataRet = folly::SemiFuture<WebTransport::StreamData>;\n  MOCK_METHOD(ReadStreamDataRet, readStreamData, ());\n  MOCK_METHOD(GenericApiRet, stopSending, (uint32_t));\n};\n\nclass MockStreamWriteHandle : public WebTransport::StreamWriteHandle {\n public:\n  explicit MockStreamWriteHandle(uint64_t inId)\n      : WebTransport::StreamWriteHandle(inId) {\n  }\n\n  using WriteStreamDataRet =\n      folly::Expected<WebTransport::FCState, WebTransport::ErrorCode>;\n  MOCK_METHOD(WriteStreamDataRet,\n              writeStreamData,\n              (std::unique_ptr<folly::IOBuf>,\n               bool,\n               WebTransport::ByteEventCallback*));\n  MOCK_METHOD(\n      (folly::Expected<folly::SemiFuture<uint64_t>, WebTransport::ErrorCode>),\n      awaitWritable,\n      ());\n\n  MOCK_METHOD(GenericApiRet, resetStream, (uint32_t));\n  MOCK_METHOD(GenericApiRet, setPriority, (quic::PriorityQueue::Priority));\n  MOCK_METHOD(quic::PriorityQueue::Priority, getPriority, (), (const));\n};\n\nclass MockWebTransport : public WebTransport {\n public:\n  MockWebTransport() {\n    EXPECT_CALL(*this, createUniStream()).WillRepeatedly([&] {\n      auto id = nextUniStreamId_;\n      nextUniStreamId_ += 4;\n      auto handle = new testing::NiceMock<MockStreamWriteHandle>(id);\n      writeHandles_.emplace(id, handle);\n      return handle;\n    });\n    EXPECT_CALL(*this, createBidiStream()).WillRepeatedly([&] {\n      auto id = nextBidiStreamId_;\n      nextBidiStreamId_ += 4;\n      BidiStreamHandle handle(\n          {.readHandle = new MockStreamReadHandle(id),\n           .writeHandle = new testing::NiceMock<MockStreamWriteHandle>(id)});\n      readHandles_.emplace(id, handle.readHandle);\n      writeHandles_.emplace(id, handle.writeHandle);\n      return handle;\n    });\n  }\n  using CreateUniStreamRet = folly::Expected<StreamWriteHandle*, ErrorCode>;\n  MOCK_METHOD(CreateUniStreamRet, createUniStream, ());\n\n  using CreateBidiStreamRet = folly::Expected<BidiStreamHandle, ErrorCode>;\n  MOCK_METHOD(CreateBidiStreamRet, createBidiStream, ());\n\n  using AwaitStreamCreditRet = folly::SemiFuture<folly::Unit>;\n  MOCK_METHOD(AwaitStreamCreditRet, awaitUniStreamCredit, ());\n  MOCK_METHOD(AwaitStreamCreditRet, awaitBidiStreamCredit, ());\n\n  using ReadStreamDataRet =\n      folly::Expected<folly::SemiFuture<StreamData>, WebTransport::ErrorCode>;\n  MOCK_METHOD(ReadStreamDataRet, readStreamData, (uint64_t));\n  MOCK_METHOD(MockStreamWriteHandle::WriteStreamDataRet,\n              writeStreamData,\n              (uint64_t,\n               std::unique_ptr<folly::IOBuf>,\n               bool,\n               WebTransport::ByteEventCallback*));\n  MOCK_METHOD(GenericApiRet, resetStream, (uint64_t, uint32_t));\n  MOCK_METHOD(GenericApiRet,\n              setPriority,\n              (uint64_t, quic::PriorityQueue::Priority));\n  MOCK_METHOD(GenericApiRet,\n              setPriorityQueue,\n              (std::unique_ptr<quic::PriorityQueue>),\n              (noexcept));\n  MOCK_METHOD(GenericApiRet, stopSending, (uint64_t, uint32_t));\n  MOCK_METHOD(GenericApiRet, sendDatagram, (std::unique_ptr<folly::IOBuf>));\n  MOCK_METHOD((const folly::SocketAddress&), getLocalAddress, (), (const));\n  MOCK_METHOD(quic::TransportInfo, getTransportInfo, (), (const, override));\n  MOCK_METHOD((const folly::SocketAddress&), getPeerAddress, (), (const));\n  MOCK_METHOD(GenericApiRet, closeSession, (folly::Optional<uint32_t>));\n  MOCK_METHOD(\n      (folly::Expected<folly::SemiFuture<uint64_t>, WebTransport::ErrorCode>),\n      awaitWritable,\n      (uint64_t));\n\n  void cleanupStream(uint64_t id) {\n    auto handleIt = writeHandles_.find(id);\n    if (handleIt != writeHandles_.end()) {\n      delete handleIt->second;\n      writeHandles_.erase(handleIt);\n    }\n  }\n\n  void cleanupReadHandle(uint64_t id) {\n    auto handleIt = readHandles_.find(id);\n    if (handleIt != readHandles_.end()) {\n      delete handleIt->second;\n      readHandles_.erase(handleIt);\n    }\n  }\n\n  uint64_t nextBidiStreamId_{0};\n  uint64_t nextUniStreamId_{2};\n  std::map<uint64_t, StreamHandleBase*> writeHandles_;\n  std::map<uint64_t, StreamHandleBase*> readHandles_;\n};\n\nclass MockWebTransportHandler : public WebTransportHandler {\n public:\n  MOCK_METHOD(void,\n              onNewUniStream,\n              (WebTransport::StreamReadHandle * readHandle),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              onNewBidiStream,\n              (WebTransport::BidiStreamHandle bidiHandle),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              onDatagram,\n              (std::unique_ptr<folly::IOBuf> datagram),\n              (noexcept, override));\n  MOCK_METHOD(void,\n              onSessionEnd,\n              (folly::Optional<uint32_t> error),\n              (noexcept, override));\n  MOCK_METHOD(void, onSessionDrain, (), (noexcept, override));\n};\n\nstruct DummyWtHandler : public WebTransportHandler {\n  DummyWtHandler() = default;\n  void onNewUniStream(\n      WebTransport::StreamReadHandle* readHandle) noexcept override {\n    ctx->peerStreams.push_back({.readHandle = readHandle});\n  }\n  void onNewBidiStream(\n      WebTransport::BidiStreamHandle bidiHandle) noexcept override {\n    ctx->peerStreams.push_back(bidiHandle);\n  }\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept override {\n  }\n  void onSessionEnd(folly::Optional<uint32_t> error) noexcept override {\n    ctx->err = error.value_or(0);\n  }\n  void onSessionDrain() noexcept override {\n  }\n  void onWebTransportSession(\n      std::shared_ptr<WebTransport> wtSession) noexcept override {\n    this->wtSession = std::move(wtSession);\n  }\n\n  static std::unique_ptr<DummyWtHandler> make() {\n    return std::make_unique<DummyWtHandler>();\n  }\n\n  struct Ctx {\n    std::vector<WebTransport::BidiStreamHandle> peerStreams;\n    folly::Optional<uint32_t> err;\n  };\n  std::shared_ptr<Ctx> ctx = std::make_shared<Ctx>();\n  std::shared_ptr<WebTransport> wtSession;\n};\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/QuicWebTransportTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/webtransport/QuicWebTransport.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nusing namespace proxygen;\nusing namespace proxygen::test;\nusing namespace testing;\nusing quic::MockQuicSocketDriver;\n\nclass MockDeliveryCallback : public WebTransport::ByteEventCallback {\n public:\n  MOCK_METHOD(void, onByteEvent, (quic::StreamId, uint64_t), (noexcept));\n\n  MOCK_METHOD(void,\n              onByteEventCanceled,\n              (quic::StreamId, uint64_t),\n              (noexcept));\n};\n\nnamespace {\nconst uint32_t WT_ERROR_1 = 19;\nconst uint32_t WT_ERROR_2 = 77;\n} // namespace\n\nclass QuicWebTransportTest : public Test {\n protected:\n  void SetUp() override {\n    handler_ = std::make_unique<StrictMock<MockWebTransportHandler>>();\n    transport_ = std::make_unique<QuicWebTransport>(socketDriver_.getSocket());\n    transport_->setHandler(handler_.get());\n    socketDriver_.setMaxUniStreams(100);\n  }\n\n  void TearDown() override {\n    if (transport_) {\n      webTransport()->closeSession();\n    }\n  }\n\n  WebTransport* webTransport() {\n    return transport_.get();\n  }\n\n  folly::EventBase eventBase_;\n  MockQuicSocketDriver socketDriver_{\n      &eventBase_,\n      nullptr,\n      nullptr,\n      MockQuicSocketDriver::TransportEnum::SERVER,\n      \"alpn1\"};\n  std::unique_ptr<StrictMock<MockWebTransportHandler>> handler_;\n  std::unique_ptr<QuicWebTransport> transport_;\n};\n\nTEST_F(QuicWebTransportTest, PeerUniStream) {\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([this](WebTransport::StreamReadHandle* readHandle) {\n        readHandle->awaitNextRead(\n            &eventBase_,\n            [](WebTransport::StreamReadHandle*,\n               uint64_t,\n               folly::Try<WebTransport::StreamData> data) {\n              EXPECT_FALSE(data.hasException());\n              EXPECT_EQ(data->data->computeChainDataLength(), 5);\n              EXPECT_TRUE(data->fin);\n            });\n      });\n  socketDriver_.addReadEvent(2, folly::IOBuf::copyBuffer(\"hello\"), true);\n  eventBase_.loop();\n}\n\nTEST_F(QuicWebTransportTest, PeerBidiStream) {\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([this](WebTransport::BidiStreamHandle bidiHandle) {\n        auto wtImpl = dynamic_cast<WebTransportImpl*>(transport_.get());\n        ASSERT_NE(wtImpl, nullptr);\n        wtImpl->onMaxData(1000);\n        bidiHandle.writeHandle->writeStreamData(\n            folly::IOBuf::copyBuffer(\"hello\"), true, nullptr);\n        bidiHandle.readHandle->awaitNextRead(\n            &eventBase_,\n            [](WebTransport::StreamReadHandle*,\n               uint64_t,\n               folly::Try<WebTransport::StreamData> data) {\n              EXPECT_FALSE(data.hasException());\n              EXPECT_TRUE(data->fin);\n            });\n      });\n  socketDriver_.addReadEvent(0, nullptr, true);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[0].writeBuf.chainLength(), 5);\n}\n\nTEST_F(QuicWebTransportTest, NewUniStream) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n  handle.value()->resetStream(WT_ERROR_1);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n}\n\nTEST_F(QuicWebTransportTest, NewBidiStream) {\n  auto handle = webTransport()->createBidiStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle->readHandle->getID();\n  handle->readHandle->stopSending(WT_ERROR_1);\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n  handle->writeHandle->resetStream(WT_ERROR_2);\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_2);\n}\n\nTEST_F(QuicWebTransportTest, Datagram) {\n  webTransport()->sendDatagram(folly::IOBuf::copyBuffer(\"hello\"));\n  EXPECT_EQ(socketDriver_.outDatagrams_.front().chainLength(), 5);\n  socketDriver_.addDatagram(folly::IOBuf::copyBuffer(\"world!\"));\n  EXPECT_CALL(*handler_, onDatagram(_))\n      .WillOnce([](std::unique_ptr<folly::IOBuf> datagram) {\n        EXPECT_EQ(datagram->computeChainDataLength(), 6);\n      });\n  socketDriver_.addDatagramsAvailableReadEvent();\n  eventBase_.loop();\n}\n\nTEST_F(QuicWebTransportTest, OnStopSending) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n  socketDriver_.addStopSending(id, WT_ERROR_1);\n  eventBase_.loopOnce();\n  auto res = handle.value()->writeStreamData(nullptr, true, nullptr);\n  EXPECT_TRUE(res.hasError());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::STOP_SENDING);\n  EXPECT_EQ(handle.value()->exception()->error, WT_ERROR_1);\n}\n\nTEST_F(QuicWebTransportTest, ConnectionError) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  EXPECT_CALL(*handler_, onSessionEnd(_)).WillOnce([](const auto& err) {\n    EXPECT_EQ(*err, WT_ERROR_1);\n  });\n  socketDriver_.deliverConnectionError(\n      quic::QuicError(quic::ApplicationErrorCode(WT_ERROR_1), \"peer close\"));\n  eventBase_.loop();\n}\n\n// Test that onSessionEnd can safely destroy the transport (use-after-free\n// prevention). The handler callback nullifies handler_ before invocation,\n// so destroying `this` inside the callback is safe.\nTEST_F(QuicWebTransportTest, ConnectionErrorHandlerDestroysTransport) {\n  bool handlerCalled = false;\n  EXPECT_CALL(*handler_, onSessionEnd(_))\n      .WillOnce([this, &handlerCalled](const auto& err) {\n        EXPECT_EQ(*err, WT_ERROR_1);\n        handlerCalled = true;\n        // Simulate the handler destroying the transport during the callback.\n        // This would cause a use-after-free if handler_ was accessed after\n        // onSessionEnd returns, but we clear handler_ before calling.\n        transport_.reset();\n      });\n  socketDriver_.deliverConnectionError(\n      quic::QuicError(quic::ApplicationErrorCode(WT_ERROR_1), \"peer close\"));\n  eventBase_.loop();\n  EXPECT_TRUE(handlerCalled);\n  // transport_ is now null because the handler destroyed it\n  EXPECT_EQ(transport_, nullptr);\n}\n\nTEST_F(QuicWebTransportTest, SetPriority) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  socketDriver_.expectSetPriority(\n      handle.value()->getID(), quic::HTTPPriorityQueue::Priority(1, false, 1));\n  handle.value()->setPriority(quic::HTTPPriorityQueue::Priority(1, false, 1));\n  handle.value()->writeStreamData(nullptr, true, nullptr);\n  eventBase_.loop();\n}\n\nTEST_F(QuicWebTransportTest, WriteWithDeliveryCallback) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto mockCallback = std::make_unique<StrictMock<MockDeliveryCallback>>();\n\n  folly::StringPiece data = \"test data\";\n\n  uint64_t expectedStreamId = handle.value()->getID();\n  uint32_t expectedOffset = data.size();\n  EXPECT_CALL(*mockCallback, onByteEvent(expectedStreamId, expectedOffset))\n      .Times(1);\n\n  auto wtImpl = dynamic_cast<WebTransportImpl*>(transport_.get());\n  ASSERT_NE(wtImpl, nullptr);\n  wtImpl->onMaxData(1000);\n\n  handle.value()->writeStreamData(\n      folly::IOBuf::copyBuffer(data), true, mockCallback.get());\n  // The MockQuicSocketDriver automatically simulates the delivery of data\n  // written to the QUIC socket.\n  eventBase_.loop();\n}\n\nTEST_F(QuicWebTransportTest, CloseTransportCancelsReadTokens) {\n  WebTransport::StreamReadHandle* readHandle1 = nullptr;\n  WebTransport::StreamReadHandle* readHandle2 = nullptr;\n  folly::CancellationToken token1;\n  folly::CancellationToken token2;\n\n  // Create two read handles from incoming uni streams\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        readHandle1 = handle;\n        token1 = handle->getCancelToken();\n      })\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        readHandle2 = handle;\n        token2 = handle->getCancelToken();\n      });\n\n  // Trigger two incoming uni streams\n  socketDriver_.addReadEvent(2, nullptr, false);\n  socketDriver_.addReadEvent(6, nullptr, false);\n  eventBase_.loopOnce();\n\n  ASSERT_NE(readHandle1, nullptr);\n  ASSERT_NE(readHandle2, nullptr);\n  EXPECT_FALSE(token1.isCancellationRequested());\n  EXPECT_FALSE(token2.isCancellationRequested());\n\n  // Invoke read on the first handle (will be pending since no data)\n  auto future1 = readHandle1->readStreamData();\n  bool readErrorReceived = false;\n\n  std::move(future1)\n      .via(&eventBase_)\n      .thenTry([&](folly::Try<WebTransport::StreamData> result) {\n        EXPECT_TRUE(result.hasException());\n        // Verify the exception is a WebTransport::Exception\n        auto* ex = result.tryGetExceptionObject<WebTransport::Exception>();\n        ASSERT_NE(ex, nullptr);\n        // Verify the error code matches the connection error\n        // This is broken behavior but it's what mvfst currently does\n        EXPECT_EQ(ex->error, WT_ERROR_1);\n        readErrorReceived = true;\n      });\n\n  // Close the transport by delivering a connection error\n  EXPECT_CALL(*handler_, onSessionEnd(_));\n  socketDriver_.deliverConnectionError(quic::QuicError(\n      quic::ApplicationErrorCode(WT_ERROR_1), \"connection close\"));\n  eventBase_.loop();\n\n  // Verify both tokens are cancelled\n  EXPECT_TRUE(token1.isCancellationRequested());\n  EXPECT_TRUE(token2.isCancellationRequested());\n\n  // Verify the pending read received an error\n  EXPECT_TRUE(readErrorReceived);\n}\n\nTEST_F(QuicWebTransportTest, DestructorTerminatesOpenStreams) {\n  WebTransport::StreamReadHandle* uniReadHandle = nullptr;\n  WebTransport::StreamReadHandle* bidiReadHandle = nullptr;\n  WebTransport::StreamWriteHandle* bidiWriteHandle = nullptr;\n  WebTransport::StreamWriteHandle* uniWriteHandle = nullptr;\n\n  // Create an incoming uni stream\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        uniReadHandle = handle;\n      });\n  socketDriver_.addReadEvent(2, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(uniReadHandle, nullptr);\n\n  // Create an incoming bidi stream\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        bidiReadHandle = handle.readHandle;\n        bidiWriteHandle = handle.writeHandle;\n      });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(bidiReadHandle, nullptr);\n  ASSERT_NE(bidiWriteHandle, nullptr);\n\n  // Create an outgoing uni stream\n  auto uniStreamRes = webTransport()->createUniStream();\n  ASSERT_TRUE(uniStreamRes.hasValue());\n  uniWriteHandle = uniStreamRes.value();\n\n  // Start pending read operations\n  bool uniReadErrorReceived = false;\n  bool bidiReadErrorReceived = false;\n\n  // When transport is deleted with no code passed to close, it passes 0 to the\n  // transport. Currently the transport echoes this error back to all open\n  // streams, hence ex->error is 0.\n  auto uniReadFuture = uniReadHandle->readStreamData();\n  std::move(uniReadFuture)\n      .via(&eventBase_)\n      .thenTry([&](folly::Try<WebTransport::StreamData> result) {\n        EXPECT_TRUE(result.hasException());\n        auto* ex = result.tryGetExceptionObject<WebTransport::Exception>();\n        ASSERT_NE(ex, nullptr);\n        EXPECT_EQ(ex->error, 0);\n        uniReadErrorReceived = true;\n      });\n\n  auto bidiReadFuture = bidiReadHandle->readStreamData();\n  std::move(bidiReadFuture)\n      .via(&eventBase_)\n      .thenTry([&](folly::Try<WebTransport::StreamData> result) {\n        EXPECT_TRUE(result.hasException());\n        auto* ex = result.tryGetExceptionObject<WebTransport::Exception>();\n        ASSERT_NE(ex, nullptr);\n        EXPECT_EQ(ex->error, 0);\n        bidiReadErrorReceived = true;\n      });\n\n  // Store cancellation tokens before destroying transport\n  auto uniReadToken = uniReadHandle->getCancelToken();\n  auto bidiReadToken = bidiReadHandle->getCancelToken();\n  auto bidiWriteToken = bidiWriteHandle->getCancelToken();\n  auto uniWriteToken = uniWriteHandle->getCancelToken();\n\n  // Verify cancellation tokens are not yet cancelled\n  EXPECT_FALSE(uniReadToken.isCancellationRequested());\n  EXPECT_FALSE(bidiReadToken.isCancellationRequested());\n  EXPECT_FALSE(bidiWriteToken.isCancellationRequested());\n  EXPECT_FALSE(uniWriteToken.isCancellationRequested());\n\n  // Destroy the transport - this triggers terminateSessionStreams in the\n  // destructor which calls stopReadingWebTransportIngress with an error code.\n  // The mock socket will transition stream states to ERROR, allowing clean\n  // shutdown.\n  transport_.reset();\n  eventBase_.loop();\n\n  // Verify all cancellation tokens are now cancelled\n  EXPECT_TRUE(uniReadToken.isCancellationRequested());\n  EXPECT_TRUE(bidiReadToken.isCancellationRequested());\n  EXPECT_TRUE(bidiWriteToken.isCancellationRequested());\n  EXPECT_TRUE(uniWriteToken.isCancellationRequested());\n\n  // Verify pending reads received errors\n  EXPECT_TRUE(uniReadErrorReceived);\n  EXPECT_TRUE(bidiReadErrorReceived);\n}\n\n// TODO:\n//\n// new streams with no handler\n// receive connection end\n// create bidi fails\n// create uni fails\n// writeChain fails\n// write blocks\n// reset fails\n// pause/resume\n// setReadCallback fails\n// sendDatagram fails\n// close with error\n// await uni/bidi stream credit\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/QuicWtSessionTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/webtransport/QuicWtSession.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n#include <quic/state/StateData.h>\n\nusing namespace proxygen;\nusing namespace proxygen::test;\nusing namespace testing;\nusing quic::MockQuicSocketDriver;\n\nnamespace {\nconstexpr uint32_t WT_ERROR_1 = 19;\nconstexpr uint32_t WT_ERROR_2 = 77;\nconstexpr uint64_t kMaxWtIngressBuf = 65'535;\n\nclass MockConnectionState : public quic::QuicConnectionStateBase {\n public:\n  explicit MockConnectionState(quic::QuicNodeType type)\n      : quic::QuicConnectionStateBase(type) {\n  }\n};\n\nclass MockDeliveryCallback : public WebTransport::ByteEventCallback {\n public:\n  MOCK_METHOD(void, onByteEvent, (quic::StreamId, uint64_t), (noexcept));\n\n  MOCK_METHOD(void,\n              onByteEventCanceled,\n              (quic::StreamId, uint64_t),\n              (noexcept));\n};\n} // namespace\n\nclass QuicWtSessionTest : public Test {\n protected:\n  void SetUp() override {\n    EXPECT_CALL(*socketDriver_.getSocket(), getState())\n        .WillRepeatedly(Return(&mockState_));\n\n    auto handler = std::make_unique<StrictMock<MockWebTransportHandler>>();\n    handler_ = handler.get();\n    session_ = std::make_unique<QuicWtSession>(socketDriver_.getSocket(),\n                                               std::move(handler));\n    socketDriver_.setMaxUniStreams(100);\n  }\n\n  void TearDown() override {\n    if (session_) {\n      webTransport()->closeSession();\n    }\n  }\n\n  WebTransport* webTransport() {\n    return session_.get();\n  }\n\n  folly::EventBase eventBase_;\n  MockConnectionState mockState_{quic::QuicNodeType::Server};\n  MockQuicSocketDriver socketDriver_{\n      &eventBase_,\n      nullptr,\n      nullptr,\n      MockQuicSocketDriver::TransportEnum::SERVER,\n      \"alpn1\"};\n  StrictMock<MockWebTransportHandler>* handler_{nullptr};\n  std::unique_ptr<QuicWtSession> session_;\n};\n\nTEST_F(QuicWtSessionTest, PeerUniStream) {\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([this](WebTransport::StreamReadHandle* readHandle) {\n        readHandle->awaitNextRead(\n            &eventBase_,\n            [](WebTransport::StreamReadHandle*,\n               uint64_t,\n               folly::Try<WebTransport::StreamData> data) {\n              EXPECT_FALSE(data.hasException());\n              EXPECT_EQ(data->data->computeChainDataLength(), 5);\n              EXPECT_TRUE(data->fin);\n            });\n      });\n  socketDriver_.addReadEvent(2, folly::IOBuf::copyBuffer(\"hello\"), true);\n  eventBase_.loop();\n}\n\nTEST_F(QuicWtSessionTest, PeerBidiStream) {\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([this](WebTransport::BidiStreamHandle bidiHandle) {\n        bidiHandle.writeHandle->writeStreamData(\n            folly::IOBuf::copyBuffer(\"hello\"), true, nullptr);\n        bidiHandle.readHandle->awaitNextRead(\n            &eventBase_,\n            [](WebTransport::StreamReadHandle*,\n               uint64_t,\n               folly::Try<WebTransport::StreamData> data) {\n              EXPECT_FALSE(data.hasException());\n              EXPECT_TRUE(data->fin);\n            });\n      });\n  socketDriver_.addReadEvent(0, nullptr, true);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[0].writeBuf.chainLength(), 5);\n}\n\nTEST_F(QuicWtSessionTest, NewUniStream) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n  handle.value()->resetStream(WT_ERROR_1);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n}\n\nTEST_F(QuicWtSessionTest, NewBidiStream) {\n  auto handle = webTransport()->createBidiStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle->readHandle->getID();\n  handle->readHandle->stopSending(WT_ERROR_1);\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n  handle->writeHandle->resetStream(WT_ERROR_2);\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_2);\n}\n\nTEST_F(QuicWtSessionTest, AwaitUniStreamCredit) {\n  // credit already available -> immediate future\n  auto fastFut = webTransport()->awaitUniStreamCredit();\n  EXPECT_TRUE(fastFut.isReady());\n\n  // set max uni streams to 0 to exhaust stream credit\n  socketDriver_.setMaxUniStreams(0);\n\n  // try creating a uni stream when no credit is available, should fail\n  auto res = webTransport()->createUniStream();\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  auto fut = webTransport()->awaitUniStreamCredit();\n  EXPECT_FALSE(fut.isReady());\n\n  // calling with 0 streams available should not fulfill the promise\n  socketDriver_.getSocket()->connCb_->onUnidirectionalStreamsAvailable(0);\n  EXPECT_FALSE(fut.isReady());\n\n  // set max uni streams to make credit available\n  socketDriver_.setMaxUniStreams(1);\n  EXPECT_TRUE(fut.isReady());\n  std::move(fut).getTry();\n\n  // now that we have credit, creating a uni stream should succeed\n  auto res2 = webTransport()->createUniStream();\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_NE(res2.value(), nullptr);\n}\n\nTEST_F(QuicWtSessionTest, AwaitBidiStreamCredit) {\n  // credit already available -> immediate future\n  auto fastFut = webTransport()->awaitBidiStreamCredit();\n  EXPECT_TRUE(fastFut.isReady());\n\n  // set max bidi streams to 0 to exhaust stream credit\n  socketDriver_.setMaxBidiStreams(0);\n\n  // try creating a bidi stream when no credit is available, should fail\n  auto res = webTransport()->createBidiStream();\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::STREAM_CREATION_ERROR);\n  auto fut = webTransport()->awaitBidiStreamCredit();\n  EXPECT_FALSE(fut.isReady());\n\n  // calling with 0 streams available should not fulfill the promise\n  socketDriver_.getSocket()->connCb_->onBidirectionalStreamsAvailable(0);\n  EXPECT_FALSE(fut.isReady());\n\n  // set max bidi streams to make credit available\n  socketDriver_.setMaxBidiStreams(1);\n  EXPECT_TRUE(fut.isReady());\n  std::move(fut).getTry();\n\n  // now that we have credit, creating a bidi stream should succeed\n  auto res2 = webTransport()->createBidiStream();\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_NE(res2->readHandle, nullptr);\n  EXPECT_NE(res2->writeHandle, nullptr);\n}\n\nTEST_F(QuicWtSessionTest, WriteStreamData) {\n  // id=2 is client-initiated uni, so it's not egress for the server\n  constexpr uint64_t kClientInitiatedUniId = 2;\n\n  auto result = webTransport()->writeStreamData(\n      kClientInitiatedUniId, folly::IOBuf::copyBuffer(\"test\"), true, nullptr);\n  EXPECT_FALSE(result.hasValue());\n  EXPECT_EQ(result.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);\n\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n\n  auto data = folly::IOBuf::copyBuffer(\"hello world\");\n  auto res =\n      webTransport()->writeStreamData(id, std::move(data), true, nullptr);\n  EXPECT_TRUE(res.hasValue());\n\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[id].writeBuf.chainLength(), 11);\n}\n\nTEST_F(QuicWtSessionTest, ReadStreamData) {\n  // id=3 is server-initiated uni, so it's not ingress for the server\n  constexpr uint64_t kServerInitiatedUniId = 3;\n\n  auto res = webTransport()->readStreamData(kServerInitiatedUniId);\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);\n\n  // simulate peer opening a bidi stream\n  WebTransport::StreamReadHandle* readHandle = nullptr;\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        readHandle = handle.readHandle;\n      });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(readHandle, nullptr);\n\n  auto id = readHandle->getID();\n  auto readRes = webTransport()->readStreamData(id);\n  EXPECT_TRUE(readRes.hasValue());\n\n  auto data = folly::IOBuf::copyBuffer(\"test data\");\n  socketDriver_.addReadEvent(id, std::move(data), true);\n  eventBase_.loopOnce();\n\n  EXPECT_TRUE(readRes.value().isReady());\n  auto streamData = std::move(readRes.value()).getTry();\n  EXPECT_TRUE(streamData.hasValue());\n  EXPECT_EQ(streamData.value().data->computeChainDataLength(), 9);\n  EXPECT_TRUE(streamData.value().fin);\n}\n\nTEST_F(QuicWtSessionTest, Datagram) {\n  // send multiple datagrams\n  webTransport()->sendDatagram(folly::IOBuf::copyBuffer(\"out1\"));\n  webTransport()->sendDatagram(folly::IOBuf::copyBuffer(\"out2\"));\n  EXPECT_EQ(socketDriver_.outDatagrams_.size(), 2);\n\n  // receive multiple datagrams\n  socketDriver_.addDatagram(folly::IOBuf::copyBuffer(\"in1\"));\n  socketDriver_.addDatagram(folly::IOBuf::copyBuffer(\"in2\"));\n\n  std::vector<std::string> received;\n  EXPECT_CALL(*handler_, onDatagram(_))\n      .Times(2)\n      .WillRepeatedly([&](std::unique_ptr<folly::IOBuf> datagram) {\n        received.push_back(datagram->toString());\n      });\n  socketDriver_.addDatagramsAvailableReadEvent();\n  eventBase_.loop();\n\n  ASSERT_EQ(received.size(), 2);\n  EXPECT_EQ(received[0], \"in1\");\n  EXPECT_EQ(received[1], \"in2\");\n\n  // sending a datagram that exceeds size limit should fail\n  auto maxSize = socketDriver_.getSocket()->getDatagramSizeLimit();\n  auto largeDatagram = folly::IOBuf::create(maxSize + 100);\n  largeDatagram->append(maxSize + 100);\n  auto res = webTransport()->sendDatagram(std::move(largeDatagram));\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::GENERIC_ERROR);\n\n  // simulate datagram read failure: close only the connection's write state\n  // so readDatagramBufs() returns CONNECTION_CLOSED, while read state stays\n  // OPEN to allow the datagram available event to fire.\n  // closeSession(kInternalError) will be called, which closes the socket.\n  socketDriver_.streams_[quic::kConnectionStreamId].writeState =\n      quic::MockQuicSocketDriver::StateEnum::CLOSED;\n  socketDriver_.addDatagramsAvailableReadEvent();\n  eventBase_.loop();\n  session_.reset();\n}\n\nTEST_F(QuicWtSessionTest, StopSending) {\n  // id=3 is server-initiated uni, so it's not ingress for the server\n  constexpr uint64_t kServerInitiatedUniId = 3;\n\n  auto res = webTransport()->stopSending(kServerInitiatedUniId, WT_ERROR_1);\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);\n\n  // simulate peer opening a bidi stream\n  WebTransport::StreamReadHandle* readHandle = nullptr;\n\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        readHandle = handle.readHandle;\n      });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(readHandle, nullptr);\n\n  // we call stop sending\n  auto id = readHandle->getID();\n  auto res2 = webTransport()->stopSending(id, WT_ERROR_1);\n  EXPECT_TRUE(res2.hasValue());\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n\n  // receive stop sending\n  auto handle = webTransport()->createBidiStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id2 = handle->writeHandle->getID();\n  auto* writeHandle = handle->writeHandle;\n  EXPECT_EQ(writeHandle->exception(), nullptr);\n\n  // trigger stop sending to cancel the write handle and set exception\n  socketDriver_.addStopSending(id2, WT_ERROR_1);\n  eventBase_.loopOnce();\n\n  EXPECT_NE(writeHandle->exception(), nullptr);\n  EXPECT_EQ(writeHandle->exception()->error, WT_ERROR_1);\n}\n\nTEST_F(QuicWtSessionTest, ResetStream) {\n  // id=2 is client-initiated uni, so it's not egress for the server\n  constexpr uint64_t kClientInitiatedUniId = 2;\n\n  auto res = webTransport()->resetStream(kClientInitiatedUniId, WT_ERROR_1);\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);\n\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n\n  auto res2 = webTransport()->resetStream(id, WT_ERROR_1);\n  EXPECT_TRUE(res2.hasValue());\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[id].error, WT_ERROR_1);\n}\n\nTEST_F(QuicWtSessionTest, SetPriority) {\n  auto handle = webTransport()->createUniStream();\n  ASSERT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n\n  quic::HTTPPriorityQueue::Priority priority(/*u=*/3,\n                                             /*i=*/true,\n                                             /*o=*/100);\n  auto result = webTransport()->setPriority(id, priority);\n  EXPECT_TRUE(result.hasValue());\n\n  auto pri = socketDriver_.getSocket()->getStreamPriority(id);\n  ASSERT_TRUE(pri.has_value());\n  quic::HTTPPriorityQueue::Priority httpPriority(*pri);\n  EXPECT_EQ(httpPriority->urgency, 3);\n  EXPECT_EQ(httpPriority->incremental, true);\n}\n\nTEST_F(QuicWtSessionTest, AwaitWritable) {\n  // Note: This test verifies basic functionality but doesn't test the blocking\n  // scenario where awaitWritable returns a \"not ready\" future.\n  // MockQuicSocketDriver auto-flushes writes immediately, making it difficult\n  // to test the blocked state. The blocking behavior is tested at the\n  // WtStreamManager level (see WtStreamManagerTest's AwaitWritableTest).\n\n  // id=2 is client-initiated uni, so it's not egress for the server\n  constexpr uint64_t kClientInitiatedUniId = 2;\n\n  auto res = webTransport()->awaitWritable(kClientInitiatedUniId);\n  EXPECT_FALSE(res.hasValue());\n  EXPECT_EQ(res.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);\n\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto streamId = handle.value()->getID();\n\n  auto res2 = webTransport()->awaitWritable(streamId);\n  EXPECT_TRUE(res2.hasValue());\n\n  // verify the future completes successfully with available bytes\n  EXPECT_TRUE(res2.value().isReady());\n  auto bytes = std::move(res2).value().getTry();\n  EXPECT_TRUE(bytes.hasValue());\n  EXPECT_GT(bytes.value(), 0);\n}\n\nTEST_F(QuicWtSessionTest, ConnectionError) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  EXPECT_CALL(*handler_, onSessionEnd(_)).WillOnce([](const auto& err) {\n    EXPECT_EQ(*err, WT_ERROR_1);\n  });\n  socketDriver_.deliverConnectionError(\n      quic::QuicError(quic::ApplicationErrorCode(WT_ERROR_1), \"peer close\"));\n  eventBase_.loop();\n}\n\nTEST_F(QuicWtSessionTest, ConnectionEnd) {\n  EXPECT_CALL(*handler_, onSessionEnd(_)).WillOnce([](const auto& err) {\n    EXPECT_FALSE(err.has_value());\n  });\n  socketDriver_.deliverConnectionError(\n      quic::QuicError(quic::LocalErrorCode::NO_ERROR, \"clean close\"));\n  eventBase_.loop();\n}\n\nTEST_F(QuicWtSessionTest, DeliveryCallback) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  auto mockCallback = std::make_unique<StrictMock<MockDeliveryCallback>>();\n  folly::StringPiece data = \"test data\";\n\n  uint64_t expectedId = handle.value()->getID();\n  uint32_t expectedOffset = data.size();\n  EXPECT_CALL(*mockCallback, onByteEvent(expectedId, expectedOffset)).Times(1);\n\n  handle.value()->writeStreamData(\n      folly::IOBuf::copyBuffer(data), true, mockCallback.get());\n  // The MockQuicSocketDriver automatically simulates the delivery of data\n  // written to the QUIC socket.\n  eventBase_.loop();\n}\n\nTEST_F(QuicWtSessionTest, CloseTransportCancelsReadTokens) {\n  WebTransport::StreamReadHandle* readHandle1 = nullptr;\n  WebTransport::StreamReadHandle* readHandle2 = nullptr;\n  folly::CancellationToken token1;\n  folly::CancellationToken token2;\n\n  // create two read handles from incoming uni streams\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        readHandle1 = handle;\n        token1 = handle->getCancelToken();\n      })\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        readHandle2 = handle;\n        token2 = handle->getCancelToken();\n      });\n\n  // trigger two incoming uni streams\n  socketDriver_.addReadEvent(2, nullptr, false);\n  socketDriver_.addReadEvent(6, nullptr, false);\n  eventBase_.loopOnce();\n\n  ASSERT_NE(readHandle1, nullptr);\n  ASSERT_NE(readHandle2, nullptr);\n  EXPECT_FALSE(token1.isCancellationRequested());\n  EXPECT_FALSE(token2.isCancellationRequested());\n\n  // invoke read on the first handle (will be pending since no data)\n  auto fut = readHandle1->readStreamData();\n\n  // close the transport by delivering a connection error\n  EXPECT_CALL(*handler_, onSessionEnd(_));\n  socketDriver_.deliverConnectionError(quic::QuicError(\n      quic::ApplicationErrorCode(WT_ERROR_1), \"connection close\"));\n  eventBase_.loop();\n\n  // cancellation tokens should now all be cancelled\n  EXPECT_TRUE(token1.isCancellationRequested());\n  EXPECT_TRUE(token2.isCancellationRequested());\n  EXPECT_TRUE(fut.isReady());\n  auto readResult = std::move(fut).getTry();\n  EXPECT_TRUE(readResult.hasException());\n  auto* ex = readResult.tryGetExceptionObject<WebTransport::Exception>();\n  ASSERT_NE(ex, nullptr);\n  EXPECT_EQ(ex->error, WT_ERROR_1);\n}\n\nTEST_F(QuicWtSessionTest, DestructorTerminatesOpenStreams) {\n  WebTransport::StreamReadHandle* uniReadHandle = nullptr;\n  WebTransport::StreamReadHandle* bidiReadHandle = nullptr;\n  WebTransport::StreamWriteHandle* bidiWriteHandle = nullptr;\n  WebTransport::StreamWriteHandle* uniWriteHandle = nullptr;\n\n  // create an incoming uni stream\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce([&](WebTransport::StreamReadHandle* handle) {\n        uniReadHandle = handle;\n      });\n  socketDriver_.addReadEvent(2, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(uniReadHandle, nullptr);\n\n  // create an incoming bidi stream\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        bidiReadHandle = handle.readHandle;\n        bidiWriteHandle = handle.writeHandle;\n      });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(bidiReadHandle, nullptr);\n  ASSERT_NE(bidiWriteHandle, nullptr);\n\n  // create an outgoing uni stream\n  auto uniStreamRes = webTransport()->createUniStream();\n  ASSERT_TRUE(uniStreamRes.hasValue());\n  uniWriteHandle = uniStreamRes.value();\n\n  // start pending read operations\n  auto uniReadFuture = uniReadHandle->readStreamData();\n  auto bidiReadFuture = bidiReadHandle->readStreamData();\n\n  // store cancellation tokens before destroying transport\n  auto uniReadToken = uniReadHandle->getCancelToken();\n  auto bidiReadToken = bidiReadHandle->getCancelToken();\n  auto bidiWriteToken = bidiWriteHandle->getCancelToken();\n  auto uniWriteToken = uniWriteHandle->getCancelToken();\n\n  // verify cancellation tokens are not yet cancelled\n  EXPECT_FALSE(uniReadToken.isCancellationRequested());\n  EXPECT_FALSE(bidiReadToken.isCancellationRequested());\n  EXPECT_FALSE(bidiWriteToken.isCancellationRequested());\n  EXPECT_FALSE(uniWriteToken.isCancellationRequested());\n\n  session_.reset();\n  eventBase_.loop();\n\n  // cancellation tokens should now all be cancelled\n  EXPECT_TRUE(uniReadToken.isCancellationRequested());\n  EXPECT_TRUE(bidiReadToken.isCancellationRequested());\n  EXPECT_TRUE(bidiWriteToken.isCancellationRequested());\n  EXPECT_TRUE(uniWriteToken.isCancellationRequested());\n\n  EXPECT_TRUE(uniReadFuture.isReady());\n  auto uniReadResult = std::move(uniReadFuture).getTry();\n  EXPECT_TRUE(uniReadResult.hasException());\n  auto* uniEx = uniReadResult.tryGetExceptionObject<WebTransport::Exception>();\n  ASSERT_NE(uniEx, nullptr);\n  EXPECT_EQ(uniEx->error, 0);\n\n  EXPECT_TRUE(bidiReadFuture.isReady());\n  auto bidiReadResult = std::move(bidiReadFuture).getTry();\n  EXPECT_TRUE(bidiReadResult.hasException());\n  auto* bidiEx =\n      bidiReadResult.tryGetExceptionObject<WebTransport::Exception>();\n  ASSERT_NE(bidiEx, nullptr);\n  EXPECT_EQ(bidiEx->error, 0);\n}\n\nTEST_F(QuicWtSessionTest, ReadError) {\n  WebTransport::StreamReadHandle* readHandle = nullptr;\n  EXPECT_CALL(*handler_, onNewUniStream(_))\n      .WillOnce(\n          [&](WebTransport::StreamReadHandle* handle) { readHandle = handle; });\n  socketDriver_.addReadEvent(2, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(readHandle, nullptr);\n\n  // set up a pending read on the stream\n  auto readFut = readHandle->readStreamData();\n\n  // deliver a stream-level read error\n  socketDriver_.addReadError(\n      2, quic::QuicErrorCode(quic::ApplicationErrorCode(WT_ERROR_2)));\n  eventBase_.loopOnce();\n\n  // the read future should resolve with the error\n  EXPECT_TRUE(readFut.isReady());\n  auto result = std::move(readFut).getTry();\n  EXPECT_TRUE(result.hasException());\n  auto* ex = result.tryGetExceptionObject<WebTransport::Exception>();\n  ASSERT_NE(ex, nullptr);\n  EXPECT_EQ(ex->error, WT_ERROR_2);\n}\n\nTEST_F(QuicWtSessionTest, ConnectionEndWithError) {\n  auto handle = webTransport()->createUniStream();\n  EXPECT_TRUE(handle.hasValue());\n  EXPECT_CALL(*handler_, onSessionEnd(_)).WillOnce([](const auto& err) {\n    EXPECT_TRUE(err.has_value());\n    EXPECT_EQ(*err, WT_ERROR_1);\n  });\n  // directly invoke the onConnectionEnd(QuicError) overload via the\n  // connection callback, rather than going through deliverConnectionError\n  socketDriver_.getSocket()->connCb_->onConnectionEnd(\n      quic::QuicError(quic::ApplicationErrorCode(WT_ERROR_1), \"peer close\"));\n  eventBase_.loop();\n  session_.reset();\n}\n\nTEST_F(QuicWtSessionTest, ReadAvailableReadFails) {\n  WebTransport::StreamReadHandle* readHandle = nullptr;\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        readHandle = handle.readHandle;\n      });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(readHandle, nullptr);\n\n  auto id = readHandle->getID();\n  socketDriver_.setReadError(id);\n  // trigger readAvailable: read() will fail, session should continue\n  socketDriver_.addReadEvent(id, folly::IOBuf::copyBuffer(\"data\"), false);\n  eventBase_.loopOnce();\n  auto uniHandle = webTransport()->createUniStream();\n  EXPECT_TRUE(uniHandle.hasValue());\n}\n\nTEST_F(QuicWtSessionTest, WriteFlowControlBlocked) {\n  auto handle = webTransport()->createUniStream();\n  ASSERT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n\n  // block writes by setting stream flow control window to 0\n  socketDriver_.setStreamFlowControlWindow(id, 0);\n\n  // write data: eventsAvailable() will see maxData==0 and call\n  // notifyPendingWriteOnStream instead of writing\n  handle.value()->writeStreamData(\n      folly::IOBuf::copyBuffer(\"blocked data\"), false, nullptr);\n  eventBase_.loopOnce();\n\n  // data should NOT have been written to the socket\n  EXPECT_EQ(socketDriver_.streams_[id].writeBuf.chainLength(), 0);\n\n  // unblock by restoring flow control window: this triggers\n  // onStreamWriteReady() which re-inserts into priority queue and calls\n  // eventsAvailable()\n  socketDriver_.setStreamFlowControlWindow(id, 65536);\n  eventBase_.loop();\n\n  // data should now be written\n  EXPECT_EQ(socketDriver_.streams_[id].writeBuf.chainLength(), 12);\n}\n\nTEST_F(QuicWtSessionTest, WriteChainFails) {\n  // use a bidi stream so the read handle keeps the stream entry alive\n  // after resetStream (uni stream would be freed immediately)\n  WebTransport::BidiStreamHandle bidiHandle{nullptr, nullptr};\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce(\n          [&](WebTransport::BidiStreamHandle handle) { bidiHandle = handle; });\n  socketDriver_.addReadEvent(0, nullptr, false);\n  eventBase_.loopOnce();\n  ASSERT_NE(bidiHandle.writeHandle, nullptr);\n  auto id = bidiHandle.writeHandle->getID();\n\n  // block writes via flow control so data queues in WtStreamManager\n  socketDriver_.setStreamFlowControlWindow(id, 0);\n\n  bidiHandle.writeHandle->writeStreamData(\n      folly::IOBuf::copyBuffer(\"will fail\"), true, nullptr);\n  eventBase_.loopOnce();\n\n  // data is queued but not written (flow-control blocked)\n  EXPECT_EQ(socketDriver_.streams_[id].writeBuf.chainLength(), 0);\n\n  // poison the stream write state, then resume flow control and fire callback\n  auto& stream = socketDriver_.streams_[id];\n  stream.flowControlWindow = 65536;\n  stream.writeState = quic::MockQuicSocketDriver::StateEnum::ERROR;\n\n  // directly fire onStreamWriteReady to trigger\n  // eventsAvailable() -> writeChain fails -> resetStream(kInternalError)\n  auto pendingCb = stream.pendingWriteCb;\n  stream.pendingWriteCb.stream = nullptr;\n  if (pendingCb.stream) {\n    pendingCb.stream->onStreamWriteReady(id, 65536);\n  }\n  eventBase_.loop();\n\n  // the stream should have been reset with an error\n  EXPECT_EQ(socketDriver_.streams_[id].writeState,\n            quic::MockQuicSocketDriver::StateEnum::ERROR);\n}\n\nTEST_F(QuicWtSessionTest, StreamWriteError) {\n  auto handle = webTransport()->createUniStream();\n  ASSERT_TRUE(handle.hasValue());\n  auto id = handle.value()->getID();\n\n  // block writes by setting stream flow control window to 0\n  socketDriver_.setStreamFlowControlWindow(id, 0);\n\n  // write data to trigger the flow-control-blocked path\n  // eventsAvailable() sees maxData==0, calls notifyPendingWriteOnStream\n  handle.value()->writeStreamData(\n      folly::IOBuf::copyBuffer(\"blocked\"), false, nullptr);\n  eventBase_.loopOnce();\n\n  // the stream now has a pending write callback registered\n  // deliver a write error to trigger onStreamWriteError()\n  socketDriver_.deliverWriteError(\n      id,\n      socketDriver_.streams_[id],\n      quic::QuicErrorCode(quic::ApplicationErrorCode(WT_ERROR_1)));\n  eventBase_.loop();\n\n  // the stream should have been reset with kInternalError\n  EXPECT_EQ(socketDriver_.streams_[id].writeState,\n            quic::MockQuicSocketDriver::StateEnum::ERROR);\n}\n\nTEST_F(QuicWtSessionTest, IngressBackpressure) {\n  // simulate peer opening two bidi streams\n  WebTransport::StreamReadHandle* readHandle1 = nullptr;\n  WebTransport::StreamReadHandle* readHandle2 = nullptr;\n\n  EXPECT_CALL(*handler_, onNewBidiStream(_))\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        readHandle1 = handle.readHandle;\n      })\n      .WillOnce([&](WebTransport::BidiStreamHandle handle) {\n        readHandle2 = handle.readHandle;\n      });\n\n  socketDriver_.addReadEvent(0, nullptr, false);\n  socketDriver_.addReadEvent(4, nullptr, false);\n  eventBase_.loopOnce();\n\n  ASSERT_NE(readHandle1, nullptr);\n  ASSERT_NE(readHandle2, nullptr);\n\n  auto streamId1 = readHandle1->getID();\n  auto streamId2 = readHandle2->getID();\n\n  // fill id=1 buffer to trigger pause when bufferedBytes() >=\n  // kMaxWTIngressBuf\n  auto buf1 = folly::IOBuf::create(kMaxWtIngressBuf);\n  buf1->append(kMaxWtIngressBuf);\n  socketDriver_.addReadEvent(streamId1, std::move(buf1), false);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[streamId1].readState,\n            MockQuicSocketDriver::PAUSED);\n  EXPECT_EQ(socketDriver_.streams_[streamId2].readState,\n            MockQuicSocketDriver::OPEN); // id=2 should be independent of\n                                         // id=1\n\n  // fill id=2 buffer to trigger pause\n  auto buf2 = folly::IOBuf::create(kMaxWtIngressBuf);\n  buf2->append(kMaxWtIngressBuf);\n  socketDriver_.addReadEvent(streamId2, std::move(buf2), false);\n  eventBase_.loop();\n  EXPECT_EQ(socketDriver_.streams_[streamId1].readState,\n            MockQuicSocketDriver::PAUSED);\n  EXPECT_EQ(socketDriver_.streams_[streamId2].readState,\n            MockQuicSocketDriver::PAUSED);\n\n  // resume id=1 by reading\n  auto fut1 = readHandle1->readStreamData();\n  EXPECT_TRUE(fut1.isReady());\n  std::move(fut1).via(&eventBase_).thenValue([](WebTransport::StreamData) {});\n  eventBase_.loop();\n\n  // id=1 resumed, id=2 still paused\n  EXPECT_EQ(socketDriver_.streams_[streamId1].readState,\n            MockQuicSocketDriver::OPEN);\n  EXPECT_EQ(socketDriver_.streams_[streamId2].readState,\n            MockQuicSocketDriver::PAUSED);\n}\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/WebTransportAPITest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Singleton.h>\n#include <folly/io/async/EventBase.h>\n#include <proxygen/lib/http/webtransport/QuicWebTransport.h>\n#include <proxygen/lib/http/webtransport/test/Mocks.h>\n\nnamespace proxygen::test {\n\nTEST(WebTransportTest, ErrorCodes) {\n  EXPECT_EQ(WebTransport::toHTTPErrorCode(0), WebTransport::kFirstErrorCode);\n  EXPECT_EQ(WebTransport::toHTTPErrorCode(std::numeric_limits<uint32_t>::max()),\n            WebTransport::kLastErrorCode);\n  EXPECT_EQ(WebTransport::toApplicationErrorCode(0).error(),\n            WebTransport::ErrorCode::GENERIC_ERROR);\n  EXPECT_EQ(WebTransport::toApplicationErrorCode(WebTransport::kFirstErrorCode)\n                .value(),\n            0);\n  EXPECT_EQ(WebTransport::toApplicationErrorCode(WebTransport::kLastErrorCode)\n                .value(),\n            std::numeric_limits<uint32_t>::max());\n}\n\nTEST(WebTransportTest, AwaitNextRead) {\n  folly::SingletonVault::singleton()->registrationComplete();\n  folly::EventBase evb;\n  MockStreamReadHandle readHandle(0);\n  EXPECT_CALL(readHandle, readStreamData()).WillOnce(testing::Invoke([] {\n    return folly::makeFuture(\n        WebTransport::StreamData({.data = nullptr, .fin = true}));\n  }));\n\n  readHandle.awaitNextRead(\n      &evb,\n      [&](WebTransport::StreamReadHandle* handle,\n          uint64_t,\n          folly::Try<WebTransport::StreamData> streamData) {\n        EXPECT_EQ(handle, nullptr);\n        EXPECT_EQ(streamData.value().data, nullptr);\n        EXPECT_TRUE(streamData.value().fin);\n      });\n  evb.loopOnce();\n}\n\nTEST(WebTransportTest, AwaitNextReadTimeout) {\n  folly::SingletonVault::singleton()->registrationComplete();\n  folly::EventBase evb;\n  MockStreamReadHandle readHandle(0);\n  auto [promise, future] =\n      folly::makePromiseContract<WebTransport::StreamData>();\n  EXPECT_CALL(readHandle, readStreamData())\n      .WillOnce(testing::Invoke([&, &future = future] { //\n        return std::move(future);\n      }));\n\n  readHandle.awaitNextRead(\n      &evb,\n      [&, &promise = promise](WebTransport::StreamReadHandle* handle,\n                              uint64_t,\n                              folly::Try<WebTransport::StreamData> streamData) {\n        EXPECT_EQ(handle, nullptr);\n        EXPECT_NE(streamData.tryGetExceptionObject<folly::FutureException>(),\n                  nullptr);\n        // The promise core holds a keep alive to the event base, kill it here\n        auto deadPromise = std::move(promise);\n      },\n      std::chrono::milliseconds(100));\n  evb.loop();\n}\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/http/webtransport/test/WtStreamManagerTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/http/webtransport/WtStreamManager.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <quic/priority/HTTPPriorityQueue.h>\n\nnamespace proxygen::coro::test {\n\nusing WtStreamManager = detail::WtStreamManager;\n\n// Helper to check which stream is next in the priority queue\nvoid expectNextWritable(const quic::PriorityQueue& pq,\n                        uint64_t expectedStreamId) {\n  EXPECT_FALSE(pq.empty());\n  auto nextId = pq.peekNextScheduledID();\n  EXPECT_TRUE(nextId.isStreamID());\n  EXPECT_EQ(nextId.asStreamID(), expectedStreamId);\n}\n\nstruct WtSmEgressCb : WtStreamManager::EgressCallback {\n  WtSmEgressCb() = default;\n  ~WtSmEgressCb() override = default;\n  void eventsAvailable() noexcept override {\n    evAvail_ = true;\n  }\n  bool evAvail_{false};\n};\n\nstruct WtSmIngressCb : WtStreamManager::IngressCallback {\n  WtSmIngressCb() = default;\n  ~WtSmIngressCb() override = default;\n  void onNewPeerStream(uint64_t streamId) noexcept override {\n    peerIds.push_back(streamId);\n  }\n  std::vector<uint64_t> peerIds;\n};\n\nusing WtConfig = WtStreamManager::WtConfig;\nusing MaxStreamsBidi = WtStreamManager::MaxStreamsBidi;\nusing MaxStreamsUni = WtStreamManager::MaxStreamsUni;\nusing Result = WtStreamManager::Result;\nusing MaxConnData = WtStreamManager::MaxConnData;\nusing MaxStreamData = WtStreamManager::MaxStreamData;\nusing ResetStream = WtStreamManager::ResetStream;\nusing DrainSession = WtStreamManager::DrainSession;\nusing CloseSession = WtStreamManager::CloseSession;\nusing StopSending = WtStreamManager::StopSending;\n\nTEST(WtStreamManager, BasicSelfBidi) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // 0x00 is the next expected bidi stream id for client\n  EXPECT_TRUE(streamManager.canCreateBidi());\n  auto bidiRes = streamManager.getOrCreateBidiHandle(0x00);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // creating out of order streams is allowed (e.g. 0x08 vs 0x04)\n  // necessary to support quic/http3 as they both use underlying quic stream id\n  EXPECT_TRUE(streamManager.canCreateBidi());\n  bidiRes = streamManager.getOrCreateBidiHandle(0x08);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // limit saturated => getOrCreateBidiHandle returns nullptr\n  EXPECT_FALSE(streamManager.canCreateBidi());\n  bidiRes = streamManager.getOrCreateBidiHandle(0x04);\n  EXPECT_EQ(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr);\n}\n\nTEST(WtStreamManager, BasicSelfUni) {\n  WtConfig config{.peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // 0x02 is the next expected uni stream id for client\n  EXPECT_TRUE(streamManager.canCreateUni());\n  auto bidiRes = streamManager.getOrCreateBidiHandle(0x02);\n  EXPECT_EQ(bidiRes.readHandle, nullptr); // egress only\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // creating out of order streams is allowed (e.g. 0x0a vs 0x06)\n  // necessary to support quic/http3 as they both use underlying quic stream id\n  EXPECT_TRUE(streamManager.canCreateUni());\n  bidiRes = streamManager.getOrCreateBidiHandle(0x0a);\n  EXPECT_EQ(bidiRes.readHandle, nullptr); // egress only\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // limit saturated => getOrCreateBidiHandle returns nullptr\n  EXPECT_FALSE(streamManager.canCreateUni());\n  bidiRes = streamManager.getOrCreateBidiHandle(0x06);\n  EXPECT_EQ(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr);\n}\n\nTEST(WtStreamManager, BasicPeerBidi) {\n  WtConfig config{.selfMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // 0x01 is the next expected bidi stream for server\n  auto bidiRes = streamManager.getOrCreateBidiHandle(0x01);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // receiving out of order streams is allowed (e.g. 0x09 vs 0x05)\n  // hmm should we reject this for http/2 (technically violates max streams)\n  bidiRes = streamManager.getOrCreateBidiHandle(0x09);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_NE(bidiRes.writeHandle, nullptr);\n\n  // limit saturated => getOrCreateBidiHandle returns nullptr\n  bidiRes = streamManager.getOrCreateBidiHandle(0x05);\n  EXPECT_EQ(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr);\n\n  CHECK_EQ(ingressCb.peerIds.size(), 2);\n  EXPECT_EQ(ingressCb.peerIds[0], 0x01);\n  EXPECT_EQ(ingressCb.peerIds[1], 0x09);\n}\n\nTEST(WtStreamManager, BasicPeerUni) {\n  WtConfig config{.selfMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // 0x03 is the next expected uni stream for server\n  auto bidiRes = streamManager.getOrCreateBidiHandle(0x03);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr); // ingress only\n\n  // receiving out of order streams is allowed (e.g. 0x0b vs 0x07)\n  // hmm should we reject this for http/2 (technically violates max streams)\n  bidiRes = streamManager.getOrCreateBidiHandle(0x0b);\n  EXPECT_NE(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr); // ingress only\n\n  // limit saturated => no handle returned\n  bidiRes = streamManager.getOrCreateBidiHandle(0x07);\n  EXPECT_EQ(bidiRes.readHandle, nullptr);\n  EXPECT_EQ(bidiRes.writeHandle, nullptr); // ingress only\n\n  // validate peer streams\n  CHECK_EQ(ingressCb.peerIds.size(), 2);\n  EXPECT_EQ(ingressCb.peerIds[0], 0x03);\n  EXPECT_EQ(ingressCb.peerIds[1], 0x0b);\n}\n\nTEST(WtStreamManager, NextBidiUniHandle) {\n  WtConfig config{.peerMaxStreamsBidi = 2, .peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // next egress handle tests\n  auto uni = CHECK_NOTNULL(streamManager.createEgressHandle());\n  EXPECT_EQ(uni->getID(), 0x02);\n  uni = streamManager.createEgressHandle();\n  EXPECT_EQ(uni->getID(), 0x06);\n\n  // next bidi handle test\n  auto bidi = streamManager.createBidiHandle();\n  EXPECT_NE(bidi.readHandle, nullptr);\n  EXPECT_NE(bidi.writeHandle, nullptr);\n  EXPECT_EQ(bidi.readHandle->getID(), bidi.writeHandle->getID());\n  EXPECT_EQ(bidi.readHandle->getID(), 0x00);\n\n  bidi = streamManager.createBidiHandle();\n  EXPECT_NE(bidi.readHandle, nullptr);\n  EXPECT_NE(bidi.writeHandle, nullptr);\n  EXPECT_EQ(bidi.readHandle->getID(), bidi.writeHandle->getID());\n  EXPECT_EQ(bidi.readHandle->getID(), 0x04);\n}\n\nTEST(WtStreamManager, StreamLimits) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // a single egress handle should succeed\n  auto uni = streamManager.createEgressHandle();\n  EXPECT_NE(uni, nullptr);\n\n  // a single bidi handle should succeed\n  auto bidi = streamManager.createBidiHandle();\n  EXPECT_NE(bidi.readHandle, nullptr);\n  EXPECT_NE(bidi.writeHandle, nullptr);\n\n  // next egress handle should fail\n  uni = streamManager.createEgressHandle();\n  EXPECT_EQ(uni, nullptr);\n\n  // next bidi handle should fail\n  bidi = streamManager.createBidiHandle();\n  EXPECT_EQ(bidi.readHandle, nullptr);\n  EXPECT_EQ(bidi.writeHandle, nullptr);\n\n  // peer grants one additional credit for each of bidi and uni\n  EXPECT_TRUE(streamManager.onMaxStreams(MaxStreamsBidi{2}));\n  EXPECT_TRUE(streamManager.onMaxStreams(MaxStreamsUni{2}));\n\n  // next egress handle should succeed\n  uni = CHECK_NOTNULL(streamManager.createEgressHandle());\n  EXPECT_NE(uni, nullptr);\n\n  // next bidi handle should succeed\n  bidi = streamManager.createBidiHandle();\n  EXPECT_NE(bidi.readHandle, nullptr);\n  EXPECT_NE(bidi.writeHandle, nullptr);\n}\n\nTEST(WtStreamManager, EnqueueIngressData) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // next createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle);\n  constexpr std::string_view kData = \"abcdefghijklmnopqrstuvwxyz\";\n  for (auto c : kData) {\n    EXPECT_TRUE(streamManager.enqueue(\n        *one.readHandle, {folly::IOBuf::fromString(std::string{c}), false}));\n  }\n  auto oneFut = one.readHandle->readStreamData();\n  EXPECT_TRUE(oneFut.isReady() && oneFut.value().data);\n  EXPECT_EQ(oneFut.value().data->toString(), kData);\n}\n\nTEST(WtStreamManager, EnqueueIngressDataRwnd) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // next createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  auto two = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle && two.readHandle && two.writeHandle);\n\n  constexpr auto kBufLen = 65'535;\n\n  EXPECT_EQ(streamManager.bufferedBytes(*one.readHandle), 0);\n  // both conn & stream recv window exactly full, expect success\n  EXPECT_TRUE(\n      streamManager.enqueue(*one.readHandle, {makeBuf(kBufLen), false}));\n  EXPECT_EQ(streamManager.bufferedBytes(*one.readHandle), kBufLen);\n  // enqueuing a additional byte in one will fail (stream recv window full)\n  EXPECT_FALSE(\n      streamManager.enqueue(*one.readHandle, {makeBuf(kBufLen), false}));\n\n  // enqueuing a single byte in two will fail (conn recv window full)\n  EXPECT_FALSE(streamManager.enqueue(*two.readHandle, {makeBuf(1), false}));\n  auto twoFut = two.readHandle->readStreamData();\n  EXPECT_FALSE(twoFut.isReady()); // no data buffered, ::enqueue failed\n\n  // reading stream data should succeed\n  auto oneFut = one.readHandle->readStreamData();\n  EXPECT_TRUE(oneFut.isReady()); // enqueue should fulfill promise\n  EXPECT_EQ(oneFut.value().data->computeChainDataLength(), kBufLen);\n  EXPECT_FALSE(oneFut.value().fin);\n  EXPECT_EQ(streamManager.bufferedBytes(*one.readHandle), 0);\n}\n\nTEST(WtStreamManager, WriteEgressHandle) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // next two ::createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  auto two = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle && two.readHandle && two.writeHandle);\n\n  // Set priorities to make ordering predictable (urgency, incremental)\n  one.writeHandle->setPriority(quic::HTTPPriorityQueue::Priority(\n      0, false)); // Higher priority (urgency 0)\n  two.writeHandle->setPriority(quic::HTTPPriorityQueue::Priority(\n      1, false)); // Lower priority (urgency 1)\n\n  constexpr auto kBufLen = 65'535;\n  // kBufLen will fill up both conn & stream egress windows\n  auto res = one.writeHandle->writeStreamData(\n      /*data=*/makeBuf(kBufLen), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(res.hasValue() && res.value() == WebTransport::FCState::BLOCKED);\n  // enqueue an additional byte into one\n  res = one.writeHandle->writeStreamData(\n      /*data=*/makeBuf(1), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(res.hasValue() && res.value() == WebTransport::FCState::BLOCKED);\n\n  // each stream has an individual egress buffer of kBufLen before applying\n  // backpressure => writing (kBufLen - 1) bytes into two should return\n  // UNBLOCKED\n  res = two.writeHandle->writeStreamData(\n      makeBuf(kBufLen - 1), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(res.hasValue() &&\n              res.value() == WebTransport::FCState::UNBLOCKED);\n\n  // we should be able to dequeue kBufLen data from one.writeHandle\n  expectNextWritable(*priorityQueue, one.writeHandle->getID());\n  EXPECT_EQ(streamManager.nextWritable(), one.writeHandle);\n  auto dequeue = streamManager.dequeue(*one.writeHandle, /*atMost=*/kBufLen);\n  EXPECT_EQ(dequeue.data->computeChainDataLength(), kBufLen);\n  EXPECT_FALSE(dequeue.fin);\n  // one still in queue but blocked on conn FC - dequeue again to move to\n  // connFcBlockedStreams_\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n  dequeue = streamManager.dequeue(*one.writeHandle, /*atMost=*/kBufLen);\n  EXPECT_EQ(dequeue.data, nullptr);\n  EXPECT_FALSE(dequeue.fin);\n\n  // two is now next writable - dequeue would move to connFcBlockedStreams_\n  expectNextWritable(*priorityQueue, two.writeHandle->getID());\n\n  // grant one a single byte of stream credit; dequeuing from one should yield\n  // nothing since we're still blocked on conn flow control\n  EXPECT_TRUE(streamManager.onMaxData(\n      MaxStreamData{{kBufLen + 1}, one.writeHandle->getID()}));\n  // two is still next writable (one is blocked on conn FC)\n  expectNextWritable(*priorityQueue, two.writeHandle->getID());\n\n  // grant one additional byte of conn credit; dequeue from one should yield\n  // byte + eof\n  EXPECT_TRUE(streamManager.onMaxData(MaxConnData{kBufLen + 1}));\n  // one is re-inserted from connFcBlockedStreams_ and should be next (higher\n  // priority)\n  expectNextWritable(*priorityQueue, one.writeHandle->getID());\n  EXPECT_EQ(streamManager.nextWritable(), one.writeHandle);\n  dequeue = streamManager.dequeue(*one.writeHandle, /*atMost=*/kBufLen);\n  EXPECT_EQ(dequeue.data->computeChainDataLength(), 1);\n  EXPECT_TRUE(dequeue.fin);\n\n  // stream two is next writable, but blocked on conn egress flow control\n  expectNextWritable(*priorityQueue, two.writeHandle->getID());\n\n  // grant enough conn fc credit to unblock two completely\n  EXPECT_TRUE(streamManager.onMaxData(MaxConnData{kBufLen * 2}));\n  // two still next\n  expectNextWritable(*priorityQueue, two.writeHandle->getID());\n  EXPECT_EQ(streamManager.nextWritable(), two.writeHandle);\n  dequeue = streamManager.dequeue(*two.writeHandle, /*atMost=*/kBufLen);\n  EXPECT_EQ(dequeue.data->computeChainDataLength(), kBufLen - 1);\n  EXPECT_TRUE(dequeue.fin);\n}\n\nTEST(WtStreamManager, DequeueWriteConnFcBlocked) {\n  WtConfig config{.peerMaxStreamsUni = 1};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  auto uni = CHECK_NOTNULL(streamManager.createEgressHandle());\n  // write kBufLen & fin into stream (fills egress buffer)\n  constexpr auto kBufLen = 65'535;\n  auto res = uni->writeStreamData(\n      makeBuf(kBufLen), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(res.hasValue() && *res == WebTransport::FCState::BLOCKED);\n\n  // we should be able to dequeue kBufLen data from one.writeHandle\n  expectNextWritable(*priorityQueue, uni->getID());\n  auto dequeue = streamManager.dequeue(*uni, /*atMost=*/kBufLen);\n  EXPECT_TRUE(dequeue.data && dequeue.fin);\n}\n\nTEST(WtStreamManager, BidiHandleCancellation) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // next ::createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle);\n\n  auto res = one.writeHandle->writeStreamData(\n      /*data=*/makeBuf(100), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(res.hasValue() &&\n              res.value() == WebTransport::FCState::UNBLOCKED);\n\n  // StreamManager::onStopSending should request cancellation of egress handle\n  auto ct = one.writeHandle->getCancelToken();\n  streamManager.onStopSending({one.writeHandle->getID(), 0x00});\n  EXPECT_TRUE(ct.isCancellationRequested());\n  // stream should be removed from queue after cancellation\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n  EXPECT_TRUE(priorityQueue->empty());\n\n  // StreamManager::onResetStream should request cancellation of ingress handle\n  auto fut = one.readHandle->readStreamData();\n  ct = one.readHandle->getCancelToken();\n  streamManager.onResetStream({one.writeHandle->getID(), 0x00});\n  EXPECT_TRUE(ct.isCancellationRequested());\n  EXPECT_TRUE(fut.isReady() && fut.hasException());\n}\n\nTEST(WtStreamManager, GrantFlowControlCredit) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  constexpr auto kBufLen = 65'535;\n\n  // next ::createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle);\n  // fills up both conn- & stream-level flow control\n  EXPECT_TRUE(streamManager.enqueue(*one.readHandle,\n                                    {makeBuf(kBufLen), /*fin=*/false}));\n\n  auto fut = one.readHandle->readStreamData();\n  EXPECT_TRUE(fut.isReady() && fut.hasValue());\n\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false)); // callback should have\n                                                        // triggered\n  auto events = streamManager.moveEvents();\n  CHECK_GE(events.size(), 2); // one conn & one stream fc\n  auto maxStreamData = std::get<MaxStreamData>(events[0]);\n  auto maxConnData = std::get<MaxConnData>(events[1]);\n  EXPECT_EQ(maxStreamData.streamId, one.readHandle->getID());\n  EXPECT_EQ(maxStreamData.maxData, kBufLen * 2);\n  EXPECT_EQ(maxConnData.maxData, kBufLen * 2);\n\n  // next ::createBidiHandle should succeed\n  auto two = streamManager.createBidiHandle();\n  CHECK(two.readHandle && two.writeHandle);\n  // fills up both conn- & stream-level flow control\n  fut = two.readHandle->readStreamData();\n  EXPECT_TRUE(\n      streamManager.enqueue(*two.readHandle, {makeBuf(kBufLen), /*fin=*/true}));\n  // should have triggered only connection-level flow control since fin=true\n  events = streamManager.moveEvents();\n  EXPECT_GE(events.size(), 1);\n  EXPECT_TRUE(std::holds_alternative<MaxConnData>(events[0]));\n\n  // validate that receiving a reset_stream releases connection-level flow\n  // control\n  auto* ingress = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x03));\n  streamManager.enqueue(*ingress, {makeBuf(kBufLen), /*fin=*/false});\n  streamManager.onResetStream(ResetStream{ingress->getID(), 0x00});\n  events = streamManager.moveEvents();\n  CHECK_GE(events.size(), 1);\n  maxConnData = std::get<MaxConnData>(events[0]);\n  EXPECT_EQ(maxConnData.maxData,\n            kBufLen * 4); // we've previously already issued 2x kBufLen\n                          // increments, this is the 3rd\n}\n\nTEST(WtStreamManager, GrantConnFlowControlCreditAfterRead) {\n  WtConfig config{.peerMaxStreamsBidi = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  constexpr auto kBufLen = 65'535;\n\n  // enqueue a total of kBufLen bytes across handles one & two\n  auto one = streamManager.createBidiHandle();\n  auto two = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle && two.readHandle && two.writeHandle);\n\n  EXPECT_TRUE(streamManager.enqueue(*one.readHandle,\n                                    {makeBuf(kBufLen - 1), /*fin=*/true}));\n  EXPECT_TRUE(\n      streamManager.enqueue(*two.readHandle, {makeBuf(1), /*fin=*/true}));\n\n  // we should release conn-fc credit only after app reading kBufLen / 2 bytes\n  // => reading the single bytes from two should not release conn-fc\n  auto read = two.readHandle->readStreamData();\n  EXPECT_TRUE(read.isReady());\n  EXPECT_FALSE(egressCb.evAvail_);\n\n  // reading from one releases flow control (total bytes read > kBufLen / 2)\n  read = one.readHandle->readStreamData();\n  EXPECT_TRUE(read.isReady());\n\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false));\n  auto events = streamManager.moveEvents();\n  EXPECT_EQ(events.size(), 1);\n  EXPECT_TRUE(std::holds_alternative<MaxConnData>(events[0]));\n}\n\nTEST(WtStreamManager, NonDefaultFlowControlValues) {\n  WtConfig config{};\n  config.peerMaxConnData = 100;\n  config.peerMaxStreamDataBidi = config.peerMaxStreamDataUni = 60;\n\n  config.selfMaxConnData = config.selfMaxStreamDataBidi =\n      config.selfMaxStreamDataUni = 100;\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n  constexpr auto kBufLen = 100;\n\n  // enqueue 100 bytes of data\n  auto one = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle);\n  EXPECT_TRUE(streamManager.enqueue(*one.readHandle,\n                                    {makeBuf(kBufLen), /*fin=*/false}));\n\n  // read synchronously available\n  auto fut = one.readHandle->readStreamData();\n  EXPECT_TRUE(fut.isReady() && fut.hasValue());\n\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false)); // callback should have\n                                                        // triggered\n  auto events = streamManager.moveEvents();\n  CHECK_GE(events.size(), 2); // one conn & one stream fc\n  auto maxStreamData = std::get<MaxStreamData>(events[0]);\n  auto maxConnData = std::get<MaxConnData>(events[1]);\n  EXPECT_EQ(maxStreamData.streamId, one.readHandle->getID());\n  EXPECT_EQ(maxStreamData.maxData, kBufLen * 2);\n  EXPECT_EQ(maxConnData.maxData, kBufLen * 2);\n\n  // dequeuing data from one.writeHandle is limited by stream egress fc, which\n  // is 60\n  one.writeHandle->writeStreamData(\n      makeBuf(kBufLen), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  // one is next writable\n  EXPECT_EQ(streamManager.nextWritable(), one.writeHandle);\n  expectNextWritable(*priorityQueue, one.writeHandle->getID());\n  auto dequeue = streamManager.dequeue(*one.writeHandle,\n                                       std::numeric_limits<uint64_t>::max());\n  EXPECT_EQ(dequeue.data->computeChainDataLength(), 60);\n  EXPECT_FALSE(dequeue.fin);\n\n  // dequeuing data from two's writeHandle is now limited by conn egress fc,\n  // which is 100-60=40\n  auto* two = CHECK_NOTNULL(streamManager.createEgressHandle());\n  two->writeStreamData(\n      makeBuf(kBufLen), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_EQ(streamManager.nextWritable(), two);\n  expectNextWritable(*priorityQueue, two->getID());\n  dequeue = streamManager.dequeue(*two, std::numeric_limits<uint64_t>::max());\n  EXPECT_EQ(dequeue.data->computeChainDataLength(), 40);\n  EXPECT_FALSE(dequeue.fin);\n}\n\nTEST(WtStreamManager, ResetStreamReleasesConnFlowControl) {\n  WtConfig config{.selfMaxStreamsUni = 10};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n  constexpr auto kBufLen = 65'535;\n  constexpr auto kHalfBufLen = (kBufLen / 2) + 1;\n\n  auto* one = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x03));\n  auto* two = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x07));\n  auto* three = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x0b));\n\n  // queue kHalfBufLen across all handles; ensure the last reset releases\n  // conn fc\n  streamManager.enqueue(*one, {makeBuf(kHalfBufLen - 2), /*fin=*/false});\n  streamManager.onResetStream({one->getID()});\n  EXPECT_FALSE(egressCb.evAvail_);\n\n  streamManager.enqueue(*two, {makeBuf(1), /*fin=*/false});\n  streamManager.onResetStream({two->getID()});\n  EXPECT_FALSE(egressCb.evAvail_);\n\n  streamManager.enqueue(*three, {makeBuf(1), /*fin=*/false});\n  streamManager.onResetStream({three->getID()});\n  EXPECT_TRUE(egressCb.evAvail_);\n\n  // validate MaxConnData value\n  auto events = streamManager.moveEvents();\n  EXPECT_EQ(events.size(), 1);\n  auto maxConnData = std::get<MaxConnData>(events[0]);\n  EXPECT_EQ(maxConnData.maxData, kBufLen + kHalfBufLen);\n}\n\nTEST(WtStreamManager, StopSendingResetStreamTest) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n  constexpr auto kBufLen = 65'535;\n\n  // next ::createBidiHandle should succeed\n  auto one = streamManager.createBidiHandle();\n  CHECK(one.readHandle && one.writeHandle);\n  auto id = one.readHandle->getID();\n\n  // stop sending should invoke callback and resolve pending promise\n  auto rp = one.readHandle->readStreamData();\n  one.readHandle->stopSending(0);\n  EXPECT_TRUE(rp.isReady() && rp.hasException()); // exception via stopSending\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false));\n  auto events = streamManager.moveEvents();\n  EXPECT_EQ(events.size(), 1);\n  auto stopSending = std::get<StopSending>(events[0]);\n  EXPECT_EQ(stopSending.streamId, id);\n  EXPECT_EQ(stopSending.err, 0);\n\n  // fill up egress buffer to ensure ::resetStream resolves pending promise\n  one.writeHandle->writeStreamData(\n      makeBuf(kBufLen), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  auto wp = one.writeHandle->awaitWritable();\n\n  // reset stream should invoke callback and resolve pending promise\n  one.writeHandle->resetStream(/*error=*/1);\n  EXPECT_TRUE(wp.hasValue() && wp->isReady() && wp->hasException());\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false));\n  events = streamManager.moveEvents();\n  EXPECT_EQ(events.size(), 1);\n  auto resetStream = std::get<ResetStream>(events[0]);\n  EXPECT_EQ(resetStream.streamId, id);\n  EXPECT_EQ(resetStream.err, 1);\n\n  // bidirectionally reset => stream deleted\n  EXPECT_FALSE(streamManager.hasStreams());\n\n  // ::resetStream on a unidirectional egress stream should erase the stream\n  auto* egress = CHECK_NOTNULL(streamManager.createEgressHandle());\n  EXPECT_TRUE(streamManager.hasStreams());\n  egress->resetStream(/*error=*/0); // read side is closed for an egress-only\n                                    // handle; bidirectionally complete after\n                                    // ::resetStream\n  EXPECT_FALSE(streamManager.hasStreams());\n}\n\nTEST(WtStreamManager, AwaitWritableTest) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  constexpr auto kBufLen = 65'535;\n  // next ::createBidiHandle should succeed\n  auto eh = CHECK_NOTNULL(streamManager.createEgressHandle());\n\n  // await writable future should be synchronously ready & equal to kBufLen\n  // (default egress stream fc)\n  auto await = eh->awaitWritable();\n  EXPECT_TRUE(await.hasValue() && await.value().isReady() &&\n              await.value().value() == kBufLen);\n\n  // send kBufLen + 1 bytes\n  auto fcRes = eh->writeStreamData(\n      makeBuf(kBufLen + 1), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(fcRes.hasValue() &&\n              fcRes.value() == WebTransport::FCState::BLOCKED);\n\n  // await writable future; not ready awaiting egress buffer space\n  await = eh->awaitWritable();\n  EXPECT_TRUE(await.hasValue() && !await.value().isReady());\n\n  // granting additional fc credit does not unblock egress buffer\n  streamManager.onMaxData({{kBufLen + 2}, eh->getID()});\n  EXPECT_TRUE(await.hasValue() && !await.value().isReady());\n\n  // dequeue will resolve promise\n  streamManager.dequeue(*eh, kBufLen);\n  EXPECT_TRUE(await->isReady() &&\n              await->value() == kBufLen - 1); // minus one because we enqueued\n                                              // kBufLen + 1 bytes of data\n}\n\nTEST(WtStreamManager, WritableStreams) {\n  WtConfig config{.peerMaxStreamsUni = 3};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n  constexpr auto kBufLen = 65'535;\n  constexpr auto kAtMost = std::numeric_limits<uint64_t>::max();\n\n  // next two ::createEgressHandle should succeed\n  auto one = CHECK_NOTNULL(streamManager.createEgressHandle());\n  auto two = CHECK_NOTNULL(streamManager.createEgressHandle());\n\n  // 1 byte + eof; next writableStream == one\n  auto writeRes = one->writeStreamData(\n      makeBuf(1), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(writeRes.hasValue() &&\n              writeRes.value() == WebTransport::FCState::UNBLOCKED);\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false));\n  // next writable stream is one\n  EXPECT_EQ(streamManager.nextWritable(), one);\n  expectNextWritable(*priorityQueue, one->getID());\n\n  // dequeue should yield the expected results\n  auto dequeue = streamManager.dequeue(*one, kAtMost);\n  EXPECT_TRUE(dequeue.data->length() == 1 && dequeue.fin);\n\n  // no more writableStreams\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n  EXPECT_TRUE(priorityQueue->empty());\n\n  // write kBufLen data; which will exceed conn flow control by one byte)\n  writeRes = two->writeStreamData(\n      makeBuf(kBufLen), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  EXPECT_TRUE(writeRes.hasValue() &&\n              writeRes.value() == WebTransport::FCState::BLOCKED);\n  EXPECT_TRUE(std::exchange(egressCb.evAvail_, false));\n  // next writable stream is two\n  EXPECT_EQ(streamManager.nextWritable(), two);\n  expectNextWritable(*priorityQueue, two->getID());\n\n  // dequeue will yield kBufLen - 1 (limited by conn flow control)\n  dequeue = streamManager.dequeue(*two, kAtMost);\n  EXPECT_EQ(dequeue.data->length(), kBufLen - 1);\n  EXPECT_FALSE(dequeue.fin);\n  // two still in queue but blocked on conn FC - dequeue again to move to\n  // connFcBlockedStreams_\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n  dequeue = streamManager.dequeue(*two, kAtMost);\n  EXPECT_EQ(dequeue.data, nullptr);\n  EXPECT_FALSE(dequeue.fin);\n  // no writable streams, blocked on conn flow control\n  EXPECT_TRUE(priorityQueue->empty());\n\n  // issue one additional byte of conn fc\n  EXPECT_TRUE(streamManager.onMaxData(MaxConnData{kBufLen + 1}));\n\n  // two is now writable after conn flow control granted\n  EXPECT_EQ(streamManager.nextWritable(), two);\n  expectNextWritable(*priorityQueue, two->getID());\n  dequeue = streamManager.dequeue(*two, kAtMost);\n  EXPECT_EQ(dequeue.data->length(), 1);\n  EXPECT_FALSE(dequeue.fin);\n\n  // FIN-only stream should still be writable even when connection FC is blocked\n  auto three = CHECK_NOTNULL(streamManager.createEgressHandle());\n  three->writeStreamData(nullptr, /*fin=*/true, /*byteEventCallback=*/nullptr);\n  EXPECT_EQ(streamManager.nextWritable(), three);\n  expectNextWritable(*priorityQueue, three->getID());\n}\n\nTEST(WtStreamManager, DrainWtSession) {\n  WtConfig config{.peerMaxStreamsBidi = 2, .peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // drain session, expect enqueued event\n  streamManager.onDrainSession({});\n  streamManager.drain();\n  EXPECT_TRUE(egressCb.evAvail_);\n  auto events = streamManager.moveEvents();\n  CHECK(!events.empty() && std::holds_alternative<DrainSession>(events[0]));\n\n  // streams can still be created after drain\n  auto bidi = streamManager.createBidiHandle();\n  auto* egress = streamManager.createEgressHandle();\n  EXPECT_TRUE(bidi.readHandle && bidi.writeHandle && egress);\n\n  // shutdown session\n  streamManager.shutdown(CloseSession{});\n\n  // no streams can be opened after shutdown\n  bidi = streamManager.createBidiHandle();\n  egress = streamManager.createEgressHandle();\n  EXPECT_FALSE(bidi.readHandle || bidi.writeHandle || egress);\n}\n\nTEST(WtStreamManager, CloseWtSession) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // ensure cancellation source is cancelled when invoked ::onCloseSession\n  auto one = streamManager.createBidiHandle();\n  auto oneRead = one.readHandle->readStreamData();\n\n  auto* two = streamManager.createEgressHandle();\n  auto cts = {one.readHandle->getCancelToken(),\n              one.writeHandle->getCancelToken(),\n              two->getCancelToken()};\n\n  streamManager.onCloseSession(CloseSession{0, \"\"});\n  for (auto& ct : cts) {\n    EXPECT_TRUE(ct.isCancellationRequested());\n  }\n\n  // read promise should have exception set\n  EXPECT_TRUE(oneRead.isReady() && oneRead.hasException());\n\n  // sends a CloseSession in response to a peer initiated CloseSession;\n  // this is okay wrt WebTransport rfc\n  auto events = streamManager.moveEvents();\n  EXPECT_TRUE(std::holds_alternative<CloseSession>(events.back()));\n\n  // a locally initiated close should enqueue an event\n  streamManager.shutdown(CloseSession{0, \"\"});\n}\n\nTEST(WtStreamManager, ResetStreamReliableSize) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // enqueue 100 bytes, ensure ::onResetStream does not deallocate stream until\n  // after 100 bytes are read\n  auto ingress = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x03));\n  streamManager.enqueue(*ingress, {makeBuf(100), /*fin=*/false});\n\n  auto ct = ingress->getCancelToken();\n  streamManager.onResetStream(\n      ResetStream{ingress->getID(), 0x00, /*reliableSize=*/100});\n  // stream still alive\n  EXPECT_TRUE(streamManager.hasStreams());\n  EXPECT_FALSE(ct.isCancellationRequested());\n\n  // read the 100 buffered bytes successfully\n  auto read = ingress->readStreamData();\n  EXPECT_TRUE(read.isReady() &&\n              read.value().data->computeChainDataLength() == 100 &&\n              read.value().fin == false);\n\n  // future read fails with exception\n  read = ingress->readStreamData();\n  EXPECT_TRUE(read.isReady() && read.hasException());\n  EXPECT_FALSE(streamManager.hasStreams());\n}\n\nTEST(WtStreamManager, InvalidCtrlFrames) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  auto bidi = streamManager.createBidiHandle();\n  // stream grant offset (i.e. 0) < stream current offset (i.e. 64KiB)\n  EXPECT_EQ(\n      streamManager.onMaxData(MaxStreamData{{0}, bidi.readHandle->getID()}),\n      Result::Fail);\n\n  // conn grant offset (i.e. 0) < conn stream offset (i.e. 64KiB) fails\n  EXPECT_EQ(streamManager.onMaxData(MaxConnData{0}), Result::Fail);\n\n  // MaxStreamsBidi{0} < peer.bidi (i.e. 1) fails\n  EXPECT_EQ(streamManager.onMaxStreams(MaxStreamsBidi{0}), Result::Fail);\n\n  // MaxStreamsUni{0} < peer.uni (i.e. 1) fails\n  EXPECT_EQ(streamManager.onMaxStreams(MaxStreamsUni{0}), Result::Fail);\n}\n\nTEST(WtStreamManager, IssueMaxStreamsBidiUni) {\n  WtConfig config{.selfMaxStreamsBidi = 2, .selfMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  auto peerBidi = streamManager.getOrCreateBidiHandle(0x01);\n  CHECK(peerBidi.readHandle && peerBidi.writeHandle);\n  auto peerUni = CHECK_NOTNULL(streamManager.getOrCreateIngressHandle(0x03));\n\n  // rst_stream & stop_sending on peerBidi will close stream => issue\n  // MaxStreamsBidi\n  peerBidi.readHandle->stopSending(0);\n  peerBidi.writeHandle->resetStream(0);\n\n  // reading fin on peerUni will close stream => issue MaxStreamsUni\n  streamManager.enqueue(*peerUni, {makeBuf(0), true});\n  auto read = peerUni->readStreamData();\n  EXPECT_TRUE(read.isReady());\n\n  EXPECT_TRUE(egressCb.evAvail_);\n  // validate MaxStreamsBidi&MaxStreamsUni\n  auto events = streamManager.moveEvents();\n  EXPECT_EQ(events.size(), 4);\n  // events[0] & events[1] are StopSending & ResetStream\n  auto maxStreamsBidi = std::get<MaxStreamsBidi>(events[2]);\n  auto maxStreamsUni = std::get<MaxStreamsUni>(events[3]);\n  EXPECT_EQ(maxStreamsBidi.maxStreams, 3);\n  EXPECT_EQ(maxStreamsUni.maxStreams, 3);\n}\n\nTEST(WtStreamManager, ShutdownOpenStreams) {\n  WtConfig config;\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // create bidi stream\n  auto bidi = streamManager.createBidiHandle();\n  CHECK(bidi.readHandle && bidi.writeHandle);\n\n  // reset one direction\n  bidi.writeHandle->resetStream(0);\n\n  // ::shutdown with streams still open\n  streamManager.shutdown(CloseSession{});\n}\n\nTEST(WtStreamManager, NoopNonExistentStreams) {\n  WtConfig config{.peerMaxStreamsBidi = 2, .peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  streamManager.onStopSending({.streamId = 0});\n  streamManager.onResetStream({.streamId = 1});\n  streamManager.onMaxData({.streamId = 2});\n\n  {\n    // no stream state expected\n    auto handle = streamManager.getBidiHandle(0);\n    EXPECT_FALSE(handle.readHandle || handle.writeHandle);\n  }\n\n  {\n    // no stream state expected\n    auto handle = streamManager.getBidiHandle(1);\n    EXPECT_FALSE(handle.readHandle || handle.writeHandle);\n  }\n\n  {\n    // no stream state expected\n    auto handle = streamManager.getBidiHandle(2);\n    EXPECT_FALSE(handle.readHandle || handle.writeHandle);\n  }\n}\n\nTEST(WtStreamManager, OnlyFinPending) {\n  WtConfig config{.peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  auto one = CHECK_NOTNULL(streamManager.createEgressHandle());\n  auto two = CHECK_NOTNULL(streamManager.createEgressHandle());\n\n  // Set priorities to make ordering predictable (urgency, incremental)\n  one->setPriority(quic::HTTPPriorityQueue::Priority(\n      0, false)); // Higher priority (urgency 0)\n  two->setPriority(quic::HTTPPriorityQueue::Priority(\n      1, false)); // Lower priority (urgency 1)\n\n  one->writeStreamData(\n      /*data*/ makeBuf(1), /*fin=*/true, /*byteEventCallback=*/nullptr);\n  // next expected writable stream is one\n  EXPECT_EQ(streamManager.nextWritable(), one);\n  expectNextWritable(*priorityQueue, one->getID());\n\n  two->writeStreamData(\n      /*data*/ nullptr, /*fin=*/true, /*byteEventCallback=*/nullptr);\n  // one still has higher priority, so it should still be next\n  EXPECT_EQ(streamManager.nextWritable(), one);\n  expectNextWritable(*priorityQueue, one->getID());\n}\n\nTEST(WtStreamManager, NextWritableReturnsNullptrWhenQueueEmpty) {\n  WtConfig config{.peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // Queue is empty, nextWritable should return nullptr\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n\n  // Create a stream but don't write data - queue should still be empty\n  auto one = CHECK_NOTNULL(streamManager.createEgressHandle());\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n\n  // Write data to make stream writable\n  one->writeStreamData(\n      makeBuf(10), /*fin=*/false, /*byteEventCallback=*/nullptr);\n  EXPECT_NE(streamManager.nextWritable(), nullptr);\n  EXPECT_EQ(streamManager.nextWritable(), one);\n}\n\nTEST(WtStreamManager, NextWritableReturnsNullptrWhenHeadIsDatagram) {\n  WtConfig config{.peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  // Insert a datagram directly into the priority queue (bypassing\n  // StreamPriorityQueue)\n  auto datagramId = quic::PriorityQueue::Identifier::fromDatagramFlowID(42);\n  priorityQueue->insertOrUpdate(datagramId,\n                                quic::HTTPPriorityQueue::Priority(0, false));\n\n  // Even though there's something in the queue, nextWritable should return\n  // nullptr because the head is a datagram, not a stream\n  EXPECT_FALSE(priorityQueue->empty());\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n\n  // Now add a stream with lower priority\n  auto one = CHECK_NOTNULL(streamManager.createEgressHandle());\n  one->setPriority(quic::HTTPPriorityQueue::Priority(1, false));\n  one->writeStreamData(\n      makeBuf(10), /*fin=*/false, /*byteEventCallback=*/nullptr);\n\n  // Datagram is still at head (higher priority), so nextWritable still returns\n  // nullptr\n  EXPECT_EQ(streamManager.nextWritable(), nullptr);\n\n  // Remove the datagram, now the stream should be at the head\n  priorityQueue->erase(datagramId);\n  EXPECT_EQ(streamManager.nextWritable(), one);\n}\n\nusing WtReadHandle = WebTransport::StreamReadHandle;\nusing WtWriteHandle = WebTransport::StreamWriteHandle;\n\nstruct WtReadCallback : WtStreamManager::ReadCallback {\n  ~WtReadCallback() override = default;\n  void readReady(WtReadHandle&) noexcept override {\n    signalled = true;\n  }\n\n  bool signalled = false;\n};\n\nstruct MockDeliveryCallback : WebTransport::ByteEventCallback {\n  MOCK_METHOD(void, onByteEvent, (uint64_t, uint64_t), (noexcept, override));\n  MOCK_METHOD(void,\n              onByteEventCanceled,\n              (uint64_t, uint64_t),\n              (noexcept, override));\n};\n\nTEST(WtStreamManager, PerStreamCallbacks) {\n  WtConfig config{};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  auto bidi = streamManager.createBidiHandle();\n  CHECK(bidi.readHandle && bidi.writeHandle);\n\n  // set stream callbacks\n  WtReadCallback rcb;\n  streamManager.setReadCb(*bidi.readHandle, &rcb);\n\n  bidi.writeHandle->writeStreamData(\n      makeBuf(10), /*fin=*/false, /*byteEventCallback=*/nullptr);\n\n  // read before enqueue\n  auto read = bidi.readHandle->readStreamData();\n  EXPECT_FALSE(read.isReady());\n  // enqueue 10 bytes\n  streamManager.enqueue(*bidi.readHandle, {.data = makeBuf(10), .fin = false});\n  EXPECT_TRUE(read.isReady());\n  EXPECT_TRUE(std::exchange(rcb.signalled, false)); // signalled\n\n  // read after enqueue\n  streamManager.enqueue(*bidi.readHandle, {.data = makeBuf(10), .fin = false});\n  read = bidi.readHandle->readStreamData();\n  EXPECT_TRUE(read.isReady());\n  EXPECT_TRUE(std::exchange(rcb.signalled, false)); // signalled\n}\n\nTEST(WtStreamManager, DeliveryCallback) {\n  WtConfig config{.peerMaxStreamsUni = 5};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  MockDeliveryCallback cb1, cb2, cb3, cb4, cb5;\n\n  // single write with callback\n  auto* h1 = CHECK_NOTNULL(streamManager.createEgressHandle());\n  h1->writeStreamData(makeBuf(100), /*fin=*/true, &cb1);\n  auto deq1 = streamManager.dequeue(*h1, 100);\n  EXPECT_EQ(deq1.data->computeChainDataLength(), 100);\n  EXPECT_TRUE(deq1.fin);\n  EXPECT_EQ(deq1.deliveryCallback, &cb1);\n  EXPECT_EQ(deq1.lastByteStreamOffset, 99);\n\n  // multiple writes with different callbacks\n  auto* h2 = CHECK_NOTNULL(streamManager.createEgressHandle());\n  h2->writeStreamData(makeBuf(100), /*fin=*/false, &cb1);\n  h2->writeStreamData(makeBuf(50), /*fin=*/false, &cb2);\n  h2->writeStreamData(makeBuf(75), /*fin=*/true, &cb3);\n\n  auto d2a = streamManager.dequeue(*h2, 100);\n  EXPECT_EQ(d2a.data->computeChainDataLength(), 100);\n  EXPECT_EQ(d2a.deliveryCallback, &cb1);\n  EXPECT_EQ(d2a.lastByteStreamOffset, 99);\n\n  auto d2b = streamManager.dequeue(*h2, 100);\n  EXPECT_EQ(d2b.data->computeChainDataLength(), 50);\n  EXPECT_EQ(d2b.deliveryCallback, &cb2);\n  EXPECT_EQ(d2b.lastByteStreamOffset, 149);\n\n  auto d2c = streamManager.dequeue(*h2, 100);\n  EXPECT_EQ(d2c.data->computeChainDataLength(), 75);\n  EXPECT_TRUE(d2c.fin);\n  EXPECT_EQ(d2c.deliveryCallback, &cb3);\n  EXPECT_EQ(d2c.lastByteStreamOffset, 224);\n\n  // writes without callbacks coalesce with final callback write\n  auto* h3 = CHECK_NOTNULL(streamManager.createEgressHandle());\n  h3->writeStreamData(makeBuf(100), /*fin=*/false, nullptr);\n  h3->writeStreamData(makeBuf(100), /*fin=*/false, nullptr);\n  h3->writeStreamData(makeBuf(50), /*fin=*/true, &cb4);\n\n  auto deq3 = streamManager.dequeue(*h3, 300);\n  EXPECT_EQ(deq3.data->computeChainDataLength(), 250);\n  EXPECT_TRUE(deq3.fin);\n  EXPECT_EQ(deq3.deliveryCallback, &cb4);\n  EXPECT_EQ(deq3.lastByteStreamOffset, 249);\n\n  // callback delivered only when write completes\n  auto* h4 = CHECK_NOTNULL(streamManager.createEgressHandle());\n  h4->writeStreamData(makeBuf(100), /*fin=*/true, &cb5);\n\n  auto d4a = streamManager.dequeue(*h4, 60);\n  EXPECT_EQ(d4a.data->computeChainDataLength(), 60);\n  EXPECT_EQ(d4a.deliveryCallback, nullptr);\n  EXPECT_EQ(d4a.lastByteStreamOffset, 59);\n\n  auto d4b = streamManager.dequeue(*h4, 60);\n  EXPECT_EQ(d4b.data->computeChainDataLength(), 40);\n  EXPECT_TRUE(d4b.fin);\n  EXPECT_EQ(d4b.deliveryCallback, &cb5);\n  EXPECT_EQ(d4b.lastByteStreamOffset, 99);\n\n  // fin-only write with callback\n  auto* h5 = CHECK_NOTNULL(streamManager.createEgressHandle());\n  h5->writeStreamData(makeBuf(50), /*fin=*/false, nullptr);\n  auto d5a = streamManager.dequeue(*h5, 100);\n  EXPECT_EQ(d5a.data->computeChainDataLength(), 50);\n  EXPECT_EQ(d5a.deliveryCallback, nullptr);\n  EXPECT_EQ(d5a.lastByteStreamOffset, 49);\n\n  h5->writeStreamData(nullptr, /*fin=*/true, &cb1);\n  auto d5b = streamManager.dequeue(*h5, 100);\n  EXPECT_EQ(d5b.data->computeChainDataLength(), 0);\n  EXPECT_TRUE(d5b.fin);\n  EXPECT_EQ(d5b.deliveryCallback, &cb1);\n  EXPECT_EQ(d5b.lastByteStreamOffset, 49);\n}\n\nTEST(WtStreamManager, ByteEventCancellation) {\n  WtConfig config{.peerMaxStreamsUni = 2};\n  WtSmEgressCb egressCb;\n  WtSmIngressCb ingressCb;\n  auto priorityQueue = std::make_unique<quic::HTTPPriorityQueue>();\n  WtStreamManager streamManager{\n      detail::WtDir::Client, config, egressCb, ingressCb, *priorityQueue};\n\n  ::testing::StrictMock<MockDeliveryCallback> cb1, cb2;\n\n  auto* h = CHECK_NOTNULL(streamManager.createEgressHandle());\n  auto id = h->getID();\n\n  // first write: 100 bytes at offset 0 => last byte offset = 99\n  h->writeStreamData(makeBuf(100), /*fin=*/false, &cb1);\n  // second write: 50 bytes at offset 100 => last byte offset = 149\n  h->writeStreamData(makeBuf(50), /*fin=*/false, &cb2);\n  EXPECT_CALL(cb1, onByteEventCanceled(id, 99));\n  EXPECT_CALL(cb2, onByteEventCanceled(id, 149));\n\n  // reset stream triggers cancellation of all pending byte event callbacks\n  h->resetStream(/*error=*/0);\n}\n\n} // namespace proxygen::coro::test\n"
  },
  {
    "path": "proxygen/lib/pools/generators/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_pools_generators_member_group_config)\n\nproxygen_add_library(proxygen_pools_generators_server_config\n  EXPORTED_DEPS\n    proxygen_pools_generators_member_group_config\n    Folly::folly_network_address\n)\n\nproxygen_add_library(proxygen_pools_generators_server_list_generator\n  SRCS\n    ServerListGenerator.cpp\n  EXPORTED_DEPS\n    proxygen_pools_generators_member_group_config\n    proxygen_pools_generators_server_config\n    Folly::folly_io_async_async_base\n    Folly::folly_network_address\n    glog::glog\n)\n\nproxygen_add_library(proxygen_pools_generators_file_server_list_generator\n  SRCS\n    FileServerListGenerator.cpp\n  DEPS\n    Folly::folly_file_util\n    Folly::folly_json_dynamic\n    Folly::folly_network_address\n  EXPORTED_DEPS\n    proxygen_pools_generators_server_list_generator\n    proxygen_utils_exception\n)\n\nadd_subdirectory(coro)\n"
  },
  {
    "path": "proxygen/lib/pools/generators/FileServerListGenerator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/FileUtil.h>\n#include <folly/SocketAddress.h>\n#include <folly/json/dynamic.h>\n#include <folly/json/json.h>\n#include <proxygen/lib/pools/generators/FileServerListGenerator.h>\n#include <sstream>\n\nusing folly::dynamic;\nusing folly::parseJson;\nusing folly::SocketAddress;\nusing std::string;\nusing std::chrono::milliseconds;\n\nnamespace proxygen {\n\nvoid FileServerListGenerator::FileGenerator::readFile(std::string& filePath,\n                                                      std::string& content) {\n  if (!folly::readFile(filePath.c_str(), content)) {\n    folly::throw_exception<Exception>(\"Error reading file %s\", filePath);\n  }\n}\nvoid FileServerListGenerator::FileGenerator::cancelServerListRequest() {\n}\n\nvoid FileServerListGenerator::FileGenerator::run(milliseconds /*timeout*/) {\n  // Read the file\n\n  VLOG(4) << \"Looking up server list from File Handle \" << params_->fileName;\n\n  std::string content;\n  try {\n    readFile(params_->fileName, content);\n  } catch (const std::exception&) {\n    callback_->serverListError(std::current_exception());\n    delete this;\n    return;\n  }\n\n  // process the content and get the server list\n  std::vector<ServerConfig> servers;\n\n  if (params_->fileType == FileType::PLAIN_TEXT) {\n    std::stringstream sstream(content);\n    string line;\n    while (std::getline(sstream, line)) {\n      SocketAddress address;\n      address.setFromHostPort(line);\n      servers.emplace_back(address.getAddressStr(), address);\n    }\n  } else if (params_->fileType == FileType::JSON) {\n    try {\n      dynamic parsedJson = parseJson(content);\n      dynamic poolMembers = parsedJson.getDefault(params_->poolName, -1);\n      // If we cannot parse out an arrray out of that.\n      if (!poolMembers.isArray()) {\n        callback_->serverListError(std::make_exception_ptr(\n            std::invalid_argument(\"Cannot find a valid pool \" +\n                                  params_->poolName + \" in file \" +\n                                  params_->fileName)));\n        delete this;\n        return;\n      }\n      // Now we have an array.\n      for (const auto& e : poolMembers) {\n        SocketAddress address;\n        address.setFromHostPort(e.asString());\n        servers.emplace_back(address.getAddressStr(), address);\n      }\n    } catch (const std::exception&) {\n      callback_->serverListError(std::current_exception());\n      delete this;\n      return;\n    }\n  } else {\n    // Unsupported FileType yet.\n    LOG(FATAL) << \"Unsupported FileServerListGenerator::FileType!\";\n  }\n\n  VLOG(4) << \"Found \" << servers.size() << \" usable servers from File \"\n          << params_->fileName;\n  callback_->serverListAvailable(std::move(servers));\n  delete this;\n}\n\nFileServerListGenerator::FileServerListGenerator(const string& fileName,\n                                                 const FileType fileType,\n                                                 const string& poolName,\n                                                 const uint16_t port)\n    : params_(fileName, fileType, poolName, port) {\n  if (fileType == FileType::PLAIN_TEXT && !poolName.empty()) {\n    throw std::invalid_argument(\n        \"FileServerListGenerator cannot accept a non-empty poolName parameter \"\n        \"when FileType is PLAIN_TEXT.\");\n  }\n  if (fileType == FileType::JSON && poolName.empty()) {\n    throw std::invalid_argument(\n        \"FileServerListGenerator cannot accept an empty poolName parameter \"\n        \"when FileType is JSON.\");\n  }\n}\n\nvoid FileServerListGenerator::listServers(Callback* callback,\n                                          milliseconds timeout) {\n  auto gen = new FileGenerator(&params_, callback);\n  callback->resetGenerator(gen);\n  gen->run(timeout);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/FileServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/pools/generators/ServerListGenerator.h>\n#include <proxygen/lib/utils/Exception.h>\n#include <sys/stat.h>\n\nnamespace proxygen {\n\n/*\n * A ServerListGenerator implementation that gets the server list from\n * a file.\n */\nclass FileServerListGenerator : public ServerListGenerator {\n public:\n  enum class FileType { PLAIN_TEXT, JSON, THRIFT, VIP_CLUSTER_MAP };\n  // If FileType is specified as PLAIN_TEXT, we will read the file line by line.\n  // If FileType is specified as JSON, we will parse it as a json and take the\n  // entry that's named poolName.\n  explicit FileServerListGenerator(\n      const std::string& fileName,\n      const FileType fileType = FileType::PLAIN_TEXT,\n      const std::string& poolName = \"\",\n      const uint16_t port = 0);\n\n  void listServers(Callback* callback,\n                   std::chrono::milliseconds timeout) override;\n\n protected:\n  struct Params {\n    explicit Params(std::string file,\n                    const FileType fileType,\n                    std::string pool,\n                    const uint16_t port)\n        : fileName(std::move(file)),\n          fileType(fileType),\n          poolName(std::move(pool)),\n          port(port) {\n    }\n    std::string fileName;\n    FileType fileType;\n    std::string poolName;\n    uint16_t port;\n  };\n\n  class FileGenerator : public ServerListGenerator::Generator {\n   public:\n    FileGenerator(Params* params, Callback* callback)\n        : params_(params), callback_(callback) {\n    }\n    ~FileGenerator() override = default;\n    virtual void readFile(std::string& filePath, std::string& content);\n    virtual void run(std::chrono::milliseconds ms);\n    void cancelServerListRequest() override;\n\n   protected:\n    Params* params_;\n    Callback* callback_;\n  };\n\n  Params params_;\n\n private:\n  // Forbidden copy constructor and assignment operator\n  FileServerListGenerator(FileServerListGenerator const&) = delete;\n  FileServerListGenerator& operator=(FileServerListGenerator const&) = delete;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/MemberGroupConfig.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n\nnamespace proxygen {\nusing MemberGroupId = int32_t;\nconst MemberGroupId kInvalidPoolMemberGroupId{-1};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/ServerConfig.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <map>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include <folly/SocketAddress.h>\n#include <proxygen/lib/pools/generators/MemberGroupConfig.h>\n\nnamespace proxygen {\n\nstruct ServerConfig {\n  ServerConfig(std::string name,\n               folly::SocketAddress address,\n               std::map<std::string, std::string> properties = {})\n      : name(std::move(name)),\n        address(std::move(address)),\n        properties(std::move(properties)) {\n  }\n\n  std::string name;\n  folly::SocketAddress address;\n  // A field for other addresses that alias the same server.\n  // For example a server may have a v4 and a v6 address.\n  // Most vector implementations start with a cap of 0 so minimal memory\n  // would be used when unused and is why this is still separated from\n  // the above preferred address.\n  std::vector<folly::SocketAddress> altAddresses;\n  std::map<std::string, std::string> properties;\n  // Optional parameter. It's only set if a server belongs to a group, which\n  // is configured in Pool Config.\n  MemberGroupId groupId_{kInvalidPoolMemberGroupId};\n\n  bool operator==(const ServerConfig& other) const {\n    return name == other.name && address == other.address &&\n           altAddresses == other.altAddresses &&\n           properties == other.properties && groupId_ == other.groupId_;\n  }\n\n  bool operator<(const ServerConfig& other) const {\n    return address < other.address;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/ServerListGenerator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/pools/generators/ServerListGenerator.h>\n\n#include <folly/io/async/EventBase.h>\n\nusing folly::EventBase;\nusing std::vector;\nusing std::chrono::milliseconds;\n\nnamespace proxygen {\n\nvoid ServerListGeneratorIf::attachEventBase(EventBase* eventBase) {\n  CHECK(!eventBase_);\n  CHECK(eventBase->isInEventBaseThread());\n\n  eventBase_ = eventBase;\n}\n\nvoid ServerListGeneratorIf::detachEventBase() {\n  CHECK(!eventBase_ || eventBase_->isInEventBaseThread());\n\n  eventBase_ = nullptr;\n}\n\nvoid ServerListGenerator::listServersBlocking(vector<ServerConfig>* results,\n                                              milliseconds timeout) {\n  // Run a EventBase to drive the asynchronous listServers() call until it\n  // finishes.\n  EventBase eventBase;\n  ServerListCallback callback;\n  attachEventBase(&eventBase);\n  listServers(&callback, timeout);\n  eventBase.loop();\n  detachEventBase();\n\n  if (callback.status != ServerListCallback::SUCCESS) {\n    if (!callback.errorPtr) {\n      LOG(FATAL)\n          << \"ServerListGenerator finished without invoking callback, timeout:\"\n          << timeout.count();\n    }\n    std::rethrow_exception(callback.errorPtr);\n  }\n\n  results->swap(callback.servers);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/ServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <utility>\n#include <vector>\n\n#include <folly/SocketAddress.h>\n#include <folly/io/async/AsyncTimeout.h>\n#include <glog/logging.h>\n\n#include <proxygen/lib/pools/generators/MemberGroupConfig.h>\n#include <proxygen/lib/pools/generators/ServerConfig.h>\n\nnamespace proxygen {\n\nclass ServerListGeneratorIf {\n public:\n  explicit ServerListGeneratorIf(folly::EventBase* eventBase = nullptr)\n      : eventBase_(eventBase) {\n  }\n\n  virtual ~ServerListGeneratorIf() = default;\n\n  /**\n   * Attach and detach from a EventBase.\n   *\n   * ServerListGenerator implementations should be provided an event base before\n   * any calls to listServers*(). This can be done via the constructor or by\n   * calling attachEventBase(), and they MUST be able to handle a call to\n   * attachEventBase() after detachEventBase().\n   */\n  virtual void attachEventBase(folly::EventBase* eventBase);\n  virtual void detachEventBase();\n\n  void setGroupId(const MemberGroupId serverGroupId) {\n    groupId_ = serverGroupId;\n  }\n\n  [[nodiscard]] MemberGroupId getGroupId() const {\n    return groupId_;\n  }\n\n protected:\n  /**\n   * This event base will be used to schedule work for any internal calls that\n   * need one. For example, for querying SMC tiers. There is no guarantee that\n   * all work will run on it nor that it will be used at all.\n   */\n  folly::EventBase* eventBase_{nullptr};\n  /**\n   * All servers generated by this generator could belong to a server group.\n   * This is the ID of such group. Note that it's optional, and only useful\n   * if you have multiple ServerListGenerators and are grouping them in\n   * different ways.\n   */\n  MemberGroupId groupId_{kInvalidPoolMemberGroupId};\n};\n\n/**\n * ServerListGenerator is an abstract class that provides an API for obtaining\n * a list of servers.\n *\n * The same ServerListGenerator may be invoked periodically, and the list of\n * servers returned may change over time.\n */\nclass ServerListGenerator : public ServerListGeneratorIf {\n public:\n  /**\n   * Handle that can be used to stop any request in progress\n   **/\n  class Generator {\n   public:\n    Generator() = default;\n\n    Generator(const Generator&) = default;\n    Generator& operator=(const Generator&) = default;\n\n    Generator(Generator&&) = default;\n    Generator& operator=(Generator&&) = default;\n\n    virtual ~Generator() = default;\n\n    virtual void cancelServerListRequest() = 0;\n  };\n\n  class Callback {\n   public:\n    Callback() : gen_(nullptr) {\n    }\n\n    Callback(const Callback&) = default;\n    Callback& operator=(const Callback&) = default;\n\n    Callback(Callback&&) = default;\n    Callback& operator=(Callback&&) = default;\n\n    virtual ~Callback() {\n      // An act of gentlemen\n      cancelServerListRequest();\n    }\n\n    /**\n     * onServerListAvailable() will be invoked when the list of servers is\n     * available.\n     *\n     * The results are passed in as an rvalue-reference.  The callback is free\n     * to swap out the contents of the results vector and then store it as\n     * their own.\n     */\n    void serverListAvailable(std::vector<ServerConfig> results) noexcept {\n      resetGenerator();\n      onServerListAvailable(std::move(results));\n    }\n\n    virtual void onServerListAvailable(\n        std::vector<ServerConfig>&& results) noexcept = 0;\n\n    /**\n     * onServerListError will be invoked if there was a problem fetching servers\n     * list.\n     */\n    void serverListError(std::exception_ptr error) noexcept {\n      resetGenerator();\n      onServerListError(std::move(error));\n    }\n\n    virtual void onServerListError(std::exception_ptr error) noexcept = 0;\n\n    /**\n     * Cancel all the server generator requests running at the moment\n     */\n    void cancelServerListRequest() {\n      if (gen_) {\n        gen_->cancelServerListRequest();\n        resetGenerator();\n      }\n    }\n\n    void resetGenerator(Generator* g = nullptr, bool takeOwnership = false) {\n      // This function may get called when Generator is not instantiated.\n      // This can happen if the tier is down and hence the\n      // generator is not even created, but instead serverListAvailable gets\n      // called with empty list\n      if ((gen_ == nullptr) && (g == nullptr)) {\n        return;\n      }\n      CHECK((gen_ == nullptr) ^ (g == nullptr)) << gen_ << \" \" << g;\n\n      // Subclasses first call resetGenerator(gen, takeOwnership) after creating\n      // a generator. After a success/error/timeout callback is called, this\n      // class calls resetGenerator(nullptr)\n      // gen_ can be self-owning or we can have ServerListGenerator own gen_.\n      // In the self-owning case, the subclassed ServerListGenerator uses the\n      // default value for takeOwnership (= false) and the generator is expected\n      // to delete itself after calling a callback.\n      // The non-self-owning case is needed when the generator may  run on a\n      // different thread and hence it would be unwise to delete itself\n      // before a callback calls resetGenerator()\n      if (g == nullptr && takeOwnershipOfGenerator_) {\n        delete gen_;\n      }\n      gen_ = g;\n      takeOwnershipOfGenerator_ = takeOwnership;\n    }\n\n    /*\n     * Represents the current on-going request\n     */\n    Generator* gen_{nullptr};\n    bool takeOwnershipOfGenerator_{false};\n  };\n\n  explicit ServerListGenerator(folly::EventBase* base = nullptr)\n      : ServerListGeneratorIf(base) {\n  }\n\n  // Forbidden copy constructor and assignment operator\n  ServerListGenerator(ServerListGenerator const&) = delete;\n  ServerListGenerator& operator=(ServerListGenerator const&) = delete;\n\n  ServerListGenerator(ServerListGenerator&&) = default;\n  ServerListGenerator& operator=(ServerListGenerator&&) = default;\n\n  ~ServerListGenerator() override {\n    detachEventBase();\n  }\n\n  /**\n   * Generate the list of servers and invoke the callback when completed.\n   */\n  virtual void listServers(Callback* callback,\n                           std::chrono::milliseconds timeout) = 0;\n\n  /**\n   * Generate the list of servers synchronously.\n   *\n   * This method will block and not return until the list of servers is\n   * available.  If an error occurs, it will throw an exception.\n   *\n   * This is intended as a convenience API for call sites that do not need\n   * asynchronous operation.\n   *\n   * The vector \"results\" will be replaced by the server list.\n   *\n   * The ServerListGenerator must not already have been attached to a\n   * EventBase.\n   */\n  virtual void listServersBlocking(std::vector<ServerConfig>* results,\n                                   std::chrono::milliseconds timeout);\n};\n\nusing ServerConfigList = std::vector<ServerConfig>;\n\n// A default ServerListGenerator::Callback interface for consumers that\n// simply want the call status and result returned directly.\nclass ServerListCallback : public ServerListGenerator::Callback {\n public:\n  enum StatusEnum : std::uint8_t {\n    NOT_FINISHED,\n    SUCCESS,\n    ERROR,\n    CANCELLED,\n  };\n\n  explicit ServerListCallback() = default;\n\n  void onServerListAvailable(\n      std::vector<ServerConfig>&& results) noexcept override {\n    servers.swap(results);\n    status = SUCCESS;\n  }\n  void onServerListError(std::exception_ptr error) noexcept override {\n    errorPtr = std::move(error);\n    status = ERROR;\n  }\n  virtual void serverListRequestCancelled() {\n    status = CANCELLED;\n  }\n\n  StatusEnum status{NOT_FINISHED};\n  std::vector<ServerConfig> servers;\n  std::exception_ptr errorPtr;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_pools_generators_coro_server_list_generator\n  EXPORTED_DEPS\n    proxygen_pools_generators_member_group_config\n    proxygen_pools_generators_server_config\n    proxygen_pools_generators_server_list_generator\n    Folly::folly_coro_task\n)\n\nproxygen_add_library(proxygen_pools_generators_coro_file_server_list_generator\n  SRCS\n    FileCoroServerListGenerator.cpp\n  DEPS\n    Folly::folly_file_util\n  EXPORTED_DEPS\n    proxygen_pools_generators_coro_server_list_generator\n)\n\nproxygen_add_library(proxygen_pools_generators_coro_json_file_server_list_generator\n  SRCS\n    JsonFileCoroServerListGenerator.cpp\n  DEPS\n    Folly::folly_json_dynamic\n    Folly::folly_network_address\n  EXPORTED_DEPS\n    proxygen_pools_generators_coro_file_server_list_generator\n)\n\nproxygen_add_library(proxygen_pools_generators_coro_plain_text_file_server_list_generator\n  SRCS\n    PlainTextFileCoroServerListGenerator.cpp\n  DEPS\n    Folly::folly_network_address\n  EXPORTED_DEPS\n    proxygen_pools_generators_coro_file_server_list_generator\n)\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/CoroServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/coro/Task.h>\n#include <proxygen/lib/pools/generators/MemberGroupConfig.h>\n#include <proxygen/lib/pools/generators/ServerConfig.h>\n#include <proxygen/lib/pools/generators/ServerListGenerator.h>\n\nnamespace proxygen {\n\n/**\n * CoroServerListGenerator is an abstract class that provides an API for\n * obtaining a list of servers.\n *\n * The same CoroServerListGenerator may be invoked periodically, and the list of\n * servers returned may change over time.\n */\nclass CoroServerListGenerator : public ServerListGeneratorIf {\n public:\n  explicit CoroServerListGenerator(folly::EventBase* eventBase = nullptr)\n      : ServerListGeneratorIf(eventBase) {\n  }\n\n  CoroServerListGenerator(CoroServerListGenerator&&) = default;\n  CoroServerListGenerator& operator=(CoroServerListGenerator&&) = default;\n\n  // Forbidden copy constructor and assignment operator\n  CoroServerListGenerator(CoroServerListGenerator const&) = delete;\n  CoroServerListGenerator& operator=(CoroServerListGenerator const&) = delete;\n\n  ~CoroServerListGenerator() override = default;\n\n  /**\n   * Generate the list of servers and invoke the callback when completed.\n   */\n  virtual folly::coro::Task<std::vector<ServerConfig>> listServers() = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/FileCoroServerListGenerator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/FileUtil.h>\n#include <proxygen/lib/pools/generators/coro/FileCoroServerListGenerator.h>\n\nnamespace proxygen {\n\nFileCoroServerListGenerator::FileCoroServerListGenerator(std::string fileName)\n    : fileName_(std::move(fileName)) {\n}\n\nfolly::coro::Task<std::vector<ServerConfig>>\nFileCoroServerListGenerator::listServers() {\n  VLOG(4) << \"Looking up server list from File Handle \" << fileName_;\n\n  std::string content;\n  if (!folly::readFile(fileName_.c_str(), content)) {\n    co_yield folly::coro::co_error(\n        std::runtime_error(\"Error reading file \" + fileName_));\n  }\n\n  auto servers = parseFile(content);\n\n  VLOG(4) << \"Found \" << servers.size() << \" usable servers from File \"\n          << fileName_;\n  co_return servers;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/FileCoroServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/pools/generators/coro/CoroServerListGenerator.h>\n\nnamespace proxygen {\n\n/*\n * The base class for a CoroServerListGenerator implementation that gets the\n * server list from a file with some encoding.\n */\nclass FileCoroServerListGenerator : public CoroServerListGenerator {\n public:\n  explicit FileCoroServerListGenerator(std::string fileName);\n\n  FileCoroServerListGenerator(FileCoroServerListGenerator&&) noexcept = default;\n  FileCoroServerListGenerator& operator=(\n      FileCoroServerListGenerator&&) noexcept = default;\n\n  // Forbidden copy constructor and assignment operator\n  FileCoroServerListGenerator(FileCoroServerListGenerator const&) = delete;\n  FileCoroServerListGenerator& operator=(FileCoroServerListGenerator const&) =\n      delete;\n\n  ~FileCoroServerListGenerator() override = default;\n\n  folly::coro::Task<std::vector<ServerConfig>> listServers() override;\n\n protected:\n  virtual std::vector<ServerConfig> parseFile(const std::string& contents) = 0;\n\n  std::string fileName_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/JsonFileCoroServerListGenerator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/pools/generators/coro/JsonFileCoroServerListGenerator.h\"\n\n#include <folly/SocketAddress.h>\n#include <folly/json/dynamic.h>\n#include <folly/json/json.h>\n\nnamespace proxygen {\n\nJsonFileCoroServerListGenerator::JsonFileCoroServerListGenerator(\n    std::string fileName, std::string poolName)\n    : FileCoroServerListGenerator(std::move(fileName)),\n      poolName_(std::move(poolName)) {\n  if (poolName_.empty()) {\n    throw std::invalid_argument(\"poolName cannot be empty\");\n  }\n}\n\nstd::vector<ServerConfig> JsonFileCoroServerListGenerator::parseFile(\n    const std::string& contents) {\n\n  std::vector<ServerConfig> servers;\n  folly::dynamic parsedJson = folly::parseJson(contents);\n  folly::dynamic poolMembers = parsedJson.getDefault(poolName_, -1);\n  // If we cannot parse out an arrray out of that.\n  if (!poolMembers.isArray()) {\n    throw std::invalid_argument(\"Cannot find a valid pool \" + poolName_ +\n                                \" in file \" + fileName_);\n  }\n  // Now we have an array.\n  for (const auto& e : poolMembers) {\n    folly::SocketAddress address;\n    address.setFromHostPort(e.asString());\n    servers.emplace_back(address.getAddressStr(), address);\n  }\n\n  return servers;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/JsonFileCoroServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/pools/generators/coro/FileCoroServerListGenerator.h>\n\nnamespace proxygen {\n\nclass JsonFileCoroServerListGenerator : public FileCoroServerListGenerator {\n public:\n  JsonFileCoroServerListGenerator(std::string filename, std::string poolName);\n\n  std::vector<ServerConfig> parseFile(const std::string& contents) override;\n\n private:\n  std::string poolName_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/PlainTextFileCoroServerListGenerator.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/pools/generators/coro/PlainTextFileCoroServerListGenerator.h\"\n\n#include <sstream>\n#include <string>\n\n#include <folly/SocketAddress.h>\n\nnamespace proxygen {\n\nPlainTextFileCoroServerListGenerator::PlainTextFileCoroServerListGenerator(\n    std::string fileName)\n    : FileCoroServerListGenerator(std::move(fileName)) {\n}\n\nstd::vector<ServerConfig> PlainTextFileCoroServerListGenerator::parseFile(\n    const std::string& contents) {\n  std::vector<ServerConfig> servers;\n  std::stringstream sstream(contents);\n  std::string line;\n  while (std::getline(sstream, line)) {\n    folly::SocketAddress address;\n    address.setFromHostPort(line);\n    servers.emplace_back(address.getAddressStr(), address);\n  }\n  return servers;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/pools/generators/coro/PlainTextFileCoroServerListGenerator.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/pools/generators/coro/FileCoroServerListGenerator.h>\n\nnamespace proxygen {\n\nclass PlainTextFileCoroServerListGenerator\n    : public FileCoroServerListGenerator {\n public:\n  explicit PlainTextFileCoroServerListGenerator(std::string filename);\n\n  std::vector<ServerConfig> parseFile(const std::string& contents) override;\n\n private:\n  std::string poolName_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_sampling_sampled\n  EXPORTED_DEPS\n    proxygen_sampling\n)\n\nproxygen_add_library(proxygen_sampling_multi_sampled\n  EXPORTED_DEPS\n    proxygen_sampling\n    Folly::folly_container_f14_hash\n)\n\nproxygen_add_library(proxygen_sampling\n  SRCS\n    Sampling.cpp\n  DEPS\n    Folly::folly_hash_hash\n    Folly::folly_random\n    glog::glog\n  EXPORTED_DEPS\n    Folly::folly_function\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_sampling_sampling_functions\n  EXPORTED_DEPS\n    mvfst::mvfst_codec_types\n    Folly::folly_hash_hash\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/sampling/MultiSampled.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <string>\n\n#include <folly/container/F14Map.h>\n#include <proxygen/lib/sampling/Sampling.h>\n\nnamespace proxygen {\n\nclass MultiSampled {\n public:\n  MultiSampled() = default;\n\n  virtual ~MultiSampled() = default;\n\n  void sample(const std::string& tag, const Sampling& sampling) {\n    if (sampling.isLucky()) {\n      weights_[tag] = sampling.getWeight();\n    }\n  }\n\n  [[nodiscard]] int getSamplingWeight(const std::string& tag) const {\n    auto it = weights_.find(tag);\n    if (it == weights_.end()) {\n      return 0;\n    }\n    return it->second;\n  }\n\n  [[nodiscard]] bool isSampled() const {\n    // we are sampled if we have at least a non-zero weight\n    return weights_.size() > 0;\n  }\n\n  [[nodiscard]] bool isSampled(const std::string& tag) const {\n    return getSamplingWeight(tag) > 0;\n  }\n\n private:\n  folly::F14FastMap<std::string, int> weights_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/Sampled.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/sampling/Sampling.h>\n\nnamespace proxygen {\n\n/**\n * implements the concept of a sampled object, which maintains the side effects\n * of using a sampling object. Designed to be inherited.\n */\nclass Sampled {\n\n public:\n  Sampled() = default;\n\n  explicit Sampled(const Sampling& sampling) {\n    sample(sampling);\n  }\n\n  virtual ~Sampled() = default;\n\n  void sample(const Sampling& sampling) {\n    if (sampling.isLucky()) {\n      samplingWeight_ = sampling.getWeight();\n    }\n  }\n\n  [[nodiscard]] uint32_t getSamplingWeight() const {\n    return samplingWeight_;\n  }\n\n  [[nodiscard]] bool isSampled() const {\n    return samplingWeight_ > 0;\n  }\n\n protected:\n  uint32_t samplingWeight_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/Sampling.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/sampling/Sampling.h>\n\n#include <limits>\n\n#include <glog/logging.h>\n\n#include <folly/Random.h>\n#include <folly/hash/Hash.h>\n\nnamespace proxygen {\n\nSampling::Sampling(double rate) {\n  updateRate(rate);\n}\n\nSampling::~Sampling() = default;\n\nvoid Sampling::updateRate(double rate) {\n  CHECK(rate >= 0.0 && rate <= 1.0);\n  rate_ = rate;\n  weight_ = rateToWeight(rate);\n}\n\nuint32_t Sampling::rateToWeight(double rate) {\n  // we lose some decimals when the sampling fraction is very low\n  uint32_t scaledRate = kErrTolerance * rate;\n  // avoid division by zero\n  if (scaledRate == 0) {\n    return 0;\n  }\n  return kErrTolerance / scaledRate;\n}\n\nuint32_t Sampling::rateToKey(double rate) {\n  return std::numeric_limits<uint32_t>::max() * rate;\n}\n\nuint32_t Sampling::getIntRate() const {\n  return rateToKey(rate_);\n}\n\nbool Sampling::isLucky(uint32_t samplingKey) {\n  return folly::Random::rand32() <= samplingKey;\n}\n\nbool Sampling::isLucky(const std::string& key) const {\n  return isLucky(folly::StringPiece(key));\n}\n\nbool Sampling::isLucky(const folly::StringPiece key) const {\n  // have a shortcut path for 1 (enabled) and 0 (disabled) as these are\n  // gonna be the most common\n  if (weight_ == 1) {\n    return true;\n  } else if (weight_ == 0) {\n    return false;\n  }\n  // else compute the hash\n  return folly::hash::fnv32_buf_BROKEN(key.data(), key.size()) < getIntRate();\n}\n\nbool Sampling::isLucky() const {\n  // short path for rate = 1.0\n  if (rate_ >= 1.0) {\n    return true;\n  }\n  // roll the dice\n  return folly::Random::randDouble01() < rate_;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/Sampling.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <string>\n\n#include <folly/Function.h>\n#include <folly/Range.h>\n\nnamespace proxygen {\n\nclass Sampling {\n public:\n  static const uint32_t kErrTolerance = 1000000;\n\n  explicit Sampling(double rate = 1.0);\n  Sampling(const Sampling&) = default;\n\n  virtual ~Sampling();\n\n  static uint32_t rateToWeight(double rate);\n\n  static uint32_t rateToKey(double rate);\n\n  static bool isLucky(uint32_t samplingKey);\n\n  [[nodiscard]] bool isLucky() const;\n\n  [[nodiscard]] bool isLucky(const std::string& key) const;\n  [[nodiscard]] bool isLucky(folly::StringPiece key) const;\n\n  [[nodiscard]] uint32_t getWeight() const {\n    return weight_;\n  }\n\n  void updateRate(double rate);\n\n  [[nodiscard]] uint32_t getIntRate() const;\n\n  void runSampled(folly::FunctionRef<void()> func) {\n    if (isLucky()) {\n      func();\n    }\n  }\n\n private:\n  double rate_{0.0};\n  uint32_t weight_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/SamplingFunctions.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/hash/Hash.h>\n#include <quic/codec/QuicConnectionId.h>\n\n#include <cstdint>\n#include <limits>\n\nnamespace proxygen {\n\ninline bool shouldLogQuicConnection(const quic::ConnectionId& connId,\n                                    int64_t salt,\n                                    uint32_t weight) {\n  // Logging is off\n  if (weight == 0) {\n    return false;\n  }\n  // Hash the combination of connection id and salt.\n  // If the hash value is less than or equal to (2^32-1)/weight, log,\n  // in which case sample ratio is 1/weight.\n\n  // Hash code stolen from folly::hash::hash_combine_generic.\n  // Reason why not using hash_combine_generic directly is that we want to\n  // generate a hash value whose type size is consistent across systems,\n  // so that server and client can be sure that they are logging the same\n  // connection. However hash_combine_generic returns a size_t type which does\n  // not satisfy this requirement.\n  uint32_t connIdNumHash =\n      folly::hash::fnv32_buf_BROKEN(connId.data(), connId.size());\n  uint32_t saltHash = folly::hash::fnv32_buf_BROKEN(&salt, sizeof(salt));\n  uint32_t hash =\n      folly::hash::twang_32from64((uint64_t(connIdNumHash) << 32) | saltHash);\n  if (hash <= std::numeric_limits<uint32_t>::max() / weight) {\n    return true;\n  }\n  return false;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/sampling/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET SamplingTest DEPENDS proxygen testmain)\n"
  },
  {
    "path": "proxygen/lib/sampling/test/SamplingTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <limits>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/sampling/Sampling.h>\n\nusing proxygen::Sampling;\nusing std::string;\n\nTEST(SamplingTests, Basic) {\n  Sampling rate(1.0);\n  EXPECT_EQ(rate.isLucky(), true);\n  EXPECT_EQ(rate.getWeight(), 1);\n\n  rate = Sampling(0.0);\n  EXPECT_EQ(rate.isLucky(), false);\n  EXPECT_EQ(rate.getWeight(), 0);\n\n  rate = Sampling(0.5);\n  EXPECT_EQ(rate.getWeight(), 2);\n\n  rate = Sampling(0.00001);\n  EXPECT_EQ(rate.getWeight(), 100000);\n\n  rate = Sampling(0.000001);\n  EXPECT_EQ(rate.getWeight(), 1000000);\n\n  // this is beyond the error tolerance\n  rate = Sampling(0.000000000001);\n  EXPECT_EQ(rate.getWeight(), 0);\n}\n\nTEST(SamplingTests, UpdateRate) {\n  Sampling sampling(0.5);\n  EXPECT_EQ(sampling.getWeight(), 2);\n  sampling.updateRate(0.05);\n  EXPECT_EQ(sampling.getWeight(), 20);\n}\n\nTEST(SamplingTests, Key) {\n  Sampling sampling(1.0);\n  EXPECT_EQ(sampling.getIntRate(), std::numeric_limits<uint32_t>::max());\n  sampling.updateRate(0);\n  EXPECT_EQ(sampling.getIntRate(), 0);\n  sampling.updateRate(0.5);\n  EXPECT_EQ(sampling.getIntRate(), std::numeric_limits<uint32_t>::max() / 2);\n}\n\nTEST(SamplingTests, HashKey) {\n  Sampling sampling(1.0);\n  string key = \"test\";\n  EXPECT_TRUE(sampling.isLucky(key));\n  sampling.updateRate(0.0);\n  EXPECT_FALSE(sampling.isLucky(key));\n  sampling.updateRate(0.5);\n  EXPECT_FALSE(sampling.isLucky(key));\n  // repeat\n  EXPECT_FALSE(sampling.isLucky(key));\n}\n\nTEST(SamplingTests, runSampled) {\n  {\n    int counter = 0;\n\n    Sampling sampling(1.0);\n    for (int i = 0; i < 3; i++) {\n      sampling.runSampled([&] { ++counter; });\n    }\n    EXPECT_EQ(counter, 3);\n  }\n  {\n    int counter = 0;\n    Sampling sampling(0);\n    for (int i = 0; i < 3; i++) {\n      sampling.runSampled([&] { ++counter; });\n    }\n    EXPECT_EQ(counter, 0);\n  }\n  {\n    int counter = 0;\n    Sampling sampling(0.5);\n    for (int i = 0; i < 100; i++) {\n      sampling.runSampled([&] { ++counter; });\n    }\n    EXPECT_GT(counter, 0);\n    EXPECT_LT(counter, 100);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/services/AcceptorConfiguration.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <fcntl.h>\n#include <folly/String.h>\n#include <folly/io/async/AsyncSocket.h>\n#include <list>\n#include <proxygen/lib/http/codec/HTTPSettings.h>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <wangle/acceptor/ServerSocketConfig.h>\n#include <zlib.h>\n\nnamespace proxygen {\nclass HeaderIndexingStrategy;\n\n/**\n * Configuration for a single Acceptor.\n *\n * This configures not only accept behavior, but also some types of SSL\n * behavior that may make sense to configure on a per-VIP basis (e.g. which\n * cert(s) we use, etc).\n */\nstruct AcceptorConfiguration : public wangle::ServerSocketConfig {\n  /**\n   * Determines if connection should respect HTTP2 priorities\n   **/\n  bool HTTP2PrioritiesEnabled{true};\n\n  /**\n   * The number of milliseconds a transaction can be idle before we close it.\n   */\n  std::chrono::milliseconds transactionIdleTimeout{600000};\n\n  /**\n   * The name of the protocol to use on non-TLS connections.\n   */\n  std::string plaintextProtocol;\n\n  /**\n   * True if HTTP/1.0 messages should always be serialized as HTTP/1.1\n   *\n   * Maximizes connection re-use\n   */\n  bool forceHTTP1_0_to_1_1{false};\n\n  /**\n   * HTTP/2 or SPDY settings for this acceptor\n   */\n  SettingsList egressSettings;\n\n  /**\n   * The maximum number of transactions the remote could initiate\n   * per connection on protocols that allow multiplexing.\n   */\n  uint32_t maxConcurrentIncomingStreams{0};\n\n  /**\n   * Flow control parameters.\n   *\n   *  initialReceiveWindow     = amount to advertise to peer via SETTINGS\n   *  receiveStreamWindowSize  = amount to increase per-stream window via\n   *                             WINDOW_UPDATE\n   *  receiveSessionWindowSize = amount to increase per-session window via\n   *                             WINDOW_UPDATE\n   *                             This also controls the size of the per-session\n   *                             read buffer.\n   */\n  size_t initialReceiveWindow{65536};\n  size_t receiveStreamWindowSize{65536};\n  size_t receiveSessionWindowSize{65536};\n\n  /**\n   * These parameters control how many bytes HTTPSession's will buffer in user\n   * space before applying backpressure to handlers.  -1 means use the\n   * built-in HTTPSession default (64kb)\n   */\n  int64_t writeBufferLimit{-1};\n\n  /**\n   * Determines if HTTP2 ping is enabled on connection\n   **/\n  bool HTTP2PingEnabled{false};\n\n  /**\n   * When the server sends a complete response prior to the client sending an\n   * entire request (for non-upgraded streams), the server will subsequently\n   * write RST_STREAM/NO_ERROR (h2) or STOP_SENDING/NO_ERROR (h3). This has no\n   * effect in http/1.1.\n   */\n  bool serverEarlyResponseEnabled{false};\n\n  /* Strategy for which headers to insert into HPACK/QPACK dynamic table */\n  const HeaderIndexingStrategy* headerIndexingStrategy{nullptr};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_services_http_acceptor\n  EXPORTED_DEPS\n    proxygen_services_acceptor_configuration\n    proxygen_utils_shared_wheel_timer\n    proxygen_utils_timeoutset\n    wangle::wangle_acceptor\n    Folly::folly_io_async_server_socket\n)\n\nproxygen_add_library(proxygen_services_acceptor_configuration\n  EXPORTED_DEPS\n    proxygen_http_codec_codec_common\n    wangle::wangle_acceptor\n    Folly::folly_io_async_async_socket\n    Folly::folly_string\n    ZLIB::ZLIB\n)\n\nproxygen_add_library(proxygen_services_service_configuration)\n\nproxygen_add_library(proxygen_services\n  SRCS\n    RequestWorkerThread.cpp\n    RequestWorkerThreadNoExecutor.cpp\n    Service.cpp\n  DEPS\n    Folly::folly_io_async_event_base_manager\n  EXPORTED_DEPS\n    proxygen_services_worker\n    proxygen_utils_acceptor_address\n    wangle::wangle_acceptor\n    wangle::wangle_acceptor_acceptor_core\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_server_socket\n)\n\nproxygen_add_library(proxygen_services_worker\n  SRCS\n    WorkerThread.cpp\n  DEPS\n    Folly::folly_io_async_event_base_manager\n    Folly::folly_io_async_io_uring_backend\n    Folly::folly_string\n    glog::glog\n  EXPORTED_DEPS\n    Folly::folly_io_async_async_base\n    Folly::folly_portability\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/services/HTTPAcceptor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/AsyncServerSocket.h>\n#include <memory>\n#include <proxygen/lib/services/AcceptorConfiguration.h>\n#include <proxygen/lib/utils/AsyncTimeoutSet.h>\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n#include <wangle/acceptor/Acceptor.h>\n\nnamespace proxygen {\n\nclass HTTPAcceptor : public wangle::Acceptor {\n public:\n  explicit HTTPAcceptor(std::shared_ptr<const AcceptorConfiguration> accConfig)\n      : Acceptor(std::move(accConfig)) {\n  }\n\n  void init(folly::AsyncServerSocket* serverSocket,\n            folly::EventBase* eventBase,\n            wangle::SSLStats* /*stat*/ = nullptr,\n            std::shared_ptr<const fizz::server::FizzServerContext> fizzCtx =\n                nullptr) override {\n    timer_ = createTransactionTimeoutSet(eventBase);\n    Acceptor::init(serverSocket, eventBase, nullptr, fizzCtx);\n  }\n\n  [[nodiscard]] std::shared_ptr<const AcceptorConfiguration> getConfig() const {\n    return std::static_pointer_cast<const AcceptorConfiguration>(\n        Acceptor::getConfig());\n  }\n\n  /**\n   * Access the general-purpose timeout manager for transactions.\n   */\n  const WheelTimerInstance& getTransactionTimeoutSet() {\n    return *timer_;\n  }\n\n protected:\n  std::unique_ptr<WheelTimerInstance> timer_;\n\n  std::unique_ptr<WheelTimerInstance> createTransactionTimeoutSet(\n      folly::EventBase* eventBase) {\n    return std::make_unique<WheelTimerInstance>(\n        getConfig()->transactionIdleTimeout, eventBase);\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/RequestWorkerThread.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/services/RequestWorkerThread.h>\n\n#include <csignal>\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/lib/services/ServiceWorker.h>\n\nnamespace proxygen {\n\nstatic const uint32_t requestIdBits = 56;\nstatic const uint64_t requestIdMask = ((1ULL << requestIdBits) - 1);\n\nthread_local RequestWorkerThread* RequestWorkerThread::currentRequestWorker_ =\n    nullptr;\n\nRequestWorkerThread::RequestWorkerThread(FinishCallback& callback,\n                                         uint8_t threadId,\n                                         folly::EventBase* evb)\n    : nextRequestId_(static_cast<uint64_t>(threadId) << requestIdBits),\n      callback_(callback),\n      evb_(evb) {\n}\n\nRequestWorkerThread::~RequestWorkerThread() {\n  currentRequestWorker_ = nullptr;\n}\n\nuint8_t RequestWorkerThread::getWorkerId() const {\n  return static_cast<uint8_t>(nextRequestId_ >> requestIdBits);\n}\n\nuint64_t RequestWorkerThread::nextRequestId() {\n  uint64_t requestId = getRequestWorkerThread()->nextRequestId_;\n  getRequestWorkerThread()->nextRequestId_ =\n      (requestId & ~requestIdMask) | ((requestId + 1) & requestIdMask);\n  return requestId;\n}\n\nvoid RequestWorkerThread::flushStats() {\n  CHECK(getEventBase()->isInEventBaseThread());\n  for (auto& p : serviceWorkers_) {\n    p.second->flushStats();\n  }\n}\n\nvoid RequestWorkerThread::setup() {\n  CHECK(evb_);\n  evb_->runImmediatelyOrRunInEventBaseThreadAndWait([&]() {\n    sigset_t ss;\n\n    // Ignore some signals\n    sigemptyset(&ss);\n    sigaddset(&ss, SIGHUP);\n    sigaddset(&ss, SIGINT);\n    sigaddset(&ss, SIGQUIT);\n    sigaddset(&ss, SIGUSR1);\n    sigaddset(&ss, SIGUSR2);\n    sigaddset(&ss, SIGPIPE);\n    sigaddset(&ss, SIGALRM);\n    sigaddset(&ss, SIGTERM);\n    sigaddset(&ss, SIGCHLD);\n    sigaddset(&ss, SIGIO);\n    PCHECK(pthread_sigmask(SIG_BLOCK, &ss, nullptr) == 0);\n\n    currentRequestWorker_ = this;\n    callback_.workerStarted(this);\n  });\n}\n\nvoid RequestWorkerThread::forceStop() {\n  evb_->terminateLoopSoon();\n}\n\nvoid RequestWorkerThread::cleanup() {\n  LOG(INFO) << \"Worker \" << unsigned(getWorkerId()) << \" in cleanup\";\n  callback_.workerFinished(this);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/RequestWorkerThread.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <folly/io/async/EventBase.h>\n#include <map>\n#include <wangle/acceptor/LoadShedConfiguration.h>\n\nnamespace proxygen {\n\nclass Service;\nclass ServiceWorker;\n\n/**\n * RequestWorkerThread intended to be used with a folly::Executor, and also\n * contains a list of ServiceWorkers running in this thread.\n */\nclass RequestWorkerThread {\n public:\n  class FinishCallback {\n   public:\n    virtual ~FinishCallback() noexcept = default;\n    virtual void workerStarted(RequestWorkerThread*) = 0;\n    virtual void workerFinished(RequestWorkerThread*) = 0;\n  };\n\n  /**\n   * Create a new RequestWorkerThread.\n   *\n   * @param proxygen  The object to notify when this worker finishes.\n   * @param threadId  A unique ID for this worker.\n   * @param evbName   The event base will ne named to this name (thread name)\n   */\n  RequestWorkerThread(FinishCallback& callback,\n                      uint8_t threadId,\n                      folly::EventBase* evb);\n\n  ~RequestWorkerThread();\n\n  /**\n   * Return a unique 64bit identifier.\n   */\n  static uint64_t nextRequestId();\n\n  /**\n   * Return unique 8bit worker ID.\n   */\n  [[nodiscard]] uint8_t getWorkerId() const;\n\n  static RequestWorkerThread* getRequestWorkerThread() {\n    return currentRequestWorker_;\n  }\n\n  /**\n   * Track the ServiceWorker objects in-use by this worker.\n   */\n  void addServiceWorker(Service* service, ServiceWorker* sw) {\n    CHECK(serviceWorkers_.find(service) == serviceWorkers_.end());\n    serviceWorkers_[service] = sw;\n  }\n\n  /**\n   * For a given service, returns the ServiceWorker associated with this\n   * RequestWorkerThread\n   */\n  ServiceWorker* getServiceWorker(Service* service) const {\n    auto it = serviceWorkers_.find(service);\n    CHECK(it != serviceWorkers_.end());\n    return it->second;\n  }\n\n  /**\n   * Get/set the worker thread's bound load shed configuration instance.\n   * Used by derivative classes.  Updates are propagated seamlessly via\n   * the use of swapping such that threads will automatically see updated\n   * fields on update.\n   */\n  [[nodiscard]] std::shared_ptr<const wangle::LoadShedConfiguration>\n  getLoadShedConfig() const {\n    return loadShedConfig_;\n  }\n  void setLoadShedConfig(\n      std::shared_ptr<const wangle::LoadShedConfiguration> loadShedConfig) {\n    loadShedConfig_.swap(loadShedConfig);\n  }\n\n  /**\n   * Flush any thread-local stats being tracked by our ServiceWorkers.\n   *\n   * This must be invoked from within worker's thread.\n   */\n  void flushStats();\n\n  void forceStop();\n\n  folly::EventBase* getEventBase() {\n    return evb_;\n  }\n\n  void setup();\n  void cleanup();\n\n private:\n  // The next request id within this thread. The id has its highest byte set to\n  // the thread id, so is unique across the process.\n  uint64_t nextRequestId_;\n\n  // The ServiceWorkers executing in this worker\n  std::map<Service*, ServiceWorker*> serviceWorkers_;\n\n  // Every worker instance has their own version of load shed config.\n  // This enables every request worker thread, and derivative there of,\n  // to both access and update this field in a thread-safe way.\n  std::shared_ptr<const wangle::LoadShedConfiguration> loadShedConfig_{nullptr};\n\n  FinishCallback& callback_;\n  folly::EventBase* evb_{nullptr};\n\n  static thread_local RequestWorkerThread* currentRequestWorker_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/RequestWorkerThreadNoExecutor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/services/RequestWorkerThreadNoExecutor.h>\n\n#include <folly/io/async/EventBaseManager.h>\n#include <proxygen/lib/services/ServiceWorker.h>\n\nnamespace proxygen {\n\nstatic const uint32_t requestIdBits = 56;\nstatic const uint64_t requestIdMask = ((1ULL << requestIdBits) - 1);\n\nRequestWorkerThreadNoExecutor::RequestWorkerThreadNoExecutor(\n    FinishCallback& callback, uint8_t threadId, const std::string& evbName)\n    : WorkerThread(folly::EventBaseManager::get(), evbName),\n      nextRequestId_(static_cast<uint64_t>(threadId) << requestIdBits),\n      callback_(callback) {\n}\n\nRequestWorkerThreadNoExecutor::~RequestWorkerThreadNoExecutor() {\n  // It is important to reset the underlying event base in advance of this\n  // class' destruction as it may be that there are functions awaiting\n  // execution that possess a reference to this class.\n  resetEventBase();\n}\n\nuint8_t RequestWorkerThreadNoExecutor::getWorkerId() const {\n  return static_cast<uint8_t>(nextRequestId_ >> requestIdBits);\n}\n\nuint64_t RequestWorkerThreadNoExecutor::nextRequestId() {\n  uint64_t requestId = getRequestWorkerThreadNoExecutor()->nextRequestId_;\n  getRequestWorkerThreadNoExecutor()->nextRequestId_ =\n      (requestId & ~requestIdMask) | ((requestId + 1) & requestIdMask);\n  return requestId;\n}\n\nvoid RequestWorkerThreadNoExecutor::flushStats() {\n  CHECK(getEventBase()->isInEventBaseThread());\n  for (auto& p : serviceWorkers_) {\n    p.second->flushStats();\n  }\n}\n\nvoid RequestWorkerThreadNoExecutor::setup() {\n  WorkerThread::setup();\n  callback_.workerStarted(this);\n}\n\nvoid RequestWorkerThreadNoExecutor::cleanup() {\n  WorkerThread::cleanup();\n  callback_.workerFinished(this);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/RequestWorkerThreadNoExecutor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <map>\n#include <proxygen/lib/services/WorkerThread.h>\n#include <wangle/acceptor/LoadShedConfiguration.h>\n\nnamespace proxygen {\n\nclass Service;\nclass ServiceWorker;\n\n/**\n * RequestWorkerThreadNoExecutor extends WorkerThread, and also contains a list\n * of ServiceWorkers running in this thread.\n */\nclass RequestWorkerThreadNoExecutor : public WorkerThread {\n public:\n  class FinishCallback {\n   public:\n    virtual ~FinishCallback() noexcept = default;\n    virtual void workerStarted(RequestWorkerThreadNoExecutor*) = 0;\n    virtual void workerFinished(RequestWorkerThreadNoExecutor*) = 0;\n  };\n\n  /**\n   * Create a new RequestWorkerThreadNoExecutor.\n   *\n   * @param proxygen  The object to notify when this worker finishes.\n   * @param threadId  A unique ID for this worker.\n   * @param evbName   The event base will ne named to this name (thread name)\n   */\n  RequestWorkerThreadNoExecutor(FinishCallback& callback,\n                                uint8_t threadId,\n                                const std::string& evbName = std::string());\n\n  /**\n   * Reset the underlying event base prior to WorkerThread destruction.\n   */\n  ~RequestWorkerThreadNoExecutor() override;\n\n  /**\n   * Return a unique 64bit identifier.\n   */\n  static uint64_t nextRequestId();\n\n  /**\n   * Return unique 8bit worker ID.\n   */\n  [[nodiscard]] uint8_t getWorkerId() const;\n\n  static RequestWorkerThreadNoExecutor* getRequestWorkerThreadNoExecutor() {\n    auto* self = dynamic_cast<RequestWorkerThreadNoExecutor*>(\n        WorkerThread::getCurrentWorkerThread());\n    CHECK_NOTNULL(self);\n    return self;\n  }\n\n  /**\n   * Track the ServiceWorker objects in-use by this worker.\n   */\n  void addServiceWorker(Service* service, ServiceWorker* sw) {\n    CHECK(serviceWorkers_.find(service) == serviceWorkers_.end());\n    serviceWorkers_[service] = sw;\n  }\n\n  /**\n   * For a given service, returns the ServiceWorker associated with this\n   * RequestWorkerThreadNoExecutor\n   */\n  ServiceWorker* getServiceWorker(Service* service) const {\n    auto it = serviceWorkers_.find(service);\n    CHECK(it != serviceWorkers_.end());\n    return it->second;\n  }\n\n  /**\n   * Get/set the worker thread's bound load shed configuration instance.\n   * Used by derivative classes.  Updates are propagated seamlessly via\n   * the use of swapping such that threads will automatically see updated\n   * fields on update.\n   */\n  [[nodiscard]] std::shared_ptr<const wangle::LoadShedConfiguration>\n  getLoadShedConfig() const {\n    return loadShedConfig_;\n  }\n  void setLoadShedConfig(\n      std::shared_ptr<const wangle::LoadShedConfiguration> loadShedConfig) {\n    loadShedConfig_.swap(loadShedConfig);\n  }\n\n  /**\n   * Flush any thread-local stats being tracked by our ServiceWorkers.\n   *\n   * This must be invoked from within worker's thread.\n   */\n  void flushStats();\n\n private:\n  void setup() override;\n  void cleanup() override;\n\n  // The next request id within this thread. The id has its highest byte set to\n  // the thread id, so is unique across the process.\n  uint64_t nextRequestId_;\n\n  // The ServiceWorkers executing in this worker\n  folly::F14ValueMap<Service*, ServiceWorker*> serviceWorkers_;\n\n  // Every worker instance has their own version of load shed config.\n  // This enables every request worker thread, and derivative there of,\n  // to both access and update this field in a thread-safe way.\n  std::shared_ptr<const wangle::LoadShedConfiguration> loadShedConfig_{nullptr};\n\n  FinishCallback& callback_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/Service.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/services/Service.h>\n\n#include <proxygen/lib/services/RequestWorkerThread.h>\n#include <proxygen/lib/services/RequestWorkerThreadNoExecutor.h>\n#include <proxygen/lib/services/ServiceWorker.h>\n\nnamespace proxygen {\n\nService::Service() = default;\n\nService::~Service() = default;\n\nvoid Service::addServiceWorker(std::unique_ptr<ServiceWorker> worker,\n                               RequestWorkerThread* reqWorker) {\n  reqWorker->addServiceWorker(this, worker.get());\n  workers_.emplace_back(std::move(worker));\n}\n\nvoid Service::addServiceWorker(std::unique_ptr<ServiceWorker> worker,\n                               RequestWorkerThreadNoExecutor* reqWorker) {\n  reqWorker->addServiceWorker(this, worker.get());\n  workers_.emplace_back(std::move(worker));\n}\n\nvoid Service::clearServiceWorkers() {\n  workers_.clear();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/Service.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/EventBase.h>\n#include <list>\n#include <memory>\n\nnamespace proxygen {\n\nclass ServiceWorker;\nclass RequestWorkerThread;\nclass RequestWorkerThreadNoExecutor;\n\n/*\n * A Service object represents a network service running in proxygen.\n *\n * The Service object is primarily a construct used for managing startup and\n * shutdown.  The RunProxygen() call will accept a list of Service objects, and\n * will invoke start() on each service to start them.  When shutdown has been\n * requested, it will invoke failHealthChecks() on each service followed (after\n * some period of time) by stopAccepting().\n *\n * All of these methods are invoked from the main thread.\n */\nclass Service {\n public:\n  Service();\n  virtual ~Service();\n\n  /**\n   * Initialize the service.\n   *\n   * init() will be invoked from proxygen's main thread, before the worker\n   * threads have started processing their event loops.\n   *\n   * The return value indicates if the service is enabled or not.  Return true\n   * if the service is enabled and was initialized successfully, and false if\n   * the service is disabled or is intialized successfully.  Throw an exception\n   * if an error occurred initializing it.\n   */\n  virtual void init(folly::EventBase* mainEventBase,\n                    const std::list<RequestWorkerThread*>& workers) = 0;\n\n  /**\n   * Finish any service initialization that requires the use of the worker\n   * threads.\n   */\n  virtual void finishInit() {\n  }\n\n  /**\n   * Start to accept connection on the listening sockect(s)\n   *\n   * All the expansive preparation work should be done befofe startAccepting(),\n   * i.g., in constructor or init().  startAccepting() should be lightweight,\n   * ideally just the call of accept() on all the listening sockects.\n   * Otherwise, the first accepted connection may experience high latency.\n   */\n  virtual void startAccepting() {\n  }\n\n  /**\n   * Mark the service as about to stop; invoked from main thread.\n   *\n   * This indicates that the service will be told to stop at some later time\n   * and should continue to service requests but tell the healthchecker that it\n   * is dying.\n   */\n  virtual void failHealthChecks() {\n  }\n\n  /**\n   * Stop accepting all new work; invoked from proxygen's main thread.\n   *\n   * This should cause the service to stop accepting new work, and begin to\n   * fully shut down. stop() may return before all work has completed, but it\n   * should eventually cause all events for this service to be removed from the\n   * main EventBase and from the worker threads.\n   */\n  virtual void stopAccepting() = 0;\n\n  /**\n   * Pause listening for new connections; invoked from proxygen's main thread.\n   *\n   * This should cause the service to pause listening for new connections.\n   * The already accepted connections must not be affected.\n   * It may or may not be followed by stopAccepting or resume listening.\n   */\n  virtual void pauseListening() {\n  }\n\n  /**\n   * Drain remaining client connections; invoked from proxygen's main thread.\n   */\n  virtual void drainConnections() {\n  }\n\n  /**\n   * Forcibly stop \"pct\" (0.0 to 1.0) of the remaining client connections.\n   *\n   * If the service does not stop on its own after stopAccepting() is called,\n   * then proxygen might call dropConnections() several times to gradually\n   * stop all processing before finally calling forceStop().\n   *\n   * Spread the stop over \"dropDuration\" milliseconds with intervals chosen by\n   * each service.\n   */\n  virtual void dropConnections(double /*pct*/,\n                               std::chrono::milliseconds /*dropDuration*/ =\n                                   std::chrono::milliseconds(0)) {\n  }\n\n  /**\n   * Forcibly stop the service.\n   *\n   * If the service does not stop on its own after stopAccepting() is called,\n   * forceStop() will eventually be called to forcibly stop all processing.\n   *\n   * (At the moment this isn't pure virtual simply because I haven't had the\n   * time to update all existing services to implement forceStop().  Proxygen\n   * will forcibly terminate the event loop even if a service does not stop\n   * processing when forceStop() is called, so properly implementing\n   * forceStop() isn't strictly required.)\n   */\n  virtual void forceStop() {\n  }\n\n  /**\n   * Doesn't delete all memory claimed by this service, but prepares for object\n   * destruction. For example threads/event loops that should be stopped prior\n   * to object destruction.\n   */\n  virtual void cleanup() = 0;\n\n  /**\n   * Perform per-thread init.\n   *\n   * This method will be called once for each RequestWorkerThread thread, just\n   * after the worker thread started.\n   */\n  virtual void initWorkerState(RequestWorkerThread*) {\n  }\n\n  /**\n   * Perform per-thread cleanup.\n   *\n   * This method will be called once for each RequestWorkerThread thread, just\n   * before that thread is about to exit.  Note that this method is called from\n   * the worker thread itself, not from the main thread.\n   *\n   * failHealthChecks() and stopAccepting() will always be called in the main\n   * thread before cleanupWorkerState() is called in any of the worker threads.\n   *\n   * forceStop() may be called in the main thread at any point during shutdown.\n   * (i.e., Some worker threads may already have finished and called\n   * cleanupWorkerState().  Once forceStop() is invoked, the remaining threads\n   * will forcibly exit and then call cleanupWorkerState().)\n   */\n  virtual void cleanupWorkerState(RequestWorkerThread* /*worker*/) {\n  }\n\n  /**\n   * Services can return arbitrary objects whose lifetime needs to be extended\n   * until after workers threads have joined.\n   */\n  virtual std::vector<std::shared_ptr<void>>\n  keepAliveUntilWorkersDone() noexcept {\n    return {};\n  }\n\n  /**\n   * Add a new ServiceWorker (subclasses should create one ServiceWorker\n   * per worker thread)\n   */\n  void addServiceWorker(std::unique_ptr<ServiceWorker> worker,\n                        RequestWorkerThread* reqWorker);\n  void addServiceWorker(std::unique_ptr<ServiceWorker> worker,\n                        RequestWorkerThreadNoExecutor* reqWorker);\n\n  /**\n   * List of workers\n   */\n  [[nodiscard]] const std::list<std::unique_ptr<ServiceWorker>>&\n  getServiceWorkers() const {\n    return workers_;\n  }\n\n  /**\n   * Delete all the workers\n   */\n  void clearServiceWorkers();\n\n  void addWorkerEventBase(folly::EventBase* evb) {\n    workerEvbs_.push_back(evb);\n  }\n\n  const std::vector<folly::EventBase*>& getWorkerEventBases() {\n    return workerEvbs_;\n  }\n\n private:\n  // Forbidden copy constructor and assignment opererator\n  Service(Service const&) = delete;\n  Service& operator=(Service const&) = delete;\n\n  // Workers\n  std::list<std::unique_ptr<ServiceWorker>> workers_;\n  std::vector<folly::EventBase*> workerEvbs_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/ServiceConfiguration.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\nnamespace proxygen {\n\n/**\n * Base class for all the Configuration objects\n */\nclass ServiceConfiguration {\n public:\n  ServiceConfiguration() : takeoverEnabled_(false) {\n  }\n\n  virtual ~ServiceConfiguration() = default;\n\n  /**\n   * Set/get whether or not we should enable socket takeover\n   */\n  void setTakeoverEnabled(bool enabled) {\n    takeoverEnabled_ = enabled;\n  }\n  [[nodiscard]] bool takeoverEnabled() const {\n    return takeoverEnabled_;\n  }\n\n private:\n  bool takeoverEnabled_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/ServiceWorker.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/AsyncServerSocket.h>\n#include <list>\n#include <memory>\n#include <proxygen/lib/utils/AcceptorAddress.h>\n#include <wangle/acceptor/Acceptor.h>\n#include <wangle/acceptor/ConnectionCounter.h>\n\nnamespace proxygen {\n\nclass Service;\nclass RequestWorkerThread;\n\n/**\n * ServiceWorker contains all of the per-thread information for a Service.\n *\n * ServiceWorker contains a pointer back to the single global Service.\n * It contains a list of ProxyAcceptor objects for this worker thread,\n * one per VIP.\n *\n * ServiceWorker fits into the Proxygen object hierarchy as follows:\n *\n * - Service: one instance (globally across the entire program)\n * - ServiceWorker: one instance per thread\n * - ServiceAcceptor: one instance per VIP per thread\n */\nclass ServiceWorker {\n public:\n  using AcceptorMap =\n      std::map<AcceptorAddress, std::unique_ptr<wangle::Acceptor>>;\n\n  using NamedAddressMap = std::map<std::string, AcceptorAddress>;\n\n  ServiceWorker(Service* service, RequestWorkerThread* worker)\n      : service_(service), worker_(worker) {\n  }\n\n  virtual ~ServiceWorker() = default;\n\n  [[nodiscard]] Service* getService() const {\n    return service_;\n  }\n\n  void addServiceAcceptor(const folly::SocketAddress& address,\n                          std::unique_ptr<wangle::Acceptor> acceptor) {\n    addServiceAcceptor(\n        AcceptorAddress(address, AcceptorAddress::AcceptorType::TCP),\n        std::move(acceptor));\n  }\n\n  void addServiceAcceptor(const AcceptorAddress& accAddress,\n                          std::unique_ptr<wangle::Acceptor> acceptor) {\n    namedAddress_.emplace(acceptor->getName(), accAddress);\n    addAcceptor(accAddress, std::move(acceptor), acceptors_);\n  }\n\n  void drainServiceAcceptor(const folly::SocketAddress& address) {\n    drainServiceAcceptor(\n        AcceptorAddress(address, AcceptorAddress::AcceptorType::TCP));\n  }\n\n  void drainServiceAcceptor(const AcceptorAddress& accAddress) {\n    // Move the old acceptor to drainingAcceptors_ if present\n    const auto& it = acceptors_.find(accAddress);\n    if (it != acceptors_.end()) {\n      auto name = it->second->getName();\n      addAcceptor(accAddress, std::move(it->second), drainingAcceptors_);\n      acceptors_.erase(it);\n      namedAddress_.erase(name);\n    }\n  }\n\n  [[nodiscard]] RequestWorkerThread* getRequestWorkerThread() const {\n    return worker_;\n  }\n\n  const AcceptorMap& getAcceptors() {\n    return acceptors_;\n  }\n\n  /**\n   * Find an acceptor by name running in the same request worker.\n   */\n  [[nodiscard]] wangle::Acceptor* getAcceptorByName(std::string name) const {\n    auto it = namedAddress_.find(name);\n    if (it != namedAddress_.end()) {\n      auto it2 = acceptors_.find(it->second);\n      if (it2 != acceptors_.end()) {\n        return it2->second.get();\n      }\n    }\n    return nullptr;\n  }\n\n  const AcceptorMap& getDrainingAcceptors() {\n    return drainingAcceptors_;\n  }\n\n  // Flush any thread-local stats that the service is tracking\n  virtual void flushStats() {\n  }\n\n  // Destruct all the acceptors\n  virtual void clearAcceptors() {\n    acceptors_.clear();\n    namedAddress_.clear();\n    drainingAcceptors_.clear();\n  }\n\n  virtual void clearDrainingAcceptors() {\n    drainingAcceptors_.clear();\n  }\n\n  virtual void forceStop() {\n  }\n\n private:\n  // Forbidden copy constructor and assignment operator\n  ServiceWorker(ServiceWorker const&) = delete;\n  ServiceWorker& operator=(ServiceWorker const&) = delete;\n\n  void addAcceptor(const AcceptorAddress& accAddress,\n                   std::unique_ptr<wangle::Acceptor> acceptor,\n                   AcceptorMap& acceptors) {\n    CHECK(acceptors.find(accAddress) == acceptors.end());\n    acceptors.insert(std::make_pair(accAddress, std::move(acceptor)));\n  }\n\n  /**\n   * The global Service object.\n   */\n  Service* service_;\n\n  /**\n   * The RequestWorkerThread that is actually responsible for running the\n   * EventBase loop in this thread.\n   */\n  RequestWorkerThread* worker_;\n\n  /**\n   * A list of the Acceptor objects specific to this worker thread, one\n   * Acceptor per VIP.\n   */\n  AcceptorMap acceptors_;\n\n  /**\n   * A list of the addresses indexed by name, used to look up an\n   * acceptor by name\n   */\n  NamedAddressMap namedAddress_;\n\n  /**\n   * A list of Acceptors that are being drained and will be deleted soon.\n   */\n  AcceptorMap drainingAcceptors_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/WorkerThread.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/services/WorkerThread.h>\n\n#include <folly/io/async/EventBase.h>\n\n#include <folly/String.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/io/async/IoUringBackend.h>\n#include <glog/logging.h>\n#include <signal.h>\n\n#if !FOLLY_MOBILE && __has_include(<liburing.h>)\n\nDEFINE_int32(pwt_io_uring_capacity, -1, \"io_uring backend capacity\");\nDEFINE_int32(pwt_io_uring_max_submit, 128, \"io_uring backend max submit\");\nDEFINE_int32(pwt_io_uring_max_get, -1, \"io_uring backend max get\");\nDEFINE_bool(pwt_io_uring_use_registered_fds,\n            false,\n            \"io_uring backend use registered fds\");\n\nnamespace {\nstd::unique_ptr<folly::EventBaseBackendBase> getEventBaseBackend() {\n  if (FLAGS_pwt_io_uring_capacity > 0) {\n    try {\n      folly::IoUringOptions options;\n      options.setCapacity(static_cast<size_t>(FLAGS_pwt_io_uring_capacity))\n          .setMaxSubmit(static_cast<size_t>(FLAGS_pwt_io_uring_max_submit))\n          .setMaxGet(static_cast<size_t>(FLAGS_pwt_io_uring_max_get))\n          .setUseRegisteredFds(FLAGS_pwt_io_uring_use_registered_fds);\n\n      auto ret = std::make_unique<folly::IoUringBackend>(std::move(options));\n      LOG(INFO) << \"Allocating io_uring backend(\" << FLAGS_pwt_io_uring_capacity\n                << \",\" << FLAGS_pwt_io_uring_max_submit << \",\"\n                << FLAGS_pwt_io_uring_max_get << \",\"\n                << FLAGS_pwt_io_uring_use_registered_fds << \"): \" << ret.get();\n\n      return ret;\n    } catch (const std::exception& ex) {\n      LOG(INFO) << \"Failure creating io_uring backend: \" << ex.what();\n    }\n  }\n  return folly::EventBase::getDefaultBackend();\n}\n} // namespace\n\n#else\n\nnamespace {\nstd::unique_ptr<folly::EventBaseBackendBase> getEventBaseBackend() {\n  return folly::EventBase::getDefaultBackend();\n}\n} // namespace\n\n#endif\n\nnamespace proxygen {\n\nthread_local WorkerThread* WorkerThread::currentWorker_ = nullptr;\n\nWorkerThread::WorkerThread(folly::EventBaseManager* eventBaseManager,\n                           const std::string& evbName)\n    : eventBaseManager_(eventBaseManager),\n      eventBase_(std::make_unique<folly::EventBase>(\n          folly::EventBase::Options().setBackendFactory(\n              [] { return getEventBaseBackend(); }))) {\n  // Only set the event base name if not empty.\n  // While not ideal, this preserves the previous program name inheritance\n  // behavior.\n  if (!evbName.empty()) {\n    eventBase_->setName(evbName);\n  }\n\n  LOG(INFO) << \"Created WorkerThread \" << this << \", evb =  \" << evbName;\n}\n\nWorkerThread::~WorkerThread() {\n  CHECK(state_ == State::IDLE);\n\n  // Reset the underlying event base.  This will execute all associated\n  // execution pending funcs if not already reset.\n  resetEventBase();\n}\n\nvoid WorkerThread::start() {\n  CHECK(state_ == State::IDLE);\n  state_ = State::STARTING;\n\n  {\n    // because you could theoretically call wait in parallel with start,\n    // why are you in such a hurry anyways?\n    std::lock_guard<std::mutex> guard(joinLock_);\n    thread_ = std::thread([&]() mutable {\n      this->setup();\n      this->runLoop();\n      this->cleanup();\n    });\n  }\n  eventBase_->waitUntilRunning();\n  // The server has been set up and is now in the loop implementation\n}\n\nvoid WorkerThread::stopWhenIdle() {\n  // Call runInEventBaseThread() to perform all of the work in the actual\n  // worker thread.\n  //\n  // This way we don't have to synchronize access to state_.\n  eventBase_->runInEventBaseThread([this] {\n    if (state_ == State::RUNNING) {\n      state_ = State::STOP_WHEN_IDLE;\n      eventBase_->terminateLoopSoon();\n      // state_ could be IDLE if we don't execute this callback until the\n      // EventBase is destroyed in the WorkerThread destructor\n    } else if (state_ != State::IDLE && state_ != State::STOP_WHEN_IDLE) {\n      LOG(FATAL) << \"stopWhenIdle() called in unexpected state \"\n                 << static_cast<int>(state_);\n    }\n  });\n}\n\nvoid WorkerThread::forceStop() {\n  // Call runInEventBaseThread() to perform all of the work in the actual\n  // worker thread.\n  //\n  // This way we don't have to synchronize access to state_.\n  eventBase_->runInEventBaseThread([this] {\n    if (state_ == State::RUNNING || state_ == State::STOP_WHEN_IDLE) {\n      state_ = State::FORCE_STOP;\n      eventBase_->terminateLoopSoon();\n      // state_ could be IDLE if we don't execute this callback until the\n      // EventBase is destroyed in the WorkerThread destructor\n    } else if (state_ != State::IDLE) {\n      LOG(FATAL) << \"forceStop() called in unexpected state \"\n                 << static_cast<int>(state_);\n    }\n  });\n}\n\nvoid WorkerThread::wait() {\n  std::lock_guard<std::mutex> guard(joinLock_);\n  if (thread_.joinable()) {\n    thread_.join();\n  }\n}\n\nvoid WorkerThread::setup() {\n#ifndef _MSC_VER\n  sigset_t ss;\n\n  // Ignore some signals\n  sigemptyset(&ss);\n  sigaddset(&ss, SIGHUP);\n  sigaddset(&ss, SIGINT);\n  sigaddset(&ss, SIGQUIT);\n  sigaddset(&ss, SIGUSR1);\n  sigaddset(&ss, SIGUSR2);\n  sigaddset(&ss, SIGPIPE);\n  sigaddset(&ss, SIGALRM);\n  sigaddset(&ss, SIGTERM);\n  sigaddset(&ss, SIGCHLD);\n  sigaddset(&ss, SIGIO);\n  PCHECK(pthread_sigmask(SIG_BLOCK, &ss, nullptr) == 0);\n#endif\n\n  // Update the currentWorker_ thread-local pointer\n  CHECK(nullptr == currentWorker_);\n  currentWorker_ = this;\n\n  // Update the manager with the event base this worker runs on\n  if (eventBaseManager_) {\n    eventBaseManager_->setEventBase(eventBase_.get(), false);\n  }\n}\n\nvoid WorkerThread::cleanup() {\n  currentWorker_ = nullptr;\n  if (eventBaseManager_) {\n    eventBaseManager_->clearEventBase();\n  }\n}\n\nvoid WorkerThread::resetEventBase() {\n  eventBase_.reset();\n}\n\nvoid WorkerThread::runLoop() {\n  // Update state_\n  CHECK(state_ == State::STARTING);\n  state_ = State::RUNNING;\n\n  VLOG(1) << \"WorkerThread \" << this << \" starting\";\n\n  // Call loopForever().  This will only return after stopWhenIdle() or\n  // forceStop() has been called.\n  eventBase_->loopForever();\n\n  if (state_ == State::STOP_WHEN_IDLE) {\n    // We have been asked to stop when there are no more events left.\n    // Call loop() to finish processing events.  This will return when there\n    // are no more events to process, or after forceStop() has been called.\n    VLOG(1) << \"WorkerThread \" << this << \" finishing non-internal events\";\n    eventBase_->loop();\n  }\n\n  CHECK(state_ == State::STOP_WHEN_IDLE || state_ == State::FORCE_STOP);\n  state_ = State::IDLE;\n\n  VLOG(1) << \"WorkerThread \" << this << \" terminated\";\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/WorkerThread.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <atomic>\n#include <cstdint>\n#include <folly/Portability.h>\n#include <folly/io/async/EventBase.h>\n#include <mutex>\n#include <thread>\n\nnamespace folly {\nclass EventBaseManager;\n}\n\nnamespace proxygen {\n\n/**\n * A WorkerThread represents an independent event loop that runs in its own\n * thread.\n */\nclass WorkerThread {\n public:\n  explicit WorkerThread(folly::EventBaseManager* ebm,\n                        const std::string& evbName = std::string());\n  virtual ~WorkerThread();\n\n  /**\n   * Begin execution of the worker.\n   *\n   * This starts the worker thread, and returns immediately.\n   */\n  void start();\n\n  /**\n   * Request that the worker thread stop when there are no more events to\n   * process.\n   *\n   * Normally each worker thread runs forever, even if it is idle with no\n   * events to process.  This function requests that the worker thread return\n   * when it becomes idle.\n   *\n   * This is used for graceful shutdown: Once the services have been asked to\n   * shutdown, stopWhenIdle() can be called on the WorkerThread so that it will\n   * return as soon as the services in this thread no longer have any events to\n   * process.\n   *\n   * Typically you will still want to call forceStop() after a timeout, in case\n   * some of the services take too long to shut down gracefully.\n   */\n  void stopWhenIdle();\n\n  /**\n   * Request that the worker stop executing as soon as possible.\n   *\n   * This will terminate the worker thread's event loop, and cause the thread\n   * to return.  If there are any services still running in the worker thread,\n   * their events will no longer be processed.\n   *\n   * This function is asynchronous: it signals the worker thread to stop, and\n   * returns without waiting for the thread to actually terminate.  The wait()\n   * method must be called to wait for the thread to terminate.\n   */\n  void forceStop();\n\n  /**\n   * Synchronously wait for termination of the worker thread.\n   *\n   * Note that the worker thread will only terminate after stopWhenIdle() or\n   * forceStop() has been called, so you typically should only call wait()\n   * after first using one of these functions.\n   */\n  void wait();\n\n  /**\n   * Get the EventBase used to drive the events in this worker thread.\n   */\n  folly::EventBase* getEventBase() {\n    return eventBase_.get();\n  }\n\n  /**\n   * Get native handle of the underlying thread object\n   * (valid only when the thread is running).\n   */\n  std::thread::native_handle_type getThreadNativeHandle() noexcept {\n    return thread_.native_handle();\n  }\n\n  /**\n   * Get ID of the underlying thread objects\n   * (valid only when the thread is running).\n   */\n  [[nodiscard]] std::thread::id getThreadId() const noexcept {\n    return thread_.get_id();\n  }\n\n  /**\n   * Get the current WorkerThread running this thread.\n   *\n   * Returns nullptr if called from a thread that is not running inside\n   * WorkerThread.\n   */\n  static WorkerThread* getCurrentWorkerThread() {\n    return currentWorker_;\n  }\n\n protected:\n  virtual void setup();\n  virtual void cleanup();\n\n  /**\n   * Resets the underlying WorkerThread event base.  This should only be\n   * called from a destructor and is protected so that subclasses may leverage\n   * it as needed.\n   */\n  void resetEventBase();\n\n private:\n  enum class State : uint8_t {\n    IDLE,           // Not yet started\n    STARTING,       // start() called, thread not fully started yet\n    RUNNING,        // Thread running normally\n    STOP_WHEN_IDLE, // stopWhenIdle() called, not stopped yet\n    FORCE_STOP,     // forceStop() called, but the loop is still cleaning up\n  };\n\n  // Forbidden copy constructor and assignment operator\n  WorkerThread(WorkerThread const&) = delete;\n  WorkerThread& operator=(WorkerThread const&) = delete;\n\n  void runLoop();\n\n  State state_{State::IDLE};\n  std::thread thread_;\n  std::mutex joinLock_;\n  folly::EventBaseManager* eventBaseManager_{nullptr};\n  std::unique_ptr<folly::EventBase> eventBase_;\n\n  // A thread-local pointer to the current WorkerThread for this thread\n  static thread_local WorkerThread* currentWorker_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/services/test/AcceptorTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <wangle/acceptor/Acceptor.h>\n\n#include <folly/io/async/EventBase.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n\nusing namespace folly;\nusing namespace wangle;\n\nclass TestConnection : public wangle::ManagedConnection {\n public:\n  void timeoutExpired() noexcept override {\n  }\n  void describe(std::ostream& /*os*/) const override {\n  }\n  [[nodiscard]] bool isBusy() const override {\n    return false;\n  }\n  void notifyPendingShutdown() override {\n  }\n  void closeWhenIdle() override {\n  }\n  void dropConnection(const std::string& /* errorMsg */ = \"\") override {\n    delete this;\n  }\n  void dumpConnectionState(uint8_t /*loglevel*/) override {\n  }\n\n  [[nodiscard]] const folly::SocketAddress& getPeerAddress()\n      const noexcept override {\n    return dummyAddress;\n  }\n\n  folly::SocketAddress dummyAddress;\n};\n\nclass TestAcceptor : public Acceptor {\n public:\n  explicit TestAcceptor(std::shared_ptr<const ServerSocketConfig> accConfig)\n      : Acceptor(std::move(accConfig)) {\n  }\n\n  void onNewConnection(folly::AsyncTransport::UniquePtr /*sock*/,\n                       const folly::SocketAddress* /*address*/,\n                       const std::string& /*nextProtocolName*/,\n                       SecureTransportType /*secureTransportType*/,\n                       const TransportInfo& /*tinfo*/) override {\n    addConnection(new TestConnection);\n\n    getEventBase()->terminateLoopSoon();\n  }\n};\n\nTEST(AcceptorTest, Basic) {\n\n  EventBase base;\n  auto socket = AsyncServerSocket::newSocket(&base);\n  auto config = std::make_shared<ServerSocketConfig>();\n\n  TestAcceptor acceptor(config);\n  socket->addAcceptCallback(&acceptor, &base);\n\n  acceptor.init(socket.get(), &base);\n  socket->bind(0);\n  socket->listen(100);\n\n  SocketAddress addy;\n  socket->getAddress(&addy);\n\n  socket->startAccepting();\n\n  auto client_socket = AsyncSocket::newSocket(&base, addy);\n\n  base.loopForever();\n\n  CHECK_EQ(acceptor.getNumConnections(), 1);\n\n  CHECK(acceptor.getState() == Acceptor::State::kRunning);\n  acceptor.forceStop();\n  socket->stopAccepting();\n  base.loop();\n}\n"
  },
  {
    "path": "proxygen/lib/services/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET AcceptorTest DEPENDS proxygen testmain)\nproxygen_add_test(TARGET RequestWorkerThreadTest DEPENDS proxygen testmain)\n"
  },
  {
    "path": "proxygen/lib/services/test/RequestWorkerThreadTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/portability/GTest.h>\n\n#include \"proxygen/lib/services/RequestWorkerThread.h\"\n\nnamespace proxygen {\n\nclass FinishCallbackTest : public RequestWorkerThread::FinishCallback {\n public:\n  FinishCallbackTest(uint64_t* requestId,\n                     folly::Executor::KeepAlive<folly::EventBase> keepalive)\n      : requestId_(requestId), keepalive_(std::move(keepalive)) {\n  }\n\n  void workerStarted(RequestWorkerThread*) override {\n  }\n  void workerFinished(RequestWorkerThread* worker) override {\n    // The worker has finished executing its main work, but it still may\n    // complete work scheduled on the event base, so let's test that.\n    worker->getEventBase()->runInEventBaseThread([&] {\n      // Because this code is called on pre-destruction and because there are\n      // keepalives in the test below, this code will still run.\n      *requestId_ = RequestWorkerThread::nextRequestId();\n      keepalive_.reset();\n    });\n  }\n\n private:\n  uint64_t* requestId_;\n  folly::Executor::KeepAlive<folly::EventBase> keepalive_;\n};\n\nTEST(RequestWorkerThreadTest, nextRequestIdDuringShutdown) {\n  auto executor = std::make_unique<folly::IOThreadPoolExecutor>(\n      1,\n      std::make_shared<folly::NamedThreadFactory>(\"test\"),\n      folly::EventBaseManager::get(),\n      folly::IOThreadPoolExecutor::Options().setWaitForAll(false));\n  folly::EventBase* firstEventBase = executor->getAllEventBases()[0].get();\n  auto requestId = static_cast<uint64_t>(-1);\n  FinishCallbackTest finishCallback(&requestId,\n                                    executor->getAllEventBases()[0]);\n  std::thread stopThread;\n  // This will prevent the event bases from getting destructor\n  auto worker = RequestWorkerThread(finishCallback, 0, firstEventBase);\n  firstEventBase->runOnDestructionStart([&]() { worker.cleanup(); });\n  worker.setup();\n  worker.forceStop();\n  executor->stop();\n  ASSERT_NE(requestId, -1);\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/ssl/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_ssl\n  EXPORTED_DEPS\n    Folly::folly_portability_openssl\n    glog::glog\n)\n"
  },
  {
    "path": "proxygen/lib/ssl/ProxygenSSL.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/OpenSSL.h>\n#include <glog/logging.h>\n\nnamespace proxygen {\n\nclass ProxygenSSL {\n public:\n  static int getSSLAppContextConfigIndex() {\n    static auto index = [] {\n      auto idx =\n          SSL_CTX_get_ex_new_index(0,\n                                   (void*)\"proxygen client context config\",\n                                   nullptr,\n                                   nullptr,\n                                   nullptr);\n      CHECK(idx >= 0);\n      return idx;\n    }();\n    return index;\n  }\n\n  static int getSSLAppContextStatsIndex() {\n    static auto index = [] {\n      auto idx = SSL_CTX_get_ex_new_index(\n          0, (void*)\"proxygen wangle::SSLStats\", nullptr, nullptr, nullptr);\n      CHECK(idx >= 0);\n      return idx;\n    }();\n    return index;\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/ssl/ThreadLocalSSLStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/ssl/ThreadLocalSSLStats.h>\n\nusing facebook::fb303::AVG;\nusing facebook::fb303::PERCENT;\nusing facebook::fb303::RATE;\nusing facebook::fb303::SUM;\n\nnamespace proxygen {\n\nTLSSLStats::TLSSLStats(const std::string& prefix)\n    : sslAcceptLatency_(\n          prefix + \"_ssl_accept_lat\", 100, 0, 10000, AVG, 50, 95, 99),\n      sslAcceptLatencyTS_(prefix + \"_ssl_accept_lat\", AVG),\n      tlsTicketNew_(prefix + \"_tls_ticket_new\", SUM),\n      tlsTicketHit_(prefix + \"_tls_ticket_hit\", SUM),\n      tlsTicketMiss_(prefix + \"_tls_ticket_miss\", SUM),\n      sslSessionNew_(prefix + \"_ssl_sess_new\", SUM),\n      sslSessionHit_(prefix + \"_ssl_sess_hit\", SUM),\n      sslSessionForeignHit_(prefix + \"_ssl_sess_foreign_hit\", SUM),\n      sslSessionTotalMiss_(prefix + \"_ssl_sess_total_miss\", SUM),\n      sslSessionRemove_(prefix + \"_ssl_sess_remove\", SUM),\n      sslSessionFree_(prefix + \"_ssl_sess_free\", SUM),\n      sslSessionSetError_(prefix + \"_ssl_sess_error_set\", SUM),\n      sslSessionGetError_(prefix + \"_ssl_sess_error_get\", SUM),\n      sslClientRenegotiations_(prefix + \"_ssl_client_renegotiations\", SUM),\n      clientCertMismatch_(prefix + \"_client_cert_mismatch\", SUM),\n      tlsTicketInvalidRotation_(prefix + \"_tls_ticket_invalid_rotation\", SUM),\n      sslUpstreamHandshakes_(prefix + \"_ssl_upstream_handshakes\", SUM),\n      sslUpstreamResumes_(prefix + \"_ssl_upstream_resumes\", SUM),\n      sslUpstreamErrors_(prefix + \"_ssl_upstream_errors\", SUM),\n      sslUpstreamVerifyErrors_(prefix + \"_ssl_upstream_verify_errors\", SUM),\n      replayCacheNumRequests_(\n          prefix + \"_replay_cache.proxygen.external.num_requests\", SUM, RATE),\n      replayCacheNumHits_(prefix + \"_replay_cache.proxygen.external.num_hits\",\n                          SUM,\n                          PERCENT,\n                          RATE),\n      replayCacheNumErrors_(\n          prefix + \"_replay_cache.proxygen.external.num_errors\",\n          SUM,\n          PERCENT,\n          RATE),\n      replayCacheDuration_(prefix + \"_replay_cache.proxygen.external.duration\",\n                           100,\n                           0,\n                           5000,\n                           AVG,\n                           50,\n                           95,\n                           99),\n      newSSLHandshakeShed_(prefix + \"_ssl_handshake_shed_new\", SUM, RATE),\n      sslHandshakeErrors_(prefix + \"_ssl_handshake_errors\", SUM, PERCENT),\n      sslHandshakeSuccesses_(prefix + \"_ssl_handshake_successes\", SUM),\n      sslResumptions_(prefix + \"_ssl_resumptions\", SUM),\n      fizzHandshakeErrors_(prefix + \"_fizz_handshake_errors\", SUM, PERCENT),\n      fizzHandshakeProtocolErrors_(prefix + \"_fizz_handshake_protocol_errors\",\n                                   SUM),\n      fizzHandshakeSuccesses_(prefix + \"_fizz_handshake_successes\", SUM),\n      tfoSuccess_(prefix + \"_tfo_success\", SUM),\n      sslServerCertExpiring_(prefix + \"_ssl_server_cert_expiring\", SUM),\n      sslServerCertExpiringCritical_(\n          prefix + \"_ssl_server_cert_expiring_critical\", SUM),\n      tlsUnknown_(prefix + \"_tls_unknown\", SUM),\n      tlsVersion_1_0_(prefix + \"_tls_v1_0\", SUM),\n      tlsVersion_1_1_(prefix + \"_tls_v1_1\", SUM),\n      tlsVersion_1_2_(prefix + \"_tls_v1_2\", SUM),\n      tlsVersion_1_3_(prefix + \"_tls_v1_3\", SUM),\n      tlsInsecureConnection(prefix + \"_tls_insecure_connection\", SUM),\n      fizzPskTypeNotSupported_(prefix + \"_fizz_psktype_not_supported\", SUM),\n      fizzPskTypeNotAttempted_(prefix + \"_fizz_psktype_not_attempted\", SUM),\n      fizzPskTypeRejected_(prefix + \"_fizz_psktype_rejected\", SUM),\n      fizzPskTypeExternal_(prefix + \"_fizz_psktype_external\", SUM),\n      fizzPskTypeResumption_(prefix + \"_fizz_psktype_resumption\", SUM) {\n}\n\nvoid TLSSLStats::recordSSLAcceptLatency(int64_t latency) noexcept {\n  if (latency >= 0) {\n    sslAcceptLatency_.add(latency);\n    sslAcceptLatencyTS_.add(latency);\n  }\n}\n\nvoid TLSSLStats::recordTLSTicket(bool ticketNew, bool ticketHit) noexcept {\n  if (ticketNew) {\n    tlsTicketNew_.add(1);\n  } else if (ticketHit) {\n    tlsTicketHit_.add(1);\n  } else {\n    tlsTicketMiss_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordSSLSession(bool sessionNew,\n                                  bool sessionHit,\n                                  bool foreign) noexcept {\n  if (sessionNew) {\n    sslSessionNew_.add(1);\n  } else if (sessionHit) {\n    sslSessionHit_.add((foreign) ? 0 : 1);\n    sslSessionForeignHit_.add((foreign) ? 1 : 0);\n  } else {\n    sslSessionTotalMiss_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordSSLSessionRemove() noexcept {\n  sslSessionRemove_.add(1);\n}\n\nvoid TLSSLStats::recordSSLSessionFree(uint32_t freed) noexcept {\n  sslSessionFree_.add(freed);\n}\n\nvoid TLSSLStats::recordSSLSessionSetError(uint32_t /*err*/) noexcept {\n  sslSessionSetError_.add(1);\n}\n\nvoid TLSSLStats::recordSSLSessionGetError(uint32_t /*err*/) noexcept {\n  sslSessionGetError_.add(1);\n}\n\nvoid TLSSLStats::recordClientRenegotiation() noexcept {\n  sslClientRenegotiations_.add(1);\n}\n\nvoid TLSSLStats::recordSSLClientCertificateMismatch() noexcept {\n  clientCertMismatch_.add(1);\n}\n\nvoid TLSSLStats::recordTLSTicketRotation(bool valid) noexcept {\n  if (!valid) {\n    tlsTicketInvalidRotation_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordSSLUpstreamConnection(bool handshake) noexcept {\n  if (handshake) {\n    sslUpstreamHandshakes_.add(1);\n  } else {\n    sslUpstreamResumes_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordSSLUpstreamConnectionError(bool verifyError) noexcept {\n  if (verifyError) {\n    sslUpstreamVerifyErrors_.add(1);\n  } else {\n    sslUpstreamErrors_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordReplayCacheRequestComplete(uint64_t duration,\n                                                  bool cacheHit) noexcept {\n  replayCacheNumRequests_.add(1);\n  replayCacheDuration_.add(duration);\n  replayCacheNumHits_.add(cacheHit ? 1 : 0);\n  replayCacheNumErrors_.add(0);\n}\n\nvoid TLSSLStats::recordReplayCacheRequestError() noexcept {\n  replayCacheNumRequests_.add(1);\n  replayCacheNumErrors_.add(1);\n}\n\nvoid TLSSLStats::recordNewSSLHandshakeShed() {\n  newSSLHandshakeShed_.add(1);\n}\n\nvoid TLSSLStats::recordSSLHandshake(bool success) {\n  if (success) {\n    sslHandshakeSuccesses_.add(1);\n    sslHandshakeErrors_.add(0);\n  } else {\n    sslHandshakeErrors_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordFizzHandshake(bool success) {\n  if (success) {\n    fizzHandshakeSuccesses_.add(1);\n    fizzHandshakeErrors_.add(0);\n  } else {\n    fizzHandshakeErrors_.add(1);\n  }\n}\n\nvoid TLSSLStats::recordSSLConnectionReuse() {\n  sslResumptions_.add(1);\n}\n\nvoid TLSSLStats::recordFizzHandshakeProtocolError() {\n  fizzHandshakeProtocolErrors_.add(1);\n}\n\nvoid TLSSLStats::recordTFOSuccess() {\n  tfoSuccess_.add(1);\n}\n\nvoid TLSSLStats::recordPskType(folly::Optional<fizz::PskType> pskType) {\n  if (!pskType.has_value()) {\n    return;\n  }\n  auto pskTypeVal = pskType.value();\n  switch (pskTypeVal) {\n    case fizz::PskType::NotSupported:\n      fizzPskTypeNotSupported_.add(1);\n      break;\n    case fizz::PskType::NotAttempted:\n      fizzPskTypeNotAttempted_.add(1);\n      break;\n    case fizz::PskType::Rejected:\n      fizzPskTypeRejected_.add(1);\n      break;\n    case fizz::PskType::External:\n      fizzPskTypeExternal_.add(1);\n      break;\n    case fizz::PskType::Resumption:\n      fizzPskTypeResumption_.add(1);\n      break;\n  }\n}\n\nvoid TLSSLStats::recordServerCertExpiring() noexcept {\n  sslServerCertExpiring_.add(1);\n}\n\nvoid TLSSLStats::recordServerCertExpiringCritical() noexcept {\n  sslServerCertExpiringCritical_.add(1);\n}\n\nvoid TLSSLStats::recordTLSVersion(fizz::ProtocolVersion tlsVersion) noexcept {\n  switch (tlsVersion) {\n    case fizz::ProtocolVersion::tls_1_0:\n      tlsVersion_1_0_.add(1);\n      return;\n    case fizz::ProtocolVersion::tls_1_1:\n      tlsVersion_1_1_.add(1);\n      return;\n    case fizz::ProtocolVersion::tls_1_2:\n      tlsVersion_1_2_.add(1);\n      return;\n    case fizz::ProtocolVersion::tls_1_3:\n    case fizz::ProtocolVersion::tls_1_3_23:\n    case fizz::ProtocolVersion::tls_1_3_23_fb:\n    case fizz::ProtocolVersion::tls_1_3_26:\n    case fizz::ProtocolVersion::tls_1_3_26_fb:\n    case fizz::ProtocolVersion::tls_1_3_28:\n      tlsVersion_1_3_.add(1);\n      return;\n  }\n  tlsUnknown_.add(1);\n}\n\nvoid TLSSLStats::recordInsecureConnection() noexcept {\n  tlsInsecureConnection.add(1);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/ssl/ThreadLocalSSLStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/protocol/Types.h>\n#include <folly/ThreadLocal.h>\n#include <proxygen/lib/stats/StatsWrapper.h>\n#include <string>\n#include <wangle/ssl/SSLStats.h>\n#include <wangle/ssl/SSLUtil.h>\n\nnamespace proxygen {\n\nclass ProxygenSSLStats : public wangle::SSLStats {\n public:\n  ~ProxygenSSLStats() override = default;\n\n  virtual void recordReplayCacheRequestComplete(uint64_t duration,\n                                                bool cacheHit) noexcept = 0;\n  virtual void recordReplayCacheRequestError() noexcept = 0;\n\n  virtual void recordNewSSLHandshakeShed() = 0;\n\n  virtual void recordPskType(folly::Optional<fizz::PskType> pskType) = 0;\n  /**\n   * Keep track of SSL handshake errors\n   */\n  virtual void recordSSLHandshake(bool success) = 0;\n\n  virtual void recordFizzHandshake(bool success) = 0;\n\n  virtual void recordSSLConnectionReuse() = 0;\n\n  // Protocol level errors only\n  virtual void recordFizzHandshakeProtocolError() = 0;\n\n  virtual void recordTFOSuccess() = 0;\n\n  virtual void recordServerCertExpiring() noexcept = 0;\n\n  virtual void recordServerCertExpiringCritical() noexcept = 0;\n\n  // TLS usage\n  virtual void recordTLSVersion(fizz::ProtocolVersion tlsVersion) noexcept = 0;\n\n  virtual void recordInsecureConnection() noexcept = 0;\n};\n\nclass TLSSLStats : public ProxygenSSLStats {\n public:\n  TLSSLStats(const std::string& prefix);\n  ~TLSSLStats() override = default;\n\n  // downstream\n  void recordSSLAcceptLatency(int64_t latency) noexcept override;\n  void recordTLSTicket(bool ticketNew, bool ticketHit) noexcept override;\n  void recordSSLSession(bool sessionNew,\n                        bool sessionHit,\n                        bool foreign) noexcept override;\n  void recordSSLSessionRemove() noexcept override;\n  void recordSSLSessionFree(uint32_t freed) noexcept override;\n  void recordSSLSessionSetError(uint32_t err) noexcept override;\n  void recordSSLSessionGetError(uint32_t err) noexcept override;\n  void recordClientRenegotiation() noexcept override;\n  void recordSSLClientCertificateMismatch() noexcept override;\n  void recordTLSTicketRotation(bool valid) noexcept override;\n\n  // upstream\n  void recordSSLUpstreamConnection(bool handshake) noexcept override;\n  void recordSSLUpstreamConnectionError(bool verifyError) noexcept override;\n\n  // ProxygenSSLStats interface.\n\n  void recordReplayCacheRequestComplete(uint64_t duration,\n                                        bool cacheHit) noexcept override;\n  void recordReplayCacheRequestError() noexcept override;\n\n  void recordNewSSLHandshakeShed() override;\n\n  void recordPskType(folly::Optional<fizz::PskType> pskType) override;\n  /**\n   * Keep track of SSL handshake errors\n   */\n  void recordSSLHandshake(bool success) override;\n\n  void recordFizzHandshake(bool success) override;\n\n  void recordSSLConnectionReuse() override;\n\n  void recordFizzHandshakeProtocolError() override;\n\n  void recordTFOSuccess() override;\n\n  void recordServerCertExpiring() noexcept override;\n\n  void recordServerCertExpiringCritical() noexcept override;\n\n  void recordTLSVersion(fizz::ProtocolVersion tlsVersion) noexcept override;\n\n  void recordInsecureConnection() noexcept override;\n\n private:\n  // Forbidden copy constructor and assignment operator\n  TLSSLStats(TLSSLStats const&) = delete;\n  TLSSLStats& operator=(TLSSLStats const&) = delete;\n\n  // downstream\n  StatsWrapper::TLHistogram sslAcceptLatency_;\n  StatsWrapper::TLTimeseries sslAcceptLatencyTS_;\n  StatsWrapper::TLTimeseries tlsTicketNew_;\n  StatsWrapper::TLTimeseries tlsTicketHit_;\n  StatsWrapper::TLTimeseries tlsTicketMiss_;\n  StatsWrapper::TLTimeseries sslSessionNew_;\n  StatsWrapper::TLTimeseries sslSessionHit_;\n  StatsWrapper::TLTimeseries sslSessionForeignHit_;\n  StatsWrapper::TLTimeseries sslSessionTotalMiss_;\n  StatsWrapper::TLTimeseries sslSessionRemove_;\n  StatsWrapper::TLTimeseries sslSessionFree_;\n  StatsWrapper::TLTimeseries sslSessionSetError_;\n  StatsWrapper::TLTimeseries sslSessionGetError_;\n  StatsWrapper::TLTimeseries sslClientRenegotiations_;\n  StatsWrapper::TLTimeseries clientCertMismatch_;\n  StatsWrapper::TLTimeseries tlsTicketInvalidRotation_;\n\n  // upstream\n  StatsWrapper::TLTimeseries sslUpstreamHandshakes_;\n  StatsWrapper::TLTimeseries sslUpstreamResumes_;\n  StatsWrapper::TLTimeseries sslUpstreamErrors_;\n  StatsWrapper::TLTimeseries sslUpstreamVerifyErrors_;\n  // replay_cache service\n  StatsWrapper::TLTimeseries replayCacheNumRequests_;\n  StatsWrapper::TLTimeseries replayCacheNumHits_;\n  StatsWrapper::TLTimeseries replayCacheNumErrors_;\n  StatsWrapper::TLHistogram replayCacheDuration_;\n\n  // ssl handshake metrics\n  StatsWrapper::TLTimeseries newSSLHandshakeShed_;\n  StatsWrapper::TLTimeseries sslHandshakeErrors_;\n  StatsWrapper::TLTimeseries sslHandshakeSuccesses_;\n  StatsWrapper::TLTimeseries sslResumptions_;\n  StatsWrapper::TLTimeseries fizzHandshakeErrors_;\n  StatsWrapper::TLTimeseries fizzHandshakeProtocolErrors_;\n  StatsWrapper::TLTimeseries fizzHandshakeSuccesses_;\n  StatsWrapper::TLTimeseries tfoSuccess_;\n  StatsWrapper::TLTimeseries sslServerCertExpiring_;\n  StatsWrapper::TLTimeseries sslServerCertExpiringCritical_;\n  // tlsUnknown_ is to track and make sure that we do not\n  // support a TLS versions that is unintented. This is\n  // also used for audits.\n  StatsWrapper::TLTimeseries tlsUnknown_;\n  StatsWrapper::TLTimeseries tlsVersion_1_0_;\n  StatsWrapper::TLTimeseries tlsVersion_1_1_;\n  StatsWrapper::TLTimeseries tlsVersion_1_2_;\n  StatsWrapper::TLTimeseries tlsVersion_1_3_;\n  StatsWrapper::TLTimeseries tlsInsecureConnection;\n\n  // PskTypes counters\n  StatsWrapper::TLTimeseries fizzPskTypeNotSupported_;\n  StatsWrapper::TLTimeseries fizzPskTypeNotAttempted_;\n  StatsWrapper::TLTimeseries fizzPskTypeRejected_;\n  StatsWrapper::TLTimeseries fizzPskTypeExternal_;\n  StatsWrapper::TLTimeseries fizzPskTypeResumption_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/BaseStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/stats/BaseStats.h\"\n\nDEFINE_bool(basestats_all_time_timeseries,\n            false,\n            \"If true, BaseStats will use all-time aggregations\");\n\n/*static*/\nbool proxygen::BaseStats::isAllTimeTimeseriesEnabled() {\n  return FLAGS_basestats_all_time_timeseries;\n}\n"
  },
  {
    "path": "proxygen/lib/stats/BaseStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fb303/ThreadCachedServiceData.h>\n#include <fb303/detail/QuantileStatWrappers.h>\n\nDECLARE_bool(basestats_all_time_timeseries);\n\nnamespace proxygen {\n\n/*\n * Counter definitions for use in child classes.  Updating all\n * children thus becomes as simple as updating these definitions.\n * It is thus intended and recommended for all callers to refer to\n * BaseStats::<counter> when wishing to use counters.\n */\nclass BaseStats {\n private:\n  // Private constructor so its clear nothing else should implement this class\n  BaseStats() = default;\n\n public:\n  // TODO: given the simple nature of TLCounter and that it is explicitly\n  // thread safe via the use of atomics, we may only want single local\n  // instance instead of wrapped (per thread) instances.\n  using TLCounter = facebook::fb303::CounterWrapper;\n  // Please avoid adding DynamicTimeseriesWrapper if we can.\n  // At a minimum they require formatters and map lookups for\n  // operations and make it easier to violate the constraint that all counters\n  // are created at startup.\n  using TLTimeseriesMinute = facebook::fb303::MinuteOnlyTimeseriesWrapper;\n  // TLTimeseries was exporting as TimeseriesPolymorphicWrapper\n  // were are trying to get rid of .600/.3600 counters\n  // therefore aliasing it to TLTTimeSeriesMinute which only\n  // exports .60 counters\n  using TLTimeseries = TLTimeseriesMinute;\n  // Wrapper which constructs windowed 60s and conditionally all-time timeseries\n  // based on gflag. All-time timeseries' only use case is in prospect tests\n  // where windowed timeseries cause test flakiness if the proxygend instance is\n  // alive for >60s. The underlying timeseries object is constructed on the heap\n  // to work around compiler error of the fb303 objects having no default\n  // constructor.\n  class TLTimeseriesMinuteAndAllTime final {\n   public:\n    template <typename... Args>\n    explicit TLTimeseriesMinuteAndAllTime(std::string name,\n                                          const Args&... args) {\n      // Static assertion that will fail at compile time if Args is empty\n      static_assert(\n          sizeof...(Args) > 0,\n          \"TLTimeseriesMinuteAndAllTime requires at least one argument \"\n          \"after the name, typically the export type (e.g., \"\n          \"facebook::fb303::SUM)\");\n      if (FLAGS_basestats_all_time_timeseries) {\n        impl_ = std::make_unique<facebook::fb303::MinuteTimeseriesWrapper>(\n            std::move(name), args...);\n      } else {\n        impl_ = std::make_unique<facebook::fb303::MinuteOnlyTimeseriesWrapper>(\n            std::move(name), args...);\n      }\n    }\n    void add(int64_t value = 1) {\n      impl_->add(value);\n    }\n\n   private:\n    std::unique_ptr<facebook::fb303::TimeseriesWrapperBase> impl_;\n  };\n\n  class LazyQuantileStatWrapper {\n   private:\n    inline size_t getNumCountersCreated() {\n      return statWrapperInfo_->quantiles.size() *\n             statWrapperInfo_->slidingWindowPeriods.size();\n    }\n\n   public:\n    explicit LazyQuantileStatWrapper(\n        folly::StringPiece name,\n        folly::Range<const facebook::fb303::ExportType*> stats =\n            facebook::fb303::ExportTypeConsts::kCountAvg,\n        folly::Range<const double*> quantiles =\n            facebook::fb303::QuantileConsts::kP95_P99_P999,\n        folly::Range<const size_t*> slidingWindowPeriods =\n            facebook::fb303::SlidingWindowPeriodConsts::kOneMin) {\n      // We convert these to owned datastructures\n      std::vector<facebook::fb303::ExportType> statsVec;\n      statsVec.reserve(stats.size());\n      std::vector<double> quantilesVec;\n      quantilesVec.reserve(quantiles.size());\n      std::vector<size_t> slidingWindowPeriodsVec;\n      slidingWindowPeriodsVec.reserve(slidingWindowPeriods.size());\n\n      for (auto stat : stats) {\n        statsVec.push_back(stat);\n      }\n      for (auto quantile : quantiles) {\n        quantilesVec.push_back(quantile);\n      }\n      for (auto slidingWindowPeriod : slidingWindowPeriods) {\n        slidingWindowPeriodsVec.push_back(slidingWindowPeriod);\n      }\n      statWrapperInfo_ =\n          std::make_unique<QuantileStatWrapperInfo>(QuantileStatWrapperInfo{\n              .name = name.toString(),\n              .stats = std::move(statsVec),\n              .quantiles = std::move(quantilesVec),\n              .slidingWindowPeriods = std::move(slidingWindowPeriodsVec)});\n\n      numCountersSaved_.incrementValue(getNumCountersCreated());\n    }\n\n    void addValue(double value) {\n      folly::call_once(initQuantileStatFlag, [&]() {\n        statWrapper_ =\n            std::make_unique<facebook::fb303::detail::QuantileStatWrapper>(\n                statWrapperInfo_->name,\n                statWrapperInfo_->stats,\n                statWrapperInfo_->quantiles,\n                statWrapperInfo_->slidingWindowPeriods);\n        numCountersSaved_.incrementValue(static_cast<size_t>(-1) *\n                                         getNumCountersCreated());\n        statWrapperInfo_.reset();\n      });\n      statWrapper_->addValue(value);\n    }\n\n   protected:\n    // A class to store objects needed to create a QuantileStatWrapper object\n    // for lazy construction\n    struct QuantileStatWrapperInfo {\n      std::string name;\n      std::vector<facebook::fb303::ExportType> stats;\n      std::vector<double> quantiles;\n      std::vector<size_t> slidingWindowPeriods;\n    };\n\n    folly::once_flag initQuantileStatFlag;\n    std::unique_ptr<QuantileStatWrapperInfo> statWrapperInfo_;\n    std::unique_ptr<facebook::fb303::detail::QuantileStatWrapper> statWrapper_;\n    // Keep track of the number of times we have saved a counter by using the\n    // lazy behavior\n    static inline TLCounter numCountersSaved_{\n        \"num_counters_saved_with_lazy_quantile_stat\"};\n  };\n\n  using TLHistogram = facebook::fb303::MinuteOnlyHistogram;\n  using TLHistogramMinuteAndAllTime = facebook::fb303::HistogramWrapper;\n  // Please avoid adding DynamicHistogramWrapper if we can.\n  // At a minimum they require formatters and map lookups for\n  // operations and make it easier to violate the constraint that all counters\n  // are created at startup.\n\n  // Following are helpers to add/increment optional BaseStats types\n  template <typename StatT>\n  static void addToOptionalStat(StatT& tlStat, int64_t value) {\n    if (tlStat) {\n      tlStat->add(value);\n    }\n  }\n  template <typename StatT>\n  static void addValueToOptionalStat(StatT& tlStat, int64_t value) {\n    if (tlStat) {\n      tlStat->addValue(value);\n    }\n  }\n\n  template <typename StatT>\n  static void incrementOptionalCounter(std::optional<StatT>& tlCounter,\n                                       facebook::fb303::CounterType value) {\n    if (tlCounter) {\n      tlCounter->incrementValue(value);\n    }\n  }\n\n  static bool isAllTimeTimeseriesEnabled();\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/PeriodicStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <atomic>\n#include <chrono>\n#include <folly/Function.h>\n#include <folly/ThreadLocal.h>\n#include <folly/executors/FunctionScheduler.h>\n#include <folly/synchronization/Rcu.h>\n#include <memory>\n\nnamespace proxygen {\n\n/**\n * PeriodicStats:\n *\n * An abstract class that implements the internals of periodically retrieving\n * some collection of data, making it available to other threads in a safe\n * and performant way.  Can be used to build a thread-safe mechanism for\n * retrieving desired information (i.e. multiple readers, single writer).\n * In this way readers view a consistent representation of the underlying\n * data, as opposed to a model where each reader fetches the data separately,\n * potentially at different times, and thus potentially coming to different\n * conclusions (not to mention wasting resources performing the same\n * operations).\n *\n * Template class T must:\n * - be copyable (careful about storing pointers in T for this reason).\n * - implement method getLastUpdateTime() that returns a\n * chrono timepoint, since epoch, that denotes for what time T was valid.\n *\n * All public methods are thread-safe.\n */\ntemplate <class T>\nclass PeriodicStats {\n public:\n  /**\n   * Default constructor that will initialize data_ as appropriate.\n   * Underlying thread that updates locally cached Data is not\n   * started yet.\n   */\n  explicit PeriodicStats(\n      T* data,\n      folly::rcu_domain* periodic_stats_domain = &folly::rcu_default_domain())\n      : data_(data), periodic_stats_domain_(periodic_stats_domain) {\n  }\n  virtual ~PeriodicStats() {\n    stopRefresh();\n    modifyData(nullptr, /*sync=*/true);\n  }\n  PeriodicStats(const PeriodicStats&) = delete;\n  PeriodicStats& operator=(const PeriodicStats&) = delete;\n  PeriodicStats(PeriodicStats&&) = delete;\n  PeriodicStats& operator=(PeriodicStats&&) = delete;\n\n  /**\n   * A caller can set a custom callback, to be executed by the function\n   * scheduler thread, after each refresh.  This can only be set while the\n   * refreshing thread is not active.\n   */\n  void setRefreshCB(folly::Function<void()>&& cb) {\n    std::lock_guard<std::mutex> guard(schedulerMutex_);\n    CHECK(!scheduler_);\n    refreshCb_ = std::move(cb);\n  }\n\n  // Returns the current refresh interval in ms of the underlying data.\n  std::chrono::milliseconds getRefreshIntervalMs() {\n    return refreshPeriodMs_;\n  }\n\n  /**\n   * Refreshes the underlying data using the period specified by employing\n   * the underlying function scheduler.  If already refreshing, the current\n   * period will instead be updated and on next scheduling the updated period\n   * applied.\n   */\n  void refreshWithPeriod(\n      std::chrono::milliseconds periodMs,\n      std::chrono::milliseconds initialDelayMs = std::chrono::milliseconds(0)) {\n    CHECK_GE(periodMs.count(), 0);\n    std::lock_guard<std::mutex> guard(schedulerMutex_);\n    refreshPeriodMs_ = periodMs;\n    if (!scheduler_) {\n      scheduler_ = std::make_unique<folly::FunctionScheduler>();\n      scheduler_->setThreadName(\"periodic_stats\");\n      // Steady here implies that scheduling will be fixed as opposed to\n      // offsetting from the current time which is desired to ensure minimal\n      // use of synchronization for getCurrentData()\n      scheduler_->setSteady(true);\n\n      std::function<void()> updateFunc(\n          std::bind(&PeriodicStats::updateCachedData, this));\n      std::function<std::chrono::milliseconds()> intervalFunc(\n          std::bind(&PeriodicStats::getRefreshIntervalMs, this));\n\n      scheduler_->addFunctionGenericDistribution(updateFunc,\n                                                 intervalFunc,\n                                                 \"periodic_stats\",\n                                                 \"periodic_stats_interval\",\n                                                 initialDelayMs);\n\n      scheduler_->start();\n    }\n  }\n\n  /**\n   * Stops refreshing the underlying data.  It is possible that an update is\n   * occuring as this method is run in which case the guarantee becomes that no\n   * subsequent refresh will be scheduled.\n   */\n  void stopRefresh() {\n    std::lock_guard<std::mutex> guard(schedulerMutex_);\n    scheduler_.reset();\n  }\n\n  /**\n   * Readers utilize this method to get their thread local representation\n   * of the current data.  The way in which this works is that while there is\n   * a thread local representation of the data, there is also a global\n   * representation that is guarded.  Asynchronously, the refresh thread will\n   * update the global representation in a thread safe manner.  Then, as\n   * readers wish to consume this data, they are instead presented with a\n   * thread local representation that is updated if:\n   *  A) their representation is uninitialized\n   *  B) their representation is old\n   *\n   * Method is virtual for testing reasons.\n   */\n  virtual const T& getCurrentData() const {\n    {\n      std::scoped_lock guard(*periodic_stats_domain_);\n      auto* loadedData = data_.load();\n      if (loadedData->getLastUpdateTime() != tlData_->getLastUpdateTime()) {\n        // Should be fine using the default assignment operator the compiler\n        // gave us I think...this will stop being true if loadedData starts\n        // storing pointers.\n        *tlData_ = *loadedData;\n      }\n    }\n    return *tlData_;\n  }\n  // Same as above except no local update will be performed, even if newer\n  // data is available.\n  virtual const T& getPreviousData() const {\n    return *tlData_;\n  }\n\n protected:\n  /**\n   * Returns a new instance of T data that will be owned and cached\n   * by this class.  Subclasses can implement accordingly to override the\n   * returned instance.\n   *\n   * If nullptr is returned, no update is performed.  Subclasses can leverage\n   * this flow in case no new data is available or if they want the current\n   * data to remain in use.\n   */\n  virtual T* getNewData() const = 0;\n\n  // Wrapper for updating and retiring old cached data_ via RCU.\n  // The 'sync' parameter controls whether the previous object is deleted\n  // right away vs in a delayed fashion\n  void modifyData(T* newData, bool sync = false) {\n    auto* oldData = data_.exchange(newData);\n    if (sync) {\n      folly::rcu_synchronize(*periodic_stats_domain_);\n      delete oldData;\n    } else {\n      folly::rcu_retire(oldData, {}, *periodic_stats_domain_);\n    }\n  }\n\n  /**\n   * Wrapper for the internal function scheduler to call in order to update\n   * data_ via getNewData() and modifyData().  Method virtual for test\n   * purposes.\n   */\n  void updateCachedData() {\n    auto* newData = getNewData();\n    if (newData) {\n      modifyData(newData);\n      if (refreshCb_) {\n        refreshCb_();\n      }\n    }\n  }\n\n  /**\n   * data_ represents the source of truth for the class.\n   * tlData_ is updated on a 'as-used' basis from data_ via RCU\n   * synchronization.\n   */\n  std::atomic<T*> data_;\n  folly::ThreadLocal<T> tlData_;\n\n  // Refresh management fields\n\n  /**\n   * scheduler_ represents the underlying abstraction that will create\n   * the thread that updates data_ asynchronously.\n   *\n   * The mutex specifically synchronizes access to scheduler_.  It is\n   * leveraged to ensure all public APIs are thread-safe.\n   *\n   * The refreshPeriodMs_ is the amount of time that must elapse before\n   * the underlying cached data is updated.  It is atomic as while it can\n   * be read/set by the caller under the schedulerMutex_, it is also read\n   * by the underlying function scheduler thread.\n   *\n   * periodic_stats_domain is a custom RCU domain to ensure that related\n   * operations are not affected by other RCU usage that could cause\n   * concurrency related issues.\n   *\n   * The refreshCb_, if specified, provides the caller the functionality to\n   * execute an arbitrary function on refresh.\n   */\n  std::unique_ptr<folly::FunctionScheduler> scheduler_;\n  std::mutex schedulerMutex_;\n  std::atomic<std::chrono::milliseconds> refreshPeriodMs_{\n      std::chrono::milliseconds(0)};\n  folly::rcu_domain* periodic_stats_domain_;\n  folly::Function<void()> refreshCb_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/PeriodicStatsDataBase.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n\nnamespace proxygen {\n\n/**\n * Simple base class that a subclass may implement in order to create a data\n * structure suitable for use by PeriodicStats.  It is not a hard requirement\n * but as this class implements the PeriodicStats template interface expected\n * from wrapped data any other class can simply implement this class to buy-in\n * without caring about any other internals.\n *\n * Users must call setLastUpdateTime() appropriately when generating new data\n * for use in PeriodicStats.  This is not done by default in the constructor\n * in case an implementation takes a not trivial amount of time after object\n * construction.\n */\nclass PeriodicStatsDataBase {\n public:\n  PeriodicStatsDataBase() = default;\n  ~PeriodicStatsDataBase() = default;\n\n  /**\n   * Refreshes the time (from epoch) when this record was created (so for\n   * which the utilization metrics are valid).\n   */\n  void refreshLastUpdateTime() {\n    time_ = getEpochTime();\n  }\n  void setLastUpdateTime(std::chrono::milliseconds updateTime) {\n    time_ = updateTime;\n  }\n\n  /**\n   * Gets the time (from epoch) when this record was created (so for\n   * which the utilization metrics are valid).\n   */\n  [[nodiscard]] std::chrono::milliseconds getLastUpdateTime() const {\n    return time_;\n  }\n\n  // Helper method to get ms since epoch.\n  static std::chrono::milliseconds getEpochTime() {\n    return std::chrono::duration_cast<std::chrono::milliseconds>(\n        std::chrono::system_clock::now().time_since_epoch());\n  }\n\n protected:\n  // Refresh management fields\n  std::chrono::milliseconds time_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/ResourceData.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <folly/logging/xlog.h>\n#include <glog/stl_logging.h>\n#include <stdint.h>\n#include <vector>\n\n#include <proxygen/lib/stats/PeriodicStatsDataBase.h>\n\nnamespace proxygen {\n\nstruct CpuStats {\n  double cpuUsageRatio;\n  std::vector<double> cpuCoreUsageRatios;\n  double cpuUtilPercentileConfigured = 0;\n  double cpuRatioUtilPercentile = 0;\n  double cpuSoftIrqUsageRatio = 0;\n  std::vector<double> softIrqCpuCoreRatioUtils;\n  // this field is only used to calculate\n  // pro-rate softirq of cgroup\n  double cpuSysUsageRatio = 0;\n};\n\n/**\n * Container struct to store various resource utilization data.\n */\nstruct ResourceData : public PeriodicStatsDataBase {\n public:\n  ResourceData() = default;\n  virtual ~ResourceData() = default;\n\n  /**\n   * Gets the cpu ratio utilization (0, 1.0 over the last update interval)\n   * Note for the purposes of this implementation, a core is considered\n   * utilized should it not be idle (i.e. excludes both idle and iowait\n   * proc stats).\n   */\n  [[nodiscard]] double getCpuRatioUtil(bool normalized = true) const {\n    return normalized ? std::min(1.0, cpuRatioUtil_) : cpuRatioUtil_;\n  }\n\n  /**\n   * Gets the cpu percentage utilization (0-100 over last update interval)\n   * Note for the purposes of this implementation, a core is considered\n   * utilized should it not be idle (i.e. excludes both idle and iowait\n   * proc stats)\n   *\n   * Cgroup CPU utilization might be significantly off during peak utilization\n   * i.e. way above 100% due to CPU throttling, pass `normalized` as false if\n   * you want to see values above 100, for example if you aggregate values over\n   * some window.\n   */\n  [[nodiscard]] double getCpuPctUtil(bool normalized = true) const {\n    return getPctFromRatio(getCpuRatioUtil(normalized));\n  }\n\n  /**\n   * Gets the cpu utilization ratio (0-1.0 over last update interval),\n   * aggregated over all cores using a configured quantile.\n   * Utilized = non-idle and non-iowait\n   *\n   * Cgroup CPU utilization might be significantly off during peak utilization\n   * i.e. way above 100% due to CPU throttling, pass `normalized` as false if\n   * you want to see values above 100, for example if you aggregate values over\n   * some window.\n   */\n  [[nodiscard]] double getCpuUtilPercentileRatio(bool normalized = true) const {\n    return normalized ? std::min(1.0, cpuRatioUtilPercentile_)\n                      : cpuRatioUtilPercentile_;\n  }\n\n  /**\n   * Gets the cpu percentage utilization (0-100 over last update interval),\n   * aggregated over all cores using a configured quantile.\n   * Utilized = non-idle and non-iowait\n   *\n   * Cgroup CPU utilization might be significantly off during peak utilization\n   * i.e. way above 100% due to CPU throttling, pass `normalized` as false if\n   * you want to see values above 100, for example if you aggregate values over\n   * some window.\n   */\n  [[nodiscard]] double getCpuUtilPercentile(bool normalized = true) const {\n    return getPctFromRatio(getCpuUtilPercentileRatio(normalized));\n  }\n\n  /**\n   * Gets the quantile configured for producing the aggregation from\n   * getCpuUtilPercentile().\n   */\n  [[nodiscard]] double getCpuUtilPercentileConfigured() const {\n    return cpuUtilPercentileConfigured_;\n  }\n\n  /**\n   * Gets the average soft cpu ratio utilization (0, 1.0 over the last update\n   * interval).\n   */\n  [[nodiscard]] double getSoftIrqCpuRatioUtil() const {\n    return cpuSoftIrqRatioUtil_;\n  }\n\n  /**\n   * Gets the soft cpu ratio utilization (0, 1.0 over the last update interval)\n   * for each cpu core in the system.  Note for the purposes of this\n   * implementation, a core is considered utilized should it not be idle\n   * (i.e. excludes both idle and iowait proc stats).\n   */\n  [[nodiscard]] const std::vector<double>& getSoftIrqCpuCoreRatioUtils() const {\n    return softIrqCpuCoreRatioUtils_;\n  }\n\n  /**\n   * Gets the number of employed cpu cores as inferred by the length of the\n   * per core soft utilization data already queried.\n   */\n  [[nodiscard]] uint64_t getNumLogicalCpuCores() const {\n    return softIrqCpuCoreRatioUtils_.size();\n  }\n\n  /**\n   * Returns the percentage that maps to the specified ratio in the range\n   * [0.0, 1.0].\n   */\n  static double getPctFromRatio(double ratio) {\n    return ratio * 100;\n  }\n\n  // Gets the unused memory (free/available) of the system in bytes\n  [[nodiscard]] uint64_t getUnusedMemBytes() const {\n    return totalMemBytes_ - usedMemBytes_;\n  }\n\n  // Gets the used memory of the system in bytes\n  [[nodiscard]] uint64_t getUsedMemBytes() const {\n    return usedMemBytes_;\n  }\n\n  [[nodiscard]] double getMemPressureFullAvg10Pct() const {\n    return memPressureFullAvg10Pct_;\n  }\n\n  [[nodiscard]] double getMemPressureFullAvg60Pct() const {\n    return memPressureFullAvg60Pct_;\n  }\n\n  [[nodiscard]] double getMemPressureFullAvg300Pct() const {\n    return memPressureFullAvg300Pct_;\n  }\n\n  [[nodiscard]] double getMemPressureSomeAvg10Pct() const {\n    return memPressureSomeAvg10Pct_;\n  }\n\n  [[nodiscard]] double getMemPressureSomeAvg60Pct() const {\n    return memPressureSomeAvg60Pct_;\n  }\n\n  [[nodiscard]] double getMemPressureSomeAvg300Pct() const {\n    return memPressureSomeAvg300Pct_;\n  }\n\n  [[nodiscard]] int64_t getMemEventsHighCount() const {\n    return memEventsHighCount_;\n  }\n\n  [[nodiscard]] int64_t getMemEventsMaxCount() const {\n    return memEventsMaxCount_;\n  }\n\n  [[nodiscard]] double getCpuPressureAvg10Pct() const {\n    return cpuPressureAvg10Pct_;\n  }\n\n  [[nodiscard]] double getCpuPressureAvg60Pct() const {\n    return cpuPressureAvg60Pct_;\n  }\n\n  [[nodiscard]] double getCpuPressureAvg300Pct() const {\n    return cpuPressureAvg300Pct_;\n  }\n\n  [[nodiscard]] double getIoPressureAvg10Pct() const {\n    return ioPressureAvg10Pct_;\n  }\n\n  [[nodiscard]] double getIoPressureAvg60Pct() const {\n    return ioPressureAvg60Pct_;\n  }\n\n  [[nodiscard]] double getIoPressureAvg300Pct() const {\n    return ioPressureAvg300Pct_;\n  }\n\n  // Gets the total memory of the system in bytes\n  [[nodiscard]] uint64_t getTotalMemBytes() const {\n    return totalMemBytes_;\n  }\n\n  /**\n   * Gets the unused memory (0-100 Free/available) of the system as a\n   * percent\n   */\n  [[nodiscard]] double getFreeMemPct() const {\n    return 100 - getUsedMemPct();\n  }\n\n  // Gets the used memory (0-100) of the system as a percent\n  [[nodiscard]] double getUsedMemPct() const {\n    return ((double)usedMemBytes_) / totalMemBytes_ * 100;\n  }\n\n  // Gets the used anonymous memory (0-1.0) of the system as a ratio\n  [[nodiscard]] double getUsedMemRatio() const {\n    return ((double)usedMemBytes_) / totalMemBytes_;\n  }\n\n  // Returns current level of TCP memory consumption\n  // measured in memory pages\n  [[nodiscard]] uint64_t getTcpMemPages() const {\n    return tcpMemoryPages_;\n  }\n\n  // Returns Low TCP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getLowTcpMemLimitPages() const {\n    return minTcpMemLimit_;\n  }\n\n  // Returns Pressure TCP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getPressureTcpMemLimitPages() const {\n    return pressureTcpMemLimit_;\n  }\n\n  // Returns Low TCP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getMaxTcpMemLimitPages() const {\n    return maxTcpMemLimit_;\n  }\n\n  // Gets the used TCP memory (0-1.0) as a ratio to max TCP mem limit\n  [[nodiscard]] double getTcpMemRatio() const {\n    return calculateRatio(tcpMemoryPages_, maxTcpMemLimit_);\n  }\n\n  // Gets the low TCP memory threshold (0-1.0) as a ratio to\n  // max TCP mem limit\n  [[nodiscard]] double getLowTcpMemLimitRatio() const {\n    return calculateRatio(minTcpMemLimit_, maxTcpMemLimit_);\n  }\n\n  // Gets the TCP memory pressure threshold (0-1.0) as a ratio to\n  // max TCP mem limit\n  [[nodiscard]] double getPressureTcpMemLimitRatio() const {\n    return calculateRatio(pressureTcpMemLimit_, maxTcpMemLimit_);\n  }\n\n  // Gets the used TCP memory (0-100) of the system as a percent\n  [[nodiscard]] double getUsedTcpMemPct() const {\n    return getTcpMemRatio() * 100;\n  }\n\n  // Gets the low TCP memory threshold (0-100) of the system as a percent\n  [[nodiscard]] double getLowTcpMemThresholdPct() const {\n    return getLowTcpMemLimitRatio() * 100;\n  }\n\n  // Gets the Pressure TCP memory threshold (0-100) of the system as a percent\n  [[nodiscard]] double getPressureTcpMemThresholdPct() const {\n    return getPressureTcpMemLimitRatio() * 100;\n  }\n\n  // Returns True if TCP memory fields were initialized successfully,\n  // returns False otherwise\n  [[nodiscard]] bool tcpMemoryStatsCollected() const {\n    return tcpMemoryPages_ != 0 && maxTcpMemLimit_ != 0 &&\n           pressureTcpMemLimit_ != 0 && minTcpMemLimit_ != 0;\n  }\n\n  // Returns current level of UDP memory consumption\n  // measured in memory pages\n  [[nodiscard]] uint64_t getUdpMemPages() const {\n    return udpMemoryPages_;\n  }\n\n  // Returns Low UDP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getLowUdpMemLimitPages() const {\n    return minUdpMemLimit_;\n  }\n\n  // Returns Pressure UDP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getPressureUdpMemLimitPages() const {\n    return pressureUdpMemLimit_;\n  }\n\n  // Returns Low UDP Memory threshold measured in memory pages\n  [[nodiscard]] uint64_t getMaxUdpMemLimitPages() const {\n    return maxUdpMemLimit_;\n  }\n\n  // Gets the used UDP memory (0-1.0) as a ratio to max UDP mem limit\n  [[nodiscard]] double getUdpMemRatio() const {\n    return calculateRatio(udpMemoryPages_, maxUdpMemLimit_);\n  }\n\n  // Gets the low UDP memory threshold (0-1.0) as a ratio to\n  // max UDP mem limit\n  [[nodiscard]] double getLowUdpMemLimitRatio() const {\n    return calculateRatio(minUdpMemLimit_, maxUdpMemLimit_);\n  }\n\n  // Gets the UDP memory pressure threshold (0-1.0) as a ratio to\n  // max UDP mem limit\n  [[nodiscard]] double getPressureUdpMemLimitRatio() const {\n    return calculateRatio(pressureUdpMemLimit_, maxUdpMemLimit_);\n  }\n\n  // Gets the used UDP memory (0-100) of the system as a percent\n  [[nodiscard]] double getUsedUdpMemPct() const {\n    return getUdpMemRatio() * 100;\n  }\n\n  // Gets the low UDP memory threshold (0-100) of the system as a percent\n  [[nodiscard]] double getLowUdpMemThresholdPct() const {\n    return getLowUdpMemLimitRatio() * 100;\n  }\n\n  // Gets the Pressure UDP memory threshold (0-100) of the system as a percent\n  [[nodiscard]] double getPressureUdpMemThresholdPct() const {\n    return getPressureUdpMemLimitRatio() * 100;\n  }\n\n  // Returns True if UDP memory fields were initialized successfully,\n  // returns False otherwise\n  [[nodiscard]] bool udpMemoryStatsCollected() const {\n    return udpMemoryPages_ != 0 && maxUdpMemLimit_ != 0 &&\n           pressureUdpMemLimit_ != 0 && minUdpMemLimit_ != 0;\n  }\n\n  // Gets the NIC utilization ratio [0, 1.0]\n  [[nodiscard]] double getNicUtilRatio() const {\n    return nicUtilRatio_;\n  }\n\n  // Returns True if NIC utilization was successfully collected\n  [[nodiscard]] bool nicStatsCollected() const {\n    return nicUtilRatio_ > 0.0;\n  }\n\n  void setCpuStats(CpuStats&& cpuStats) {\n    cpuRatioUtil_ = cpuStats.cpuUsageRatio;\n    cpuCoreUsageRatios_ = std::move(cpuStats.cpuCoreUsageRatios);\n    cpuUtilPercentileConfigured_ = cpuStats.cpuUtilPercentileConfigured;\n    cpuRatioUtilPercentile_ = cpuStats.cpuRatioUtilPercentile;\n    cpuSoftIrqRatioUtil_ = cpuStats.cpuSoftIrqUsageRatio;\n    softIrqCpuCoreRatioUtils_ = std::move(cpuStats.softIrqCpuCoreRatioUtils);\n  }\n\n  void setMemStats(uint64_t usedMemBytes, uint64_t totalMemBytes) {\n    usedMemBytes_ = usedMemBytes;\n    totalMemBytes_ = totalMemBytes;\n  }\n\n  void setCpuPressureStats(double avg10, double avg60, double avg300) {\n    cpuPressureAvg10Pct_ = avg10;\n    cpuPressureAvg60Pct_ = avg60;\n    cpuPressureAvg300Pct_ = avg300;\n  }\n\n  void setMemPressureFullStats(double avg10, double avg60, double avg300) {\n    memPressureFullAvg10Pct_ = avg10;\n    memPressureFullAvg60Pct_ = avg60;\n    memPressureFullAvg300Pct_ = avg300;\n  }\n\n  void setMemPressureSomeStats(double avg10, double avg60, double avg300) {\n    memPressureSomeAvg10Pct_ = avg10;\n    memPressureSomeAvg60Pct_ = avg60;\n    memPressureSomeAvg300Pct_ = avg300;\n  }\n\n  void setMemEventsStats(int64_t highCount, int64_t maxCount) {\n    memEventsHighCount_ = highCount;\n    memEventsMaxCount_ = maxCount;\n  }\n\n  void setIoPressureStats(double avg10, double avg60, double avg300) {\n    ioPressureAvg10Pct_ = avg10;\n    ioPressureAvg60Pct_ = avg60;\n    ioPressureAvg300Pct_ = avg300;\n  }\n\n  /**\n   * Sets the structure fields describing TCP memory state.\n   */\n  void setTcpMemStats(uint64_t current,\n                      uint64_t minThreshold,\n                      uint64_t pressureThreshold,\n                      uint64_t maxThreshold) {\n    tcpMemoryPages_ = current;\n    minTcpMemLimit_ = minThreshold;\n    pressureTcpMemLimit_ = pressureThreshold;\n    maxTcpMemLimit_ = maxThreshold;\n  }\n\n  /**\n   * Sets the structure fields describing UDP memory state.\n   */\n  void setUdpMemStats(uint64_t current,\n                      uint64_t minThreshold,\n                      uint64_t pressureThreshold,\n                      uint64_t maxThreshold) {\n    udpMemoryPages_ = current;\n    minUdpMemLimit_ = minThreshold;\n    pressureUdpMemLimit_ = pressureThreshold;\n    maxUdpMemLimit_ = maxThreshold;\n  }\n\n  /**\n   * Sets the NIC utilization ratio [0, 1.0].\n   */\n  void setNicStats(double utilRatio) {\n    nicUtilRatio_ = utilRatio;\n  }\n\n protected:\n  // Convert an absolute integer value and max limit to a float point ratio.\n  [[nodiscard]] double calculateRatio(uint64_t value, uint64_t maxLimit) const {\n    if (maxLimit != 0) {\n      return double(value) / maxLimit;\n    }\n    return 0.0;\n  }\n\n  // Resource utilization metrics\n  double cpuRatioUtil_{0};\n  std::vector<double> cpuCoreUsageRatios_;\n  double cpuRatioUtilPercentile_{0};\n  double cpuUtilPercentileConfigured_{61};\n  double cpuSoftIrqRatioUtil_{0};\n  std::vector<double> softIrqCpuCoreRatioUtils_;\n  uint64_t usedMemBytes_{0};\n  uint64_t totalMemBytes_{0};\n  uint64_t tcpMemoryPages_{0};\n  uint64_t maxTcpMemLimit_{0};\n  uint64_t pressureTcpMemLimit_{0};\n  uint64_t minTcpMemLimit_{0};\n  uint64_t udpMemoryPages_{0};\n  uint64_t maxUdpMemLimit_{0};\n  uint64_t pressureUdpMemLimit_{0};\n  uint64_t minUdpMemLimit_{0};\n\n  // NIC utilization\n  double nicUtilRatio_{0.0};\n\n  // Pressure metrics (experimental)\n  double cpuPressureAvg10Pct_{0};\n  double cpuPressureAvg60Pct_{0};\n  double cpuPressureAvg300Pct_{0};\n\n  double memPressureFullAvg10Pct_{0};\n  double memPressureFullAvg60Pct_{0};\n  double memPressureFullAvg300Pct_{0};\n\n  double memPressureSomeAvg10Pct_{0};\n  double memPressureSomeAvg60Pct_{0};\n  double memPressureSomeAvg300Pct_{0};\n\n  int64_t memEventsHighCount_{0};\n  int64_t memEventsMaxCount_{0};\n\n  double ioPressureAvg10Pct_{0};\n  double ioPressureAvg60Pct_{0};\n  double ioPressureAvg300Pct_{0};\n};\n\n/**\n * A class that abstracts away actually fetching the underlying data.\n * Handles abstraction of the fetching (from particular OSes, container\n * systems, etc)\n */\nclass Resources {\n public:\n  virtual ~Resources() = default;\n\n  /**\n   * getCurrentData performs the querying resource utilization metrics.\n   */\n  virtual ResourceData getCurrentData() = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/ResourceStats.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/stats/ResourceStats.h>\n\nnamespace proxygen {\n\nResourceStats::ResourceStats(std::unique_ptr<Resources> resources)\n    : PeriodicStats<ResourceData>(\n          new ResourceData(resources->getCurrentData())),\n      resources_(std::move(resources)) {\n}\n\nResourceStats::~ResourceStats() {\n  // Stop refreshing on destruction so the function scheduler thread can\n  // not access destructed class members.\n  stopRefresh();\n}\n\nResourceData* ResourceStats::getNewData() const {\n  return new ResourceData(resources_->getCurrentData());\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/ResourceStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/stats/PeriodicStats.h>\n#include <proxygen/lib/stats/ResourceData.h>\n\nnamespace proxygen {\n\n/**\n * ResourceStats:\n *\n * A class designed to abstract away the internals of retrieving\n * various resource utilization metrics, built using PeriodicStats.\n * See PeriodicStats class documentation for specifics.\n */\nclass ResourceStats : public PeriodicStats<ResourceData> {\n public:\n  /**\n   * Note: as CPU pct utilization requires intervals and at init time there\n   * is only a single data point, pct utilization is initially seeded from\n   * proc loadavg.\n   */\n  explicit ResourceStats(std::unique_ptr<Resources> resources);\n  ~ResourceStats() override;\n\n protected:\n  /**\n   * Override getNewData so that we can return an instance of ResourceData.\n   */\n  ResourceData* getNewData() const override;\n\n  /**\n   * Abstraction that enables callers to provide their own implementations\n   * of the entity that actually queries various metrics.\n   */\n  std::unique_ptr<Resources> resources_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/gen_StatsWrapper.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nset -e\n\nif [ \"x$1\" != \"x\" ];then\n  OUTPUT_DIR=\"$1\"\nfi\n\nstatsWrapper=$(cat <<EOF\n/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/stats/BaseStats.h>\n\n#pragma once\n\nnamespace proxygen {\n\nusing StatsWrapper = proxygen::BaseStats;\n\n} // proxygen\nEOF\n)\n\ntouch \"${OUTPUT_DIR?}/proxygen/lib/stats/StatsWrapper.h\"\necho \"$statsWrapper\" >  \"${OUTPUT_DIR?}/proxygen/lib/stats/StatsWrapper.h\"\n\necho \"Generated StatsWrapper.h\"\n"
  },
  {
    "path": "proxygen/lib/stats/test/BaseStatsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <gtest/gtest.h>\n#include <proxygen/lib/stats/BaseStats.h>\n\nusing namespace ::testing;\n\nnamespace proxygen {\nclass TestLazyQuantileStatWrapper : public BaseStats::LazyQuantileStatWrapper {\n public:\n  explicit TestLazyQuantileStatWrapper(\n      folly::StringPiece name,\n      folly::Range<const facebook::fb303::ExportType*> stats =\n          facebook::fb303::ExportTypeConsts::kCountAvg,\n      folly::Range<const double*> quantiles =\n          facebook::fb303::QuantileConsts::kP95_P99_P999,\n      folly::Range<const size_t*> slidingWindowPeriods =\n          facebook::fb303::SlidingWindowPeriodConsts::kOneMin)\n      : BaseStats::LazyQuantileStatWrapper(\n            name, stats, quantiles, slidingWindowPeriods) {\n  }\n  bool quantileStatCreated() {\n    return statWrapper_.get() != nullptr;\n  }\n  // The metadata needed to create the quantile stat on-demand\n  bool metadataExists() {\n    return statWrapperInfo_.get() != nullptr;\n  }\n\n  size_t getNumSavedCounters() {\n    return numCountersSaved_.getTcStatUnsafe()->value();\n  }\n};\n\nTEST(ProxyStatsWrapperTest, LazyQuantileStatWrapper) {\n  TestLazyQuantileStatWrapper wrapper(\"test\");\n  EXPECT_TRUE(wrapper.metadataExists());\n  EXPECT_FALSE(wrapper.quantileStatCreated());\n  wrapper.addValue(10);\n  EXPECT_FALSE(wrapper.metadataExists());\n  EXPECT_TRUE(wrapper.quantileStatCreated());\n  wrapper.addValue(100);\n  EXPECT_FALSE(wrapper.metadataExists());\n  EXPECT_TRUE(wrapper.quantileStatCreated());\n}\n\nTEST(ProxyStatsWrapperTest, MultipleLazyQuantileStatWrapper) {\n  TestLazyQuantileStatWrapper wrapper1(\"test1\");\n  TestLazyQuantileStatWrapper wrapper2(\"test2\");\n  EXPECT_TRUE(wrapper1.metadataExists());\n  EXPECT_TRUE(wrapper2.metadataExists());\n  EXPECT_FALSE(wrapper1.quantileStatCreated());\n  EXPECT_FALSE(wrapper2.quantileStatCreated());\n  wrapper1.addValue(10);\n  wrapper2.addValue(10);\n  EXPECT_FALSE(wrapper1.metadataExists());\n  EXPECT_FALSE(wrapper2.metadataExists());\n  EXPECT_TRUE(wrapper1.quantileStatCreated());\n  EXPECT_TRUE(wrapper2.quantileStatCreated());\n}\n\nTEST(ProxyStatsWrapperTest, TestSavingsCounter) {\n  TestLazyQuantileStatWrapper wrapper(\n      \"test1\",\n      facebook::fb303::ExportTypeConsts::kCountAvg,\n      facebook::fb303::QuantileConsts::kP95_P99_P999,\n      facebook::fb303::SlidingWindowPeriodConsts::kOneMinTenMin);\n  EXPECT_EQ(wrapper.getNumSavedCounters(), 6);\n  wrapper.addValue(10);\n  // Add a value a second time to make sure it's only decremented on the first\n  // counter bump\n  wrapper.addValue(10);\n  EXPECT_EQ(wrapper.getNumSavedCounters(), 0);\n}\n\nTEST(ProxyStatsWrapperTest, LazyQuantileStatWrapperConcurrency) {\n  TestLazyQuantileStatWrapper wrapper(\"test\");\n  auto bumpStat = [&]() {\n    for (int i = 0; i < 100; i++) {\n      wrapper.addValue(10);\n    }\n  };\n\n  std::vector<std::thread> threads;\n  threads.reserve(10);\n  for (int i = 0; i < 10; i++) {\n    threads.emplace_back(bumpStat);\n  }\n  for (auto& thread : threads) {\n    thread.join();\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET PeriodicStatsTest\n  SOURCES\n    PeriodicStatsTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET ResourceDataTest\n  SOURCES\n    ResourceDataTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET ResourceStatsTest\n  SOURCES\n    ResourceStatsTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET BaseStatsTest\n  SOURCES\n    BaseStatsTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/stats/test/MockARLResourceStats.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <proxygen/facebook/lib/statistics/ARLResourceStats.h>\n#include <proxygen/lib/stats/ResourceData.h>\n\n#include <proxygen/lib/stats/test/MockResources.h>\n\nnamespace proxygen {\n\n// MockARLResourceStats used by AdaptiveRateLimiterUnderTest.\n// Tests in this class should not actually need to interact with this object.\n// Tests should overide what the shed algo returns to customize behavior.\nclass MockARLResourceStats : public ARLResourceStats {\n public:\n  explicit MockARLResourceStats(std::unique_ptr<MockResources> resources)\n      : ARLResourceStats(std::move(resources)) {\n  }\n\n  MOCK_METHOD(const ARLResourceData&, getCurrentData, (), (const));\n  MOCK_METHOD(const ARLResourceData&, getPreviousData, (), (const));\n\n  // Tests in this class should not care what value is returned from\n  // getCurrentData() as it is the shed algo that consumes it.  So we\n  // only need to make sure its called the number of times we expect.\n  void expectGetCurrentDataToBeCalled(int times) {\n    if (times > 0) {\n      EXPECT_CALL(*this, getCurrentData())\n          .Times(times)\n          .WillRepeatedly(testing::ReturnRef(dummyARLResourceData_));\n    } else {\n      EXPECT_CALL(*this, getCurrentData()).Times(times);\n    }\n  }\n\n  void expectGetPreviousDataToReturnValue(const ARLResourceData& data) {\n    EXPECT_CALL(*this, getPreviousData()).WillOnce(testing::ReturnRef(data));\n  }\n\n private:\n  // Dummy ARLResourceData that only exists so expectGetCurrentDataToBeCalled()\n  // can return a valid reference.\n  ARLResourceData dummyARLResourceData_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/stats/test/MockResources.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <proxygen/facebook/lib/statistics/ARLResourceStats.h>\n#include <proxygen/facebook/lib/statistics/HostResources.h>\n#include <proxygen/lib/stats/ResourceData.h>\n\n/**\n * Test Resources implementation that allows us to control what resource data is\n * returned to ResourceStats\n */\nclass MockResources : public proxygen::Resources {\n public:\n  MOCK_METHOD(proxygen::ResourceData, getCurrentData, ());\n\n  void expectNextCallsToReturnValue(const proxygen::ResourceData& data,\n                                    uint64_t times) {\n    EXPECT_CALL(*this, getCurrentData())\n        .Times(times)\n        .WillRepeatedly(testing::Return(data));\n  }\n\n  void expectAllCallsToReturnValue(const proxygen::ResourceData& data) {\n    EXPECT_CALL(*this, getCurrentData()).WillRepeatedly(testing::Return(data));\n  }\n\n  struct GetTestResourceDataParams {\n    double cpuUtilRatio{0.5};\n    double cpuSoftIrqUtilRatio{0.5};\n    uint64_t numCpuCores{1};\n    double memUtilRatio{0.5};\n    double memPressureFullRatio{0.0};\n    double tcpMemUtilRatio{0.5};\n    double udpMemUtilRatio{0.5};\n    double nicUtilRatio{0.0};\n    std::chrono::milliseconds lastUpdateTime{-1};\n  };\n\n  // Helper method to get a ResourceData instance formed using the built-in\n  // test params.\n  static proxygen::ResourceData getTestResourceData(\n      const GetTestResourceDataParams& params) {\n    proxygen::ResourceData data;\n    auto cpuStats =\n        proxygen::CpuStats{.cpuUsageRatio = params.cpuUtilRatio,\n                           .cpuCoreUsageRatios = std::vector<double>(\n                               params.numCpuCores, params.cpuUtilRatio),\n                           .cpuUtilPercentileConfigured = 80,\n                           .cpuRatioUtilPercentile = params.cpuUtilRatio,\n                           .cpuSoftIrqUsageRatio = params.cpuSoftIrqUtilRatio,\n                           .softIrqCpuCoreRatioUtils = std::vector<double>(\n                               params.numCpuCores, params.cpuSoftIrqUtilRatio)};\n    data.setCpuStats(std::move(cpuStats));\n    uint64_t totalMemBytes = 100;\n    data.setMemStats(\n        folly::to<uint64_t>(round(params.memUtilRatio * totalMemBytes)),\n        totalMemBytes);\n    data.setMemPressureFullStats(params.memPressureFullRatio * 100,  // avg10\n                                 params.memPressureFullRatio * 100,  // avg60\n                                 params.memPressureFullRatio * 100); // avg300\n    uint64_t totalNetMemBytes = 100;\n    data.setTcpMemStats(\n        folly::to<uint64_t>(round(params.tcpMemUtilRatio * totalNetMemBytes)),\n        25,\n        75,\n        totalNetMemBytes);\n    data.setUdpMemStats(\n        folly::to<uint64_t>(round(params.udpMemUtilRatio * totalNetMemBytes)),\n        25,\n        75,\n        totalNetMemBytes);\n\n    if (params.nicUtilRatio > 0.0) {\n      data.setNicStats(params.nicUtilRatio);\n    }\n\n    if (params.lastUpdateTime < std::chrono::milliseconds(0)) {\n      data.refreshLastUpdateTime();\n    } else {\n      data.setLastUpdateTime(params.lastUpdateTime);\n    }\n\n    return data;\n  }\n};\n"
  },
  {
    "path": "proxygen/lib/stats/test/PeriodicStatsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/stats/PeriodicStats.h>\n#include <proxygen/lib/stats/test/PeriodicStatsTestHelper.h>\n\n#include <chrono>\n#include <folly/portability/GTest.h>\n#include <folly/synchronization/Baton.h>\n\nusing namespace proxygen;\n\n// Test container for use as PeriodicStats template type in tests.\nclass PeriodicStatsData {\n public:\n  [[nodiscard]] std::chrono::milliseconds getLastUpdateTime() const {\n    return time_;\n  }\n\n  void setLastUpdateTime(\n      std::chrono::milliseconds updateTime =\n          std::chrono::duration_cast<std::chrono::milliseconds>(\n              std::chrono::system_clock::now().time_since_epoch())) {\n    time_ = updateTime;\n  }\n\n private:\n  std::chrono::milliseconds time_{0};\n};\n\n// Test instance of PeriodicStats used in subsequent tests.\nclass PeriodicStatsUnderTest : public PeriodicStats<PeriodicStatsData> {\n public:\n  PeriodicStatsUnderTest(PeriodicStatsData* initialData)\n      : PeriodicStats<PeriodicStatsData>(initialData) {\n  }\n\n  PeriodicStatsData& getMutableData() {\n    return *data_.load();\n  }\n\n protected:\n  // Subclass implementation.  We could have opted to mock this method but\n  // technically this interface could be private and we specically only care\n  // about validating and verifying the public interface of this class.\n  PeriodicStatsData* getNewData() const override {\n    auto* data = new PeriodicStatsData();\n    // As this is a test context, we really only need the timestamp to be new\n    // and not actually accurate.\n    data->setLastUpdateTime(data_.load()->getLastUpdateTime() +\n                            std::chrono::milliseconds(1));\n    return data;\n  }\n};\n\nclass PeriodicStatsTest : public ::testing::Test {\n public:\n  void SetUp() override {\n    auto* data = new PeriodicStatsData();\n    data->setLastUpdateTime();\n    initialUpdateTime_ = data->getLastUpdateTime();\n\n    periodStatsUT_ = std::make_unique<PeriodicStatsUnderTest>(data);\n    periodicStatsUTHelper_ =\n        std::make_unique<PeriodicStatsTestHelper<PeriodicStatsData>>(\n            periodStatsUT_.get());\n  }\n\n  void TearDown() override {\n    periodStatsUT_->stopRefresh();\n  }\n\n protected:\n  std::chrono::milliseconds initialUpdateTime_{0};\n  std::unique_ptr<PeriodicStatsUnderTest> periodStatsUT_{nullptr};\n  std::unique_ptr<PeriodicStatsTestHelper<PeriodicStatsData>>\n      periodicStatsUTHelper_{nullptr};\n};\n\n// Tests Below\n\nTEST_F(PeriodicStatsTest, GetCurrentData) {\n  const auto data = periodStatsUT_->getCurrentData();\n  EXPECT_EQ(data.getLastUpdateTime(), initialUpdateTime_);\n}\n\nTEST_F(PeriodicStatsTest, GetCurrentDataRefreshes) {\n  // Get the current data and copy it for comparison later.\n  // This is required as getCurrentData returns a reference.\n  // Also verify that getting it again returns the same data.\n  const auto data = periodStatsUT_->getCurrentData();\n  auto copiedData = data;\n  EXPECT_EQ(copiedData.getLastUpdateTime(),\n            periodStatsUT_->getCurrentData().getLastUpdateTime());\n\n  // Now mutate the cached data directly and expect to get this mutated\n  // version back, even if the refresh thread has not been started.\n  periodStatsUT_->getMutableData().setLastUpdateTime(\n      periodStatsUT_->getMutableData().getLastUpdateTime() +\n      std::chrono::milliseconds(1));\n  const auto refreshedData = periodStatsUT_->getCurrentData();\n  auto copiedRefreshedData = refreshedData;\n  EXPECT_GT(copiedRefreshedData.getLastUpdateTime(),\n            copiedData.getLastUpdateTime());\n  EXPECT_EQ(copiedRefreshedData.getLastUpdateTime(),\n            periodStatsUT_->getCurrentData().getLastUpdateTime());\n\n  // Now set a refresh period to something longer than the rest of the test\n  // duration but with an initial delay of 1 ms so that the data refreshes\n  // once but never again for the duration of the test.\n  periodicStatsUTHelper_->waitForRefresh({});\n\n  // Now we simply verify that we indeed refreshed once, but that we don't\n  // for the remainder of this test.\n  const auto newData = periodStatsUT_->getCurrentData();\n  auto copiedNewData = newData;\n  EXPECT_GT(copiedNewData.getLastUpdateTime(),\n            copiedRefreshedData.getLastUpdateTime());\n  periodicStatsUTHelper_->waitForRefresh(\n      {.startRefresh = false, .waitResult = false});\n  EXPECT_EQ(periodStatsUT_->getCurrentData().getLastUpdateTime(),\n            copiedNewData.getLastUpdateTime());\n\n  periodStatsUT_->stopRefresh();\n}\n"
  },
  {
    "path": "proxygen/lib/stats/test/PeriodicStatsTestHelper.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/stats/PeriodicStats.h>\n\n#include <chrono>\n#include <folly/portability/GTest.h>\n#include <folly/synchronization/Baton.h>\n\ntemplate <class T>\nclass PeriodicStatsTestHelper {\n public:\n  PeriodicStatsTestHelper(proxygen::PeriodicStats<T>* psUT) {\n    psUT_ = psUT;\n\n    std::function<void()> refreshCb(\n        std::bind(&PeriodicStatsTestHelper::refreshCb, this));\n    psUT_->setRefreshCB(refreshCb);\n  }\n\n  struct WaitForRefreshParams {\n    bool startRefresh{true};\n    bool waitResult{true};\n\n    std::chrono::milliseconds waitDuration{1000};\n    std::chrono::milliseconds refreshPeriod{30000};\n    std::chrono::milliseconds refreshInitialDelay{0};\n  };\n\n  // Uses folly batons to synchrously wait, as specified, for the next refresh\n  // of the underlying data.\n  void waitForRefresh(WaitForRefreshParams params) {\n    folly::Baton<> updateCachedDataBaton;\n    updateCachedDataBaton_ = &updateCachedDataBaton;\n    if (params.startRefresh) {\n      psUT_->refreshWithPeriod(\n          std::chrono::milliseconds(params.refreshPeriod),\n          std::chrono::milliseconds(params.refreshInitialDelay));\n    }\n    EXPECT_EQ(updateCachedDataBaton.try_wait_for(params.waitDuration),\n              params.waitResult);\n  }\n\n private:\n  // Refresh cob invoked on refresh of underlying data.\n  void refreshCb() {\n    auto updateCachedDataBaton = updateCachedDataBaton_.exchange(nullptr);\n    if (updateCachedDataBaton) {\n      updateCachedDataBaton->post();\n    }\n  }\n\n  // A synchronization primitive that may be used by tests to block until the\n  // next refresh.\n  std::atomic<folly::Baton<>*> updateCachedDataBaton_{nullptr};\n\n  // Wrapped PeriodicStats insance under test.\n  proxygen::PeriodicStats<T>* psUT_;\n};\n"
  },
  {
    "path": "proxygen/lib/stats/test/ResourceDataTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/stats/ResourceData.h\"\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nclass ResourceDataTest : public ::testing::Test {};\n"
  },
  {
    "path": "proxygen/lib/stats/test/ResourceStatsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/stats/ResourceStats.h>\n#include <proxygen/lib/stats/test/MockResources.h>\n#include <proxygen/lib/stats/test/PeriodicStatsTestHelper.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nclass ResourceStatsUnderTest : public ResourceStats {\n public:\n  explicit ResourceStatsUnderTest(std::unique_ptr<MockResources> resources)\n      : ResourceStats(std::move(resources)) {\n  }\n\n  // Allows callers the ability to set expectations and return values\n  // appropriately.\n  MockResources* getMockResources() const {\n    return static_cast<MockResources*>(resources_.get());\n  }\n};\n\nclass ResourceStatsTest : public ::testing::Test {\n public:\n  void SetUp() override {\n    // Setup resourceStatsUT_ for initial use.\n    auto mockResources = std::make_unique<testing::StrictMock<MockResources>>();\n    mockResources->expectNextCallsToReturnValue(\n        MockResources::getTestResourceData(testRDParams), 1);\n\n    resourceStatsUT_ =\n        std::make_unique<ResourceStatsUnderTest>(std::move(mockResources));\n    periodicStatsUTHelper_ =\n        std::make_unique<PeriodicStatsTestHelper<ResourceData>>(\n            resourceStatsUT_.get());\n  }\n\n  void TearDown() override {\n    resourceStatsUT_->stopRefresh();\n  }\n\n protected:\n  MockResources::GetTestResourceDataParams testRDParams;\n  std::unique_ptr<ResourceStatsUnderTest> resourceStatsUT_;\n  std::unique_ptr<PeriodicStatsTestHelper<ResourceData>> periodicStatsUTHelper_{\n      nullptr};\n};\n\n// Verifies that the initial data instance is correctly copied to a caller.\nTEST_F(ResourceStatsTest, Constructs) {\n  const auto data = resourceStatsUT_->getCurrentData();\n\n  EXPECT_EQ(data.getCpuRatioUtil(), testRDParams.cpuUtilRatio);\n\n  EXPECT_EQ(data.getSoftIrqCpuCoreRatioUtils().size(),\n            testRDParams.numCpuCores);\n  for (auto const& cpuSoftIrqCoreRatioUtil :\n       data.getSoftIrqCpuCoreRatioUtils()) {\n    EXPECT_EQ(cpuSoftIrqCoreRatioUtil, testRDParams.cpuSoftIrqUtilRatio);\n    ;\n  }\n\n  EXPECT_EQ(data.getUsedMemPct(), testRDParams.memUtilRatio * 100);\n\n  EXPECT_EQ(data.getTcpMemRatio(), testRDParams.tcpMemUtilRatio);\n\n  EXPECT_EQ(data.getUdpMemRatio(), testRDParams.udpMemUtilRatio);\n}\n\n// Verify that on refresh that GetCurrentData is called exactly once.\nTEST_F(ResourceStatsTest, GetCurrentDataOnRefresh) {\n  EXPECT_CALL(*resourceStatsUT_->getMockResources(), getCurrentData());\n  periodicStatsUTHelper_->waitForRefresh({});\n}\n"
  },
  {
    "path": "proxygen/lib/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\nadd_library(testtransport TestAsyncTransport.cpp)\ntarget_include_directories(\n    testtransport PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIR}\n)\ntarget_compile_options(\n    testtransport PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\ntarget_link_libraries(testtransport PUBLIC proxygen)\n\nadd_library(testmain TestMain.cpp)\ntarget_include_directories(\n    testmain PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\n    ${LIBGMOCK_INCLUDE_DIR}\n    ${LIBGTEST_INCLUDE_DIR}\n)\ntarget_link_libraries(testmain PUBLIC Folly::folly)\ntarget_compile_options(\n    testmain PRIVATE\n    ${_PROXYGEN_COMMON_COMPILE_OPTIONS}\n)\n"
  },
  {
    "path": "proxygen/lib/test/TestAsyncTransport.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/test/TestAsyncTransport.h>\n\n#include <folly/SocketAddress.h>\n#include <folly/io/IOBuf.h>\n#include <folly/io/async/AsyncSocketException.h>\n#include <folly/io/async/EventBase.h>\n#include <vector>\n\nusing folly::AsyncSocketException;\nusing folly::AsyncTimeout;\nusing folly::AsyncTransport;\nusing folly::EventBase;\nusing folly::IOBuf;\nusing folly::SocketAddress;\nusing folly::WriteFlags;\nusing proxygen::TimePoint;\nusing std::shared_ptr;\nusing std::unique_ptr;\n\n/*\n * TestAsyncTransport::ReadEvent\n */\n\nclass TestAsyncTransport::ReadEvent {\n public:\n  ReadEvent(const void* buf, size_t buflen, std::chrono::milliseconds delay)\n      : buffer_(nullptr),\n        readStart_(nullptr),\n        dataEnd_(nullptr),\n        isError_(false),\n        exception_(folly::AsyncSocketException::UNKNOWN, \"\"),\n        delay_(delay) {\n    if (buflen == 0) {\n      // This means EOF\n      return;\n    }\n    CHECK_NOTNULL(buf);\n\n    buffer_ = static_cast<char*>(malloc(buflen));\n    if (buffer_ == nullptr) {\n      throw std::bad_alloc();\n    }\n    memcpy(buffer_, buf, buflen);\n    readStart_ = buffer_;\n    dataEnd_ = buffer_ + buflen;\n  }\n\n  ReadEvent(const folly::AsyncSocketException& ex,\n            std::chrono::milliseconds delay)\n      : buffer_(nullptr),\n        readStart_(nullptr),\n        dataEnd_(nullptr),\n        isError_(true),\n        exception_(ex),\n        delay_(delay) {\n  }\n\n  ReadEvent(std::unique_ptr<IOBuf> buf, std::chrono::milliseconds delay)\n      : buffer_(nullptr),\n        readStart_(nullptr),\n        dataEnd_(nullptr),\n        isError_(false),\n        exception_(folly::AsyncSocketException::UNKNOWN, \"\"),\n        movableBuffer_(std::move(buf)),\n        delay_(delay) {\n  }\n\n  ~ReadEvent() {\n    free(buffer_);\n  }\n\n  [[nodiscard]] std::chrono::milliseconds getDelay() const {\n    return delay_;\n  }\n\n  [[nodiscard]] bool isFinalEvent() const {\n    return buffer_ == nullptr && movableBuffer_.get() == nullptr;\n  }\n\n  [[nodiscard]] const char* getBuffer() const {\n    return readStart_;\n  }\n\n  [[nodiscard]] size_t getLength() const {\n    return (dataEnd_ - readStart_);\n  }\n\n  void consumeData(size_t length) {\n    CHECK_LE(readStart_ + length, dataEnd_);\n    readStart_ += length;\n  }\n\n  [[nodiscard]] bool isError() const {\n    return isError_;\n  }\n\n  [[nodiscard]] const folly::AsyncSocketException& getException() const {\n    return exception_;\n  }\n\n  [[nodiscard]] bool isMovableBuffer() const {\n    return movableBuffer_.get() != nullptr;\n  }\n\n  std::unique_ptr<IOBuf> getMovableBuffer() {\n    return std::move(movableBuffer_);\n  }\n\n private:\n  char* buffer_;\n  char* readStart_;\n  char* dataEnd_;\n\n  bool isError_;\n  folly::AsyncSocketException exception_;\n\n  std::unique_ptr<IOBuf> movableBuffer_;\n\n  std::chrono::milliseconds delay_;\n};\n\n/*\n * TestAsyncTransport::WriteEvent methods\n */\n\nTestAsyncTransport::WriteEvent::WriteEvent(TimePoint time, size_t count)\n    : time_(time), count_(count) {\n  // Initialize all of the iov_base pointers to nullptr.  This way we won't free\n  // an uninitialized pointer in the destructor if we fail to allocate any of\n  // the buffers.\n  for (size_t n = 0; n < count_; ++n) {\n    vec_[n].iov_base = nullptr;\n  }\n}\n\nTestAsyncTransport::WriteEvent::~WriteEvent() {\n  for (size_t n = 0; n < count_; ++n) {\n    free(vec_[n].iov_base);\n  }\n}\n\nshared_ptr<TestAsyncTransport::WriteEvent>\nTestAsyncTransport::WriteEvent::newEvent(const struct iovec* vec,\n                                         size_t count) {\n  size_t bufLen = sizeof(WriteEvent) + (count * sizeof(struct iovec));\n  void* buf = malloc(bufLen);\n  if (buf == nullptr) {\n    throw std::bad_alloc();\n  }\n\n  auto now = proxygen::getCurrentTime();\n  shared_ptr<WriteEvent> event(new (buf) WriteEvent(now, count), destroyEvent);\n  for (size_t n = 0; n < count; ++n) {\n    size_t len = vec[n].iov_len;\n    event->vec_[n].iov_len = len;\n    if (len == 0) {\n      event->vec_[n].iov_base = nullptr;\n      continue;\n    }\n\n    event->vec_[n].iov_base = malloc(len);\n    if (event->vec_[n].iov_base == nullptr) {\n      throw std::bad_alloc();\n    }\n    memcpy(event->vec_[n].iov_base, vec[n].iov_base, len);\n  }\n\n  return event;\n}\n\nvoid TestAsyncTransport::WriteEvent::destroyEvent(WriteEvent* event) {\n  event->~WriteEvent();\n  free(event);\n}\n\n/*\n * TestAsyncTransport methods\n */\n\nTestAsyncTransport::TestAsyncTransport(EventBase* eventBase)\n    : AsyncTimeout(eventBase),\n      eventBase_(eventBase),\n      readCallback_(nullptr),\n      sendTimeout_(0),\n      readState_(kStateOpen),\n      writeState_(kStateOpen),\n      readEvents_() {\n}\n\nvoid TestAsyncTransport::setReadCB(AsyncTransport::ReadCallback* callback) {\n  if (readCallback_ == callback) {\n    return;\n  }\n\n  if (callback == nullptr) {\n    cancelTimeout();\n    readCallback_ = nullptr;\n    return;\n  }\n\n  bool wasNull = (readCallback_ == nullptr);\n\n  if (readState_ == kStateClosed) {\n    callback->readEOF();\n    return;\n  } else if (readState_ == kStateError) {\n    folly::AsyncSocketException ex(folly::AsyncSocketException::NOT_OPEN,\n                                   \"setReadCB() called with socket in \"\n                                   \"invalid state\");\n    callback->readErr(ex);\n    return;\n  }\n\n  CHECK_EQ(readState_, kStateOpen);\n  readCallback_ = callback;\n\n  // If the callback was previously nullptr, read events were paused, so we need\n  // to reschedule them now.\n  //\n  // If it was set before, read events are still scheduled, so we are done now\n  // and can return.\n  if (!wasNull) {\n    return;\n  }\n\n  if (!proxygen::timePointInitialized(nextReadEventTime_)) {\n    // Either readEvents_ is empty, or startReadEvents() hasn't been called yet\n    return;\n  }\n  CHECK(!readEvents_.empty());\n  scheduleNextReadEvent(proxygen::getCurrentTime());\n}\n\nTestAsyncTransport::ReadCallback* TestAsyncTransport::getReadCallback() const {\n  return dynamic_cast<TestAsyncTransport::ReadCallback*>(readCallback_);\n}\n\nvoid TestAsyncTransport::write(AsyncTransport::WriteCallback* callback,\n                               const void* buf,\n                               size_t bytes,\n                               WriteFlags flags) {\n  iovec op;\n  op.iov_base = const_cast<void*>(buf);\n  op.iov_len = bytes;\n  this->writev(callback, &op, 1, flags);\n}\n\nvoid TestAsyncTransport::writev(AsyncTransport::WriteCallback* callback,\n                                const iovec* vec,\n                                size_t count,\n                                WriteFlags flags) {\n  if (isSet(flags, WriteFlags::CORK)) {\n    corkCount_++;\n  } else if (isSet(flags, WriteFlags::EOR)) {\n    eorCount_++;\n  }\n  if (!writesAllowed()) {\n    AsyncSocketException ex(AsyncSocketException::NOT_OPEN,\n                            \"write() called on non-open TestAsyncTransport\");\n    auto cb = dynamic_cast<WriteCallback*>(callback);\n    DCHECK(cb);\n    cb->writeErr(0, ex);\n    return;\n  }\n\n  shared_ptr<WriteEvent> event = WriteEvent::newEvent(vec, count);\n  if (writeState_ == kStatePaused || pendingWriteEvents_.size() > 0) {\n    pendingWriteEvents_.emplace_back(event, callback);\n  } else {\n    CHECK_EQ(writeState_, kStateOpen);\n    writeEvents_.push_back(event);\n    callback->writeSuccess();\n  }\n}\n\nvoid TestAsyncTransport::writeChain(AsyncTransport::WriteCallback* callback,\n                                    std::unique_ptr<folly::IOBuf>&& iob,\n                                    WriteFlags flags) {\n  size_t count = iob->countChainElements();\n  std::vector<iovec> vec(count);\n  const IOBuf* head = iob.get();\n  const IOBuf* next = head;\n  unsigned i = 0;\n  do {\n    vec[i].iov_base = const_cast<uint8_t*>(next->data());\n    vec[i++].iov_len = next->length();\n    next = next->next();\n  } while (next != head);\n  this->writev(callback, vec.data(), count, flags);\n}\n\nvoid TestAsyncTransport::close() {\n  closeNow();\n}\n\nvoid TestAsyncTransport::closeNow() {\n  if (readState_ == kStateOpen) {\n    readState_ = kStateClosed;\n\n    if (readCallback_ != nullptr) {\n      folly::AsyncTransport::ReadCallback* callback = readCallback_;\n      readCallback_ = nullptr;\n      callback->readEOF();\n    }\n  }\n  shutdownWriteNow();\n}\n\nvoid TestAsyncTransport::shutdownWrite() {\n  shutdownWriteNow();\n}\n\nvoid TestAsyncTransport::shutdownWriteNow() {\n  DestructorGuard g(this);\n  failPendingWrites();\n  if (writeState_ == kStateOpen || writeState_ == kStatePaused) {\n    writeState_ = kStateClosed;\n  }\n}\n\nvoid TestAsyncTransport::getPeerAddress(SocketAddress* addr) const {\n  // This isn't really accurate, but close enough for testing.\n  addr->setFromIpPort(\"127.0.0.1\", 0);\n}\n\nvoid TestAsyncTransport::getLocalAddress(SocketAddress* addr) const {\n  // This isn't really accurate, but close enough for testing.\n  addr->setFromIpPort(\"127.0.0.1\", 0);\n}\n\nbool TestAsyncTransport::good() const {\n  return (readState_ == kStateOpen && writesAllowed());\n}\n\nbool TestAsyncTransport::readable() const {\n  return false;\n}\n\nbool TestAsyncTransport::connecting() const {\n  return false;\n}\n\nbool TestAsyncTransport::error() const {\n  return (readState_ == kStateError || writeState_ == kStateError);\n}\n\nvoid TestAsyncTransport::attachEventBase(EventBase* eventBase) {\n  CHECK(nullptr == eventBase_);\n  CHECK(nullptr == readCallback_);\n  eventBase_ = eventBase;\n}\n\nvoid TestAsyncTransport::detachEventBase() {\n  CHECK_NOTNULL(eventBase_);\n  CHECK(nullptr == readCallback_);\n  eventBase_ = nullptr;\n}\n\nbool TestAsyncTransport::isDetachable() const {\n  return true;\n}\n\nEventBase* TestAsyncTransport::getEventBase() const {\n  return eventBase_;\n}\n\nvoid TestAsyncTransport::setSendTimeout(uint32_t milliseconds) {\n  sendTimeout_ = milliseconds;\n}\n\nuint32_t TestAsyncTransport::getSendTimeout() const {\n  return sendTimeout_;\n}\n\nvoid TestAsyncTransport::pauseWrites() {\n  if (writeState_ != kStateOpen) {\n    LOG(FATAL) << \"cannot pause writes on non-open transport; state=\"\n               << writeState_;\n  }\n  writeState_ = kStatePaused;\n}\n\nvoid TestAsyncTransport::resumeWrites() {\n  if (writeState_ != kStatePaused) {\n    LOG(FATAL) << \"cannot resume writes on non-paused transport; state=\"\n               << writeState_;\n  }\n  writeState_ = kStateOpen;\n  for (auto event = pendingWriteEvents_.begin();\n       event != pendingWriteEvents_.end() && writeState_ == kStateOpen;\n       event = pendingWriteEvents_.begin()) {\n    writeEvents_.push_back(event->first);\n    pendingWriteEvents_.pop_front();\n    event->second->writeSuccess();\n  }\n}\n\nvoid TestAsyncTransport::failPendingWrites() {\n  // writeError() callback might try to delete this object\n  DestructorGuard g(this);\n  while (!pendingWriteEvents_.empty()) {\n    auto event = pendingWriteEvents_.front();\n    pendingWriteEvents_.pop_front();\n    AsyncSocketException ex(AsyncSocketException::NOT_OPEN,\n                            \"Transport closed locally\");\n    auto cb = dynamic_cast<WriteCallback*>(event.second);\n    if (cb) {\n      cb->writeErr(0, ex);\n    }\n  }\n}\n\nvoid TestAsyncTransport::addReadEvent(\n    folly::IOBufQueue& chain, std::chrono::milliseconds delayFromPrevious) {\n  while (true) {\n    unique_ptr<IOBuf> cur = chain.pop_front();\n    if (!cur) {\n      break;\n    }\n    addReadEvent(cur->data(), cur->length(), delayFromPrevious);\n  }\n}\n\nvoid TestAsyncTransport::addReadEvent(\n    const void* buf,\n    size_t buflen,\n    std::chrono::milliseconds delayFromPrevious) {\n  if (!readEvents_.empty() && readEvents_.back()->isFinalEvent()) {\n    LOG(FATAL) << \"cannot add more read events after an error or EOF\";\n  }\n\n  auto event = std::make_shared<ReadEvent>(buf, buflen, delayFromPrevious);\n  addReadEvent(event);\n}\n\nvoid TestAsyncTransport::addReadEvent(\n    const char* buf, std::chrono::milliseconds delayFromPrevious) {\n  addReadEvent(buf, strlen(buf), delayFromPrevious);\n}\n\nvoid TestAsyncTransport::addMovableReadEvent(\n    std::unique_ptr<IOBuf> buf, std::chrono::milliseconds delayFromPrevious) {\n  addReadEvent(std::make_shared<ReadEvent>(std::move(buf), delayFromPrevious));\n}\n\nvoid TestAsyncTransport::addReadEOF(\n    std::chrono::milliseconds delayFromPrevious) {\n  addReadEvent(nullptr, 0, delayFromPrevious);\n}\n\nvoid TestAsyncTransport::addReadError(\n    const folly::AsyncSocketException& ex,\n    std::chrono::milliseconds delayFromPrevious) {\n  if (!readEvents_.empty() && readEvents_.back()->isFinalEvent()) {\n    LOG(FATAL) << \"cannot add a read error after an error or EOF\";\n  }\n\n  auto event = std::make_shared<ReadEvent>(ex, delayFromPrevious);\n  addReadEvent(event);\n}\n\nvoid TestAsyncTransport::addReadEvent(const shared_ptr<ReadEvent>& event) {\n  bool firstEvent = readEvents_.empty();\n  readEvents_.push_back(event);\n\n  if (!firstEvent) {\n    return;\n  }\n  if (!proxygen::timePointInitialized(prevReadEventTime_)) {\n    return;\n  }\n\n  nextReadEventTime_ = prevReadEventTime_ + event->getDelay();\n  if (readCallback_ == nullptr) {\n    return;\n  }\n\n  scheduleNextReadEvent(proxygen::getCurrentTime());\n}\n\nvoid TestAsyncTransport::startReadEvents() {\n  auto now = proxygen::getCurrentTime();\n  prevReadEventTime_ = now;\n\n  if (readEvents_.empty()) {\n    return;\n  }\n  nextReadEventTime_ = prevReadEventTime_ + readEvents_.front()->getDelay();\n\n  if (readCallback_ == nullptr) {\n    return;\n  }\n\n  scheduleNextReadEvent(now);\n}\n\nvoid TestAsyncTransport::scheduleNextReadEvent(TimePoint now) {\n  if (nextReadEventTime_ <= now) {\n    fireNextReadEvent();\n  } else {\n    scheduleTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(\n        nextReadEventTime_ - now));\n  }\n}\n\nvoid TestAsyncTransport::fireNextReadEvent() {\n  DestructorGuard dg(this);\n  CHECK(!readEvents_.empty());\n  CHECK_NOTNULL(readCallback_);\n\n  // maxReadAtOnce prevents us from starving other users of this EventBase\n  unsigned int const maxReadAtOnce = 30;\n  for (unsigned int n = 0; n < maxReadAtOnce; ++n) {\n    fireOneReadEvent();\n\n    if (readCallback_ == nullptr || eventBase_ == nullptr ||\n        !proxygen::timePointInitialized(nextReadEventTime_)) {\n      return;\n    }\n    auto now = proxygen::getCurrentTime();\n    if (nextReadEventTime_ > now) {\n      scheduleTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(\n          nextReadEventTime_ - now));\n      return;\n    }\n  }\n\n  // Trigger fireNextReadEvent() to be called the next time around the event\n  // loop.\n  eventBase_->runInLoop([this] { fireNextReadEvent(); });\n}\n\nvoid TestAsyncTransport::fireOneReadEvent() {\n  CHECK(!readEvents_.empty());\n  CHECK_NOTNULL(readCallback_);\n\n  const shared_ptr<ReadEvent>& event = readEvents_.front();\n\n  // Handle a read event using a movable buffer\n  if (event->isMovableBuffer()) {\n    CHECK(readCallback_->isBufferMovable());\n    readCallback_->readBufferAvailable(event->getMovableBuffer());\n    readEvents_.pop_front();\n\n    if (readEvents_.empty()) {\n      nextReadEventTime_ = {};\n    } else {\n      nextReadEventTime_ = prevReadEventTime_ + readEvents_.front()->getDelay();\n    }\n    return;\n  }\n\n  // Note that we call getReadBuffer() here even if we know the next event may\n  // be an EOF or an error.  This matches the behavior of AsyncSocket.\n  // (Because AsyncSocket merely gets notification that the socket is readable,\n  // and has to call getReadBuffer() before it can make the actual read call to\n  // get an error or EOF.)\n  void* buf;\n  size_t buflen;\n  try {\n    readCallback_->getReadBuffer(&buf, &buflen);\n  } catch (...) {\n    // TODO: we should convert the error to a AsyncSocketException and call\n    // readError() here.\n    LOG(FATAL) << \"readCallback_->getReadBuffer() threw an error\";\n  }\n  if (buf == nullptr || buflen == 0) {\n    // TODO: we should just call readError() here.\n    LOG(FATAL) << \"readCallback_->getReadBuffer() returned a nullptr or \"\n                  \"empty buffer\";\n  }\n\n  // Handle errors\n  if (event->isError()) {\n    // Errors immediately move both read and write to an error state\n    readState_ = kStateError;\n    writeState_ = kStateError;\n\n    // event is just a reference to the shared_ptr, so make a real copy of the\n    // pointer before popping it off the readEvents_ list.\n    shared_ptr<ReadEvent> eventPointerCopy = readEvents_.front();\n    readEvents_.pop_front();\n    CHECK(readEvents_.empty());\n    nextReadEventTime_ = {};\n\n    auto callback = readCallback_;\n    readCallback_ = nullptr;\n    callback->readErr(eventPointerCopy->getException());\n    return;\n  }\n\n  // Handle EOF\n  size_t available = event->getLength();\n  if (available == 0) {\n    readState_ = kStateClosed;\n\n    readEvents_.pop_front();\n    CHECK(readEvents_.empty());\n    nextReadEventTime_ = {};\n\n    auto callback = readCallback_;\n    readCallback_ = nullptr;\n    callback->readEOF();\n    return;\n  }\n\n  // Handle a normal read event\n  size_t readlen;\n  bool more;\n  if (available <= buflen) {\n    readlen = available;\n    more = false;\n  } else {\n    readlen = buflen;\n    more = true;\n  }\n  memcpy(buf, event->getBuffer(), readlen);\n  if (more) {\n    event->consumeData(readlen);\n  } else {\n    prevReadEventTime_ = nextReadEventTime_;\n    // Note: since event is just a reference to the shared_ptr in readEvents_,\n    // we shouldn't access the event any more after popping it off here.\n    readEvents_.pop_front();\n\n    if (readEvents_.empty()) {\n      nextReadEventTime_ = {};\n    } else {\n      nextReadEventTime_ = prevReadEventTime_ + readEvents_.front()->getDelay();\n    }\n  }\n  readCallback_->readDataAvailable(readlen);\n}\n\nvoid TestAsyncTransport::timeoutExpired() noexcept {\n  CHECK_NOTNULL(readCallback_);\n  CHECK(!readEvents_.empty());\n  fireNextReadEvent();\n}\n"
  },
  {
    "path": "proxygen/lib/test/TestAsyncTransport.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <deque>\n#include <folly/SocketAddress.h>\n#include <folly/io/IOBufQueue.h>\n#include <folly/io/async/AsyncTimeout.h>\n#include <folly/io/async/AsyncTransport.h>\n#include <proxygen/lib/utils/Time.h>\n\nclass TestAsyncTransport\n    : public folly::AsyncTransport\n    , private folly::AsyncTimeout {\n public:\n  class WriteEvent {\n   public:\n    static std::shared_ptr<WriteEvent> newEvent(const struct iovec* vec,\n                                                size_t count);\n\n    [[nodiscard]] proxygen::TimePoint getTime() const {\n      return time_;\n    }\n    [[nodiscard]] const struct iovec* getIoVec() const {\n      return vec_;\n    }\n    [[nodiscard]] size_t getCount() const {\n      return count_;\n    }\n\n   private:\n    static void destroyEvent(WriteEvent* event);\n\n    WriteEvent(proxygen::TimePoint time, size_t count);\n    ~WriteEvent();\n\n    proxygen::TimePoint time_;\n    size_t count_;\n    struct iovec vec_[]; // NOLINT(modernize-avoid-c-arrays)\n  };\n\n  explicit TestAsyncTransport(folly::EventBase* eventBase);\n\n  // AsyncTransport methods\n  void setReadCB(folly::AsyncTransport::ReadCallback* callback) override;\n  [[nodiscard]] ReadCallback* getReadCallback() const override;\n  void write(folly::AsyncTransport::WriteCallback* callback,\n             const void* buf,\n             size_t bytes,\n             folly::WriteFlags flags = folly::WriteFlags::NONE) override;\n  void writev(folly::AsyncTransport::WriteCallback* callback,\n              const struct iovec* vec,\n              size_t count,\n              folly::WriteFlags flags = folly::WriteFlags::NONE) override;\n  void writeChain(folly::AsyncTransport::WriteCallback* callback,\n                  std::unique_ptr<folly::IOBuf>&& iob,\n                  folly::WriteFlags flags = folly::WriteFlags::NONE) override;\n  void close() override;\n  void closeNow() override;\n  void shutdownWrite() override;\n  void shutdownWriteNow() override;\n  void getPeerAddress(folly::SocketAddress* addr) const override;\n  void getLocalAddress(folly::SocketAddress* addr) const override;\n  [[nodiscard]] bool good() const override;\n  [[nodiscard]] bool readable() const override;\n  [[nodiscard]] bool connecting() const override;\n  [[nodiscard]] bool error() const override;\n  void attachEventBase(folly::EventBase* eventBase) override;\n  void detachEventBase() override;\n  [[nodiscard]] bool isDetachable() const override;\n  [[nodiscard]] folly::EventBase* getEventBase() const override;\n  void setSendTimeout(uint32_t milliseconds) override;\n  [[nodiscard]] uint32_t getSendTimeout() const override;\n\n  // Methods to control read events\n  void addReadEvent(const void* buf,\n                    size_t buflen,\n                    std::chrono::milliseconds delayFromPrevious);\n  void addReadEvent(folly::IOBufQueue& chain,\n                    std::chrono::milliseconds delayFromPrevious);\n  void addReadEvent(const char* buf,\n                    std::chrono::milliseconds delayFromPrevious =\n                        std::chrono::milliseconds(0));\n  void addMovableReadEvent(std::unique_ptr<folly::IOBuf> buf,\n                           std::chrono::milliseconds delayFromPrevious =\n                               std::chrono::milliseconds(0));\n  void addReadEOF(std::chrono::milliseconds delayFromPrevious);\n  void addReadError(const folly::AsyncSocketException& ex,\n                    std::chrono::milliseconds delayFromPrevious);\n  void startReadEvents();\n\n  void pauseWrites();\n  void resumeWrites();\n\n  // Methods to get the data written to this transport\n  std::deque<std::shared_ptr<WriteEvent>>* getWriteEvents() {\n    return &writeEvents_;\n  }\n\n  uint32_t getEORCount() {\n    return eorCount_;\n  }\n\n  uint32_t getCorkCount() {\n    return corkCount_;\n  }\n\n  void setAppBytesWritten(size_t bytes) {\n    appBytesWritten_ = bytes;\n  }\n  void setRawBytesWritten(size_t bytes) {\n    rawBytesWritten_ = bytes;\n  }\n\n  [[nodiscard]] size_t getAppBytesWritten() const override {\n    return appBytesWritten_;\n  }\n  [[nodiscard]] size_t getRawBytesWritten() const override {\n    return rawBytesWritten_;\n  }\n  [[nodiscard]] size_t getAppBytesReceived() const override {\n    return 0;\n  }\n  [[nodiscard]] size_t getRawBytesReceived() const override {\n    return 0;\n  }\n  [[nodiscard]] bool isEorTrackingEnabled() const override {\n    return eorTrackingEnabled_;\n  }\n  void setEorTracking(bool flag) override {\n    eorTrackingEnabled_ = flag;\n  }\n\n private:\n  enum StateEnum {\n    kStateOpen,\n    kStatePaused,\n    kStateClosed,\n    kStateError,\n  };\n\n  class ReadEvent;\n\n  [[nodiscard]] bool writesAllowed() const {\n    return writeState_ == kStateOpen || writeState_ == kStatePaused;\n  }\n\n  // Forbidden copy constructor and assignment opererator\n  TestAsyncTransport(TestAsyncTransport const&);\n  TestAsyncTransport& operator=(TestAsyncTransport const&);\n\n  void addReadEvent(const std::shared_ptr<ReadEvent>& event);\n  void scheduleNextReadEvent(proxygen::TimePoint now);\n  void fireNextReadEvent();\n  void fireOneReadEvent();\n  void failPendingWrites();\n\n  // AsyncTimeout methods\n  void timeoutExpired() noexcept override;\n\n  folly::EventBase* eventBase_;\n  folly::AsyncTransport::ReadCallback* readCallback_;\n  uint32_t sendTimeout_;\n\n  proxygen::TimePoint prevReadEventTime_{};\n  proxygen::TimePoint nextReadEventTime_{};\n  StateEnum readState_;\n  StateEnum writeState_;\n  std::deque<std::shared_ptr<ReadEvent>> readEvents_;\n  std::deque<std::shared_ptr<WriteEvent>> writeEvents_;\n  std::deque<std::pair<std::shared_ptr<WriteEvent>,\n                       folly::AsyncTransport::WriteCallback*>>\n      pendingWriteEvents_;\n\n  size_t appBytesWritten_{0};\n  size_t rawBytesWritten_{0};\n  bool eorTrackingEnabled_{false};\n  uint32_t eorCount_{0};\n  uint32_t corkCount_{0};\n};\n"
  },
  {
    "path": "proxygen/lib/test/TestMain.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n// Use this main function in gtest unit tests to enable glog\n#include <folly/portability/GFlags.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n\nint main(int argc, char* argv[]) {\n  testing::InitGoogleTest(&argc, argv);\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  google::InitGoogleLogging(argv[0]);\n  google::InstallFailureSignalHandler();\n  LOG(INFO) << \"Running tests from TestMain.cpp\";\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "proxygen/lib/transport/AsyncUDPSocketFactory.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"proxygen/lib/transport/AsyncUDPSocketFactory.h\"\n\nnamespace proxygen {\n\nAsyncUDPSocketFactory::AsyncUDPSocketFactory(\n    folly::EventBase* eventBase,\n    folly::SocketAddress v6Address,\n    std::optional<folly::SocketAddress> v4Address)\n    : v6Address_(std::move(v6Address)), v4Address_(std::move(v4Address)) {\n  CHECK(eventBase);\n  eventBase_ = eventBase;\n}\n\nfolly::Expected<std::unique_ptr<folly::AsyncUDPSocket>, proxygen::Exception>\nAsyncUDPSocketFactory::createSocket(\n    const folly::SocketAddress& destinationAddress,\n    SocketCreateOptions options) {\n  std::unique_ptr<folly::AsyncUDPSocket> socket =\n      std::make_unique<folly::AsyncUDPSocket>(eventBase_);\n\n  auto maybeAddress = getBindingAddress(destinationAddress);\n\n  if (!maybeAddress.hasValue()) {\n    return folly::makeUnexpected(Exception(\"Unable to create socket error=\",\n                                           maybeAddress.error().what()));\n  }\n\n  try {\n    socket->bind(maybeAddress.value());\n    if (options.connectSocket) {\n      socket->connect(destinationAddress);\n    }\n  } catch (const std::runtime_error& ex) {\n    return folly::makeUnexpected(Exception(ex.what()));\n  }\n\n  return socket;\n}\n\nfolly::Expected<folly::SocketAddress, proxygen::Exception>\nAsyncUDPSocketFactory::getBindingAddress(\n    const folly::SocketAddress& destination) {\n\n  if (destination.getIPAddress().isV4()) {\n    if (!v4Address_.has_value()) {\n      return folly::makeUnexpected(Exception(\n          \"Bind addresses not provided for address family, destination=\",\n          destination.getAddressStr()));\n    }\n\n    return v4Address_.value();\n  }\n\n  return v6Address_;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/AsyncUDPSocketFactory.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Expected.h>\n#include <folly/SocketAddress.h>\n#include <folly/io/async/AsyncUDPSocket.h>\n#include <optional>\n#include <proxygen/lib/utils/Exception.h>\n\nnamespace proxygen {\n\nclass AsyncUDPSocketFactory {\n public:\n  struct SocketCreateOptions {\n    bool connectSocket{false};\n  };\n\n  explicit AsyncUDPSocketFactory(\n      folly::EventBase* eventBase,\n      folly::SocketAddress v6BindAddress,\n      std::optional<folly::SocketAddress> v4BindAddress = std::nullopt);\n  ~AsyncUDPSocketFactory() = default;\n\n  folly::Expected<std::unique_ptr<folly::AsyncUDPSocket>, proxygen::Exception>\n  createSocket(const folly::SocketAddress& destinationAddress,\n               SocketCreateOptions options = getDefaultCreateOptions());\n\n private:\n  static SocketCreateOptions getDefaultCreateOptions() {\n    return SocketCreateOptions{.connectSocket = false};\n  }\n\n  folly::Expected<folly::SocketAddress, proxygen::Exception> getBindingAddress(\n      const folly::SocketAddress& destination);\n\n  folly::EventBase* eventBase_{nullptr};\n  folly::SocketAddress v6Address_;\n  std::optional<folly::SocketAddress> v4Address_;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_transport_persistent_fizz_psk_cache\n  SRCS\n    PersistentFizzPskCache.cpp\n  EXPORTED_DEPS\n    wangle::wangle_client_persistence\n    fizz::fizz\n)\n\nproxygen_add_library(proxygen_transport_persistent_quic_psk_cache\n  SRCS\n    PersistentQuicPskCache.cpp\n  DEPS\n    mvfst::mvfst_client_cached_server_tp_serialization\n    fizz::fizz\n    Folly::folly_conv\n  EXPORTED_DEPS\n    proxygen_transport_persistent_fizz_psk_cache\n    mvfst::mvfst_fizz_client_handshake_psk_cache\n    wangle::wangle_client_persistence\n    fizz::fizz\n    Folly::folly_json_dynamic\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_transport_h3_datagram_async_socket\n  SRCS\n    H3DatagramAsyncSocket.cpp\n  DEPS\n    proxygen_hq_server\n    proxygen_transport_connect_udp_utils\n    mvfst::mvfst_common_udpsocket_folly_async_udp_socket\n    mvfst::mvfst_fizz_client_handshake\n    wangle::wangle_acceptor_acceptor_core\n    fizz::fizz\n    Folly::folly_file_util\n  EXPORTED_DEPS\n    proxygen_http_session_hq_upstream_session\n    proxygen_http_session_http_transaction\n    mvfst::mvfst_api_transport\n    mvfst::mvfst_client\n    fizz::fizz\n    Folly::folly_io_async_async_socket_exception\n    Folly::folly_io_async_async_udp_socket\n)\n\nproxygen_add_library(proxygen_transport_connect_udp_utils\n  SRCS\n    ConnectUDPUtils.cpp\n  DEPS\n    mvfst::mvfst_codec_types\n    mvfst::mvfst_folly_utils\n    Folly::folly_string\n  EXPORTED_DEPS\n    Folly::folly_io_iobuf\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_transport_persistent_quic_token_cache\n  SRCS\n    PersistentQuicTokenCache.cpp\n  EXPORTED_DEPS\n    mvfst::mvfst_fizz_client_handshake_token_cache\n    wangle::wangle_client_persistence\n)\n\nproxygen_add_library(proxygen_transport_async_udp_socket_factory\n  SRCS\n    AsyncUDPSocketFactory.cpp\n  EXPORTED_DEPS\n    proxygen_utils_exception\n    Folly::folly_expected\n    Folly::folly_io_async_async_udp_socket\n    Folly::folly_network_address\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/transport/ConnectUDPUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/ConnectUDPUtils.h>\n\n#include <folly/String.h>\n#include <folly/io/Cursor.h>\n#include <quic/codec/QuicInteger.h>\n#include <quic/folly_utils/Utils.h>\n\nnamespace proxygen {\n\nnamespace {\n// Percent-encode colons in an IPv6 address as %3A\nstd::string percentEncodeIPv6(const std::string& host) {\n  std::string result;\n  result.reserve(host.size() * 3);\n  for (char c : host) {\n    if (c == ':') {\n      result.append(\"%3A\");\n    } else {\n      result.push_back(c);\n    }\n  }\n  return result;\n}\n} // namespace\n\nConnectUDPTarget expandConnectUDPTemplate(const std::string& uriTemplate,\n                                          const std::string& targetHost,\n                                          uint16_t targetPort) {\n  // Encode IPv6 colons\n  std::string encodedHost = (targetHost.find(':') != std::string::npos)\n                                ? percentEncodeIPv6(targetHost)\n                                : targetHost;\n  auto portStr = folly::to<std::string>(targetPort);\n\n  // Perform template variable substitution\n  std::string expanded = uriTemplate;\n  {\n    auto pos = expanded.find(\"{target_host}\");\n    while (pos != std::string::npos) {\n      expanded.replace(pos, 13, encodedHost);\n      pos = expanded.find(\"{target_host}\", pos + encodedHost.size());\n    }\n  }\n  {\n    auto pos = expanded.find(\"{target_port}\");\n    while (pos != std::string::npos) {\n      expanded.replace(pos, 13, portStr);\n      pos = expanded.find(\"{target_port}\", pos + portStr.size());\n    }\n  }\n\n  ConnectUDPTarget result;\n\n  // Parse scheme\n  auto schemeEnd = expanded.find(\"://\");\n  if (schemeEnd == std::string::npos) {\n    // No scheme found, return expanded as path\n    result.path = expanded;\n    return result;\n  }\n  result.scheme = expanded.substr(0, schemeEnd);\n\n  // Parse authority (host:port)\n  auto authorityStart = schemeEnd + 3;\n  auto pathStart = expanded.find('/', authorityStart);\n  if (pathStart == std::string::npos) {\n    result.authority = expanded.substr(authorityStart);\n    result.path = \"/\";\n  } else {\n    result.authority =\n        expanded.substr(authorityStart, pathStart - authorityStart);\n    result.path = expanded.substr(pathStart);\n  }\n\n  return result;\n}\n\nstd::unique_ptr<folly::IOBuf> prependContextId(\n    std::unique_ptr<folly::IOBuf> payload) {\n  auto contextIdBuf = folly::IOBuf::create(1);\n  folly::io::Appender appender(contextIdBuf.get(), 1);\n  auto res =\n      quic::encodeQuicInteger(0, [&](auto val) { appender.writeBE(val); });\n  CHECK(res.has_value());\n  contextIdBuf->appendToChain(std::move(payload));\n  return contextIdBuf;\n}\n\nfolly::Optional<std::unique_ptr<folly::IOBuf>> stripContextId(\n    std::unique_ptr<folly::IOBuf> datagram) {\n  if (!datagram || datagram->computeChainDataLength() == 0) {\n    return folly::none;\n  }\n\n  folly::io::Cursor cursor(datagram.get());\n  auto contextIdResult = quic::follyutils::decodeQuicInteger(cursor);\n  if (!contextIdResult) {\n    return folly::none;\n  }\n\n  auto [contextId, varintLen] = *contextIdResult;\n  if (contextId != 0) {\n    return folly::none;\n  }\n\n  // Return the remaining payload after the context ID\n  folly::IOBufQueue queue;\n  queue.append(std::move(datagram));\n  queue.trimStart(varintLen);\n  auto result = queue.move();\n  if (!result) {\n    result = folly::IOBuf::create(0);\n  }\n  return result;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/ConnectUDPUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <folly/Optional.h>\n#include <folly/io/IOBuf.h>\n#include <string>\n\nnamespace proxygen {\n\n// Result of expanding a CONNECT-UDP URI template.\nstruct ConnectUDPTarget {\n  std::string scheme;    // e.g., \"https\"\n  std::string authority; // e.g., \"127.0.0.1:4443\"\n  std::string path;      // e.g., \"/masque?h=192.0.2.6&p=443\"\n};\n\n// Expand a CONNECT-UDP URI template (RFC 6570 Level 3).\n// Replaces {target_host} and {target_port}. Percent-encodes IPv6 colons\n// as %3A in the host value.\n// Example: expandConnectUDPTemplate(\n//   \"https://proxy:4443/masque?h={target_host}&p={target_port}\",\n//   \"192.0.2.6\", 443)\n// returns {scheme=\"https\", authority=\"proxy:4443\",\n//          path=\"/masque?h=192.0.2.6&p=443\"}\nConnectUDPTarget expandConnectUDPTemplate(const std::string& uriTemplate,\n                                          const std::string& targetHost,\n                                          uint16_t targetPort);\n\n// Prepend QUIC varint context ID 0 (single byte 0x00) to a datagram payload.\nstd::unique_ptr<folly::IOBuf> prependContextId(\n    std::unique_ptr<folly::IOBuf> payload);\n\n// Strip QUIC varint context ID from datagram.\n// Returns the payload if context ID is 0, or folly::none if context ID != 0\n// or the datagram is too short to parse.\nfolly::Optional<std::unique_ptr<folly::IOBuf>> stripContextId(\n    std::unique_ptr<folly::IOBuf> datagram);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/H3DatagramAsyncSocket.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/H3DatagramAsyncSocket.h>\n\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <folly/FileUtil.h>\n#include <proxygen/httpserver/samples/hq/HQParams.h>\n#include <proxygen/lib/transport/ConnectUDPUtils.h>\n#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>\n#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>\n#include <utility>\n#include <wangle/acceptor/TransportInfo.h>\n\nnamespace proxygen {\n\nusing folly::AsyncSocketException;\n\nH3DatagramAsyncSocket::H3DatagramAsyncSocket(folly::EventBase* evb,\n                                             Options options)\n    : folly::AsyncUDPSocket(evb),\n      evb_(evb),\n      options_(std::move(options)),\n      transportConnected_(false),\n      pendingEOM_(false),\n      inResumeRead_(false) {\n}\n\nconst folly::AsyncTransportCertificate* FOLLY_NULLABLE\nH3DatagramAsyncSocket::getPeerCertificate() const {\n  if (!upstreamSession_) {\n    return nullptr;\n  }\n  if (const auto sock = upstreamSession_->getQuicSocket()) {\n    return sock->getPeerCertificate().get();\n  }\n  return nullptr;\n}\n\nconst folly::SocketAddress& H3DatagramAsyncSocket::address() const {\n  static folly::SocketAddress localFallbackAddress;\n  if (!upstreamSession_) {\n    return localFallbackAddress;\n  }\n  return upstreamSession_->getLocalAddress();\n}\n\nvoid H3DatagramAsyncSocket::closeWithError(const AsyncSocketException& ex) {\n  if (pendingError_.has_value()) {\n    LOG(ERROR) << \"Multiple errors. Previous error: '\" << pendingError_->what()\n               << \"'\";\n    return;\n  }\n  if (!readCallback_) {\n    LOG(ERROR)\n        << \"Error with readCallback not set. Will deliver when resuming reads.\";\n    pendingError_ = ex;\n    return;\n  }\n  readCallback_->onReadError(ex);\n  closeRead();\n}\n\nvoid H3DatagramAsyncSocket::connectSuccess() {\n  if (!options_.httpRequest_) {\n    closeWithError({AsyncSocketException::BAD_ARGS, \"No HTTP Request\"});\n    return;\n  }\n\n  if (!upstreamSession_) {\n    closeWithError({AsyncSocketException::INTERNAL_ERROR,\n                    \"ConnectSuccess with invalid session\"});\n    return;\n  }\n\n  // Send the HTTPMessage\n  txn_ = upstreamSession_->newTransaction(this);\n  if (!txn_ || !txn_->canSendHeaders()) {\n    closeWithError({AsyncSocketException::INTERNAL_ERROR, \"Transaction Error\"});\n    return;\n  }\n  if (options_.rfcMode_) {\n    options_.httpRequest_->getHeaders().set(\"Capsule-Protocol\", \"?1\");\n  }\n  txn_->sendHeaders(*options_.httpRequest_);\n  upstreamSession_->closeWhenIdle();\n  transportConnected_ = true;\n}\n\nvoid H3DatagramAsyncSocket::onReplaySafe() {\n}\n\nvoid H3DatagramAsyncSocket::connectError(quic::QuicError error) {\n  closeWithError({AsyncSocketException::NETWORK_ERROR,\n                  fmt::format(\"connectError: '{}'\", error.message)});\n}\n\nvoid H3DatagramAsyncSocket::setTransaction(\n    proxygen::HTTPTransaction* /*txn*/) noexcept {\n  CHECK(!txn_);\n}\n\nvoid H3DatagramAsyncSocket::detachTransaction() noexcept {\n  VLOG(4) << \"Transaction Detached\";\n  txn_ = nullptr;\n}\n\nvoid H3DatagramAsyncSocket::onHeadersComplete(\n    std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n  if (msg->getStatusCode() != 200) {\n    closeWithError(\n        {AsyncSocketException::INTERNAL_ERROR,\n         fmt::format(\"HTTP Error: status code {}\", msg->getStatusCode())});\n    return;\n  }\n\n  // Upstream ready to receive buffers.\n  for (auto& datagram : writeBuf_) {\n    txn_->sendDatagram(std::move(datagram));\n  }\n  writeBuf_.clear();\n}\n\nvoid H3DatagramAsyncSocket::onDatagram(\n    std::unique_ptr<folly::IOBuf> datagram) noexcept {\n  if (options_.rfcMode_) {\n    auto stripped = stripContextId(std::move(datagram));\n    if (!stripped.has_value()) {\n      // Non-zero context ID or malformed — silently drop\n      VLOG(4) << \"Dropping datagram with non-zero or invalid context ID\";\n      return;\n    }\n    datagram = std::move(stripped.value());\n  }\n\n  if (!readCallback_) {\n    if (readBuf_.size() < rcvBufPkts_) {\n      // buffer until reads are resumed\n      readBuf_.emplace_back(std::move(datagram));\n    } else {\n      VLOG_EVERY_N(2, 1000) << \"Dropped incoming datagram.\";\n    }\n    return;\n  }\n\n  deliverDatagram(std::move(datagram));\n}\n\nvoid H3DatagramAsyncSocket::deliverDatagram(\n    std::unique_ptr<folly::IOBuf> datagram) noexcept {\n  void* buf{nullptr};\n  size_t len{0};\n  ReadCallback::OnDataAvailableParams params;\n  CHECK(readCallback_);\n  CHECK(datagram);\n  if (!readCallback_->shouldOnlyNotify()) {\n\n    readCallback_->getReadBuffer(&buf, &len);\n    if (buf == nullptr || len == 0 ||\n        len < datagram->computeChainDataLength()) {\n      LOG(ERROR) << \"Buffer too small to deliver \"\n                 << datagram->computeChainDataLength() << \" bytes datagram\";\n      return;\n    }\n    datagram->coalesce();\n    memcpy(buf, datagram->data(), datagram->length());\n    readCallback_->onDataAvailable((upstreamSession_\n                                        ? upstreamSession_->getPeerAddress()\n                                        : connectAddress_),\n                                   size_t(datagram->length()),\n                                   /*truncated*/ false,\n                                   params);\n  } else {\n    datagram->coalesce();\n    pendingDelivery_ = std::move(datagram);\n    readCallback_->onNotifyDataAvailable(*this);\n  }\n}\n\nssize_t H3DatagramAsyncSocket::recvmsg(struct msghdr* msg, int /*flags*/) {\n  if (!pendingDelivery_) {\n    return 0;\n  }\n  if (msg->msg_iovlen <= 0 || !msg->msg_iov || !msg->msg_iov[0].iov_base) {\n    return -1;\n  }\n  // This isn't ideal because we are doing double copies, but it is a\n  // limitation of the interface for now.\n  size_t copyLen =\n      std::min(pendingDelivery_->length(), msg->msg_iov[0].iov_len);\n  memcpy(msg->msg_iov[0].iov_base, pendingDelivery_->data(), copyLen);\n  ssize_t ret = pendingDelivery_->length();\n  pendingDelivery_ = nullptr;\n  return ret;\n}\n\nint H3DatagramAsyncSocket::recvmmsg(struct mmsghdr* msgvec,\n                                    unsigned int vlen,\n                                    unsigned int flags,\n                                    struct timespec* /*timeout*/) {\n  CHECK_GT(vlen, 0);\n  auto bytesReceived = recvmsg(&msgvec->msg_hdr, flags);\n  if (bytesReceived < 0) {\n    return -1;\n  }\n  msgvec->msg_len = bytesReceived;\n  return 1;\n}\n\nvoid H3DatagramAsyncSocket::onBody(\n    std::unique_ptr<folly::IOBuf> body) noexcept {\n  if (!options_.capsuleCallback_) {\n    return;\n  }\n  // TODO this only supports parsing capsules that come in one\n  // onBody call and isn't a proper streaming parser.\n  folly::io::Cursor cursor(body.get());\n  auto leftToParse = body->computeChainDataLength();\n  while (leftToParse > 0) {\n    auto typeRes = quic::follyutils::decodeQuicInteger(cursor, leftToParse);\n    if (!typeRes) {\n      LOG(ERROR) << \"Failed to decode capsule type.\";\n      return;\n    }\n    auto [type, typeLen] = typeRes.value();\n    leftToParse -= typeLen;\n    auto capLengthRes =\n        quic::follyutils::decodeQuicInteger(cursor, leftToParse);\n    if (!capLengthRes) {\n      LOG(ERROR) << \"Failed to decode capsule length: type=\" << type;\n      return;\n    }\n    auto [capLength, capLengthLen] = capLengthRes.value();\n    leftToParse -= capLengthLen;\n    if (capLength > leftToParse) {\n      LOG(ERROR) << \"Not enough data for capsule: type=\" << type\n                 << \" length=\" << capLength;\n      return;\n    }\n    H3Capsule ret{.type = type, .length = capLength, .data = nullptr};\n    cursor.cloneAtMost(ret.data, capLength);\n    options_.capsuleCallback_(std::move(ret));\n  }\n}\n\nvoid H3DatagramAsyncSocket::onTrailers(\n    std::unique_ptr<proxygen::HTTPHeaders> /*trailers*/) noexcept {\n}\n\nvoid H3DatagramAsyncSocket::onEOM() noexcept {\n  if (!readCallback_) {\n    // close when resuming reads, after flushing buffered datagrams\n    pendingEOM_ = true;\n  } else {\n    closeRead();\n  }\n}\n\nvoid H3DatagramAsyncSocket::onUpgrade(\n    proxygen::UpgradeProtocol /*protocol*/) noexcept {\n  closeWithError(\n      {AsyncSocketException::NOT_SUPPORTED, \"onUpgrade not supported\"});\n}\n\nvoid H3DatagramAsyncSocket::onError(\n    const proxygen::HTTPException& error) noexcept {\n  closeWithError({AsyncSocketException::INTERNAL_ERROR, error.describe()});\n}\n\nvoid H3DatagramAsyncSocket::onEgressPaused() noexcept {\n}\n\nvoid H3DatagramAsyncSocket::onEgressResumed() noexcept {\n}\n\nvoid H3DatagramAsyncSocket::startClient() {\n  quic::TransportSettings transportSettings;\n  transportSettings.datagramConfig.enabled = true;\n  transportSettings.maxRecvPacketSize = options_.maxDatagramSize_;\n  transportSettings.canIgnorePathMTU = true;\n  if (sndBufPkts_ > 0) {\n    transportSettings.datagramConfig.writeBufSize = sndBufPkts_;\n  }\n  if (rcvBufPkts_ > 0) {\n    transportSettings.datagramConfig.readBufSize = rcvBufPkts_;\n  }\n  if (!upstreamSession_) {\n    auto qEvb = std::make_shared<quic::FollyQuicEventBase>(evb_);\n    auto sock = std::make_unique<quic::FollyQuicAsyncUDPSocket>(qEvb);\n    auto fizzClientContext =\n        quic::FizzClientQuicHandshakeContext::Builder()\n            .setFizzClientContext(createFizzClientContext())\n            .setCertificateVerifier(options_.certVerifier_)\n            .build();\n    auto client = std::make_shared<quic::QuicClientTransport>(\n        qEvb, std::move(sock), fizzClientContext);\n    CHECK(connectAddress_.isInitialized());\n    client->addNewPeerAddress(connectAddress_);\n    if (!options_.hostname_.empty()) {\n      client->setHostname(options_.hostname_);\n    }\n    if (bindAddress_.isInitialized()) {\n      client->setLocalAddress(bindAddress_);\n    }\n    client->setCongestionControllerFactory(\n        std::make_shared<quic::DefaultCongestionControllerFactory>());\n    transportSettings.datagramConfig.enabled = true;\n    client->setTransportSettings(transportSettings);\n    if (options_.rfcMode_) {\n      client->setSupportedVersions({quic::QuicVersion::QUIC_V1});\n    } else {\n      client->setSupportedVersions({quic::QuicVersion::MVFST});\n    }\n\n    wangle::TransportInfo tinfo;\n    upstreamSession_ =\n        new proxygen::HQUpstreamSession(options_.txnTimeout_,\n                                        options_.connectTimeout_,\n                                        nullptr, // controller\n                                        tinfo,\n                                        nullptr); // codecfiltercallback\n    upstreamSession_->setSocket(client);\n    upstreamSession_->setConnectCallback(this);\n    upstreamSession_->setInfoCallback(this);\n    if (options_.rfcMode_) {\n      upstreamSession_->setEgressSettings(\n          {{proxygen::SettingsId::_HQ_DATAGRAM_RFC, 1},\n           {proxygen::SettingsId::ENABLE_CONNECT_PROTOCOL, 1}});\n    } else {\n      upstreamSession_->setEgressSettings(\n          {{proxygen::SettingsId::_HQ_DATAGRAM, 1}});\n    }\n\n    VLOG(4) << \"connecting to \" << connectAddress_.describe();\n    upstreamSession_->startNow();\n    client->start(upstreamSession_, upstreamSession_);\n  }\n}\n\nstd::shared_ptr<fizz::client::FizzClientContext>\nH3DatagramAsyncSocket::createFizzClientContext() {\n  auto ctx = std::make_shared<fizz::client::FizzClientContext>();\n\n  if (options_.certAndKey_.has_value()) {\n    std::string certData;\n    folly::readFile(options_.certAndKey_->first.c_str(), certData);\n    std::string keyData;\n    folly::readFile(options_.certAndKey_->second.c_str(), keyData);\n    auto cert = fizz::openssl::CertUtils::makeSelfCert(certData, keyData);\n    auto certMgr = std::make_shared<fizz::client::CertManager>();\n    certMgr->addCert(std::move(cert));\n    ctx->setClientCertManager(std::move(certMgr));\n  }\n\n  std::vector<std::string> supportedAlpns =\n      quic::samples::kDefaultSupportedAlpns;\n  ctx->setSupportedAlpns(supportedAlpns);\n  ctx->setRequireAlpn(true);\n  ctx->setSendEarlyData(false);\n  return ctx;\n}\n\nssize_t H3DatagramAsyncSocket::write(const folly::SocketAddress& address,\n                                     const std::unique_ptr<folly::IOBuf>& buf) {\n  if (!buf) {\n    LOG(ERROR) << \"Invalid write data\";\n    errno = EINVAL;\n    return -1;\n  }\n  if (!connectAddress_.isInitialized()) {\n    LOG(ERROR) << \"Socket not connected. Must call connect()\";\n    errno = ENOTCONN;\n    return -1;\n  }\n  // Can only write to the one address we are connected to\n  if (address != connectAddress_) {\n    LOG(ERROR) << \"Socket can only write to address \" << connectAddress_;\n    errno = EINVAL;\n    return -1;\n  }\n  auto size = buf->computeChainDataLength();\n  if (!transportConnected_) {\n    if (writeBuf_.size() < sndBufPkts_) {\n      VLOG(10) << \"Socket not connected yet. Buffering datagram\";\n      auto bufCopy = buf->clone();\n      if (options_.rfcMode_) {\n        bufCopy = prependContextId(std::move(bufCopy));\n      }\n      writeBuf_.emplace_back(std::move(bufCopy));\n      return size;\n    }\n    LOG(ERROR) << \"Socket write buffer is full. Discarding datagram\";\n    errno = ENOBUFS;\n    return -1;\n  }\n  if (!txn_) {\n    LOG(ERROR) << \"Unable to create HTTP/3 transaction. Discarding datagram\";\n    errno = ECANCELED;\n    return -1;\n  }\n  if (size > txn_->getDatagramSizeLimit()) {\n    LOG(ERROR) << \"Datagram too large len=\" << size\n               << \" transport max datagram size len=\"\n               << txn_->getDatagramSizeLimit() << \". Discarding datagram\";\n    errno = EMSGSIZE;\n    return -1;\n  }\n  auto datagramBuf = buf->clone();\n  if (options_.rfcMode_) {\n    datagramBuf = prependContextId(std::move(datagramBuf));\n  }\n  if (!txn_->sendDatagram(std::move(datagramBuf))) {\n    LOG(ERROR) << \"Transport write buffer is full. Discarding datagram\";\n    // sendDatagram can only fail for exceeding the maximum size (checked\n    // above) and if the write buffer is full\n    errno = ENOBUFS;\n    return -1;\n  }\n  return size;\n}\n\nvoid H3DatagramAsyncSocket::resumeRead(ReadCallback* cob) {\n  // TODO: avoid re-entrancy\n  if (inResumeRead_) {\n    return;\n  }\n  SCOPE_EXIT {\n    inResumeRead_ = false;\n  };\n  inResumeRead_ = true;\n  readCallback_ = CHECK_NOTNULL(cob);\n  folly::DelayedDestruction::DestructorGuard dg(this);\n  // if there are buffered datagrams, deliver those first.\n  auto it = readBuf_.begin();\n  while (it != readBuf_.end()) {\n    // the read callback could be reset from onDataAvailable\n    if (readCallback_) {\n      deliverDatagram(std::move(*it));\n      it = readBuf_.erase(it);\n    } else {\n      return;\n    }\n  }\n  // then, deliver errors\n  if (pendingError_.has_value()) {\n    auto err = *pendingError_;\n    pendingError_ = folly::none;\n    readCallback_->onReadError(err);\n    closeRead();\n  } else if (pendingEOM_) {\n    // or close reads if EOM was seen already\n    pendingEOM_ = false;\n    closeRead();\n  }\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/H3DatagramAsyncSocket.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n#include <fizz/client/FizzClientContext.h>\n#include <folly/io/async/AsyncSocketException.h>\n#include <folly/io/async/AsyncUDPSocket.h>\n#include <proxygen/lib/http/session/HQUpstreamSession.h>\n#include <proxygen/lib/http/session/HTTPTransaction.h>\n#include <quic/api/QuicSocket.h>\n#include <quic/client/QuicClientTransport.h>\n\nnamespace proxygen {\n\nclass H3DatagramAsyncSocketTest;\n\nclass H3DatagramAsyncSocket\n    : public folly::AsyncUDPSocket\n    , HQSession::ConnectCallback\n    , HTTPSessionBase::InfoCallback\n    , proxygen::HTTPTransactionHandler\n    , public folly::DelayedDestruction {\n\n  friend class H3DatagramAsyncSocketTest;\n\n public:\n  enum class Mode {\n    CLIENT = 0,\n    SERVER = 1,\n  };\n\n  struct H3Capsule {\n    uint64_t type{0};\n    uint64_t length{0};\n    std::unique_ptr<folly::IOBuf> data;\n  };\n\n  struct Options {\n    Mode mode_;\n    std::chrono::milliseconds txnTimeout_{10000};\n    std::chrono::milliseconds connectTimeout_{3000};\n    std::shared_ptr<proxygen::HTTPMessage> httpRequest_;\n    folly::Optional<std::pair<std::string, std::string>> certAndKey_;\n    std::shared_ptr<const fizz::CertificateVerifier> certVerifier_;\n    uint16_t maxDatagramSize_{1400};\n    std::function<void(H3Capsule&&)> capsuleCallback_;\n    bool rfcMode_{false};  // When true, use RFC 9298 Extended CONNECT\n    std::string hostname_; // TLS SNI hostname (if empty, uses connect address)\n  };\n\n public:\n  H3DatagramAsyncSocket(folly::EventBase* evb,\n                        H3DatagramAsyncSocket::Options options);\n\n  ~H3DatagramAsyncSocket() override {\n    if (txn_) {\n      txn_->setHandler(nullptr);\n    }\n    if (upstreamSession_) {\n      upstreamSession_->setConnectCallback(nullptr);\n      upstreamSession_->setInfoCallback(nullptr);\n    }\n  }\n\n  H3DatagramAsyncSocket(const H3DatagramAsyncSocket&) = delete;\n  H3DatagramAsyncSocket& operator=(const H3DatagramAsyncSocket&) = delete;\n  H3DatagramAsyncSocket(H3DatagramAsyncSocket&&) = delete;\n  H3DatagramAsyncSocket& operator=(H3DatagramAsyncSocket&&) = delete;\n\n  HTTPTransaction* getTransaction() {\n    return txn_;\n  }\n\n  const folly::AsyncTransportCertificate* getPeerCertificate() const;\n\n  /*\n   * AsyncUDPSocket\n   */\n  const folly::SocketAddress& address() const override;\n\n  void bind(const folly::SocketAddress& address,\n            BindOptions /*options*/ = BindOptions()) override {\n    bindAddress_ = address;\n  }\n\n  void connect(const folly::SocketAddress& address) override {\n    CHECK(options_.mode_ == Mode::CLIENT);\n    connectAddress_ = address;\n    startClient();\n  }\n\n  void setFD(folly::NetworkSocket /*fd*/, FDOwnership /*ownership*/) override {\n    LOG(FATAL) << __func__ << \" not supported\";\n  }\n\n  void setCmsgs(const folly::SocketCmsgMap& /*cmsgs*/) override {\n    LOG(FATAL) << __func__ << \" not supported\";\n  }\n\n  void appendCmsgs(const folly::SocketCmsgMap& /*cmsgs*/) override {\n    LOG(FATAL) << __func__ << \" not supported\";\n  }\n\n  ssize_t write(const folly::SocketAddress& address,\n                const std::unique_ptr<folly::IOBuf>& buf) override;\n\n  int writem(folly::Range<folly::SocketAddress const*> /*addrs*/,\n             const std::unique_ptr<folly::IOBuf>* /*bufs*/,\n             size_t /*count*/) override {\n    return -1;\n  }\n\n  ssize_t writeGSO(const folly::SocketAddress& /*address*/,\n                   const std::unique_ptr<folly::IOBuf>& /*buf*/,\n                   folly::AsyncUDPSocket::WriteOptions /*options*/) override {\n    return -1;\n  }\n\n  ssize_t writeChain(const folly::SocketAddress& /*address*/,\n                     std::unique_ptr<folly::IOBuf>&& /*buf*/,\n                     WriteOptions /*options*/) override {\n    return -1;\n  }\n\n  int writemGSO(folly::Range<folly::SocketAddress const*> /*addrs*/,\n                const std::unique_ptr<folly::IOBuf>* /*bufs*/,\n                size_t /*count*/,\n                const WriteOptions* /*options*/) override {\n    return -1;\n  }\n\n  ssize_t writev(const folly::SocketAddress& /*address*/,\n                 const struct iovec* /*vec*/,\n                 size_t /*iovec_len*/,\n                 folly::AsyncUDPSocket::WriteOptions /*options*/) override {\n    return -1;\n  }\n\n  ssize_t writev(const folly::SocketAddress& /*address*/,\n                 const struct iovec* /*vec*/,\n                 size_t /*iovec_len*/) override {\n    return -1;\n  }\n\n  ssize_t recvmsg(struct msghdr* /*msg*/, int /*flags*/) override;\n\n  int recvmmsg(struct mmsghdr* msgvec,\n               unsigned int vlen,\n               unsigned int flags,\n               struct timespec* /*timeout*/) override;\n\n  void resumeRead(ReadCallback* cob) override;\n\n  void pauseRead() override {\n    readCallback_ = nullptr;\n  }\n\n  void close() override {\n    if (txn_ && !txn_->isEgressEOMSeen()) {\n      txn_->sendEOM();\n    }\n    if (upstreamSession_) {\n      upstreamSession_->closeWhenIdle();\n    }\n  }\n\n  folly::NetworkSocket getNetworkSocket() const override {\n    // Not great but better than crashing.\n    VLOG(4) << \"getNetworkSocket returning fake socket\";\n    return {};\n  }\n\n  void setReusePort(bool /*reusePort*/) override {\n    // Meaningless\n  }\n\n  void setReuseAddr(bool /*reuseAddr*/) override {\n    // Meaningless\n  }\n\n  void setRcvBuf(int rcvBuf) override {\n    if (rcvBuf > 0) {\n      // This is going to be the maximum number of packets buffered here or at\n      // the the quic transport\n      rcvBufPkts_ = rcvBuf / options_.maxDatagramSize_;\n    }\n  }\n\n  void setSndBuf(int sndBuf) override {\n    if (sndBuf > 0) {\n      // This is going to be the maximum number of packets buffered here or at\n      // the the quic transport\n      sndBufPkts_ = sndBuf / options_.maxDatagramSize_;\n    }\n  }\n\n  void setBusyPoll(int /*busyPollUs*/) override {\n    VLOG(4) << \"busy poll not supported\";\n  }\n\n  void dontFragment(bool /*df*/) override {\n    // Meaningless.\n  }\n\n  void setDFAndTurnOffPMTU() override {\n    // Meaningless.\n  }\n\n  void setErrMessageCallback(ErrMessageCallback*) override {\n    // TODO do we want to support this and convey errors this way?\n    VLOG(4) << \"err message callback not supported\";\n  }\n\n  bool isBound() const override {\n    return false;\n  }\n\n  bool isReading() const override {\n    return readCallback_ != nullptr;\n  }\n\n  void detachEventBase() override {\n    LOG(FATAL) << __func__ << \" unsupported\";\n    folly::assume_unreachable();\n  }\n\n  void attachEventBase(folly::EventBase* /*evb*/) override {\n    LOG(FATAL) << __func__ << \" unsupported\";\n    folly::assume_unreachable();\n  }\n\n  int getGSO() override {\n    return -1;\n  }\n\n  void setOverrideNetOpsDispatcher(\n      std::shared_ptr<folly::netops::Dispatcher> /*dispatcher*/) override {\n    LOG(FATAL) << __func__ << \" unsupported\";\n    folly::assume_unreachable();\n  }\n\n  std::shared_ptr<folly::netops::Dispatcher> getOverrideNetOpsDispatcher()\n      const override {\n    LOG(FATAL) << __func__ << \" unsupported\";\n    folly::assume_unreachable();\n  }\n\n private:\n  /*\n   * proxygen::HQSession::ConnectCallback\n   */\n  void connectSuccess() override;\n  void onReplaySafe() override;\n  void connectError(quic::QuicError error) override;\n\n  // HTTPTransactionHandler methods\n  void setTransaction(proxygen::HTTPTransaction* txn) noexcept override;\n  void detachTransaction() noexcept override;\n  void onHeadersComplete(\n      std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept override;\n  void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;\n  void onTrailers(\n      std::unique_ptr<proxygen::HTTPHeaders> trailers) noexcept override;\n  void onEOM() noexcept override;\n  void onUpgrade(proxygen::UpgradeProtocol protocol) noexcept override;\n  void onError(const proxygen::HTTPException& error) noexcept override;\n  void onEgressPaused() noexcept override;\n  void onEgressResumed() noexcept override;\n\n  // Set the HTTP/3 Session. for testing only\n  void setUpstreamSession(proxygen::HQUpstreamSession* session) {\n    CHECK(!upstreamSession_);\n    upstreamSession_ = session;\n    upstreamSession_->setConnectCallback(this);\n    upstreamSession_->setInfoCallback(this);\n  }\n\n  /*\n   * HTTPSessionBase::InfoCallback\n   */\n  void onDestroy(const HTTPSessionBase&) override {\n    upstreamSession_ = nullptr;\n  }\n\n private:\n  void startClient();\n  std::shared_ptr<fizz::client::FizzClientContext> createFizzClientContext();\n  void closeWithError(const folly::AsyncSocketException& ex);\n  void deliverDatagram(std::unique_ptr<folly::IOBuf> datagram) noexcept;\n\n  void closeRead() {\n    if (readCallback_) {\n      auto cb = readCallback_;\n      readCallback_ = nullptr;\n      cb->onReadClosed();\n    }\n  }\n\n  folly::EventBase* evb_;\n  Options options_;\n  folly::SocketAddress bindAddress_;\n  folly::SocketAddress connectAddress_;\n  proxygen::HQUpstreamSession* upstreamSession_{nullptr};\n\n  HTTPTransaction* txn_{nullptr};\n  folly::Optional<folly::AsyncSocketException> pendingError_;\n\n  unsigned int rcvBufPkts_{100};\n  unsigned int sndBufPkts_{100};\n  // Buffers Incoming Datagrams when reads are paused\n  std::deque<std::unique_ptr<folly::IOBuf>> readBuf_;\n  // Buffers Outgoing Datagrams before the transport is ready\n  std::deque<std::unique_ptr<folly::IOBuf>> writeBuf_;\n  std::unique_ptr<folly::IOBuf> pendingDelivery_;\n\n  bool transportConnected_ : 1;\n  bool pendingEOM_ : 1;\n  bool inResumeRead_ : 1;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentFizzPskCache.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/PersistentFizzPskCache.h>\n\nnamespace folly {\n\ntemplate <>\ndynamic toDynamic(const proxygen::PersistentCachedPsk& cached) {\n  dynamic d = dynamic::object;\n  d[\"psk\"] = cached.serialized;\n  d[\"uses\"] = cached.uses;\n  return d;\n}\n\ntemplate <>\nproxygen::PersistentCachedPsk convertTo(const dynamic& d) {\n  proxygen::PersistentCachedPsk psk;\n  psk.serialized = d[\"psk\"].asString();\n  psk.uses = folly::to<size_t>(d[\"uses\"].asInt());\n  return psk;\n}\n} // namespace folly\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentFizzPskCache.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <fizz/client/PskCache.h>\n#include <fizz/client/PskSerializationUtils.h>\n#include <wangle/client/persistence/FilePersistentCache.h>\n\nnamespace proxygen {\n\nstruct PersistentCachedPsk {\n  std::string serialized;\n  size_t uses{0};\n};\n\nclass PersistentFizzPskCache : public fizz::client::PskCache {\n public:\n  ~PersistentFizzPskCache() override = default;\n\n  PersistentFizzPskCache(const std::string& filename,\n                         wangle::PersistentCacheConfig config)\n      : cache_(filename, std::move(config)) {\n  }\n\n  void setMaxPskUses(size_t maxUses) {\n    maxPskUses_ = maxUses;\n  }\n\n  /**\n   * Returns number of times the psk has been used.\n   */\n  folly::Optional<size_t> getPskUses(const std::string& identity) {\n    auto serialized = cache_.get(identity);\n    if (serialized) {\n      return serialized->uses;\n    }\n    return folly::none;\n  }\n\n  /**\n   * Returns a PSK, without increasing its usage count. Keep in mind that it\n   * would still update the internal LRU cache\n   */\n  folly::Optional<fizz::client::CachedPsk> peekPsk(\n      const std::string& identity) {\n    auto serialized = cache_.get(identity);\n    if (serialized) {\n      try {\n        fizz::client::CachedPsk deserialized;\n        fizz::Error err;\n        FIZZ_THROW_ON_ERROR(fizz::client::deserializePsk(\n                                deserialized,\n                                err,\n                                fizz::openssl::certificateSerializer(),\n                                folly::ByteRange(serialized->serialized)),\n                            err);\n        return deserialized;\n      } catch (const std::exception& ex) {\n        LOG(ERROR) << \"Error deserializing PSK: \" << ex.what();\n        cache_.remove(identity);\n      }\n    }\n    return folly::none;\n  }\n\n  folly::Optional<fizz::client::CachedPsk> getPsk(\n      const std::string& identity) override {\n    auto serialized = cache_.get(identity);\n    if (serialized) {\n      try {\n        fizz::client::CachedPsk deserialized;\n        fizz::Error err;\n        FIZZ_THROW_ON_ERROR(fizz::client::deserializePsk(\n                                deserialized,\n                                err,\n                                fizz::openssl::certificateSerializer(),\n                                folly::ByteRange(serialized->serialized)),\n                            err);\n        serialized->uses++;\n        if (maxPskUses_ != 0 && serialized->uses >= maxPskUses_) {\n          cache_.remove(identity);\n        } else {\n          cache_.put(identity, *serialized);\n        }\n        return deserialized;\n      } catch (const std::exception& ex) {\n        LOG(ERROR) << \"Error deserializing PSK: \" << ex.what();\n        cache_.remove(identity);\n      }\n    }\n    return folly::none;\n  }\n\n  void putPsk(const std::string& identity,\n              fizz::client::CachedPsk psk) override {\n    PersistentCachedPsk serialized;\n    fizz::Error err;\n    FIZZ_THROW_ON_ERROR(\n        fizz::client::serializePsk(serialized.serialized,\n                                   err,\n                                   fizz::openssl::certificateSerializer(),\n                                   psk),\n        err);\n    serialized.uses = 0;\n    cache_.put(identity, serialized);\n  }\n\n  void removePsk(const std::string& identity) override {\n    cache_.remove(identity);\n  }\n\n private:\n  wangle::FilePersistentCache<std::string, PersistentCachedPsk> cache_;\n\n  size_t maxPskUses_{5};\n};\n} // namespace proxygen\n\nnamespace folly {\n\ntemplate <>\ndynamic toDynamic(const proxygen::PersistentCachedPsk& cached);\ntemplate <>\nproxygen::PersistentCachedPsk convertTo(const dynamic& d);\n} // namespace folly\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentQuicPskCache.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/PersistentQuicPskCache.h>\n\n#include <fizz/backend/openssl/certificate/CertUtils.h>\n#include <fizz/record/Types.h>\n#include <folly/Conv.h>\n#include <folly/json/dynamic.h>\n#include <quic/client/handshake/CachedServerTransportParametersSerialization.h>\n\nnamespace {\nconstexpr auto FIZZ_PSK = \"psk\";\nconstexpr auto QUIC_PARAMS = \"quic1\";\nconstexpr auto USES = \"uses\";\n} // namespace\n\nnamespace proxygen {\nPersistentQuicPskCache::PersistentQuicPskCache(\n    const std::string& filename, wangle::PersistentCacheConfig config)\n    : cache_(filename, std::move(config)) {\n}\n\nvoid PersistentQuicPskCache::setMaxPskUses(size_t maxUses) {\n  maxPskUses_ = maxUses;\n}\n\nquic::Optional<size_t> PersistentQuicPskCache::getPskUses(\n    const std::string& identity) {\n  auto cachedPsk = cache_.get(identity);\n  if (cachedPsk) {\n    return cachedPsk->uses;\n  }\n  return std::nullopt;\n}\n\nquic::Optional<quic::QuicCachedPsk> PersistentQuicPskCache::getPsk(\n    const std::string& identity) {\n  auto cachedPsk = cache_.get(identity);\n  if (!cachedPsk) {\n    return std::nullopt;\n  }\n  try {\n    quic::QuicCachedPsk quicCachedPsk;\n    fizz::Error err;\n    FIZZ_THROW_ON_ERROR(\n        fizz::client::deserializePsk(quicCachedPsk.cachedPsk,\n                                     err,\n                                     fizz::openssl::certificateSerializer(),\n                                     folly::ByteRange(cachedPsk->fizzPsk)),\n        err);\n\n    auto buf = folly::IOBuf::wrapBuffer(cachedPsk->quicParams.data(),\n                                        cachedPsk->quicParams.length());\n    folly::io::Cursor cursor(buf.get());\n    quic::readCachedServerTransportParameters(cursor,\n                                              quicCachedPsk.transportParams);\n\n    std::unique_ptr<folly::IOBuf> appParams;\n    fizz::detail::readBuf<uint16_t>(appParams, cursor);\n    quicCachedPsk.appParams = appParams->to<std::string>();\n\n    cachedPsk->uses++;\n    if (maxPskUses_ != 0 && cachedPsk->uses >= maxPskUses_) {\n      cache_.remove(identity);\n    } else {\n      cache_.put(identity, *cachedPsk);\n    }\n    return std::move(quicCachedPsk);\n  } catch (const std::exception& ex) {\n    LOG(ERROR) << \"Error deserializing PSK: \" << ex.what();\n    cache_.remove(identity);\n    return std::nullopt;\n  }\n}\n\nvoid PersistentQuicPskCache::putPsk(const std::string& identity,\n                                    quic::QuicCachedPsk quicCachedPsk) {\n  PersistentQuicCachedPsk cachedPsk;\n  fizz::Error err;\n  FIZZ_THROW_ON_ERROR(\n      fizz::client::serializePsk(cachedPsk.fizzPsk,\n                                 err,\n                                 fizz::openssl::certificateSerializer(),\n                                 quicCachedPsk.cachedPsk),\n      err);\n\n  auto quicParams = folly::IOBuf::create(0);\n  folly::io::Appender appender(quicParams.get(), 512);\n  quic::writeCachedServerTransportParameters(quicCachedPsk.transportParams,\n                                             appender);\n\n  FIZZ_THROW_ON_ERROR(\n      fizz::detail::writeBuf<uint16_t>(\n          err,\n          folly::IOBuf::wrapBuffer(folly::StringPiece(quicCachedPsk.appParams)),\n          appender),\n      err);\n  cachedPsk.quicParams = quicParams->to<std::string>();\n  cachedPsk.uses = 0;\n  cache_.put(identity, cachedPsk);\n}\n\nvoid PersistentQuicPskCache::removePsk(const std::string& identity) {\n  cache_.remove(identity);\n}\n} // namespace proxygen\n\nnamespace folly {\n\ntemplate <>\ndynamic toDynamic(const proxygen::PersistentQuicCachedPsk& cached) {\n  dynamic d = dynamic::object;\n  d[FIZZ_PSK] = cached.fizzPsk;\n  d[QUIC_PARAMS] = cached.quicParams;\n  d[USES] = cached.uses;\n  return d;\n}\n\ntemplate <>\nproxygen::PersistentQuicCachedPsk convertTo(const dynamic& d) {\n  proxygen::PersistentQuicCachedPsk psk;\n  psk.fizzPsk = d[FIZZ_PSK].asString();\n  psk.quicParams = d[QUIC_PARAMS].asString();\n  psk.uses = folly::to<size_t>(d[USES].asInt());\n  return psk;\n}\n} // namespace folly\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentQuicPskCache.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/transport/PersistentFizzPskCache.h>\n\n#include <fizz/client/PskSerializationUtils.h>\n#include <folly/Optional.h>\n#include <folly/json/dynamic.h>\n#include <quic/fizz/client/handshake/QuicPskCache.h>\n#include <wangle/client/persistence/FilePersistentCache.h>\n\n#include <chrono>\n#include <cstdint>\n#include <string>\n#include <unordered_map>\n\nnamespace proxygen {\n\n// TODO share quic::AppToken class for serialization\nstruct PersistentQuicCachedPsk {\n  std::string fizzPsk;\n  std::string quicParams;\n  size_t uses{0};\n};\n\nclass PersistentQuicPskCache : public quic::QuicPskCache {\n public:\n  PersistentQuicPskCache(const std::string& filename,\n                         wangle::PersistentCacheConfig config);\n\n  void setMaxPskUses(size_t maxUses);\n\n  /**\n   * Returns number of times the psk has been used.\n   */\n  quic::Optional<size_t> getPskUses(const std::string& identity);\n\n  quic::Optional<quic::QuicCachedPsk> getPsk(\n      const std::string& identity) override;\n  void putPsk(const std::string& identity,\n              quic::QuicCachedPsk quicCachedPsk) override;\n  void removePsk(const std::string& identity) override;\n\n private:\n  wangle::FilePersistentCache<std::string, PersistentQuicCachedPsk> cache_;\n  size_t maxPskUses_{5};\n};\n\n} // namespace proxygen\n\nnamespace folly {\ntemplate <>\ndynamic toDynamic(const proxygen::PersistentQuicCachedPsk& cached);\ntemplate <>\nproxygen::PersistentQuicCachedPsk convertTo(const dynamic& d);\n} // namespace folly\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentQuicTokenCache.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/PersistentQuicTokenCache.h>\n\nnamespace proxygen {\n\nPersistentQuicTokenCache::PersistentQuicTokenCache(\n    const std::string& filename, wangle::PersistentCacheConfig config)\n    : cache_(filename, std::move(config)) {\n}\n\nquic::Optional<std::string> PersistentQuicTokenCache::getToken(\n    const std::string& hostname) {\n  auto result = cache_.get(hostname);\n  return result.has_value() ? quic::Optional<std::string>(result.value())\n                            : std::nullopt;\n}\n\nvoid PersistentQuicTokenCache::putToken(const std::string& hostname,\n                                        std::string token) {\n  cache_.put(hostname, token);\n}\n\nvoid PersistentQuicTokenCache::removeToken(const std::string& hostname) {\n  cache_.remove(hostname);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/PersistentQuicTokenCache.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <string>\n\n#include <quic/fizz/client/handshake/QuicTokenCache.h>\n#include <wangle/client/persistence/FilePersistentCache.h>\n\nnamespace proxygen {\n\nclass PersistentQuicTokenCache : public quic::QuicTokenCache {\n public:\n  PersistentQuicTokenCache(const std::string& filename,\n                           wangle::PersistentCacheConfig config);\n\n  [[nodiscard]] quic::Optional<std::string> getToken(\n      const std::string& hostname) override;\n\n  void putToken(const std::string& hostname, std::string token) override;\n\n  void removeToken(const std::string&) override;\n\n private:\n  wangle::FilePersistentCache<std::string, std::string> cache_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/test/AsyncUDPSocketFactoryTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n\n#include \"proxygen/lib/transport/AsyncUDPSocketFactory.h\"\n\nnamespace {\nfolly::SocketAddress getV6TestAddress() {\n  return {\"::1\", 0};\n}\n\nfolly::SocketAddress getV4TestAddress() {\n  return {\"127.0.0.1\", 0};\n}\n} // namespace\n\nnamespace proxygen {\n\nclass AsyncUDPSocketFactoryTest : public testing::Test {\n public:\n  void TearDown() override {\n    evb_.loop();\n  }\n\n protected:\n  folly::EventBase evb_;\n};\n\nTEST_F(AsyncUDPSocketFactoryTest, CreateSuccessV6) {\n  AsyncUDPSocketFactory factory(&evb_, getV6TestAddress());\n\n  auto maybeSocket = factory.createSocket(getV6TestAddress());\n  EXPECT_FALSE(maybeSocket.hasError());\n}\n\nTEST_F(AsyncUDPSocketFactoryTest, CreateSuccessV4) {\n  AsyncUDPSocketFactory factory(&evb_, getV6TestAddress(), getV4TestAddress());\n\n  auto maybeSocket = factory.createSocket(getV4TestAddress());\n  EXPECT_FALSE(maybeSocket.hasError());\n}\n\nTEST_F(AsyncUDPSocketFactoryTest, EmptyV4BindAddresses) {\n  AsyncUDPSocketFactory factory(&evb_, getV6TestAddress());\n  auto maybeSocket = factory.createSocket(getV4TestAddress());\n  EXPECT_TRUE(maybeSocket.hasError());\n}\n\nTEST_F(AsyncUDPSocketFactoryTest, BindFailure) {\n  AsyncUDPSocketFactory factory(\n      &evb_, getV4TestAddress(), folly::SocketAddress(\"0.13.13.13\", 0));\n  auto maybeSocket = factory.createSocket(getV4TestAddress());\n  EXPECT_TRUE(maybeSocket.hasError());\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nif(NOT BUILD_TESTS)\n    return()\nendif()\n\nproxygen_add_test(TARGET TransportTests\n  SOURCES\n      H3DatagramAsyncSocketTest.cpp\n    DEPENDS\n      codectestutils\n      sessiontestutils\n      proxygen\n      testmain\n      mvfst::mvfst_codec_types\n      mvfst::mvfst_state_machine\n)\n"
  },
  {
    "path": "proxygen/lib/transport/test/ConnectUDPUtilsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/ConnectUDPUtils.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nTEST(ConnectUDPUtilsTest, ExpandTemplateBasic) {\n  auto target = expandConnectUDPTemplate(\n      \"https://127.0.0.1:4443/masque?h={target_host}&p={target_port}\",\n      \"192.0.2.6\",\n      443);\n  EXPECT_EQ(target.scheme, \"https\");\n  EXPECT_EQ(target.authority, \"127.0.0.1:4443\");\n  EXPECT_EQ(target.path, \"/masque?h=192.0.2.6&p=443\");\n}\n\nTEST(ConnectUDPUtilsTest, ExpandTemplateIPv6) {\n  auto target = expandConnectUDPTemplate(\n      \"https://proxy.example:4443/masque?h={target_host}&p={target_port}\",\n      \"2001:db8::1\",\n      8080);\n  EXPECT_EQ(target.scheme, \"https\");\n  EXPECT_EQ(target.authority, \"proxy.example:4443\");\n  EXPECT_EQ(target.path, \"/masque?h=2001%3Adb8%3A%3A1&p=8080\");\n}\n\nTEST(ConnectUDPUtilsTest, ExpandTemplateWellKnown) {\n  auto target = expandConnectUDPTemplate(\n      \"https://proxy.example:443/.well-known/masque/udp/{target_host}/\"\n      \"{target_port}/\",\n      \"target.example.com\",\n      443);\n  EXPECT_EQ(target.scheme, \"https\");\n  EXPECT_EQ(target.authority, \"proxy.example:443\");\n  EXPECT_EQ(target.path, \"/.well-known/masque/udp/target.example.com/443/\");\n}\n\nTEST(ConnectUDPUtilsTest, ExpandTemplateNoPath) {\n  auto target =\n      expandConnectUDPTemplate(\"https://proxy.example:443\", \"host\", 80);\n  EXPECT_EQ(target.scheme, \"https\");\n  EXPECT_EQ(target.authority, \"proxy.example:443\");\n  EXPECT_EQ(target.path, \"/\");\n}\n\nTEST(ConnectUDPUtilsTest, PrependContextId) {\n  auto payload = folly::IOBuf::copyBuffer(\"hello\");\n  auto result = prependContextId(std::move(payload));\n  ASSERT_NE(result, nullptr);\n  result->coalesce();\n  EXPECT_EQ(result->length(), 6);\n  EXPECT_EQ(result->data()[0], 0x00);\n  EXPECT_EQ(std::string((const char*)result->data() + 1, 5), \"hello\");\n}\n\nTEST(ConnectUDPUtilsTest, StripContextIdZero) {\n  // Build datagram with context ID 0 + payload \"hello\"\n  auto buf = folly::IOBuf::create(6);\n  buf->append(6);\n  buf->writableData()[0] = 0x00;\n  memcpy(buf->writableData() + 1, \"hello\", 5);\n\n  auto result = stripContextId(std::move(buf));\n  ASSERT_TRUE(result.has_value());\n  result.value()->coalesce();\n  EXPECT_EQ(std::string((const char*)result.value()->data(),\n                        result.value()->length()),\n            \"hello\");\n}\n\nTEST(ConnectUDPUtilsTest, StripContextIdNonZero) {\n  // Build datagram with context ID 1 + payload\n  auto buf = folly::IOBuf::create(6);\n  buf->append(6);\n  buf->writableData()[0] = 0x01;\n  memcpy(buf->writableData() + 1, \"hello\", 5);\n\n  auto result = stripContextId(std::move(buf));\n  EXPECT_FALSE(result.has_value());\n}\n\nTEST(ConnectUDPUtilsTest, StripContextIdEmpty) {\n  auto result = stripContextId(nullptr);\n  EXPECT_FALSE(result.has_value());\n}\n\nTEST(ConnectUDPUtilsTest, StripContextIdEmptyPayload) {\n  // Context ID 0 with no payload\n  auto buf = folly::IOBuf::create(1);\n  buf->append(1);\n  buf->writableData()[0] = 0x00;\n\n  auto result = stripContextId(std::move(buf));\n  ASSERT_TRUE(result.has_value());\n  EXPECT_EQ(result.value()->computeChainDataLength(), 0);\n}\n"
  },
  {
    "path": "proxygen/lib/transport/test/H3DatagramAsyncSocketTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/http/codec/test/TestUtils.h>\n#include <proxygen/lib/transport/H3DatagramAsyncSocket.h>\n#include <proxygen/lib/transport/test/H3DatagramAsyncSocketTest.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\nusing namespace quic;\nusing namespace testing;\n\nvoid H3DatagramAsyncSocketTest::SetUp() {\n  // Create the socket\n  options_.mode_ = H3DatagramAsyncSocket::Mode::CLIENT;\n  options_.txnTimeout_ = std::chrono::milliseconds(1000);\n  options_.connectTimeout_ = std::chrono::milliseconds(500);\n  options_.httpRequest_ = std::make_unique<HTTPMessage>();\n  options_.httpRequest_->setMethod(proxygen::HTTPMethod::GET);\n  options_.httpRequest_->setURL(getRemoteAddress().describe());\n  options_.httpRequest_->setMasque();\n  options_.maxDatagramSize_ = kMaxDatagramSize;\n\n  datagramSocket_ =\n      std::make_unique<H3DatagramAsyncSocket>(&eventBase_, options_);\n\n  session_ = new HQUpstreamSession(options_.txnTimeout_,\n                                   options_.connectTimeout_,\n                                   nullptr,\n                                   proxygen::mockTransportInfo,\n                                   nullptr);\n\n  socketDriver_ = std::make_unique<MockQuicSocketDriver>(\n      &eventBase_,\n      session_,\n      session_,\n      MockQuicSocketDriver::TransportEnum::CLIENT,\n      \"h3\");\n  socketDriver_->setMaxUniStreams(3);\n\n  session_->setSocket(socketDriver_->getSocket());\n  session_->setEgressSettings({{proxygen::SettingsId::_HQ_DATAGRAM, 1}});\n\n  datagramSocket_->setUpstreamSession(session_);\n  datagramSocket_->setRcvBuf(kMaxDatagramsBufferedRead * kMaxDatagramSize);\n  datagramSocket_->setSndBuf(kMaxDatagramsBufferedWrite * kMaxDatagramSize);\n\n  EXPECT_CALL(readCallbacks_, getReadBuffer_(_, _))\n      .WillRepeatedly(Invoke([this](void** buf, size_t* len) {\n        *buf = &buf_;\n        *len = kMaxDatagramSize;\n      }));\n  quic::QuicSocket::TransportInfo transportInfo;\n  EXPECT_CALL(*socketDriver_->getSocket(), getTransportInfo())\n      .WillRepeatedly(testing::Return(transportInfo));\n  EXPECT_CALL(*socketDriver_->getSocket(), getClientChosenDestConnectionId())\n      .WillRepeatedly(Return(ConnectionId::createRandom(2).value()));\n}\n\nvoid H3DatagramAsyncSocketTest::TearDown() {\n  if (datagramSocket_) {\n    datagramSocket_->close();\n  }\n  // Needed for proper teardown of the upstream session\n  eventBase_.loop();\n}\n\nfolly::SocketAddress& H3DatagramAsyncSocketTest::getLocalAddress() {\n  static folly::SocketAddress localAddr(\"::\", 0);\n  return localAddr;\n}\n\nfolly::SocketAddress& H3DatagramAsyncSocketTest::getRemoteAddress() {\n  static folly::SocketAddress remoteAddr(\"::\", 12345);\n  return remoteAddr;\n}\n\nssize_t H3DatagramAsyncSocketTest::sendDatagramUpstream(\n    std::unique_ptr<folly::IOBuf> datagram) {\n  CHECK(datagramSocket_);\n  return datagramSocket_->write(getRemoteAddress(), datagram);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, Connect) {\n  datagramSocket_->connect(getRemoteAddress());\n}\n\nTEST_P(H3DatagramAsyncSocketModeTest, ConnectAndReady) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n}\n\nINSTANTIATE_TEST_SUITE_P(Modes,\n                         H3DatagramAsyncSocketModeTest,\n                         testing::Bool(),\n                         [](const testing::TestParamInfo<bool>& info) {\n                           return info.param ? \"Rfc\" : \"Legacy\";\n                         });\n\nTEST_F(H3DatagramAsyncSocketTest, ConnectErrorBeforeReadCallbackSet) {\n  datagramSocket_->connect(getRemoteAddress());\n  connectError(quic::QuicError(LocalErrorCode::CONNECT_FAILED, \"unreachable\"));\n  EXPECT_CALL(readCallbacks_, onReadError_(_))\n      .Times(1)\n      .WillOnce(Invoke([&](auto err) {\n        EXPECT_EQ(err.getType(), folly::AsyncSocketException::NETWORK_ERROR);\n        EXPECT_EQ(std::string(err.what()),\n                  \"AsyncSocketException: connectError: 'unreachable', type = \"\n                  \"Network error\");\n      }));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, ConnectErrorAfterReadCallbackSet) {\n  datagramSocket_->connect(getRemoteAddress());\n  EXPECT_CALL(readCallbacks_, onReadError_(_))\n      .Times(1)\n      .WillOnce(Invoke([&](auto err) {\n        EXPECT_EQ(err.getType(), folly::AsyncSocketException::NETWORK_ERROR);\n        EXPECT_EQ(std::string(err.what()),\n                  \"AsyncSocketException: connectError: 'unreachable', type = \"\n                  \"Network error\");\n      }));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n  connectError(quic::QuicError(LocalErrorCode::CONNECT_FAILED, \"unreachable\"));\n}\n\nTEST_F(H3DatagramAsyncSocketTest, HTTPNon200Response) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  datagramSocket_->resumeRead(&readCallbacks_);\n  EXPECT_CALL(readCallbacks_, onReadError_(_))\n      .Times(1)\n      .WillOnce(Invoke([&](auto err) {\n        EXPECT_EQ(err.getType(), folly::AsyncSocketException::INTERNAL_ERROR);\n        EXPECT_EQ(std::string(err.what()),\n                  \"AsyncSocketException: HTTP Error: status code 407, type = \"\n                  \"Internal error\");\n      }));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  onHeadersComplete(makeResponse(407));\n}\n\nTEST_F(H3DatagramAsyncSocketTest, CloseAfter200Response) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  datagramSocket_->resumeRead(&readCallbacks_);\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  onHeadersComplete(makeResponse(200));\n}\n\nTEST_F(H3DatagramAsyncSocketTest, EOMAfter200Response) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  datagramSocket_->resumeRead(&readCallbacks_);\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  onHeadersComplete(makeResponse(200));\n  onEOM();\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DatagramsBeforeReadCallback) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  for (auto i = 0; i <= kMaxDatagramsBufferedRead * 2; ++i) {\n    onDatagram(folly::IOBuf::copyBuffer(fmt::format(\"{0:010}\", i)));\n  }\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(kMaxDatagramsBufferedRead)\n      .WillRepeatedly(Invoke(\n          [this](const folly::SocketAddress& client,\n                 size_t len,\n                 bool /*truncated*/,\n                 const MockUDPReadCallback::OnDataAvailableParams& /*params*/) {\n            EXPECT_EQ(client, session_->getPeerAddress());\n            EXPECT_EQ(len, 10);\n            auto datagramString = std::string(buf_, len);\n            EXPECT_EQ(datagramString.size(), 10);\n            // Check that only the furst kMaxDatagramsBufferedRead were\n            // buffered\n            auto num = folly::to<int>(datagramString);\n            EXPECT_LT(num, kMaxDatagramsBufferedRead);\n          }));\n  datagramSocket_->resumeRead(&readCallbacks_);\n  onEOM();\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DatagramsAndEOMBeforeReadCallback) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  auto payload =\n      \"This will be buffered and delivered when resuming reads, after EOM\";\n  onDatagram(folly::IOBuf::copyBuffer(payload));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(1)\n      .WillRepeatedly(Invoke(\n          [&](const folly::SocketAddress& client,\n              size_t len,\n              bool /*truncated*/,\n              const MockUDPReadCallback::OnDataAvailableParams& /*params*/) {\n            EXPECT_EQ(client, session_->getPeerAddress());\n            auto datagramString = std::string(buf_, len);\n            EXPECT_EQ(datagramString, payload);\n          }));\n  onEOM();\n  datagramSocket_->resumeRead(&readCallbacks_);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DiscardDatagramsBufferTooSmall) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  auto shortPayload = \"short\";\n  auto longPayload = \"this is way too long; it'll get discarded\";\n\n  EXPECT_CALL(readCallbacks_, getReadBuffer_(_, _))\n      .WillRepeatedly(Invoke([this](void** buf, size_t* len) {\n        *buf = &buf_;\n        *len = 10;\n      }));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(1)\n      .WillRepeatedly(Invoke(\n          [&](const folly::SocketAddress& client,\n              size_t len,\n              bool /*truncated*/,\n              const MockUDPReadCallback::OnDataAvailableParams& /*params*/) {\n            EXPECT_EQ(client, session_->getPeerAddress());\n            auto datagramString = std::string(buf_, len);\n            EXPECT_EQ(datagramString, shortPayload);\n          }));\n  datagramSocket_->resumeRead(&readCallbacks_);\n  onDatagram(folly::IOBuf::copyBuffer(shortPayload));\n  onDatagram(folly::IOBuf::copyBuffer(longPayload));\n  onEOM();\n}\n\nTEST_F(H3DatagramAsyncSocketTest, WriteBeforeConnect) {\n  auto ret =\n      sendDatagramUpstream(folly::IOBuf::copyBuffer(\"This will be discarded\"));\n  EXPECT_EQ(ret, -1);\n  EXPECT_EQ(errno, ENOTCONN);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, WriteInvalidBuf) {\n  std::unique_ptr<folly::IOBuf> nullptrBuf;\n  auto ret = sendDatagramUpstream(std::move(nullptrBuf));\n  EXPECT_EQ(ret, -1);\n  EXPECT_EQ(errno, EINVAL);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, WriteWrongAddress) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  auto ret = datagramSocket_->write(\n      folly::SocketAddress(\"1.2.3.4\", 0),\n      folly::IOBuf::copyBuffer(\n          \"send to wrong address. I am going to be discarded\"));\n  EXPECT_EQ(ret, -1);\n  EXPECT_EQ(errno, EINVAL);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, BufferWriteBeforeConnectSuccess) {\n  datagramSocket_->connect(getRemoteAddress());\n  std::string payload = \"This will be buffered\";\n  auto ret = sendDatagramUpstream(folly::IOBuf::copyBuffer(payload));\n  EXPECT_EQ(ret, payload.size());\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), 0);\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), 1);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DiscardWhenApplicationWriteBufferFull) {\n  datagramSocket_->connect(getRemoteAddress());\n  for (auto i = 0; i <= kMaxDatagramsBufferedWrite; ++i) {\n    auto payload = fmt::format(\"{}\", i);\n    auto ret = sendDatagramUpstream(folly::IOBuf::copyBuffer(payload));\n    if (i < kMaxDatagramsBufferedWrite) {\n      EXPECT_EQ(ret, payload.size());\n    } else {\n      EXPECT_EQ(errno, ENOBUFS);\n    }\n    EXPECT_EQ(socketDriver_->outDatagrams_.size(), 0);\n  }\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), kMaxDatagramsBufferedWrite);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, CheckErrorWhenTransportFails) {\n  EXPECT_CALL(*socketDriver_->getSocket(), writeDatagram(testing::_))\n      .WillRepeatedly(\n          Return(quic::make_unexpected(LocalErrorCode::INVALID_WRITE_DATA)));\n\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  auto ret = sendDatagramUpstream(folly::IOBuf::copyBuffer(\n      \"This is going to be discarded by the transport\"));\n  EXPECT_EQ(ret, -1);\n  EXPECT_EQ(errno, ENOBUFS);\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), 0);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, WriteLargerThanDatagramSizeLimit) {\n  EXPECT_CALL(*socketDriver_->getSocket(), getDatagramSizeLimit())\n      .WillRepeatedly(Return(10));\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  auto ret = sendDatagramUpstream(folly::IOBuf::copyBuffer(\n      \"This is way larger than 10 bytes. It'll be discarded\"));\n  EXPECT_EQ(ret, -1);\n  EXPECT_EQ(errno, EMSGSIZE);\n  EXPECT_EQ(socketDriver_->outDatagrams_.size(), 0);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DatagramsPauseResumeRead) {\n  InSequence enforceOrder;\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  datagramSocket_->resumeRead(&readCallbacks_);\n  auto payload1 = \"This will be delivered immediately\";\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _)).Times(1);\n  onDatagram(folly::IOBuf::copyBuffer(payload1));\n  datagramSocket_->pauseRead();\n\n  auto payload2 = \"This will be delivered after reads are resumed\";\n  onDatagram(folly::IOBuf::copyBuffer(payload2));\n  onEOM();\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _)).Times(1);\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n}\n\nTEST_F(H3DatagramAsyncSocketTest, DatagramsTriggerPauseRead) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  auto payload1 = \"This will trigger pauseReads\";\n  onDatagram(folly::IOBuf::copyBuffer(payload1));\n  auto payload2 = \"This will be delivered after reads are resumed\";\n  onDatagram(folly::IOBuf::copyBuffer(payload2));\n  onEOM();\n  int datagramsDelivered = 0;\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(2)\n      .WillRepeatedly(Invoke(\n          [&](const folly::SocketAddress& client,\n              size_t len,\n              bool /*truncated*/,\n              const MockUDPReadCallback::OnDataAvailableParams& /*params*/) {\n            EXPECT_EQ(client, session_->getPeerAddress());\n            datagramsDelivered++;\n            EXPECT_EQ(datagramSocket_->isReading(), true);\n            datagramSocket_->pauseRead();\n            EXPECT_EQ(datagramSocket_->isReading(), false);\n          }));\n  datagramSocket_->resumeRead(&readCallbacks_);\n  EXPECT_EQ(datagramsDelivered, 1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n  EXPECT_EQ(datagramsDelivered, 2);\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n}\n\n// Test resumeReads from onDataAvailable\nTEST_F(H3DatagramAsyncSocketTest, ResumeReadReentrancy) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n  auto payload1 = \"This will trigger resumeReads\";\n  onDatagram(folly::IOBuf::copyBuffer(payload1));\n  auto payload2 = \"This will be delivered just fine\";\n  onDatagram(folly::IOBuf::copyBuffer(payload2));\n  onEOM();\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(2)\n      .WillRepeatedly(Invoke(\n          [&](const folly::SocketAddress& client,\n              size_t len,\n              bool /*truncated*/,\n              const MockUDPReadCallback::OnDataAvailableParams& /*params*/) {\n            EXPECT_EQ(client, session_->getPeerAddress());\n            datagramSocket_->resumeRead(&readCallbacks_);\n          }));\n  EXPECT_CALL(readCallbacks_, onReadClosed_()).Times(1);\n  datagramSocket_->resumeRead(&readCallbacks_);\n}\n\n// Test delete before TransportReady\nTEST_F(H3DatagramAsyncSocketTest, DeleteSocketBeforeTransportReady) {\n  datagramSocket_->connect(getRemoteAddress());\n  datagramSocket_.reset();\n  eventBase_.loop();\n  session_->onConnectionError(quic::QuicError(\n      quic::QuicErrorCode(quic::TransportErrorCode::INTERNAL_ERROR),\n      std::string(\"connect timeout expired\")));\n}\n\n// Test that calling close twice doesn't violate the HTTP state machine by\n// trying to send EOM twice\nTEST_F(H3DatagramAsyncSocketTest, CloseTwice) {\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  datagramSocket_->close();\n  datagramSocket_->close();\n}\n\nTEST_F(H3DatagramAsyncSocketTest, RfcModeDatagramContextIdStrip) {\n  SetUpRfcMode();\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n\n  // Create datagram with context ID 0 + \"hello\"\n  auto buf = folly::IOBuf::create(6);\n  buf->append(6);\n  buf->writableData()[0] = 0x00;\n  memcpy(buf->writableData() + 1, \"hello\", 5);\n\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _))\n      .Times(1)\n      .WillOnce([this](const folly::SocketAddress& client,\n                       size_t len,\n                       bool,\n                       const MockUDPReadCallback::OnDataAvailableParams&) {\n        EXPECT_EQ(client, session_->getPeerAddress());\n        EXPECT_EQ(len, 5);\n        EXPECT_EQ(std::string(buf_, len), \"hello\");\n      });\n  datagramSocket_->resumeRead(&readCallbacks_);\n  onDatagram(std::move(buf));\n}\n\nTEST_F(H3DatagramAsyncSocketTest, RfcModeDropNonZeroContextId) {\n  SetUpRfcMode();\n  datagramSocket_->connect(getRemoteAddress());\n  session_->onTransportReady();\n  session_->onReplaySafe();\n  onHeadersComplete(makeResponse(200));\n\n  // Create datagram with context ID 1 — should be silently dropped\n  auto buf = folly::IOBuf::create(6);\n  buf->append(6);\n  buf->writableData()[0] = 0x01;\n  memcpy(buf->writableData() + 1, \"hello\", 5);\n\n  EXPECT_CALL(readCallbacks_, onDataAvailable_(_, _, _, _)).Times(0);\n  datagramSocket_->resumeRead(&readCallbacks_);\n  onDatagram(std::move(buf));\n}\n"
  },
  {
    "path": "proxygen/lib/transport/test/H3DatagramAsyncSocketTest.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/futures/Future.h>\n#include <folly/io/async/EventBaseManager.h>\n#include <folly/portability/GTest.h>\n#include <limits>\n#include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>\n#include <proxygen/lib/http/session/test/TestUtils.h>\n#include <proxygen/lib/transport/H3DatagramAsyncSocket.h>\n#include <quic/api/test/MockQuicSocket.h>\n#include <utility>\n\nconstexpr uint16_t kMaxDatagramSize = 100;\nconstexpr uint16_t kMaxDatagramsBufferedRead = 5;\nconstexpr uint16_t kMaxDatagramsBufferedWrite = 10;\n\nclass MockUDPReadCallback : public folly::AsyncUDPSocket::ReadCallback {\n public:\n  ~MockUDPReadCallback() override = default;\n\n  MOCK_METHOD(void, getReadBuffer_, (void**, size_t*));\n  void getReadBuffer(void** buf, size_t* len) noexcept override {\n    getReadBuffer_(buf, len);\n  }\n\n  MOCK_METHOD(\n      void,\n      onDataAvailable_,\n      (const folly::SocketAddress&, size_t, bool, OnDataAvailableParams));\n  void onDataAvailable(const folly::SocketAddress& client,\n                       size_t len,\n                       bool truncated,\n                       OnDataAvailableParams params) noexcept override {\n    onDataAvailable_(client, len, truncated, params);\n  }\n\n  MOCK_METHOD(void, onReadError_, (const folly::AsyncSocketException&));\n  void onReadError(const folly::AsyncSocketException& ex) noexcept override {\n    onReadError_(ex);\n  }\n\n  MOCK_METHOD(void, onReadClosed_, ());\n  void onReadClosed() noexcept override {\n    onReadClosed_();\n  }\n};\n\nnamespace proxygen {\n\nclass H3DatagramAsyncSocketTest : public testing::Test {\n public:\n  void SetUp() override;\n  void TearDown() override;\n\n protected:\n  folly::SocketAddress& getLocalAddress();\n  folly::SocketAddress& getRemoteAddress();\n  ssize_t sendDatagramUpstream(std::unique_ptr<folly::IOBuf> datagram);\n\n  /*\n   * Wrap private members of H3DatagramAsyncSocketTest so they can be called\n   * from tests\n   * */\n  void setUpstreamSession(proxygen::HQUpstreamSession* session) {\n    datagramSocket_->setUpstreamSession(session);\n  }\n  void onDatagram(std::unique_ptr<folly::IOBuf> datagram) {\n    datagramSocket_->onDatagram(std::move(datagram));\n  }\n  void connectError(quic::QuicError error) {\n    datagramSocket_->connectError(std::move(error));\n  }\n  void onHeadersComplete(std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {\n    datagramSocket_->onHeadersComplete(std::move(msg));\n  }\n  void onEOM() {\n    datagramSocket_->onEOM();\n  }\n\n  void SetUpRfcMode() {\n    options_.rfcMode_ = true;\n    // Recreate socket with rfcMode\n    datagramSocket_ =\n        std::make_unique<H3DatagramAsyncSocket>(&eventBase_, options_);\n    datagramSocket_->setUpstreamSession(session_);\n    datagramSocket_->setRcvBuf(kMaxDatagramsBufferedRead * kMaxDatagramSize);\n    datagramSocket_->setSndBuf(kMaxDatagramsBufferedWrite * kMaxDatagramSize);\n  }\n\n  folly::EventBase eventBase_;\n  std::unique_ptr<quic::MockQuicSocketDriver> socketDriver_;\n  std::unique_ptr<proxygen::H3DatagramAsyncSocket> datagramSocket_;\n  MockUDPReadCallback readCallbacks_;\n  proxygen::HQUpstreamSession* session_{nullptr};\n  proxygen::H3DatagramAsyncSocket::Options options_;\n  char buf_[kMaxDatagramSize];\n};\n\n// Parameterized fixture that runs tests in both legacy and RFC modes.\n// GetParam() == true → RFC mode; GetParam() == false → legacy mode.\nclass H3DatagramAsyncSocketModeTest\n    : public H3DatagramAsyncSocketTest\n    , public testing::WithParamInterface<bool> {\n public:\n  void SetUp() override {\n    H3DatagramAsyncSocketTest::SetUp();\n    if (GetParam()) {\n      SetUpRfcMode();\n    }\n  }\n};\n\n}; // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/test/MockAsyncTransportCertificate.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/io/async/AsyncTransportCertificate.h>\n#include <folly/io/async/ssl/OpenSSLTransportCertificate.h>\n#include <folly/portability/GMock.h>\n\nnamespace proxygen {\n\nclass MockAsyncTransportCertificate : public folly::AsyncTransportCertificate {\n public:\n  MOCK_METHOD(std::string, getIdentity, (), (const, override));\n  MOCK_METHOD(std::optional<std::string>, getDER, (), (const, override));\n};\n\nclass MockOpenSSLTransportCertificate\n    : public folly::OpenSSLTransportCertificate {\n public:\n  MOCK_METHOD(folly::ssl::X509UniquePtr, getX509, (), (const, override));\n  MOCK_METHOD(std::string, getIdentity, (), (const, override));\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/transport/test/PersistentFizzPskCacheTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/PersistentFizzPskCache.h>\n\n#include <fizz/protocol/test/CertUtil.h>\n#include <folly/FileUtil.h>\n#include <folly/portability/GTest.h>\n#include <wangle/client/persistence/test/TestUtil.h>\n\nusing namespace testing;\n\nnamespace proxygen::test {\n\nclass PersistentFizzPskCacheTest : public Test {\n public:\n  void SetUp() override {\n    file_ = wangle::getPersistentCacheFilename();\n    createCache();\n\n    std::shared_ptr<fizz::PeerCert> serverCert = fizz::test::getPeerCert(\n        fizz::test::createCert(\"server\", false, nullptr, fizz::KeyType::P256));\n\n    std::shared_ptr<fizz::PeerCert> clientCert = fizz::test::getPeerCert(\n        fizz::test::createCert(\"client\", false, nullptr, fizz::KeyType::P256));\n\n    psk1_.psk = \"PSK1\";\n    psk1_.secret = \"secret1\";\n    psk1_.type = fizz::PskType::Resumption;\n    psk1_.version = fizz::ProtocolVersion::tls_1_3;\n    psk1_.cipher = fizz::CipherSuite::TLS_AES_128_GCM_SHA256;\n    psk1_.group = fizz::NamedGroup::x25519;\n    psk1_.serverCert = serverCert;\n    psk1_.clientCert = clientCert;\n    psk1_.alpn = \"h2\";\n    psk1_.ticketAgeAdd = 0x11111111;\n    psk1_.ticketIssueTime = std::chrono::time_point<std::chrono::system_clock>(\n        std::chrono::milliseconds(1));\n    psk1_.ticketExpirationTime =\n        std::chrono::time_point<std::chrono::system_clock>(\n            std::chrono::seconds(2));\n    psk1_.ticketHandshakeTime =\n        std::chrono::time_point<std::chrono::system_clock>(\n            std::chrono::milliseconds(1));\n    psk1_.maxEarlyDataSize = 0;\n\n    psk2_.psk = \"PSK2\";\n    psk2_.secret = \"secret2\";\n    psk2_.type = fizz::PskType::Resumption;\n    psk2_.version = fizz::ProtocolVersion::tls_1_2;\n    psk2_.cipher = fizz::CipherSuite::TLS_AES_256_GCM_SHA384;\n    psk2_.group = folly::none;\n    psk2_.serverCert = serverCert;\n    psk2_.clientCert = nullptr;\n    psk2_.alpn = folly::none;\n    psk2_.ticketAgeAdd = 0x22222222;\n    psk2_.ticketIssueTime = std::chrono::time_point<std::chrono::system_clock>(\n        std::chrono::seconds(1507653827));\n    psk2_.ticketExpirationTime =\n        psk2_.ticketIssueTime + std::chrono::seconds(100);\n    psk2_.ticketHandshakeTime =\n        psk2_.ticketIssueTime - std::chrono::seconds(100);\n    psk2_.maxEarlyDataSize = 10000;\n  }\n\n  void TearDown() override {\n    cache_.reset();\n    unlink(file_.c_str());\n  }\n\n  void createCache() {\n    cache_.reset();\n    cache_ = std::make_unique<PersistentFizzPskCache>(\n        file_,\n        wangle::PersistentCacheConfig::Builder()\n            .setCapacity(50)\n            .setSyncInterval(std::chrono::seconds(1))\n            .build());\n  }\n\n  fizz::client::CachedPsk psk1_;\n  fizz::client::CachedPsk psk2_;\n  std::unique_ptr<PersistentFizzPskCache> cache_;\n  std::string file_;\n};\n\nstatic void expectMatch(const fizz::client::CachedPsk& a,\n                        const fizz::client::CachedPsk& b) {\n  EXPECT_EQ(a.psk, b.psk);\n  EXPECT_EQ(a.secret, b.secret);\n  EXPECT_EQ(a.type, b.type);\n  EXPECT_EQ(a.version, b.version);\n  EXPECT_EQ(a.cipher, b.cipher);\n  EXPECT_EQ(a.group, b.group);\n  EXPECT_EQ((a.serverCert.get() != nullptr), (b.serverCert.get() != nullptr));\n  if (!a.alpn) {\n    EXPECT_TRUE(!b.alpn);\n  } else {\n    EXPECT_EQ(*a.alpn, *b.alpn);\n  }\n  EXPECT_EQ(a.ticketAgeAdd, b.ticketAgeAdd);\n  EXPECT_EQ(a.ticketIssueTime, b.ticketIssueTime);\n  EXPECT_EQ(a.ticketExpirationTime, b.ticketExpirationTime);\n  EXPECT_EQ(a.ticketHandshakeTime, b.ticketHandshakeTime);\n  EXPECT_EQ(a.maxEarlyDataSize, b.maxEarlyDataSize);\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestInsert2Hosts) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  cache_->putPsk(\"something.com\", psk2_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  expectMatch(*cache_->getPsk(\"something.com\"), psk2_);\n  EXPECT_FALSE(cache_->getPsk(\"somethingelse.com\").has_value());\n\n  createCache();\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  expectMatch(*cache_->getPsk(\"something.com\"), psk2_);\n  EXPECT_FALSE(cache_->getPsk(\"somethingelse.com\").has_value());\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestOverwrite) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  cache_->putPsk(\"facebook.com\", psk2_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk2_);\n\n  createCache();\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk2_);\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestRemove) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  cache_->removePsk(\"facebook.com\");\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestCompletelyCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  cache_.reset();\n\n  folly::writeFile(std::string(\"HI!!!\"), file_.c_str());\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestSomewhatCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  cache_.reset();\n  auto config = wangle::PersistentCacheConfig::Builder()\n                    .setCapacity(20)\n                    .setSyncInterval(std::chrono::seconds(1))\n                    .build();\n  auto otherCache =\n      std::make_unique<wangle::FilePersistentCache<std::string, double>>(\n          file_, std::move(config));\n  otherCache->put(\"facebook.com\", 1.1111);\n  otherCache.reset();\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestEntryCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", psk1_);\n  cache_.reset();\n  auto config = wangle::PersistentCacheConfig::Builder()\n                    .setSyncInterval(std::chrono::seconds(1))\n                    .setCapacity(20)\n                    .build();\n  auto otherCache = std::make_unique<\n      wangle::FilePersistentCache<std::string, PersistentCachedPsk>>(\n      file_, std::move(config));\n  otherCache->put(\"facebook.com\",\n                  PersistentCachedPsk{.serialized = \"I'm a PSK?\", .uses = 2});\n  otherCache.reset();\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestTruncatedEntry) {\n  cache_.reset();\n  auto config = wangle::PersistentCacheConfig::Builder()\n                    .setSyncInterval(std::chrono::seconds(1))\n                    .setCapacity(20)\n                    .build();\n  auto otherCache = std::make_unique<\n      wangle::FilePersistentCache<std::string, PersistentCachedPsk>>(\n      file_, std::move(config));\n  std::string psk1Serialized;\n  fizz::Error err;\n  EXPECT_EQ(\n      serializePsk(\n          psk1Serialized, err, fizz::openssl::certificateSerializer(), psk1_),\n      fizz::Status::Success);\n  // Store PSK with last 12 characters (64 bits + 32 bits) truncated\n  otherCache->put(\"facebook.com\",\n                  PersistentCachedPsk{.serialized = psk1Serialized.substr(\n                                          0, psk1Serialized.size() - 12),\n                                      .uses = 2});\n  otherCache.reset();\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestTruncatedHandshakeTime) {\n  cache_.reset();\n  auto config = wangle::PersistentCacheConfig::Builder()\n                    .setSyncInterval(std::chrono::seconds(1))\n                    .setCapacity(20)\n                    .build();\n  auto otherCache = std::make_unique<\n      wangle::FilePersistentCache<std::string, PersistentCachedPsk>>(\n      file_, std::move(config));\n  std::string psk1Serialized;\n  fizz::Error err;\n  EXPECT_EQ(\n      serializePsk(\n          psk1Serialized, err, fizz::openssl::certificateSerializer(), psk1_),\n      fizz::Status::Success);\n  // Store PSK with last 12 characters (64 bits) truncated\n  otherCache->put(\"facebook.com\",\n                  PersistentCachedPsk{.serialized = psk1Serialized.substr(\n                                          0, psk1Serialized.size() - 8),\n                                      .uses = 2});\n  otherCache.reset();\n\n  createCache();\n  // Ought to succeed, handshake time should be assigned current time\n  auto before = std::chrono::system_clock::now();\n  auto psk = cache_->getPsk(\"facebook.com\");\n  auto after = std::chrono::system_clock::now();\n  EXPECT_TRUE(psk);\n  EXPECT_TRUE(psk->ticketHandshakeTime > before);\n  EXPECT_TRUE(psk->ticketHandshakeTime < after);\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestUnlimitedUses) {\n  cache_->setMaxPskUses(0);\n\n  cache_->putPsk(\"facebook.com\", psk1_);\n\n  for (size_t i = 0; i < 10; i++) {\n    expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  }\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestLimitedUses) {\n  cache_->setMaxPskUses(3);\n\n  cache_->putPsk(\"facebook.com\", psk1_);\n\n  for (size_t i = 0; i < 3; i++) {\n    expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  }\n\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestLimitedUsesSerialize) {\n  cache_->setMaxPskUses(3);\n\n  cache_->putPsk(\"facebook.com\", psk1_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n\n  createCache();\n  cache_->setMaxPskUses(3);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n  expectMatch(*cache_->getPsk(\"facebook.com\"), psk1_);\n\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestGetPskUses) {\n  cache_->setMaxPskUses(3);\n\n  EXPECT_EQ(folly::none, cache_->getPskUses(\"facebook.com\"));\n  cache_->putPsk(\"facebook.com\", psk1_);\n  EXPECT_EQ(0u, cache_->getPskUses(\"facebook.com\"));\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(1u, cache_->getPskUses(\"facebook.com\"));\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(2u, cache_->getPskUses(\"facebook.com\"));\n\n  createCache();\n  cache_->setMaxPskUses(3);\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(folly::none, cache_->getPskUses(\"facebook.com\"));\n}\n\nTEST_F(PersistentFizzPskCacheTest, TestPeekPsk) {\n  cache_->setMaxPskUses(5);\n\n  EXPECT_EQ(folly::none, cache_->getPskUses(\"facebook.com\"));\n  cache_->putPsk(\"facebook.com\", psk1_);\n  EXPECT_EQ(0u, cache_->getPskUses(\"facebook.com\"));\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(1u, cache_->getPskUses(\"facebook.com\"));\n\n  for (size_t i = 0; i < 10; i++) {\n    expectMatch(*cache_->peekPsk(\"facebook.com\"), psk1_);\n    EXPECT_EQ(1u, cache_->getPskUses(\"facebook.com\"));\n  }\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(2u, cache_->getPskUses(\"facebook.com\"));\n}\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/transport/test/PersistentQuicPskCacheTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/transport/PersistentQuicPskCache.h>\n\n#include <fizz/protocol/test/CertUtil.h>\n#include <folly/FileUtil.h>\n#include <folly/portability/GTest.h>\n#include <wangle/client/persistence/test/TestUtil.h>\n\nusing namespace testing;\nusing namespace quic;\n\nnamespace proxygen::test {\n\nclass PersistentQuicPskCacheTest : public Test {\n public:\n  void SetUp() override {\n    file_ = wangle::getPersistentCacheFilename();\n    createCache();\n\n    std::shared_ptr<fizz::PeerCert> serverCert = fizz::test::getPeerCert(\n        fizz::test::createCert(\"server\", false, nullptr, fizz::KeyType::P256));\n\n    std::shared_ptr<fizz::PeerCert> clientCert = fizz::test::getPeerCert(\n        fizz::test::createCert(\"client\", false, nullptr, fizz::KeyType::P256));\n\n    auto& fizzPsk1 = quicPsk1_.cachedPsk;\n    fizzPsk1.psk = \"PSK1\";\n    fizzPsk1.secret = \"secret1\";\n    fizzPsk1.type = fizz::PskType::Resumption;\n    fizzPsk1.version = fizz::ProtocolVersion::tls_1_3;\n    fizzPsk1.cipher = fizz::CipherSuite::TLS_AES_128_GCM_SHA256;\n    fizzPsk1.group = fizz::NamedGroup::x25519;\n    fizzPsk1.serverCert = serverCert;\n    fizzPsk1.clientCert = clientCert;\n    fizzPsk1.alpn = \"h2\";\n    fizzPsk1.ticketAgeAdd = 0x11111111;\n    fizzPsk1.ticketIssueTime =\n        std::chrono::time_point<std::chrono::system_clock>(\n            std::chrono::milliseconds(1));\n    fizzPsk1.ticketExpirationTime =\n        std::chrono::time_point<std::chrono::system_clock>(\n            std::chrono::seconds(2));\n    fizzPsk1.maxEarlyDataSize = 0;\n    quicPsk1_.transportParams.initialMaxStreamDataBidiLocal = 100;\n    quicPsk1_.transportParams.initialMaxStreamDataBidiRemote = 100;\n    quicPsk1_.transportParams.initialMaxStreamDataUni = 100;\n    quicPsk1_.transportParams.initialMaxData = 200;\n    quicPsk1_.transportParams.idleTimeout = 60;\n    quicPsk1_.transportParams.maxRecvPacketSize = 500;\n    quicPsk1_.transportParams.initialMaxStreamsBidi = 234;\n    quicPsk1_.transportParams.initialMaxStreamsUni = 123;\n    quicPsk1_.transportParams.knobFrameSupport = true;\n    quicPsk1_.transportParams.ackReceiveTimestampsEnabled = true;\n    quicPsk1_.transportParams.maxReceiveTimestampsPerAck = 30;\n    quicPsk1_.transportParams.receiveTimestampsExponent = 0;\n    quicPsk1_.transportParams.extendedAckFeatures = 3;\n    quicPsk1_.appParams = \"QPACK param\";\n\n    auto& fizzPsk2 = quicPsk2_.cachedPsk;\n    fizzPsk2.psk = \"PSK2\";\n    fizzPsk2.secret = \"secret2\";\n    fizzPsk2.type = fizz::PskType::Resumption;\n    fizzPsk2.version = fizz::ProtocolVersion::tls_1_2;\n    fizzPsk2.cipher = fizz::CipherSuite::TLS_AES_256_GCM_SHA384;\n    fizzPsk2.group = folly::none;\n    fizzPsk2.serverCert = serverCert;\n    fizzPsk2.clientCert = nullptr;\n    fizzPsk2.alpn = folly::none;\n    fizzPsk2.ticketAgeAdd = 0x22222222;\n    fizzPsk2.ticketIssueTime =\n        std::chrono::time_point<std::chrono::system_clock>(\n            std::chrono::seconds(1507653827));\n    fizzPsk2.ticketExpirationTime =\n        fizzPsk2.ticketIssueTime + std::chrono::seconds(100);\n    fizzPsk2.maxEarlyDataSize = 10000;\n    quicPsk2_.transportParams.initialMaxStreamDataBidiLocal = 400;\n    quicPsk2_.transportParams.initialMaxStreamDataBidiRemote = 400;\n    quicPsk2_.transportParams.initialMaxStreamDataUni = 400;\n    quicPsk2_.transportParams.initialMaxData = 800;\n    quicPsk2_.transportParams.idleTimeout = 90;\n    quicPsk2_.transportParams.maxRecvPacketSize = 800;\n    quicPsk2_.transportParams.initialMaxStreamsBidi = 2345;\n    quicPsk2_.transportParams.initialMaxStreamsUni = 1233;\n    quicPsk2_.transportParams.knobFrameSupport = false;\n    quicPsk2_.transportParams.ackReceiveTimestampsEnabled = false;\n    quicPsk2_.transportParams.maxReceiveTimestampsPerAck = 10;\n    quicPsk2_.transportParams.receiveTimestampsExponent = 0;\n    quicPsk2_.transportParams.extendedAckFeatures = 0;\n  }\n\n  void TearDown() override {\n    cache_.reset();\n    unlink(file_.c_str());\n  }\n\n  void createCache() {\n    cache_.reset();\n    cache_ = std::make_unique<PersistentQuicPskCache>(\n        file_,\n        wangle::PersistentCacheConfig::Builder()\n            .setCapacity(50)\n            .setSyncInterval(std::chrono::seconds(1))\n            .build());\n  }\n\n  QuicCachedPsk quicPsk1_;\n  QuicCachedPsk quicPsk2_;\n  std::unique_ptr<PersistentQuicPskCache> cache_;\n  std::string file_;\n};\n\nstatic void expectMatch(const QuicCachedPsk& a, const QuicCachedPsk& b) {\n  const auto& fizzPskA = a.cachedPsk;\n  const auto& fizzPskB = b.cachedPsk;\n  EXPECT_EQ(fizzPskA.psk, fizzPskB.psk);\n  EXPECT_EQ(fizzPskA.secret, fizzPskB.secret);\n  EXPECT_EQ(fizzPskA.type, fizzPskB.type);\n  EXPECT_EQ(fizzPskA.version, fizzPskB.version);\n  EXPECT_EQ(fizzPskA.cipher, fizzPskB.cipher);\n  EXPECT_EQ(fizzPskA.group, fizzPskB.group);\n  EXPECT_EQ((fizzPskA.serverCert.get() != nullptr),\n            (fizzPskB.serverCert.get() != nullptr));\n  if (!fizzPskA.alpn) {\n    EXPECT_TRUE(!fizzPskB.alpn);\n  } else {\n    EXPECT_EQ(*fizzPskA.alpn, *fizzPskB.alpn);\n  }\n  EXPECT_EQ(fizzPskA.ticketAgeAdd, fizzPskB.ticketAgeAdd);\n  EXPECT_EQ(fizzPskA.ticketIssueTime, fizzPskB.ticketIssueTime);\n  EXPECT_EQ(fizzPskA.ticketExpirationTime, fizzPskB.ticketExpirationTime);\n  EXPECT_EQ(fizzPskA.maxEarlyDataSize, fizzPskB.maxEarlyDataSize);\n\n  const auto& paramsA = a.transportParams;\n  const auto& paramsB = b.transportParams;\n  EXPECT_EQ(paramsA.initialMaxStreamDataBidiLocal,\n            paramsB.initialMaxStreamDataBidiLocal);\n  EXPECT_EQ(paramsA.initialMaxStreamDataBidiRemote,\n            paramsB.initialMaxStreamDataBidiRemote);\n  EXPECT_EQ(paramsA.initialMaxStreamDataUni, paramsB.initialMaxStreamDataUni);\n  EXPECT_EQ(paramsA.initialMaxData, paramsB.initialMaxData);\n  EXPECT_EQ(paramsA.idleTimeout, paramsB.idleTimeout);\n  EXPECT_EQ(paramsA.maxRecvPacketSize, paramsB.maxRecvPacketSize);\n  EXPECT_EQ(paramsA.initialMaxStreamsBidi, paramsB.initialMaxStreamsBidi);\n  EXPECT_EQ(paramsA.initialMaxStreamsUni, paramsB.initialMaxStreamsUni);\n  EXPECT_EQ(paramsA.knobFrameSupport, paramsB.knobFrameSupport);\n  EXPECT_EQ(paramsA.ackReceiveTimestampsEnabled,\n            paramsB.ackReceiveTimestampsEnabled);\n  EXPECT_EQ(paramsA.maxReceiveTimestampsPerAck,\n            paramsB.maxReceiveTimestampsPerAck);\n  EXPECT_EQ(paramsA.receiveTimestampsExponent,\n            paramsB.receiveTimestampsExponent);\n  EXPECT_EQ(paramsA.extendedAckFeatures, paramsB.extendedAckFeatures);\n\n  EXPECT_EQ(a.appParams, b.appParams);\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestInsert2Hosts) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  cache_->putPsk(\"something.com\", quicPsk2_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  expectMatch(*cache_->getPsk(\"something.com\"), quicPsk2_);\n  EXPECT_FALSE(cache_->getPsk(\"somethingelse.com\").has_value());\n\n  createCache();\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  expectMatch(*cache_->getPsk(\"something.com\"), quicPsk2_);\n  EXPECT_FALSE(cache_->getPsk(\"somethingelse.com\").has_value());\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestOverwrite) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  cache_->putPsk(\"facebook.com\", quicPsk2_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk2_);\n\n  createCache();\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk2_);\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestRemove) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  cache_->removePsk(\"facebook.com\");\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestCompletelyCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  cache_.reset();\n\n  folly::writeFile(std::string(\"HI!!!\"), file_.c_str());\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestSomewhatCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  cache_.reset();\n\n  auto otherCache =\n      std::make_unique<wangle::FilePersistentCache<std::string, double>>(\n          file_,\n          wangle::PersistentCacheConfig::Builder()\n              .setCapacity(20)\n              .setSyncInterval(std::chrono::seconds(1))\n              .build());\n  otherCache->put(\"facebook.com\", 1.1111);\n  otherCache.reset();\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestEntryCorruptedCache) {\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  cache_.reset();\n\n  auto otherCache = std::make_unique<\n      wangle::FilePersistentCache<std::string, PersistentQuicCachedPsk>>(\n      file_,\n      wangle::PersistentCacheConfig::Builder()\n          .setCapacity(20)\n          .setSyncInterval(std::chrono::seconds(1))\n          .build());\n  otherCache->put(\n      \"facebook.com\",\n      PersistentQuicCachedPsk{.fizzPsk = \"I'm a PSK?\", .quicParams = \"params\"});\n  otherCache.reset();\n\n  createCache();\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\"));\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestUnlimitedUses) {\n  cache_->setMaxPskUses(0);\n\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n\n  for (size_t i = 0; i < 10; i++) {\n    expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  }\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestLimitedUses) {\n  cache_->setMaxPskUses(3);\n\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n\n  for (size_t i = 0; i < 3; i++) {\n    expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  }\n\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestLimitedUsesSerialize) {\n  cache_->setMaxPskUses(3);\n\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n\n  createCache();\n  cache_->setMaxPskUses(3);\n\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n  expectMatch(*cache_->getPsk(\"facebook.com\"), quicPsk1_);\n\n  EXPECT_FALSE(cache_->getPsk(\"facebook.com\").has_value());\n}\n\nTEST_F(PersistentQuicPskCacheTest, TestGetPskUses) {\n  cache_->setMaxPskUses(3);\n\n  EXPECT_EQ(std::nullopt, cache_->getPskUses(\"facebook.com\"));\n  cache_->putPsk(\"facebook.com\", quicPsk1_);\n  EXPECT_EQ(0u, cache_->getPskUses(\"facebook.com\"));\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(1u, cache_->getPskUses(\"facebook.com\"));\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(2u, cache_->getPskUses(\"facebook.com\"));\n\n  createCache();\n  cache_->setMaxPskUses(3);\n\n  cache_->getPsk(\"facebook.com\");\n  EXPECT_EQ(std::nullopt, cache_->getPskUses(\"facebook.com\"));\n}\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/utils/AcceptorAddress.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n\n#include <folly/SocketAddress.h>\n\n#include <utility>\n\nnamespace proxygen {\n\nstruct AcceptorAddress {\n  enum class AcceptorType { TCP, UDP };\n\n  AcceptorAddress() = delete;\n  AcceptorAddress(folly::SocketAddress address, AcceptorType protocol)\n      : address(std::move(address)), protocol(protocol) {\n  }\n\n  folly::SocketAddress address;\n  AcceptorType protocol;\n};\n\ninline bool operator<(const AcceptorAddress& lv, const AcceptorAddress& rv) {\n  if (lv.address < rv.address) {\n    return true;\n  }\n  if (rv.address < lv.address) {\n    return false;\n  }\n  return lv.protocol < rv.protocol;\n}\n\ninline std::ostream& operator<<(std::ostream& os,\n                                const AcceptorAddress::AcceptorType& accType) {\n  switch (accType) {\n    case AcceptorAddress::AcceptorType::TCP:\n      os << \"TCP\";\n      break;\n    case AcceptorAddress::AcceptorType::UDP:\n      os << \"UDP\";\n      break;\n    default:\n      LOG(FATAL) << \"Unknown Acceptor type.\";\n  }\n  return os;\n}\n\ninline std::ostream& operator<<(std::ostream& os,\n                                const AcceptorAddress& accAddr) {\n  os << accAddr.address << \"<\" << accAddr.protocol << \">\";\n  return os;\n}\n\nusing AcceptorType = AcceptorAddress::AcceptorType;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/AsyncTimeoutSet.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/AsyncTimeoutSet.h>\n\n#include <folly/ScopeGuard.h>\n#include <folly/io/async/Request.h>\n\nusing std::chrono::milliseconds;\n\nnamespace proxygen {\n\nclass SimpleTimeoutClock : public AsyncTimeoutSet::TimeoutClock {\n public:\n  std::chrono::milliseconds millisecondsSinceEpoch() override {\n    return proxygen::millisecondsSinceEpoch();\n  }\n};\n\nAsyncTimeoutSet::TimeoutClock& getTimeoutClock() {\n  static SimpleTimeoutClock timeoutClock;\n\n  return timeoutClock;\n}\n\nAsyncTimeoutSet::Callback::~Callback() {\n  if (isScheduled()) {\n    cancelTimeout();\n  }\n}\n\nvoid AsyncTimeoutSet::Callback::setScheduled(AsyncTimeoutSet* timeoutSet,\n                                             Callback* prev) {\n  assert(timeoutSet_ == nullptr);\n  assert(prev_ == nullptr);\n  assert(next_ == nullptr);\n  assert(!timePointInitialized(expiration_));\n\n  timeoutSet_ = timeoutSet;\n  prev_ = prev;\n  next_ = nullptr;\n  expiration_ = timeoutSet->timeoutClock_.millisecondsSinceEpoch() +\n                timeoutSet_->getInterval();\n}\n\nvoid AsyncTimeoutSet::Callback::cancelTimeoutImpl() {\n  if (next_ == nullptr) {\n    assert(timeoutSet_->tail_ == this);\n    timeoutSet_->tail_ = prev_;\n  } else {\n    assert(timeoutSet_->tail_ != this);\n    next_->prev_ = prev_;\n  }\n\n  if (prev_ == nullptr) {\n    assert(timeoutSet_->head_ == this);\n    timeoutSet_->head_ = next_;\n    timeoutSet_->headChanged();\n  } else {\n    assert(timeoutSet_->head_ != this);\n    prev_->next_ = next_;\n  }\n\n  timeoutSet_ = nullptr;\n  prev_ = nullptr;\n  next_ = nullptr;\n  expiration_ = {};\n}\n\nAsyncTimeoutSet::AsyncTimeoutSet(folly::TimeoutManager* timeoutManager,\n                                 milliseconds intervalMS,\n                                 milliseconds atMostEveryN,\n                                 TimeoutClock* timeoutClock)\n    : folly::AsyncTimeout(timeoutManager),\n      timeoutClock_(timeoutClock ? *timeoutClock : getTimeoutClock()),\n      head_(nullptr),\n      tail_(nullptr),\n      interval_(intervalMS),\n      atMostEveryN_(atMostEveryN) {\n}\n\nAsyncTimeoutSet::AsyncTimeoutSet(folly::TimeoutManager* timeoutManager,\n                                 InternalEnum internal,\n                                 milliseconds intervalMS,\n                                 milliseconds atMostEveryN)\n    : folly::AsyncTimeout(timeoutManager, internal),\n      timeoutClock_(getTimeoutClock()),\n      head_(nullptr),\n      tail_(nullptr),\n      interval_(intervalMS),\n      atMostEveryN_(atMostEveryN) {\n}\n\nAsyncTimeoutSet::~AsyncTimeoutSet() {\n  // DelayedDestruction should ensure that we are never destroyed while inside\n  // a call to timeoutExpired().\n  assert(!inTimeoutExpired_);\n\n  // destroy() should have already cleared out the timeout list.\n  // It's a bug if anyone tries to keep using the AsyncTimeoutSet after\n  // calling destroy, so no new timeouts may have been scheduled since then.\n  assert(head_ == nullptr);\n  assert(tail_ == nullptr);\n}\n\nvoid AsyncTimeoutSet::destroy() {\n  // If there are any timeout callbacks pending, get rid of them without ever\n  // invoking them.  This is somewhat undesirable from the callback's\n  // perspective (how is it supposed to know that it will never get invoked?).\n  // Most users probably only want to destroy a AsyncTimeoutSet when it has no\n  // callbacks remaining.  Otherwise they need to implement their own code to\n  // take care of cleaning up the callbacks that will never be invoked.\n\n  // cancel from tail to head, to avoid extra calls to headChanged\n  while (tail_ != nullptr) {\n    tail_->cancelTimeout();\n  }\n\n  DelayedDestruction::destroy();\n}\n\nvoid AsyncTimeoutSet::scheduleTimeout(Callback* callback) {\n  // Cancel the callback if it happens to be scheduled already.\n  callback->cancelTimeout();\n  assert(callback->prev_ == nullptr);\n  assert(callback->next_ == nullptr);\n\n  callback->context_ = folly::RequestContext::saveContext();\n\n  Callback* old_tail = tail_;\n  if (head_ == nullptr) {\n    // We don't have any timeouts scheduled already.  We have to schedule\n    // ourself.\n    assert(tail_ == nullptr);\n    assert(!isScheduled());\n    if (!inTimeoutExpired_) {\n      this->folly::AsyncTimeout::scheduleTimeout(interval_.count());\n    }\n    head_ = callback;\n    tail_ = callback;\n  } else {\n    assert(inTimeoutExpired_ || isScheduled());\n    assert(tail_->next_ == nullptr);\n    tail_->next_ = callback;\n    tail_ = callback;\n  }\n\n  // callback->prev_ = tail_;\n  callback->setScheduled(this, old_tail);\n}\n\nvoid AsyncTimeoutSet::headChanged() {\n  if (inTimeoutExpired_) {\n    // timeoutExpired() will always update the scheduling correctly before it\n    // returns.  No need to change the state now, since we are just going to\n    // change it again later.\n    return;\n  }\n\n  if (!head_) {\n    this->folly::AsyncTimeout::cancelTimeout();\n  } else {\n    milliseconds delta =\n        head_->getTimeRemaining(timeoutClock_.millisecondsSinceEpoch());\n    this->folly::AsyncTimeout::scheduleTimeout(delta.count());\n  }\n}\n\nvoid AsyncTimeoutSet::timeoutExpired() noexcept {\n  // If destroy() is called inside timeoutExpired(), delay actual destruction\n  // until timeoutExpired() returns\n  DestructorGuard dg(this);\n\n  // timeoutExpired() can only be invoked directly from the event base loop.\n  // It should never be invoked recursively.\n  //\n  // Set inTimeoutExpired_ to true, so that we won't bother rescheduling the\n  // main AsyncTimeout inside timeoutExpired().  We'll always make sure this\n  // is up-to-date before we return.  This simply prevents us from\n  // unnecessarily modifying the main timeout heap multiple times before we\n  // return.\n  assert(!inTimeoutExpired_);\n  inTimeoutExpired_ = true;\n  SCOPE_EXIT {\n    inTimeoutExpired_ = false;\n  };\n\n  // Get the current time.\n  // For now we only compute the current time at the start of the loop.\n  // If a callback takes a very long time to execute its timeoutExpired()\n  // method, this value could potentially get stale.\n  //\n  // However, this should be rare, and it doesn't seem worth the overhead of\n  // recomputing the current time each time around the loop.  If the value does\n  // go stale, we won't invoke as many callbacks as we could.  They will have\n  // to wait until the next call to timeoutExpired().  However, we could also\n  // end up rescheduling the next timeoutExpired() call a bit late if now gets\n  // stale.  If we find that this becomes a problem in practice we could be\n  // more smart about when we recompute the current time.\n  auto now = timeoutClock_.millisecondsSinceEpoch();\n\n  while (head_ != nullptr) {\n    milliseconds delta = head_->getTimeRemaining(now);\n    if (delta > milliseconds(0)) {\n      if (delta < atMostEveryN_) {\n        delta = atMostEveryN_;\n      }\n      this->folly::AsyncTimeout::scheduleTimeout(delta.count());\n      break;\n    }\n\n    // Remember the callback to invoke, since calling cancelTimeout()\n    // on it will modify head_.\n    Callback* cb = head_;\n    head_->cancelTimeout();\n    folly::RequestContextScopeGuard rctxScopeGuard(cb->context_);\n    cb->timeoutExpired();\n  }\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/AsyncTimeoutSet.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <cstddef>\n#include <folly/io/async/AsyncTimeout.h>\n#include <folly/io/async/DelayedDestruction.h>\n#include <folly/io/async/TimeoutManager.h>\n#include <memory>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\n/**\n * AsyncTimeoutSet exists for efficiently managing a group of timeouts events\n * that always have the same timeout interval.\n *\n * AsyncTimeoutSet takes advantage of the fact that the timeouts are always\n * scheduled in sorted order.  (Since each timeout has the same interval, when\n * a new timeout is scheduled it will always be the last timeout in the set.)\n * This avoids the need to perform any additional sorting of the timeouts\n * within a single AsyncTimeoutSet.\n *\n * AsyncTimeoutSet is useful whenever you have a large group of objects that\n * each need their own timeout, but with the same interval for each object.\n * For example, managing idle timeouts for thousands of connection, or\n * scheduling health checks for a large group of servers.\n *\n * Note, this class may not be needed given libevent's\n * event_base_init_common_timeout(). We should look into using that.\n */\nclass AsyncTimeoutSet\n    : private folly::AsyncTimeout\n    , public folly::DelayedDestruction {\n public:\n  using UniquePtr = std::unique_ptr<AsyncTimeoutSet, Destructor>;\n\n  /**\n   * A callback to be notified when a timeout has expired.\n   *\n   * AsyncTimeoutSet::Callback is very similar to AsyncTimeout.  The primary\n   * distinction is that AsyncTimeout can choose its timeout interval each\n   * time it is scheduled.  On the other hand, AsyncTimeoutSet::Callback\n   * always uses the timeout interval defined by the AsyncTimeoutSet where it\n   * is scheduled.\n   */\n  class Callback {\n   public:\n    Callback() = default;\n\n    virtual ~Callback();\n\n    /**\n     * timeoutExpired() is invoked when the timeout has expired.\n     */\n    virtual void timeoutExpired() noexcept = 0;\n\n    /**\n     * Cancel the timeout, if it is running.\n     *\n     * If the timeout is not scheduled, cancelTimeout() does nothing.\n     */\n    void cancelTimeout() {\n      if (timeoutSet_ == nullptr) {\n        // We're not scheduled, so there's nothing to do.\n        return;\n      }\n      cancelTimeoutImpl();\n    }\n\n    /**\n     * Return true if this timeout is currently scheduled, and false otherwise.\n     */\n    [[nodiscard]] bool isScheduled() const {\n      return timeoutSet_ != nullptr;\n    }\n\n   private:\n    // Get the time remaining until this timeout expires\n    [[nodiscard]] std::chrono::milliseconds getTimeRemaining(\n        std::chrono::milliseconds now) const {\n      if (now >= expiration_) {\n        return std::chrono::milliseconds(0);\n      }\n      return expiration_ - now;\n    }\n\n    void setScheduled(AsyncTimeoutSet* timeoutSet, Callback* prev);\n    void cancelTimeoutImpl();\n\n    std::shared_ptr<folly::RequestContext> context_;\n\n    AsyncTimeoutSet* timeoutSet_{nullptr};\n    Callback* prev_{nullptr};\n    Callback* next_{nullptr};\n    std::chrono::milliseconds expiration_{0};\n\n    // Give AsyncTimeoutSet direct access to our members so it can take care\n    // of scheduling/cancelling.\n    friend class AsyncTimeoutSet;\n  };\n\n  /**\n   * Clock interface.  Can be used for different time implementations (eg:\n   * monotonic, system) or mocking.\n   */\n  class TimeoutClock {\n   public:\n    TimeoutClock() = default;\n\n    virtual ~TimeoutClock() = default;\n\n    virtual std::chrono::milliseconds millisecondsSinceEpoch() = 0;\n  };\n\n  /**\n   * Create a new AsyncTimeoutSet with the specified interval.\n   *\n   * If timeout clock is unspecified, it will use the default (system clock)\n   */\n  AsyncTimeoutSet(\n      folly::TimeoutManager* timeoutManager,\n      std::chrono::milliseconds intervalMS,\n      std::chrono::milliseconds atMostEveryN = std::chrono::milliseconds(0),\n      TimeoutClock* timeoutClock = nullptr);\n\n  /**\n   * Create a new AsyncTimeoutSet with the given 'internal' settting. For\n   * details on what the InternalEnum specifies, see the documentation in\n   * AsyncTimeout.h\n   */\n  AsyncTimeoutSet(\n      folly::TimeoutManager* timeoutManager,\n      InternalEnum internal,\n      std::chrono::milliseconds intervalMS,\n      std::chrono::milliseconds atMostEveryN = std::chrono::milliseconds(0));\n\n  /**\n   * Destroy the AsyncTimeoutSet.\n   *\n   * Normally a AsyncTimeoutSet should only be destroyed when there are no\n   * more callbacks pending in the set.  If there are timeout callbacks pending\n   * for this set, destroying the AsyncTimeoutSet will automatically cancel\n   * them.  If you destroy a AsyncTimeoutSet with callbacks pending, your\n   * callback code needs to be aware that the callbacks will never be invoked.\n   */\n  void destroy() override;\n\n  /**\n   * Get the interval for this AsyncTimeoutSet.\n   *\n   * Returns the timeout interval in milliseconds.  All callbacks scheduled\n   * with scheduleTimeout() will be invoked after this amount of time has\n   * passed since the call to scheduleTimeout().\n   */\n  [[nodiscard]] std::chrono::milliseconds getInterval() const {\n    return interval_;\n  }\n\n  /**\n   * Schedule the specified Callback to be invoked after the AsyncTimeoutSet's\n   * specified timeout interval.\n   *\n   * If the callback is already scheduled, this cancels the existing timeout\n   * before scheduling the new timeout.\n   */\n  void scheduleTimeout(Callback* callback);\n\n  /**\n   * Limit how frequently this AsyncTimeoutSet will fire.\n   */\n  void fireAtMostEvery(const std::chrono::milliseconds& ms) {\n    atMostEveryN_ = ms;\n  }\n\n  /**\n   * Get a pointer to the next Callback scheduled to be invoked (may be null).\n   */\n  Callback* front() {\n    return head_;\n  }\n  [[nodiscard]] const Callback* front() const {\n    return head_;\n  }\n\n protected:\n  /**\n   * Protected destructor.\n   *\n   * Use destroy() instead.  See the comments in DelayedDestruction for more\n   * details.\n   */\n  ~AsyncTimeoutSet() override;\n\n private:\n  // Forbidden copy constructor and assignment operator\n  AsyncTimeoutSet(AsyncTimeoutSet const&) = delete;\n  AsyncTimeoutSet& operator=(AsyncTimeoutSet const&) = delete;\n\n  // Private methods to be invoked by AsyncTimeoutSet::Callback\n  void headChanged();\n\n  // Methods inherited from AsyncTimeout\n  void timeoutExpired() noexcept override;\n\n  TimeoutClock& timeoutClock_;\n  Callback* head_;\n  Callback* tail_;\n  std::chrono::milliseconds interval_;\n  std::chrono::milliseconds atMostEveryN_;\n  bool inTimeoutExpired_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Auto-generated by proxygen/facebook/generate_cmake.py - DO NOT EDIT MANUALLY\n\nproxygen_add_library(proxygen_utils_headers\n  EXPORTED_DEPS\n    Folly::folly_conv\n    Folly::folly_function\n    Folly::folly_io_async_async_base\n    Folly::folly_io_iobuf\n    Folly::folly_memory\n    Folly::folly_optional\n    Folly::folly_portability_sys_resource\n    Folly::folly_portability_time\n    Folly::folly_random\n    Folly::folly_range\n    Folly::folly_string\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_cob_helper\n  EXPORTED_DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_crypt\n  SRCS\n    CryptUtil.cpp\n  DEPS\n    Folly::folly_portability_openssl\n    ${OPENSSL_LIBRARIES}\n  EXPORTED_DEPS\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_utils_exception\n  SRCS\n    Exception.cpp\n  EXPORTED_DEPS\n    proxygen_error\n    Folly::folly_conv\n)\n\nproxygen_add_library(proxygen_utils_filter_chain\n  EXPORTED_DEPS\n    Folly::folly_function\n    Folly::folly_memory\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_logging\n  SRCS\n    Logging.cpp\n  DEPS\n    Folly::folly_experimental_symbolizer_symbolizer\n    Folly::folly_format\n    Folly::folly_singleton\n    Folly::folly_string\n  EXPORTED_DEPS\n    Folly::folly_io_iobuf\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_utils_export)\n\nproxygen_add_library(proxygen_utils_util_inl\n  EXPORTED_DEPS\n    Folly::folly_portability_windows\n    Folly::folly_range\n)\n\nproxygen_add_library(proxygen_utils_sockopts\n  EXPORTED_DEPS\n    wangle::wangle_acceptor\n    Folly::folly_conv\n    Folly::folly_function\n    Folly::folly_io_async_async_base\n    Folly::folly_io_iobuf\n    Folly::folly_memory\n    Folly::folly_optional\n    Folly::folly_portability_sys_resource\n    Folly::folly_portability_time\n    Folly::folly_random\n    Folly::folly_range\n    Folly::folly_string\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_state_machine\n  EXPORTED_DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_test_utils\n  EXPORTED_DEPS\n    Folly::folly_io_iobuf\n    Folly::folly_portability_sys_resource\n    Folly::folly_random\n    Folly::folly_range\n    Folly::folly_string\n)\n\nproxygen_add_library(proxygen_utils_time\n  SRCS\n    HTTPTime.cpp\n  DEPS\n    Folly::folly_portability_time\n  EXPORTED_DEPS\n    Folly::folly_optional\n)\n\nproxygen_add_library(proxygen_utils_parse_url\n  SRCS\n    ParseURL.cpp\n  DEPS\n    proxygen_external_http_parser\n    proxygen_utils_util_inl\n    Folly::folly_portability_sockets\n  EXPORTED_DEPS\n    proxygen_utils_export\n    Folly::folly_portability_windows\n    Folly::folly_string\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_timeoutset\n  SRCS\n    AsyncTimeoutSet.cpp\n  DEPS\n    Folly::folly_io_async_request_context\n    Folly::folly_scope_guard\n  EXPORTED_DEPS\n    proxygen_utils_time_util\n    Folly::folly_io_async_async_base\n    Folly::folly_io_async_delayed_destruction\n)\n\nproxygen_add_library(proxygen_utils_time_util\n  EXPORTED_DEPS\n    Folly::folly_portability_time\n)\n\nproxygen_add_library(proxygen_utils_trace\n  SRCS\n    TraceEvent.cpp\n    TraceEventContext.cpp\n  DEPS\n    Folly::folly_json_dynamic\n  EXPORTED_DEPS\n    proxygen_utils_exception\n    proxygen_utils_export\n    proxygen_utils_time_util\n    Folly::folly_conv\n    Folly::folly_lang_exception\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_compression_filter_utils\n  EXPORTED_DEPS\n    proxygen_http_http_headers\n    proxygen_http_message\n    proxygen_utils_stream_compressor\n    proxygen_utils_zlib_stream_compressor\n    proxygen_utils_zstd_stream_compressor\n)\n\nproxygen_add_library(proxygen_utils_stream_compressor)\n\nproxygen_add_library(proxygen_utils_stream_decompressor)\n\nproxygen_add_library(proxygen_utils_zlib_stream\n  EXPORTED_DEPS\n    proxygen_utils_zlib_stream_compressor\n    proxygen_utils_zlib_stream_decompressor\n)\n\nproxygen_add_library(proxygen_utils_zlib_stream_decompressor\n  SRCS\n    ZlibStreamDecompressor.cpp\n  DEPS\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_utils_stream_decompressor\n    ZLIB::ZLIB\n)\n\nproxygen_add_library(proxygen_utils_zlib_stream_compressor\n  SRCS\n    ZlibStreamCompressor.cpp\n  DEPS\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_utils_stream_compressor\n    proxygen_utils_zlib_stream_decompressor\n    Folly::folly_portability_gflags\n    ZLIB::ZLIB\n)\n\nproxygen_add_library(proxygen_utils_zstd_stream_compressor\n  SRCS\n    ZstdStreamCompressor.cpp\n  EXPORTED_DEPS\n    proxygen_utils_stream_compressor\n    Folly::folly_compression_compression\n)\n\nproxygen_add_library(proxygen_utils_zstd_stream_decompressor\n  SRCS\n    ZstdStreamDecompressor.cpp\n  DEPS\n    Folly::folly_io_iobuf\n  EXPORTED_DEPS\n    proxygen_utils_stream_decompressor\n    Folly::folly_memory\n    ${ZSTD_LIBRARIES}\n)\n\nproxygen_add_library(proxygen_utils_rendezvous_hash\n  SRCS\n    RendezvousHash.cpp\n  DEPS\n    Folly::folly_hash_hash\n)\n\nproxygen_add_library(proxygen_utils_shared_wheel_timer\n  SRCS\n    WheelTimerInstance.cpp\n  DEPS\n    Folly::folly_io_async_event_base_manager\n    Folly::folly_singleton\n  EXPORTED_DEPS\n    Folly::folly_io_async_async_base\n)\n\nproxygen_add_library(proxygen_utils_null_trace_event_observer\n  EXPORTED_DEPS\n    proxygen_utils_trace\n)\n\nproxygen_add_library(proxygen_utils_acceptor_address\n  EXPORTED_DEPS\n    Folly::folly_network_address\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_perfect_index_map\n  EXPORTED_DEPS\n    proxygen_utils_util_inl\n    Folly::folly_fbvector\n    Folly::folly_optional\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_conditional_gate\n  EXPORTED_DEPS\n    Folly::folly_function\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_weak_ref_counted_ptr\n  EXPORTED_DEPS\n    glog::glog\n)\n\nproxygen_add_library(proxygen_utils_safe_path\n  SRCS\n    SafePathUtils.cpp\n  DEPS\n    Folly::folly_container_foreach\n    Folly::folly_portability_stdlib\n    Folly::folly_string\n    fmt::fmt\n  EXPORTED_DEPS\n    Folly::folly_exception_wrapper\n    Folly::folly_expected\n)\n\nif(BUILD_TESTS)\n  add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "proxygen/lib/utils/CobHelper.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <atomic>\n#include <functional>\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\n/**\n * A helper class to schedule N async jobs in (possibly) different threads\n * and invoke a final callback in a given thread once all are done.\n */\nclass CobHelper {\n public:\n  CobHelper(size_t itemsLeft,\n            const std::function<void()>& cob,\n            const std::function<void(const std::exception&)>& ecob)\n      : itemsLeft_(itemsLeft), cob_(cob), ecob_(ecob) {\n  }\n\n  void setError(const std::string& emsg) {\n    CHECK(!emsg.empty());\n    emsg_ = emsg;\n  }\n\n  void workerDone() {\n    uint32_t oldValue = itemsLeft_.fetch_sub(1);\n    if (oldValue != 1) {\n      return;\n    }\n\n    allDone();\n  }\n\n private:\n  void allDone() {\n    if (!emsg_.empty()) {\n      ecob_(std::runtime_error(emsg_));\n    } else {\n      cob_();\n    }\n\n    delete this;\n  }\n\n  std::atomic<uint32_t> itemsLeft_;\n  std::string emsg_;\n  std::function<void()> cob_;\n  std::function<void(const std::exception&)> ecob_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/CompressionFilterUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/http/HTTPMessage.h>\n#include <proxygen/lib/http/RFC2616.h>\n#include <proxygen/lib/utils/StreamCompressor.h>\n#include <proxygen/lib/utils/ZlibStreamCompressor.h>\n#include <proxygen/lib/utils/ZstdStreamCompressor.h>\n\nnamespace proxygen {\n\nclass CompressionFilterUtils {\n public:\n  struct FactoryOptions {\n    FactoryOptions() = default;\n    uint32_t minimumCompressionSize = 1000;\n    std::shared_ptr<const std::set<std::string>> compressibleContentTypes;\n    int32_t zlibCompressionLevel = 4;\n    int32_t zstdCompressionLevel = 8;\n    bool enableZstd = false;\n    bool independentChunks = false;\n    bool enableGzip = true;\n  };\n\n  using StreamCompressorFactory =\n      std::function<std::unique_ptr<StreamCompressor>()>;\n\n  struct FilterParams {\n    uint32_t minimumCompressionSize;\n    StreamCompressorFactory compressorFactory;\n    std::string headerEncoding;\n    std::shared_ptr<const std::set<std::string>> compressibleContentTypes;\n  };\n\n  static folly::Optional<FilterParams> getFilterParams(\n      const HTTPMessage& msg, const FactoryOptions& options) {\n    switch (\n        determineCompressionType(msg, options.enableZstd, options.enableGzip)) {\n      case CodecType::ZLIB:\n        return FilterParams{\n            .minimumCompressionSize = options.minimumCompressionSize,\n            .compressorFactory = [level = options.zlibCompressionLevel]()\n                -> std::unique_ptr<StreamCompressor> {\n              return std::make_unique<ZlibStreamCompressor>(\n                  proxygen::CompressionType::GZIP, level);\n            },\n            .headerEncoding = \"gzip\",\n            .compressibleContentTypes = options.compressibleContentTypes};\n      case CodecType::ZSTD:\n        return FilterParams{\n            .minimumCompressionSize = options.minimumCompressionSize,\n            .compressorFactory = [level = options.zstdCompressionLevel,\n                                  independent = options.independentChunks]()\n                -> std::unique_ptr<StreamCompressor> {\n              return std::make_unique<ZstdStreamCompressor>(level, independent);\n            },\n            .headerEncoding = \"zstd\",\n            .compressibleContentTypes = options.compressibleContentTypes};\n      case CodecType::NO_COMPRESSION:\n        return folly::none;\n    }\n    folly::assume_unreachable();\n  }\n\n  // Filter helpers\n\n  static bool shouldCompress(const HTTPMessage& msg,\n                             const FilterParams& params) {\n    // Skip if it is already compressed\n    auto alreadyCompressed =\n        !msg.getHeaders()\n             .getSingleOrEmpty(HTTP_HEADER_CONTENT_ENCODING)\n             .empty();\n\n    // Make final determination of whether to compress\n    return !alreadyCompressed && isCompressibleContentType(msg, params) &&\n           (msg.getIsChunked() || isMinimumCompressibleSize(msg, params));\n  }\n\n  // Verify the response is large enough to compress\n  static bool isMinimumCompressibleSize(const HTTPMessage& msg,\n                                        const FilterParams& params) {\n    auto contentLengthHeader =\n        msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH);\n\n    uint32_t contentLength = 0;\n    if (!contentLengthHeader.empty()) {\n      contentLength = folly::to<uint32_t>(contentLengthHeader);\n    }\n\n    return contentLength >= params.minimumCompressionSize;\n  }\n\n  // Check the response's content type against a list of compressible types\n  static bool isCompressibleContentType(const HTTPMessage& msg,\n                                        const FilterParams& params) {\n\n    auto responseContentType =\n        msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE);\n    folly::toLowerAscii(responseContentType);\n\n    // Handle  text/html; encoding=utf-8 case\n    auto parameter_idx = responseContentType.find(';');\n    if (parameter_idx != std::string::npos) {\n      responseContentType = responseContentType.substr(0, parameter_idx);\n    }\n\n    if (params.compressibleContentTypes == nullptr) {\n      return false;\n    }\n    auto it = params.compressibleContentTypes->find(responseContentType);\n    return it != params.compressibleContentTypes->end();\n  }\n\n  enum class CodecType : uint8_t {\n    NO_COMPRESSION = 0,\n    ZLIB = 1,\n    ZSTD = 2,\n  };\n\n  static CodecType determineCompressionType(\n      folly::StringPiece acceptEncodingHeader,\n      bool enableZstd,\n      bool enableGzip) noexcept {\n    RFC2616::TokenPairVec output;\n    // Accept encoding header could have qvalues (gzip; q=5.0)\n    if (!RFC2616::parseQvalues(acceptEncodingHeader, output)) {\n      return CodecType::NO_COMPRESSION;\n    }\n\n    auto it = std::find_if(\n        output.begin(),\n        output.end(),\n        [enableZstd, enableGzip](RFC2616::TokenQPair elem) {\n          return (enableGzip &&\n                  elem.first.compare(folly::StringPiece(\"gzip\")) == 0) ||\n                 (enableZstd &&\n                  elem.first.compare(folly::StringPiece(\"zstd\")) == 0);\n        });\n\n    if (it == output.end()) {\n      return CodecType::NO_COMPRESSION;\n    }\n    if (it->first == \"zstd\") {\n      return CodecType::ZSTD;\n    } else if (it->first == \"gzip\") {\n      return CodecType::ZLIB;\n    } else {\n      DCHECK(false) << \"found unexpected content-coding selection\";\n      return CodecType::NO_COMPRESSION;\n    }\n  }\n\n private:\n  static CodecType determineCompressionType(const HTTPMessage& msg,\n                                            bool enableZstd,\n                                            bool enableGzip) noexcept {\n    auto acceptEncodingHeader =\n        msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_ACCEPT_ENCODING);\n    return determineCompressionType(\n        acceptEncodingHeader, enableZstd, enableGzip);\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ConditionalGate.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <bitset>\n#include <folly/Function.h>\n#include <glog/logging.h>\n#include <ostream>\n#include <vector>\n\nnamespace proxygen {\n\n/**\n * ConditionalGate is used to gate the execution of a function or functions\n * on one or more conditions that must be satisfied first.  It's logically\n * equivalent to using promise/future, but without allocation overhead, and\n * designed for single threaded use cases.\n *\n * enum class Things { Thing1, Thing2 };\n *\n * ConditionalGate<Things, 2> thingsDone;\n *\n * thingsDone.then([] { std::cout << \"Everything is done\"; });\n *\n * thingsDone.set(Things::Thing1);\n * thingsDone.set(Things::Thing2);\n *\n */\ntemplate <typename E, size_t N>\nclass ConditionalGate {\n  static_assert(std::is_enum<E>(), \"ConditionalGate type must be enum\");\n  static_assert(N > 0, \"N must be greater than 0\");\n\n public:\n  /* Return where this gate is open */\n  [[nodiscard]] bool allConditionsMet() const {\n    return conditions_.all();\n  }\n\n  /* Run the function f when all conditions in this gate are true */\n  void then(folly::Function<void()> f) {\n    if (conditions_.all()) {\n      f();\n    } else {\n      functions_.emplace_back(std::move(f));\n    }\n  }\n\n  /* Set the condition to true */\n  void set(E e) {\n    set(static_cast<size_t>(e));\n  }\n  void set(size_t i = 0) {\n    CHECK_LT(i, conditions_.size());\n    CHECK(!conditions_[i]);\n    conditions_[i] = true;\n    if (conditions_.all()) {\n      invoke();\n    }\n  }\n\n  /* Get the current state of the condition */\n  bool get(E e) const {\n    return get(static_cast<size_t>(e));\n  }\n  [[nodiscard]] bool get(size_t i = 0) const {\n    CHECK_LT(i, conditions_.size());\n    return conditions_[i];\n  }\n\n  void describe(std::ostream& os) const {\n    for (size_t i = 0; i < conditions_.size(); i++) {\n      os << i << \"=\" << static_cast<uint8_t>(conditions_[i]) << \" \";\n    }\n  }\n\n private:\n  void invoke() {\n    auto functions = std::move(functions_);\n    for (auto& f : functions) {\n      f();\n    }\n  }\n\n  std::bitset<N> conditions_;\n  std::vector<folly::Function<void()>> functions_;\n};\n\ntemplate <typename E, size_t N>\ninline std::ostream& operator<<(std::ostream& os,\n                                const ConditionalGate<E, N>& g) {\n  g.describe(os);\n  return os;\n}\n\nnamespace detail {\nenum class ReadyEnum { Ready = 0 };\n}\n/**\n * ReadyGate is a simple specialization of ConditionalGate with a single\n * condition.\n *\n * ReadyGate thingReady_;\n\n * thingReady_.then([] { std::cout << \"It's ready!\"; });\n *\n * thingReady_.set();\n */\nusing ReadyGate = ConditionalGate<detail::ReadyEnum, 1>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ConsistentHash.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n\nclass ConsistentHash {\n public:\n  virtual ~ConsistentHash() = default;\n\n  /**\n   * build() builds the hashing pool based on a vector of nodes with their keys\n   * and weights.\n   *\n   * The bevahior of calling build multiple times is undefined.\n   *\n   * build() is not thread safe with get(), documented below.\n   */\n  virtual void build(std::vector<std::pair<std::string, uint64_t>> &) = 0;\n\n  /**\n   * get(key, N) finds the node ranked N in the consistent hashing space\n   * for the given key.\n   *\n   * The returning value is the node's index in the input vector of build().\n   */\n  [[nodiscard]] virtual size_t get(const uint64_t key,\n                                   const size_t rank = 0) const = 0;\n\n  /**\n   * get max error rate the current hashing space\n   *\n   */\n  [[nodiscard]] virtual double getMaxErrorRate() const = 0;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/CryptUtil.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/CryptUtil.h>\n\n#include <folly/portability/OpenSSL.h>\n#include <iomanip>\n#include <openssl/buffer.h>\n#include <openssl/md5.h>\n#include <sstream>\n\nnamespace proxygen {\n\n// Base64 encode using openssl\nstd::string base64Encode(folly::ByteRange text) {\n  std::string result;\n  BIO *b64 = BIO_new(BIO_f_base64());\n  if (b64 == nullptr) {\n    return result;\n  }\n  BIO *bmem = BIO_new(BIO_s_mem());\n  if (bmem == nullptr) {\n    BIO_free_all(b64);\n    return result;\n  }\n  BUF_MEM *bptr;\n\n  // chain base64 filter with the memory buffer\n  // so that text will be encoded by base64 and flushed to buffer\n  BIO *chain = BIO_push(b64, bmem);\n  if (chain == nullptr) {\n    BIO_free_all(b64);\n    return result;\n  }\n  BIO_set_flags(chain, BIO_FLAGS_BASE64_NO_NL);\n  BIO_write(chain, text.begin(), text.size());\n  if (BIO_flush(chain) != 1) {\n    BIO_free_all(chain);\n    return result;\n  }\n\n  BIO_get_mem_ptr(chain, &bptr);\n\n  if (bptr && bptr->length > 0) {\n    result = std::string((char *)bptr->data, bptr->length);\n  }\n\n  // free the whole BIO chain (b64 and mem)\n  BIO_free_all(chain);\n  return result;\n}\n\n// MD5 encode using openssl\nstd::string md5Encode(folly::ByteRange text) {\n  static_assert(MD5_DIGEST_LENGTH == 16);\n\n  unsigned char digest[MD5_DIGEST_LENGTH];\n  MD5(text.begin(), text.size(), digest);\n\n  // convert digest to hex string\n  std::ostringstream ss;\n  ss << std::hex << std::setfill('0');\n  for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {\n    ss << std::setw(2) << (unsigned int)digest[i];\n  }\n  return ss.str();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/CryptUtil.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Range.h>\n#include <string>\n\nnamespace proxygen {\n\n// Base64 encode using openssl, may return empty string on allocation failure\nstd::string base64Encode(folly::ByteRange text);\n\n// MD5 encode using openssl\nstd::string md5Encode(folly::ByteRange text);\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/Exception.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/Exception.h>\n\n#include <utility>\n\nnamespace proxygen {\n\nException::Exception(std::string msg) : msg_(std::move(msg)), code_(0) {\n}\n\nException::Exception(const char* msg) : msg_(msg), code_(0) {\n}\n\nException::Exception(const Exception& other)\n    : msg_(other.msg_),\n      code_(other.code_),\n      proxygenError_(other.proxygenError_) {\n}\n\nException::Exception(Exception&& other) noexcept\n    : msg_(other.msg_),\n      code_(other.code_),\n      proxygenError_(other.proxygenError_) {\n}\n\nconst char* Exception::what() const noexcept {\n  return msg_.c_str();\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/Exception.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Conv.h>\n#include <proxygen/lib/http/ProxygenErrorEnum.h>\n#include <string>\n#include <utility>\n\nnamespace proxygen {\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4521) // 'proxygen::Exception': multiple copy ctors\n#endif\n/**\n * Base class for all exceptions.\n */\nclass Exception : public std::exception {\n public:\n  explicit Exception(std::string msg);\n  explicit Exception(const char* msg);\n  Exception(const Exception&);\n  Exception(Exception& other) : Exception(std::as_const(other)) {\n  }\n  Exception(Exception&&) noexcept;\n\n  template <typename... Args>\n  explicit Exception(Args&&... args)\n      : msg_(folly::to<std::string>(std::forward<Args>(args)...)), code_(0) {\n  }\n\n  ~Exception() noexcept override = default;\n\n  // std::exception methods\n  [[nodiscard]] const char* what() const noexcept override;\n\n  // Accessors for code\n  void setCode(int code) {\n    code_ = code;\n  }\n\n  [[nodiscard]] int getCode() const {\n    return code_;\n  }\n\n  // Accessors for ProxygenError\n  [[nodiscard]] bool hasProxygenError() const {\n    return (proxygenError_ != kErrorNone);\n  }\n\n  void setProxygenError(ProxygenError proxygenError) {\n    proxygenError_ = proxygenError;\n  }\n\n  [[nodiscard]] ProxygenError getProxygenError() const {\n    return proxygenError_;\n  }\n\n private:\n  const std::string msg_;\n  int code_;\n  ProxygenError proxygenError_{kErrorNone};\n};\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/Export.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n// From https://gcc.gnu.org/wiki/Visibility\n#if defined _WIN32 || defined __CYGWIN__\n// The current uses of this doesn't actually\n// have any need to be present on Windows,\n// and building proxygen as a dynamic lib\n// under Windows is not currently supported.\n// HHVM builds Proxygen as a static library.\n#define FB_EXPORT\n#define FB_LOCAL\n#else\n#if __GNUC__ >= 4\n#define FB_EXPORT __attribute__((visibility(\"default\")))\n#define FB_LOCAL __attribute__((visibility(\"hidden\")))\n#else\n#define FB_EXPORT\n#define FB_LOCAL\n#endif\n#endif\n"
  },
  {
    "path": "proxygen/lib/utils/FilterChain.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Function.h>\n#include <folly/Memory.h>\n#include <glog/logging.h>\n#include <memory>\n#include <utility>\n\nnamespace proxygen {\n\n/**\n * A generic two-way filter. That is, this filter intercepts calls from an\n * object A to some object B on an interface T1. It also intercepts calls from B\n * to A on an interface T2.\n *\n * Subclass GenericFilter templatized on the appropriate interfaces to intercept\n * calls between T1 and T2.\n *\n * T2 must have the ability to tell T1 to call it back using a certain\n * object. Many different callback patterns have a different name for this\n * function, but this filter needs to be informed when the callback object\n * changes. Therefore, when you subclass this object, you must call\n *\n * void setCallbackInternal(T2*);\n *\n * from the appropriate virtual function and not forward that function call\n * along the chain, or the filter will not work and may have errors!\n */\ntemplate <typename T1,\n          typename T2,\n          void (T1::*set_callback)(T2*),\n          bool TakeOwnership,\n          typename Dp = std::default_delete<T1>>\nclass GenericFilter\n    : public T1\n    , public T2 {\n public:\n  using Filter = GenericFilter<T1, T2, set_callback, TakeOwnership, Dp>;\n  /**\n   * @param calls You will intercept calls to T1 interface iff you\n   *              pass true for this parameter.\n   * @param callbacks You will intercept calls to T2 interface iff you\n   *                  pass true for this parameter.\n   */\n  GenericFilter(bool calls, bool callbacks)\n      : kWantsCalls_(calls), kWantsCallbacks_(callbacks) {\n  }\n\n  ~GenericFilter() override {\n    if (TakeOwnership) {\n      callbackSource_ = nullptr;\n    }\n    // For the last filter in the chain, next_ is null, and call_ points\n    // to the concrete implementation object.\n    auto next = next_ ? next_ : call_;\n    drop();\n    if (TakeOwnership && next) {\n      Dp()(next);\n    }\n  }\n\n  /**\n   * @param nextFilter the new filter to insert after this filter\n   */\n  void append(Filter* nextFilter) {\n    nextFilter->next_ = next_;\n    nextFilter->prev_ = this;\n    nextFilter->call_ = call_;\n    nextFilter->callback_ = kWantsCallbacks_ ? this : callback_;\n    nextFilter->callSource_ = kWantsCalls_ ? this : callSource_;\n    nextFilter->callbackSource_ = callbackSource_;\n    if (next_) {\n      next_->prev_ = nextFilter;\n    }\n\n    if (nextFilter->kWantsCalls_) {\n      if (kWantsCalls_) {\n        call_ = nextFilter;\n      } else {\n        callSource_->call_ = nextFilter;\n      }\n      if (next_) {\n        next_->callSource_ = nextFilter;\n      }\n    }\n    if (nextFilter->kWantsCallbacks_) {\n      // Find the first filter before this one that wants callbacks\n      auto cur = this;\n      while (cur->prev_ && !cur->kWantsCallbacks_) {\n        cur = cur->prev_;\n      }\n      cur->callbackSource_ = nextFilter;\n      // Make sure nextFilter gets callbacks\n      ((nextFilter->callbackSource_)->*(set_callback))(nextFilter);\n    }\n    next_ = nextFilter;\n  }\n\n  const bool kWantsCalls_;\n  const bool kWantsCallbacks_;\n\n protected:\n  /**\n   * Note: you MUST override the set_callback function and call\n   * setCallbackInternal() from your derived class.\n   */\n  void setCallbackInternal(T2* cb) {\n    setCallbackInternalImpl(cb, this);\n  }\n\n  /**\n   * Removes this filter from the chain. For owning chains the caller must\n   * manually delete the filter\n   */\n  void drop() {\n    if (prev_) {\n      prev_->next_ = next_;\n    }\n    if (next_) {\n      next_->prev_ = prev_;\n    }\n    // TODO: could call fn gated by std::enable_if\n    if (kWantsCalls_ && callSource_) {\n      callSource_->call_ = call_;\n      if (call_) {\n        auto callFilter = dynamic_cast<Filter*>(call_);\n        if (callFilter) {\n          callFilter->callSource_ = callSource_;\n        }\n      }\n    }\n    // TODO: could call fn gated by std::enable_if\n    if (kWantsCallbacks_ && callbackSource_) {\n      ((callbackSource_)->*(set_callback))(callback_);\n      if (callback_) {\n        auto callbackFilter = dynamic_cast<Filter*>(callback_);\n        if (callbackFilter) {\n          callbackFilter->callbackSource_ = callbackSource_;\n        }\n      }\n    }\n    call_ = callbackSource_ = nullptr;\n    callback_ = nullptr;\n    callSource_ = next_ = prev_ = nullptr;\n  }\n\n  // Next \"call\" filter (call_ == next_ iff next_->kWantsCalls_)\n  T1* call_{nullptr};\n  // Next \"callback\" filter (callback_ == prev_ iff prev_->kWantsCallbacks_)\n  T2* callback_{nullptr};\n\n private:\n  void setCallbackInternalImpl(T2* cb, T2* sourceSet) {\n    if (callback_ != cb) {\n      callback_ = cb;\n      ((callbackSource_)->*(set_callback))(cb ? sourceSet : nullptr);\n    }\n  }\n\n  // The next filter in the chain (towards T1 implementation)\n  Filter* next_{nullptr};\n  // The previous filter in the chain (towards T2 implementation)\n  Filter* prev_{nullptr};\n  // The first filter before this one in the chain that wants calls.\n  // Only valid if kWantsCalls_ is true.\n  Filter* callSource_{nullptr};\n  // Pointer to the first filter or object after this one in the chain\n  // that makes callbacks. Only valid if kWantsCallbacks_ is true.\n  T1* callbackSource_{nullptr};\n\n  template <class A, class B, class F, void (A::*fn)(B*), bool Own>\n  friend class FilterChain;\n};\n\n/**\n * This class can be treated the same as a T1*, however internally it contains a\n * chain of filters for T1 and T2. These filters are inserted between the\n * original callback and destination.\n *\n * If a filter does not care about one side of the calls, it will pass through\n * the calls, saving a virtual function call. In the example below Filter1 only\n * wants to intercept callbacks, and Filter2 only wants to intercept calls.\n *\n *\n *      FilterChain        Filter1          Filter2        destination\n *     _____________    _____________    _____________    _____________\n *     |           |    |           |    |           |    |           |\n *     |   call_------------------------>|   call_------->|\"call\"     | T1\n *     |           |    |           |    |           |    |           |\n *  T2 | callback_ |<-----callback_ |<--------------------|\"callback\" |\n *     |___________|    |___________|    |___________|    |___________|\n *\n *\n * This class is templatized on the two interfaces, T1, T2, as well as on the\n * pass through filter implementation, FilterType, the special set callback\n * function, and a boolean indicating whether the chain owns the filters (if\n * false, the filters must delete themselves at the correct time). FilterType\n * must have GenericFilter as an ancestor.\n */\ntemplate <typename T1,\n          typename T2,\n          typename FilterType,\n          void (T1::*set_callback)(T2*),\n          bool TakeOwnership>\nclass FilterChain : private FilterType {\n public:\n  explicit FilterChain(std::unique_ptr<T1> destination)\n      : FilterType(false, false) {\n    static_assert(TakeOwnership,\n                  \"unique_ptr constructor only available \"\n                  \"if the chain owns the filters.\");\n    this->call_ = CHECK_NOTNULL(destination.release());\n    this->callback_ = nullptr; // must call setCallback() explicitly\n    this->callSource_ = this;\n    this->callbackSource_ = this->call_;\n    this->chainEnd_ = this->call_;\n  }\n\n  explicit FilterChain(T1* destination) : FilterType(false, false) {\n    static_assert(!TakeOwnership,\n                  \"raw pointer constructor only available \"\n                  \"if the chain doesn't own the filters.\");\n    this->call_ = CHECK_NOTNULL(destination);\n    this->callback_ = nullptr; // must call setCallback() explicitly\n    this->callSource_ = this;\n    this->callbackSource_ = this->call_;\n    this->chainEnd_ = this->call_;\n  }\n\n  /**\n   * Set the callback for this entire filter chain. Setting this to null will\n   * uninstall the callback from the concrete object at the end of the chain.\n   */\n  void setCallback(T2* cb) override {\n    this->setCallbackInternalImpl(cb, cb);\n  }\n\n  /**\n   * Returns the head of the call chain. Do* not* call T1::set_callback() on\n   * this member. To change the callback of this entire filter chain, use the\n   * separate setCallback() method.\n   */\n  T1* call() {\n    return this->call_;\n  }\n  const T1* call() const {\n    return this->call_;\n  }\n\n  /**\n   * Returns the concrete implementation at the end of the filter chain.\n   */\n  T1* getChainEndPtr() {\n    return chainEnd_;\n  }\n  const T1& getChainEnd() const {\n    return *chainEnd_;\n  }\n\n  using FilterChainType = GenericFilter<T1, T2, set_callback, TakeOwnership>;\n  std::unique_ptr<T1> setDestination(std::unique_ptr<T1> destination) {\n    static_assert(TakeOwnership,\n                  \"unique_ptr setDestination only available \"\n                  \"if the chain owns the filters.\");\n    // find the last filter in the chain, and the last filter that wants calls,\n    // callbacks\n    FilterChainType* lastFilter = this;\n    FilterChainType* lastCall = this;\n    FilterChainType* lastCallback = this;\n    while (lastFilter->next_) {\n      if (lastFilter->kWantsCalls_) {\n        lastCall = lastFilter;\n      }\n      if (lastFilter->kWantsCallbacks_) {\n        lastCallback = lastFilter;\n      }\n      if (lastFilter->call_ == this->chainEnd_) {\n        // Search and replace, the last N non-call filters all point to dest\n        lastFilter->call_ = destination.get();\n      }\n      lastFilter = lastFilter->next_;\n    }\n    if (lastFilter->kWantsCalls_) {\n      lastCall = lastFilter;\n    }\n    if (lastFilter->kWantsCallbacks_) {\n      lastCallback = lastFilter;\n    }\n    lastFilter->call_ = CHECK_NOTNULL(destination.release());\n    lastCall->call_ = lastFilter->call_;\n    lastCallback->callbackSource_ = lastFilter->call_;\n    auto oldChainEnd = this->chainEnd_;\n    this->chainEnd_ = lastFilter->call_;\n\n    this->chainEnd_->setCallback(lastCallback);\n    return std::unique_ptr<T1>(oldChainEnd);\n  }\n\n  /**\n   * Adds filters with the given types to the front of the chain.\n   */\n  template <typename C, typename C2, typename... Types>\n  typename std::enable_if<std::is_constructible<C>::value>::type addFilters() {\n    // Callback <-> F1 <-> F2 ... <-> F_new <-> Destination\n    this->append(new C());\n    addFilters<C2, Types...>();\n  }\n\n  /**\n   * Base case of above function where we add a single filter.\n   */\n  template <typename C>\n  typename std::enable_if<std::is_constructible<C>::value>::type addFilters() {\n    this->append(new C());\n  }\n\n  /**\n   * Adds already constructed filters (inside unique_ptr) to the front of the\n   * chain.\n   */\n  template <typename C, typename... Types>\n  void addFilters(std::unique_ptr<C> cur, Types&&... remaining) {\n    static_assert(TakeOwnership,\n                  \"addFilters() can only take \"\n                  \"unique_ptr if the chain owns the filters\");\n    this->append(cur.release());\n    addFilters(std::forward<Types>(remaining)...);\n  }\n\n  template <typename C, typename... Types>\n  void addFilters(C* cur, Types&&... remaining) {\n    static_assert(!TakeOwnership,\n                  \"addFilters() can only take \"\n                  \"pointers if the chain doesn't own the filters\");\n    this->append(cur);\n    addFilters(std::forward<Types>(remaining)...);\n  }\n\n  /**\n   * Another way to add filters. This way is similar to 'emplace_front' and\n   * returns a reference to itself so you can chain add() calls if you like.\n   */\n  template <typename C, typename... Args>\n  FilterChain<T1, T2, FilterType, set_callback, TakeOwnership>& add(\n      Args&&... args) {\n    this->append(new C(std::forward<Args>(args)...));\n    return *this;\n  }\n\n  const T1* operator->() const {\n    return call();\n  }\n  T1* operator->() {\n    return call();\n  }\n\n  void foreach (folly::FunctionRef<void(FilterChainType*)> fn) {\n    auto cur = this->next_;\n    while (cur) {\n      auto filter = cur;\n      cur = cur->next_;\n      fn(filter);\n    }\n  }\n\n private:\n  /**\n   * Base case for addFilters() called with no arguments. It doesn't need to be\n   * public since trying to add zero filters doesn't make sense from a public\n   * API pov.\n   */\n  void addFilters() {\n  }\n\n  T1* chainEnd_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/HTTPTime.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/HTTPTime.h>\n\n#include <folly/portability/Time.h>\n\nnamespace proxygen {\n\nfolly::Optional<int64_t> parseHTTPDateTime(const std::string& s) {\n  struct tm tm = {};\n\n  if (s.empty()) {\n    return folly::none;\n  }\n\n  // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123\n  // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036\n  // Sun Nov 6 08:49:37 1994        ; ANSI C's asctime() format\n  //    Assume GMT as per rfc2616 (see HTTP-date):\n  //       - https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html\n  if (strptime(s.c_str(), \"%a, %d %b %Y %H:%M:%S GMT\", &tm) != nullptr ||\n      strptime(s.c_str(), \"%a, %d-%b-%y %H:%M:%S GMT\", &tm) != nullptr ||\n      strptime(s.c_str(), \"%a %b %d %H:%M:%S %Y\", &tm) != nullptr) {\n    return folly::Optional<int64_t>(timegm(&tm));\n  }\n\n  return folly::none;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/HTTPTime.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <stddef.h>\n#include <string>\n\nnamespace proxygen {\n\nfolly::Optional<int64_t> parseHTTPDateTime(const std::string& s);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/Logging.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/Logging.h>\n\n#include <folly/Format.h>\n#include <folly/Singleton.h>\n#include <folly/String.h>\n#include <folly/experimental/symbolizer/Symbolizer.h>\n#include <fstream>\n#include <memory>\n#include <ostream>\n#include <sstream>\n#include <sys/stat.h>\n#include <vector>\n\nusing folly::IOBuf;\nusing folly::StringPiece;\nusing std::string;\nusing std::stringstream;\nusing std::unique_ptr;\nusing std::vector;\n\nnamespace {\nproxygen::HexFollyPrinter hexFollyPrinter;\nproxygen::Hex16Printer hex16Printer;\nproxygen::ChainInfoPrinter chainInfoPrinter;\nproxygen::BinPrinter binPrinter;\n\nvector<proxygen::IOBufPrinter*> printers = {\n    &hexFollyPrinter, &hex16Printer, &chainInfoPrinter, &binPrinter};\n} // namespace\n\nnamespace proxygen {\n\nstring hexStr(StringPiece sp) {\n  string out;\n  for (auto ch : sp) {\n    out.append(folly::sformat(\"{:02x}\", (uint8_t)ch));\n  }\n  return out;\n}\n\nstring HexFollyPrinter::print(const IOBuf* buf) {\n  return folly::hexDump(buf->data(), buf->length());\n}\n\nstring Hex16Printer::print(const IOBuf* buf) {\n  stringstream out;\n  const uint8_t* data = buf->data();\n  char tmp[24];\n  for (size_t i = 0; i < buf->length(); i++) {\n    snprintf(tmp, 3, \"%02x\", data[i]);\n    out << tmp;\n    if ((i + 1) % 2 == 0) {\n      out << ' ';\n    }\n    if ((i + 1) % 16 == 0) {\n      out << std::endl;\n    }\n  }\n  return out.str();\n}\n\nstring ChainInfoPrinter::print(const IOBuf* buf) {\n  stringstream out;\n  out << \"iobuf of size \" << buf->length() << \" tailroom \" << buf->tailroom();\n  return out.str();\n}\n\nstring BinPrinter::print(const IOBuf* buf) {\n  static uint8_t bytesPerLine = 8;\n  string out;\n  const uint8_t* data = buf->data();\n  for (size_t i = 0; i < buf->length(); i++) {\n    for (int b = 7; b >= 0; b--) {\n      out += data[i] & 1 << b ? '1' : '0';\n    }\n    out += ' ';\n    out += isprint(data[i]) ? data[i] : ' ';\n    if ((i + 1) % bytesPerLine == 0) {\n      out += '\\n';\n    } else {\n      out += ' ';\n    }\n  }\n  out += '\\n';\n  return out;\n}\n\nstring IOBufPrinter::printChain(const IOBuf* buf,\n                                Format format,\n                                bool coalesce) {\n  auto index = (uint8_t)format;\n  if (printers.size() <= index) {\n    LOG(ERROR) << \"invalid format: \" << index;\n    return \"\";\n  }\n  auto printer = printers[index];\n  // empty chain\n  if (!buf) {\n    return \"\";\n  }\n\n  unique_ptr<IOBuf> cbuf = nullptr;\n  if (coalesce) {\n    cbuf = buf->clone();\n    cbuf->coalesce();\n    buf = cbuf.get();\n  }\n  auto b = buf;\n  string res;\n  do {\n    res += printer->print(b);\n    b = b->next();\n  } while (b != buf);\n  return res;\n}\n\nvoid dumpBinToFile(const string& filename, const IOBuf* buf) {\n  struct stat fstat;\n  bool exists = (stat(filename.c_str(), &fstat) == 0);\n  if (exists) {\n    // don't write anything if the file exists\n    return;\n  }\n  std::ofstream file(filename, std::ofstream::binary);\n  if (!file.is_open()) {\n    LOG(ERROR) << \"cannot open file \" << filename;\n    return;\n  }\n  if (!buf) {\n    file.close();\n    return;\n  }\n  const IOBuf* first = buf;\n  do {\n    file.write((const char*)buf->data(), buf->length());\n    buf = buf->next();\n  } while (buf != first);\n  file.close();\n  LOG(INFO) << \"wrote chain \" << IOBufPrinter::printChainInfo(buf) << \" to \"\n            << filename;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/Logging.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Optional.h>\n#include <folly/io/IOBuf.h>\n#include <sstream>\n#include <string>\n\nnamespace proxygen {\n\nclass IOBufPrinter {\n public:\n  enum class Format : uint8_t {\n    HEX_FOLLY = 0,\n    HEX_16 = 1,\n    CHAIN_INFO = 2,\n    BIN = 3,\n  };\n\n  static std::string printChain(const folly::IOBuf* buf,\n                                Format format,\n                                bool coalesce);\n\n  static std::string printHexFolly(const folly::IOBuf* buf,\n                                   bool coalesce = false) {\n    return printChain(buf, Format::HEX_FOLLY, coalesce);\n  }\n\n  static std::string printHex16(const folly::IOBuf* buf,\n                                bool coalesce = false) {\n    return printChain(buf, Format::HEX_16, coalesce);\n  }\n\n  static std::string printChainInfo(const folly::IOBuf* buf) {\n    return printChain(buf, Format::CHAIN_INFO, false);\n  }\n\n  static std::string printBin(const folly::IOBuf* buf, bool coalesce = false) {\n    return printChain(buf, Format::BIN, coalesce);\n  }\n\n  IOBufPrinter() = default;\n  virtual ~IOBufPrinter() = default;\n\n  virtual std::string print(const folly::IOBuf* buf) = 0;\n};\n\nclass Hex16Printer : public IOBufPrinter {\n public:\n  std::string print(const folly::IOBuf* buf) override;\n};\n\nclass HexFollyPrinter : public IOBufPrinter {\n public:\n  std::string print(const folly::IOBuf* buf) override;\n};\n\nclass ChainInfoPrinter : public IOBufPrinter {\n public:\n  std::string print(const folly::IOBuf* buf) override;\n};\n\nclass BinPrinter : public IOBufPrinter {\n public:\n  std::string print(const folly::IOBuf* buf) override;\n};\n\n/**\n * write the entire binary content from all the buffers into a binary file\n */\nvoid dumpBinToFile(const std::string& filename, const folly::IOBuf* buf);\n\n/**\n * helper functions for printing in hex a byte array\n * see unit test for example\n */\nstd::string hexStr(folly::StringPiece sp);\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/NullTraceEventObserver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/utils/TraceEventObserver.h>\n\nnamespace proxygen {\n/*\n * A no-op trace event observer\n */\nstruct NullTraceEventObserver : public TraceEventObserver {\n  void traceEventAvailable(TraceEvent) noexcept override {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ParseURL.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ParseURL.h>\n\n#include <algorithm>\n#include <charconv>\n\n#include <folly/portability/Sockets.h>\n#include <proxygen/lib/utils/UtilInl.h>\n\n#include <proxygen/external/http_parser/http_parser.h>\n\nnamespace {\n\n// Helper function for C++17 compatibility (starts_with is C++20)\nconstexpr inline bool startsWith(std::string_view str,\n                                 std::string_view prefix) noexcept {\n#if __cplusplus >= 202002L\n  return str.starts_with(prefix);\n#else\n  return str.size() >= prefix.size() &&\n         str.compare(0, prefix.size(), prefix) == 0;\n#endif\n}\n\n} // namespace\n\nnamespace proxygen {\n\n/**\n * According to RFC 3986, a generic HTTP URL is of the form:\n *   scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]\n *\n * ParseURL use http_parser to parse internet url, that supports internet\n * sematic url use double slash:\n *   http://host/path\n *   ftp://host/path\n *   rtmp://host/path\n *\n * It does not support special scheme like:\n *   mailto:user@host:port\n *   news:path\n *\n * And ParseURL support partial form (URI reference):\n *   host:port/path?query#fragment\n *   /path?query#fragment\n *   ?query\n *   #fragment\n *\n */\n\n// Helper function to check if URL has valid scheme.\n// http_parser only support full form scheme with double slash,\n// and the scheme must be all alphabetic charecter.\nstatic bool validateScheme(std::string_view url) {\n  auto schemeEnd = url.find(\"://\");\n  if (schemeEnd == std::string_view::npos || schemeEnd == 0) {\n    return false;\n  }\n\n  auto scheme = url.substr(0, schemeEnd);\n  return std::all_of(scheme.begin(), scheme.end(), isAlpha);\n}\n\nbool ParseURL::isSupportedScheme(std::string_view location) {\n  static constexpr std::array<std::string_view, 2> kSupportedSchemes{\"http\",\n                                                                     \"https\"};\n  auto schemeEnd = location.find(\"://\");\n  if (schemeEnd == std::string_view::npos) {\n    // Location doesn't contain a scheme, so use the one from the original URL\n    return true;\n  }\n  auto scheme = location.substr(0, schemeEnd);\n\n  return std::find(kSupportedSchemes.begin(),\n                   kSupportedSchemes.end(),\n                   scheme) != kSupportedSchemes.end();\n}\n\nstd::optional<std::string> ParseURL::getRedirectDestination(\n    std::string_view url,\n    std::string_view requestScheme,\n    std::string_view location,\n    std::string_view headerHost) noexcept {\n  auto newUrl = ParseURL::parseURL(location);\n  if (!newUrl) {\n    DLOG(INFO) << \"Unparsable location header=\" << location;\n    return std::nullopt;\n  }\n  if (!newUrl->hasHost()) {\n    // New URL is relative\n    std::optional<ParseURL> oldURL = ParseURL::parseURL(url);\n    if (!oldURL || !oldURL->hasHost()) {\n      // Old URL was relative, try host header\n      oldURL = ParseURL::parseURL(headerHost);\n      if (!oldURL || !oldURL->hasHost()) {\n        VLOG(2) << \"Cannot determine destination for relative redirect \"\n                << \"location=\" << location << \" orig url=\" << url\n                << \" host=\" << headerHost;\n        return std::nullopt;\n      }\n    } // else oldURL was absolute and has a host\n    std::stringstream ss;\n    ss << requestScheme << \"://\" << oldURL->hostAndPort() << location;\n    return ss.str();\n  } else {\n    return std::string(newUrl->url());\n  }\n}\n\nvoid ParseURL::parse(bool strict) noexcept {\n  if (url_ == \"/\") {\n    path_ = url_;\n    valid_ = true;\n    return;\n  }\n  if (validateScheme(url_)) {\n    struct http_parser_url u{};\n    memset(&u, 0, sizeof(struct http_parser_url)); // init before used\n    valid_ = !(http_parser_parse_url_options(\n        url_.data(),\n        url_.size(),\n        0,\n        &u,\n        strict ? F_PARSE_URL_OPTIONS_URL_STRICT : 0));\n\n    if (valid_) {\n      // Since we init the http_parser_url with all fields to 0, if the field\n      // not present in url, it would be [0, 0], means that this field starts at\n      // 0 and len = 0, we will get \"\" from this.  So no need to check field_set\n      // before get field.\n\n      scheme_ =\n          url_.substr(u.field_data[UF_SCHEMA].off, u.field_data[UF_SCHEMA].len);\n\n      if (u.field_data[UF_HOST].off != 0 &&\n          url_[u.field_data[UF_HOST].off - 1] == '[') {\n        // special case: host: [::1]\n        host_ = url_.substr(u.field_data[UF_HOST].off - 1,\n                            u.field_data[UF_HOST].len + 2);\n      } else {\n        host_ =\n            url_.substr(u.field_data[UF_HOST].off, u.field_data[UF_HOST].len);\n      }\n\n      port_ = u.port;\n\n      path_ = url_.substr(u.field_data[UF_PATH].off, u.field_data[UF_PATH].len);\n      query_ =\n          url_.substr(u.field_data[UF_QUERY].off, u.field_data[UF_QUERY].len);\n      fragment_ = url_.substr(u.field_data[UF_FRAGMENT].off,\n                              u.field_data[UF_FRAGMENT].len);\n\n      authority_ = hostAndPort();\n    }\n  } else {\n    parseNonFully(strict);\n  }\n}\n\nvoid ParseURL::parseNonFully(bool strict) noexcept {\n  if (url_.empty()) {\n    valid_ = false;\n    return;\n  }\n\n  // Check if the URL has only printable characters and no control character.\n  if (!validateURL(url_,\n                   strict ? URLValidateMode::STRICT\n                          : URLValidateMode::STRICT_COMPAT)) {\n    valid_ = false;\n    return;\n  }\n\n  auto pathStart = url_.find('/');\n  auto queryStart = url_.find('?');\n  auto hashStart = url_.find('#');\n\n  auto queryEnd = std::min(hashStart, std::string_view::npos);\n  auto pathEnd = std::min(queryStart, hashStart);\n  auto authorityEnd = std::min(pathStart, pathEnd);\n\n  authority_ = url_.substr(0, authorityEnd);\n\n  if (pathStart < pathEnd) {\n    path_ = url_.substr(pathStart, pathEnd - pathStart);\n  } else {\n    // missing the '/', e.g. '?query=3'\n    path_ = \"\";\n  }\n\n  if (queryStart < queryEnd) {\n    query_ = url_.substr(queryStart + 1, queryEnd - queryStart - 1);\n  } else if (queryStart != std::string_view::npos && hashStart < queryStart) {\n    valid_ = false;\n    return;\n  }\n\n  if (hashStart != std::string_view::npos) {\n    fragment_ = url_.substr(hashStart + 1);\n  }\n\n  if (!parseAuthority()) {\n    valid_ = false;\n    return;\n  }\n\n  valid_ = true;\n}\n\nbool ParseURL::parseAuthority() noexcept {\n  constexpr auto npos = std::string_view::npos;\n  std::string_view authority(authority_);\n  auto left = authority.find('[');\n  auto right = authority.find(']');\n\n  auto pos = authority.find(':', right != npos ? right + 1 : 0);\n  if (pos != npos) {\n    auto port = authority.substr(pos + 1);\n    auto end = port.data() + port.size();\n    auto result = std::from_chars(port.data(), end, port_);\n    if (result.ec != std::errc{} || result.ptr != end) {\n      return false;\n    }\n  }\n\n  if (left == npos && right == npos) {\n    // not a ipv6 literal\n    host_ = authority.substr(0, pos);\n    return true;\n  } else if (left < right && right != npos) {\n    // a ipv6 literal\n    host_ = authority.substr(left, right - left + 1);\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool ParseURL::hostIsIPAddress() {\n  if (!valid_) {\n    return false;\n  }\n\n  stripBrackets();\n  // we have to make a copy of hostNoBrackets_ since the string_view is not\n  // null-terminated\n  std::string hostNoBrackets(hostNoBrackets_);\n  int af = hostNoBrackets.find(':') == std::string::npos ? AF_INET : AF_INET6;\n  char buf4[sizeof(in_addr)];\n  char buf6[sizeof(in6_addr)];\n  return inet_pton(af, hostNoBrackets.c_str(), af == AF_INET ? buf4 : buf6) ==\n         1;\n}\n\nvoid ParseURL::stripBrackets() noexcept {\n  if (hostNoBrackets_.empty()) {\n    if (!host_.empty() && host_.front() == '[' && host_.back() == ']') {\n      hostNoBrackets_ = host_.substr(1, host_.size() - 2);\n    } else {\n      hostNoBrackets_ = host_;\n    }\n  }\n}\n\nstd::optional<std::string_view> ParseURL::getQueryParam(\n    std::string_view query, const std::string_view name) noexcept {\n  while (!query.empty()) {\n    std::string_view param = query.substr(0, query.find('&'));\n    query.remove_prefix(std::min(query.size(), param.size() + 1));\n    if (!startsWith(param, name)) {\n      continue;\n    }\n    param.remove_prefix(name.size());\n    if (param.empty()) {\n      return param;\n    } else if (param.front() == '=') {\n      param.remove_prefix(1);\n      return param;\n    }\n  }\n  return std::nullopt;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ParseURL.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <optional>\n#include <sstream>\n#include <string>\n#include <string_view>\n\n#include <glog/logging.h>\n#include <proxygen/lib/utils/Export.h>\n\nnamespace proxygen {\n\n// ParseURL can handle non-fully-formed URLs. This class must not persist beyond\n// the lifetime of the buffer underlying the input StringPiece\n\nclass ParseURL {\n public:\n  /* Parse a URL.  If parsing succeeds, return a fully formed ParseURL with\n   * valid() == true.  If parsing fails, returns nothing. If you need the\n   * partial parse results, use parseURLMaybeInvalid below.\n   */\n  static std::optional<ParseURL> parseURL(std::string_view urlVal,\n                                          bool strict = false) noexcept {\n    ParseURL parseUrl(urlVal, strict);\n    if (parseUrl.valid()) {\n      return parseUrl;\n    }\n    return std::nullopt;\n  }\n\n  /* Parse a URL.  Returns a ParseURL object that may or may not be valid.\n   * Caller should check valid()\n   */\n  static ParseURL parseURLMaybeInvalid(std::string_view urlVal,\n                                       bool strict = false) noexcept {\n    return ParseURL(urlVal, strict);\n  }\n\n  static bool isSupportedScheme(std::string_view location);\n\n  static std::optional<std::string> getRedirectDestination(\n      std::string_view url,\n      std::string_view requestScheme,\n      std::string_view location,\n      std::string_view headerHost) noexcept;\n\n  // Deprecated.  Will be removed soon\n  explicit ParseURL(std::string_view urlVal, bool strict = true) noexcept {\n    init(urlVal, strict);\n  }\n\n  ParseURL(ParseURL&& goner) noexcept\n      : url_(goner.url_),\n        scheme_(goner.scheme_),\n        path_(goner.path_),\n        query_(goner.query_),\n        fragment_(goner.fragment_),\n        port_(goner.port_),\n        valid_(goner.valid_),\n        initialized_(goner.initialized_) {\n    moveHostAndAuthority(std::move(goner));\n  }\n\n  ParseURL& operator=(ParseURL&& goner) noexcept {\n    url_ = goner.url_;\n    scheme_ = goner.scheme_;\n    path_ = goner.path_;\n    query_ = goner.query_;\n    fragment_ = goner.fragment_;\n    port_ = goner.port_;\n    valid_ = goner.valid_;\n    initialized_ = goner.initialized_;\n    moveHostAndAuthority(std::move(goner));\n    return *this;\n  }\n\n  ParseURL& operator=(const ParseURL&) = delete;\n  ParseURL(const ParseURL&) = delete;\n\n  ParseURL() = default;\n\n  void init(std::string_view urlVal, bool strict = false) {\n    CHECK(!initialized_);\n    url_ = urlVal;\n    parse(strict);\n    initialized_ = true;\n  }\n\n  operator bool() const {\n    return valid();\n  }\n\n  [[nodiscard]] std::string_view url() const {\n    return url_;\n  }\n\n  [[nodiscard]] std::string_view scheme() const {\n    return scheme_;\n  }\n\n  [[nodiscard]] std::string authority() const {\n    return authority_;\n  }\n\n  [[nodiscard]] bool hasHost() const {\n    return valid() && !host_.empty();\n  }\n\n  [[nodiscard]] std::string_view host() const {\n    return host_;\n  }\n\n  [[nodiscard]] uint16_t port() const {\n    return port_;\n  }\n\n  [[nodiscard]] std::string hostAndPort() const {\n    if (port_ == 0) {\n      return std::string(host_);\n    }\n    std::stringstream ss;\n    ss << host_ << \":\" << port_;\n    return ss.str();\n  }\n\n  [[nodiscard]] std::string_view path() const {\n    return path_;\n  }\n\n  [[nodiscard]] std::string_view query() const {\n    return query_;\n  }\n\n  [[nodiscard]] std::string_view fragment() const {\n    return fragment_;\n  }\n\n  [[nodiscard]] bool valid() const {\n    return valid_;\n  }\n\n  [[nodiscard]] std::string_view hostNoBrackets() {\n    stripBrackets();\n    return hostNoBrackets_;\n  }\n\n  [[nodiscard]] bool hostIsIPAddress();\n\n  FB_EXPORT void stripBrackets() noexcept;\n\n  [[nodiscard]] static std::optional<std::string_view> getQueryParam(\n      std::string_view query, const std::string_view name) noexcept;\n\n private:\n  void moveHostAndAuthority(ParseURL&& goner) noexcept {\n    if (!valid_) {\n      return;\n    }\n    int64_t hostOff = -1;\n    int64_t hostNoBracketsOff = -1;\n    const auto isFromUrl = [url = goner.url_](std::string_view s) {\n      return s.data() >= url.data() && s.data() < url.data() + url.size();\n    };\n    if (goner.host_.empty() || isFromUrl(goner.host_)) {\n      // relative url_\n      host_ = goner.host_;\n    } else {\n      // relative authority_\n      hostOff = goner.host_.data() - goner.authority_.data();\n    }\n    if (goner.hostNoBrackets_.empty() || isFromUrl(goner.hostNoBrackets_)) {\n      // relative url_\n      hostNoBrackets_ = goner.hostNoBrackets_;\n    } else {\n      // relative authority_\n      hostNoBracketsOff =\n          goner.hostNoBrackets_.data() - goner.authority_.data();\n    }\n    authority_ = std::move(goner.authority_);\n    std::string_view authority(authority_);\n    if (hostOff >= 0) {\n      host_ = authority.substr(hostOff, goner.host_.size());\n    }\n    if (hostNoBracketsOff >= 0) {\n      hostNoBrackets_ =\n          authority.substr(hostNoBracketsOff, goner.hostNoBrackets_.size());\n    }\n  }\n\n  FB_EXPORT void parse(bool strict) noexcept;\n\n  void parseNonFully(bool strict) noexcept;\n\n  bool parseAuthority() noexcept;\n\n  std::string_view url_;\n  std::string_view scheme_;\n  std::string authority_;\n  std::string_view host_;\n  std::string_view hostNoBrackets_;\n  std::string_view path_;\n  std::string_view query_;\n  std::string_view fragment_;\n  uint16_t port_{0};\n  bool valid_{false};\n  bool initialized_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/PerfectIndexMap.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n\n#include <folly/FBVector.h>\n#include <folly/Optional.h>\n#include <proxygen/lib/utils/UtilInl.h>\n#include <string>\n\n// TODO: consider changing API methods that take in an reference with versions\n// that don't so that callers can give up ownership if they like via std::move\n// operator (and subsequently we would use it as well).\n// TODO: templatize the use of string as the value type; can instead use\n// fbstring\n\nnamespace proxygen {\n\n// A type of key-value map implemented by indexing values in a vector.\n// Performance is on average that of an unordered_map or better when the\n// index is used effectively.  CPU and memory usage are significantly reduced\n// due to the use of a perfect hashing function and vectors.\n\n// Key is a one byte (i.e. uint8_t) type.\n// OtherKey represents the value of Key for which strings which\n//    do not possess unique mappings are mapped to.\n// NoneKey represents the value of Key which no strings map to;\n//    field is reserved to denote deleted elements within the map.\n// PerfectHashStrToKey represents the perfect hash mapping function used\n//    internally to map string keys to their Key values\n// AllowDuplicates represents a flag that controls whether the map supports\n//    duplicate keys.  It is mainly used for optimization purposes as the\n//    implementation of default handling adds additional useless work for such\n//    cases in which duplicates are not tolerated.\ntemplate <typename Key,\n          Key OtherKey,\n          Key NoneKey,\n          Key (*PerfectHashStrToKey)(const std::string &),\n          bool AllowDuplicates,\n          bool CaseInsensitive>\nclass PerfectIndexMap {\n public:\n  static_assert(sizeof(Key) == 1, \"Key must be of size 1 byte.\");\n  PerfectIndexMap() = default;\n  virtual ~PerfectIndexMap() = default;\n\n  // Getters into the underlying map.\n\n  [[nodiscard]] folly::Optional<std::string> getSingleOrNone(\n      const std::string &keyStr) const {\n    auto key = PerfectHashStrToKey(keyStr);\n    if (key == OtherKey) {\n      const std::string *result = getSingleOtherKey(keyStr);\n      return (result == nullptr ? folly::none\n                                : folly::Optional<std::string>(*result));\n    } else {\n      return getSingleOrNone(key);\n    }\n  }\n\n  [[nodiscard]] folly::Optional<std::string> getSingleOtherKeyOrNone(\n      const std::string &keyStr) const {\n    CHECK(PerfectHashStrToKey(keyStr) == OtherKey);\n    const std::string *result = getSingleOtherKey(keyStr);\n    return (result == nullptr ? folly::none\n                              : folly::Optional<std::string>(*result));\n  }\n\n  bool update(Key key,\n              const std::string &checkValue,\n              const std::string &newValue) {\n    CHECK(key != OtherKey && key != NoneKey);\n    std::string *getResult = getSingleKeyForUpdate(key);\n    if (getResult != nullptr && stringsEqual(*getResult, checkValue)) {\n      *getResult = newValue;\n      return true;\n    }\n    return false;\n  }\n\n  folly::Optional<std::string> getSingleOrNone(Key key) const {\n    CHECK(key != OtherKey && key != NoneKey);\n    const std::string *result = getSingleKey(key);\n    return (result == nullptr ? folly::none\n                              : folly::Optional<std::string>(*result));\n  }\n\n  // Adders into the underlying map.\n\n  void add(const std::string &keyStr, const std::string &value) {\n    CHECK(AllowDuplicates);\n    auto key = PerfectHashStrToKey(keyStr);\n    if (key == OtherKey) {\n      addOtherKeyToIndex(keyStr, value);\n    } else {\n      add(key, value);\n    }\n  }\n\n  void add(Key key, const std::string &value) {\n    CHECK(AllowDuplicates && key != OtherKey && key != NoneKey);\n    addKeyToIndex(key, value);\n  }\n\n  // Setters into the underyling map.\n  // Functionally, only one reference to the specified key remains after\n  // the operation completes regardless of the state of AllowDuplicates\n\n  void set(const std::string &keyStr, const std::string &value) {\n    auto key = PerfectHashStrToKey(keyStr);\n    if (key == OtherKey) {\n      setOtherKey(keyStr, value);\n    } else {\n      set(key, value);\n    }\n  }\n\n  void set(Key key, const std::string &value) {\n    CHECK(key != OtherKey && key != NoneKey);\n    setKey(key, value);\n  }\n\n  // Removers from the underlying map.\n  // Functionally, all references to the specified key are removed after the\n  // operation completes, regardless of the state of UnqiueEntries\n\n  bool remove(const std::string &keyStr) {\n    auto key = PerfectHashStrToKey(keyStr);\n    if (key == OtherKey) {\n      return removeOtherKey(keyStr);\n    } else {\n      return remove(key);\n    }\n  }\n  bool remove(Key key) {\n    CHECK(key != OtherKey && key != NoneKey);\n    return removeKey(key);\n  }\n\n  size_t size() {\n    return keys_.size() - noneKeyCount_;\n  }\n\n  void setOtherKey(const std::string &keyStr, const std::string &value) {\n    bool set = false;\n    size_t searchIndex = 0;\n    while (searchForOtherKey(keyStr, searchIndex) != -1) {\n      if (!set) {\n        replaceOtherKeyAtIndex(searchIndex, keyStr, value);\n        if (AllowDuplicates) {\n          set = true;\n        } else {\n          return;\n        }\n      } else {\n        removeAtIndex(otherKeyNamesKeysIndex_[searchIndex]);\n      }\n      ++searchIndex;\n    }\n    if (!set) {\n      addOtherKeyToIndex(keyStr, value);\n    }\n  }\n\n private:\n  // Utility method for comparing strings private to this class as specified\n  // by template parameters.\n  [[nodiscard]] bool stringsEqual(const std::string &strA,\n                                  const std::string &strB) const {\n    if (CaseInsensitive) {\n      // One might be tempted to merge this statement with that above\n      // but that would be wrong.  If CaseInsensitive is true, we do not\n      // want any other check evaluating.\n      return caseInsensitiveEqual(strA, strB);\n    } else {\n      return strA == strB;\n    }\n  }\n  // Private implementations of public methods for code reuse.\n  // Technically this flow adds more copies (for ex in the case of passing\n  // nullptr around as keyStr when not needed) and so slows things down a bit\n  // but this way the code can effectively be reused and there is a single\n  // implementation source.\n\n  const std::string *getSingleKey(Key key) const {\n    const Key *data = keys_.data();\n    if (data) {\n      auto offset = searchForKey(key, data);\n      if (data) {\n        if (AllowDuplicates && searchForKey(key, ++data) != -1) {\n          return nullptr;\n        }\n        return &values_[offset];\n      }\n    }\n    return nullptr;\n  }\n  [[nodiscard]] const std::string *getSingleOtherKey(\n      const std::string &keyStr) const {\n    size_t searchIndex = 0;\n    auto index = searchForOtherKey(keyStr, searchIndex);\n    if (index != -1) {\n      if (AllowDuplicates && searchForOtherKey(keyStr, ++searchIndex) != -1) {\n        return nullptr;\n      }\n      return &values_[index];\n    }\n    return nullptr;\n  }\n\n  void setKey(Key key, const std::string &value) {\n    const Key *data = keys_.data();\n    bool set = false;\n    std::ptrdiff_t offset;\n    while (data) {\n      offset = searchForKey(key, data);\n      if (data) {\n        if (!set) {\n          replaceKeyAtIndex(offset, key, value);\n          if (AllowDuplicates) {\n            set = true;\n          } else {\n            return;\n          }\n        } else {\n          removeAtIndex(offset);\n        }\n        ++data;\n      }\n    }\n    if (!set) {\n      addKeyToIndex(key, value);\n    }\n  }\n\n  bool removeKey(Key key) {\n    const Key *data = keys_.data();\n    bool anyRemoved = false;\n    std::ptrdiff_t offset;\n    while (data) {\n      offset = searchForKey(key, data);\n      if (data) {\n        removeAtIndex(offset);\n        anyRemoved = true;\n        if (!AllowDuplicates) {\n          break;\n        }\n        ++data;\n      }\n    }\n    return anyRemoved;\n  }\n  bool removeOtherKey(const std::string &keyStr) {\n    bool anyRemoved = false;\n    size_t searchIndex = 0;\n    while (searchForOtherKey(keyStr, searchIndex) != -1) {\n      removeAtIndex(otherKeyNamesKeysIndex_[searchIndex]);\n      anyRemoved = true;\n      if (!AllowDuplicates) {\n        break;\n      }\n      ++searchIndex;\n    }\n    return anyRemoved;\n  }\n\n  // Utility methods for searching our index.  The data pointer / start index\n  // are passed by reference so they can be updated by the search itself.  The\n  // methods return on the first occurrence found from the specified start\n  // searching position.\n  std::ptrdiff_t searchForKey(Key key, const Key *&data) const {\n    if (data) {\n      data = (Key *)memchr(\n          (void *)data, key, keys_.size() - (data - keys_.data()));\n      if (data) {\n        return data - keys_.data();\n      }\n    }\n    return -1;\n  }\n  std::ptrdiff_t searchForOtherKey(const std::string &keyStr,\n                                   size_t &startIndex) const {\n    while (startIndex < otherKeyNamesKeysIndex_.size()) {\n      // The key can only be OtherKey or NoneKey\n      if (keys_[otherKeyNamesKeysIndex_[startIndex]] == OtherKey) {\n        if (CaseInsensitive) {\n          // One might be tempted to merge this statement with that above\n          // but that would be wrong.  If CaseInsensitive is true, we do not\n          // want any other check evaluating.\n          if (caseInsensitiveEqual(otherKeyNames_[startIndex], keyStr)) {\n            return otherKeyNamesKeysIndex_[startIndex];\n          }\n        } else {\n          if (otherKeyNames_[startIndex] == keyStr) {\n            return otherKeyNamesKeysIndex_[startIndex];\n          }\n        }\n      }\n      ++startIndex;\n    }\n    return -1;\n  }\n\n  // Utility methods for adding / modifying to our index.\n  void addKeyToIndex(Key key, const std::string &value) {\n    keys_.push_back(key);\n    values_.emplace_back(value);\n  }\n  void addOtherKeyToIndex(const std::string &keyStr, const std::string &value) {\n    keys_.push_back(OtherKey);\n    otherKeyNames_.emplace_back(keyStr);\n    otherKeyNamesKeysIndex_.push_back(keys_.size() - 1);\n    values_.emplace_back(value);\n    ++otherKeyCount_;\n  }\n  void replaceKeyAtIndex(std::ptrdiff_t index,\n                         Key key,\n                         const std::string &value) {\n    keys_[index] = key;\n    values_[index] = value;\n  }\n  void replaceOtherKeyAtIndex(size_t namesIndex,\n                              const std::string &keyStr,\n                              const std::string &value) {\n    otherKeyNames_[namesIndex] = keyStr;\n    values_[otherKeyNamesKeysIndex_[namesIndex]] = value;\n  }\n\n  // Utility methods for removing from our index.\n  void removeAtIndex(std::ptrdiff_t index) {\n    CHECK(keys_[index] != NoneKey);\n    if (keys_[index] == OtherKey) {\n      --otherKeyCount_;\n    }\n    keys_[index] = NoneKey;\n    ++noneKeyCount_;\n    // We purposefully omit actually clearing some other data structures\n    // as it is often useful for debugging purposes to leave this data around.\n  }\n  std::string *getSingleKeyForUpdate(Key key) {\n    const Key *data = keys_.data();\n    if (data) {\n      auto offset = searchForKey(key, data);\n      if (data) {\n        if (AllowDuplicates && searchForKey(key, ++data) != -1) {\n          return nullptr;\n        }\n        return &values_[offset];\n      }\n    }\n    return nullptr;\n  }\n\n  /*\n   * Illustrating the below data structures, for a map where:\n   *     otherKeyCount_ == 2\n   *     noneKeyCount_ == 1\n   *     keys_.size() == 5\n   *     using OK = OtherKey\n   *     using NK = NoneKey\n   *     using K = some key which is not OK or NK\n   *     using --- = out of bounds\n   * The above config implies the size of our map is 4, with 2 OKs and 2 Ks.\n   * Also take note from a debugging standpoint, the fact that no entry in\n   * otherKeyNames_ maps back to index 3 (via otherKeyNamesKeysIndex_) suggests\n   * the removed key (NK) was regular K, not an OK, before being removed.\n   *\n   * ------|-------|-------------------------|----------------|-----------------|\n   * Index | keys_ | otherKeyNamesKeysIndex_ | otherKeyNames_ |     values_ |\n   * ______|_______|_________________________|________________|_________________|\n   *   0   |  OK1  |           0             |   <OK1_name>   |    <OK1_value> |\n   *   1   |  K1   |           4             |   <OK2_name>   |     <K1_value> |\n   *   2   |  K2   |          ---            |     ---        |     <K2_value> |\n   *   3   |  NK   |          ---            |     ---        |     <NK_value> |\n   *   4   |  OK2  |          ---            |     ---        |    <OK2_value> |\n   *   5   |  ---  |          ---            |     ---        |       --- |\n   *  ...  |  ...  |          ...            |     ...        |       ... |\n   * ----------------------------------------------------------------------------\n   */\n\n  size_t otherKeyCount_{0};\n  size_t noneKeyCount_{0};\n  // And using the above two fields we can obviously infer what the\n  // notOtherKeyCount is.\n\n  // 1-byte Key vector used as a primary index into values_.\n  // Thus keys_.size() == values_.size().\n  // The total size of the map is thus always <= keys_.size().\n  // The reason it is not always == is because the map supports removals and on\n  // such operations, we do not resize this vector.\n  folly::fbvector<Key> keys_;\n\n  // A vector used to reverse lookup a particular OtherKey's entry\n  // (in otherKeyNames_) position within keys_.\n  // Strictly speaking: otherKeyNamesKeysIndex_.size() == otherKeyNames_.size().\n  // Stated differently, the N-th OtherKey's name is in otherKeyNames_[N] and\n  // its corresponding value is in values_(namesKeyIndex[N]) because\n  // namesKeyIndex[N] refers to the entry's position in keys_.\n  folly::fbvector<size_t> otherKeyNamesKeysIndex_;\n\n  // Storage for names whose keys are mapped to OtherKey.\n  // This vector can never have more elements than the total numer of OtherKey\n  // entries in the map as it should be equal to this count.\n  // Thus strictly speaking: otherKeyNames_.size() <= keys_.size() where the\n  // only time they could ever be equal is if EVERY entry EVER inserted into\n  // the map was an OtherKey.\n  folly::fbvector<std::string> otherKeyNames_;\n\n  // Storage for all values, OtherKey or other.\n  // Thus values_.size() == keys_.size().\n  folly::fbvector<std::string> values_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/RendezvousHash.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/RendezvousHash.h>\n\n#include <algorithm>\n#include <cmath> /* pow */\n#include <folly/hash/Hash.h>\n#include <limits>\n#include <map>\n#include <vector>\n\nnamespace proxygen {\nvoid RendezvousHash::build(\n    std::vector<std::pair<std::string, uint64_t>>& nodes) {\n  for (auto it = nodes.begin(); it != nodes.end(); ++it) {\n    std::string key = it->first;\n    uint64_t weight = it->second;\n    weights_.emplace_back(computeHash(key.c_str(), key.size()), weight);\n  }\n}\n\nvoid RendezvousHash::buildEqualWeights(std::vector<uint64_t>& nodes) {\n  for (const auto& hash : nodes) {\n    weights_.emplace_back(hash, /*weight*/ 1);\n  }\n}\n\n/*\n * The algorithm of RendezvousHash goes like this:\n * Assuming we have 3 clusters with names and weights:\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n * | n=\"ash4c07\"  | n=\"frc1c12\"  | n=\"prn1c11\"  |\n * | w=100        | w=400        | w=500        |\n * ==============================================\n * To prepare, we calculate a hash for each cluster based on its name:\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n * | n=\"ash4c07\"  | n=\"frc1c12\"  | n=\"prn1c11\"  |\n * | w = 100      | w=400        | w=500        |\n * | h = hash(n)  | h = hash(n)  | h = hash(n)  |\n * ==============================================\n *\n * When a key comes, we have to decide which cluster we want to assign it to:\n * E.g., k = 10240\n *\n * For each cluster, we calculate a combined hash with the sum of key\n * and the cluster's hash:\n *\n *\n *                             ==============================================\n *                             | Cluster1     | Cluster2     | Cluster3     |\n *                                               ...\n *        k                    | h=hash(n)    | h = hash(n)  | h=hash(n)    |\n *        |                    ==============================================\n *        |                                          |\n *        +-------------+----------------------------+\n *                      |\n *                  ch=hash(h + k)\n *                      |\n *                      v\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n * | n=\"ash4c07\"  | n=\"frc1c12\"  | n=\"prn1c11\"  |\n * | w=100        | w=400        | w=500        |\n * | h=hash(n)    | h = hash(n)  | h=hash(n)    |\n * |ch=hash(h+k)  |ch = hash(h+k)|ch=hash(h+k)  |\n * ==============================================\n *\n * ch is now a random variable from 0 to max_int that follows\n * uniform distribution,\n * we need to scale it to a r.v. * from 0 to 1 by dividing it with max_int:\n *\n * scaledHash = ch / max_int\n *\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n *                    ....\n * |ch=hash(h+k)  |ch = hash(h+k)|ch=hash(h+k)  |\n * ==============================================\n *                      |\n *                    sh=ch/max_int\n *                      |\n *                      v\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n *                    ....\n * |ch=hash(h+k)  |ch = hash(h+k)|ch=hash(h+k)  |\n * |sh=ch/max_int |sh=ch/max_int |sh=ch/max_int |\n * ==============================================\n *\n * We also need to respect the weights, we have to scale it again with\n * a function of its weight:\n *\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n *                    ....\n * |sh=ch/max_int |sh=ch/max_int |sh=ch/max_int |\n * ==============================================\n *                      |\n *                      |\n *               sw = pow(sh, 1/w)\n *                      |\n *                      V\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n *                    ....\n * |sh=ch/max_int |sh=ch/max_int |sh=ch/max_int |\n * |sw=pow(sh,1/w)|sw=pow(sh,1/w)|sw=pow(sh,1/w)|\n * ==============================================\n *\n * We now calculate who has the largest sw, that is the cluster that we are\n * going to map k into:\n * ==============================================\n * | Cluster1     | Cluster2     | Cluster3     |\n *                    ....\n * |sw=pow(sh,1/w)|sw=pow(sh,1/w)|sw=pow(sh,1/w)|\n * ==============================================\n *                      |\n *                     max(sw)\n *                      |\n *                      V\n *                   Cluster\n *\n */\nsize_t RendezvousHash::get(const uint64_t key, const size_t rank) const {\n  return getNthByWeightedHash(key, rank, nullptr);\n}\n\n/*\n * Calculate Hash scaled by weight and return Top N weights.\n * */\nsize_t RendezvousHash::getNthByWeightedHash(\n    const uint64_t key,\n    const size_t rank,\n    std::vector<size_t>* returnRankIds) const {\n  size_t modRank = rank % weights_.size();\n  // optimize if required to return element with max weight, rank ==\n  // weights_.size(), keep track of the maxWeightIndex instead of populating\n  // scaledWeights array.\n  double maxWeight = -1;\n  int maxWeightIndex = 0;\n\n  std::vector<std::pair<double, size_t>> scaledWeights;\n  if (modRank != 0) {\n    scaledWeights.reserve(weights_.size());\n  }\n  for (size_t i = 0; i < weights_.size(); ++i) {\n    const auto& entry = weights_[i];\n    // combine the hash with the cluster together\n    const double combinedHash = computeHash(entry.first + key);\n    // Note that double(UINT64_MAX) cannot be exactly represented as a float. We\n    // ignore the small inaccuracy here.\n    const double scaledHash =\n        (double)combinedHash /\n        static_cast<double>(std::numeric_limits<uint64_t>::max());\n    double scaledWeight = 0;\n    if (entry.second != 0) {\n      scaledWeight = pow(scaledHash, (double)1 / entry.second);\n    }\n    if (modRank == 0) {\n      if (scaledWeight > maxWeight) {\n        maxWeight = scaledWeight;\n        maxWeightIndex = i;\n      }\n    } else {\n      scaledWeights.emplace_back(scaledWeight, i);\n    }\n  }\n\n  size_t rankIndex;\n  if (modRank == 0) {\n    rankIndex = maxWeightIndex;\n  } else {\n    std::nth_element(scaledWeights.begin(),\n                     scaledWeights.begin() + modRank,\n                     scaledWeights.end(),\n                     std::greater<std::pair<double, size_t>>());\n    rankIndex = scaledWeights[modRank].second;\n  }\n\n  if (returnRankIds) {\n    if (modRank == 0) {\n      returnRankIds->push_back(rankIndex);\n    } else {\n      returnRankIds->reserve(modRank);\n      for (size_t i = 0; i < modRank; i++) {\n        returnRankIds->push_back(scaledWeights[i].second);\n      }\n    }\n  }\n\n  return rankIndex;\n}\n\n/*\n * Returns a consistent hash selection of N elements from array.\n *\n * This type of selection only obeys the probability distribution\n * when all weights are identical.\n * */\nstd::vector<size_t> RendezvousHash::selectNUnweighted(const uint64_t key,\n                                                      const size_t rank) const {\n  std::vector<size_t> selection;\n  // shortcut if rank is equal or larger than array size\n  if (rank >= weights_.size()) {\n    selection = std::vector<size_t>(weights_.size());\n    std::generate(\n        selection.begin(), selection.end(), [n = 0]() mutable { return n++; });\n    return selection;\n  }\n\n  getNthByWeightedHash(key, rank, &selection);\n  return selection;\n}\n\nuint64_t RendezvousHash::computeHash(const char* data, size_t len) const {\n  return folly::hash::fnv64_buf_BROKEN(data, len);\n}\n\nuint64_t RendezvousHash::computeHash(uint64_t i) const {\n  return folly::hash::twang_mix64(i);\n}\n\ndouble RendezvousHash::getMaxErrorRate() const {\n  return 0;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/RendezvousHash.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/utils/ConsistentHash.h>\n#include <string>\n#include <vector>\n\nnamespace proxygen {\n/*\n * Weighted Rendezvous Hash is a way to consistently route requests to\n * candidates.\n * Unlike ConsistentHash, Weighted Rendezvous Hash supports the action to\n * reduce the relative weight of a candidate while incurring minimum data\n * movement.\n */\nclass RendezvousHash : public ConsistentHash {\n public:\n  [[nodiscard]] double getMaxErrorRate() const override;\n\n  // Build the list of possible candidates' hash weight and their input weight.\n  // Hash weight is taken as the hash(candidate id), whereas input weight is\n  // the relative weight of traffic that candidate should receive.\n  // Input is a vector of pairs of candidate id and its relative weight.\n  void build(std::vector<std::pair<std::string, uint64_t>>&) override;\n  // Similar to build, but instead the hash weights are already known\n  // beforehand and each candidate has the same relative weight.\n  void buildEqualWeights(std::vector<uint64_t>&);\n\n  [[nodiscard]] size_t get(const uint64_t key,\n                           const size_t rank = 0) const override;\n\n  [[nodiscard]] std::vector<size_t> selectNUnweighted(const uint64_t key,\n                                                      const size_t rank) const;\n\n protected:\n  size_t getNthByWeightedHash(const uint64_t key,\n                              const size_t modRank,\n                              std::vector<size_t>* returnRankIds) const;\n\n  uint64_t computeHash(const char* data, size_t len) const;\n\n  [[nodiscard]] uint64_t computeHash(uint64_t i) const;\n\n  std::vector<std::pair<uint64_t, uint64_t>> weights_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/SafePathUtils.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"SafePathUtils.h\"\n\n#include <algorithm>\n#include <cerrno>\n#include <filesystem>\n#include <fmt/format.h>\n#include <optional>\n#include <string>\n\n#include <folly/String.h>\n#include <folly/container/Foreach.h>\n#include <folly/portability/Stdlib.h>\n\n#ifndef PATH_MAX\n#define PATH_MAX 4096\n#endif\n\n#ifndef MAX_LONG_PATH\n#define MAX_LONG_PATH 32767\n#endif\n\nusing namespace std;\n\nnamespace proxygen {\n\nstd::string SafePath::getPath(const std::string& filePath,\n                              const std::vector<std::string>& allowlist,\n                              bool useRealPaths) {\n  if (allowlist.empty()) {\n    throw std::runtime_error(\"Allowlist is empty!\");\n  }\n\n  auto normalizedPathExpected = getNormalizedPathSafe(filePath);\n  if (normalizedPathExpected.hasError()) {\n    normalizedPathExpected.error().throw_exception();\n  }\n  auto normalizedPath = std::move(normalizedPathExpected).value();\n\n  // If enabled, make sure all paths are real paths to avoid a false mismatch.\n  std::optional<std::string> maybeRealFilePath;\n  std::optional<std::vector<std::string>> maybeRealAllowlist;\n  if (useRealPaths) {\n    auto realPathExpected = getRealPathSafe(filePath);\n    if (realPathExpected.hasError()) {\n      realPathExpected.error().throw_exception();\n    }\n    maybeRealFilePath = std::move(realPathExpected).value();\n\n    std::vector<std::string> realAllowlist;\n    realAllowlist.reserve(allowlist.size());\n    for (const auto& path : allowlist) {\n      auto realPath = getRealPathSafe(path);\n      if (realPath.hasError()) {\n        realPath.error().throw_exception();\n      }\n      realAllowlist.emplace_back(std::move(realPath).value());\n    }\n    maybeRealAllowlist.emplace(std::move(realAllowlist));\n  }\n\n  auto finalFilePath = maybeRealFilePath.value_or(normalizedPath);\n  auto it = maybeRealAllowlist\n                ? std::find(maybeRealAllowlist.value().begin(),\n                            maybeRealAllowlist.value().end(),\n                            finalFilePath)\n                : std::find(allowlist.begin(), allowlist.end(), finalFilePath);\n  auto mismatch = maybeRealAllowlist ? it == maybeRealAllowlist.value().end()\n                                     : it == allowlist.end();\n  if (mismatch) {\n    throw std::runtime_error(\n        fmt::format(\"File path={} doesn't match with any of the allowlisted \"\n                    \"values, used real paths={}, normalized path={}\",\n                    finalFilePath,\n                    useRealPaths,\n                    normalizedPath));\n  }\n  return normalizedPath;\n}\n\nfolly::Expected<std::string, folly::exception_wrapper> SafePath::getPathSafe(\n    const std::string& filePath,\n    const std::string& baseDirectory,\n    bool useRealPaths) {\n  if (baseDirectory.empty()) {\n    return folly::makeUnexpected(\n        folly::make_exception_wrapper<std::runtime_error>(\n            \"Base directory is empty!\"));\n  }\n\n  auto normalizedPathExpected = getNormalizedPathSafe(filePath);\n  if (normalizedPathExpected.hasError()) {\n    return folly::makeUnexpected(std::move(normalizedPathExpected).error());\n  }\n  auto normalizedPath = std::move(normalizedPathExpected).value();\n\n  // If enabled, make sure all paths are real paths to avoid a false mismatch.\n  std::optional<std::string> maybeRealFilePath;\n  std::optional<std::string> maybeRealBaseDirectory;\n  if (useRealPaths) {\n    auto realFilePathExpected = getRealPathSafe(filePath);\n    if (realFilePathExpected.hasError()) {\n      return folly::makeUnexpected(std::move(realFilePathExpected).error());\n    }\n    maybeRealFilePath = std::move(realFilePathExpected).value();\n\n    auto realBaseDirectoryExpected = getRealPathSafe(baseDirectory);\n    if (realBaseDirectoryExpected.hasError()) {\n      return folly::makeUnexpected(\n          std::move(realBaseDirectoryExpected).error());\n    }\n    maybeRealBaseDirectory = std::move(realBaseDirectoryExpected).value();\n  }\n\n  auto finalFilePath = maybeRealFilePath.value_or(normalizedPath);\n  auto finalBaseDirectory = maybeRealBaseDirectory.value_or(baseDirectory);\n  if (!startsWithBaseDir(finalFilePath, finalBaseDirectory)) {\n    return folly::makeUnexpected(\n        folly::make_exception_wrapper<std::runtime_error>(\n            fmt::format(\"File path={} doesn't start with the base \"\n                        \"directory={}, used real paths={}, normalized path={}\",\n                        finalFilePath,\n                        finalBaseDirectory,\n                        useRealPaths,\n                        normalizedPath)));\n  }\n\n  return folly::makeExpected<folly::exception_wrapper>(\n      std::move(normalizedPath));\n}\n\nstd::string SafePath::getPath(const std::string& filePath,\n                              const std::string& baseDirectory,\n                              bool useRealPaths) {\n  auto result = getPathSafe(filePath, baseDirectory, useRealPaths);\n  if (UNLIKELY(result.hasError())) {\n    result.error().throw_exception();\n  }\n\n  return std::move(result).value();\n}\n\nstring SafePath::cleanPath(const string& path) {\n  std::filesystem::path output;\n  std::filesystem::path inPath(path);\n  FOR_EACH_RANGE(it, inPath.begin(), inPath.end()) {\n    if (*it == \".\") {\n      continue;\n    }\n    if (*it == \"..\") {\n      if (output.filename().string() != \"/\") {\n        output = output.parent_path();\n      }\n      continue;\n    }\n    output /= *it;\n  }\n  output.make_preferred();\n  return output.string();\n}\n\nfolly::Expected<std::string, folly::exception_wrapper>\nSafePath::getNormalizedPathSafe(const std::string& filePath) {\n  // cleanPath removes all ../, ./ references from file path\n  // For example, if file path is \"/tmp/../etc/passwd\", cleanPath returns\n  // \"/etc/passwd\"\n  std::string normalizedPath = cleanPath(filePath);\n\n  if (normalizedPath == \"\") {\n    return folly::makeUnexpected(\n        folly::make_exception_wrapper<std::runtime_error>(fmt::format(\n            \"Normalized file path is empty, original path={}\", filePath)));\n  }\n\n  // Preventing null byte injection. \"/etc/passwd\\0.png\" => \"/etc/passwd\"\n  std::string resultPath(normalizedPath.c_str());\n\n  // path length can not exceed maximum path size\n#ifdef _WIN32\n  std::filesystem::path p(normalizedPath);\n  if (p.is_relative()) {\n    // From\n    // https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation\n    // \"Because you cannot use the \"\\\\?\\\" prefix with a relative path,\n    // relative paths are always limited to a total of PATH_MAX characters.\"\n    if (resultPath.length() > PATH_MAX) {\n      return folly::makeUnexpected(folly::make_exception_wrapper<\n                                   std::runtime_error>(fmt::format(\n          \"Normalized file path={} is too long, original path={}, path_max={}\",\n          normalizedPath,\n          filePath,\n          PATH_MAX)));\n    }\n  } else {\n    if (resultPath.length() > MAX_LONG_PATH) {\n      return folly::makeUnexpected(folly::make_exception_wrapper<\n                                   std::runtime_error>(fmt::format(\n          \"Normalized file path={} is too long, original path={}, path_max={}\",\n          normalizedPath,\n          filePath,\n          MAX_LONG_PATH)));\n    }\n  }\n#else\n  if (resultPath.length() > PATH_MAX) {\n    return folly::makeUnexpected(folly::make_exception_wrapper<\n                                 std::runtime_error>(fmt::format(\n        \"Normalized file path={} is too long, original path={}, path_max={}\",\n        normalizedPath,\n        filePath,\n        PATH_MAX)));\n  }\n#endif\n\n  return resultPath;\n}\n\nbool SafePath::startsWithBaseDir(const std::string& filePath,\n                                 const std::string& baseDirectory) {\n  if (filePath == baseDirectory) {\n    return true;\n  }\n\n  // Adding \"/\" at the end of provided base directory so that input like\n  // \"/tmp/root_sensitive\" can not get bypassed where base directory is\n  // \"/tmp/root\"\n  auto pathSeparator =\n      static_cast<char>(std::filesystem::path::preferred_separator);\n  std::string tempBaseDir;\n  tempBaseDir.reserve(baseDirectory.size() + 1);\n  tempBaseDir.append(baseDirectory);\n  if (tempBaseDir.back() != pathSeparator) {\n    tempBaseDir += pathSeparator;\n  }\n\n  return filePath.starts_with(tempBaseDir);\n}\n\nfolly::Expected<std::string, folly::exception_wrapper>\nSafePath::getRealPathSafe(const std::string& filePath) {\n  char* result;\n#ifdef _WIN32\n  char buffer[MAX_LONG_PATH];\n  result = _fullpath(buffer, filePath.c_str(), MAX_LONG_PATH);\n#else\n  char buffer[PATH_MAX];\n  result = realpath(filePath.c_str(), buffer);\n#endif\n  if (result == nullptr) {\n    return folly::makeUnexpected(\n        folly::make_exception_wrapper<std::runtime_error>(\n            fmt::format(\"Unable to read real path={}, errno={}\",\n                        filePath,\n                        folly::errnoStr(errno))));\n  }\n  std::string absolutePath(buffer);\n\n  return absolutePath;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/SafePathUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <algorithm>\n#include <string>\n\n#include <folly/ExceptionWrapper.h>\n#include <folly/Expected.h>\n\nnamespace proxygen {\n\n/**\n * This class provides some APIs for cleaning and validating file path to\n * prevent path traversal attack\n */\nclass SafePath {\n public:\n  /**\n   * Checking against whitelist of permitted values\n   * - normalizes file path\n   * - checks if the normalized path matches with any of the whitelisted values\n   * - if useRealPaths is enabled, then the file path's real path will be used\n   * for validation\n   * - throws error if the normalized path doesn't belong to the whitelist\n   */\n  static std::string getPath(const std::string& filePath,\n                             const std::vector<std::string>& whitelist,\n                             bool useRealPaths = false);\n\n  /**\n   * Checking prefix and suffix of file path\n   * - normalizes file path\n   * - checks if the normalized path starts with the given base directory\n   * - if useRealPaths is enabled, then the file path's real path will be used\n   * for validation\n   * - throws error if the prefix and/or suffix dont match\n   */\n  static std::string getPath(const std::string& filePath,\n                             const std::string& baseDirectory,\n                             bool useRealPaths = false);\n\n  /**\n   * Checking prefix and suffix of file path\n   * - normalizes file path\n   * - checks if the normalized path starts with the given base directory\n   * - if useRealPaths is enabled, then the file path's real path will be used\n   * for validation\n   * - returns unexpected variant if the prefix and/or suffix dont match\n   */\n  static folly::Expected<std::string, folly::exception_wrapper> getPathSafe(\n      const std::string& filePath,\n      const std::string& baseDirectory,\n      bool useRealPaths = false);\n\n  /**\n   * Helper functions have visibility \"private\" so that these can not be called\n   * from outside. We dont want them to be used standalone because they don't\n   * provide complete protection.\n   */\n private:\n  static folly::Expected<std::string, folly::exception_wrapper>\n  getNormalizedPathSafe(const std::string& filePath);\n\n  static folly::Expected<std::string, folly::exception_wrapper> getRealPathSafe(\n      const std::string& filePath);\n\n  static bool startsWithBaseDir(const std::string& filePath,\n                                const std::string& baseDirectory);\n\n  /**\n   * Remove '.' components, '..' components along with the preceding\n   * component if any, duplicate '/'s and trailing '/'.\n   */\n  static std::string cleanPath(const std::string& path);\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/StateMachine.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n#include <limits>\n#include <tuple>\n\nnamespace proxygen {\n\ntemplate <typename T>\nclass StateMachine {\n public:\n  using State = typename T::State;\n  using Event = typename T::Event;\n\n  static State getNewInstance() {\n    return T::getInitialState();\n  }\n\n  static bool transit(State& state, Event event) {\n    bool ok;\n    State newState;\n\n    std::tie(newState, ok) = T::find(state, event);\n    if (!ok) {\n      LOG_EVERY_N(ERROR, 100)\n          << T::getName() << \": invalid transition tried: \" << state << \" \"\n          << event;\n      return false;\n    } else {\n      VLOG(6) << T::getName() << \": transitioning from \" << state << \" to \"\n              << newState;\n      state = newState;\n      return true;\n    }\n  }\n\n  static bool canTransit(const State state, Event event) {\n    bool ok;\n\n    std::tie(std::ignore, ok) = T::find(state, event);\n    return ok;\n  }\n};\n\n/**\n * The transition table is a [N x M] matrix with N rows and M\n * columns.  Each row represents a state, and each column represents\n * an event.  A valid transition (S1, e) -> S2 is represented by\n * storing the index of S2 (the new state) at transitions[S1, e]. An\n * invalid transition is represented by storing a max value instead of\n * S2 index.\n */\ntemplate <class State, class Event>\nclass TransitionTable {\n private:\n  size_t index(State s, Event e) const {\n    return static_cast<uint64_t>(s) * nEvents_ + static_cast<uint64_t>(e);\n  }\n\n  std::vector<uint8_t> transitions_;\n  const size_t nStates_{0};\n  const size_t nEvents_{0};\n\n public:\n  TransitionTable(\n      size_t nStates,\n      size_t nEvents,\n      std::vector<std::pair<std::pair<State, Event>, State>> transitions)\n      : nStates_(nStates), nEvents_(nEvents) {\n    CHECK_LT(static_cast<uint64_t>(nStates),\n             std::numeric_limits<uint8_t>::max());\n    // Set all transitions to invalid\n    transitions_.resize(nStates * nEvents, std::numeric_limits<uint8_t>::max());\n\n    for (auto t : transitions) {\n      auto src_state = t.first.first;\n      auto event = t.first.second;\n      auto dst_state_idx = static_cast<uint8_t>(t.second);\n      transitions_[index(src_state, event)] = dst_state_idx;\n    }\n  }\n\n  TransitionTable() = delete;\n  TransitionTable& operator=(const TransitionTable&) = delete;\n  TransitionTable(const TransitionTable&) = delete;\n  TransitionTable(TransitionTable&& goner)\n      : transitions_(std::move(goner.transitions_)),\n        nStates_(goner.nStates_),\n        nEvents_(goner.nEvents_) {\n  }\n\n  std::pair<State, bool> find(State s, Event e) const {\n    CHECK_LT(static_cast<uint64_t>(s), nStates_);\n    CHECK_LT(static_cast<uint64_t>(e), nEvents_);\n    uint8_t result = transitions_[index(s, e)];\n    if (result == std::numeric_limits<uint8_t>::max()) {\n      return std::make_pair(s, false);\n    }\n    return std::make_pair(State(result), true);\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/StreamCompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n\nnamespace folly {\nclass IOBuf;\n}\n\nnamespace proxygen {\n\n/**\n * Interface to be implemented by all stream compressors\n */\nclass StreamCompressor {\n public:\n  virtual ~StreamCompressor() = default;\n\n  virtual std::unique_ptr<folly::IOBuf> compress(const folly::IOBuf* in,\n                                                 bool trailer = true) = 0;\n  virtual bool hasError() = 0;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/StreamDecompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <memory>\n\nnamespace folly {\nclass IOBuf;\n}\n\nnamespace proxygen {\n\nenum class CompressionType : uint8_t { NONE, DEFLATE, GZIP, ZSTD };\n\n/**\n * Abstract base class for stream decompressor implementations.\n */\nclass StreamDecompressor {\n public:\n  virtual ~StreamDecompressor() = default;\n  virtual std::unique_ptr<folly::IOBuf> decompress(const folly::IOBuf* in) = 0;\n  virtual bool hasError() = 0;\n  virtual bool finished() = 0;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TestUtils.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Random.h>\n#include <folly/Range.h>\n#include <folly/String.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/SysResource.h>\n\n#ifndef NDEBUG\n#define EXPECT_DEATH_NO_CORE(token, regex) \\\n  {                                        \\\n    rlimit oldLim;                         \\\n    getrlimit(RLIMIT_CORE, &oldLim);       \\\n    rlimit newLim{0, oldLim.rlim_max};     \\\n    setrlimit(RLIMIT_CORE, &newLim);       \\\n    EXPECT_DEATH(token, regex);            \\\n    setrlimit(RLIMIT_CORE, &oldLim);       \\\n  }\n#else\n#define EXPECT_DEATH_NO_CORE(tken, regex) \\\n  {                                       \\\n  }\n#endif\n\ninline folly::StringPiece getContainingDirectory(folly::StringPiece input) {\n  auto pos = folly::rfind(input, '/');\n  if (pos == std::string::npos) {\n    pos = 0;\n  } else {\n    pos += 1;\n  }\n  return input.subpiece(0, pos);\n}\n\ninline bool queryStringsAreEquivalent(const std::string& query,\n                                      const std::string& expectedQuery) {\n  std::unordered_set<std::string> urlQueryParams;\n  std::unordered_set<std::string> expectedUrlQueryParams;\n  folly::splitTo<std::string>(\n      '&', query, std::inserter(urlQueryParams, urlQueryParams.begin()));\n  folly::splitTo<std::string>(\n      '&',\n      expectedQuery,\n      std::inserter(expectedUrlQueryParams, expectedUrlQueryParams.begin()));\n\n  if (urlQueryParams.size() != expectedUrlQueryParams.size()) {\n    return false;\n  }\n  for (const auto& element : urlQueryParams) {\n    if (expectedUrlQueryParams.find(element) == expectedUrlQueryParams.end()) {\n      return false;\n    }\n  }\n  return true;\n}\n\ninline bool urlsAreEquivalent(const std::string& url,\n                              const std::string& expectedUrl) {\n  std::vector<std::string> urlFragments;\n  std::vector<std::string> expectedUrlFragments;\n\n  folly::split('#', url, urlFragments, true);\n  folly::split('#', expectedUrl, expectedUrlFragments, true);\n\n  if (urlFragments.size() != expectedUrlFragments.size()) {\n    return false;\n  }\n\n  if (urlFragments.size() == 2 &&\n      (urlFragments[1] != expectedUrlFragments[1])) {\n    return false;\n  }\n\n  std::vector<std::string> urlPreludeAndQuery;\n  std::vector<std::string> expectedUrlPreludeAndQuery;\n\n  folly::split('?', urlFragments[0], urlPreludeAndQuery, true);\n  folly::split('?', expectedUrlFragments[0], expectedUrlPreludeAndQuery, true);\n\n  if (urlPreludeAndQuery.size() != expectedUrlPreludeAndQuery.size()) {\n    return false;\n  }\n\n  if (urlPreludeAndQuery[0] != expectedUrlPreludeAndQuery[0]) {\n    return false;\n  }\n\n  if (urlPreludeAndQuery.size() == 2) {\n    return queryStringsAreEquivalent(urlPreludeAndQuery[1],\n                                     expectedUrlPreludeAndQuery[1]);\n  } else {\n    return true;\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/utils/Time.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <folly/portability/Time.h>\n#include <memory>\n#include <string>\n\nnamespace proxygen {\n\nusing SteadyClock = std::chrono::steady_clock;\nusing SystemClock = std::chrono::system_clock;\nusing TimePoint = SteadyClock::time_point;\nusing SystemTimePoint = SystemClock::time_point;\nusing SteadyTimePoint = SteadyClock::time_point;\n\ntemplate <typename T>\nbool durationInitialized(const T& duration) {\n  static T zero(0);\n  return duration != T::max() && duration >= zero;\n}\n\ntemplate <typename T>\nbool timePointInitialized(const T& time) {\n  static T epoch;\n  return time > epoch;\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::time_point<ClockType> getCurrentTime() {\n  return ClockType::now();\n}\n\ninline std::chrono::system_clock::time_point toSystemTimePoint(TimePoint t) {\n  return std::chrono::system_clock::now() +\n         std::chrono::duration_cast<std::chrono::system_clock::duration>(\n             t - SteadyClock::now());\n}\n\ninline time_t toTimeT(TimePoint t) {\n  return std::chrono::system_clock::to_time_t(toSystemTimePoint(t));\n}\n\ninline std::chrono::microseconds microsecondsSinceEpoch() {\n  return std::chrono::duration_cast<std::chrono::microseconds>(\n      std::chrono::system_clock::now().time_since_epoch());\n}\n\ninline std::chrono::milliseconds millisecondsSinceEpoch() {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::system_clock::now().time_since_epoch());\n}\n\ninline std::chrono::seconds secondsSinceEpoch() {\n  return std::chrono::duration_cast<std::chrono::seconds>(\n      std::chrono::system_clock::now().time_since_epoch());\n}\n\ninline std::chrono::microseconds microsecondsSinceEpoch(TimePoint t) {\n  return std::chrono::duration_cast<std::chrono::microseconds>(\n      toSystemTimePoint(t).time_since_epoch());\n}\n\ninline std::chrono::milliseconds millisecondsSinceEpoch(TimePoint t) {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(\n      toSystemTimePoint(t).time_since_epoch());\n}\n\ninline std::chrono::seconds secondsSinceEpoch(TimePoint t) {\n  return std::chrono::duration_cast<std::chrono::seconds>(\n      toSystemTimePoint(t).time_since_epoch());\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::microseconds microsecondsBetween(\n    std::chrono::time_point<ClockType> finish,\n    std::chrono::time_point<ClockType> start) {\n  return std::chrono::duration_cast<std::chrono::microseconds>(finish - start);\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::milliseconds millisecondsBetween(\n    std::chrono::time_point<ClockType> finish,\n    std::chrono::time_point<ClockType> start) {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::seconds secondsBetween(\n    std::chrono::time_point<ClockType> finish,\n    std::chrono::time_point<ClockType> start) {\n  return std::chrono::duration_cast<std::chrono::seconds>(finish - start);\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::milliseconds millisecondsSince(\n    std::chrono::time_point<ClockType> t) {\n  return millisecondsBetween(getCurrentTime<ClockType>(), t);\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::microseconds microsecondsSince(\n    std::chrono::time_point<ClockType> t) {\n  return microsecondsBetween(getCurrentTime<ClockType>(), t);\n}\n\ntemplate <typename ClockType = SteadyClock>\ninline std::chrono::seconds secondsSince(std::chrono::time_point<ClockType> t) {\n  return secondsBetween(getCurrentTime<ClockType>(), t);\n}\n\n/**\n * Get the current date and time in string formats: %Y-%m-%d and %H:%M:%S.\n */\ninline void getDateTimeStr(char datebuf[32], char timebuf[32]) {\n  time_t now = toTimeT(getCurrentTime<SteadyClock>());\n  struct tm now_tm;\n  localtime_r(&now, &now_tm);\n  if (datebuf) {\n    strftime(datebuf, sizeof(char) * 32, \"%Y-%m-%d\", &now_tm);\n  }\n  if (timebuf) {\n    strftime(timebuf, sizeof(char) * 32, \"%H:%M:%S\", &now_tm);\n  }\n}\n\n/**\n * Get the current date + offset days in %Y-%m-%d format.\n */\ninline void getDateOffsetStr(char datebuf[32], int dayOffset) {\n  time_t t = toTimeT(getCurrentTime<SteadyClock>());\n  t += dayOffset * 24 * 60 * 60;\n  struct tm final_tm;\n  localtime_r(&t, &final_tm);\n  strftime(datebuf, sizeof(char) * 32, \"%Y-%m-%d\", &final_tm);\n}\n\n/**\n * Helper method to convert TimePoint to a printable date and time string. It\n * will convert static time to system time.\n *\n * @param tp      TimePoint\n * @return        a human readable date and time string at UTC timezone.\n *                If there is any error, returns empty string.\n */\ninline std::string getDateTimeStr(TimePoint tp) {\n  time_t t = toTimeT(tp);\n  struct tm final_tm;\n  gmtime_r(&t, &final_tm);\n  char buf[256];\n  if (strftime(buf, sizeof(buf), \"%Y-%m-%dT%H:%M:%S %z\", &final_tm) > 0) {\n    return std::string(buf);\n  }\n  return \"\";\n}\n\n/**\n * Class used to get steady time. We use a separate class to mock it easier.\n */\ntemplate <typename ClockType = SteadyClock>\nclass TimeUtilGeneric {\n public:\n  virtual ~TimeUtilGeneric() = default;\n\n  [[nodiscard]] virtual std::chrono::time_point<ClockType> now() const {\n    return getCurrentTime<ClockType>();\n  }\n\n  static const std::chrono::time_point<ClockType>& getZeroTimePoint() {\n    const static std::chrono::time_point<ClockType> kZeroTimePoint{};\n    return kZeroTimePoint;\n  }\n\n  /**\n   * Please use strongly typed time_point. This is for avoiding the copy and\n   * garbage collection of time_point in Lua.\n   */\n  virtual uint64_t msSinceEpoch() {\n    return millisecondsSinceEpoch().count();\n  }\n\n  virtual uint64_t microsSinceEpoch() {\n    return microsecondsSinceEpoch().count();\n  }\n};\n\n// Typedef so as to not disrupting callers who use 'TimeUtil' before we\n// made it TimeUtilGeneric\nusing TimeUtil = TimeUtilGeneric<>;\n\ntemplate <typename Duration>\nclass StopWatch {\n  /**\n   * A stopwatch that can be started and stopped. Elapsed time will continue to\n   * be accumulated until reset is called.\n   *\n   * Note: Calling a start on a running stopwatch has the effect of reseting\n   * the initial checkpoint.\n   */\n public:\n  class TimedScope {\n   public:\n    explicit TimedScope(StopWatch<Duration>& stopWatch)\n        : stopWatch_(stopWatch) {\n      stopWatch_.start();\n    }\n\n    ~TimedScope() {\n      stopWatch_.stop();\n    }\n\n   private:\n    StopWatch<Duration>& stopWatch_;\n  };\n\n  explicit StopWatch(std::shared_ptr<TimeUtil> timeSource = nullptr) {\n    if (!timeSource) {\n      timeSource = std::make_shared<TimeUtil>();\n    }\n    timeSource_ = timeSource;\n  }\n\n  void start() {\n    start_ = timeSource_->now();\n    running_ = true;\n  }\n\n  void stop() {\n    if (!running_) {\n      return;\n    }\n    elapsed_ +=\n        std::chrono::duration_cast<Duration>(timeSource_->now() - start_);\n    running_ = false;\n  }\n\n  void reset() {\n    elapsed_ = elapsed_.zero();\n    running_ = false;\n  }\n\n  [[nodiscard]] bool running() const {\n    return running_;\n  }\n\n  Duration getElapsedTime() const {\n    return elapsed_;\n  }\n\n  TimedScope createTimedScope() {\n    return TimedScope(*this);\n  }\n\n protected:\n  bool running_{false};\n  std::shared_ptr<TimeUtil> timeSource_;\n  TimePoint start_;\n  Duration elapsed_{0};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TraceEvent.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/TraceEvent.h>\n\n#include <atomic>\n#include <folly/json/json.h>\n#include <sstream>\n#include <string>\n\nnamespace proxygen {\n\n// Helpers used to make TraceEventType/TraceFieldType can be used with GLOG\nstd::ostream& operator<<(std::ostream& os, TraceEventType eventType) {\n  os << getTraceEventTypeString(eventType);\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, TraceFieldType fieldType) {\n  os << getTraceFieldTypeString(fieldType);\n  return os;\n}\n\nTraceEvent::TraceEvent(TraceEventType type, uint32_t parentID)\n    : type_(type), parentID_(parentID) {\n  static std::atomic<uint32_t> counter(0);\n  id_ = counter++;\n}\n\nvoid TraceEvent::start(const TimeUtil& tm) {\n  stateFlags_ |= State::STARTED;\n  start_ = tm.now();\n}\n\nvoid TraceEvent::start(TimePoint startTime) {\n  stateFlags_ |= State::STARTED;\n  start_ = startTime;\n}\n\nvoid TraceEvent::end(const TimeUtil& tm) {\n  stateFlags_ |= State::ENDED;\n  end_ = tm.now();\n}\n\nvoid TraceEvent::end(TimePoint endTime) {\n  stateFlags_ |= State::ENDED;\n  end_ = endTime;\n}\n\nbool TraceEvent::hasStarted() const {\n  return stateFlags_ & State::STARTED;\n}\n\nbool TraceEvent::hasEnded() const {\n  return stateFlags_ & State::ENDED;\n}\n\nbool TraceEvent::readBoolMeta(TraceFieldType key, bool& dest) const {\n  return readMeta(key, dest);\n}\n\nbool TraceEvent::readStrMeta(TraceFieldType key, std::string& dest) const {\n  return readMeta(key, dest);\n}\nbool TraceEvent::addMetaInternal(TraceFieldType key, MetaData&& value) {\n  auto rc = metaData_.emplace(key, value);\n\n  // replace if key already exist\n  if (!rc.second) {\n    rc.first->second = std::move(value);\n  }\n\n  return rc.second;\n}\n\nstd::string TraceEvent::toString() const {\n  std::ostringstream out;\n  int startSinceEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(\n                            start_.time_since_epoch())\n                            .count();\n  int endSinceEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(\n                          end_.time_since_epoch())\n                          .count();\n  out << \"TraceEvent(\";\n  out << \"type='\" << getTraceEventTypeString(type_) << \"', \";\n  out << \"id='\" << id_ << \"', \";\n  out << \"parentID='\" << parentID_ << \"', \";\n  out << \"start='\" << startSinceEpoch << \"', \";\n  out << \"end='\" << endSinceEpoch << \"', \";\n  out << \"metaData='{\";\n  auto itr = getMetaDataItr();\n  while (itr.isValid()) {\n    out << getTraceFieldTypeString(itr.getKey()) << \": \"\n        << itr.getValueAs<std::string>() << \", \";\n    itr.next();\n  }\n  out << \"}')\";\n  return out.str();\n}\n\nstd::string TraceEvent::MetaData::ConvVisitor<std::string>::operator()(\n    const std::vector<std::string>& operand) const {\n  // parse string vector to json string.\n  folly::dynamic data = folly::dynamic::array;\n  for (auto item : operand) {\n    data.push_back(item);\n  }\n  return folly::toJson(data);\n}\n\nstd::ostream& operator<<(std::ostream& out, const TraceEvent& event) {\n  out << event.toString();\n  return out;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TraceEvent.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/utils/Exception.h>\n#include <proxygen/lib/utils/Export.h>\n#include <proxygen/lib/utils/Time.h>\n#include <proxygen/lib/utils/TraceEventType.h>\n#include <proxygen/lib/utils/TraceFieldType.h>\n\n#include <glog/logging.h>\n\n#include <folly/Conv.h>\n#include <folly/lang/Exception.h>\n\n#include <map>\n#include <string>\n#include <variant>\n#include <vector>\n\nnamespace proxygen {\n// Helpers used to make TraceEventType/TraceFieldType can be used with GLOG\nFB_EXPORT std::ostream& operator<<(std::ostream& os, TraceEventType eventType);\nFB_EXPORT std::ostream& operator<<(std::ostream& os, TraceFieldType fieldType);\n\n/**\n * Simple structure to track timing of event in request flow then we can\n * report back to the application.\n */\nclass TraceEvent {\n public:\n  struct MetaData {\n   public:\n    using MetaDataType =\n        std::variant<int64_t, std::string, std::vector<std::string>>;\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_integral<T>::value,\n                                                 void>::type>\n    /* implicit */ MetaData(T value) : value_(folly::to<int64_t>(value)) {\n    }\n\n    /* implicit */ MetaData(const std::string& value) : value_(value) {\n    }\n\n    /* implicit */ MetaData(std::string&& value) : value_(std::move(value)) {\n    }\n\n    /* implicit */ MetaData(const char* value) : value_(std::string(value)) {\n    }\n\n    /* implicit */ MetaData(const folly::fbstring& value)\n        : value_(value.toStdString()) {\n    }\n\n    /* implicit */ MetaData(const std::vector<std::string>& value)\n        : value_(value) {\n    }\n\n    /* implicit */ MetaData(std::vector<std::string>&& value)\n        : value_(std::move(value)) {\n    }\n\n    template <typename T>\n    T getValueAs() const {\n      ConvVisitor<T> visitor;\n      return std::visit(visitor, value_);\n    }\n\n    [[nodiscard]] const std::type_info& type() const {\n      return std::visit(\n          [](const auto& v) -> const std::type_info& { return typeid(v); },\n          value_);\n    }\n\n    template <typename T>\n    struct ConvVisitor {\n      T operator()(const std::vector<std::string>& /* Unused */) const {\n        folly::throw_exception<Exception>(\"Not supported for type\");\n      }\n\n      template <typename U>\n      T operator()(U& operand) const {\n        return folly::to<T>(operand);\n      }\n    };\n\n    MetaDataType value_;\n  };\n\n  using MetaDataMap = std::map<TraceFieldType, MetaData>;\n\n  class Iterator {\n   public:\n    explicit Iterator(const TraceEvent& event)\n        : event_(event), itr_(event.metaData_.begin()) {\n    }\n\n    ~Iterator() = default;\n\n    void next() {\n      ++itr_;\n    }\n\n    [[nodiscard]] bool isValid() const {\n      return itr_ != event_.metaData_.end();\n    }\n\n    [[nodiscard]] TraceFieldType getKey() const {\n      return itr_->first;\n    }\n\n    template <typename T>\n    T getValueAs() const {\n      return itr_->second.getValueAs<T>();\n    }\n\n    [[nodiscard]] const std::type_info& type() const {\n      return itr_->second.type();\n    }\n\n   private:\n    const TraceEvent& event_;\n    MetaDataMap::const_iterator itr_;\n  };\n\n  FB_EXPORT explicit TraceEvent(TraceEventType type, uint32_t parentID = 0);\n\n  /**\n   * Sets the start time to the current time according to the TimeUtil.\n   */\n  void start(const TimeUtil& tm);\n\n  /**\n   * Sets the start time to the given TimePoint.\n   */\n  void start(TimePoint startTime);\n\n  /**\n   * Sets the end time to the current time according to the TimeUtil.\n   */\n  void end(const TimeUtil& tm);\n\n  /**\n   * Sets the end time to the given TimePoint.\n   */\n  void end(TimePoint endTime);\n\n  /**\n   * @Returns true iff start() has been called on this TraceEvent.\n   */\n  [[nodiscard]] bool hasStarted() const;\n\n  /**\n   * @Returns true iff end() has been called on this TraceEvent.\n   */\n  [[nodiscard]] bool hasEnded() const;\n\n  [[nodiscard]] TimePoint getStartTime() const {\n    return start_;\n  }\n\n  [[nodiscard]] TimePoint getEndTime() const {\n    return end_;\n  }\n\n  [[nodiscard]] TraceEventType getType() const {\n    return type_;\n  }\n\n  [[nodiscard]] uint32_t getID() const {\n    return id_;\n  }\n\n  void setParentID(uint32_t parent) {\n    parentID_ = parent;\n  }\n\n  [[nodiscard]] uint32_t getParentID() const {\n    return parentID_;\n  }\n\n  [[nodiscard]] bool hasTraceField(TraceFieldType field) const {\n    return metaData_.count(field);\n  }\n\n  template <typename T>\n  T getTraceFieldDataAs(TraceFieldType field) const {\n    const auto itr = metaData_.find(field);\n    CHECK(itr != metaData_.end());\n    return itr->second.getValueAs<T>();\n  }\n\n  void setMetaData(MetaDataMap&& input) {\n    metaData_ = std::move(input);\n  }\n\n  [[nodiscard]] const MetaDataMap& getMetaData() const {\n    return metaData_;\n  }\n\n  [[nodiscard]] Iterator getMetaDataItr() const {\n    return Iterator(*this);\n  }\n\n  template <typename T>\n  bool addMeta(TraceFieldType key, T&& value) {\n    MetaData val(std::forward<T>(value));\n    return addMetaInternal(key, std::move(val));\n  }\n\n  template <typename T>\n  bool addMeta(TraceFieldType key, const T& value) {\n    MetaData val(value);\n    return addMetaInternal(key, std::move(val));\n  }\n\n  template <typename T>\n  bool readIntMeta(TraceFieldType key, T& dest) const {\n    static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value,\n                  \"readIntMeta should take an intergral type of paremeter\");\n    return readMeta(key, dest);\n  }\n\n  bool readBoolMeta(TraceFieldType key, bool& dest) const;\n\n  bool readStrMeta(TraceFieldType key, std::string& dest) const;\n\n  [[nodiscard]] std::string toString() const;\n\n  friend std::ostream& operator<<(std::ostream& out, const TraceEvent& event);\n\n  friend class Iterator;\n\n private:\n  template <typename T>\n  bool readMeta(TraceFieldType key, T& dest) const {\n    const auto itr = metaData_.find(key);\n    if (itr != metaData_.end()) {\n      return folly::catch_exception(\n          [&]() -> bool {\n            dest = itr->second.getValueAs<T>();\n            return true;\n          },\n          +[]() -> bool { return false; });\n    }\n    return false;\n  }\n\n  FB_EXPORT bool addMetaInternal(TraceFieldType key, MetaData&& val);\n\n  enum State {\n    NOT_STARTED = 0,\n    STARTED = 1,\n    ENDED = 2,\n  };\n\n  uint8_t stateFlags_{0};\n  TraceEventType type_;\n  uint32_t id_;\n  uint32_t parentID_;\n  TimePoint start_;\n  TimePoint end_;\n  MetaDataMap metaData_;\n};\n\ntemplate <>\nstruct TraceEvent::MetaData::ConvVisitor<std::vector<std::string>> {\n  std::vector<std::string> operator()(\n      const std::vector<std::string>& operand) const {\n    return operand;\n  }\n\n  template <typename U>\n  std::vector<std::string> operator()(U& /* Unused */) const {\n    folly::throw_exception<Exception>(\"Not supported for type\");\n  }\n};\n\ntemplate <>\nstruct TraceEvent::MetaData::ConvVisitor<std::string> {\n  std::string operator()(const std::vector<std::string>& operand) const;\n\n  template <typename U>\n  std::string operator()(U& operand) const {\n    return folly::to<std::string>(operand);\n  }\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TraceEventContext.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/TraceEventContext.h>\n#include <proxygen/lib/utils/TraceEventObserver.h>\n\nnamespace proxygen {\n\nvoid TraceEventContext::traceEventAvailable(const TraceEvent& event) {\n  for (const auto observer : observers_) {\n    observer->traceEventAvailable(event);\n  }\n}\n\nbool TraceEventContext::isAllTraceEventNeeded() const {\n  return allTraceEventNeeded_;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TraceEventContext.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <cstdint>\n#include <vector>\n\nnamespace proxygen {\n\nstruct TraceEventObserver;\nclass TraceEvent;\n\nclass TraceEventContext {\n public:\n  // Optional parent id for all sub trace events to add.\n  uint32_t parentID;\n\n  TraceEventContext(uint32_t pID,\n                    std::vector<TraceEventObserver*> observers,\n                    bool allTraceEventNeeded = false)\n      : parentID(pID),\n        observers_(std::move(observers)),\n        allTraceEventNeeded_(allTraceEventNeeded) {\n  }\n\n  explicit TraceEventContext(uint32_t pID = 0,\n                             TraceEventObserver* observer = nullptr,\n                             bool allTraceEventNeeded = false)\n      : parentID(pID), allTraceEventNeeded_(allTraceEventNeeded) {\n    if (observer) {\n      observers_.push_back(observer);\n    }\n  }\n\n  void traceEventAvailable(const TraceEvent& event);\n\n  [[nodiscard]] bool isAllTraceEventNeeded() const;\n\n private:\n  // Observer vector to observe all trace events about to occur\n  std::vector<TraceEventObserver*> observers_;\n\n  // Whether the observers actually care about all trace events from this\n  // context or only necessary ones.\n  bool allTraceEventNeeded_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/TraceEventObserver.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <proxygen/lib/utils/TraceEvent.h>\n\nnamespace proxygen {\n\n/*\n * Observer interface to record trace events.\n *\n * Subclasses of TraceEventObserver may log the trace events\n * to a destination analytics pipeline or forward them elsewhere.\n */\nstruct TraceEventObserver {\n  virtual ~TraceEventObserver() = default;\n  /**\n   * Lets the handler receive an arbitrary TraceEvent.\n   */\n  virtual void traceEventAvailable(TraceEvent) noexcept {\n  }\n  virtual void emitTraceEvents(std::vector<TraceEvent>) noexcept {\n  }\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/URL.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <sstream>\n#include <string>\n#include <string_view>\n#include <utility>\n\n#include <folly/String.h>\n#include <folly/portability/Windows.h> // for windows compatibility: STRICT maybe defined by some win headers\n#include <proxygen/lib/utils/ParseURL.h>\n\n#ifdef STRICT\n#undef STRICT\n#endif\n\n#ifdef STRICT_COMPAT\n#undef STRICT_COMPAT\n#endif\n\nnamespace proxygen {\n\n/**\n * Struct representing a URL.\n */\n\nclass URL {\n public:\n  URL() = default;\n\n  enum class Mode { STRICT_COMPAT, STRICT };\n  explicit URL(std::string_view url,\n               bool secure = false,\n               Mode strict = Mode::STRICT_COMPAT) noexcept\n      : URL(ParseURL::parseURLMaybeInvalid(url, strict == Mode::STRICT),\n            secure,\n            strict) {\n  }\n\n  explicit URL(ParseURL parseUrl,\n               bool secure = false,\n               Mode strict = Mode::STRICT_COMPAT) noexcept\n      : host_(parseUrl.hostNoBrackets()),\n        path_(parseUrl.path()),\n        query_(parseUrl.query()),\n        fragment_(parseUrl.fragment()),\n        url_(parseUrl.url()) {\n    setScheme(std::string(parseUrl.scheme()), secure);\n\n    if (parseUrl.port()) {\n      port_ = parseUrl.port();\n    } else {\n      port_ = isSecure() ? 443 : 80;\n    }\n\n    if (strict == Mode::STRICT) {\n      valid_ &= parseUrl.valid();\n    }\n    // TODO: In STRICT_COMPAT, parseUrl.valid() is not checked, so URL.valid()\n    // can be true so long as the scheme is http(s).\n  }\n\n  static std::string createUrl(std::string_view scheme,\n                               std::string_view hostAndPort,\n                               std::string_view path,\n                               std::string_view query,\n                               std::string_view fragment) noexcept {\n    std::ostringstream out;\n    out << scheme << \"://\" << hostAndPort << '/' << path;\n    if (!query.empty()) {\n      out << '?' << query;\n    }\n    if (!fragment.empty()) {\n      out << '#' << fragment;\n    }\n    return out.str();\n  }\n\n  URL(std::string scheme,\n      std::string host,\n      uint16_t port = 0,\n      std::string path = \"\",\n      std::string query = \"\",\n      std::string fragment = \"\") noexcept\n      : host_(std::move(host)),\n        port_(port),\n        path_(std::move(path)),\n        query_(std::move(query)),\n        fragment_(std::move(fragment)) {\n    setScheme(std::move(scheme), false);\n    url_ = createUrl(scheme_, getHostAndPort(), path_, query_, fragment_);\n\n    if (port_ == 0) {\n      port_ = isSecure() ? 443 : 80;\n    }\n  }\n\n  [[nodiscard]] bool isValid() const noexcept {\n    return valid_;\n  }\n\n  [[nodiscard]] const std::string& getUrl() const noexcept {\n    return url_;\n  }\n\n  [[nodiscard]] uint16_t getPort() const noexcept {\n    return port_;\n  }\n\n  [[nodiscard]] const std::string& getScheme() const noexcept {\n    return scheme_;\n  }\n\n  [[nodiscard]] bool isSecure() const noexcept {\n    return scheme_ == \"https\";\n  }\n\n  [[nodiscard]] bool hasHost() const noexcept {\n    return valid_ && !host_.empty();\n  }\n\n  [[nodiscard]] const std::string& getHost() const noexcept {\n    return host_;\n  }\n\n  [[nodiscard]] std::string getHostAndPort() const noexcept {\n    if (port_ == 0) {\n      return host_;\n    }\n    std::stringstream ss;\n    ss << host_ << \":\" << port_;\n    return ss.str();\n  }\n\n  [[nodiscard]] std::string getHostAndPortOmitDefault() const noexcept {\n    bool defaultPort = port_ == 0 || port_ == (isSecure() ? 443 : 80);\n    if (defaultPort) {\n      return host_;\n    }\n    std::stringstream ss;\n    ss << host_ << \":\" << port_;\n    return ss.str();\n  }\n\n  [[nodiscard]] const std::string& getPath() const noexcept {\n    return path_;\n  }\n\n  [[nodiscard]] const std::string& getQuery() const noexcept {\n    return query_;\n  }\n\n  [[nodiscard]] const std::string& getFragment() const noexcept {\n    return fragment_;\n  }\n\n  [[nodiscard]] std::string makeRelativeURL() const noexcept {\n    std::stringstream ss;\n    if (path_.empty()) {\n      ss << \"/\";\n    } else {\n      ss << path_;\n    }\n    if (!query_.empty()) {\n      ss << '?' << query_;\n    }\n    if (!fragment_.empty()) {\n      ss << '#' << fragment_;\n    }\n    return ss.str();\n  }\n\n  friend bool operator==(const URL& lhs, const URL& rhs) {\n    return lhs.getScheme() == rhs.getScheme() &&\n           lhs.getHost() == rhs.getHost() && lhs.getPort() == rhs.getPort() &&\n           lhs.getPath() == rhs.getPath() && lhs.getQuery() == rhs.getQuery() &&\n           lhs.getFragment() == rhs.getFragment() &&\n           lhs.getUrl() == rhs.getUrl();\n  }\n\n  friend bool operator!=(const URL& lhs, const URL& rhs) {\n    return !(lhs == rhs);\n  }\n\n private:\n  void setScheme(std::string scheme, bool secure) {\n    // empty scheme means it wasn't specified.  Caller can force it to secure\n    if (scheme.empty() && secure) {\n      scheme_ = \"https\";\n    } else {\n      scheme_ = std::move(scheme);\n      folly::toLowerAscii(scheme_);\n    }\n\n    valid_ = (scheme_ == \"http\" || scheme_ == \"https\");\n  }\n\n  std::string scheme_;\n  std::string host_;\n  uint16_t port_;\n  std::string path_;\n  std::string query_;\n  std::string fragment_;\n\n  std::string url_;\n\n  /* Does this represent a valid URL */\n  bool valid_{false};\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/UnionBasedStatic.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n// This pattern is used to create a static whose destructor will never be\n// called. This is useful since some proxygen clients call exit() directly in\n// multithreaded programs explicitly against recommendations which lead to\n// shutdown crashes due to dependent statics being cleaned up while the threads\n// are still executing.\n\n// IMPLEMENTATION MACROS\n// (Don't use these directly.)\n#define DECLARE_UNION_STATIC_UNION_IMPL(type, name) \\\n  union name##Union {                               \\\n    type data;                                      \\\n    name##Union() {                                 \\\n    }                                               \\\n    ~name##Union() {                                \\\n    }                                               \\\n  }\n\n#define DECLARE_UNION_STATIC_UNION_ARRAY_IMPL(type, size, name) \\\n  union name##Union {                                           \\\n    type data[size];                                            \\\n    name##Union() {                                             \\\n    }                                                           \\\n    ~name##Union() {                                            \\\n    }                                                           \\\n  }\n\n#define DEFINE_UNION_STATIC_UNION_IMPL(type, name, var) \\\n  DECLARE_UNION_STATIC_UNION_IMPL(type, name) var\n\n#define DEFINE_UNION_STATIC_UNION_CONST_IMPL(type, name, var) \\\n  DECLARE_UNION_STATIC_UNION_IMPL(type const, name) var\n\n#define DEFINE_UNION_STATIC_UNION_CONST_ARRAY_IMPL(type, size, name, var) \\\n  DECLARE_UNION_STATIC_UNION_ARRAY_IMPL(type const, size, name) var\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#define ATTRIBUTE_CONSTRUCTOR\n#else\n#define ATTRIBUTE_CONSTRUCTOR __attribute__((__constructor__))\n#endif\n\n// The const_casts are only needed if creating a const union but it's a\n// no-op otherwise so keep it to avoid creating even more macro helpers.\n#if defined(_MSC_VER) && !defined(__clang__)\n#define DEFINE_UNION_STATIC_CONSTRUCTOR_IMPL(type, name, var) \\\n  ATTRIBUTE_CONSTRUCTOR                                       \\\n  void init##name##Union() {                                  \\\n    new (const_cast<type*>(&var.data)) type();                \\\n  }                                                           \\\n                                                              \\\n  static struct staticInit##name##UnionStruct {               \\\n    staticInit##name##UnionStruct() {                         \\\n      init##name##Union();                                    \\\n    }                                                         \\\n  } staticInit##name##Union;\n#else\n#define DEFINE_UNION_STATIC_CONSTRUCTOR_IMPL(type, name, var) \\\n  ATTRIBUTE_CONSTRUCTOR                                       \\\n  void init##name##Union() {                                  \\\n    new (const_cast<type*>(&var.data)) type();                \\\n  }\n#endif\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#define DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var, ...) \\\n  ATTRIBUTE_CONSTRUCTOR                                                \\\n  void init##name##Union() {                                           \\\n    new (const_cast<type*>(&var.data)) type(__VA_ARGS__);              \\\n  }                                                                    \\\n                                                                       \\\n  static struct staticInit##name##UnionStruct {                        \\\n    staticInit##name##UnionStruct() {                                  \\\n      init##name##Union();                                             \\\n    }                                                                  \\\n  } staticInit##name##Union;\n#else\n#define DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var, ...) \\\n  ATTRIBUTE_CONSTRUCTOR                                                \\\n  void init##name##Union() {                                           \\\n    new (const_cast<type*>(&var.data)) type(__VA_ARGS__);              \\\n  }\n#endif\n// END IMPLEMENTATION MACROS\n\n// Use var.data to access the actual member of interest. Zero and argument\n// versions are provided. If you need to do custom construction, like using a\n// brace-enclosed initializer list, use the NO_INIT variant and then define\n// your own __attribute__((__constructor__)) function to do the initialization.\n#define DEFINE_UNION_STATIC(type, name, var)       \\\n  DEFINE_UNION_STATIC_UNION_IMPL(type, name, var); \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_IMPL(type, name, var)\n\n#define DEFINE_UNION_STATIC_ARGS(type, name, var, ...) \\\n  DEFINE_UNION_STATIC_UNION_IMPL(type, name, var);     \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var, __VA_ARGS__)\n\n#define DEFINE_UNION_STATIC_NO_INIT(type, name, var) \\\n  DEFINE_UNION_STATIC_UNION_IMPL(type, name, var)\n\n// Same as the above three except used to create a const union.\n#define DEFINE_UNION_STATIC_CONST(type, name, var)       \\\n  DEFINE_UNION_STATIC_UNION_CONST_IMPL(type, name, var); \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_IMPL(type, name, var)\n\n#define DEFINE_UNION_STATIC_CONST_ARGS(type, name, var, ...) \\\n  DEFINE_UNION_STATIC_UNION_CONST_IMPL(type, name, var);     \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var, __VA_ARGS__)\n\n#define DEFINE_UNION_STATIC_CONST_NO_INIT(type, name, var) \\\n  DEFINE_UNION_STATIC_UNION_CONST_IMPL(type, name, var)\n\n#define DEFINE_UNION_STATIC_CONST_ARRAY_NO_INIT(type, size, name, var) \\\n  DEFINE_UNION_STATIC_UNION_CONST_ARRAY_IMPL(type, size, name, var)\n\n// Use these if you need to extern one of these in a header and then\n// define it in a .cpp file. For example:\n//\n// Header:\n// DECLARE_UNION_STATIC(std::string, StdString);\n// extern const StdStringUnion kStringConstant;\n//\n// Definition:\n// const IMPLEMENT_DECLARED_UNION_STATIC_ARGS(\n//   std::string, StdString, DoesNotMatter, kStringConstant, \"hello world\");\n//\n#define DECLARE_UNION_STATIC(type, name) \\\n  DECLARE_UNION_STATIC_UNION_IMPL(type, name)\n\n#define IMPLEMENT_DECLARED_UNION_STATIC(type, unionName, name, var) \\\n  unionName##Union var;                                             \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var)\n\n#define IMPLEMENT_DECLARED_UNION_STATIC_ARGS(type, unionName, name, var, ...) \\\n  unionName##Union var;                                                       \\\n  DEFINE_UNION_STATIC_CONSTRUCTOR_ARG_IMPL(type, name, var, __VA_ARGS__)\n"
  },
  {
    "path": "proxygen/lib/utils/UtilInl.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/Range.h>\n#include <folly/portability/Windows.h> // for windows compatibility: STRICT maybe defined by some win headers\n\n#ifdef STRICT\n#undef STRICT\n#endif\n\n#ifdef STRICT_COMPAT\n#undef STRICT_COMPAT\n#endif\n\nnamespace proxygen {\n\n// Case-insensitive string comparison\ninline bool caseInsensitiveEqual(folly::StringPiece s, folly::StringPiece t) {\n  return s.equals(t, folly::AsciiCaseInsensitive{});\n}\n\nstruct AsciiCaseUnderscoreInsensitive {\n  bool operator()(char lhs, char rhs) const {\n    if (lhs == '_') {\n      lhs = '-';\n    }\n    if (rhs == '_') {\n      rhs = '-';\n    }\n    return folly::AsciiCaseInsensitive()(lhs, rhs);\n  }\n};\n\n// Case-insensitive string comparison\ninline bool caseUnderscoreInsensitiveEqual(folly::StringPiece s,\n                                           folly::StringPiece t) {\n  return s.equals(t, AsciiCaseUnderscoreInsensitive{});\n}\n\nenum class URLValidateMode { STRICT_COMPAT, STRICT };\ninline bool validateURL(std::string_view url,\n                        URLValidateMode mode = URLValidateMode::STRICT) {\n  for (uint8_t p : url) {\n    if (p <= 0x20 || p == 0x7f ||\n        (p > 0x7f && mode != URLValidateMode::STRICT_COMPAT)) {\n      // no controls or unescaped spaces\n      return false;\n    }\n  }\n  return true;\n}\n\ninline size_t findLastOf(folly::StringPiece sp, char c) {\n  size_t pos = sp.size();\n  while (--pos != std::string::npos && sp[pos] != c) {\n    // pass\n  }\n  return pos;\n}\n\ntemplate <typename Tout, typename Tin>\nTout clamped_downcast(Tin value) {\n  return static_cast<Tout>(\n      std::min(static_cast<uint64_t>(value),\n               static_cast<uint64_t>(std::numeric_limits<Tout>::max())));\n}\n\n// Like std::isalpha but independent of the current locale.\ninline bool isAlpha(uint8_t c) {\n  return ((unsigned int)(c | 0x20) - 'a') < 26U;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/WeakRefCountedPtr.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n\nnamespace proxygen {\n\n/**\n * WeakRefCountedPtr allows an object to handout pointers to itself that:\n *   - Can extend the target object's lifetime, if the object chooses to do so.\n *   - Are aware of whether the target object has been destroyed.\n *\n * WeakRefCountedPtr is not thread-safe for performance reasons.\n *\n * Comparison to other approaches:\n *   - shared_ptr<T> is too strong, while weak_ptr<T> is too weak:\n *      - shared_ptr<T> would require an object to remain alive\n *      - weak_ptr<T> does not delay destruction\n *      - WeakRefCountedPtr is a happy medium; an object is aware of whether\n *        WeakRefCountedPtr exist that point to it and can choose to delay its\n *        own destruction, or can safely destroy.\n *   - Because WeakRefCountedPtr is not thread-safe, it avoids using atomics\n *     that would be required for shared_ptr and weak_ptr.\n *\n * Enabling for a type T:\n *   1. Derive from EnableWeakRefCountedPtr<T>.\n *   2. Delay destruction if (numWeakRefCountedPtrs() != 0).\n *   3. Implement onWeakRefCountedPtrDestroy, called in ~WeakRefCountedPtr.\n *      If the object delayed destruction due to outstanding WeakRefCountedPtr,\n *      it should check if (numWeakRefCountedPtrs() == 0). If this condition is\n *      met, then no WeakRefCountedPtr remain and destruction can proceed.\n *   4. An object is not required to delay its destruction if WeakRefCountedPtr\n *      still exist (2); if it is destroyed while they still exist, the\n *      consumers will be able to determine this.\n *\n * Getting and using a WeakRefCountedPtr<T>:\n *   1. Call T::getWeakRefCountedPtr<T> to get a WeakRefCountedPtr<T>.\n *      In the case of derived classes, T must have an ancestor B that derived\n *      from EnableWeakRefCountedPtr<B>.\n *   2. On each use of the WeakRefCountedPtr, check if target still exists by:\n *        (a) Call get() and check if T* returned != nullptr, OR\n *        (b) Check if the WeakRefCountedPtr object evaluates to true.\n *   3. If target still exists, can use get() or -> to get pointer to target.\n *   4. Target pointer should not be stored.\n */\n\ntemplate <class T1, class T2>\nclass WeakRefCountedPtr;\n\ntemplate <class T>\nstruct WeakRefCountedPtrState {\n  T* ptr{nullptr};\n  uint64_t count{0};\n};\n\n/**\n * Derive from EnableWeakRefCountedPtr to enable WeakRefCountedPtr for class.\n */\ntemplate <class T>\nclass EnableWeakRefCountedPtr {\n public:\n  EnableWeakRefCountedPtr() : state_(nullptr) {\n    static_assert(std::is_base_of<EnableWeakRefCountedPtr<T>, T>::value,\n                  \"T must derive from EnableWeakRefCountedPtr\");\n  }\n\n  /**\n   * Destructor.\n   *\n   * If any WeakRefCountedPtr are pointing to this object, any future calls to\n   * WeakRefCountedPtr::get will return nullptr after the destructor executes.\n   */\n  virtual ~EnableWeakRefCountedPtr() {\n    if (state_) {\n      state_->ptr = nullptr;\n      if (state_->count == 0) {\n        delete state_;\n      }\n    }\n  }\n\n  /**\n   * Returns a WeakRefCountedPtr<T>.\n   *\n   * T must either have EnableWeakRefCountedPtr<T> as a base class or must have\n   * some ancestor class B that derives from EnableWeakRefCountedPtr<B>.\n   *\n   * To check if the WeakRefCountedPtr's target still exists, you can either:\n   *   (a) Call WeakRefCountedPtr<T>::get() and check if T* returned != nullptr.\n   *   (b) Check if WeakRefCountedPtr<T> evaluates to true.\n   *\n   * If target object still exists, you can use get() or -> to get the pointer.\n   */\n  template <typename T2 = T>\n  WeakRefCountedPtr<T, T2> getWeakRefCountedPtr() {\n    if (!state_) {\n      // state not setup yet, do it now\n      state_ = new WeakRefCountedPtrState<T>();\n      state_->ptr = static_cast<T*>(this);\n    }\n    return WeakRefCountedPtr<T, T2>(state_);\n  }\n\n  /**\n   * Returns number of WeakRefCountedPtr pointing to this object.\n   */\n  [[nodiscard]] uint64_t numWeakRefCountedPtrs() const {\n    if (!state_) {\n      return 0;\n    }\n    return state_->count;\n  }\n\n protected:\n  /**\n   * Called when a WeakRefCountedPtr is created.\n   *\n   * Default implementation is a no-op.\n   */\n  virtual void onWeakRefCountedPtrCreate() {\n  }\n\n  /**\n   * Called when a WeakRefCountedPtr is destroyed.\n   *\n   * If the object has delayed destruction due to outstanding WeakRefCountedPtr,\n   * implementaton should check if (numWeakRefCountedPtrs() == 0). If condition\n   * is met, then no WeakRefCountedPtr remain and destruction can proceed.\n   */\n  virtual void onWeakRefCountedPtrDestroy() = 0;\n\n  // WeakRefCountedPtr must be a friend so that it can call\n  // onWeakRefCountedPtrDestroy()\n  //\n  // We do not care about its precise template arguments.\n  template <typename T1, typename T2>\n  friend class WeakRefCountedPtr;\n\n private:\n  WeakRefCountedPtrState<T>* state_{nullptr};\n};\n\n/**\n * WeakRefCountedPtr pointing to object of type T2, which derives from T1.\n *\n * To check if the WeakRefCountedPtr's target still exists, you can either:\n *   (a) Call WeakRefCountedPtr<T>::get() and check if T* returned != nullptr.\n *   (b) Check if WeakRefCountedPtr<T> evaluates to true.\n *\n * If target object still exists, you can use get() or -> to get the pointer.\n */\ntemplate <class T1, class T2 = T1>\nclass WeakRefCountedPtr {\n public:\n  WeakRefCountedPtr() : state_(nullptr) {\n    static_assert(std::is_base_of<T1, T2>::value, \"T2 must be derived from T1\");\n  }\n\n  WeakRefCountedPtr(const WeakRefCountedPtr& rhs) {\n    initPtr(rhs.state_);\n  }\n\n  WeakRefCountedPtr(WeakRefCountedPtr&& rhs) {\n    state_ = rhs.state_;\n    rhs.state_ = nullptr;\n  }\n\n  WeakRefCountedPtr& operator=(const WeakRefCountedPtr& rhs) {\n    destroyPtr();\n    initPtr(rhs.state_);\n    return *this;\n  }\n\n  WeakRefCountedPtr& operator=(WeakRefCountedPtr&& rhs) {\n    destroyPtr();\n    state_ = rhs.state_;\n    rhs.state_ = nullptr;\n    return *this;\n  }\n\n  ~WeakRefCountedPtr() {\n    destroyPtr();\n  }\n\n  T2* get() const {\n    if (!state_) {\n      return nullptr;\n    }\n    return static_cast<T2*>(state_->ptr);\n  }\n\n  T2* operator->() const {\n    return get();\n  }\n\n  operator bool() const {\n    return (get());\n  }\n\n  void reset() {\n    destroyPtr();\n  }\n\n private:\n  WeakRefCountedPtr(WeakRefCountedPtrState<T1>* state) {\n    initPtr(state);\n  }\n\n  /**\n   * Initializes the WeakRefCountedPtr.\n   *   - Increments reference count.\n   */\n  void initPtr(WeakRefCountedPtrState<T1>* state) {\n    CHECK(!state_);\n    state_ = state;\n    if (state_) {\n      state_->count++;\n      if (state_->ptr) {\n        state_->ptr->onWeakRefCountedPtrCreate();\n      }\n      CHECK_GE(state_->count, 1); // sanity if state_->count is ever unsigned\n    }\n  }\n\n  /**\n   * Destroys the WeakRefCountedPtr.\n   *   - Decrements reference count.\n   *   - Calls onWeakRefCountedPtrDestroy on target object if it still exists.\n   *   - Cleans up state object if target destroyed and no other ptrs remain.\n   */\n  void destroyPtr() {\n    if (!state_) {\n      return;\n    }\n    CHECK_GE(state_->count, 1);\n    state_->count--;\n    if (state_->ptr) {\n      state_->ptr->onWeakRefCountedPtrDestroy();\n    } else if (!state_->count) {\n      delete state_;\n    }\n    state_ = nullptr;\n  }\n\n  WeakRefCountedPtrState<T1>* state_{nullptr};\n  friend EnableWeakRefCountedPtr<T1>;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/WheelTimerInstance.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/WheelTimerInstance.h>\n\n#include <folly/Singleton.h>\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/EventBaseManager.h>\n\nnamespace proxygen {\n\nWheelTimerInstance::WheelTimerInstance() = default;\n\nWheelTimerInstance::WheelTimerInstance(folly::HHWheelTimer* timer)\n    : wheelTimerPtr_(timer) {\n  if (timer) {\n    // If you use an external timer with no default timeout set, you will get\n    // a check failed if you attempt to schedule a default timeout.\n    defaultTimeoutMS_ = timer->getDefaultTimeout();\n  }\n}\n\nWheelTimerInstance::WheelTimerInstance(\n    std::chrono::milliseconds defaultTimeoutMS, folly::EventBase* eventBase)\n    : defaultTimeoutMS_(defaultTimeoutMS) {\n  if (!eventBase) {\n    eventBase = folly::EventBaseManager::get()->getEventBase();\n  }\n  wheelTimerPtr_ = &eventBase->timer();\n}\n\nWheelTimerInstance::WheelTimerInstance(const WheelTimerInstance& timerInstance)\n\n    = default;\n\nWheelTimerInstance::WheelTimerInstance(\n    WheelTimerInstance&& timerInstance) noexcept\n    : wheelTimerPtr_(std::move(timerInstance.wheelTimerPtr_)),\n      defaultTimeoutMS_(std::move(timerInstance.defaultTimeoutMS_)) {\n}\n\nstd::chrono::milliseconds WheelTimerInstance::getDefaultTimeout() const {\n  return defaultTimeoutMS_;\n}\n\nvoid WheelTimerInstance::setDefaultTimeout(std::chrono::milliseconds timeout) {\n  defaultTimeoutMS_ = timeout;\n}\n\nvoid WheelTimerInstance::scheduleTimeout(\n    folly::HHWheelTimer::Callback* callback,\n    std::chrono::milliseconds timeout) {\n  if (wheelTimerPtr_) {\n    wheelTimerPtr_->scheduleTimeout(callback, timeout);\n  } else {\n    VLOG(2) << \"Ingoring scheduleTimeout on an empty WheelTimerInstance\";\n  }\n}\n\nvoid WheelTimerInstance::scheduleTimeout(\n    folly::HHWheelTimer::Callback* callback) {\n  CHECK_GE(defaultTimeoutMS_.count(), 0);\n  scheduleTimeout(callback, defaultTimeoutMS_);\n}\n\nWheelTimerInstance& WheelTimerInstance::operator=(const WheelTimerInstance& t) =\n    default;\n\nWheelTimerInstance& WheelTimerInstance::operator=(\n    const WheelTimerInstance&& timer) {\n  wheelTimerPtr_ = std::move(timer.wheelTimerPtr_);\n  defaultTimeoutMS_ = std::move(timer.defaultTimeoutMS_);\n  return *this;\n}\n\nWheelTimerInstance::operator bool() const {\n  return (wheelTimerPtr_ != nullptr);\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/WheelTimerInstance.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <chrono>\n#include <memory>\n\n#include <folly/io/async/HHWheelTimer.h>\n\nnamespace folly {\n\nclass EventBase;\n\n}\n\nnamespace proxygen {\n\n/*\n * Class to be used to schedule timeouts, has associated HHWheelTimer & timeout\n */\nclass WheelTimerInstance {\n public:\n  // will ignore all scheduleTimeout operations, to be used instead of\n  // nullptr for HHWheelTimer\n  WheelTimerInstance();\n\n  // will use WheelTimer of the EventBase thread\n  explicit WheelTimerInstance(std::chrono::milliseconds defaultTimeoutMS,\n                              folly::EventBase* eventBase = nullptr);\n\n  WheelTimerInstance(const WheelTimerInstance& timerInstance);\n  WheelTimerInstance(WheelTimerInstance&& timerInstance) noexcept;\n\n  // timer could be nullptr which is correct usecase meaning that timeout\n  // will not be scheduled\n  explicit WheelTimerInstance(folly::HHWheelTimer* timer);\n\n  [[nodiscard]] std::chrono::milliseconds getDefaultTimeout() const;\n  void setDefaultTimeout(std::chrono::milliseconds timeout);\n\n  // These timeout callbacks will be scheduled on the current thread\n  void scheduleTimeout(folly::HHWheelTimer::Callback* callback,\n                       std::chrono::milliseconds timeout);\n  void scheduleTimeout(folly::HHWheelTimer::Callback* callback);\n\n  WheelTimerInstance& operator=(const WheelTimerInstance& timer);\n  WheelTimerInstance& operator=(const WheelTimerInstance&& timer);\n\n  // returns true if it is empty\n  explicit operator bool() const;\n\n  [[nodiscard]] folly::HHWheelTimer* getWheelTimer() const {\n    return wheelTimerPtr_;\n  }\n\n private:\n  folly::HHWheelTimer* wheelTimerPtr_{nullptr}; // to support cases when\n  // external WheelTimer is\n  // specified\n\n  std::chrono::milliseconds defaultTimeoutMS_;\n};\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZlibStreamCompressor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ZlibStreamCompressor.h>\n\n#include <folly/io/Cursor.h>\n\nusing folly::IOBuf;\n\n// IOBuf uses 24 bytes of data for bookeeping purposes, so requesting for 4073\n// bytes of data will be rounded up to an allocation of 1 page.\nFOLLY_GFLAGS_DEFINE_int64(\n    zlib_compressor_buffer_growth,\n    2024,\n    \"The buffer growth size to use during IOBuf zlib deflation\");\n\nnamespace proxygen {\n\nnamespace {\n\nstd::unique_ptr<IOBuf> addOutputBuffer(z_stream* stream, uint32_t length) {\n  CHECK_EQ(stream->avail_out, 0);\n\n  auto buf = IOBuf::create(length);\n  buf->append(buf->capacity());\n\n  stream->next_out = buf->writableData();\n  stream->avail_out = buf->length();\n\n  return buf;\n}\n\nint deflateHelper(z_stream* stream, IOBuf* out, int flush) {\n  if (stream->avail_out == 0) {\n    out->prependChain(\n        addOutputBuffer(stream, FLAGS_zlib_compressor_buffer_growth));\n  }\n\n  return deflate(stream, flush);\n}\n} // namespace\n\nvoid ZlibStreamCompressor::init() {\n  if (init_) {\n    return;\n  }\n  init_ = true;\n\n  status_ = Z_OK;\n\n  zlibStream_.zalloc = Z_NULL;\n  zlibStream_.zfree = Z_NULL;\n  zlibStream_.opaque = Z_NULL;\n  zlibStream_.total_in = 0;\n  zlibStream_.next_in = Z_NULL;\n  zlibStream_.avail_in = 0;\n  zlibStream_.avail_out = 0;\n  zlibStream_.next_out = Z_NULL;\n\n  DCHECK(level_ == Z_DEFAULT_COMPRESSION ||\n         (level_ >= Z_NO_COMPRESSION && level_ <= Z_BEST_COMPRESSION))\n      << \"Invalid Zlib compression level. level=\" << level_;\n\n  switch (type_) {\n    case CompressionType::GZIP: {\n      auto windowBits = type_ == CompressionType::GZIP ? GZIP_WINDOW_BITS\n                                                       : DEFLATE_WINDOW_BITS;\n      status_ = deflateInit2(&zlibStream_,\n                             level_,\n                             Z_DEFLATED,\n                             windowBits,\n                             MAX_MEM_LEVEL,\n                             Z_DEFAULT_STRATEGY);\n    } break;\n    case CompressionType::DEFLATE:\n      status_ = deflateInit(&zlibStream_, level_);\n      break;\n    default:\n      DCHECK(false) << \"Unsupported zlib compression type.\";\n      break;\n  }\n\n  if (status_ != Z_OK) {\n    LOG(ERROR) << \"error initializing zlib stream. r=\" << status_;\n  }\n}\n\nZlibStreamCompressor::ZlibStreamCompressor(CompressionType type, int level)\n    : type_(type), level_(level) {\n}\n\nZlibStreamCompressor::~ZlibStreamCompressor() {\n  if (init_) {\n    status_ = deflateEnd(&zlibStream_);\n  }\n}\n\n// Compress an IOBuf chain. Compress can be called multiple times and the\n// Zlib stream will be synced after each call. trailer must be set to\n// true on the final compression call.\nstd::unique_ptr<IOBuf> ZlibStreamCompressor::compress(const IOBuf* in,\n                                                      bool trailer) {\n  init();\n  auto bufferLength = FLAGS_zlib_compressor_buffer_growth;\n\n  auto out = addOutputBuffer(&zlibStream_, bufferLength);\n\n  for (auto& range : *in) {\n    uint64_t remaining = range.size();\n    uint64_t written = 0;\n    while (remaining) {\n      uint32_t step = remaining;\n      zlibStream_.next_in = const_cast<uint8_t*>(range.data() + written);\n      zlibStream_.avail_in = step;\n      remaining -= step;\n      written += step;\n\n      while (zlibStream_.avail_in != 0) {\n        status_ = deflateHelper(&zlibStream_, out.get(), Z_NO_FLUSH);\n        if (status_ != Z_OK) {\n          LOG(DFATAL) << \"Deflate failed: \" << zlibStream_.msg;\n          return nullptr;\n        }\n      }\n    }\n  }\n\n  if (trailer) {\n    do {\n      status_ = deflateHelper(&zlibStream_, out.get(), Z_FINISH);\n    } while (status_ == Z_OK);\n\n    if (status_ != Z_STREAM_END) {\n      LOG(DFATAL) << \"Deflate failed: \" << zlibStream_.msg;\n      return nullptr;\n    }\n  } else {\n    do {\n      status_ = deflateHelper(&zlibStream_, out.get(), Z_SYNC_FLUSH);\n    } while (zlibStream_.avail_out == 0);\n\n    if (status_ != Z_OK) {\n      LOG(DFATAL) << \"Deflate failed: \" << zlibStream_.msg;\n      return nullptr;\n    }\n  }\n\n  out->prev()->trimEnd(zlibStream_.avail_out);\n\n  zlibStream_.next_out = Z_NULL;\n  zlibStream_.avail_out = 0;\n\n  return out;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZlibStreamCompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <folly/portability/GFlags.h>\n#include <memory>\n#include <proxygen/lib/utils/StreamCompressor.h>\n#include <proxygen/lib/utils/ZlibStreamDecompressor.h>\n#include <zlib.h>\n\nnamespace folly {\nclass IOBuf;\n}\n\nFOLLY_GFLAGS_DECLARE_int64(zlib_compressor_buffer_growth);\n\nnamespace proxygen {\n\nclass ZlibStreamCompressor : public StreamCompressor {\n public:\n  explicit ZlibStreamCompressor(CompressionType type, int level);\n\n  ~ZlibStreamCompressor() override;\n\n  void init();\n\n  std::unique_ptr<folly::IOBuf> compress(const folly::IOBuf* in,\n                                         bool trailer = true) override;\n\n  int getStatus() {\n    return status_;\n  }\n\n  bool hasError() override {\n    return status_ != Z_OK && status_ != Z_STREAM_END;\n  }\n\n  bool finished() {\n    return status_ == Z_STREAM_END;\n  }\n\n private:\n  CompressionType type_{CompressionType::NONE};\n  int level_{Z_DEFAULT_COMPRESSION};\n  z_stream zlibStream_;\n  int status_{Z_OK};\n  bool init_{false};\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZlibStreamDecompressor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ZlibStreamDecompressor.h>\n\n#include <folly/io/Cursor.h>\n\nusing folly::IOBuf;\n\nnamespace proxygen {\n\nvoid ZlibStreamDecompressor::init(CompressionType type) {\n  DCHECK(type_ == CompressionType::NONE) << \"Must be uninitialized\";\n  type_ = type;\n  status_ = Z_OK;\n  zlibStream_.zalloc = Z_NULL;\n  zlibStream_.zfree = Z_NULL;\n  zlibStream_.opaque = Z_NULL;\n  zlibStream_.total_in = 0;\n  zlibStream_.next_in = Z_NULL;\n  zlibStream_.avail_in = 0;\n  zlibStream_.avail_out = 0;\n  zlibStream_.next_out = Z_NULL;\n\n  DCHECK(type == CompressionType::DEFLATE || type == CompressionType::GZIP);\n  auto windowBits =\n      type_ == CompressionType::GZIP ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;\n  status_ = inflateInit2(&zlibStream_, windowBits);\n}\n\nZlibStreamDecompressor::ZlibStreamDecompressor(\n    CompressionType type,\n    uint64_t zlib_decompressor_buffer_growth,\n    uint64_t zlib_decompressor_buffer_minsize)\n    : type_(CompressionType::NONE),\n      decompressor_buffer_growth_(zlib_decompressor_buffer_growth),\n      decompressor_buffer_minsize_(zlib_decompressor_buffer_minsize),\n      status_(Z_OK) {\n  init(type);\n}\n\nZlibStreamDecompressor::~ZlibStreamDecompressor() {\n  if (type_ != CompressionType::NONE) {\n    status_ = inflateEnd(&zlibStream_);\n  }\n}\n\nstd::unique_ptr<IOBuf> ZlibStreamDecompressor::decompress(const IOBuf* in) {\n  auto out = IOBuf::create(decompressor_buffer_growth_);\n  auto appender = folly::io::Appender(out.get(), decompressor_buffer_growth_);\n\n  const IOBuf* crtBuf = in;\n  size_t offset = 0;\n  while (true) {\n    // Advance to the next IOBuf if necessary\n    DCHECK_GE(crtBuf->length(), offset);\n    if (crtBuf->length() == offset) {\n      crtBuf = crtBuf->next();\n      offset = 0;\n      if (crtBuf == in) {\n        // We hit the end of the IOBuf chain, and are done.\n        break;\n      }\n    }\n\n    if (status_ == Z_STREAM_END) {\n      // we convert this into a stream error\n      status_ = Z_STREAM_ERROR;\n      // we should probably bump up a counter here\n      LOG(ERROR) << \"error uncompressing buffer: reached end of zlib data \"\n                    \"before the end of the buffer\";\n      return nullptr;\n    }\n\n    // Ensure there is space in the output IOBuf\n    appender.ensure(decompressor_buffer_minsize_);\n    DCHECK_GT(appender.length(), 0);\n\n    const size_t origAvailIn = crtBuf->length() - offset;\n    zlibStream_.next_in = const_cast<uint8_t*>(crtBuf->data() + offset);\n    zlibStream_.avail_in = origAvailIn;\n    zlibStream_.next_out = appender.writableData();\n    zlibStream_.avail_out = appender.length();\n    status_ = inflate(&zlibStream_, Z_PARTIAL_FLUSH);\n    if (status_ != Z_OK && status_ != Z_STREAM_END) {\n      LOG(INFO) << \"error uncompressing buffer: r=\" << status_;\n      return nullptr;\n    }\n\n    // Adjust the input offset ahead\n    auto inConsumed = origAvailIn - zlibStream_.avail_in;\n    offset += inConsumed;\n    // Move output buffer ahead\n    auto outMove = appender.length() - zlibStream_.avail_out;\n    appender.append(outMove);\n  }\n\n  return out;\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZlibStreamDecompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n#include <proxygen/lib/utils/StreamDecompressor.h>\n#include <zlib.h>\n\nnamespace folly {\nclass IOBuf;\n}\n\nnamespace proxygen {\n\nnamespace {\nconstexpr uint64_t kZlibDecompressorBufferGrowthDefault = 480;\nconstexpr uint64_t kZlibDecompressorBufferMinsizeDefault = 64;\n\n/**\n * These are misleading. See zlib.h for explanation of windowBits param. 31\n * implies a window log of 15 with enabled detection and decoding of the gzip\n * format.\n */\nconstexpr int GZIP_WINDOW_BITS = 31;\nconstexpr int DEFLATE_WINDOW_BITS = 15;\n} // namespace\n\nclass ZlibStreamDecompressor : public StreamDecompressor {\n public:\n  explicit ZlibStreamDecompressor(CompressionType type,\n                                  uint64_t zlib_decompressor_buffer_growth =\n                                      kZlibDecompressorBufferGrowthDefault,\n                                  uint64_t zlib_decompressor_buffer_minsize =\n                                      kZlibDecompressorBufferMinsizeDefault);\n\n  ZlibStreamDecompressor() = default;\n\n  ~ZlibStreamDecompressor() override;\n\n  void init(CompressionType type);\n\n  std::unique_ptr<folly::IOBuf> decompress(const folly::IOBuf* in) override;\n\n  int getStatus() {\n    return status_;\n  }\n\n  bool hasError() override {\n    return status_ != Z_OK && status_ != Z_STREAM_END;\n  }\n\n  bool finished() override {\n    return status_ == Z_STREAM_END;\n  }\n\n private:\n  CompressionType type_{CompressionType::NONE};\n  uint64_t decompressor_buffer_growth_{kZlibDecompressorBufferGrowthDefault};\n  uint64_t decompressor_buffer_minsize_{kZlibDecompressorBufferMinsizeDefault};\n  z_stream zlibStream_;\n  int status_{-1};\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZstdStreamCompressor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ZstdStreamCompressor.h>\n\n#include <folly/compression/Compression.h>\n\nnamespace proxygen {\n\nZstdStreamCompressor::ZstdStreamCompressor(int compressionLevel,\n                                           bool independentChunks)\n    : codec_(nullptr),\n      compressionLevel_(compressionLevel),\n      independent_(independentChunks) {\n}\n\nfolly::compression::StreamCodec& ZstdStreamCompressor::getCodec() {\n  if (!codec_) {\n    codec_ = folly::compression::getStreamCodec(\n        folly::compression::CodecType::ZSTD, compressionLevel_);\n  }\n  return *codec_;\n}\n\nstd::unique_ptr<folly::IOBuf> ZstdStreamCompressor::compress(\n    const folly::IOBuf* in, bool last) {\n  if (error_) {\n    return nullptr;\n  }\n\n  if (in == nullptr) {\n    error_ = true;\n    return nullptr;\n  }\n\n  try {\n    folly::IOBuf clone;\n    if (in->isChained()) {\n      clone = in->cloneCoalescedAsValueWithHeadroomTailroom(0, 0);\n      in = &clone;\n    }\n\n    auto op = last || independent_\n                  ? folly::compression::StreamCodec::FlushOp::END\n                  : folly::compression::StreamCodec::FlushOp::FLUSH;\n    auto& codec = getCodec();\n    if (independent_) {\n      codec.resetStream(in->length());\n    }\n\n    auto compressBound = codec.maxCompressedLength(in->length());\n    auto out = folly::IOBuf::create(compressBound + 1);\n\n    folly::ByteRange inrange{in->data(), in->length()};\n    folly::MutableByteRange outrange{out->writableTail(), out->tailroom()};\n\n    auto success = codec.compressStream(inrange, outrange, op);\n\n    if (!success) {\n      error_ = true;\n      return {};\n    }\n\n    DCHECK_EQ(inrange.size(), 0);\n    DCHECK_GT(outrange.size(), 0);\n\n    out->append(outrange.begin() - out->tail());\n\n    if (op == folly::compression::StreamCodec::FlushOp::END) {\n      codec_.reset();\n    }\n\n    return out;\n  } catch (const std::exception&) {\n    error_ = true;\n  }\n  return {};\n}\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZstdStreamCompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <memory>\n\n#include <folly/compression/Compression.h>\n#include <proxygen/lib/utils/StreamCompressor.h>\n\nnamespace folly {\nclass IOBuf;\nnamespace compression {\nclass StreamCodec;\n}\nnamespace io {\nusing folly::compression::StreamCodec;\n}\n} // namespace folly\n\nnamespace proxygen {\n\nclass ZstdStreamCompressor : public StreamCompressor {\n public:\n  /**\n   * The Zstd content-coding for HTTP allows us to use multiple Zstd frames\n   * in a single message, if we want. This gives us some freedom to choose how\n   * to perform the compression.\n   *\n   * When independentChunks == false, this compressor produces a single frame,\n   * maintaining the history of the stream across chunks, so that previous\n   * content can be used to compress new chunks. This necessitates holding that\n   * history in memory (the memory cost of which is worth considering if you\n   * are holding many long-lived streams open).\n   *\n   * When independentChunks == true, each chunk is emitted as an independent\n   * frame. This means they can't take advantage of previous chunks, so you\n   * will get a worse compression ratio. However, no state needs to be stored\n   * between chunks, so there's no memory footprint cost.\n   */\n  explicit ZstdStreamCompressor(int compressionLevel,\n                                bool independentChunks = false);\n\n  ~ZstdStreamCompressor() override = default;\n\n  std::unique_ptr<folly::IOBuf> compress(const folly::IOBuf*,\n                                         bool last = true) override;\n\n  bool hasError() override {\n    return error_;\n  }\n\n private:\n  folly::compression::StreamCodec& getCodec();\n\n  std::unique_ptr<folly::compression::StreamCodec> codec_;\n  const int compressionLevel_;\n  const bool independent_;\n  bool error_ = false;\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZstdStreamDecompressor.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n\nnamespace proxygen {\n\nvoid ZstdStreamDecompressor::freeDCtx(ZSTD_DCtx* dctx) {\n  ZSTD_freeDCtx(dctx);\n}\n\nZstdStreamDecompressor::ZstdStreamDecompressor(\n    bool reuseOutBuf, std::optional<uint64_t> maxDecompressionRatio)\n    : status_(ZstdStatusType::NONE),\n      dctx_(ZSTD_createDCtx()),\n      cachedIOBuf_(nullptr),\n      reuseOutBuf_(reuseOutBuf),\n      maxDecompressionRatio_(maxDecompressionRatio) {\n}\n\nstd::unique_ptr<folly::IOBuf> ZstdStreamDecompressor::decompress(\n    const folly::IOBuf* in) {\n  if (!dctx_) {\n    status_ = ZstdStatusType::ERROR;\n  }\n  if (hasError()) {\n    return nullptr;\n  }\n\n  const size_t outBufAllocSize = ZSTD_DStreamOutSize();\n\n  auto out = (reuseOutBuf_ && cachedIOBuf_ != nullptr)\n                 ? std::move(cachedIOBuf_)\n                 : folly::IOBuf::create(outBufAllocSize);\n  auto appender = folly::io::Appender(out.get(), outBufAllocSize);\n\n  for (const folly::ByteRange& range : *in) {\n    if (range.data() == nullptr) {\n      continue;\n    }\n\n    ZSTD_inBuffer ibuf = {range.data(), range.size(), 0};\n    while (ibuf.pos < ibuf.size) {\n      status_ = ZstdStatusType::CONTINUE;\n      appender.ensure(outBufAllocSize);\n      DCHECK_GT(appender.length(), 0);\n\n      auto prevIbufPos = ibuf.pos;\n      ZSTD_outBuffer obuf = {appender.writableData(), appender.length(), 0};\n      auto ret = ZSTD_decompressStream(dctx_.get(), &obuf, &ibuf);\n      if (ZSTD_isError(ret)) {\n        status_ = ZstdStatusType::ERROR;\n        return nullptr;\n      } else if (ret == 0) {\n        status_ = ZstdStatusType::FINISHED;\n      }\n\n      appender.append(obuf.pos);\n\n      // Track cumulative bytes for ratio enforcement.\n      totalInputBytes_ += (ibuf.pos - prevIbufPos);\n      totalOutputBytes_ += obuf.pos;\n\n      auto maxDecompressionRatio =\n          maxDecompressionRatio_.value_or(std::numeric_limits<uint64_t>::max());\n      bool exceeded =\n          totalInputBytes_ > 0 &&\n          totalOutputBytes_ / totalInputBytes_ > maxDecompressionRatio;\n      if (exceeded) {\n        status_ = ZstdStatusType::ERROR;\n        return nullptr;\n      }\n    }\n  }\n\n  if (reuseOutBuf_ && out->computeChainDataLength() == 0) {\n    cachedIOBuf_ = std::move(out);\n    return nullptr;\n  }\n\n  return out;\n}\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/ZstdStreamDecompressor.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n// We need access to zstd internals (to read frame headers etc.)\n#ifndef ZSTD_STATIC_LINKING_ONLY\n#define ZSTD_STATIC_LINKING_ONLY\n#endif\n#ifndef ZDICT_STATIC_LINKING_ONLY\n#define ZDICT_STATIC_LINKING_ONLY\n#endif\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <zdict.h>\n#include <zstd.h>\n\n#include <folly/Memory.h>\n\n#include <proxygen/lib/utils/StreamDecompressor.h>\n\nnamespace proxygen {\n\nclass ZstdStreamDecompressor : public StreamDecompressor {\n public:\n  explicit ZstdStreamDecompressor(\n      bool reuseOutBuf = false,\n      std::optional<uint64_t> maxDecompressionRatio = std::nullopt);\n\n  // May return nullptr on error / no output.\n  std::unique_ptr<folly::IOBuf> decompress(const folly::IOBuf* in) override;\n\n  bool hasError() override {\n    return status_ == ZstdStatusType::ERROR;\n  }\n\n  // Note that this will return true anytime the stream is at a frame boundary.\n  // The Zstd spec makes it clear that a response may be composed of multiple\n  // concatenated frames. So this may return false positives. It should never\n  // return a false negative, though.\n  bool finished() override {\n    return status_ == ZstdStatusType::FINISHED;\n  }\n\n private:\n  static void freeDCtx(ZSTD_DCtx* dctx);\n\n  enum class ZstdStatusType : int { NONE, CONTINUE, ERROR, FINISHED };\n\n  ZstdStatusType status_;\n\n  const std::unique_ptr<ZSTD_DCtx,\n                        folly::static_function_deleter<ZSTD_DCtx, freeDCtx>>\n      dctx_;\n\n  std::unique_ptr<folly::IOBuf> cachedIOBuf_; // For reuse when output is\n                                              // 0-sized\n\n  bool reuseOutBuf_; // Controls whether we may reuse the decompress outBuf\n\n  std::optional<uint64_t> maxDecompressionRatio_;\n  uint64_t totalInputBytes_{0};\n  uint64_t totalOutputBytes_{0};\n};\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/gen_perfect_hash_table.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Utility method for generating the header and source code for a generic perfect\n# hash table data structure by leveraging gperf, input template header and gperf\n# source files, and input word list files that respresent the enum fields.\nfunction generate_perfect_hash_table {\n\n  # $1 is a space separated list of files that store words that will correspond\n  #    to output enum fields.\n  # $2 is the path to the input template header file.\n  # $3 is the path to the 'awk' script that will output format the template\n  #    header file.\n  # $4 is the desired output path of the generated header file.\n  # $5 is the path to the input template gperf file.\n  # $6 is the path to the 'awk' script that will format the template gperf file\n  #    prior to it being passed to the gperf binary.\n  # $7 is the desired output path of the generated source file.\n  # $8 is the optional path to the gperf binary to use.\n\n  # The inputs to 'cat' below are not quoted as they are space separated lists\n  # of strings (files); 'cat' will concatenate them (if applicable) and pipe the\n  # combined output onwards to 'awk'.\n\n  # Generate output header file.\n  LC_ALL=C sort -u ${1?} | awk \"${3?}\" - \"${2?}\" > \"${4?}\"\n\n  # Generate output source file.\n  LC_ALL=C sort -u ${1?} | awk \"${6?}\" - \"${5?}\" | \\\n  ${8:-gperf} -m5 -D --output-file=\"${7?}\"\n\n  # Here we delete one of the comment lines gperf adds to the top of the file.\n  # i.e. /* Command-line: .../gperf -m5 --output-file=...  */\n  # The reason this line is removed is the '...'s are actually expanded to\n  # absolute paths on the build machine which prevents buck's cache from being\n  # used effectively.\n  # Prefer perl to sed so that it works on both Mac and Linux.\n  perl -n -i -e \"print unless m /\\\\/* Command-line: /\" \"${7?}\"\n}\n"
  },
  {
    "path": "proxygen/lib/utils/gen_trace_event_constants.py",
    "content": "#!/bin/env python3\n# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport optparse\nimport os\nimport re\nimport sys\n\n\ndef main(argv):\n    # args parser\n    parser = optparse.OptionParser()\n    parser.add_option(\n        \"--install_dir\",\n        dest=\"install_dir\",\n        type=\"string\",\n        default=None,\n        help=\"Absolute path to generate files\",\n    )\n    parser.add_option(\n        \"--fbcode_dir\",\n        dest=\"fbcode_dir\",\n        type=\"string\",\n        default=None,\n        help=\"Absolute path to fbcode directory\",\n    )\n    parser.add_option(\n        \"--input_files\",\n        dest=\"input_files\",\n        type=\"string\",\n        default=None,\n        help=\"Relative path of input file\",\n    )\n    parser.add_option(\n        \"--output_scope\",\n        dest=\"output_scope\",\n        type=\"string\",\n        default=None,\n        help=\"namespace / package of output file\",\n    )\n    parser.add_option(\n        \"--output_type\",\n        dest=\"output_type\",\n        type=\"choice\",\n        choices=[\"java\", \"cpp\"],\n        default=None,\n        help=\"File type to generate\",\n    )\n    parser.add_option(\n        \"--header_path\",\n        dest=\"header_path\",\n        type=\"string\",\n        default=None,\n        help=\"Relative path to cpp header\",\n    )\n    options, _ = parser.parse_args()\n\n    assert options.install_dir is not None, \"Missing arg: --install_dir\"\n    assert options.fbcode_dir is not None, \"Missing arg: --fbcode_dir\"\n    assert options.input_files is not None, \"Missing arg: --input_files\"\n    assert options.output_scope is not None, \"Missing arg: --output_scope\"\n    assert options.output_type is not None, \"Missing arg: --output_type\"\n\n    file_names = options.input_files.split(\",\")\n    for file_name in file_names:\n        # strip the file extension and use the file name for class name\n        class_name = os.path.basename(file_name).split(\".\")[0]\n\n        # parse items from source\n        items = []\n        with open(file_name) as inf:\n            for line in inf:\n                sp = re.match(r\"(.*), \\\"(.*)\\\"\", line, re.I)\n                if sp:\n                    items.append((sp.group(1), sp.group(2)))\n\n        if options.output_type == \"java\":\n            gen_java(items, class_name, options.install_dir, options.output_scope)\n\n        elif options.output_type == \"cpp\":\n            assert options.header_path is not None, \"Missing arg: --header_path\"\n\n            gen_cpp_header(items, class_name, options.install_dir, options.output_scope)\n            gen_cpp_source(\n                items,\n                class_name,\n                options.install_dir,\n                options.header_path,\n                options.output_scope,\n            )\n\n\n\"\"\"\nGenerate java interface class\n\"\"\"\n\n\ndef gen_java(items, class_name, install_dir, output_scope):\n    packages = output_scope.split(\".\")\n    file_name = \"%s.java\" % class_name\n    file_path = os.path.join(*([install_dir, \"src\"] + packages))\n    output_file = os.path.join(file_path, file_name)\n\n    if not os.path.exists(file_path):\n        os.makedirs(file_path)\n\n    with open(output_file, \"w+\") as outf:\n        outf.write(\"// Copyright 2015-present Facebook. All Rights Reserved.\\n\")\n        outf.write(\"// ** AUTOGENERATED FILE. DO NOT HAND-EDIT **\\n\\n\")\n        outf.write(\"package %s;\\n\\n\" % \".\".join(packages))\n        outf.write(\"public interface %s {\\n\" % class_name)\n\n        for item in items:\n            outf.write(\n                '    public static final String {} = \"{}\";\\n'.format(item[0], item[1])\n            )\n\n        outf.write(\"}\\n\")\n\n\n\"\"\"\nGenerate cpp enum class and provide convert function from / to string\n\"\"\"\n\n\ndef gen_cpp_header(items, class_name, install_dir, output_scope):\n    namespaces = output_scope.split(\"::\")\n    file_name = \"%s.h\" % class_name\n    output_file = os.path.join(install_dir, file_name)\n\n    with open(output_file, \"w+\") as outf:\n        outf.write(\"// Copyright 2015-present Facebook. All Rights Reserved.\\n\")\n        outf.write(\"// ** AUTOGENERATED FILE. DO NOT HAND-EDIT **\\n\\n\")\n        outf.write(\"#pragma once\\n\\n\")\n        outf.write(\"#include <string>\\n\\n\")\n        for ns in namespaces:\n            outf.write(\"namespace %s { \" % ns)\n        outf.write(\"\\n\\n\")\n\n        # generate enum class\n        outf.write(\"enum class %s {\\n\" % class_name)\n        for item in items:\n            outf.write(\"    %s,\\n\" % item[0])\n        outf.write(\"};\\n\\n\")\n\n        # enum to string convert function\n        outf.write(\n            \"extern const std::string& get{}String({});\\n\".format(\n                class_name, class_name\n            )\n        )\n\n        outf.write(\n            \"extern %s get%sFromString(const std::string&);\\n\"\n            % (class_name, class_name)\n        )\n        for _ in namespaces:\n            outf.write(\"}\")\n        outf.write(\"\\n\\n\")\n\n\n\"\"\"\nGenerate cpp const string and implement convert function\n\"\"\"\n\n\ndef gen_cpp_source(items, class_name, install_dir, header_path, output_scope):\n    namespaces = output_scope.split(\"::\")\n    file_name = \"%s.cpp\" % class_name\n    output_file = os.path.join(install_dir, file_name)\n\n    with open(output_file, \"w+\") as outf:\n        outf.write(\"// Copyright 2015-present Facebook. All Rights Reserved.\\n\")\n        outf.write(\"// ** AUTOGENERATED FILE. DO NOT HAND-EDIT **\\n\\n\")\n        outf.write('#include \"{}/{}.h\"\\n\\n'.format(header_path, class_name))\n        outf.write(\"#include <stdexcept>\\n\\n\")\n\n        for ns in namespaces:\n            outf.write(\"namespace %s { \" % ns)\n        outf.write(\"\\n\\n\")\n\n        # const string names\n        for item in items:\n            outf.write(\n                'static const std::string k%s%s = \"%s\";\\n'\n                % (class_name, item[0], item[1])\n            )\n\n        # generate enum to string convert function\n        outf.write(\n            \"const std::string& get{}String({} type) {{\\n\".format(\n                class_name, class_name\n            )\n        )\n\n        outf.write('  static const std::string k%sInvalidType = \"\";\\n' % class_name)\n\n        outf.write(\"\\n  switch (type) {\\n\")\n        for item in items:\n            outf.write(\n                \"    case %s::%s : return k%s%s;\\n\"\n                % (class_name, item[0], class_name, item[0])\n            )\n        outf.write(\"  }\\n\")\n        outf.write(\"  return k%sInvalidType;\\n\" % class_name)\n        outf.write(\"};\\n\\n\")\n\n        outf.write(\n            \"  %s get%sFromString(const std::string& str) {\\n\"\n            % (class_name, class_name)\n        )\n        for item in items:\n            outf.write(\n                \"  if (str == k%s%s)  return %s::%s;\\n\"\n                % (class_name, item[0], class_name, item[0])\n            )\n        outf.write(\n            \" throw std::invalid_argument\"\n            ' (\"No matching %s from string\");\\n' % (class_name)\n        )\n        outf.write(\"};\\n\")\n        outf.write(\"\\n\\n\")\n        for _ in namespaces:\n            outf.write(\"}\")\n        outf.write(\"\\n\\n\")\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv))\n"
  },
  {
    "path": "proxygen/lib/utils/perfect_hash_table_template.cpp.gperf",
    "content": "%{\n// Copyright 2015-present Facebook.  All rights reserved.\n\n#include \"%%header%%\"\n#include <cstring>\n#include <folly/String.h>\n#include <glog/logging.h>\n\nnamespace proxygen {\n\n%}\n\n%language=C++\n%compare-lengths\n%ignore-case\n%struct-type\n%readonly-tables\n%global-table\n%enum\n%define class-name %%name_internal%%\n\nstruct %%name_container%% { const char* name; %%name_enum%% code; };\n\n// Placeholder for the gen script to insert enum values alongside '%%'\n// separators separating declarations, keywords, and functions.\n// Note am currently unable to prevent this comment from being present in the\n// output file.\n%%%%%\n\n%%name_enum%% %%name%%::hash(const char* name, size_t len) {\n  const %%name_container%%* match =\n    %%name_internal%%::in_word_set(name, len);\n  return (match == nullptr) ? %%enum_other%% : match->code;\n}\n\nstd::string* %%name%%::initNames(\n    %%table_type_name%% type) {\n  auto names = new std::string[%%name%%::num_codes];\n  const uint8_t OFFSET = 2; // first 2 values are reserved for special cases\n  for (uint64_t j = 0; j < %%name%%::num_codes - OFFSET; ++j) {\n    uint8_t code = wordlist[j].code;\n    DCHECK_EQ(names[code], std::string());\n    // this would mean a duplicate code in the .gperf file\n    names[code] = wordlist[j].name;\n    if (type == %%table_type_name%%::TABLE_LOWERCASE) {\n      folly::toLowerAscii(const_cast<char*>(names[code].data()),\n          names[code].size());\n    }\n  }\n  return names;\n}\n\nconst std::string* %%name%%::getPointerToTable(\n    %%table_type_name%% type) {\n  // The actual  tables are static and initialized here in source\n  // so as to prevent duplicate initializations that could occur through the\n  // use of inline semantics or compilation unit referencing if defined in a\n  // header\n  switch(type) {\n    case %%table_type_name%%::TABLE_CAMELCASE:\n      static const std::string* camelcaseTable = initNames(type);\n      return camelcaseTable;\n    case %%table_type_name%%::TABLE_LOWERCASE:\n      static const std::string* lowercaseTable = initNames(type);\n      return lowercaseTable;\n    default:\n      // Controlled abort here so its clear from a crash stack this method\n      // was called with a table type for which there is no current\n      // implementation\n      LOG(FATAL) << \"unknown table type\";\n  }\n}\n\n} // proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/perfect_hash_table_template.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n// clang-format off\n#pragma once\n\n#include <cstdint>\n#include <string>\n\n#include <proxygen/lib/utils/Export.h>\n\nnamespace proxygen {\n\n/**\n * Codes (hashes)\n */\nenum %%name_enum%% : uint8_t {\n  // Code reserved to indicate the absence of a valid field.\n  %%enum_prefix%%_NONE = 0,\n  // Code reserved to indicate a value for which there is no perfect mapping to\n  // a unique field (i.e. the default field for all other values).\n  %%enum_prefix%%_OTHER = 1,\n\n  /* The following is a placeholder for the build script to generate a list\n   * of enum values.\n   * Note am currently unable to prevent this comment from being present in the\n   * output file.\n   */\n%%%%%\n\n};\n\nconst uint8_t %%name_enum%%CommonOffset = 2;\n\nenum class %%table_type_name%%: uint8_t {\n  TABLE_CAMELCASE = 0,\n  TABLE_LOWERCASE = 1,\n};\n\nclass %%name%% {\n public:\n  // Perfect hash function to match specified names\n  FB_EXPORT static %%name_enum%% hash(const char* name, size_t len);\n\n  FB_EXPORT inline static %%name_enum%% hash(const std::string& name) {\n    return hash(name.data(), name.length());\n  }\n\n  FB_EXPORT static std::string* initNames(%%table_type_name%% type);\n\n  /* The following is a placeholder for the build script to generate a field\n   * that stores the max number of defined enum fields.\n   i.e. constexpr static uint64_t num_codes;\n   * Note am currently unable to prevent this comment from being present in the\n   * output file.\n   */\n$$$$$\n\n  static const std::string* getPointerToTable(\n    %%table_type_name%% type);\n\n  inline static const std::string* getPointerToName(%%name_enum%% code,\n      %%table_type_name%% type = %%table_type_name%%::TABLE_CAMELCASE) {\n    return getPointerToTable(type) + code;\n  }\n\n  inline static bool isNameFromTable(const std::string* headerName,\n      %%table_type_name%% type) {\n    return getCodeFromTableName(headerName, type) >=\n      %%name_enum%%CommonOffset;\n  }\n\n  // This method supplements hash().  If dealing with string pointers, some\n  // pointing to entries in the the name table and some not, this\n  // method can be used in place of hash to reverse map a string from the\n  // name table to its code.\n  inline static %%name_enum%% getCodeFromTableName(\n      const std::string* headerName, %%table_type_name%% type) {\n    if (headerName == nullptr) {\n      return %%enum_prefix%%_NONE;\n    } else {\n      auto diff = headerName - getPointerToTable(type);\n      if (diff >= %%name_enum%%CommonOffset && diff < (long)num_codes) {\n        return static_cast<%%name_enum%%>(diff);\n      } else {\n        return %%enum_prefix%%_OTHER;\n      }\n    }\n  }\n\n};\n\n} // proxygen\n// clang-format on\n"
  },
  {
    "path": "proxygen/lib/utils/samples/TraceEventType.txt",
    "content": "/*\n * Trace Event Type, please follow the pattern when you add new one.\n */\n\nTotalRequest, \"TotalRequest\"\nRequestExchange, \"HTTPRequestExchange\"\nResponseBodyRead, \"HTTPResponseBodyRead\"\nPreConnect, \"PreConnect\"\nPostConnect, \"PostConnect\"\nDnsResolution, \"DNSResolution\"\nDnsCache, \"DNSCache\"\nRetryingDnsResolution, \"RetryingDNSResolution\"\nTcpConnect, \"TCPConnect\"\nTlsSetup, \"TLSSetup\"\nTotalConnect, \"TotalConnect\"\nDecompression, \"decompression_filter\"\nCertVerification, \"cert_verification\"\nProxyConnect, \"proxy_connect\"\nPush, \"push\"\nScheduling, \"scheduling\"\nNetworkChange, \"network_change\"\nMultiConnector, \"multi_connector\"\nSingleConnector, \"single_connector\"\nSessionTransactions, \"SessionTransactions\"\nTCPInfo, \"TCPInfo\"\nConnInfo, \"ConnInfo\"\nZeroSetup, \"ZeroSetup\"\nZeroVerification, \"ZeroVerification\"\nZeroConnector, \"ZeroConnector\"\nReplaySafety, \"ReplaySafety\"\nHTTPPerfParameters, \"HTTPPerfParameters\"\nRetryFilter, \"RetryFilter\"\nZeroFallback, \"ZeroFallback\"\nTLSCachedInfo, \"TLSCachedInfo\"\n\n/*\n * XXX: Too bad we have to define events in Liger core for the platform\n *      wrappers :(\n */\nFBLigerProtocol, \"FBLigerProtocol\"\n\n/*\n * MQTT Client events\n */\nMQTTClient, \"MQTTClient\"\nMQTTMessage, \"MQTTMessage\"\nMQTTConnect, \"MQTTConnect\"\nMQTTMessageStart, \"MQTTMessageStart\"\n"
  },
  {
    "path": "proxygen/lib/utils/samples/TraceFieldType.txt",
    "content": "/*\n * Trace Event Meta Data Fields, please follow the pattern when you add new one.\n */\n\n/* ---- Error Fields ---- */\nErrorStage, \"error_stage\"\nError, \"error_description\"\nProxygenError, \"proxygen_error\"\nHTTPStatus, \"http_status\"\nDirectionError, \"error_direction\"\nCodecError, \"codec_error\"\n\n/* ---- Used in TotalRequest ---- */\nCallPath, \"call_path\"\n\n/* ---- Used in RequestExchange ---- */\nUri, \"uri\"\nIsSecure, \"is_secure\"\nUsingProxy, \"using_proxy\"\nStatusCode, \"status_code\"\nProtocol, \"protocol\"\nSecurityProtocol, \"security_protocol\"\nReplaySafe, \"replay_safe\"\nLocalAddr, \"local_addr\"\nLocalPort, \"local_port\"\nContentType, \"content_type\"\nReqHeaderSize, \"request_header_size\"\nReqHeaderCompSize, \"request_header_compressed_size\"\nReqBodySize, \"request_body_size\"\nRspHeaderSize, \"response_header_size\"\nRspHeaderCompSize, \"response_header_compressed_size\"\nRedirectLocation, \"redirect_location\"\nNumRedirects, \"num_redirects\"\nRedirectResponseCode, \"redirect_response_code\"\nNumZeroRttRetries, \"num_retries\"\nUsingHTTP2, \"using_http2\"\nFirstBodyByteFlushedRatio, \"first_body_byte_flushed_ratio\"\nLastBodyByteFlushedRatio, \"last_body_byte_flushed_ratio\"\nFlowControlPauses, \"flow_control_pauses\"\nHTTPMethod, \"http_method\"\nRangeRequest, \"range_request\"\nRequestSendTime, \"wire request time in liger epoch\"\n\n/* ---- Used in ResponseBodyRead ---- */\nRspIntvlAvg, \"response_interval_average\"\nRspIntvlStdDev, \"response_interval_stddev\"\nRspNumOnBody, \"response_number_on_body\"\nServerQuality, \"response_server_quality\"\nServerRtt, \"server_rtt\"\nRecvToAck, \"flow_control_recv_to_ack\"\nServerRtx, \"server_rtx\"\nServerCwnd, \"server_cwnd\"\nServerMss, \"server_mss\"\nServerTotalBytesWritten, \"server_tbw\"\n\n/* ---- Used in PreConnect ---- */\nNewConnection, \"new_connection\"\nIsWaitingForNewConn, \"waiting_for_new_connection\"\nNewConnTimeout, \"new_conn_timeout\"\nInFlightConns, \"in_flight_conns\"\nCachedSessions, \"cached_sessions\"\nCachedActiveSessions, \"cached_active_sessions\"\nConnsStarted, \"conns_started\"\nRequestsWaited, \"requests_waited\"\nTotalRequestsWaited, \"total_requests_waited\",\nTotalConnsStarted, \"total_conns_started\",\nTotalBackupConnsStarted, \"total_backup_conns_started\",\nSessionCacheHitType, \"session_cache_hit_type\"\nPerDomainLimit, \"per_domain_limit\"\nDynamicDomainLimitRatio, \"dynamic_domain_limit_ratio\"\nLoadBalancing, \"load_balancing\"\nMaxConnectionRetryCount, \"max_connection_retry_count\"\nMaxIdleHTTPSessions, \"max_idle_http_sessions\"\nMaxIdleHTTP2Sessions, \"max_idle_http2_sessions\"\nConnRoutingStale, \"connection_routing_stale\"\nAltSvcHost, \"alt_svc_host\"\nInjectedSocket, \"injected_socket\"\n\n/* ---- Used in TcpConnect ---- */\nServerAddr, \"server_address\"\nServerPort, \"server_port\"\nCachedFamily, \"cached_family\"\n\n/* ---- Used in PostConnect ---- */\nNewSession, \"new_session\"\nNumWaiting, \"num_waiting\"\n\n/* ---- Used in DnsResolution and DNSCache ---- */\nHostName, \"host_name\"\nIpAddr, \"ip_address\"\nPort, \"port\"\nCNameRedirects, \"cname_redirects\"\nCanonicalName, \"canonical_name\"\nNumberResolvers, \"number_resolvers\"\nResolversSerialized, \"resolvers_serialized\"\nRequestFamily, \"request_family\"\nNumberAnswers, \"number_answers\"\nNumberDNSRetries, \"number_retries\"\nResolvedSuccess, \"resolved_success\"\nTXT, \"TXT\"\n\n/* ---- Used in DNSCache only ---- */\nDNSCacheHit, \"dns_cache_hit\"\nDNSCacheStale, \"dns_cache_stale\"\nDNSPreconnectDomain, \"dns_preconnect_domain\"\n\n/* ---- Used in TlsSetup ---- */\nTLSReused, \"tls_reused\"\nTLSCacheHit, \"tls_cache_hit\"\nCipherName, \"cipher_name\"\nTLSVersion, \"ssl_version\"\nOpenSSLVersion, \"openssl_version\"\nTLSCachePersistence, \"tls_cache_persistence\"\n\n/* ----- Used in TLSCachedInfo ---- */\nTLSCachedInfoHit, \"tls_cached_info_cert_cache_hit\"\n\n/* ---- Used in Decompression ---- */\nRspBodySize, \"response_body_size\"\nRspBodyCompSize, \"response_body_compressed_size\"\nCompressionType, \"compression_type\"\nCompressionDictSuccess, \"compression_dict_success\"\nCompressionDictError, \"compression_dict_error\"\nCompressionDictID, \"compression_dict_id\"\n\n/* ---- Used in CertVerification ---- */\nVerifiedSuccess, \"verified_success\"\nVerifiedChain, \"verified_chain\"\nVerifiedTime, \"verified_time\"\nVerifiedServerAddress, \"verified_server_address\"\nVerifiedProxyAddress, \"verified_proxy_address\"\nVerifiedError, \"verified_error\"\nVerifiedReason, \"verified_reason\"\nVerifiedHostname, \"verified_hostname\"\nVerifiedMatchedCommonName, \"verified_matchedCommonName\"\nVerifiedMatchedSubjectAltName, \"verified_matchedSubjectAltName\"\nVerifiedNameMatched, \"verified_nameMatched\"\nVerifiedHostnameFailMessage, \"verified_hostnameFailMessage\"\n\n/* ---- Used in Cert Signature Algorithm Verification ---- */\nSignatureAlgorithmCertSHA1, \"signature_algorithm_verified_cert_sha1\"\n\n/* ---- Used in Cert Failure Verification ---- */\nFailureVerifiedCertDepthInChain, \"failure_verified_cert_depth_in_chain\"\nVerifiedChainFailuresOverridden, \"verified_chain_failures_overridden\"\nVerifiedChainFailureVerificationTime, \"verified_chain_failure_verification_time\"\n\n/* ---- Used in Cert Pinning ---- */\nPinningReason, \"verified_pinning_reason\"\nPinningRequiredHash, \"verified_pinning_required_hash\"\nPinningUserHash, \"verified_pinning_user_hash\"\nPinningExcludedHash, \"verified_pinning_excluded_hash\"\nPinningRequiredFound, \"verified_pinning_required_found\"\nPinningUserInstalledFound, \"verified_pinning_user_installed_found\"\nPinningUserInstalledCount, \"verified_pinning_user_installed_count\"\nPinningExcludedFound, \"verified_pinning_excluded_found\"\nPinningTimePin, \"verified_pinning_time_pin\"\nPinningHost, \"verified_pinning_host\"\nPinningSuccess, \"verified_pinning_success\"\nVerifiedTimeMerge, \"verified_time_merge\"\n\n/* ---- Used in Cert Revocation ---- */\nRevokeReason, \"verified_revoke_reason\"\nRevokeSuccess, \"verified_revoke_success\"\n\n/* ---- Used in ProxyConnect ---- */\nProxyHost, \"proxy_host\"\nProxyPort, \"proxy_port\"\nProxyRespStatus, \"proxy_response_status\"\nProxyRespBody, \"proxy_response_body\"\nProxyUpstreamDest, \"proxy_upstream_dest\"\n\n/* ---- Used in Scheduling ---- */\nSchedulerType, \"scheduler_type\"\nInitialPriority, \"initial_priority\"\nSizeOfQueue, \"size_of_queue\"\n\n/* ---- Used in NetworkChange ---- */\nPreviousState, \"previous_state\"\nCurrentState, \"current_state\"\nNetworkID, \"network_id\"\n\n/* ---- Used in MultiConnector ---- */\nNumConnAttempts, \"number_conn_attempts\"\nAttemptAddrs, \"attempt_addresses\"\nAttemptAddrFamily, \"attempt_address_family\"\nSucceededConnTime, \"succeeded_conn_time\"\n\n/* ---- Used in FBLigerProtocol ---- */\nRequestID, \"request_id\"\nHumanReadableName, \"human_readable_name\"\n\n/* ---- Used in SessionTransactions ---- */\nCurrentTransactions, \"current_txns\"\nHistoricalMaximumTransactions, \"historical_max_txns\"\nNumberTransactionsServed, \"number_txns_served\"\n\n/* ---- TCP Info --- */\nCwnd, \"cwnd\"\nCwndBytes, \"cwnd_bytes\"\nTotalRetx, \"total_retx\"\nInflightPacketLoss, \"inflight_packet_loss\"\nRTT, \"rtt\"\nRTTVar, \"rtt_variance\"\nRTO, \"rto\"\nMSS, \"sending_mss\"\nMTU, \"mtu\"\nRcvWnd, \"recv_window\"\nUpstreamCapacity, \"upstream_capacity\"\n\n/* ---- Used in ConnInfo ----*/\nReqsSucceed, \"reqs_succeed\"\nReqsFailed, \"reqs_failed\"\nTTFB, \"ttfb\"\nTTLB, \"ttlb\"\nConnLifeSpan, \"connection_life_span\"\nEgressBuffered, \"egress_buffered\"\n\n/* ---- Used in ZeroSetup ---- */\nSCFGCacheHit, \"zero_scfg_cache_hit\"\nSCFGExpired, \"zero_scfg_expired\"\nZeroAEAD, \"zero_aead\"\nZeroKex, \"zero_kex\"\nZeroVersion, \"zero_version\"\nZeroRttEnabled, \"zero_rtt_enabled\"\nZeroFallbackEnabled, \"zero_fallback_enabled\"\n\n/* --- Used in ZeroVerification --- */\nZeroVerifiedSuccess, \"zero_verified_success\"\nZeroVerifiedError, \"zero_verified_error\"\n\n/* --- Used in ZeroConnector -- */\nTFOAttempted, \"tfo_attempted\"\nTFOFinished, \"tfo_finished\"\n\n/* --- Used in MQTT Client --- */\nMQTTConnAttempts, \"mqtt_conn_attempts\"\nIsForeground, \"is_foreground\"\nMQTTBytesWritten, \"mqtt_bytes_written\"\nMQTTBytesRead, \"mqtt_bytes_read\"\nRawBytesWritten, \"raw_bytes_written\"\nRawBytesRead, \"raw_bytes_read\"\nEventLoopTimeAvg, \"event_loop_time_avg\"\nTransportType, \"transport_type\"\n\n/* --- Used in MQTTMessage --- */\nMQTTMsgRemainingLength, \"mqtt_msg_remaining_length\"\nMsgType, \"msg_type\"\nIsMsgRecv, \"is_msg_received\"lishV\nConnectReturnCode, \"connect_return_code\"\nMQTTMsgBytes, \"mqtt_msg_bytes\"\nMsgTopic, \"msg_topic\"\nQoS, \"qos\"\n\n/* ---- Used in Push ----*/\nIsPushRequest, \"is_push_request\"\nPushConnectedInFlight, \"push_connected_in_flight\"\nPushOrphaned, \"push_orphaned\"\n\n/* ---- Used in analytics logging ----*/\nAnalyticsTags, \"analytics_tags\"\n"
  },
  {
    "path": "proxygen/lib/utils/test/AsyncTimeoutSetTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/AsyncTimeoutSet.h>\n\n#include <folly/io/async/EventBase.h>\n#include <folly/io/async/EventUtil.h>\n#include <folly/io/async/test/MockTimeoutManager.h>\n#include <folly/io/async/test/UndelayedDestruction.h>\n#include <folly/io/async/test/Util.h>\n#include <folly/portability/GTest.h>\n#include <map>\n#include <vector>\n\nusing namespace proxygen;\nusing namespace testing;\nusing folly::AsyncTimeout;\nusing folly::test::MockTimeoutManager;\nusing std::chrono::milliseconds;\n\nusing StackTimeoutSet = folly::UndelayedDestruction<AsyncTimeoutSet>;\n\nclass MockTimeoutClock : public AsyncTimeoutSet::TimeoutClock {\n public:\n  MockTimeoutClock() = default;\n\n  MOCK_METHOD(std::chrono::milliseconds, millisecondsSinceEpoch, ());\n};\n\nclass TestTimeout : public AsyncTimeoutSet::Callback {\n public:\n  template <typename... Args>\n  explicit TestTimeout(Args&&... args) {\n    addTimeout(std::forward<Args>(args)...);\n    _scheduleNext();\n  }\n  TestTimeout() = default;\n\n  void addTimeout(AsyncTimeoutSet* set) {\n    nextSets_.push_back(set);\n  }\n\n  template <typename... Args>\n  void addTimeout(AsyncTimeoutSet* set, Args&&... args) {\n    addTimeout(set);\n    addTimeout(std::forward<Args>(args)...);\n  }\n\n  void timeoutExpired() noexcept override {\n    timestamps.emplace_back(clock_->millisecondsSinceEpoch());\n    _scheduleNext();\n    if (fn) {\n      fn();\n    }\n  }\n\n  void _scheduleNext() {\n    if (nextSets_.empty()) {\n      return;\n    }\n    AsyncTimeoutSet* nextSet = nextSets_.front();\n    nextSets_.pop_front();\n    nextSet->scheduleTimeout(this);\n  }\n\n  std::deque<milliseconds> timestamps;\n  std::function<void()> fn;\n\n  static void setTimeoutClock(MockTimeoutClock& clock) {\n    clock_ = &clock;\n  }\n\n private:\n  static MockTimeoutClock* clock_;\n  std::deque<AsyncTimeoutSet*> nextSets_;\n};\n\nMockTimeoutClock* TestTimeout::clock_ = nullptr;\n\nclass TimeoutTest : public testing::Test {\n public:\n  void SetUp() override {\n    TestTimeout::setTimeoutClock(timeoutClock_);\n    setClock(milliseconds(0));\n\n    EXPECT_CALL(timeoutManager_, attachTimeoutManager(_, _))\n        .WillRepeatedly(Return());\n\n    EXPECT_CALL(timeoutManager_, scheduleTimeout(_, _))\n        .WillRepeatedly(Invoke([this](AsyncTimeout* p, milliseconds t) {\n          timeoutManager_.cancelTimeout(p);\n          folly::event_ref_flags(p->getEvent()->getEvent()) |= EVLIST_TIMEOUT;\n          timeouts_.emplace(t + timeoutClock_.millisecondsSinceEpoch(), p);\n          return true;\n        }));\n\n    EXPECT_CALL(timeoutManager_, cancelTimeout(_))\n        .WillRepeatedly(Invoke([this](AsyncTimeout* p) {\n          for (auto it = timeouts_.begin(); it != timeouts_.end(); it++) {\n            if (it->second == p) {\n              timeouts_.erase(it);\n              break;\n            }\n          }\n        }));\n  }\n\n  void loop() {\n    for (auto t = timeoutClock_.millisecondsSinceEpoch() + milliseconds(1);\n         !timeouts_.empty();\n         t++) {\n      setClock(t);\n    }\n  }\n\n  void setClock(milliseconds ms) {\n    EXPECT_CALL(timeoutClock_, millisecondsSinceEpoch())\n        .WillRepeatedly(Return(ms));\n\n    while (!timeouts_.empty() &&\n           timeoutClock_.millisecondsSinceEpoch() >= timeouts_.begin()->first) {\n      AsyncTimeout* timeout = timeouts_.begin()->second;\n      timeouts_.erase(timeouts_.begin());\n      folly::event_ref_flags(timeout->getEvent()->getEvent()) &=\n          ~EVLIST_TIMEOUT;\n      timeout->timeoutExpired();\n    }\n  }\n\n protected:\n  MockTimeoutManager timeoutManager_;\n  MockTimeoutClock timeoutClock_;\n  std::multimap<milliseconds, AsyncTimeout*> timeouts_;\n};\n\n/*\n * Test firing some simple timeouts that are fired once and never rescheduled\n */\nTEST_F(TimeoutTest, FireOnce) {\n  StackTimeoutSet ts10(\n      &timeoutManager_, milliseconds(10), milliseconds(0), &timeoutClock_);\n  StackTimeoutSet ts5(\n      &timeoutManager_, milliseconds(5), milliseconds(0), &timeoutClock_);\n\n  const AsyncTimeoutSet::Callback* nullCallback = nullptr;\n  ASSERT_EQ(ts10.front(), nullCallback);\n  ASSERT_EQ(ts5.front(), nullCallback);\n\n  TestTimeout t1;\n  TestTimeout t2;\n  TestTimeout t3;\n\n  ts5.scheduleTimeout(&t1); // fires at time=5\n\n  // tick forward to time=2 and schedule another 5ms (@7ms) and a\n  // 10ms (12ms) timeout\n\n  setClock(milliseconds(2));\n\n  ts5.scheduleTimeout(&t2);\n  ts10.scheduleTimeout(&t3);\n\n  ASSERT_EQ(ts10.front(), &t3);\n  ASSERT_EQ(ts5.front(), &t1);\n\n  setClock(milliseconds(5));\n  ASSERT_EQ(ts5.front(), &t2);\n\n  setClock(milliseconds(7));\n  ASSERT_EQ(ts5.front(), nullCallback);\n\n  ASSERT_EQ(t1.timestamps.size(), 1);\n  ASSERT_EQ(t2.timestamps.size(), 1);\n\n  setClock(milliseconds(12));\n\n  ASSERT_EQ(t3.timestamps.size(), 1);\n\n  ASSERT_EQ(ts10.front(), nullCallback);\n\n  ASSERT_EQ(t1.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t2.timestamps[0], milliseconds(7));\n  ASSERT_EQ(t3.timestamps[0], milliseconds(12));\n}\n\n/*\n * Test some timeouts that are scheduled on one timeout set, then moved to\n * another timeout set.\n */\nTEST_F(TimeoutTest, SwitchTimeoutSet) {\n  StackTimeoutSet ts10(\n      &timeoutManager_, milliseconds(10), milliseconds(0), &timeoutClock_);\n  StackTimeoutSet ts5(\n      &timeoutManager_, milliseconds(5), milliseconds(0), &timeoutClock_);\n\n  TestTimeout t1(&ts5, &ts10, &ts5);\n  TestTimeout t2(&ts10, &ts10, &ts5);\n  TestTimeout t3(&ts5, &ts5, &ts10, &ts5);\n\n  ts5.scheduleTimeout(&t1);\n\n  loop();\n\n  ASSERT_EQ(t1.timestamps.size(), 3);\n  ASSERT_EQ(t2.timestamps.size(), 3);\n  ASSERT_EQ(t3.timestamps.size(), 4);\n\n  ASSERT_EQ(t1.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t1.timestamps[1] - t1.timestamps[0], milliseconds(10));\n  ASSERT_EQ(t1.timestamps[2] - t1.timestamps[1], milliseconds(5));\n\n  ASSERT_EQ(t2.timestamps[0], milliseconds(10));\n  ASSERT_EQ(t2.timestamps[1] - t2.timestamps[0], milliseconds(10));\n  ASSERT_EQ(t2.timestamps[2] - t2.timestamps[1], milliseconds(5));\n\n  ASSERT_EQ(t3.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t3.timestamps[1] - t3.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t3.timestamps[2] - t3.timestamps[1], milliseconds(10));\n  ASSERT_EQ(t3.timestamps[3] - t3.timestamps[2], milliseconds(5));\n  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(25));\n}\n\n/*\n * Test cancelling a timeout when it is scheduled to be fired right away.\n */\nTEST_F(TimeoutTest, CancelTimeout) {\n  StackTimeoutSet ts5(\n      &timeoutManager_, milliseconds(5), milliseconds(0), &timeoutClock_);\n  StackTimeoutSet ts10(\n      &timeoutManager_, milliseconds(10), milliseconds(0), &timeoutClock_);\n  StackTimeoutSet ts20(\n      &timeoutManager_, milliseconds(20), milliseconds(0), &timeoutClock_);\n\n  // Create several timeouts that will all fire in 5ms.\n  TestTimeout t5_1(&ts5);\n  TestTimeout t5_2(&ts5);\n  TestTimeout t5_3(&ts5);\n  TestTimeout t5_4(&ts5);\n  TestTimeout t5_5(&ts5);\n\n  // Also create a few timeouts to fire in 10ms\n  TestTimeout t10_1(&ts10);\n  TestTimeout t10_2(&ts10);\n  TestTimeout t10_3(&ts10);\n\n  TestTimeout t20_1(&ts20);\n  TestTimeout t20_2(&ts20);\n\n  // Have t5_1 cancel t5_2 and t5_4.\n  //\n  // Cancelling t5_2 will test cancelling a timeout that is at the head of the\n  // list and ready to be fired.\n  //\n  // Cancelling t5_4 will test cancelling a timeout in the middle of the list\n  t5_1.fn = [&] {\n    t5_2.cancelTimeout();\n    t5_4.cancelTimeout();\n  };\n\n  // Have t5_3 cancel t5_5.\n  // This will test cancelling the last remaining timeout.\n  //\n  // Then have t5_3 reschedule itself.\n  t5_3.fn = [&] {\n    t5_5.cancelTimeout();\n    // Reset our function so we won't continually reschedule ourself\n    auto fn = std::move(t5_3.fn);\n    ts5.scheduleTimeout(&t5_3);\n\n    // Also test cancelling timeouts in another timeset that isn't ready to\n    // fire yet.\n    //\n    // Cancel the middle timeout in ts10.\n    t10_2.cancelTimeout();\n    // Cancel both the timeouts in ts20.\n    t20_1.cancelTimeout();\n    t20_2.cancelTimeout();\n  };\n\n  loop();\n\n  ASSERT_EQ(t5_1.timestamps.size(), 1);\n  ASSERT_EQ(t5_1.timestamps[0], milliseconds(5));\n\n  ASSERT_EQ(t5_3.timestamps.size(), 2);\n  ASSERT_EQ(t5_3.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t5_3.timestamps[1] - t5_3.timestamps[0], milliseconds(5));\n\n  ASSERT_EQ(t10_1.timestamps.size(), 1);\n  ASSERT_EQ(t10_1.timestamps[0], milliseconds(10));\n\n  ASSERT_EQ(t10_3.timestamps.size(), 1);\n  ASSERT_EQ(t10_3.timestamps[0], milliseconds(10));\n\n  // Cancelled timeouts\n  ASSERT_EQ(t5_2.timestamps.size(), 0);\n  ASSERT_EQ(t5_4.timestamps.size(), 0);\n  ASSERT_EQ(t5_5.timestamps.size(), 0);\n  ASSERT_EQ(t10_2.timestamps.size(), 0);\n  ASSERT_EQ(t20_1.timestamps.size(), 0);\n  ASSERT_EQ(t20_2.timestamps.size(), 0);\n  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(10));\n}\n\n/*\n * Test destroying a AsyncTimeoutSet with timeouts outstanding\n */\nTEST_F(TimeoutTest, DestroyTimeoutSet) {\n  AsyncTimeoutSet::UniquePtr ts5(new AsyncTimeoutSet(\n      &timeoutManager_, milliseconds(5), milliseconds(0), &timeoutClock_));\n  AsyncTimeoutSet::UniquePtr ts10(new AsyncTimeoutSet(\n      &timeoutManager_, milliseconds(10), milliseconds(0), &timeoutClock_));\n\n  TestTimeout t5_1(ts5.get());\n  TestTimeout t5_2(ts5.get());\n  TestTimeout t5_3(ts5.get());\n\n  TestTimeout t10_1(ts10.get());\n  TestTimeout t10_2(ts10.get());\n\n  // Have t5_1 destroy ts10\n  t5_1.fn = [&] { ts10.reset(); };\n  // Have t5_2 destroy ts5\n  // Note that this will call destroy() on ts5 inside ts5's timeoutExpired()\n  // method.\n  t5_2.fn = [&] { ts5.reset(); };\n\n  loop();\n\n  ASSERT_EQ(t5_1.timestamps.size(), 1);\n  ASSERT_EQ(t5_1.timestamps[0], milliseconds(5));\n  ASSERT_EQ(t5_2.timestamps.size(), 1);\n  ASSERT_EQ(t5_2.timestamps[0], milliseconds(5));\n\n  ASSERT_EQ(t5_3.timestamps.size(), 0);\n  ASSERT_EQ(t10_1.timestamps.size(), 0);\n  ASSERT_EQ(t10_2.timestamps.size(), 0);\n  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(5));\n}\n\n/*\n * Test the atMostEveryN parameter, to ensure that the timeout does not fire\n * too frequently.\n */\nTEST_F(TimeoutTest, AtMostEveryN) {\n  // Create a timeout set with a 25ms interval, to fire no more than once\n  // every 6ms.\n  milliseconds interval(25);\n  milliseconds atMostEveryN(6);\n  StackTimeoutSet ts25(\n      &timeoutManager_, interval, atMostEveryN, &timeoutClock_);\n\n  // Create 60 timeouts to be added to ts25 at 1ms intervals.\n  uint32_t numTimeouts = 60;\n  std::vector<TestTimeout> timeouts(numTimeouts);\n\n  // Create a scheduler timeout to add the timeouts 1ms apart.\n  // Note, these will start firing partway through scheduling them\n  for (uint32_t index = 0; index < numTimeouts; index++) {\n    setClock(milliseconds(index));\n    timeouts[index].timeoutExpired();\n    ts25.scheduleTimeout(&timeouts[index]);\n  }\n\n  loop();\n\n  // We scheduled timeouts 1ms apart, when the AsyncTimeoutSet is only allowed\n  // to wake up at most once every 3ms.  It will therefore wake up every 3ms\n  // and fire groups of approximately 3 timeouts at a time.\n  //\n  // This is \"approximately 3\" since it may get slightly behind and fire 4 in\n  // one interval, etc.  CHECK_TIMEOUT normally allows a few milliseconds of\n  // tolerance.  We have to add the same into our checking algorithm here.\n  for (uint32_t idx = 0; idx < numTimeouts; ++idx) {\n    ASSERT_EQ(timeouts[idx].timestamps.size(), 2);\n\n    auto scheduledTime = timeouts[idx].timestamps[0] + interval;\n    auto firedTime = timeouts[idx].timestamps[1];\n    // Assert that the timeout fired at roughly the right time.\n    // CHECK_TIMEOUT() normally has a tolerance of 5ms.  Allow an additional\n    // atMostEveryN.\n    milliseconds tolerance = atMostEveryN;\n    ASSERT_GE(firedTime, scheduledTime);\n    ASSERT_LT(firedTime, scheduledTime + tolerance);\n\n    // Assert that the difference between the previous timeout and now was\n    // either very small (fired in the same event loop), or larger than\n    // atMostEveryN.\n    if (idx == 0) {\n      // no previous value\n      continue;\n    }\n    auto prev = timeouts[idx - 1].timestamps[1];\n\n    auto delta = firedTime - prev;\n    if (delta >= milliseconds(1)) {\n      ASSERT_GE(delta, atMostEveryN);\n    }\n  }\n  ASSERT_LE(timeoutClock_.millisecondsSinceEpoch(),\n            milliseconds(numTimeouts) + interval + atMostEveryN);\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/CMakeLists.txt",
    "content": "# Copyright (c) Meta Platforms, Inc. and affiliates.\n# All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n\nproxygen_add_test(TARGET AsyncTimeoutSetTest\n  SOURCES\n    AsyncTimeoutSetTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET TraceEventTest\n  SOURCES\n    TraceEventTest.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n\nproxygen_add_test(TARGET UtilTests\n  SOURCES\n    CompressionFilterUtilsTest.cpp\n    ConditionalGateTest.cpp\n    CryptUtilTest.cpp\n    GenericFilterTest.cpp\n    HTTPTimeTest.cpp\n    LoggingTests.cpp\n    ParseURLTest.cpp\n    PerfectIndexMapTest.cpp\n    RendezvousHashTest.cpp\n    TimeTest.cpp\n    UtilTest.cpp\n    WeakRefCountedPtrTest.cpp\n    ZlibTests.cpp\n    #ZstdTests.cpp\n  DEPENDS\n    proxygen\n    testmain\n)\n"
  },
  {
    "path": "proxygen/lib/utils/test/CompressionFilterUtilsTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/utils/CompressionFilterUtils.h>\n\nusing namespace proxygen;\n\nnamespace {\n\nclass DetermineCompressionTypeTest : public ::testing::Test {\n protected:\n  using CodecType = CompressionFilterUtils::CodecType;\n\n  CodecType determineType(const std::string& acceptEncoding,\n                          bool enableZstd = true,\n                          bool enableGzip = true) {\n    return CompressionFilterUtils::determineCompressionType(\n        acceptEncoding, enableZstd, enableGzip);\n  }\n};\n\nTEST_F(DetermineCompressionTypeTest, Zstd) {\n  EXPECT_EQ(determineType(\"zstd\"), CodecType::ZSTD);\n}\n\nTEST_F(DetermineCompressionTypeTest, Gzip) {\n  EXPECT_EQ(determineType(\"gzip\"), CodecType::ZLIB);\n}\n\nTEST_F(DetermineCompressionTypeTest, GzipWithQuality) {\n  EXPECT_EQ(determineType(\"gzip; q=5.0\"), CodecType::ZLIB);\n}\n\nTEST_F(DetermineCompressionTypeTest, Empty) {\n  EXPECT_EQ(determineType(\"\"), CodecType::NO_COMPRESSION);\n}\n\nTEST_F(DetermineCompressionTypeTest, UnsupportedEncoding) {\n  EXPECT_EQ(determineType(\"br\"), CodecType::NO_COMPRESSION);\n}\n\nTEST_F(DetermineCompressionTypeTest, ZstdDisabled) {\n  EXPECT_EQ(determineType(\"zstd\", false, true), CodecType::NO_COMPRESSION);\n}\n\nTEST_F(DetermineCompressionTypeTest, GzipDisabled) {\n  EXPECT_EQ(determineType(\"gzip\", true, false), CodecType::NO_COMPRESSION);\n}\n\nTEST_F(DetermineCompressionTypeTest, MultipleEncodingsZstdFirst) {\n  EXPECT_EQ(determineType(\"zstd, gzip\"), CodecType::ZSTD);\n}\n\nTEST_F(DetermineCompressionTypeTest, MultipleEncodingsGzipFirst) {\n  EXPECT_EQ(determineType(\"gzip, zstd\"), CodecType::ZLIB);\n}\n\nTEST_F(DetermineCompressionTypeTest, ZstdDisabledFallbackToGzip) {\n  EXPECT_EQ(determineType(\"zstd, gzip\", false, true), CodecType::ZLIB);\n}\n\n} // namespace\n"
  },
  {
    "path": "proxygen/lib/utils/test/ConditionalGateTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n\n#include <proxygen/lib/utils/ConditionalGate.h>\n\nusing namespace proxygen;\n\nclass ConditionalGateTest : public testing::Test {\n protected:\n  enum class Things : uint8_t { Thing1 = 0, Thing2 };\n  ConditionalGate<Things, 2> gate;\n  bool done{false};\n};\n\nTEST_F(ConditionalGateTest, Delay) {\n  gate.then([this] { done = true; });\n  EXPECT_FALSE(done);\n  gate.set(Things::Thing1);\n  EXPECT_FALSE(done);\n  gate.set(Things::Thing2);\n  EXPECT_TRUE(done);\n}\n\nTEST_F(ConditionalGateTest, DelayMulti) {\n  bool otherDone = false;\n  gate.then([this] { done = true; });\n  gate.then([&otherDone] { otherDone = true; });\n  EXPECT_FALSE(done);\n  EXPECT_FALSE(otherDone);\n  gate.set(Things::Thing1);\n  EXPECT_FALSE(done);\n  EXPECT_FALSE(otherDone);\n  gate.set(Things::Thing2);\n  EXPECT_TRUE(done);\n  EXPECT_TRUE(otherDone);\n}\n\nTEST_F(ConditionalGateTest, Immediate) {\n  gate.set(Things::Thing1);\n  EXPECT_FALSE(done);\n  EXPECT_TRUE(gate.get(Things::Thing1));\n  EXPECT_FALSE(gate.get(Things::Thing2));\n  gate.set(Things::Thing2);\n  EXPECT_FALSE(done);\n  gate.then([this] { done = true; });\n  EXPECT_TRUE(done);\n  EXPECT_TRUE(gate.allConditionsMet());\n}\n\nTEST(ReadyGateTest, Delay) {\n  ReadyGate ready;\n  bool done = false;\n  ready.then([&done] { done = true; });\n  EXPECT_FALSE(done);\n  ready.set();\n  EXPECT_TRUE(done);\n}\n\nTEST(ReadyGateTest, Immediate) {\n  ReadyGate ready;\n  bool done = false;\n  ready.set();\n  EXPECT_FALSE(done);\n  ready.then([&done] { done = true; });\n  EXPECT_TRUE(done);\n}\n\nTEST(ConditionalGateDeleteTest, DeleteFromCallback) {\n  auto ready = std::make_unique<ReadyGate>();\n  ready->then([&ready] { ready.reset(); });\n  ready->set();\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/CryptUtilTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/CryptUtil.h>\n\n#include <folly/portability/GTest.h>\n#include <string>\n\nusing namespace proxygen;\n\nusing folly::ByteRange;\n\nTEST(CryptUtilTest, Base64EncodeTest) {\n  ASSERT_EQ(\"\",\n            base64Encode(ByteRange(reinterpret_cast<const unsigned char*>(\"\"),\n                                   (size_t)0)));\n  ASSERT_EQ(\n      \"YQ==\",\n      base64Encode(ByteRange(reinterpret_cast<const unsigned char*>(\"a\"), 1)));\n  ASSERT_EQ(\n      \"YWE=\",\n      base64Encode(ByteRange(reinterpret_cast<const unsigned char*>(\"aa\"), 2)));\n  ASSERT_EQ(\n      \"QWxhZGRpbjpvcGVuIHNlc2FtZQ==\",\n      base64Encode(ByteRange(\n          reinterpret_cast<const unsigned char*>(\"Aladdin:open sesame\"), 19)));\n}\n\nTEST(CryptUtilTest, MD5EncodeTest) {\n  ASSERT_EQ(\"d41d8cd98f00b204e9800998ecf8427e\",\n            md5Encode(ByteRange(reinterpret_cast<const unsigned char*>(\"\"),\n                                (size_t)0)));\n  ASSERT_EQ(\n      \"a7a93b8ac14a48faa68e4afb57b00fc7\",\n      md5Encode(ByteRange(\n          reinterpret_cast<const unsigned char*>(\"Aladdin:open sesame\"), 19)));\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/GenericFilterTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <deque>\n#include <folly/Memory.h>\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/utils/FilterChain.h>\n#include <stdlib.h>\n\nusing namespace proxygen;\nusing namespace testing;\n\nusing std::unique_ptr;\n\nnamespace detail {\n\n// Helper to get a raw pointer from a unique_ptr\n\ntemplate <typename T>\nT* get_pointer(const unique_ptr<T>& ptr) {\n  return ptr.get();\n}\n\ntemplate <typename T>\nT* get_pointer(T* ptr) {\n  return ptr;\n}\n\n} // namespace detail\n\nclass TesterInterface {\n public:\n  class Callback {\n   public:\n    virtual ~Callback() = default;\n    virtual void onA() = 0;\n  };\n  virtual ~TesterInterface() = default;\n  virtual void setCallback(Callback* cb) = 0;\n  virtual void doA() = 0;\n};\n\nclass MockTester : public TesterInterface {\n public:\n  Callback* cb_{nullptr};\n  void setCallback(Callback* cb) override {\n    cb_ = cb;\n  }\n  MOCK_METHOD(void, doA, ());\n};\n\nclass MockTesterCallback : public TesterInterface::Callback {\n public:\n  MOCK_METHOD(void, onA, ());\n};\n\ntemplate <bool Owned>\nclass TestFilter\n    : public GenericFilter<TesterInterface,\n                           TesterInterface::Callback,\n                           &TesterInterface::setCallback,\n                           Owned> {\n public:\n  TestFilter()\n      : GenericFilter<TesterInterface,\n                      TesterInterface::Callback,\n                      &TesterInterface::setCallback,\n                      Owned>(true, true) {\n  }\n\n  TestFilter(bool calls, bool callbacks)\n      : GenericFilter<TesterInterface,\n                      TesterInterface::Callback,\n                      &TesterInterface::setCallback,\n                      Owned>(calls, callbacks) {\n  }\n\n  void setCallback(TesterInterface::Callback* cb) override {\n    this->setCallbackInternal(cb);\n  }\n  void doA() override {\n    do_++;\n    this->call_->doA();\n  }\n  void onA() override {\n    on_++;\n    this->callback_->onA();\n  }\n  uint32_t do_{0};\n  uint32_t on_{0};\n  uint32_t id_{idCounter_++};\n  static uint32_t idCounter_;\n};\ntemplate <bool Owned>\nuint32_t TestFilter<Owned>::idCounter_ = 0;\n\ntemplate <bool Owned>\nclass TestFilterNoCallback : public TestFilter<Owned> {\n public:\n  TestFilterNoCallback() : TestFilter<Owned>(true, false) {\n  }\n};\n\ntemplate <bool Owned>\nclass TestFilterNoCall : public TestFilter<Owned> {\n public:\n  TestFilterNoCall() : TestFilter<Owned>(false, true) {\n  }\n};\n\ntemplate <bool Owned>\nclass TestFilterNoCallbackNoCall : public TestFilter<Owned> {\n public:\n  TestFilterNoCallbackNoCall() : TestFilter<Owned>(false, false) {\n  }\n};\n\ntemplate <bool Owned>\ntypename std::enable_if<Owned, unique_ptr<MockTester>>::type getTester() {\n  return std::make_unique<MockTester>();\n}\n\ntemplate <bool Owned>\ntypename std::enable_if<!Owned, MockTester*>::type getTester() {\n  return new MockTester();\n}\n\ntemplate <bool Owned>\nclass GenericFilterTest : public testing::Test {\n public:\n  void basicTest();\n\n  void testFilters(const std::deque<TestFilter<Owned>*>& filters,\n                   MockTesterCallback* expectedCb);\n\n  void SetUp() override {\n    chain_ = std::make_unique<FilterChain<TesterInterface,\n                                          TesterInterface::Callback,\n                                          TestFilter<Owned>,\n                                          &TesterInterface::setCallback,\n                                          Owned>>(getTester<Owned>());\n    chain().setCallback(&callback_);\n    actor_ = CHECK_NOTNULL(static_cast<MockTester*>(chain_->call()));\n  }\n  FilterChain<TesterInterface,\n              TesterInterface::Callback,\n              TestFilter<Owned>,\n              &TesterInterface::setCallback,\n              Owned>&\n  chain() {\n    return *chain_;\n  }\n\n  template <typename FilterT>\n  typename std::enable_if<!Owned, FilterT*>::type getFilter() {\n    return new FilterT();\n  }\n\n  template <typename FilterT>\n  typename std::enable_if<Owned, unique_ptr<FilterT>>::type getFilter() {\n    return std::make_unique<FilterT>();\n  }\n\n  template <typename FilterT>\n  void addFilterToChain(std::deque<TestFilter<Owned>*>& refs) {\n    auto f = getFilter<FilterT>();\n    refs.push_front(::detail::get_pointer(f));\n    chain().addFilters(std::move(f));\n  }\n\n  std::deque<TestFilter<Owned>*> getRandomFilters(unsigned num) {\n    std::deque<TestFilter<Owned>*> filters;\n    srand(0);\n    for (unsigned i = 0; i < num; ++i) {\n      auto r = rand() % 4;\n      if (r == 0) {\n        addFilterToChain<TestFilter<Owned>>(filters);\n      } else if (r == 1) {\n        addFilterToChain<TestFilterNoCall<Owned>>(filters);\n      } else if (r == 2) {\n        addFilterToChain<TestFilterNoCallback<Owned>>(filters);\n      } else if (r == 3) {\n        addFilterToChain<TestFilterNoCallbackNoCall<Owned>>(filters);\n      }\n      basicTest();\n    }\n    return filters;\n  }\n\n  MockTesterCallback callback_;\n  unique_ptr<FilterChain<TesterInterface,\n                         TesterInterface::Callback,\n                         TestFilter<Owned>,\n                         &TesterInterface::setCallback,\n                         Owned>>\n      chain_;\n  MockTester* actor_{nullptr};\n};\n\ntemplate <bool Owned>\nvoid GenericFilterTest<Owned>::basicTest() {\n  InSequence enforceOrder;\n\n  // Test call side\n  EXPECT_CALL(*actor_, doA());\n  chain()->doA();\n\n  // Now poke the callback side\n  EXPECT_CALL(callback_, onA());\n  CHECK_NOTNULL(actor_->cb_);\n  actor_->cb_->onA();\n}\n\ntemplate <bool Owned>\nvoid GenericFilterTest<Owned>::testFilters(\n    const std::deque<TestFilter<Owned>*>& filters,\n    MockTesterCallback* expectedCb) {\n  for (auto f : filters) {\n    f->do_ = 0;\n    f->on_ = 0;\n  }\n  // Call\n  EXPECT_CALL(*actor_, doA());\n  chain()->doA();\n  // Callback\n  if (expectedCb) {\n    EXPECT_CALL(*expectedCb, onA());\n    CHECK_NOTNULL(actor_->cb_);\n    actor_->cb_->onA();\n  }\n  for (auto f : filters) {\n    if (f->kWantsCalls_) {\n      EXPECT_EQ(f->do_, 1);\n    } else {\n      EXPECT_EQ(f->do_, 0);\n    }\n    if (f->kWantsCallbacks_) {\n      EXPECT_EQ(f->on_, expectedCb ? 1 : 0);\n    } else {\n      EXPECT_EQ(f->on_, 0);\n    }\n  }\n}\n\nusing OwnedGenericFilterTest = GenericFilterTest<true>;\nusing UnownedGenericFilterTest = GenericFilterTest<false>;\n\nTEST_F(OwnedGenericFilterTest, EmptyChain) {\n  basicTest();\n}\n\nTEST_F(OwnedGenericFilterTest, SingleElemChain) {\n  auto filterUnique = std::make_unique<TestFilter<true>>();\n  auto filter = filterUnique.get();\n  chain().addFilters(std::move(filterUnique));\n  EXPECT_EQ(filter->do_, 0);\n  EXPECT_EQ(filter->on_, 0);\n  basicTest();\n  EXPECT_EQ(filter->do_, 1);\n  EXPECT_EQ(filter->on_, 1);\n}\n\nTEST_F(OwnedGenericFilterTest, MultiElemChain) {\n  auto f1 = std::make_unique<TestFilter<true>>();\n  auto f2 = std::make_unique<TestFilter<true>>();\n  auto f3 = std::make_unique<TestFilter<true>>();\n  TestFilter<true>* fp1 = f1.get();\n  TestFilter<true>* fp2 = f2.get();\n  TestFilter<true>* fp3 = f3.get();\n  chain().addFilters(std::move(f1), std::move(f2), std::move(f3));\n  basicTest();\n  EXPECT_EQ(fp1->do_, 1);\n  EXPECT_EQ(fp1->on_, 1);\n  EXPECT_EQ(fp2->do_, 1);\n  EXPECT_EQ(fp2->on_, 1);\n  EXPECT_EQ(fp3->do_, 1);\n  EXPECT_EQ(fp3->on_, 1);\n}\n\nTEST_F(OwnedGenericFilterTest, MultiElemMultiAdd) {\n  std::deque<TestFilter<true>*> filters;\n  for (unsigned i = 0; i < 10; ++i) {\n    auto filter = std::make_unique<TestFilter<true>>();\n    filters.push_back(filter.get());\n    chain().addFilters(std::move(filter));\n  }\n  basicTest();\n  for (auto filter : filters) {\n    EXPECT_EQ(filter->do_, 1);\n    EXPECT_EQ(filter->on_, 1);\n  }\n}\n\nTEST_F(OwnedGenericFilterTest, Wants) {\n  auto f1 = std::make_unique<TestFilter<true>>();\n  auto f2 = std::make_unique<TestFilterNoCallback<true>>();\n  auto f3 = std::make_unique<TestFilterNoCall<true>>();\n  auto f4 = std::make_unique<TestFilterNoCallbackNoCall<true>>();\n  TestFilter<true>* fp1 = f1.get();\n  TestFilter<true>* fp2 = f2.get();\n  TestFilter<true>* fp3 = f3.get();\n  TestFilter<true>* fp4 = f4.get();\n  chain().addFilters(\n      std::move(f1), std::move(f2), std::move(f3), std::move(f4));\n  basicTest();\n  EXPECT_EQ(fp1->do_, 1);\n  EXPECT_EQ(fp1->on_, 1);\n  // Only calls\n  EXPECT_EQ(fp2->do_, 1);\n  EXPECT_EQ(fp2->on_, 0);\n  // Only callbacks\n  EXPECT_EQ(fp3->do_, 0);\n  EXPECT_EQ(fp3->on_, 1);\n  // No callbacks or calls\n  EXPECT_EQ(fp4->do_, 0);\n  EXPECT_EQ(fp4->on_, 0);\n}\n\nTEST_F(OwnedGenericFilterTest, WantsMultiAdd) {\n  auto f1 = std::make_unique<TestFilterNoCallback<true>>();\n  auto f2 = std::make_unique<TestFilterNoCall<true>>();\n  TestFilter<true>* fp1 = f1.get();\n  TestFilter<true>* fp2 = f2.get();\n  chain().addFilters(std::move(f1));\n  basicTest();\n\n  EXPECT_EQ(fp1->do_, 1);\n  EXPECT_EQ(fp1->on_, 0);\n  EXPECT_EQ(fp2->do_, 0);\n  EXPECT_EQ(fp2->on_, 0);\n\n  chain().addFilters(std::move(f2));\n  basicTest();\n\n  EXPECT_EQ(fp1->do_, 2);\n  EXPECT_EQ(fp1->on_, 0);\n  EXPECT_EQ(fp2->do_, 0);\n  EXPECT_EQ(fp2->on_, 1);\n}\n\nTEST_F(OwnedGenericFilterTest, WantsMultiAddHard) {\n  const unsigned NUM_FILTERS = 5000;\n  auto filters = getRandomFilters(NUM_FILTERS);\n  // Now check the counts on each filter. Filters are pushed to the front\n  // of the chain, so filters towards the front have low call/callback counts\n  for (unsigned i = 0; i < NUM_FILTERS; ++i) {\n    auto f = filters[i];\n    if (f->kWantsCalls_) {\n      EXPECT_EQ(f->do_, i + 1);\n    } else {\n      EXPECT_EQ(f->do_, 0);\n    }\n    if (f->kWantsCallbacks_) {\n      EXPECT_EQ(f->on_, i + 1);\n    } else {\n      EXPECT_EQ(f->on_, 0);\n    }\n  }\n}\n\nTEST_F(OwnedGenericFilterTest, ChangeCallback) {\n  // The call-only filter in the chain doesn't want callbacks, so doing\n  // chain()->setCallback() is an error! Instead, must use chain().setCallback()\n  auto f = std::make_unique<TestFilterNoCallback<true>>();\n  MockTesterCallback callback2;\n\n  TestFilter<true>* fp = f.get();\n  chain().addFilters(std::move(f));\n  basicTest();\n\n  EXPECT_EQ(fp->do_, 1);\n  EXPECT_EQ(fp->on_, 0);\n\n  chain().setCallback(&callback2);\n  EXPECT_EQ(actor_->cb_, &callback2);\n  EXPECT_CALL(callback2, onA());\n  actor_->cb_->onA();\n\n  EXPECT_EQ(fp->on_, 0);\n}\n\nTEST_F(UnownedGenericFilterTest, All) {\n  const unsigned NUM_FILTERS = 5000;\n  auto filters = getRandomFilters(NUM_FILTERS);\n  // Now check the counts on each filter\n  unsigned i = 0;\n  for (auto f : filters) {\n    if (f->kWantsCalls_) {\n      EXPECT_EQ(f->do_, i + 1);\n    } else {\n      EXPECT_EQ(f->do_, 0);\n    }\n    if (f->kWantsCallbacks_) {\n      EXPECT_EQ(f->on_, i + 1);\n    } else {\n      EXPECT_EQ(f->on_, 0);\n    }\n    delete f;\n    ++i;\n  }\n  delete actor_;\n}\n\nTEST_F(OwnedGenericFilterTest, SetNullCb) {\n  // Some objects have a special behavior when the callback is set to\n  // nullptr. So in this case, we need to make sure it propagates\n  auto filters = getRandomFilters(100);\n  chain().setCallback(nullptr);\n  CHECK(nullptr == actor_->cb_);\n\n  testFilters(filters, nullptr);\n\n  MockTesterCallback head;\n  chain().setCallback(&head);\n\n  testFilters(filters, &head);\n\n  TesterInterface::Callback* cb = &head;\n  for (auto f : filters) {\n    if (f->kWantsCallbacks_) {\n      cb = f;\n    }\n  }\n  // The actor's callback should be the last filter in the chain that\n  // wants callbacks\n  ASSERT_EQ(actor_->cb_, cb);\n}\n\n// This class owns itself\nclass TestFilterOddDeleteDo : public TestFilter<false> {\n public:\n  explicit TestFilterOddDeleteDo(int* deletions)\n      : TestFilter<false>(true, true), deletions_(CHECK_NOTNULL(deletions)) {\n  }\n  ~TestFilterOddDeleteDo() override {\n    ++*deletions_;\n  }\n\n  void doA() override {\n    auto call = call_;\n    if (id_ % 2) {\n      delete this;\n    } else if (times_++) {\n      delete this;\n    }\n    call->doA();\n  };\n  unsigned times_{0};\n  int* const deletions_;\n};\n\nTEST_F(UnownedGenericFilterTest, DeleteDo) {\n  // Test where a filter in the middle of the chain deletes itself early\n  int deletions = 0;\n\n  for (unsigned i = 0; i < 4; ++i) {\n    chain().addFilters(new TestFilterOddDeleteDo(&deletions));\n  }\n\n  for (unsigned i = 0; i < 2; ++i) {\n    // First time around, the odd id's get deleted\n    // Second time should just forward the calls normally\n    EXPECT_CALL(*actor_, doA());\n    chain()->doA();\n    EXPECT_EQ(deletions, (i + 1) * 2);\n  }\n  basicTest();\n  delete actor_;\n}\n\ntemplate <bool Owned = false>\nclass TestFilterOddDeleteOn : public TestFilter<Owned> {\n public:\n  explicit TestFilterOddDeleteOn(int* deletions)\n      : deletions_(CHECK_NOTNULL(deletions)) {\n  }\n  ~TestFilterOddDeleteOn() override {\n    ++*deletions_;\n  }\n\n  void onA() override {\n    auto callback = this->callback_;\n    if (this->id_ % 2) {\n      delete this;\n    } else if (times_++) {\n      delete this;\n    }\n    callback->onA();\n  };\n  unsigned times_{0};\n  int* const deletions_;\n};\n\nTEST_F(UnownedGenericFilterTest, DeleteOn) {\n  // Test where a filter in the middle of the chain deletes itself early\n  int deletions = 0;\n\n  for (unsigned i = 0; i < 4; ++i) {\n    chain().addFilters(new TestFilterOddDeleteOn<>(&deletions));\n  }\n\n  for (unsigned i = 0; i < 2; ++i) {\n    // First time around, the odd id's get deleted\n    // Second time should just forward the calls normally\n    EXPECT_CALL(callback_, onA());\n    actor_->cb_->onA();\n    EXPECT_EQ(deletions, (i + 1) * 2);\n  }\n  basicTest();\n  delete actor_;\n}\n\nTEST_F(OwnedGenericFilterTest, DeleteChain) {\n  // Add some filters to the chain and reset the chain. Make sure all the\n  // filters are deleted.\n  const unsigned NUM_FILTERS = 1000;\n  int deletions = 0;\n  for (unsigned i = 0; i < NUM_FILTERS; ++i) {\n    chain().addFilters(\n        std::make_unique<TestFilterOddDeleteOn<true>>(&deletions));\n  }\n  chain_.reset();\n  EXPECT_EQ(deletions, NUM_FILTERS);\n}\n\nTEST_F(OwnedGenericFilterTest, GetChainEnd) {\n  for (unsigned i = 1; i < 100; ++i) {\n    auto filters = getRandomFilters(i);\n    EXPECT_EQ(actor_, &chain().getChainEnd());\n  }\n}\n\nTEST_F(OwnedGenericFilterTest, SetDestination) {\n  auto filters = getRandomFilters(20);\n  EXPECT_CALL(*actor_, doA());\n  chain()->doA();\n  auto tester2 = getTester<true>();\n  actor_ = tester2.get();\n  auto oldTester = chain().setDestination(std::move(tester2));\n  EXPECT_CALL(*actor_, doA());\n  chain()->doA();\n}\n\nTEST_F(OwnedGenericFilterTest, Foreach) {\n  auto filters = getRandomFilters(20);\n  size_t count = 0;\n  chain().foreach (\n      [&count](GenericFilter<TesterInterface,\n                             TesterInterface::Callback,\n                             &TesterInterface::setCallback,\n                             true,\n                             std::default_delete<TesterInterface>>* filter) {\n        if (dynamic_cast<TestFilter<true>*>(filter)) {\n          count++;\n        }\n      });\n  EXPECT_EQ(count, 20);\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/HTTPTimeTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/HTTPTime.h>\n\n#include <folly/portability/GTest.h>\n\nusing proxygen::parseHTTPDateTime;\n\nTEST(HTTPTimeTests, InvalidTimeTest) {\n  EXPECT_FALSE(parseHTTPDateTime(\"Hello, World\").has_value());\n  EXPECT_FALSE(parseHTTPDateTime(\"Sun, 33 Nov 1994 08:49:37 GMT\").has_value());\n  EXPECT_FALSE(parseHTTPDateTime(\"Sun, 06 Nov 1800\").has_value());\n}\n\nTEST(HTTPTimeTests, ValidTimeTest) {\n  // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3\n  EXPECT_TRUE(parseHTTPDateTime(\"Sun, 06 Nov 1994 08:49:37 GMT\").has_value());\n  EXPECT_TRUE(parseHTTPDateTime(\"Sunday, 06-Nov-94 08:49:37 GMT\").has_value());\n  EXPECT_TRUE(parseHTTPDateTime(\"Sun Nov  6 08:49:37 1994\").has_value());\n}\n\nTEST(HTTPTimeTests, EqualTimeTest) {\n  auto a = parseHTTPDateTime(\"Thu, 07 Mar 2013 08:49:37 GMT\");\n  EXPECT_TRUE(a.has_value());\n  auto b = parseHTTPDateTime(\"Thursday, 07-Mar-13 08:49:37 GMT\");\n  EXPECT_TRUE(b.has_value());\n  auto c = parseHTTPDateTime(\"Thu Mar 7 08:49:37 2013\");\n  EXPECT_TRUE(c.has_value());\n\n  EXPECT_EQ(a.value(), b.value());\n  EXPECT_EQ(a.value(), c.value());\n  EXPECT_EQ(b.value(), c.value());\n}\n\nTEST(HTTPTimeTests, ReallyOldTimeTest) {\n  auto a = parseHTTPDateTime(\"Thu, 07 Mar 1970 08:49:37 GMT\");\n  EXPECT_TRUE(a.has_value());\n  auto b = parseHTTPDateTime(\"Thu, 07 Mar 1971 08:49:37 GMT\");\n  EXPECT_TRUE(b.has_value());\n  auto c = parseHTTPDateTime(\"Thu, 07 Mar 1980 08:49:37 GMT\");\n  EXPECT_TRUE(c.has_value());\n\n  EXPECT_LT(a, b);\n  EXPECT_LT(a, c);\n  EXPECT_LT(b, c);\n}\n\nTEST(HTTPTimeTests, TzToUnixTsTest) {\n  auto a = parseHTTPDateTime(\"Wed, 13 Jun 2018 21:43:49 GMT\");\n  EXPECT_EQ(a.value(), 1528926229);\n  auto b = parseHTTPDateTime(\"Wed, 31 Dec 1969 23:59:59 GMT\");\n  EXPECT_EQ(b.value(), -1);\n  auto c = parseHTTPDateTime(\"Thu, 01 Jan 1970 00:00:00 GMT\");\n  EXPECT_EQ(c.value(), 0);\n  auto d = parseHTTPDateTime(\"Thu, 01 Jan 1970 00:00:01 GMT\");\n  EXPECT_EQ(d.value(), 1);\n\n  auto e = parseHTTPDateTime(\"Wed, 13-Jun-18 21:43:49 GMT\");\n  EXPECT_EQ(e.value(), 1528926229);\n  auto f = parseHTTPDateTime(\"Wed, 31-Dec-69 23:59:59 GMT\");\n  EXPECT_EQ(f.value(), -1);\n  auto g = parseHTTPDateTime(\"Wed, 01-Jan-70 00:00:00 GMT\");\n  EXPECT_EQ(g.value(), 0);\n  auto h = parseHTTPDateTime(\"Thu, 01-Jan-70 00:00:01 GMT\");\n  EXPECT_EQ(h.value(), 1);\n\n  auto i = parseHTTPDateTime(\"Wed Jun 13 21:43:49 2018\");\n  EXPECT_EQ(i.value(), 1528926229);\n  auto j = parseHTTPDateTime(\"Wed Dec 31 23:59:59 1969\");\n  EXPECT_EQ(j.value(), -1);\n  auto k = parseHTTPDateTime(\"Thu Jan 1 00:00:00 1970\");\n  EXPECT_EQ(k.value(), 0);\n  auto l = parseHTTPDateTime(\"Thu Jan 1 00:00:01 1970\");\n  EXPECT_EQ(l.value(), 1);\n\n  auto m = parseHTTPDateTime(\"Tue, 19 Jan 2038 03:14:07 GMT\");\n  EXPECT_EQ(m.value(), 2147483647);\n\n  auto n = parseHTTPDateTime(\"Thu, 01 Jan 1970 00:00:01 PST\");\n  EXPECT_FALSE(n.has_value());\n  auto o = parseHTTPDateTime(\"Thu, 01 Jan 1970 00:00:01\");\n  EXPECT_FALSE(o.has_value());\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/LoggingTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Format.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/utils/Logging.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nclass LoggingTests : public testing::Test {};\n\nTEST_F(LoggingTests, PrintHexIobuf) {\n  unique_ptr<IOBuf> buf = IOBuf::create(128);\n  EXPECT_EQ(IOBufPrinter::printHexFolly(buf.get()), \"\");\n  EXPECT_EQ(IOBufPrinter::printHex16(buf.get()), \"\");\n\n  uint8_t* data = buf->writableData();\n  data[0] = 0x0C;\n  data[1] = 0xFF;\n  data[2] = 0x00;\n  data[3] = 0x10;\n  buf->append(4);\n  EXPECT_TRUE(IOBufPrinter::printHexFolly(buf.get()) != \"\");\n  EXPECT_EQ(IOBufPrinter::printHex16(buf.get()), \"0cff 0010 \");\n\n  // some linewrap\n  for (int i = 0; i < 16; i++) {\n    data[4 + i] = 0xFE;\n  }\n  buf->append(16);\n  EXPECT_TRUE(IOBufPrinter::printHexFolly(buf.get()) != \"\");\n  string info = IOBufPrinter::printChainInfo(buf.get());\n  EXPECT_TRUE(info.find(\"iobuf of size 20 tailroom \") != string::npos);\n  EXPECT_EQ(IOBufPrinter::printHex16(buf.get()),\n            \"0cff 0010 fefe fefe fefe fefe fefe fefe \\nfefe fefe \");\n}\n\nTEST_F(LoggingTests, HexString) {\n  uint8_t buf[] = {0x03, 0x04, 0x11, 0x22, 0xBB, 0xAA};\n  string s((const char*)buf, sizeof(buf));\n  EXPECT_EQ(\"03041122bbaa\", hexStr(s));\n}\n\nTEST_F(LoggingTests, DumpBin) {\n  // null IOBuf\n  EXPECT_EQ(IOBufPrinter::printBin(nullptr), \"\");\n\n  unique_ptr<IOBuf> b1 = IOBuf::create(128);\n  b1->writableData()[0] = 0x33;\n  b1->writableData()[1] = 0x77;\n  b1->append(2);\n  unique_ptr<IOBuf> b2 = IOBuf::create(128);\n  b2->writableData()[0] = 0xFF;\n  b2->append(1);\n  b1->appendChain(std::move(b2));\n  EXPECT_EQ(IOBufPrinter::printBin(b1.get()),\n            \"00110011 3 01110111 w \\n11111111   \\n\");\n  // with coalescing\n  EXPECT_EQ(IOBufPrinter::printBin(b1.get(), true),\n            \"00110011 3 01110111 w 11111111   \\n\");\n}\n\nTEST_F(LoggingTests, DumpBinToFile) {\n  struct stat fstat;\n  auto tmpfile(folly::to<string>(\"/tmp/test_\", getpid(), \".bin\"));\n\n  unlink(tmpfile.c_str());\n  unique_ptr<IOBuf> buf = IOBuf::create(128);\n  // the content doesn't matter\n  buf->append(2);\n  dumpBinToFile(tmpfile, buf.get());\n  EXPECT_EQ(stat(tmpfile.c_str(), &fstat), 0);\n\n  // check if it's going to overwrite the existing file\n  buf->append(4);\n  dumpBinToFile(tmpfile, buf.get());\n  EXPECT_EQ(stat(tmpfile.c_str(), &fstat), 0);\n  EXPECT_EQ(fstat.st_size, 2);\n  unlink(tmpfile.c_str());\n\n  // null iobuf\n  dumpBinToFile(tmpfile, nullptr);\n  // unable to open file\n  dumpBinToFile(\"/proc/test\", nullptr);\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/MockTime.h",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#include <glog/logging.h>\n#include <proxygen/lib/utils/Time.h>\n\nnamespace proxygen {\n\ntemplate <typename ClockType = std::chrono::steady_clock>\nclass MockTimeUtilGeneric : public TimeUtilGeneric<ClockType> {\n public:\n  void advance(std::chrono::milliseconds ms) {\n    t_ += ms;\n  }\n\n  void setCurrentTime(std::chrono::time_point<ClockType> t) {\n    CHECK(t.time_since_epoch() > t_.time_since_epoch())\n        << \"Time can not move backwards\";\n    t_ = t;\n  }\n\n  void verifyAndClear() {\n  }\n\n  std::chrono::time_point<ClockType> now() const override {\n    return t_;\n  }\n\n private:\n  std::chrono::time_point<ClockType> t_;\n};\n\nusing MockTimeUtil = MockTimeUtilGeneric<>;\n\n} // namespace proxygen\n"
  },
  {
    "path": "proxygen/lib/utils/test/ParseURLTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/ParseURL.h>\n\n#include <folly/portability/GTest.h>\n\nusing proxygen::ParseURL;\nusing std::string;\n\nvoid testParseURL(const string& url,\n                  const string& expectedScheme,\n                  const string& expectedPath,\n                  const string& expectedQuery,\n                  const string& expectedHost,\n                  const uint16_t expectedPort,\n                  const string& expectedAuthority,\n                  const bool expectedValid = true,\n                  const bool strict = false) {\n  auto u = ParseURL::parseURLMaybeInvalid(url, strict);\n\n  if (expectedValid) {\n    EXPECT_EQ(url, u.url());\n    EXPECT_EQ(expectedScheme, u.scheme());\n    EXPECT_EQ(expectedPath, u.path());\n    EXPECT_EQ(expectedQuery, u.query());\n    EXPECT_EQ(expectedHost, u.host());\n    EXPECT_EQ(expectedPort, u.port());\n    EXPECT_EQ(expectedAuthority, u.authority());\n    EXPECT_EQ(expectedValid, u.valid());\n  } else {\n    // invalid, do not need to test values\n    EXPECT_EQ(expectedValid, u.valid());\n  }\n}\n\nvoid testHostIsIpAddress(const string& url, const bool expected) {\n  auto u = ParseURL::parseURLMaybeInvalid(url);\n  EXPECT_TRUE(!expected || u.valid());\n  EXPECT_EQ(expected, u.hostIsIPAddress());\n}\n\nTEST(ParseURL, HostNoBrackets) {\n  auto p = ParseURL::parseURL(\"/bar\");\n\n  EXPECT_EQ(\"\", p->host());\n  EXPECT_EQ(\"\", p->hostNoBrackets());\n\n  p = ParseURL::parseURL(\"localhost\");\n  EXPECT_EQ(\"localhost\", p->host());\n  EXPECT_EQ(\"localhost\", p->hostNoBrackets());\n\n  p = ParseURL::parseURL(\"localhost:80\");\n  EXPECT_EQ(\"localhost\", p->host());\n  EXPECT_EQ(\"localhost\", p->hostNoBrackets());\n\n  p = ParseURL::parseURL(\"[::1]:80\");\n  EXPECT_EQ(\"[::1]\", p->host());\n  EXPECT_EQ(\"::1\", p->hostNoBrackets());\n\n  p = ParseURL::parseURL(\"1.2.3.4:80\");\n  EXPECT_EQ(\"1.2.3.4\", p->host());\n  EXPECT_EQ(\"1.2.3.4\", p->hostNoBrackets());\n}\n\nTEST(ParseURL, FullyFormedURL) {\n  testParseURL(\"http://localhost:80/foo?bar#qqq\",\n               \"http\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"http://localhost:80/foo?bar\",\n               \"http\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"http://localhost:80/foo\",\n               \"http\",\n               \"/foo\",\n               \"\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\n      \"http://localhost:80/\", \"http\", \"/\", \"\", \"localhost\", 80, \"localhost:80\");\n  testParseURL(\n      \"http://localhost:80\", \"http\", \"\", \"\", \"localhost\", 80, \"localhost:80\");\n  testParseURL(\"http://localhost\", \"http\", \"\", \"\", \"localhost\", 0, \"localhost\");\n  testParseURL(\"http://[2401:db00:2110:3051:face:0:3f:0]/\",\n               \"http\",\n               \"/\",\n               \"\",\n               \"[2401:db00:2110:3051:face:0:3f:0]\",\n               0,\n               \"[2401:db00:2110:3051:face:0:3f:0]\");\n  testParseURL(\"http://[2401:db00:2110:3051:face:0:3f:0]:8080/\",\n               \"http\",\n               \"/\",\n               \"\",\n               \"[2401:db00:2110:3051:face:0:3f:0]\",\n               8080,\n               \"[2401:db00:2110:3051:face:0:3f:0]:8080\");\n}\n\nTEST(ParseURL, ValidNonHttpScheme) {\n  testParseURL(\"https://localhost:80/foo?bar#qqq\",\n               \"https\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"rtmp://localhost:80/foo?bar#qqq\",\n               \"rtmp\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"ftp://localhost:80/foo?bar#qqq\",\n               \"ftp\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"proxygen://localhost:80/foo?bar#qqq\",\n               \"proxygen\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"test://localhost:80/foo?bar#qqq\",\n               \"test\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n}\n\nTEST(ParseURL, InvalidScheme) {\n  testParseURL(\"test123://localhost:80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"test.1://localhost:80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"://localhost:80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"123://localhost:80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"---://localhost:80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n}\n\nTEST(ParseURL, NoScheme) {\n  testParseURL(\"localhost:80/foo?bar#qqq\",\n               \"\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\"localhost:80/foo?bar\",\n               \"\",\n               \"/foo\",\n               \"bar\",\n               \"localhost\",\n               80,\n               \"localhost:80\");\n  testParseURL(\n      \"localhost:80/foo\", \"\", \"/foo\", \"\", \"localhost\", 80, \"localhost:80\");\n  testParseURL(\"localhost:80/\", \"\", \"/\", \"\", \"localhost\", 80, \"localhost:80\");\n  testParseURL(\"localhost:80\", \"\", \"\", \"\", \"localhost\", 80, \"localhost:80\");\n  testParseURL(\"localhost\", \"\", \"\", \"\", \"localhost\", 0, \"localhost\");\n}\n\nTEST(ParseURL, NoSchemeIP) {\n  testParseURL(\"1.2.3.4:54321/foo?bar#qqq\",\n               \"\",\n               \"/foo\",\n               \"bar\",\n               \"1.2.3.4\",\n               54321,\n               \"1.2.3.4:54321\");\n  testParseURL(\"[::1]:80/foo?bar\", \"\", \"/foo\", \"bar\", \"[::1]\", 80, \"[::1]:80\");\n  testParseURL(\"[::1]/foo?bar\", \"\", \"/foo\", \"bar\", \"[::1]\", 0, \"[::1]\");\n}\n\nTEST(ParseURL, PathOnly) {\n  testParseURL(\"/f/o/o?bar#qqq\", \"\", \"/f/o/o\", \"bar\", \"\", 0, \"\");\n  testParseURL(\"/f/o/o?bar\", \"\", \"/f/o/o\", \"bar\", \"\", 0, \"\");\n  testParseURL(\"/f/o/o\", \"\", \"/f/o/o\", \"\", \"\", 0, \"\");\n  testParseURL(\"/\", \"\", \"/\", \"\", \"\", 0, \"\");\n  testParseURL(\"?foo=bar\", \"\", \"\", \"foo=bar\", \"\", 0, \"\");\n  testParseURL(\"?#\", \"\", \"\", \"\", \"\", 0, \"\");\n  testParseURL(\"#/foo/bar\", \"\", \"\", \"\", \"\", 0, \"\");\n}\n\nTEST(ParseURL, QueryIsURL) {\n  testParseURL(\"/?ids=http://vlc.afreecodec.com/download/\",\n               \"\",\n               \"/\",\n               \"ids=http://vlc.afreecodec.com/download/\",\n               \"\",\n               0,\n               \"\");\n  testParseURL(\"/plugins/facepile.php?href=http://www.vakan.nl/hotels\",\n               \"\",\n               \"/plugins/facepile.php\",\n               \"href=http://www.vakan.nl/hotels\",\n               \"\",\n               0,\n               \"\");\n}\n\nTEST(ParseURL, InvalidURL) {\n  testParseURL(\"http://tel:198433511/\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"localhost:80/foo#bar?qqq\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"#?\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"#?hello\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"[::1/foo?bar\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"http://tel:198433511/test\\n\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"/test\\n\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\n      \"http://foo.com/test\\xff\", \"\", \"\", \"\", \"\", 0, \"\", false, /*strict=*/true);\n  testParseURL(\"http://foo.com/test\\xff\",\n               \"http\",\n               \"/test\\xff\",\n               \"\",\n               \"foo.com\",\n               0,\n               \"foo.com\",\n               true,\n               /*strict=*/false);\n  testParseURL(\"test\\xff\", \"\", \"\", \"\", \"\", 0, \"\", false, /*strict=*/true);\n  testParseURL(\n      \"/test\\xff\", \"\", \"/test\\xff\", \"\", \"\", 0, \"\", true, /*strict=*/false);\n  testParseURL(\"localhost:80abc\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"localhost:0x50\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"localhost:-80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"localhost:+80\", \"\", \"\", \"\", \"\", 0, \"\", false);\n  testParseURL(\"localhost:80.0\", \"\", \"\", \"\", \"\", 0, \"\", false);\n}\n\nTEST(ParseURL, IsHostIPAddress) {\n  testHostIsIpAddress(\"http://127.0.0.1:80\", true);\n  testHostIsIpAddress(\"127.0.0.1:80\", true);\n  testHostIsIpAddress(\"127.0.0.1\", true);\n  testHostIsIpAddress(\"http://[::1]:80\", true);\n  testHostIsIpAddress(\"[::1]\", true);\n  testHostIsIpAddress(\"[::1]:80\", true);\n  testHostIsIpAddress(\"[::AB]\", true);\n\n  testHostIsIpAddress(\"http://localhost:80\", false);\n  testHostIsIpAddress(\"http://localhost\", false);\n  testHostIsIpAddress(\"localhost:80\", false);\n  testHostIsIpAddress(\"localhost\", false);\n  testHostIsIpAddress(\"1.2.3.-1\", false);\n  testHostIsIpAddress(\"1.2.3.999\", false);\n  testHostIsIpAddress(\"::1\", false);\n  testHostIsIpAddress(\"[::99999999]\", false);\n\n  // invalid url\n  testHostIsIpAddress(\"\", false);\n  testHostIsIpAddress(\"127.0.0.1:80/foo#bar?qqq\", false);\n}\n\nTEST(ParseURL, SupportedScheme) {\n  // Valid URL with scheme\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"http://example.com\"));\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"http://example.com/\"));\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"https://example.com\"));\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"https://example.com/\"));\n\n  // Protocol-relative URL\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"//example.com\"));\n\n  // Relative URL\n  EXPECT_TRUE(ParseURL::isSupportedScheme(\"/path\"));\n}\n\nTEST(ParseURL, NotSupportedScheme) {\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"://\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"http ://example.com/\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"://example.com\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"HTTP://EXAMPLE.COM/\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"ftp://example.com\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"httpx://example.com/\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"shttp://example.com/\"));\n  EXPECT_FALSE(ParseURL::isSupportedScheme(\"httpss://example.com/\"));\n}\n\nTEST(ParseURL, PortOverflow) {\n  std::string_view url(\"http://foo:12345\");\n  url.remove_suffix(4);\n  auto u = ParseURL::parseURL(url, true);\n  EXPECT_EQ(u->port(), 1);\n}\n\nTEST(ParseURL, GetQueryParam) {\n  auto u = ParseURL::parseURL(\"localhost/?foo=1&bar=2&baz&bazz=3&bak=\");\n  ASSERT_TRUE(u.has_value());\n  auto q = u->query();\n  EXPECT_EQ(ParseURL::getQueryParam(q, \"foo\"), \"1\");\n  EXPECT_EQ(ParseURL::getQueryParam(q, \"bar\"), \"2\");\n  EXPECT_EQ(ParseURL::getQueryParam(q, \"baz\"), \"\");\n  EXPECT_EQ(ParseURL::getQueryParam(q, \"bazz\"), \"3\");\n  EXPECT_EQ(ParseURL::getQueryParam(q, \"bak\"), \"\");\n  EXPECT_FALSE(ParseURL::getQueryParam(q, \"fooo\").has_value());\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/PerfectIndexMapBenchmark.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <algorithm>\n#include <folly/Benchmark.h>\n#include <folly/container/F14Map.h>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <proxygen/lib/utils/PerfectIndexMap.h>\n#include <unordered_map>\n\nusing namespace proxygen;\n\n// buck build @mode/opt proxygen/lib/utils/test:perfect_index_map_benchmark\n// ./buck-out/gen/proxygen/lib/utils/test/perfect_index_map_benchmark\n// -bm_min_iters 1000\n// ============================================================================\n// proxygen/lib/utils/test/PerfectIndexMapBenchmark.cpprelative  time/iter\n// iters/s\n// ============================================================================\n// F14UniqueInserts                                             4.95us  202.03K\n// F14UniqueGets                                                4.79us  208.62K\n// UnorderedMapUniqueInserts                                    6.45us  154.97K\n// UnorderedMapUniqueGets                                       7.25us  138.01K\n// PerfectIndexMapUniqueInsertsCode                             2.59us  386.13K\n// PerfectIndexMapUniqueInsertsHashCodeString                   6.45us  155.10K\n// PerfectIndexMapUniqueInsertsHashOtherString                 29.62us   33.76K\n// PerfectIndexMapUniqueGetsCode                                3.56us  281.26K\n// PerfectIndexMapUniqueGetsCodeString                          7.77us  128.65K\n// PerfectIndexMapUniqueGetsOtherString                        32.05us   31.20K\n// ============================================================================\n\nnamespace {\n\nstd::vector<HTTPHeaderCode> getTestHeaderCodes() {\n  std::vector<HTTPHeaderCode> testHeaderCodes;\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    testHeaderCodes.push_back(static_cast<HTTPHeaderCode>(j));\n  }\n  return testHeaderCodes;\n}\n\nstd::vector<const std::string*> getTestHeaderCodeStrings() {\n  std::vector<const std::string*> testHeadersCodeStrings;\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    testHeadersCodeStrings.push_back(\n        HTTPCommonHeaders::getPointerToName(static_cast<HTTPHeaderCode>(j)));\n  }\n  return testHeadersCodeStrings;\n}\n\nstd::vector<const std::string*> getTestHeaderOtherStrings() {\n  std::vector<const std::string*> testHeadersOtherStrings;\n  for (uint64_t j = HTTPHeaderCodeCommonOffset;\n       j < HTTPCommonHeaders::num_codes;\n       ++j) {\n    testHeadersOtherStrings.push_back(new std::string(\n        *HTTPCommonHeaders::getPointerToName(static_cast<HTTPHeaderCode>(j)) +\n        \"0\"));\n  }\n  return testHeadersOtherStrings;\n}\n\nstatic const std::vector<HTTPHeaderCode> testHeaderCodes = getTestHeaderCodes();\n\nstatic const std::vector<const std::string*> testHeadersCodeStrings =\n    getTestHeaderCodeStrings();\n\nstatic const std::vector<const std::string*> testHeadersOtherStrings =\n    getTestHeaderOtherStrings();\n\nusing DefaultPerfectIndexMap = PerfectIndexMap<HTTPHeaderCode,\n                                               HTTP_HEADER_OTHER,\n                                               HTTP_HEADER_NONE,\n                                               HTTPCommonHeaders::hash,\n                                               false,\n                                               false>;\n\n} // namespace\n\nvoid UnorderedMapInsertBench(\n    std::unordered_map<std::string, std::string>& testMap,\n    const std::vector<const std::string*>& keysAndValues,\n    int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& keyAndValue : keysAndValues) {\n      // Modeled after old impl of varstore\n      testMap[*keyAndValue] = *keyAndValue;\n    }\n  }\n}\nvoid F14InsertBench(folly::F14FastMap<std::string, std::string>& testMap,\n                    const std::vector<const std::string*>& keysAndValues,\n                    int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& keyAndValue : keysAndValues) {\n      // Modeled after old impl of varstore\n      testMap[*keyAndValue] = *keyAndValue;\n    }\n  }\n}\n\nvoid F14GetBench(folly::F14FastMap<std::string, std::string>& testMap,\n                 const std::vector<const std::string*>& keys,\n                 int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& key : keys) {\n      // Modeled after old impl of varstore\n      auto it = testMap.find(*key);\n      folly::Optional<std::string> result =\n          (it == testMap.end() ? folly::none\n                               : (folly::Optional<std::string>)it->second);\n      CHECK(result != folly::none);\n    }\n  }\n}\n\nvoid PerfectIndexMapInsertCodeBench(\n    DefaultPerfectIndexMap& map,\n    const std::vector<HTTPHeaderCode>& keys,\n    const std::vector<const std::string*>& values,\n    int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (unsigned long j = 0; j < keys.size(); ++j) {\n      map.set(keys[j], *values[j]);\n    }\n  }\n}\n\nvoid PerfectIndexMapInsertHashBench(\n    DefaultPerfectIndexMap& map,\n    const std::vector<const std::string*>& keysAndValues,\n    int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& keyAndValue : keysAndValues) {\n      map.set(*keyAndValue, *keyAndValue);\n    }\n  }\n}\n\nvoid UnorderedMapGetBench(std::unordered_map<std::string, std::string>& testMap,\n                          const std::vector<const std::string*>& keys,\n                          int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& key : keys) {\n      // Modeled after old impl of varstore\n      auto it = testMap.find(*key);\n      folly::Optional<std::string> result =\n          (it == testMap.end() ? folly::none\n                               : (folly::Optional<std::string>)it->second);\n      CHECK(result != folly::none);\n    }\n  }\n}\n\nvoid PerfectIndexMapGetCodeBench(DefaultPerfectIndexMap& map,\n                                 const std::vector<HTTPHeaderCode>& keys,\n                                 int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& key : keys) {\n      CHECK(map.getSingleOrNone(key) != folly::none);\n    }\n  }\n}\n\nvoid PerfectIndexMapGetStringBench(DefaultPerfectIndexMap& map,\n                                   const std::vector<const std::string*>& keys,\n                                   int iters) {\n  for (int i = 0; i < iters; ++i) {\n    for (auto const& key : keys) {\n      CHECK(map.getSingleOrNone(*key) != folly::none);\n    }\n  }\n}\n\nfolly::F14FastMap<std::string, std::string> bF14UniqueInsertsMap;\nBENCHMARK(F14UniqueInserts, iters) {\n  F14InsertBench(bF14UniqueInsertsMap, testHeadersCodeStrings, iters);\n}\n\nfolly::F14FastMap<std::string, std::string> getBenchF14UniqueGetsTestMap() {\n  folly::F14FastMap<std::string, std::string> testMap;\n  F14InsertBench(testMap, testHeadersCodeStrings, 1);\n  return testMap;\n}\n\nfolly::F14FastMap<std::string, std::string> bF14UniqueGetsMap =\n    getBenchF14UniqueGetsTestMap();\nBENCHMARK(F14UniqueGets, iters) {\n  F14GetBench(bF14UniqueGetsMap, testHeadersCodeStrings, iters);\n}\n\nstd::unordered_map<std::string, std::string> bUnorderedMapUniqueInsertsMap;\nBENCHMARK(UnorderedMapUniqueInserts, iters) {\n  UnorderedMapInsertBench(\n      bUnorderedMapUniqueInsertsMap, testHeadersCodeStrings, iters);\n}\n\nstd::unordered_map<std::string, std::string>\ngetBenchUnorderedMapUniqueGetsTestMap() {\n  std::unordered_map<std::string, std::string> testMap;\n  UnorderedMapInsertBench(testMap, testHeadersCodeStrings, 1);\n  return testMap;\n}\n\nstd::unordered_map<std::string, std::string> bUnorderedMapUniqueGetsMap =\n    getBenchUnorderedMapUniqueGetsTestMap();\nBENCHMARK(UnorderedMapUniqueGets, iters) {\n  UnorderedMapGetBench(\n      bUnorderedMapUniqueGetsMap, testHeadersCodeStrings, iters);\n}\n\nDefaultPerfectIndexMap bPerfectIndexMapUniqueInsertsCodeMap;\nBENCHMARK(PerfectIndexMapUniqueInsertsCode, iters) {\n  PerfectIndexMapInsertCodeBench(bPerfectIndexMapUniqueInsertsCodeMap,\n                                 testHeaderCodes,\n                                 testHeadersCodeStrings,\n                                 iters);\n}\n\nDefaultPerfectIndexMap bPerfectIndexMapUniqueInsertsHashCodeStringTestMap;\nBENCHMARK(PerfectIndexMapUniqueInsertsHashCodeString, iters) {\n  PerfectIndexMapInsertHashBench(\n      bPerfectIndexMapUniqueInsertsHashCodeStringTestMap,\n      testHeadersCodeStrings,\n      iters);\n}\n\nDefaultPerfectIndexMap bPerfectIndexMapUniqueInsertsHashOtherStringTestMap;\nBENCHMARK(PerfectIndexMapUniqueInsertsHashOtherString, iters) {\n  PerfectIndexMapInsertHashBench(\n      bPerfectIndexMapUniqueInsertsHashOtherStringTestMap,\n      testHeadersOtherStrings,\n      iters);\n}\n\nDefaultPerfectIndexMap getBenchPerfectIndexMapUniqueGetsCodeTestMap() {\n  DefaultPerfectIndexMap testMap;\n  PerfectIndexMapInsertCodeBench(\n      testMap, testHeaderCodes, testHeadersCodeStrings, 1);\n  return testMap;\n}\nDefaultPerfectIndexMap bPerfectIndexMapUniqueGetsCodeMap =\n    getBenchPerfectIndexMapUniqueGetsCodeTestMap();\nBENCHMARK(PerfectIndexMapUniqueGetsCode, iters) {\n  PerfectIndexMapGetCodeBench(\n      bPerfectIndexMapUniqueGetsCodeMap, testHeaderCodes, iters);\n}\n\nDefaultPerfectIndexMap bPerfectIndexMapUniqueGetsCodeStringMap =\n    getBenchPerfectIndexMapUniqueGetsCodeTestMap();\nBENCHMARK(PerfectIndexMapUniqueGetsCodeString, iters) {\n  PerfectIndexMapGetStringBench(\n      bPerfectIndexMapUniqueGetsCodeStringMap, testHeadersCodeStrings, iters);\n}\n\nDefaultPerfectIndexMap getBenchPerfectIndexMapUniqueGetsOtherStringTestMap() {\n  DefaultPerfectIndexMap testMap;\n  PerfectIndexMapInsertHashBench(testMap, testHeadersOtherStrings, 1);\n  return testMap;\n}\nDefaultPerfectIndexMap bPerfectIndexMapUniqueGetsOtherStringMap =\n    getBenchPerfectIndexMapUniqueGetsOtherStringTestMap();\nBENCHMARK(PerfectIndexMapUniqueGetsOtherString, iters) {\n  PerfectIndexMapGetStringBench(\n      bPerfectIndexMapUniqueGetsOtherStringMap, testHeadersOtherStrings, iters);\n}\n\nint main(int argc, char** argv) {\n  gflags::ParseCommandLineFlags(&argc, &argv, true);\n  folly::runBenchmarks();\n\n  // Not explicitly required but lets free memory we specifically allocated.\n  for (auto* testHeaderOtherString : testHeadersOtherStrings) {\n    delete testHeaderOtherString;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/PerfectIndexMapTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/PerfectIndexMap.h>\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/http/HTTPCommonHeaders.h>\n#include <string>\n\nusing namespace folly;\nusing namespace proxygen;\n\n// This struct exists to WAR the gtest limitation that we can only pass a\n// single template parameter to our test fixture.  Thus in this case our single\n// test parameter contains the type and value information required to\n// instantiate the map under test correctly.\ntemplate <typename Key,\n          Key OtherKey,\n          Key NoneKey,\n          Key (*PerfectHashStrToKey)(const std::string&),\n          bool AllowDuplicates,\n          bool CaseInsensitive,\n          uint8_t KeyCommonOffset,\n          uint64_t NumKeys>\nstruct PerfectIndexMapTestsTemplateParams {\n  typedef Key TKey;\n  static const Key TOtherKey = OtherKey;\n  static const Key TNoneKey = NoneKey;\n  static const bool TAllowDuplicates = AllowDuplicates;\n  static const bool TCaseInsensitive = CaseInsensitive;\n\n  // Pass through wrapper for the hashing method.\n  // This method only exists because am unsure how to properly capture it and\n  // expose it as a subsequent template parameter for consumers.\n  static Key Hash(const std::string& name) {\n    return PerfectHashStrToKey(name);\n  }\n\n  static const uint8_t TKeyCommonOffset = KeyCommonOffset;\n  static const uint64_t TNumKeys = NumKeys;\n};\n\n// Wrapper test class allowing us to create the desired PerfectIndexMap via\n// the specified template parameter\ntemplate <class T>\nclass PerfectIndexMapTests : public testing::Test {\n protected:\n  PerfectIndexMap<typename T::TKey,\n                  T::TOtherKey,\n                  T::TNoneKey,\n                  T::Hash,\n                  T::TAllowDuplicates,\n                  T::TCaseInsensitive>\n      testMap_;\n};\n\n// Register the template configurations we wish to automatically test\ntypedef testing::Types<\n    PerfectIndexMapTestsTemplateParams<HTTPHeaderCode,\n                                       HTTP_HEADER_OTHER,\n                                       HTTP_HEADER_NONE,\n                                       HTTPCommonHeaders::hash,\n                                       false,\n                                       true,\n                                       HTTPHeaderCodeCommonOffset,\n                                       HTTPCommonHeaders::num_codes>,\n    PerfectIndexMapTestsTemplateParams<HTTPHeaderCode,\n                                       HTTP_HEADER_OTHER,\n                                       HTTP_HEADER_NONE,\n                                       HTTPCommonHeaders::hash,\n                                       true,\n                                       true,\n                                       HTTPHeaderCodeCommonOffset,\n                                       HTTPCommonHeaders::num_codes>,\n    PerfectIndexMapTestsTemplateParams<HTTPHeaderCode,\n                                       HTTP_HEADER_OTHER,\n                                       HTTP_HEADER_NONE,\n                                       HTTPCommonHeaders::hash,\n                                       true,\n                                       false,\n                                       HTTPHeaderCodeCommonOffset,\n                                       HTTPCommonHeaders::num_codes>,\n    PerfectIndexMapTestsTemplateParams<HTTPHeaderCode,\n                                       HTTP_HEADER_OTHER,\n                                       HTTP_HEADER_NONE,\n                                       HTTPCommonHeaders::hash,\n                                       false,\n                                       false,\n                                       HTTPHeaderCodeCommonOffset,\n                                       HTTPCommonHeaders::num_codes>>\n    TestTypes;\nTYPED_TEST_SUITE(PerfectIndexMapTests, TestTypes);\n\nTYPED_TEST(PerfectIndexMapTests, BasicKeySetAddRemoveGetSingleOrNone) {\n  typedef typename TypeParam::TKey Key;\n\n  EXPECT_EQ(this->testMap_.size(), 0);\n\n  // Insert numInserted distinct keys and duplicate values into the map.\n  auto numInserted = TypeParam::TNumKeys - TypeParam::TKeyCommonOffset;\n  for (uint64_t j = TypeParam::TKeyCommonOffset; j < TypeParam::TNumKeys; ++j) {\n    this->testMap_.set(static_cast<Key>(j), std::to_string(j));\n  }\n  EXPECT_EQ(this->testMap_.size(), numInserted);\n\n  // Setting a duplicate should not increase the size of the map, regardless\n  // of whether duplicates are supported\n  Key key = static_cast<Key>(TypeParam::TKeyCommonOffset);\n  this->testMap_.set(key, std::to_string(TypeParam::TKeyCommonOffset));\n  EXPECT_EQ(this->testMap_.size(), numInserted);\n\n  // Adding is only allowed when duplicates are and so here we expect the size\n  // of the map to change.\n  if (TypeParam::TAllowDuplicates) {\n    this->testMap_.add(key, std::to_string(TypeParam::TKeyCommonOffset));\n    EXPECT_EQ(this->testMap_.size(), numInserted + 1);\n  }\n\n  // Remove the last added element in the map (and its duplicate if applicable)\n  // Adjusts numInserted as appropriate\n  this->testMap_.remove(key);\n  EXPECT_EQ(this->testMap_.size(), --numInserted);\n\n  // Verify the integrity of the map\n  for (uint64_t j = TypeParam::TKeyCommonOffset + 1; j < TypeParam::TNumKeys;\n       ++j) {\n    key = static_cast<Key>(j);\n    auto optional = this->testMap_.getSingleOrNone(key);\n    ASSERT_TRUE(optional.hasValue());\n    ASSERT_EQ(optional.value(), std::to_string(j));\n  }\n}\n\nTYPED_TEST(PerfectIndexMapTests, BasicOtherKeySetAddRemoveGetSingleOrNone) {\n  EXPECT_EQ(this->testMap_.size(), 0);\n\n  // Insert numInserted distinct keys and values in to the map\n  int numInserted = 10;\n  std::string val;\n  for (int num = 0; num < numInserted; ++num) {\n    val = std::to_string(num);\n    this->testMap_.set(val, val);\n  }\n  EXPECT_EQ(this->testMap_.size(), numInserted);\n\n  // Setting a duplicate should not increase the size of the map, regardless\n  // of whether duplicates are supported\n  this->testMap_.set(val, val);\n  EXPECT_EQ(this->testMap_.size(), numInserted);\n\n  // Adding is only allowed when duplicates are and so here we expect the size\n  // of the map to change.\n  if (TypeParam::TAllowDuplicates) {\n    this->testMap_.add(val, val);\n    EXPECT_EQ(this->testMap_.size(), numInserted + 1);\n  }\n\n  // Remove the last added element in the map (and its duplicate if applicable)\n  // Adjusts numInserted as appropriate\n  this->testMap_.remove(val);\n  EXPECT_EQ(this->testMap_.size(), --numInserted);\n\n  // Verify the integrity of the map\n  for (int num = 0; num < numInserted; ++num) {\n    val = std::to_string(num);\n    auto optional = this->testMap_.getSingleOrNone(val);\n    ASSERT_TRUE(optional.hasValue());\n    ASSERT_EQ(optional.value(), val);\n  }\n}\n\n// Note there is no corresponding key string case sensitivity test as that is\n// controlled by the specified hashing function of the map.  The user in\n// creating the map thus chooses whether the hashing function should be case\n// sensitive or not and thus does not require explicit testing as part of this\n// class\nTYPED_TEST(PerfectIndexMapTests, OtherStringCaseSensitivity) {\n  std::string testString = \"test\";\n  std::string modTestString = \"tEsT\";\n  std::string addModTestString = \"TeSt\";\n  this->testMap_.set(testString, testString);\n\n  auto currentCount = this->testMap_.size();\n  folly::Optional<std::string> optional;\n  if (TypeParam::TCaseInsensitive) {\n    optional = this->testMap_.getSingleOrNone(modTestString);\n    ASSERT_TRUE(optional.has_value());\n    ASSERT_EQ(optional.value(), testString);\n\n    this->testMap_.set(modTestString, modTestString);\n    EXPECT_EQ(this->testMap_.size(), currentCount);\n\n    optional = this->testMap_.getSingleOrNone(testString);\n    ASSERT_TRUE(optional.has_value());\n    ASSERT_EQ(optional.value(), modTestString);\n  } else {\n    this->testMap_.set(modTestString, modTestString);\n    EXPECT_EQ(this->testMap_.size(), ++currentCount);\n\n    this->testMap_.set(testString, testString);\n    EXPECT_EQ(this->testMap_.size(), currentCount);\n\n    optional = this->testMap_.getSingleOrNone(testString);\n    ASSERT_TRUE(optional.has_value());\n    ASSERT_EQ(optional.value(), testString);\n\n    optional = this->testMap_.getSingleOrNone(modTestString);\n    ASSERT_TRUE(optional.has_value());\n    ASSERT_EQ(optional.value(), modTestString);\n  }\n\n  if (TypeParam::TAllowDuplicates) {\n    // Finally verify that two exact same keys are treated as duplicates.\n    this->testMap_.add(addModTestString, addModTestString);\n    optional = this->testMap_.getSingleOrNone(testString);\n    if (TypeParam::TCaseInsensitive) {\n      EXPECT_FALSE(optional.has_value());\n    } else {\n      EXPECT_TRUE(optional.has_value());\n    }\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/RendezvousHashTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Conv.h>\n#include <folly/container/Foreach.h>\n#include <folly/portability/GTest.h>\n#include <map>\n#include <vector>\n\n#include <proxygen/lib/utils/RendezvousHash.h>\n\nusing namespace proxygen;\n\nTEST(RendezvousHash, Consistency) {\n  RendezvousHash hashes;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < 10; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 1);\n  }\n  hashes.build(nodes);\n\n  for (size_t rank = 0; rank < nodes.size() + 2; rank++) {\n    std::map<uint64_t, size_t> mapping;\n    for (int i = 0; i < 10000; ++i) {\n      mapping[i] = hashes.get(i, rank);\n    }\n\n    for (auto&& [key, expected] : mapping) {\n      EXPECT_EQ(expected, hashes.get(key, rank));\n    }\n  }\n}\n\nTEST(RendezvousHash, ConsistencyWithNewNode) {\n  RendezvousHash hashes;\n  int numNodes = 10;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 1);\n  }\n  hashes.build(nodes);\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n  hashes = RendezvousHash();\n  // Adding a new node and rebuild the hash\n  nodes.emplace_back(folly::to<std::string>(\"key\", numNodes), 1);\n  hashes.build(nodes);\n  // traffic should only flow to the new node\n  for (auto&& [key, expected] : mapping) {\n    size_t id = hashes.get(key);\n    EXPECT_TRUE(expected == id || numNodes == int(id));\n  }\n}\n\nTEST(RendezvousHash, ConsistencyWithIncreasedWeight) {\n  RendezvousHash hashes;\n  int numNodes = 10;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n  hashes.build(nodes);\n\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n\n  // Increase the weight by 2\n  nodes.clear();\n  hashes = RendezvousHash();\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i * 2);\n  }\n  hashes.build(nodes);\n\n  // traffic shouldn't flow at all\n  for (auto&& [key, expected] : mapping) {\n    EXPECT_EQ(expected, hashes.get(key));\n  }\n}\n\nTEST(RendezvousHash, ConsistentFlowToIncreasedWeightNode) {\n  RendezvousHash hashes;\n  int numNodes = 10;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n  hashes.build(nodes);\n\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n\n  nodes.clear();\n  // Increase the weight for a single node\n  hashes = RendezvousHash();\n\n  nodes.emplace_back(folly::to<std::string>(\"key\", 0), 10);\n\n  for (int i = 1; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n  hashes.build(nodes);\n  // traffic should only flow to the first node\n  for (auto&& [key, expected] : mapping) {\n    size_t id = hashes.get(key);\n    EXPECT_TRUE(expected == id || 0 == int(id));\n  }\n}\n\nTEST(RendezvousHash, ConsistentFlowToDecreasedWeightNodes) {\n  RendezvousHash hashes;\n  int numNodes = 18;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 100);\n  }\n  hashes.build(nodes);\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n\n  nodes.clear();\n  hashes = RendezvousHash();\n\n  // decrease the weights for 5 nodes\n  for (int i = 0; i < 5; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 50);\n  }\n\n  // keep the weights for the rest unchanged\n  for (int i = 5; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 100);\n  }\n\n  hashes.build(nodes);\n  for (auto&& [key, expected] : mapping) {\n    // traffic should only flow to nodes with decreased nodes\n    size_t id = hashes.get(key);\n    EXPECT_TRUE(expected == id || id >= 5);\n  }\n}\n\nTEST(RendezvousHash, ConsistentFlowToDecreasedWeightNode) {\n  RendezvousHash hashes;\n  int numNodes = 10;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n  hashes.build(nodes);\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n\n  // Increase the weight for a single node\n  nodes.clear();\n  hashes = RendezvousHash();\n\n  for (int i = 0; i < numNodes - 1; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n\n  // zero the weight of the last node\n  nodes.emplace_back(folly::to<std::string>(\"key\", numNodes - 1), 0);\n  hashes.build(nodes);\n  for (auto&& [key, expected] : mapping) {\n    // traffic should only flow from the zero weight cluster to others\n    size_t id = hashes.get(key);\n    if (expected == (uint64_t)numNodes - 1) {\n      EXPECT_TRUE(expected != id);\n    } else {\n      EXPECT_TRUE(expected == id);\n    }\n  }\n}\n\nTEST(RendezvousHash, ConsistencyWithDecreasedWeight) {\n  RendezvousHash hashes;\n  int numNodes = 10;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i * 2);\n  }\n  hashes.build(nodes);\n  std::map<uint64_t, size_t> mapping;\n  for (uint64_t i = 0; i < 10000; ++i) {\n    mapping[i] = hashes.get(i);\n  }\n\n  // Decrease the weight by 2\n  nodes.clear();\n  hashes = RendezvousHash();\n\n  for (int i = 0; i < numNodes; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), i);\n  }\n  hashes.build(nodes);\n\n  // traffic shouldn't flow at all\n  for (auto&& [key, expected] : mapping) {\n    EXPECT_EQ(expected, hashes.get(key));\n  }\n}\n\nTEST(ConsistentHashRing, DistributionAccuracy) {\n  std::vector<std::string> keys = {\n      \"ash_proxy\", \"prn_proxy\", \"snc_proxy\", \"frc_proxy\"};\n\n  std::vector<std::vector<uint64_t>> weights = {\n      {248, 342, 2, 384},\n      {10, 10, 10, 10},\n      {25, 25, 10, 10},\n      {100, 10, 10, 1},\n      {100, 5, 5, 5},\n      {922337203685, 12395828300, 50192385101, 59293845010}};\n\n  for (auto& weight : weights) {\n    RendezvousHash hashes;\n    std::vector<std::pair<std::string, uint64_t>> nodes;\n    FOR_EACH_RANGE(i, 0, keys.size()) {\n      nodes.emplace_back(keys[i], weight[i]);\n    }\n    hashes.build(nodes);\n\n    std::vector<uint64_t> distribution(keys.size());\n\n    for (uint64_t i = 0; i < 21000; ++i) {\n      distribution[hashes.get(i)]++;\n    }\n\n    uint64_t totalWeight = 0;\n\n    for (auto& w : weight) {\n      totalWeight += w;\n    }\n\n    double maxError = 0.0;\n    for (size_t i = 0; i < keys.size(); ++i) {\n      double expected = 100.0 * weight[i] / totalWeight;\n      double actual = 100.0 * distribution[i] / 21000;\n\n      maxError = std::max(maxError, fabs(expected - actual));\n    }\n    // make sure the error rate is less than 1.0%\n    EXPECT_LE(maxError, 1.0);\n  }\n}\n\nTEST(RendezvousHash, selectNUnweighted) {\n  RendezvousHash hashes;\n  std::vector<std::pair<std::string, uint64_t>> nodes;\n  int size = 100;\n  for (int i = 0; i < size; ++i) {\n    nodes.emplace_back(folly::to<std::string>(\"key\", i), 1);\n  }\n  hashes.build(nodes);\n  auto seed = 91484253;\n\n  // rank > size\n  auto select = hashes.selectNUnweighted(seed, size + 10);\n  EXPECT_EQ(select.size(), size);\n  for (int i = 0; i < size; i++) {\n    EXPECT_EQ(select[i], i);\n  }\n\n  // check valid index in selection\n  int rank = size / 4;\n  select = hashes.selectNUnweighted(seed, rank);\n  EXPECT_EQ(select.size(), rank);\n  std::set<size_t> uniqueIndex;\n  for (auto index : select) {\n    EXPECT_EQ(uniqueIndex.count(index), 0);\n    uniqueIndex.insert(index);\n    EXPECT_LT(index, size);\n  }\n\n  // change seed, check different selection\n  for (int i = 1; i < 100; i++) {\n    select = hashes.selectNUnweighted(seed + i, rank);\n    int different = 0;\n    for (auto index : select) {\n      if (uniqueIndex.count(index) == 0) {\n        different++;\n      }\n    }\n    EXPECT_GT(different, 0);\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/TimeTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/Time.h>\n#include <proxygen/lib/utils/test/MockTime.h>\n\n#include <folly/String.h>\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nTEST(TimeTest, GetDateTimeStr) {\n  ASSERT_FALSE(getDateTimeStr(getCurrentTime()).empty());\n\n  SystemClock::time_point sys_tp{}; // epoch timepoint\n  SteadyClock::time_point tp =\n      SteadyClock::now() + std::chrono::duration_cast<SteadyClock::duration>(\n                               sys_tp - SystemClock::now());\n  std::string time;\n  std::string timeZone;\n  folly::split(' ', getDateTimeStr(tp), time, timezone);\n  // getDateTimeStr return value includes the local timezone:\n  // e.g 1970-01-01T00:00:00 +0000\n  // Do not compare the timezone\n  ASSERT_EQ(\"1970-01-01T00:00:00\", time);\n}\n\nTEST(StopWatchTest, StartStopReset) {\n  auto mockTime = std::make_shared<MockTimeUtil>();\n  StopWatch<std::chrono::microseconds> stopWatch(mockTime);\n\n  stopWatch.start();\n  mockTime->advance(std::chrono::milliseconds(1));\n  stopWatch.stop();\n\n  EXPECT_EQ(stopWatch.getElapsedTime().count(),\n            std::chrono::microseconds(1000).count());\n\n  stopWatch.reset();\n  EXPECT_EQ(stopWatch.getElapsedTime().count(),\n            std::chrono::microseconds(0).count());\n}\n\nTEST(StopWatchTest, StartTwiceReset) {\n  auto mockTime = std::make_shared<MockTimeUtil>();\n  StopWatch<std::chrono::microseconds> stopWatch(mockTime);\n\n  stopWatch.start();\n  mockTime->advance(std::chrono::milliseconds(1));\n  stopWatch.start();\n  stopWatch.stop();\n\n  EXPECT_EQ(stopWatch.getElapsedTime().count(),\n            std::chrono::microseconds(0).count());\n}\n\nTEST(StopWatchTest, ContinueWithoutReset) {\n  auto mockTime = std::make_shared<MockTimeUtil>();\n  StopWatch<std::chrono::microseconds> stopWatch(mockTime);\n\n  stopWatch.start();\n  mockTime->advance(std::chrono::milliseconds(1));\n  stopWatch.stop();\n\n  mockTime->advance(std::chrono::milliseconds(1));\n\n  stopWatch.start();\n  mockTime->advance(std::chrono::milliseconds(1));\n  stopWatch.stop();\n\n  EXPECT_EQ(stopWatch.getElapsedTime().count(),\n            std::chrono::microseconds(2000).count());\n}\n\nTEST(StopWatchTest, StopWatchTimedScope) {\n  auto mockTime = std::make_shared<MockTimeUtil>();\n  StopWatch<std::chrono::microseconds> stopWatch(mockTime);\n\n  {\n    auto timedScope = stopWatch.createTimedScope();\n    mockTime->advance(std::chrono::milliseconds(1));\n  }\n\n  EXPECT_EQ(stopWatch.getElapsedTime().count(),\n            std::chrono::microseconds(1000).count());\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/TraceEventTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/TraceEvent.h>\n\n#include <proxygen/lib/utils/Exception.h>\n#include <proxygen/lib/utils/TraceEventType.h>\n#include <proxygen/lib/utils/TraceFieldType.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\n#include <string>\n#include <vector>\n\nusing namespace proxygen;\n\nTEST(TraceEventTest, IntegralDataIntegralValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  int64_t data(13);\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_EQ(data,\n            traceEvent.getTraceFieldDataAs<int64_t>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, IntegralDataStringValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  int64_t intData(13);\n  traceEvent.addMeta(TraceFieldType::Protocol, intData);\n\n  std::string strData(std::to_string(intData));\n\n  ASSERT_EQ(\n      strData,\n      traceEvent.getTraceFieldDataAs<std::string>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, IntegralDataVectorValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  int64_t data(13);\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_THROW(traceEvent.getTraceFieldDataAs<std::vector<std::string>>(\n                   TraceFieldType::Protocol),\n               Exception);\n}\n\nTEST(TraceEventTest, StringDataIntegralValueConvertible) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  int64_t intData(13);\n  std::string strData(std::to_string(intData));\n  traceEvent.addMeta(TraceFieldType::Protocol, strData);\n\n  ASSERT_EQ(intData,\n            traceEvent.getTraceFieldDataAs<int64_t>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, StringDataIntegralValueNonConvertible) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::string data(\"Abc\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_ANY_THROW(\n      traceEvent.getTraceFieldDataAs<int64_t>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, StringDataStringValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::string data(\"Abc\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_EQ(\n      data,\n      traceEvent.getTraceFieldDataAs<std::string>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, StringDataVectorValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::string data(\"Abc\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_THROW(traceEvent.getTraceFieldDataAs<std::vector<std::string>>(\n                   TraceFieldType::Protocol),\n               Exception);\n}\n\nTEST(TraceEventTest, VectorDataIntegralValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::vector<std::string> data;\n  data.emplace_back(\"Abc\");\n  data.emplace_back(\"Hij\");\n  data.emplace_back(\"Xyz\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_THROW(\n      traceEvent.getTraceFieldDataAs<int64_t>(TraceFieldType::Protocol),\n      Exception);\n}\n\nTEST(TraceEventTest, VectorDataStringValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::vector<std::string> data;\n  data.emplace_back(\"A\");\n  data.emplace_back(\"B\");\n  data.emplace_back(\"C\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  ASSERT_EQ(\n      \"[\\\"A\\\",\\\"B\\\",\\\"C\\\"]\",\n      traceEvent.getTraceFieldDataAs<std::string>(TraceFieldType::Protocol));\n}\n\nTEST(TraceEventTest, VectorDataVectorValue) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n\n  std::vector<std::string> data;\n  data.emplace_back(\"A\");\n  data.emplace_back(\"B\");\n  data.emplace_back(\"C\");\n  traceEvent.addMeta(TraceFieldType::Protocol, data);\n\n  auto extractedData(traceEvent.getTraceFieldDataAs<std::vector<std::string>>(\n      TraceFieldType::Protocol));\n\n  EXPECT_THAT(extractedData, testing::ContainerEq(data));\n}\n\nTEST(TraceEventTest, IteratorValueTypeCheckInteger) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n  int64_t intData(13);\n  traceEvent.addMeta(TraceFieldType::Protocol, intData);\n\n  auto itr = traceEvent.getMetaDataItr();\n  ASSERT_TRUE(itr.isValid());\n  ASSERT_EQ(TraceFieldType::Protocol, itr.getKey());\n  ASSERT_EQ(typeid(int64_t), itr.type());\n\n  itr.next();\n  ASSERT_FALSE(itr.isValid());\n}\n\nTEST(TraceEventTest, IteratorValueTypeCheckString) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n  std::string strData(\"abc\");\n  traceEvent.addMeta(TraceFieldType::Protocol, strData);\n\n  auto itr = traceEvent.getMetaDataItr();\n  ASSERT_TRUE(itr.isValid());\n  ASSERT_EQ(TraceFieldType::Protocol, itr.getKey());\n  ASSERT_EQ(typeid(std::string), itr.type());\n\n  itr.next();\n  ASSERT_FALSE(itr.isValid());\n}\n\nTEST(TraceEventTest, IteratorValueTypeCheckStringArray) {\n  TraceEvent traceEvent((TraceEventType::TotalRequest));\n  std::vector<std::string> arrData;\n  arrData.emplace_back(\"A\");\n  arrData.emplace_back(\"B\");\n  arrData.emplace_back(\"C\");\n  traceEvent.addMeta(TraceFieldType::Protocol, arrData);\n\n  auto itr = traceEvent.getMetaDataItr();\n  ASSERT_TRUE(itr.isValid());\n  ASSERT_EQ(TraceFieldType::Protocol, itr.getKey());\n  ASSERT_EQ(typeid(std::vector<std::string>), itr.type());\n\n  itr.next();\n  ASSERT_FALSE(itr.isValid());\n}\n\n// To\nTEST(TraceEventTest, IntegerValueToString) {\n  TraceEvent traceEvent(TraceEventType::TotalRequest, 1);\n  traceEvent.start(TimePoint(std::chrono::milliseconds(100)));\n  traceEvent.end(TimePoint(std::chrono::milliseconds(200)));\n  int64_t intData(13);\n  traceEvent.addMeta(TraceFieldType::Protocol, intData);\n\n  std::ostringstream out;\n  out << \"TraceEvent(\";\n  out << \"type='TotalRequest', \";\n  out << \"id='\" << traceEvent.getID() << \"', \";\n  out << \"parentID='1', \";\n  out << \"start='100', \";\n  out << \"end='200', \";\n  out << \"metaData='{protocol: 13, }')\";\n\n  ASSERT_EQ(out.str(), traceEvent.toString());\n}\n\nTEST(TraceEventTest, StringValueToString) {\n  TraceEvent traceEvent(TraceEventType::TotalRequest, 1);\n  traceEvent.start(TimePoint(std::chrono::milliseconds(100)));\n  traceEvent.end(TimePoint(std::chrono::milliseconds(200)));\n  std::string strData(\"abc\");\n  traceEvent.addMeta(TraceFieldType::Protocol, strData);\n\n  std::ostringstream out;\n  out << \"TraceEvent(\";\n  out << \"type='TotalRequest', \";\n  out << \"id='\" << traceEvent.getID() << \"', \";\n  out << \"parentID='1', \";\n  out << \"start='100', \";\n  out << \"end='200', \";\n  out << \"metaData='{protocol: abc, }')\";\n\n  ASSERT_EQ(out.str(), traceEvent.toString());\n}\n\nTEST(TraceEventTest, StringArrayValueToString) {\n  TraceEvent traceEvent(TraceEventType::TotalRequest, 1);\n  traceEvent.start(TimePoint(std::chrono::milliseconds(100)));\n  traceEvent.end(TimePoint(std::chrono::milliseconds(200)));\n  std::vector<std::string> arrData;\n  arrData.emplace_back(\"A\");\n  arrData.emplace_back(\"B\");\n  arrData.emplace_back(\"C\");\n  traceEvent.addMeta(TraceFieldType::Protocol, arrData);\n\n  std::ostringstream out;\n  out << \"TraceEvent(\";\n  out << \"type='TotalRequest', \";\n  out << \"id='\" << traceEvent.getID() << \"', \";\n  out << \"parentID='1', \";\n  out << \"start='100', \";\n  out << \"end='200', \";\n  out << \"metaData='{protocol: [\\\"A\\\",\\\"B\\\",\\\"C\\\"], }')\";\n\n  ASSERT_EQ(out.str(), traceEvent.toString());\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/URLTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/URL.h>\n\n#include <folly/portability/GTest.h>\n\nusing namespace proxygen;\n\nTEST(URLTest, Root) {\n  URL u(\"http\", \"www.facebook.com\", 0);\n  EXPECT_TRUE(u.isValid());\n  EXPECT_EQ(u.getUrl(), \"http://www.facebook.com/\");\n}\n\nTEST(URLTest, CapitalSheme) {\n  URL u(\"HTTPS\", \"www.facebook.com\", 0);\n  EXPECT_TRUE(u.isValid());\n  EXPECT_EQ(u.getUrl(), \"https://www.facebook.com/\");\n}\n\nTEST(URLTest, Invalid) {\n  URL u1(\"https://www.facebook.com/foo\\xff\", true, URL::Mode::STRICT);\n  EXPECT_FALSE(u1.isValid());\n  EXPECT_EQ(u1.getHost(), \"\");\n  EXPECT_EQ(u1.getPath(), \"\");\n  URL u2(\"https://www.facebook.com/foo\\xff\", true, URL::Mode::STRICT_COMPAT);\n  EXPECT_TRUE(u2.isValid());\n  EXPECT_EQ(u2.getHost(), \"www.facebook.com\");\n  EXPECT_EQ(u2.getPath(), \"/foo\\xff\");\n}\n\nTEST(URLTest, NonHTTPScheme) {\n  URL u1(\"masque://www.facebook.com/foo\", true, URL::Mode::STRICT);\n  // Invalid, but host is still set\n  EXPECT_FALSE(u1.isValid());\n  EXPECT_EQ(u1.getHost(), \"www.facebook.com\");\n  EXPECT_EQ(u1.getPath(), \"/foo\");\n}\n\nTEST(URLTest, GetPort) {\n  URL u1(\"https://www.facebook.com/foo\", true, URL::Mode::STRICT);\n  EXPECT_TRUE(u1.isValid());\n  EXPECT_EQ(u1.getPort(), 443);\n  EXPECT_EQ(u1.getHostAndPortOmitDefault(), \"www.facebook.com\");\n\n  URL u2(\"http://www.facebook.com/foo\", true, URL::Mode::STRICT);\n  EXPECT_TRUE(u2.isValid());\n  EXPECT_EQ(u2.getPort(), 80);\n  EXPECT_EQ(u2.getHostAndPortOmitDefault(), \"www.facebook.com\");\n\n  URL u3(\"http://www.facebook.com:8081/foo\", true, URL::Mode::STRICT);\n  EXPECT_TRUE(u3.isValid());\n  EXPECT_EQ(u3.getPort(), 8081);\n  EXPECT_EQ(u3.getHostAndPortOmitDefault(), \"www.facebook.com:8081\");\n\n  URL u4(\"https://www.facebook.com:8081/foo\", true, URL::Mode::STRICT);\n  EXPECT_TRUE(u4.isValid());\n  EXPECT_EQ(u4.getPort(), 8081);\n  EXPECT_EQ(u4.getHostAndPortOmitDefault(), \"www.facebook.com:8081\");\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/UtilTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/portability/GTest.h>\n#include <proxygen/lib/utils/UtilInl.h>\n\n#include <cctype>\n\nusing namespace proxygen;\n\nTEST(UtilTest, CaseInsensitiveEqual) {\n  ASSERT_TRUE(caseInsensitiveEqual(\"foo\", \"FOO\"));\n  ASSERT_TRUE(caseInsensitiveEqual(std::string(\"foo\"), \"FOO\"));\n  ASSERT_FALSE(caseInsensitiveEqual(std::string(\"foo\"), \"FOO2\"));\n  ASSERT_FALSE(caseInsensitiveEqual(\"fo\", \"FOO\"));\n  ASSERT_FALSE(caseInsensitiveEqual(\"FO\", \"FOO\"));\n}\n\nTEST(UtilTest, findLastOf) {\n  folly::StringPiece p1(\"\");\n  folly::StringPiece p2(\".\");\n  folly::StringPiece p3(\"..\");\n  folly::StringPiece p4(\"abc\");\n  folly::StringPiece p5(\"abc.def\");\n\n  EXPECT_EQ(findLastOf(p1, '.'), std::string::npos);\n  EXPECT_EQ(findLastOf(p2, '.'), 0);\n  EXPECT_EQ(findLastOf(p3, '.'), 1);\n  EXPECT_EQ(findLastOf(p4, '.'), std::string::npos);\n  EXPECT_EQ(findLastOf(p5, '.'), 3);\n}\n\nTEST(UtilTest, validateURL) {\n  EXPECT_TRUE(validateURL(\"/foo\\xff\", URLValidateMode::STRICT_COMPAT));\n  EXPECT_FALSE(validateURL(\"/foo\\xff\", URLValidateMode::STRICT));\n}\n\nTEST(UtilTest, clamped) {\n  EXPECT_EQ(clamped_downcast<uint8_t>(uint64_t(255)),\n            std::numeric_limits<uint8_t>::max());\n  EXPECT_EQ(clamped_downcast<uint8_t>(uint64_t(256)),\n            std::numeric_limits<uint8_t>::max());\n  EXPECT_EQ(clamped_downcast<uint8_t>(std::numeric_limits<uint64_t>::max()),\n            std::numeric_limits<uint8_t>::max());\n\n  EXPECT_EQ(clamped_downcast<uint16_t>(uint64_t(65535)),\n            std::numeric_limits<uint16_t>::max());\n  EXPECT_EQ(clamped_downcast<uint16_t>(uint64_t(65536)),\n            std::numeric_limits<uint16_t>::max());\n  EXPECT_EQ(clamped_downcast<uint16_t>(std::numeric_limits<uint64_t>::max()),\n            std::numeric_limits<uint16_t>::max());\n\n  EXPECT_EQ(clamped_downcast<uint32_t>(\n                uint64_t(std::numeric_limits<uint32_t>::max())),\n            std::numeric_limits<uint32_t>::max());\n  EXPECT_EQ(clamped_downcast<uint32_t>(\n                uint64_t(std::numeric_limits<uint32_t>::max()) + 1),\n            std::numeric_limits<uint32_t>::max());\n  EXPECT_EQ(clamped_downcast<uint32_t>(std::numeric_limits<uint64_t>::max()),\n            std::numeric_limits<uint32_t>::max());\n}\n\nTEST(UtilTest, isAlpha) {\n  // Test is only valid in the \"C\" locale.\n  for (uint16_t c = 0; c <= 255; c++) {\n    EXPECT_EQ(bool(isAlpha(uint8_t(c))), bool(std::isalpha(uint8_t(c))));\n  }\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/WeakRefCountedPtrTest.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <proxygen/lib/utils/WeakRefCountedPtr.h>\n\n#include <folly/portability/GMock.h>\n#include <folly/portability/GTest.h>\n\nusing namespace testing;\n\nnamespace proxygen::test {\n\nclass TestClass : public EnableWeakRefCountedPtr<TestClass> {\n public:\n  MOCK_METHOD(void, onWeakRefCountedPtrCreate, ());\n  MOCK_METHOD(void, onWeakRefCountedPtrDestroy, ());\n};\n\nclass TestDerivedClass : public TestClass {\n public:\n  using TestClass::TestClass;\n  void onlyDerivedFunc() const {\n  }\n};\n\nTEST(WeakRefCountedPtrTest, JustTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n}\n\nTEST(WeakRefCountedPtrTest, DestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr = target->getWeakRefCountedPtr();\n  EXPECT_TRUE(kaPtr);\n  EXPECT_EQ(target.get(), kaPtr.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr);\n  EXPECT_EQ(nullptr, kaPtr.get());\n}\n\nTEST(WeakRefCountedPtrTest, DestroyPtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr = target->getWeakRefCountedPtr();\n    EXPECT_TRUE(kaPtr);\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr = target->getWeakRefCountedPtr();\n  EXPECT_TRUE(kaPtr);\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr.get());\n  EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  kaPtr.reset();\n  EXPECT_FALSE(kaPtr);\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  Mock::VerifyAndClearExpectations(target.get());\n}\n\nTEST(WeakRefCountedPtrTest, JustTargetDerived) {\n  auto target = std::make_unique<StrictMock<TestDerivedClass>>();\n  target->onlyDerivedFunc();\n}\n\nTEST(WeakRefCountedPtrTest, DestroyTargetDerived) {\n  auto target = std::make_unique<StrictMock<TestDerivedClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr = target->getWeakRefCountedPtr<TestDerivedClass>();\n  EXPECT_TRUE(kaPtr);\n  CHECK_NOTNULL(kaPtr.get())->onlyDerivedFunc();\n  EXPECT_EQ(target.get(), kaPtr.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr);\n  EXPECT_EQ(nullptr, kaPtr.get());\n}\n\nTEST(WeakRefCountedPtrTest, DestroyPtrDerived) {\n  auto target = std::make_unique<StrictMock<TestDerivedClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr = target->getWeakRefCountedPtr<TestDerivedClass>();\n    EXPECT_TRUE(kaPtr);\n    CHECK_NOTNULL(kaPtr.get())->onlyDerivedFunc();\n    kaPtr->onlyDerivedFunc();\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr = target->getWeakRefCountedPtr<TestDerivedClass>();\n    EXPECT_TRUE(kaPtr);\n    CHECK_NOTNULL(kaPtr.get())->onlyDerivedFunc();\n    kaPtr->onlyDerivedFunc();\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, DestroyPtrMulti) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  {\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    auto kaPtr2 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  {\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    auto kaPtr2 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\n// Move construct and assign tests\n\nTEST(WeakRefCountedPtrTest, MoveConstruct) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    WeakRefCountedPtr<TestClass> kaPtr2(std::move(kaPtr1));\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveConstructEmpty) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    WeakRefCountedPtr<TestClass> kaPtr1;\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2(std::move(kaPtr1));\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssign) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    auto kaPtr2 = std::move(kaPtr1);\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignToExistingActivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  {\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    auto kaPtr2 = target->getWeakRefCountedPtr();\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n    kaPtr2 = std::move(kaPtr1);\n    Mock::VerifyAndClearExpectations(target.get());\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignToExistingInactivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    kaPtr2 = std::move(kaPtr1);\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignEmptyToExistingActivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n    kaPtr1 = std::move(kaPtr2);\n    Mock::VerifyAndClearExpectations(target.get());\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignEmptyToExistingInactivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    WeakRefCountedPtr<TestClass> kaPtr1;\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    kaPtr1 = std::move(kaPtr2);\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr1 = target->getWeakRefCountedPtr();\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  auto kaPtr2 = std::move(kaPtr1);\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_FALSE(kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr1);\n  EXPECT_FALSE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, MoveConstructDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr1 = target->getWeakRefCountedPtr();\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  auto kaPtr2(std::move(kaPtr1));\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_FALSE(kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, MoveAssignDestroySrcDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr1 = std::make_unique<WeakRefCountedPtr<TestClass>>(\n      target->getWeakRefCountedPtr());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  WeakRefCountedPtr<TestClass> kaPtr2(std::move(*kaPtr1));\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_FALSE(*kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1->get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  kaPtr1 = nullptr;\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, MoveConstructDestroySrcDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n  auto kaPtr1 = std::make_unique<WeakRefCountedPtr<TestClass>>(\n      target->getWeakRefCountedPtr());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  auto kaPtr2 = std::move(*kaPtr1);\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_FALSE(*kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1->get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  kaPtr1 = nullptr;\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\n// Copy construct and assign tests\n\nTEST(WeakRefCountedPtrTest, CopyConstruct) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  {\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    WeakRefCountedPtr<TestClass> kaPtr2(kaPtr1);\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyConstructEmpty) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    WeakRefCountedPtr<TestClass> kaPtr1;\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2(kaPtr1);\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssign) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    auto kaPtr2 = kaPtr1;\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignToExistingActivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(3);\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n    auto kaPtr2 = target->getWeakRefCountedPtr();\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n    kaPtr2 = kaPtr1;\n    Mock::VerifyAndClearExpectations(target.get());\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignToExistingInactivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  {\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    kaPtr2 = kaPtr1;\n    EXPECT_TRUE(kaPtr1);\n    EXPECT_TRUE(kaPtr2);\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(target.get(), kaPtr2.get());\n    EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy()).Times(2);\n  }\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignEmptyToExistingActivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    EXPECT_CALL(*target, onWeakRefCountedPtrCreate());\n    auto kaPtr1 = target->getWeakRefCountedPtr();\n    EXPECT_EQ(target.get(), kaPtr1.get());\n    EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n    kaPtr1 = kaPtr2;\n    Mock::VerifyAndClearExpectations(target.get());\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignEmptyToExistingInactivePtr) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  {\n    WeakRefCountedPtr<TestClass> kaPtr1;\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n\n    WeakRefCountedPtr<TestClass> kaPtr2;\n    kaPtr2 = kaPtr1;\n    EXPECT_FALSE(kaPtr1);\n    EXPECT_FALSE(kaPtr2);\n    EXPECT_EQ(nullptr, kaPtr1.get());\n    EXPECT_EQ(nullptr, kaPtr2.get());\n    EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n  }\n  EXPECT_EQ(0, target->numWeakRefCountedPtrs());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  auto kaPtr1 = target->getWeakRefCountedPtr();\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  auto kaPtr2 = kaPtr1;\n  EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr1);\n  EXPECT_FALSE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, CopyConstructDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  auto kaPtr1 = target->getWeakRefCountedPtr();\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  auto kaPtr2(kaPtr1);\n  EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr1.get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr1);\n  EXPECT_FALSE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr1.get());\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, CopyAssignDestroySrcDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  auto kaPtr1 = std::make_unique<WeakRefCountedPtr<TestClass>>(\n      target->getWeakRefCountedPtr());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  WeakRefCountedPtr<TestClass> kaPtr2(*kaPtr1);\n  EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(*kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  kaPtr1 = nullptr;\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\nTEST(WeakRefCountedPtrTest, CopyConstructDestroySrcDestroyTarget) {\n  auto target = std::make_unique<StrictMock<TestClass>>();\n  EXPECT_CALL(*target, onWeakRefCountedPtrCreate()).Times(2);\n  auto kaPtr1 = std::make_unique<WeakRefCountedPtr<TestClass>>(\n      target->getWeakRefCountedPtr());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  auto kaPtr2 = *kaPtr1;\n  EXPECT_EQ(2, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(*kaPtr1);\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr1->get());\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  EXPECT_CALL(*target, onWeakRefCountedPtrDestroy());\n  kaPtr1 = nullptr;\n  Mock::VerifyAndClearExpectations(target.get());\n  EXPECT_EQ(1, target->numWeakRefCountedPtrs());\n  EXPECT_TRUE(kaPtr2);\n  EXPECT_EQ(target.get(), kaPtr2.get());\n  target = nullptr;\n  EXPECT_FALSE(kaPtr2);\n  EXPECT_EQ(nullptr, kaPtr2.get());\n}\n\n} // namespace proxygen::test\n"
  },
  {
    "path": "proxygen/lib/utils/test/ZlibTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Random.h>\n#include <folly/ScopeGuard.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/utils/ZlibStreamCompressor.h>\n#include <proxygen/lib/utils/ZlibStreamDecompressor.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\n\nnamespace {\n\nclass ZlibTests : public testing::Test {};\n\nstd::unique_ptr<folly::IOBuf> makeBuf(uint32_t size) {\n  auto out = folly::IOBuf::create(size);\n  out->append(size);\n  // fill with random junk\n  folly::io::RWPrivateCursor cursor(out.get());\n  while (cursor.length() >= 8) {\n    cursor.write<uint64_t>(folly::Random::rand64());\n  }\n  while (cursor.length()) {\n    cursor.write<uint8_t>((uint8_t)folly::Random::rand32());\n  }\n  return out;\n}\n\nvoid verify(CompressionType type,\n            std::unique_ptr<IOBuf> original,\n            std::unique_ptr<IOBuf> compressed) {\n  auto zd = std::make_unique<ZlibStreamDecompressor>(type);\n\n  auto decompressed = zd->decompress(compressed.get());\n  ASSERT_FALSE(zd->hasError()) << \"Decompression error. r=\" << zd->getStatus();\n\n  IOBufEqualTo eq;\n  ASSERT_TRUE(eq(original, decompressed));\n}\n\nvoid compressThenDecompress(CompressionType type,\n                            int level,\n                            unique_ptr<IOBuf> buf) {\n\n  unique_ptr<IOBuf> compressed;\n  unique_ptr<IOBuf> decompressed;\n\n  unique_ptr<ZlibStreamCompressor> zc(new ZlibStreamCompressor(type, level));\n\n  compressed = zc->compress(buf.get(), true);\n  ASSERT_FALSE(zc->hasError()) << \"Compression error. r=\" << zc->getStatus();\n\n  verify(type, std::move(buf), std::move(compressed));\n}\n} // anonymous namespace\n\n// Try many different sizes because we've hit truncation problems before\nTEST_F(ZlibTests, CompressDecompressGzip5000) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 6, makeBuf(5000)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressGzip2000) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 6, makeBuf(2000)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressGzip1024) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 6, makeBuf(1024)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressGzip500) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 6, makeBuf(500)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressGzip50) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 6, makeBuf(50)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressDeflate) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::DEFLATE, 6, makeBuf(500)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressEmpty) {\n  ASSERT_NO_FATAL_FAILURE(\n      { compressThenDecompress(CompressionType::GZIP, 4, makeBuf(0)); });\n}\n\nTEST_F(ZlibTests, CompressDecompressChain) {\n  ASSERT_NO_FATAL_FAILURE({\n    auto buf = makeBuf(4);\n    buf->appendChain(makeBuf(38));\n    buf->appendChain(makeBuf(12));\n    buf->appendChain(makeBuf(0));\n    compressThenDecompress(CompressionType::GZIP, 6, std::move(buf));\n  });\n}\n\nTEST_F(ZlibTests, CompressDecompressStreaming) {\n  ASSERT_NO_FATAL_FAILURE({\n    auto compressor =\n        std::make_unique<ZlibStreamCompressor>(CompressionType::GZIP, 6);\n\n    auto first = makeBuf(38);\n    auto out = compressor->compress(first.get(), false);\n\n    auto second = makeBuf(12);\n    out->prev()->appendChain(compressor->compress(second.get(), false));\n    first->prev()->appendChain(std::move(second));\n\n    auto third = makeBuf(4096); // Larger than buffer size\n    out->prev()->appendChain(compressor->compress(third.get(), false));\n    first->prev()->appendChain(std::move(third));\n\n    auto empty = IOBuf::create(0);\n    out->prev()->appendChain(compressor->compress(empty.get(), true));\n\n    verify(CompressionType::GZIP, std::move(first), std::move(out));\n  });\n}\n\nTEST_F(ZlibTests, CompressDecompressSmallBuffer) {\n  ASSERT_NO_FATAL_FAILURE({\n    auto oldFlag = FLAGS_zlib_compressor_buffer_growth;\n    auto guard = folly::makeGuard(\n        [&] { FLAGS_zlib_compressor_buffer_growth = oldFlag; });\n    // NB: This is picked intentionally so we don't generate multiple\n    // zlib flush markers as ZlibStreamDecompressor fatals on them.\n    FLAGS_zlib_compressor_buffer_growth = 10;\n    compressThenDecompress(CompressionType::GZIP, 4, makeBuf(127));\n  });\n}\n"
  },
  {
    "path": "proxygen/lib/utils/test/ZstdTests.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include <folly/Random.h>\n#include <folly/ScopeGuard.h>\n#include <folly/compression/Compression.h>\n#include <folly/io/Cursor.h>\n#include <folly/io/IOBuf.h>\n#include <folly/portability/GTest.h>\n#include <glog/logging.h>\n#include <proxygen/lib/utils/ZstdStreamCompressor.h>\n#include <proxygen/lib/utils/ZstdStreamDecompressor.h>\n\nusing namespace folly;\nusing namespace proxygen;\nusing namespace std;\nusing namespace testing;\n\nnamespace {\n\nclass ZstdTests : public Test {};\n\nstd::unique_ptr<folly::IOBuf> makeBuf(uint32_t size) {\n  auto out = folly::IOBuf::create(size);\n  out->append(size);\n  // fill with random junk\n  folly::io::RWPrivateCursor cursor(out.get());\n  while (cursor.length() >= 8) {\n    cursor.write<uint64_t>(folly::Random::rand64());\n  }\n  while (cursor.length()) {\n    cursor.write<uint8_t>((uint8_t)folly::Random::rand32());\n  }\n  return out;\n}\n\nvoid verify(std::unique_ptr<IOBuf> original,\n            std::unique_ptr<IOBuf> compressed,\n            bool reuseBuf) {\n  auto zd = std::make_unique<ZstdStreamDecompressor>(reuseBuf);\n\n  auto decompressed = zd->decompress(compressed.get());\n  ASSERT_FALSE(zd->hasError()) << \"Decompression error.\";\n  ASSERT_TRUE(zd->finished());\n\n  IOBufEqualTo eq;\n  ASSERT_TRUE(eq(original, decompressed) ||\n              (original->empty() && decompressed == nullptr));\n}\n\nvoid verifyPieces(std::unique_ptr<IOBuf> original,\n                  std::vector<std::unique_ptr<IOBuf>> compressed,\n                  bool reuseBuf) {\n  auto zd = std::make_unique<ZstdStreamDecompressor>(reuseBuf);\n\n  auto decompressed = folly::IOBuf::create(0);\n\n  for (const auto& piece : compressed) {\n    auto dpiece = zd->decompress(piece.get());\n    ASSERT_FALSE(zd->hasError()) << \"Decompression error.\";\n    if (dpiece != nullptr) {\n      decompressed->prev()->appendChain(std::move(dpiece));\n    }\n  }\n\n  ASSERT_TRUE(zd->finished());\n\n  IOBufEqualTo eq;\n  ASSERT_TRUE(eq(original, decompressed));\n}\n\nstd::unique_ptr<folly::IOBuf> compress(\n    const std::unique_ptr<folly::IOBuf>& in) {\n  auto codec = std::make_unique<ZstdStreamCompressor>(\n      folly::compression::COMPRESSION_LEVEL_DEFAULT);\n  auto out = codec->compress(in.get());\n  return out;\n}\n\nvoid compressThenDecompress(unique_ptr<IOBuf> buf, bool reuseBuf) {\n  auto compressed = compress(buf);\n  verify(std::move(buf), std::move(compressed), reuseBuf);\n}\n\nvoid compressThenDecompressPieces(\n    std::vector<std::unique_ptr<folly::IOBuf>> input_pieces, bool reuseBuf) {\n  auto codec =\n      folly::compression::getStreamCodec(folly::compression::CodecType::ZSTD);\n\n  std::vector<std::unique_ptr<folly::IOBuf>> compressed_pieces;\n\n  size_t i = 0;\n  for (const auto& piece : input_pieces) {\n    const auto end = ++i == input_pieces.size();\n    const auto op = end ? folly::compression::StreamCodec::FlushOp::END\n                        : folly::compression::StreamCodec::FlushOp::FLUSH;\n    auto irange = piece->coalesce();\n    if (!end && irange.size() == 0) {\n      continue;\n    }\n    bool done;\n    do {\n      auto cpiece = folly::IOBuf::create(ZSTD_compressBound(irange.size()));\n      folly::MutableByteRange crange(cpiece->writableData(),\n                                     cpiece->tailroom());\n      done = codec->compressStream(irange, crange, op);\n      cpiece->append(crange.begin() - cpiece->tail());\n      compressed_pieces.push_back(std::move(cpiece));\n    } while (irange.size() || !done);\n  }\n\n  // assembles from back to front\n  auto input = folly::IOBuf::create(0);\n  while (!input_pieces.empty()) {\n    input->appendChain(std::move(input_pieces.back()));\n    input_pieces.pop_back();\n  }\n\n  verifyPieces(std::move(input), std::move(compressed_pieces), reuseBuf);\n}\n\nvoid compressDecompressPiecesProxygenCodec(\n    std::vector<std::unique_ptr<folly::IOBuf>> input_pieces,\n    bool independent,\n    bool reuseBuf) {\n  auto codec = std::make_unique<ZstdStreamCompressor>(\n      folly::compression::COMPRESSION_LEVEL_DEFAULT, independent);\n\n  std::vector<std::unique_ptr<folly::IOBuf>> compressed_pieces;\n\n  size_t i = 0;\n  for (const auto& piece : input_pieces) {\n    const auto end = ++i == input_pieces.size();\n    compressed_pieces.push_back(codec->compress(piece.get(), end));\n  }\n\n  // assembles from back to front\n  auto input = folly::IOBuf::create(0);\n  while (!input_pieces.empty()) {\n    input->appendChain(std::move(input_pieces.back()));\n    input_pieces.pop_back();\n  }\n\n  verifyPieces(std::move(input), std::move(compressed_pieces), reuseBuf);\n}\n\n} // anonymous namespace\n\n// Try many different sizes because we've hit truncation problems before\nTEST_F(ZstdTests, CompressDecompress1M) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE(\n        { compressThenDecompress(makeBuf(8 * 128 * 1024), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompress2000) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE(\n        { compressThenDecompress(makeBuf(2000), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompress1024) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE(\n        { compressThenDecompress(makeBuf(1024), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompress500) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE(\n        { compressThenDecompress(makeBuf(500), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompress50) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE({ compressThenDecompress(makeBuf(50), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompressEmpty) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE({ compressThenDecompress(makeBuf(0), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompressChain) {\n  for (bool reuseBuf : {false, true}) {\n    ASSERT_NO_FATAL_FAILURE({\n      auto buf = makeBuf(4);\n      buf->prependChain(makeBuf(38));\n      buf->prependChain(makeBuf(12));\n      buf->prependChain(makeBuf(0));\n      compressThenDecompress(std::move(buf), reuseBuf);\n    });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompressStreaming) {\n  for (bool reuseBuf : {false, true}) {\n    std::vector<std::unique_ptr<folly::IOBuf>> input_pieces;\n    input_pieces.push_back(makeBuf(38));\n    input_pieces.push_back(makeBuf(12));\n    input_pieces.push_back(makeBuf(4096));\n    input_pieces.push_back(makeBuf(0));\n\n    ASSERT_NO_FATAL_FAILURE(\n        { compressThenDecompressPieces(std::move(input_pieces), reuseBuf); });\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompressStreamingProxygen) {\n  for (bool reuseBuf : {false, true}) {\n    std::vector<std::unique_ptr<folly::IOBuf>> input_pieces;\n    input_pieces.push_back(makeBuf(38));\n    input_pieces.push_back(makeBuf(12));\n    input_pieces.push_back(makeBuf(4096));\n    input_pieces.push_back(makeBuf(0));\n\n    compressDecompressPiecesProxygenCodec(\n        std::move(input_pieces), false, reuseBuf);\n  }\n}\n\nTEST_F(ZstdTests, CompressDecompressStreamingProxygenIndependent) {\n  for (bool reuseBuf : {false, true}) {\n    std::vector<std::unique_ptr<folly::IOBuf>> input_pieces;\n    input_pieces.push_back(makeBuf(38));\n    input_pieces.push_back(makeBuf(12));\n    input_pieces.push_back(makeBuf(4096));\n    input_pieces.push_back(makeBuf(0));\n\n    compressDecompressPiecesProxygenCodec(\n        std::move(input_pieces), true, reuseBuf);\n  }\n}\n\n// ---- maxDecompressionRatio tests ----\n\n// Helper: create a highly compressible buffer (all zeros).\nstatic std::unique_ptr<folly::IOBuf> makeCompressibleBuf(size_t size) {\n  auto buf = folly::IOBuf::create(size);\n  buf->append(size);\n  ::memset(buf->writableData(), 0, size);\n  return buf;\n}\n\nTEST_F(ZstdTests, MaxDecompressionRatio_FailsWhenRatioTooSmall) {\n  // 1 MB of zeros compresses extremely well; a ratio of 10 should be exceeded.\n  constexpr size_t kOrigSize = 1024 * 1024;\n  auto original = makeCompressibleBuf(kOrigSize);\n  auto compressed = compress(original);\n  ASSERT_NE(compressed, nullptr);\n\n  auto zd = std::make_unique<ZstdStreamDecompressor>(\n      /*reuseOutBuf=*/false, /*maxDecompressionRatio=*/10);\n\n  auto decompressed = zd->decompress(compressed.get());\n  EXPECT_TRUE(zd->hasError());\n  EXPECT_EQ(decompressed, nullptr);\n}\n\nTEST_F(ZstdTests, MaxDecompressionRatio_PassesWhenRatioLargeEnough) {\n  constexpr size_t kOrigSize = 1024 * 1024;\n  auto original = makeCompressibleBuf(kOrigSize);\n  auto compressed = compress(original);\n  ASSERT_NE(compressed, nullptr);\n\n  // Use a very large ratio so decompression succeeds.\n  auto zd = std::make_unique<ZstdStreamDecompressor>(\n      /*reuseOutBuf=*/false, /*maxDecompressionRatio=*/1000000);\n\n  auto decompressed = zd->decompress(compressed.get());\n  EXPECT_FALSE(zd->hasError());\n  EXPECT_NE(decompressed, nullptr);\n  EXPECT_EQ(decompressed->computeChainDataLength(), kOrigSize);\n}\n\nTEST_F(ZstdTests, MaxDecompressionRatio_PassesWhenUnspecified) {\n  constexpr size_t kOrigSize = 1024 * 1024;\n  auto original = makeCompressibleBuf(kOrigSize);\n  auto compressed = compress(original);\n  ASSERT_NE(compressed, nullptr);\n\n  // Default: no ratio limit.\n  auto zd = std::make_unique<ZstdStreamDecompressor>(/*reuseOutBuf=*/false);\n\n  auto decompressed = zd->decompress(compressed.get());\n  EXPECT_FALSE(zd->hasError());\n  EXPECT_NE(decompressed, nullptr);\n  EXPECT_EQ(decompressed->computeChainDataLength(), kOrigSize);\n}\n\nTEST_F(ZstdTests, MaxDecompressionRatio_StreamingChunksFailure) {\n  // Feed compressed data in small pieces; cumulative ratio should still\n  // trigger.\n  constexpr size_t kOrigSize = 1024 * 1024;\n  auto original = makeCompressibleBuf(kOrigSize);\n  auto compressed = compress(original);\n  ASSERT_NE(compressed, nullptr);\n\n  auto zd = std::make_unique<ZstdStreamDecompressor>(\n      /*reuseOutBuf=*/false, /*maxDecompressionRatio=*/10);\n\n  // Split compressed data into small chunks.\n  auto coalesced = compressed->coalesce();\n  auto totalLen = compressed->computeChainDataLength();\n  constexpr size_t kChunkSize = 128;\n\n  bool errorDetected = false;\n  for (size_t offset = 0; offset < totalLen; offset += kChunkSize) {\n    auto chunkLen = std::min(kChunkSize, totalLen - offset);\n    auto chunk = folly::IOBuf::wrapBuffer(coalesced.data() + offset, chunkLen);\n    auto piece = zd->decompress(chunk.get());\n    if (zd->hasError()) {\n      errorDetected = true;\n      break;\n    }\n  }\n  EXPECT_TRUE(errorDetected);\n}\n"
  }
]